from __future__ import annotations import unittest from app.simulation.components.amesim.flow.orifices import AmesimPnvo001FixedOpening from app.simulation.components.amesim.media.mediums import ( AmesimHeliumPengRobinsonMedium, ) from app.simulation.core.medium import IdealGasMedium from app.simulation.registry import COMPONENT_MODEL_REGISTRY class AmesimPnvo001FixedOpeningComponentTests(unittest.TestCase): def setUp(self) -> None: self.medium = IdealGasMedium() def test_default_create_preserves_fixed_opening_contract(self) -> None: valve = COMPONENT_MODEL_REGISTRY["amesim_pnvo001_fixed"].create( "valve_1", self.medium, {}, ) self.assertIsInstance(valve, AmesimPnvo001FixedOpening) self.assertEqual(set(valve.ports), {"port_2", "port_3"}) self.assertEqual( set(valve.parameter_values), {"cq", "area0", "Cv", "Kv", "gi", "flowset", "opening"}, ) self.assertEqual(valve.opening, 1.0) def test_rejects_fractional_flowset(self) -> None: with self.assertRaisesRegex(ValueError, "must be one of"): COMPONENT_MODEL_REGISTRY["amesim_pnvo001_fixed"].create( "valve_1", self.medium, {"flowset": 1.5}, ) def test_opening_scales_effective_area(self) -> None: valve = AmesimPnvo001FixedOpening( "valve_1", self.medium, area0=2.0e-6, opening=0.25, ) self.assertAlmostEqual(valve.maximum_area, 2.0e-6) self.assertAlmostEqual(valve.effective_area, 0.5e-6) self.assertEqual(valve.component_result_values()["xv"], 0.25) def test_zero_opening_blocks_flow(self) -> None: valve = AmesimPnvo001FixedOpening("valve_1", self.medium, opening=0.0) self.assertEqual(valve.mass_flow(500000.0, 100000.0), 0.0) def test_closed_opening_only_zeroes_reported_gas_velocity(self) -> None: valve = AmesimPnvo001FixedOpening("valve_1", self.medium, opening=0.0) valve.port_2.p = 500000.0 valve.port_3.p = 100000.0 closed = valve.component_result_values() self.assertGreater(closed["cm"], 0.0) self.assertEqual(closed["gasvel"], 0.0) valve.opening = 7.0e-15 roundoff_closed = valve.component_result_values() self.assertEqual(roundoff_closed["gasvel"], 0.0) self.assertEqual(roundoff_closed["cm"], closed["cm"]) valve.opening = 1.0e-9 genuinely_open = valve.component_result_values() self.assertNotEqual(genuinely_open["gasvel"], 0.0) self.assertEqual(genuinely_open["cm"], closed["cm"]) def test_mass_flow_is_bidirectional_by_pressure_order(self) -> None: valve = AmesimPnvo001FixedOpening("valve_1", self.medium, opening=0.5) forward = valve.mass_flow(500000.0, 100000.0) reverse = valve.mass_flow(100000.0, 500000.0) self.assertGreater(forward, 0.0) self.assertLess(reverse, 0.0) self.assertAlmostEqual(forward, -reverse) def test_mass_flow_is_continuous_across_former_relative_deadband(self) -> None: valve = AmesimPnvo001FixedOpening("valve_1", self.medium, opening=0.5) base_pressure = 8.0e6 former_threshold = base_pressure * 1.0e-7 below = valve.mass_flow(base_pressure + 0.99 * former_threshold, base_pressure) above = valve.mass_flow(base_pressure + 1.01 * former_threshold, base_pressure) reverse = valve.mass_flow(base_pressure, base_pressure + 0.99 * former_threshold) self.assertGreater(below, 0.0) self.assertGreater(above, below) self.assertLess(reverse, 0.0) self.assertAlmostEqual(below, -reverse, delta=abs(below) * 1.0e-9) self.assertLess(abs(above - below), abs(above) * 0.05) self.assertEqual(valve.mass_flow(base_pressure, base_pressure), 0.0) self.assertGreater(valve.mass_flow(15.3e6 + 0.1, 15.3e6), 0.0) def test_mass_flow_uses_connected_enthalpy_from_the_upstream_side(self) -> None: valve = AmesimPnvo001FixedOpening("valve_1", self.medium, opening=0.5) hot_h = self.medium.specific_enthalpy(600.0) cold_h = self.medium.specific_enthalpy(200.0) valve.update_stream_outflows({"port_2": hot_h, "port_3": cold_h}) self.assertEqual(valve.port_2.h_outflow, cold_h) self.assertEqual(valve.port_3.h_outflow, hot_h) self.assertAlmostEqual( valve.mass_flow(500000.0, 100000.0), valve._one_way_mass_flow( upstream_pressure=500000.0, downstream_pressure=100000.0, upstream_temperature=600.0, ), ) self.assertAlmostEqual( valve.mass_flow(100000.0, 500000.0), -valve._one_way_mass_flow( upstream_pressure=500000.0, downstream_pressure=100000.0, upstream_temperature=200.0, ), ) def test_helium_flow_uses_pressure_enthalpy_and_real_gas_factor(self) -> None: medium = AmesimHeliumPengRobinsonMedium() valve = AmesimPnvo001FixedOpening( "valve_1", medium, cq=0.45, area0=78.5e-6, opening=1.0, ) upstream_pressure = 15_201_996.778497815 downstream_pressure = 405_072.8123335606 upstream_temperature = 292.3997890954483 valve.port_2.p = upstream_pressure valve.port_3.p = downstream_pressure upstream_enthalpy = medium.specific_enthalpy_at_pressure( upstream_pressure, upstream_temperature, ) valve.update_stream_outflows( { "port_2": upstream_enthalpy, "port_3": medium.specific_enthalpy_at_pressure( downstream_pressure, 393.46713105173205, ), } ) self.assertLess(upstream_enthalpy, 0.0) self.assertAlmostEqual( valve._upstream_temperature("port_2"), upstream_temperature, delta=1.0e-8, ) self.assertAlmostEqual( valve.mass_flow(upstream_pressure, downstream_pressure), 0.49551308906777447, delta=1.0e-9, ) results = valve.component_result_values() self.assertAlmostEqual( results["cm"], 0.015778323343746598, delta=1.0e-11, ) self.assertAlmostEqual( results["gasvel"], 894.7011595213406, delta=1.0e-8, ) def test_helium_laminar_transition_matches_amesim_baseline(self) -> None: medium = AmesimHeliumPengRobinsonMedium() valve = AmesimPnvo001FixedOpening( "valve_1", medium, cq=0.45, area0=78.5e-6, opening=1.0, ) cases = ( # p_up [PaA], p_down [PaA], T_up [K], dm [g/s], Cm, velocity [m/s] ( 10_837_357.913884956, 10_835_428.362241976, 255.45507181057303, 9.770711291393273, 4.07922685863417e-4, 13.951480313568323, ), ( 10_735_827.842429036, 10_735_543.135671863, 254.49635939397584, 3.4711877823059916, 1.4601624857015259e-4, 4.982778680076626, ), ( 10_703_773.135050302, 10_703_621.857082237, 254.1925398502598, 1.53560321377487, 6.475023461883112e-5, 2.208065833961227, ), ( 10_682_359.261700785, 10_682_301.022976216, 253.98926908210996, 0.25626141624058846, 1.0822847918204546e-5, 0.36890236539556787, ), ( 10_678_100.351449525, 10_678_093.704329137, 253.94881208705195, 0.0033658306146710985, 1.421965896377586e-7, 0.004846389527047451, ), ) for ( p_up, p_down, T_up, expected_dm, expected_cm, expected_velocity, ) in cases: with self.subTest(pressure_difference=p_up - p_down): mass_flow_parameter, gas_velocity = ( valve._one_way_flow_characteristics( upstream_pressure=p_up, downstream_pressure=p_down, upstream_temperature=T_up, ) ) mass_flow_g_s = ( valve._one_way_mass_flow( upstream_pressure=p_up, downstream_pressure=p_down, upstream_temperature=T_up, ) * 1.0e3 ) self.assertAlmostEqual(mass_flow_g_s, expected_dm, delta=1.0e-11) self.assertAlmostEqual( mass_flow_parameter, expected_cm, delta=1.0e-15, ) self.assertAlmostEqual( gas_velocity, expected_velocity, delta=5.0e-10, ) if __name__ == "__main__": unittest.main()