优化仿真求解性能并修复流量闭合问题(初版)
This commit is contained in:
1 parent
57b459bc72
commit
5332a788f3
55 files changed
+8973
-549
No files matched your search
+1126
-38
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from math import isfinite
|
||||
|
||||
from app.simulation.core.base import Component
|
||||
from app.simulation.core.ports import PortState
|
||||
from app.simulation.performance import profile_phase
|
||||
from app.simulation.systems.network import Endpoint, SimulationNetwork
|
||||
|
||||
@@ -19,35 +21,62 @@ class PneumaticVolumeDiagnostics:
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _PneumaticVolumeConnectionBinding:
|
||||
connected_endpoint: Endpoint
|
||||
connected_port: PortState
|
||||
|
||||
|
||||
class PneumaticVolumeResolver:
|
||||
"""Propagate AMESim pneumatic external-volume connector variables."""
|
||||
|
||||
def __init__(self, network: SimulationNetwork) -> None:
|
||||
self.network = network
|
||||
self._pneumatic_ports = tuple(
|
||||
component.get_port(definition.name)
|
||||
for component in network.components.values()
|
||||
for definition in component.active_port_definitions
|
||||
if definition.kind == "physical" and definition.domain == "pneumatic"
|
||||
)
|
||||
self._output_components = tuple(
|
||||
component
|
||||
for component in network.components.values()
|
||||
if type(component).pneumatic_volume_outputs
|
||||
is not Component.pneumatic_volume_outputs
|
||||
)
|
||||
self._connected_endpoint = self._build_connection_map()
|
||||
self.last_diagnostics: PneumaticVolumeDiagnostics | None = None
|
||||
|
||||
def _build_connection_map(self) -> dict[Endpoint, Endpoint]:
|
||||
result: dict[Endpoint, Endpoint] = {}
|
||||
def _build_connection_map(
|
||||
self,
|
||||
) -> dict[Endpoint, _PneumaticVolumeConnectionBinding]:
|
||||
result: dict[Endpoint, _PneumaticVolumeConnectionBinding] = {}
|
||||
for connection in self.network.connections:
|
||||
if connection.kind != "physical" or connection.domain != "pneumatic":
|
||||
continue
|
||||
first, second = connection.endpoints
|
||||
result[first] = second
|
||||
result[second] = first
|
||||
result[first] = _PneumaticVolumeConnectionBinding(
|
||||
connected_endpoint=second,
|
||||
connected_port=self.network.components[second.component].get_port(
|
||||
second.port
|
||||
),
|
||||
)
|
||||
result[second] = _PneumaticVolumeConnectionBinding(
|
||||
connected_endpoint=first,
|
||||
connected_port=self.network.components[first.component].get_port(
|
||||
first.port
|
||||
),
|
||||
)
|
||||
return result
|
||||
|
||||
@profile_phase("simulation.pneumatic_volume", minimum_mode="audit")
|
||||
def solve(self) -> PneumaticVolumeDiagnostics:
|
||||
for component in self.network.components.values():
|
||||
for definition in component.active_port_definitions:
|
||||
if definition.kind == "physical" and definition.domain == "pneumatic":
|
||||
port = component.get_port(definition.name)
|
||||
port.volume = 0.0
|
||||
port.volume_flow = 0.0
|
||||
for port in self._pneumatic_ports:
|
||||
port.volume = 0.0
|
||||
port.volume_flow = 0.0
|
||||
|
||||
outputs: dict[Endpoint, tuple[float, float]] = {}
|
||||
for component in self.network.components.values():
|
||||
for component in self._output_components:
|
||||
for port_name, raw_values in component.pneumatic_volume_outputs().items():
|
||||
port = component.get_port(port_name)
|
||||
definition = port.definition
|
||||
@@ -73,18 +102,15 @@ class PneumaticVolumeResolver:
|
||||
|
||||
propagated = 0
|
||||
for endpoint, values in outputs.items():
|
||||
connected = self._connected_endpoint.get(endpoint)
|
||||
if connected is None:
|
||||
binding = self._connected_endpoint.get(endpoint)
|
||||
if binding is None:
|
||||
continue
|
||||
if connected in outputs:
|
||||
if binding.connected_endpoint in outputs:
|
||||
raise ValueError(
|
||||
"A pneumatic connection cannot contain two external-volume "
|
||||
f"sources: {endpoint} and {connected}."
|
||||
f"sources: {endpoint} and {binding.connected_endpoint}."
|
||||
)
|
||||
connected_port = self.network.components[connected.component].get_port(
|
||||
connected.port
|
||||
)
|
||||
connected_port.volume, connected_port.volume_flow = values
|
||||
binding.connected_port.volume, binding.connected_port.volume_flow = values
|
||||
propagated += 1
|
||||
|
||||
diagnostics = PneumaticVolumeDiagnostics(
|
||||
|
||||
@@ -2,8 +2,10 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from math import isfinite
|
||||
from typing import Protocol
|
||||
from typing import Callable, Protocol
|
||||
|
||||
from app.simulation.core.base import Component
|
||||
from app.simulation.core.ports import PortState
|
||||
from app.simulation.performance import profile_phase
|
||||
from app.simulation.systems.network import Endpoint, SimulationNetwork
|
||||
|
||||
@@ -38,31 +40,56 @@ class SignalSolveDiagnostics:
|
||||
return {"propagated": self.propagated}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _SignalOutputBinding:
|
||||
component: Component
|
||||
evaluate: Callable[[float], dict[str, float]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _SignalConnectionBinding:
|
||||
source: PortState
|
||||
target: PortState
|
||||
|
||||
|
||||
class SignalResolver:
|
||||
"""Propagate scalar signal connections from output ports to input ports."""
|
||||
|
||||
def __init__(self, network: SimulationNetwork) -> None:
|
||||
self.network = network
|
||||
self._connections = [
|
||||
connection for connection in network.connections if connection.kind == "signal"
|
||||
]
|
||||
self._output_bindings = tuple(
|
||||
_SignalOutputBinding(component=component, evaluate=evaluate)
|
||||
for component in network.components.values()
|
||||
if (evaluate := getattr(component, "signal_output_values", None)) is not None
|
||||
)
|
||||
self._event_sources = tuple(
|
||||
(component.name, source_event_times)
|
||||
for component in network.components.values()
|
||||
if (
|
||||
source_event_times := getattr(
|
||||
component,
|
||||
"signal_event_times",
|
||||
None,
|
||||
)
|
||||
)
|
||||
is not None
|
||||
)
|
||||
self._connections = tuple(
|
||||
self._connection_binding(connection.endpoints)
|
||||
for connection in network.connections
|
||||
if connection.kind == "signal"
|
||||
)
|
||||
self.last_diagnostics: SignalSolveDiagnostics | None = None
|
||||
|
||||
@profile_phase("simulation.signal", minimum_mode="audit")
|
||||
def solve(self, time: float) -> SignalSolveDiagnostics:
|
||||
for component in self.network.components.values():
|
||||
signal_output_values = getattr(component, "signal_output_values", None)
|
||||
if signal_output_values is None:
|
||||
continue
|
||||
for port_name, value in signal_output_values(time).items():
|
||||
component.get_port(port_name).signal = float(value)
|
||||
for binding in self._output_bindings:
|
||||
for port_name, value in binding.evaluate(time).items():
|
||||
binding.component.get_port(port_name).signal = float(value)
|
||||
|
||||
propagated = 0
|
||||
for connection in self._connections:
|
||||
source, target = self._source_target(connection.endpoints)
|
||||
source_port = self.network.components[source.component].get_port(source.port)
|
||||
target_port = self.network.components[target.component].get_port(target.port)
|
||||
target_port.signal = source_port.signal
|
||||
for binding in self._connections:
|
||||
binding.target.signal = binding.source.signal
|
||||
propagated += 1
|
||||
|
||||
diagnostics = SignalSolveDiagnostics(propagated=propagated)
|
||||
@@ -86,15 +113,12 @@ class SignalResolver:
|
||||
return ()
|
||||
|
||||
events: set[float] = set()
|
||||
for component in self.network.components.values():
|
||||
source_event_times = getattr(component, "signal_event_times", None)
|
||||
if source_event_times is None:
|
||||
continue
|
||||
for component_name, source_event_times in self._event_sources:
|
||||
for raw_time in source_event_times(start, stop):
|
||||
event_time = float(raw_time)
|
||||
if not isfinite(event_time):
|
||||
raise ValueError(
|
||||
f"Signal event time from component '{component.name}' must be finite."
|
||||
f"Signal event time from component '{component_name}' must be finite."
|
||||
)
|
||||
if start < event_time < stop:
|
||||
events.add(event_time)
|
||||
@@ -109,3 +133,13 @@ class SignalResolver:
|
||||
if second_port.definition is not None and second_port.definition.nominal_role == "output":
|
||||
return second, first
|
||||
raise ValueError("Signal connection must contain one output endpoint.")
|
||||
|
||||
def _connection_binding(
|
||||
self,
|
||||
endpoints: tuple[Endpoint, Endpoint],
|
||||
) -> _SignalConnectionBinding:
|
||||
source, target = self._source_target(endpoints)
|
||||
return _SignalConnectionBinding(
|
||||
source=self.network.components[source.component].get_port(source.port),
|
||||
target=self.network.components[target.component].get_port(target.port),
|
||||
)
|
||||
@@ -807,9 +807,13 @@ def _integrate_scipy_stepwise(
|
||||
segment_accepted_steps += 1
|
||||
step_end_time = float(solver.t)
|
||||
step_end_state = [float(value) for value in solver.y]
|
||||
crosses_sample = (
|
||||
sample_index < len(sample_times)
|
||||
and sample_times[sample_index] <= step_end_time
|
||||
)
|
||||
dense_output = (
|
||||
solver.dense_output()
|
||||
if sample_times or state_transition_handler is not None
|
||||
if crosses_sample or state_transition_handler is not None
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -918,11 +922,11 @@ def _integrate_scipy_stepwise(
|
||||
else last_accepted_time
|
||||
)
|
||||
if sample_times:
|
||||
assert dense_output is not None
|
||||
while (
|
||||
sample_index < len(sample_times)
|
||||
and sample_times[sample_index] <= last_accepted_time
|
||||
):
|
||||
assert dense_output is not None
|
||||
sample_time = float(sample_times[sample_index])
|
||||
sample_state = [
|
||||
float(value) for value in dense_output(sample_time)
|
||||
|
||||
@@ -2,9 +2,10 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.simulation.core.base import DynamicComponent
|
||||
from app.simulation.core.base import Component, DynamicComponent
|
||||
from app.simulation.core.ports import PortState
|
||||
from app.simulation.performance import profile_phase
|
||||
from app.simulation.systems.network import Endpoint, SimulationNetwork
|
||||
from app.simulation.systems.network import SimulationNetwork
|
||||
|
||||
|
||||
class StreamSolveError(RuntimeError):
|
||||
@@ -27,6 +28,14 @@ class StreamSolveDiagnostics:
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _StreamConnectionBinding:
|
||||
component_name: str
|
||||
port_name: str
|
||||
connected_component: Component
|
||||
connected_port: PortState
|
||||
|
||||
|
||||
class StreamResolver:
|
||||
"""Resolve outflow enthalpy propagation after pressure and flow are known."""
|
||||
|
||||
@@ -40,28 +49,59 @@ class StreamResolver:
|
||||
self.network = network
|
||||
self.relative_tolerance = relative_tolerance
|
||||
self.max_iterations = max_iterations
|
||||
self._connected_endpoint = self._build_connection_map()
|
||||
self._components = tuple(network.components.values())
|
||||
self._dynamic_components = tuple(
|
||||
component
|
||||
for component in self._components
|
||||
if isinstance(component, DynamicComponent)
|
||||
)
|
||||
self._non_dynamic_components = tuple(
|
||||
component
|
||||
for component in self._components
|
||||
if not isinstance(component, DynamicComponent)
|
||||
)
|
||||
self._ports = tuple(
|
||||
(component.name, port_name, port)
|
||||
for component in self._components
|
||||
for port_name, port in component.ports.items()
|
||||
)
|
||||
self._connection_bindings = self._build_connection_bindings()
|
||||
self.last_diagnostics: StreamSolveDiagnostics | None = None
|
||||
|
||||
def _build_connection_map(self) -> dict[Endpoint, Endpoint]:
|
||||
result: dict[Endpoint, Endpoint] = {}
|
||||
def _build_connection_bindings(self) -> tuple[_StreamConnectionBinding, ...]:
|
||||
result: list[_StreamConnectionBinding] = []
|
||||
for connection in self.network.connections:
|
||||
if connection.kind != "physical":
|
||||
continue
|
||||
first, second = connection.endpoints
|
||||
result[first] = second
|
||||
result[second] = first
|
||||
return result
|
||||
first_component = self.network.components[first.component]
|
||||
second_component = self.network.components[second.component]
|
||||
result.append(
|
||||
_StreamConnectionBinding(
|
||||
component_name=first.component,
|
||||
port_name=first.port,
|
||||
connected_component=second_component,
|
||||
connected_port=second_component.get_port(second.port),
|
||||
)
|
||||
)
|
||||
result.append(
|
||||
_StreamConnectionBinding(
|
||||
component_name=second.component,
|
||||
port_name=second.port,
|
||||
connected_component=first_component,
|
||||
connected_port=first_component.get_port(first.port),
|
||||
)
|
||||
)
|
||||
return tuple(result)
|
||||
|
||||
def connected_enthalpies(self) -> dict[str, dict[str, float]]:
|
||||
values: dict[str, dict[str, float]] = {
|
||||
component.name: {} for component in self.network.components.values()
|
||||
component.name: {} for component in self._components
|
||||
}
|
||||
for endpoint, connected in self._connected_endpoint.items():
|
||||
connected_port = self.network.components[connected.component].get_port(
|
||||
connected.port
|
||||
for binding in self._connection_bindings:
|
||||
values[binding.component_name][binding.port_name] = (
|
||||
binding.connected_port.h_outflow
|
||||
)
|
||||
values[endpoint.component][endpoint.port] = connected_port.h_outflow
|
||||
return values
|
||||
|
||||
def connected_temperature_reference_enthalpies(
|
||||
@@ -70,26 +110,21 @@ class StreamResolver:
|
||||
"""Return connector references used for upstream temperature only."""
|
||||
|
||||
values: dict[str, dict[str, float]] = {
|
||||
component.name: {} for component in self.network.components.values()
|
||||
component.name: {} for component in self._components
|
||||
}
|
||||
for endpoint, connected in self._connected_endpoint.items():
|
||||
connected_component = self.network.components[connected.component]
|
||||
connected_port = connected_component.get_port(connected.port)
|
||||
values[endpoint.component][endpoint.port] = float(
|
||||
for binding in self._connection_bindings:
|
||||
values[binding.component_name][binding.port_name] = float(
|
||||
getattr(
|
||||
connected_component,
|
||||
binding.connected_component,
|
||||
"temperature_reference_h",
|
||||
connected_port.h_outflow,
|
||||
binding.connected_port.h_outflow,
|
||||
)
|
||||
)
|
||||
return values
|
||||
|
||||
@profile_phase("simulation.refresh", minimum_mode="audit")
|
||||
def _refresh_dynamic_components(
|
||||
self,
|
||||
components: list[DynamicComponent],
|
||||
) -> None:
|
||||
for component in components:
|
||||
def _refresh_dynamic_components(self) -> None:
|
||||
for component in self._dynamic_components:
|
||||
component.refresh_thermodynamic_ports()
|
||||
|
||||
@profile_phase("simulation.refresh", minimum_mode="audit")
|
||||
@@ -97,40 +132,34 @@ class StreamResolver:
|
||||
self,
|
||||
connected: dict[str, dict[str, float]],
|
||||
) -> None:
|
||||
for component in self.network.components.values():
|
||||
if isinstance(component, DynamicComponent):
|
||||
component.refresh_thermodynamic_ports()
|
||||
else:
|
||||
component.update_stream_outflows(connected[component.name])
|
||||
for component in self._non_dynamic_components:
|
||||
component.update_stream_outflows(connected[component.name])
|
||||
|
||||
@profile_phase("simulation.stream", minimum_mode="audit")
|
||||
def solve(self) -> tuple[StreamSolveDiagnostics, dict[str, dict[str, float]]]:
|
||||
dynamic_components = [
|
||||
component
|
||||
for component in self.network.components.values()
|
||||
if isinstance(component, DynamicComponent)
|
||||
]
|
||||
self._refresh_dynamic_components(dynamic_components)
|
||||
def solve(
|
||||
self,
|
||||
*,
|
||||
dynamic_ports_are_current: bool = False,
|
||||
) -> tuple[StreamSolveDiagnostics, dict[str, dict[str, float]]]:
|
||||
if not dynamic_ports_are_current:
|
||||
self._refresh_dynamic_components()
|
||||
|
||||
max_delta = 0.0
|
||||
for iteration in range(1, self.max_iterations + 1):
|
||||
previous = {
|
||||
(component.name, port_name): port.h_outflow
|
||||
for component in self.network.components.values()
|
||||
for port_name, port in component.ports.items()
|
||||
(component_name, port_name): port.h_outflow
|
||||
for component_name, port_name, port in self._ports
|
||||
}
|
||||
connected = self.connected_enthalpies()
|
||||
self._refresh_stream_components(connected)
|
||||
|
||||
deltas = [
|
||||
abs(port.h_outflow - previous[(component.name, port_name)])
|
||||
for component in self.network.components.values()
|
||||
for port_name, port in component.ports.items()
|
||||
abs(port.h_outflow - previous[(component_name, port_name)])
|
||||
for component_name, port_name, port in self._ports
|
||||
]
|
||||
magnitudes = [
|
||||
abs(port.h_outflow)
|
||||
for component in self.network.components.values()
|
||||
for port in component.ports.values()
|
||||
for _component_name, _port_name, port in self._ports
|
||||
]
|
||||
max_delta = max(deltas, default=0.0)
|
||||
scale = max(magnitudes + [1.0])
|
||||
|
||||
Reference in new issue
Block a user