优化仿真求解性能并修复流量闭合问题(初版)

This commit is contained in:
ljz committed 2026-08-16 17:46:05 +08:00
1 parent 57b459bc72
commit 5332a788f3
55 files changed
+8973 -549

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+45 -19
View File
@@ -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(
+54 -20
View File
@@ -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),
)
+6 -2
View File
@@ -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)
+74 -45
View File
@@ -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])