fix: seed PNVO pipe series pressure

This commit is contained in:
Codex committed 2026-08-16 11:34:35 +00:00
1 parent 22b35b2945
commit cca9d1e883
4 files changed
+110 -37

No files matched your search

+53 -32
View File
@@ -7,7 +7,10 @@ from math import expm1, isfinite, log, sqrt
import os import os
from app.simulation.components.amesim.boundary.sources import AmesimPnpl01 from app.simulation.components.amesim.boundary.sources import AmesimPnpl01
from app.simulation.components.amesim.flow.orifices import AmesimPnor001 from app.simulation.components.amesim.flow.orifices import (
AmesimPnor001,
AmesimPnvo001FixedOpening,
)
from app.simulation.components.amesim.flow.pipes import ( from app.simulation.components.amesim.flow.pipes import (
AmesimPnl00r, AmesimPnl00r,
AmesimPnl0001, AmesimPnl0001,
@@ -179,9 +182,11 @@ class AlgebraicEquationSubset:
@dataclass(frozen=True) @dataclass(frozen=True)
class PnorPnl0001SeriesBinding: class ResistancePnl0001SeriesBinding:
orifice: AmesimPnor001 resistance: AmesimPnor001 | AmesimPnvo001FixedOpening
orifice_port: str resistance_port: str
resistance_other_port: str
positive_flow_port: str
pipe: AmesimPnl0001 pipe: AmesimPnl0001
pipe_port: str pipe_port: str
@@ -315,8 +320,8 @@ class PressureFlowSolver:
variable: self._build_effort_equality_groups(variable) variable: self._build_effort_equality_groups(variable)
for variable in ("p", "x", "v") for variable in ("p", "x", "v")
} }
self._pnor_pnl0001_series_plan = ( self._resistance_pnl0001_series_plan = (
self._build_pnor_pnl0001_series_plan() self._build_resistance_pnl0001_series_plan()
) )
self._closed_resistance_pressure_plan = ( self._closed_resistance_pressure_plan = (
self._build_closed_resistance_pressure_plan() self._build_closed_resistance_pressure_plan()
@@ -498,7 +503,7 @@ class PressureFlowSolver:
return failed("activeSetCausalizationRequired") return failed("activeSetCausalizationRequired")
if self._closed_resistance_pressure_plan: if self._closed_resistance_pressure_plan:
return failed("specialClosedResistancePressureSeed") return failed("specialClosedResistancePressureSeed")
if self._pnor_pnl0001_series_plan: if self._resistance_pnl0001_series_plan:
return failed("specialSeriesPressureSeed") return failed("specialSeriesPressureSeed")
if len(self._unknowns_by_id) != len(self.unknowns): if len(self._unknowns_by_id) != len(self.unknowns):
return failed("duplicateAlgebraicUnknown") return failed("duplicateAlgebraicUnknown")
@@ -1895,68 +1900,84 @@ class PressureFlowSolver:
component.get_port(binding.port_name).p = pressure component.get_port(binding.port_name).p = pressure
binding.neighbor.get_port(binding.neighbor_port).p = pressure binding.neighbor.get_port(binding.neighbor_port).p = pressure
def _build_pnor_pnl0001_series_plan( def _build_resistance_pnl0001_series_plan(
self, self,
) -> tuple[PnorPnl0001SeriesBinding, ...]: ) -> tuple[ResistancePnl0001SeriesBinding, ...]:
bindings: list[PnorPnl0001SeriesBinding] = [] bindings: list[ResistancePnl0001SeriesBinding] = []
resistance_types = (AmesimPnor001, AmesimPnvo001FixedOpening)
for connection in self.network.connections: for connection in self.network.connections:
first_endpoint, second_endpoint = connection.endpoints first_endpoint, second_endpoint = connection.endpoints
first = self.network.components[first_endpoint.component] first = self.network.components[first_endpoint.component]
second = self.network.components[second_endpoint.component] second = self.network.components[second_endpoint.component]
if isinstance(first, AmesimPnor001) and isinstance(second, AmesimPnl0001): if isinstance(first, resistance_types) and isinstance(
orifice, orifice_port = first, first_endpoint.port second, AmesimPnl0001
):
resistance, resistance_port = first, first_endpoint.port
pipe, pipe_port = second, second_endpoint.port pipe, pipe_port = second, second_endpoint.port
elif isinstance(second, AmesimPnor001) and isinstance(first, AmesimPnl0001): elif isinstance(second, resistance_types) and isinstance(
orifice, orifice_port = second, second_endpoint.port first, AmesimPnl0001
):
resistance, resistance_port = second, second_endpoint.port
pipe, pipe_port = first, first_endpoint.port pipe, pipe_port = first, first_endpoint.port
else: else:
continue continue
if isinstance(pipe, AmesimPnl0002) or pipe_port != "port_1": if isinstance(pipe, AmesimPnl0002) or pipe_port != "port_1":
continue continue
if isinstance(resistance, AmesimPnor001):
positive_flow_port, negative_flow_port = "port_1", "port_2"
else:
positive_flow_port, negative_flow_port = "port_2", "port_3"
if resistance_port == positive_flow_port:
resistance_other_port = negative_flow_port
elif resistance_port == negative_flow_port:
resistance_other_port = positive_flow_port
else:
continue
bindings.append( bindings.append(
PnorPnl0001SeriesBinding( ResistancePnl0001SeriesBinding(
orifice=orifice, resistance=resistance,
orifice_port=orifice_port, resistance_port=resistance_port,
resistance_other_port=resistance_other_port,
positive_flow_port=positive_flow_port,
pipe=pipe, pipe=pipe,
pipe_port=pipe_port, pipe_port=pipe_port,
) )
) )
return tuple(bindings) return tuple(bindings)
def _seed_pnor_pnl0001_series_pressures(self) -> None: def _seed_resistance_pnl0001_series_pressures(self) -> None:
"""Causalize the pressure between a PNOR001 and PNL0001 R port.""" """Causalize pressure between an orifice/valve and a PNL0001 R port."""
from scipy.optimize import brentq from scipy.optimize import brentq
for binding in self._pnor_pnl0001_series_plan: for binding in self._resistance_pnl0001_series_plan:
orifice = binding.orifice resistance = binding.resistance
orifice_port = binding.orifice_port resistance_port = binding.resistance_port
pipe = binding.pipe pipe = binding.pipe
pipe_port = binding.pipe_port pipe_port = binding.pipe_port
orifice_other = "port_2" if orifice_port == "port_1" else "port_1" pressure_a = resistance.get_port(binding.resistance_other_port).p
pressure_a = orifice.get_port(orifice_other).p
pressure_b = pipe.properties().p pressure_b = pipe.properties().p
lower = min(pressure_a, pressure_b) lower = min(pressure_a, pressure_b)
upper = max(pressure_a, pressure_b) upper = max(pressure_a, pressure_b)
def mismatch(intermediate_pressure: float) -> float: def mismatch(intermediate_pressure: float) -> float:
if orifice_port == "port_2": if resistance_port == binding.positive_flow_port:
orifice_flow_into_connection = -orifice.mass_flow( resistance_flow_into_connection = resistance.mass_flow(
pressure_a,
intermediate_pressure, intermediate_pressure,
pressure_a,
) )
else: else:
orifice_flow_into_connection = orifice.mass_flow( resistance_flow_into_connection = -resistance.mass_flow(
intermediate_pressure,
pressure_a, pressure_a,
intermediate_pressure,
) )
pipe_flow_into_connection = pipe.mass_flow( pipe_flow_into_connection = pipe.mass_flow(
intermediate_pressure, intermediate_pressure,
pressure_b, pressure_b,
pipe.properties().T, pipe.properties().T,
) )
return orifice_flow_into_connection + pipe_flow_into_connection return resistance_flow_into_connection + pipe_flow_into_connection
lower_value = mismatch(lower) lower_value = mismatch(lower)
upper_value = mismatch(upper) upper_value = mismatch(upper)
@@ -1977,7 +1998,7 @@ class PressureFlowSolver:
maxiter=32, maxiter=32,
) )
) )
orifice.get_port(orifice_port).p = pressure resistance.get_port(resistance_port).p = pressure
pipe.get_port(pipe_port).p = pressure pipe.get_port(pipe_port).p = pressure
def _equation_scale_plan(self, equation) -> EquationScalePlan: def _equation_scale_plan(self, equation) -> EquationScalePlan:
@@ -2083,7 +2104,7 @@ class PressureFlowSolver:
else: else:
self._seed_equal_efforts(effort_variables) self._seed_equal_efforts(effort_variables)
self._seed_closed_resistance_pressures() self._seed_closed_resistance_pressures()
self._seed_pnor_pnl0001_series_pressures() self._seed_resistance_pnl0001_series_pressures()
seeded_flow_ids = self._solve_explicit_flow_unknowns() seeded_flow_ids = self._solve_explicit_flow_unknowns()
contact_bindings = self._seed_unilateral_contacts() contact_bindings = self._seed_unilateral_contacts()
if contact_bindings: if contact_bindings:
+4 -4
View File
@@ -324,7 +324,7 @@ class StreamPressureBlockSolver:
return failed(str(parent_reason or "parentCausalPathIneligible")) return failed(str(parent_reason or "parentCausalPathIneligible"))
if solver._closed_resistance_pressure_plan: if solver._closed_resistance_pressure_plan:
return failed("specialClosedResistancePressureSeed") return failed("specialClosedResistancePressureSeed")
if solver._pnor_pnl0001_series_plan: if solver._resistance_pnl0001_series_plan:
return failed("specialSeriesPressureSeed") return failed("specialSeriesPressureSeed")
selected = self._selected_equation_evaluation selected = self._selected_equation_evaluation
if selected is None: if selected is None:
@@ -725,10 +725,10 @@ class StreamPressureBlockSolver:
f"{binding.neighbor.name}.{binding.neighbor_port}.p", f"{binding.neighbor.name}.{binding.neighbor_port}.p",
) )
) )
for binding in solver._pnor_pnl0001_series_plan: for binding in solver._resistance_pnl0001_series_plan:
special_seed_target_ids.extend( special_seed_target_ids.extend(
( (
f"{binding.orifice.name}.{binding.orifice_port}.p", f"{binding.resistance.name}.{binding.resistance_port}.p",
f"{binding.pipe.name}.{binding.pipe_port}.p", f"{binding.pipe.name}.{binding.pipe_port}.p",
) )
) )
@@ -774,7 +774,7 @@ class StreamPressureBlockSolver:
# closure deliberately mirrors ``solver.solve(effort_variables=())``: # closure deliberately mirrors ``solver.solve(effort_variables=())``:
# the primary global solve has already propagated equal pressures. # the primary global solve has already propagated equal pressures.
solver._seed_closed_resistance_pressures() solver._seed_closed_resistance_pressures()
solver._seed_pnor_pnl0001_series_pressures() solver._seed_resistance_pnl0001_series_pressures()
for unknown in self._selected_flow_unknowns: for unknown in self._selected_flow_unknowns:
unknown.write(0.0) unknown.write(0.0)
for stage in self._selected_explicit_flow_plan: for stage in self._selected_explicit_flow_plan:
@@ -58,6 +58,58 @@ class PressureFlowSolverInitializationTests(unittest.TestCase):
"specialSeriesPressureSeed", "specialSeriesPressureSeed",
) )
def test_pnvo_pnl0001_series_pressure_is_seeded_by_flow_balance(self) -> None:
medium = IdealGasMedium()
chamber = AmesimPnch023("source", medium, p0=15.3e6)
source_plug = AmesimPnpl01("source_closed")
valve = AmesimPnvo001SignalOpening(
"valve",
medium,
opening0=1.0,
)
valve.res.signal = 1.0
pipe = AmesimPnl0001("pipe", medium, p0=14.0e6)
storage_plug = AmesimPnpl01("storage_closed")
network = SimulationNetwork("pnvo-pnl0001-series")
for component in (chamber, source_plug, valve, pipe, storage_plug):
network.add_component(component)
network.connect("source_closed", "port_1", "source", "port_1")
network.connect("source", "port_2", "valve", "port_3")
network.connect("valve", "port_2", "pipe", "port_1")
network.connect("pipe", "port_2", "storage_closed", "port_1")
chamber.refresh_thermodynamic_ports()
pipe.refresh_thermodynamic_ports()
solver = PressureFlowSolver(network)
result = solver.solve()
self.assertTrue(result.success)
self.assertEqual(result.evaluations, 0)
self.assertGreater(valve.port_2.p, pipe.port_2.p)
self.assertLess(valve.port_2.p, chamber.port_2.p)
self.assertAlmostEqual(valve.port_2.p, pipe.port_1.p)
self.assertAlmostEqual(-valve.port_2.m_flow, pipe.port_1.m_flow)
previous_intermediate_pressure = valve.port_2.p
replay_pressure = 12.0e6
replay_temperature = 293.15
replay_mass = (
medium.density(replay_pressure, replay_temperature) * pipe.volume
)
pipe.state = VolumeState(
m=replay_mass,
U=replay_mass * medium.specific_internal_energy(replay_temperature),
)
pipe.refresh_thermodynamic_ports()
replay = solver.solve()
self.assertTrue(replay.success)
self.assertEqual(replay.evaluations, 0)
self.assertNotAlmostEqual(valve.port_2.p, previous_intermediate_pressure)
self.assertAlmostEqual(valve.port_2.p, pipe.port_1.p)
self.assertAlmostEqual(-valve.port_2.m_flow, pipe.port_1.m_flow)
def test_dead_ended_pnl00r_is_seeded_at_zero_flow_pressure(self) -> None: def test_dead_ended_pnl00r_is_seeded_at_zero_flow_pressure(self) -> None:
medium = IdealGasMedium() medium = IdealGasMedium()
pipe = AmesimPnl00r("resistance", medium) pipe = AmesimPnl00r("resistance", medium)
+1 -1
View File
@@ -360,7 +360,7 @@ class ThermofluidClosurePlanTests(unittest.TestCase):
) )
] ]
self.assertEqual(len(series_solver._pnor_pnl0001_series_plan), 1) self.assertEqual(len(series_solver._resistance_pnl0001_series_plan), 1)
self.assertEqual(len(closed_pipe_solver._closed_resistance_pressure_plan), 2) self.assertEqual(len(closed_pipe_solver._closed_resistance_pressure_plan), 2)
self.assertEqual(len(resistance_solver._closed_resistance_pressure_plan), 1) self.assertEqual(len(resistance_solver._closed_resistance_pressure_plan), 1)