from __future__ import annotations import unittest from app.simulation.components.amesim.flow.orifices import AmesimPnvo001SignalOpening from app.simulation.components.amesim.signals.sources import AmesimStep0, AmesimUd00 from app.simulation.core.medium import IdealGasMedium from app.simulation.registry import COMPONENT_MODEL_REGISTRY from app.simulation.solvers.signal import SignalResolver from app.simulation.systems.network import SimulationNetwork class AmesimSignalComponentTests(unittest.TestCase): def setUp(self) -> None: self.medium = IdealGasMedium() def test_step0_output_switches_at_step_time(self) -> None: step = AmesimStep0("step_1", self.medium, initial=0.2, final=0.8, time=0.5) self.assertEqual(step.output_at(0.49), 0.2) self.assertEqual(step.output_at(0.5), 0.8) self.assertEqual(step.signal_output_values(0.5), {"out": 0.8}) self.assertEqual(step.signal_event_times(0.0, 1.0), (0.5,)) self.assertEqual(step.signal_event_times(0.5, 1.0), ()) def test_ud00_output_interpolates_piecewise_signal(self) -> None: signal = AmesimUd00( "piecewise_1", self.medium, tstart=0.5, starts=(0.0, 10.0, 20.0, 0.0, 0.0, 0.0, 0.0, 0.0), ends=(10.0, 20.0, 30.0, 0.0, 0.0, 0.0, 0.0, 0.0), durations=(1.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0), nstages=3, ) self.assertAlmostEqual(signal.output_at(0.0), 0.0) self.assertAlmostEqual(signal.output_at(1.0), 5.0) self.assertAlmostEqual(signal.output_at(2.5), 15.0) self.assertAlmostEqual(signal.output_at(5.0), 35.0) self.assertEqual(signal.signal_output_values(2.5), {"out": 15.0}) self.assertEqual(signal.signal_event_times(0.0, 5.0), (0.5, 1.5, 3.5)) def test_ud00_can_cycle_active_stages(self) -> None: signal = AmesimUd00( "piecewise_1", self.medium, starts=(0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), ends=(10.0, 20.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), durations=(1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), nstages=2, iscyclic=True, ) self.assertAlmostEqual(signal.output_at(0.25), 2.5) self.assertAlmostEqual(signal.output_at(1.25), 12.5) self.assertAlmostEqual(signal.output_at(2.25), 2.5) self.assertEqual(signal.signal_event_times(0.0, 5.0), (1.0, 2.0, 3.0, 4.0)) def test_ud00_registry_rejects_fractional_stage_controls(self) -> None: with self.assertRaisesRegex(ValueError, "must be one of"): COMPONENT_MODEL_REGISTRY["amesim_ud00"].create( "piecewise_1", self.medium, {"nstages": 1.5}, ) for name, value in (("nstages", 9.0), ("iscyclic", 2.0)): with self.subTest(name=name): with self.assertRaisesRegex( ValueError, "must be (?:one of|at most)", ): COMPONENT_MODEL_REGISTRY["amesim_ud00"].create( "piecewise_1", self.medium, {name: value}, ) def test_pnvo001_registry_rejects_invalid_flowset_choice(self) -> None: with self.assertRaisesRegex(ValueError, "must be one of"): COMPONENT_MODEL_REGISTRY["amesim_pnvo001"].create( "valve_1", self.medium, {"flowset": 1.5}, ) def test_pnvo001_signal_opening_reads_res_port(self) -> None: valve = COMPONENT_MODEL_REGISTRY["amesim_pnvo001"].create( "valve_1", self.medium, {"opening0": 0.25}, ) self.assertIsInstance(valve, AmesimPnvo001SignalOpening) self.assertEqual(set(valve.ports), {"res", "port_2", "port_3"}) self.assertAlmostEqual(valve.opening, 0.25) valve.res.signal = 1.5 self.assertAlmostEqual(valve.opening, 1.0) valve.res.signal = -0.5 self.assertAlmostEqual(valve.opening, 0.0) def test_signal_resolver_propagates_step_to_valve_input(self) -> None: network = SimulationNetwork("signal-smoke") step = AmesimStep0("step_1", self.medium, initial=0.0, final=0.75, time=0.1) valve = AmesimPnvo001SignalOpening("valve_1", self.medium, opening0=0.0) network.add_component(step) network.add_component(valve) network.connect("step_1", "out", "valve_1", "res", connection_id="signal-1") resolver = SignalResolver(network) resolver.solve(0.2) self.assertAlmostEqual(step.out.signal, 0.75) self.assertAlmostEqual(valve.res.signal, 0.75) self.assertAlmostEqual(valve.opening, 0.75) self.assertEqual(resolver.event_times(0.0, 0.2), (0.1,)) def test_signal_output_can_fan_out_but_input_has_one_driver(self) -> None: network = SimulationNetwork("signal-cardinality") step_1 = AmesimStep0( "step_1", self.medium, initial=0.0, final=0.75, time=0.1 ) step_2 = AmesimStep0( "step_2", self.medium, initial=1.0, final=0.25, time=0.1 ) valve_1 = AmesimPnvo001SignalOpening( "valve_1", self.medium, opening0=0.0 ) valve_2 = AmesimPnvo001SignalOpening( "valve_2", self.medium, opening0=0.0 ) for component in (step_1, step_2, valve_1, valve_2): network.add_component(component) network.connect("step_1", "out", "valve_1", "res") network.connect("step_1", "out", "valve_2", "res") with self.assertRaisesRegex(ValueError, "already has a driver"): network.connect("step_2", "out", "valve_1", "res") SignalResolver(network).solve(0.2) self.assertAlmostEqual(valve_1.res.signal, 0.75) self.assertAlmostEqual(valve_2.res.signal, 0.75) def test_signal_resolver_event_times_are_sorted_and_deduplicated(self) -> None: network = SimulationNetwork("signal-events") network.add_component( AmesimStep0("step_2", self.medium, initial=0.0, final=1.0, time=0.2) ) network.add_component( AmesimStep0("step_1", self.medium, initial=1.0, final=0.0, time=0.1) ) network.add_component( AmesimStep0("step_3", self.medium, initial=0.0, final=1.0, time=0.2) ) resolver = SignalResolver(network) self.assertEqual(resolver.event_times(0.0, 0.3), (0.1, 0.2)) self.assertEqual(resolver.event_times(0.2, 0.3), ()) if __name__ == "__main__": unittest.main()