Files
SystemSimulationApp/tests/test_pressure_flow_solver_initialization.py
T
ljz 971e8f2336 初版:实现 AMESim 机械因果化与事件求解
初步支持 MECMAS21 刚性质量状态归并、端止事件、恢复系数,以及 LSTP 接触和压力流量显式因果化。

已知问题:显式传播仍会重复扫描全网方程,长时刚性仿真性能待优化;自适应积分器遇到越出物理域的试探状态时,尚未实现恢复并缩步重试。
2026-08-03 15:45:48 +08:00

199 lines
7.4 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from app.simulation.components.amesim.boundary.sources import AmesimPnpl01
from app.simulation.components.amesim.flow.orifices import (
AmesimPnvo001SignalOpening,
)
from app.simulation.components.amesim.media.mediums import (
AmesimHeliumPengRobinsonMedium,
)
from app.simulation.components.experimental.storage.cylinder import Cylinder
from app.simulation.components.experimental.storage.tank import Tank
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.state import VolumeState
from app.simulation.solvers.algebraic import AlgebraicSolveError, PressureFlowSolver
from app.simulation.systems.network import SimulationNetwork
class PressureFlowSolverInitializationTests(unittest.TestCase):
@staticmethod
def _near_equal_pressure_network() -> tuple[
SimulationNetwork,
AmesimHeliumPengRobinsonMedium,
Cylinder,
Tank,
AmesimPnvo001SignalOpening,
]:
medium = AmesimHeliumPengRobinsonMedium()
high = Cylinder("high", medium, V=0.057, p0=1.0e5, T0=256.1)
low = Tank("low", medium, V=0.015, p0=1.0e5, T0=298.2)
valve = AmesimPnvo001SignalOpening(
"valve",
medium,
cq=0.45,
area0=7.85e-5,
gi=1.0,
flowset=1.0,
opening0=1.0,
)
valve.res.signal = 1.0
network = SimulationNetwork("near-equal-pressure")
for component in (high, low, valve):
network.add_component(component)
network.connect("high", "port_b", "valve", "port_2")
network.connect("valve", "port_3", "low", "port_a")
return network, medium, high, low, valve
@staticmethod
def _set_pressure_temperature(
component: Cylinder | Tank,
medium: AmesimHeliumPengRobinsonMedium,
pressure: float,
temperature: float,
) -> None:
mass = medium.density(pressure, temperature) * component.V
component.state = VolumeState(
m=mass,
U=mass * medium.specific_internal_energy(temperature),
)
component.refresh_thermodynamic_ports()
def test_current_storage_pressure_reseeds_stale_orifice_ports_and_flow(self) -> None:
network, medium, high, low, valve = self._near_equal_pressure_network()
solver = PressureFlowSolver(network, max_evaluations=10)
high_pressure = 10_790_000.0
self._set_pressure_temperature(high, medium, high_pressure, 256.1)
self._set_pressure_temperature(low, medium, high_pressure - 300.0, 298.2)
initial = solver.solve()
self.assertTrue(initial.success)
stale_low_pressure = valve.port_3.p
stale_flow = valve.port_2.m_flow
self._set_pressure_temperature(low, medium, high_pressure - 100.0, 298.2)
self.assertNotAlmostEqual(low.port_a.p, stale_low_pressure, places=3)
updated = solver.solve()
self.assertTrue(updated.success)
self.assertEqual(updated.evaluations, 0)
self.assertAlmostEqual(valve.port_2.p, high.port_b.p, places=6)
self.assertAlmostEqual(valve.port_3.p, low.port_a.p, places=6)
self.assertNotAlmostEqual(valve.port_2.m_flow, stale_flow, places=8)
self.assertAlmostEqual(
valve.port_2.m_flow,
valve.mass_flow(valve.port_2.p, valve.port_3.p),
places=10,
)
self.assertAlmostEqual(high.port_b.m_flow, -valve.port_2.m_flow, places=10)
self.assertAlmostEqual(low.port_a.m_flow, -valve.port_3.m_flow, places=10)
@staticmethod
def _closed_boundary_solver() -> PressureFlowSolver:
boundary = AmesimPnpl01("closed")
boundary.port_1.p = 100_000.0
boundary.port_1.m_flow = 1.0
network = SimulationNetwork("closed-boundary")
network.add_component(boundary)
return PressureFlowSolver(network)
@staticmethod
def _least_squares_result(x, *, status: int):
return SimpleNamespace(
x=x,
success=status > 0,
status=status,
message="test optimizer result",
nfev=1,
)
def test_status_zero_is_accepted_only_for_finite_converged_residuals(self) -> None:
exact_solver = self._closed_boundary_solver()
def exact_status_zero(_fun, x0, **_kwargs):
values = x0.copy()
flow_index = next(
index
for index, unknown in enumerate(exact_solver.unknowns)
if unknown.variable == "m_flow"
)
values[flow_index] = 0.0
return self._least_squares_result(values, status=0)
with patch.object(
PressureFlowSolver,
"_solve_explicit_flow_unknowns",
return_value=None,
), patch("scipy.optimize.least_squares", side_effect=exact_status_zero):
diagnostics = exact_solver.solve()
self.assertTrue(diagnostics.success)
self.assertEqual(diagnostics.max_scaled_residual, 0.0)
inaccurate_solver = self._closed_boundary_solver()
def inaccurate_status_zero(_fun, x0, **_kwargs):
values = x0.copy()
flow_index = next(
index
for index, unknown in enumerate(inaccurate_solver.unknowns)
if unknown.variable == "m_flow"
)
values[flow_index] = 1.0
return self._least_squares_result(values, status=0)
with patch.object(
PressureFlowSolver,
"_solve_explicit_flow_unknowns",
return_value=None,
), patch("scipy.optimize.least_squares", side_effect=inaccurate_status_zero):
with self.assertRaises(AlgebraicSolveError):
inaccurate_solver.solve()
invalid_status_solver = self._closed_boundary_solver()
def exact_invalid_status(_fun, x0, **_kwargs):
values = x0.copy()
flow_index = next(
index
for index, unknown in enumerate(invalid_status_solver.unknowns)
if unknown.variable == "m_flow"
)
values[flow_index] = 0.0
return self._least_squares_result(values, status=-1)
with patch.object(
PressureFlowSolver,
"_solve_explicit_flow_unknowns",
return_value=None,
), patch("scipy.optimize.least_squares", side_effect=exact_invalid_status):
with self.assertRaises(AlgebraicSolveError):
invalid_status_solver.solve()
def test_zero_residual_seed_does_not_bypass_positive_pressure_bound(self) -> None:
medium = IdealGasMedium()
tank = Tank("tank", medium, V=1.0)
plug = AmesimPnpl01("plug")
network = SimulationNetwork("invalid-negative-pressure")
network.add_component(tank)
network.add_component(plug)
network.connect("tank", "port_a", "plug", "port_1")
tank.state = VolumeState(m=1.0, U=-1000.0)
tank.refresh_thermodynamic_ports()
self.assertLess(tank.port_a.p, 0.0)
solver = PressureFlowSolver(network, max_evaluations=10)
with self.assertRaises(AlgebraicSolveError):
solver.solve()
self.assertIsNotNone(solver.last_diagnostics)
self.assertFalse(solver.last_diagnostics.success)
if __name__ == "__main__":
unittest.main()