验收四路模型并优化拓扑求解性能
This commit is contained in:
1 parent
caca32a513
commit
456c29b3b6
20 files changed
+1085
-137
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user