验收四路模型并优化拓扑求解性能

This commit is contained in:
huojiarong committed 2026-08-12 11:57:42 +00:00
1 parent caca32a513
commit 456c29b3b6
20 files changed
+1085 -137

No files matched your search

+106 -13
View File
@@ -1,14 +1,17 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from dataclasses import dataclass, replace
from math import floor, isfinite
from typing import Literal
from app.simulation.core.base import DynamicComponent
from app.simulation.core.metadata import ResultVariableMetadata
from app.simulation.solvers.algebraic import PressureFlowSolver
from app.simulation.solvers.mechanical import MechanicalStateReducer
from app.simulation.solvers.mechanical import (
MechanicalConstraintGroup,
MechanicalStateReducer,
)
from app.simulation.solvers.pneumatic_storage import (
IdealPneumaticStorageReducer,
ideal_storage_group_is_reducible,
@@ -267,6 +270,7 @@ class GenericFluidSystem:
self.max_thermofluid_iterations = 0
self.signal_propagation_count = 0
self.pneumatic_volume_propagation_count = 0
self._jacobian_sparsity = None
def initial_state_vector(self) -> list[float]:
return self.pneumatic_storage_reducer.synchronize_state_vector(
@@ -279,20 +283,92 @@ class GenericFluidSystem:
self.pneumatic_storage_reducer.synchronize_state_vector(values)
)
def _build_jacobian_sparsity(self):
"""Build a conservative state dependency graph for implicit solvers.
Two state entries are coupled when a physical path connects them without
crossing a third storage state. This over-approximates the local
pressure-flow/mechanical closure while preserving branch sparsity.
"""
from scipy.sparse import lil_matrix
entries = self.mechanical_state_reducer.state_entries
entry_components: list[set[str]] = []
entry_sizes: list[int] = []
for entry in entries:
if isinstance(entry, MechanicalConstraintGroup):
entry_components.append(
{component.name for component in entry.components}
)
entry_sizes.append(2)
else:
entry_components.append({entry.name})
entry_sizes.append(entry.state_size)
owner_by_component = {
component_name: entry_index
for entry_index, component_names in enumerate(entry_components)
for component_name in component_names
}
adjacency = {name: set() for name in self.network.components}
for connection in self.network.connections:
if connection.kind != "physical":
continue
first, second = connection.endpoints
adjacency[first.component].add(second.component)
adjacency[second.component].add(first.component)
dependencies: list[set[int]] = []
for entry_index, component_names in enumerate(entry_components):
visited = set(component_names)
pending = list(component_names)
found = {entry_index}
while pending:
current = pending.pop()
for neighbour in adjacency[current] - visited:
visited.add(neighbour)
neighbour_entry = owner_by_component.get(neighbour)
if (
neighbour_entry is not None
and neighbour_entry != entry_index
):
found.add(neighbour_entry)
else:
pending.append(neighbour)
dependencies.append(found)
offsets = [0]
for state_size in entry_sizes:
offsets.append(offsets[-1] + state_size)
sparsity = lil_matrix(
(offsets[-1], offsets[-1]),
dtype=bool,
)
for row_entry, column_entries in enumerate(dependencies):
for column_entry in column_entries:
sparsity[
offsets[row_entry] : offsets[row_entry + 1],
offsets[column_entry] : offsets[column_entry + 1],
] = True
return sparsity.tocsr()
def jacobian_sparsity(self):
if self._jacobian_sparsity is None:
self._jacobian_sparsity = self._build_jacobian_sparsity()
return self._jacobian_sparsity
def _close_current_state(self, time: float) -> dict[str, dict[str, float]]:
signal = self.signal_resolver.solve(time)
self.signal_propagation_count += signal.propagated
for component in self.dynamic_components:
component.refresh_thermodynamic_ports()
algebraic = self.pressure_flow_solver.solve()
pressure_flow_solve_count = 1
self.pressure_flow_solver.propagate_equal_efforts(("x", "v"))
pneumatic_volume = self.pneumatic_volume_resolver.solve()
self.pneumatic_volume_propagation_count += pneumatic_volume.propagated
if pneumatic_volume.propagated:
for component in self.dynamic_components:
component.refresh_thermodynamic_ports()
algebraic = self.pressure_flow_solver.solve()
pressure_flow_solve_count += 1
for component in self.dynamic_components:
component.refresh_thermodynamic_ports()
algebraic = self.pressure_flow_solver.solve(
effort_variables=("p",),
)
pressure_flow_solve_count = 1
# Some constitutive flow laws recover their upstream temperature from
# connected stream enthalpy, while junction stream mixing itself depends
@@ -321,7 +397,11 @@ class GenericFluidSystem:
component.update_flow_temperature_references(
temperature_reference_h[component.name]
)
algebraic = self.pressure_flow_solver.solve()
algebraic = self.pressure_flow_solver.solve(
effort_variables=(
("p",) if pressure_flow_solve_count == 0 else ()
),
)
pressure_flow_solve_count += 1
current_flows = tuple(port.m_flow for port in physical_ports)
flow_scale = max(
@@ -415,6 +495,14 @@ class GenericFluidSystem:
progress_callback(last_reported_progress, phase)
report_progress(0.0, "initializing", force=True)
integration_config = config
if isinstance(config.atol, (int, float)):
integration_config = replace(
config,
atol=self.mechanical_state_reducer.absolute_tolerances(
float(config.atol)
),
)
t_eval = simulation_sample_times(config, sample_step)
signal_event_times = self.signal_resolver.event_times(
config.t_start,
@@ -443,7 +531,7 @@ class GenericFluidSystem:
solution = integrate_ode(
rhs=monitored_rhs,
initial_state=initial_state,
config=config,
config=integration_config,
t_eval=t_eval,
cancel_check=cancel_check,
accepted_step_callback=(
@@ -455,6 +543,11 @@ class GenericFluidSystem:
if self.mechanical_state_reducer.has_state_events
else None
),
jac_sparsity=(
self.jacobian_sparsity()
if integration_config.method in {"BDF", "Radau"}
else None
),
)
if isinstance(solution, ODESolution):
run_status: SimulationRunStatus = solution.status