from __future__ import annotations import unittest from app.simulation.components.amesim.flow.pipes import AmesimPnl00r from app.simulation.core.medium import IdealGasMedium from app.simulation.registry import COMPONENT_MODEL_REGISTRY class AmesimPnl00rComponentTests(unittest.TestCase): def setUp(self) -> None: self.medium = IdealGasMedium() def test_default_create_preserves_amesim_parameter_contract(self) -> None: pipe = COMPONENT_MODEL_REGISTRY["amesim_pnl00r"].create( "pnl_1", self.medium, {}, ) self.assertIsInstance(pipe, AmesimPnl00r) self.assertEqual(set(pipe.ports), {"port_1", "port_2"}) self.assertEqual(set(pipe.parameter_values), {"diam", "le", "rr", "gi"}) self.assertEqual(pipe.parameter_values["diam"], 0.01) self.assertEqual(pipe.gi, 0) def test_rejects_fractional_gas_index(self) -> None: with self.assertRaisesRegex(ValueError, "gi must be an integer"): COMPONENT_MODEL_REGISTRY["amesim_pnl00r"].create( "pnl_1", self.medium, {"gi": 1.5}, ) def test_zero_pressure_drop_has_zero_flow_and_finite_results(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium) pipe.port_1.p = 100000.0 pipe.port_2.p = 100000.0 pipe.port_1.m_flow = 0.0 pipe.port_2.m_flow = 0.0 residuals = { residual.id.rsplit(":", 1)[1]: residual for residual in pipe.pressure_flow_equation_residuals() } self.assertEqual(pipe.mass_flow(100000.0, 100000.0), 0.0) self.assertEqual(residuals["mass_flow_balance"].value, 0.0) self.assertEqual(residuals["pressure_flow_relation"].value, 0.0) self.assertEqual(set(pipe.component_result_values()), {"re", "cm", "v", "ff"}) def test_mass_flow_is_bidirectional_by_pressure_order(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium, diam=0.014, le=1.0, rr=0.045 / 14.0) forward = pipe.mass_flow(501000.0, 500000.0) reverse = pipe.mass_flow(500000.0, 501000.0) self.assertGreater(forward, 0.0) self.assertLess(reverse, 0.0) self.assertAlmostEqual(forward, -reverse, delta=abs(forward) * 0.02) def test_mass_flow_is_continuous_across_former_relative_deadband(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium, diam=0.014, le=1.0, rr=0.045 / 14.0) base_pressure = 8.0e6 former_threshold = base_pressure * 1.0e-7 below = pipe.mass_flow(base_pressure + 0.99 * former_threshold, base_pressure) above = pipe.mass_flow(base_pressure + 1.01 * former_threshold, base_pressure) reverse = pipe.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(pipe.mass_flow(base_pressure, base_pressure), 0.0) self.assertGreater(pipe.mass_flow(15.3e6 + 0.1, 15.3e6), 0.0) def test_pressure_drop_inversion_cache_keys_all_property_inputs(self) -> None: pipe = AmesimPnl00r( "pnl_1", self.medium, diam=0.014, le=1.0, rr=0.045 / 14.0, ) pipe.port_1.h_outflow = self.medium.specific_enthalpy(300.0) pipe._mass_flow_for_pressure_drop.cache_clear() first = pipe.mass_flow(501000.0, 500000.0) after_first = pipe._mass_flow_for_pressure_drop.cache_info() second = pipe.mass_flow(501000.0, 500000.0) after_second = pipe._mass_flow_for_pressure_drop.cache_info() pipe.port_1.h_outflow = self.medium.specific_enthalpy(400.0) third = pipe.mass_flow(501000.0, 500000.0) after_temperature_change = pipe._mass_flow_for_pressure_drop.cache_info() self.assertEqual(first, second) self.assertEqual(after_first.misses, 1) self.assertEqual(after_second.hits, after_first.hits + 1) self.assertNotEqual(third, second) self.assertEqual( after_temperature_change.misses, after_second.misses + 1, ) def test_friction_factor_transitions_from_laminar_to_turbulent(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium, rr=1e-5) self.assertGreater(pipe.friction_factor(1.0e-12), 64_000_000.0) self.assertAlmostEqual(pipe.friction_factor(100.0), 64.0 / 100.0) self.assertGreater(pipe.friction_factor(100000.0), 0.0) self.assertLess(pipe.friction_factor(100000.0), 0.1) def test_reported_friction_factor_is_bounded_at_tiny_flow(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium) pipe.mass_flow = lambda _p_1, _p_2: 1.0e-30 self.assertEqual(pipe.component_result_values()["ff"], 64_000_000.0) def test_friction_factor_matches_amesim_smooth_to_rough_transition(self) -> None: pipe_20mm = AmesimPnl00r( "pnl_20mm", self.medium, diam=0.02, rr=0.045 / 20.0, ) pipe_14mm = AmesimPnl00r( "pnl_14mm", self.medium, diam=0.014, rr=0.045 / 14.0, ) self.assertAlmostEqual( pipe_20mm.friction_factor(56_887.5547), 0.0215740061043, delta=1.0e-4, ) self.assertAlmostEqual( pipe_20mm.friction_factor(700_686.41), 0.02400535718, delta=8.0e-5, ) self.assertAlmostEqual( pipe_14mm.friction_factor(726_799.66), 0.02658268645, delta=8.0e-5, ) def test_friction_factor_matches_amesim_transition_regime(self) -> None: pipe = AmesimPnl00r( "pnl_20mm", self.medium, diam=0.02, rr=0.045 / 20.0, ) self.assertAlmostEqual( pipe.friction_factor(3_699.80236), 0.0391257647759, delta=4.0e-4, ) def test_darcy_pressure_drop_uses_flow_sign(self) -> None: pipe = AmesimPnl00r("pnl_1", self.medium) density = self.medium.density(500000.0, 300.0) forward = pipe.darcy_pressure_drop(0.01, density=density, temperature=300.0) reverse = pipe.darcy_pressure_drop(-0.01, density=density, temperature=300.0) self.assertGreater(forward, 0.0) self.assertLess(reverse, 0.0) self.assertAlmostEqual(forward, -reverse) if __name__ == "__main__": unittest.main()