197 lines
7.9 KiB
Python
197 lines
7.9 KiB
Python
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_pnvo001_exact_repeated_flow_characteristics_are_cached(self) -> None:
|
|
valve = AmesimPnvo001SignalOpening("valve_1", self.medium, opening0=0.25)
|
|
valve._one_way_flow_characteristics.cache_clear()
|
|
|
|
first = valve._one_way_flow_characteristics(
|
|
upstream_pressure=501000.0,
|
|
downstream_pressure=500000.0,
|
|
upstream_temperature=300.0,
|
|
)
|
|
after_first = valve._one_way_flow_characteristics.cache_info()
|
|
second = valve._one_way_flow_characteristics(
|
|
upstream_pressure=501000.0,
|
|
downstream_pressure=500000.0,
|
|
upstream_temperature=300.0,
|
|
)
|
|
after_second = valve._one_way_flow_characteristics.cache_info()
|
|
changed_pressure = valve._one_way_flow_characteristics(
|
|
upstream_pressure=502000.0,
|
|
downstream_pressure=500000.0,
|
|
upstream_temperature=300.0,
|
|
)
|
|
after_pressure_change = valve._one_way_flow_characteristics.cache_info()
|
|
|
|
self.assertEqual(first, second)
|
|
self.assertEqual(after_first.misses, 1)
|
|
self.assertEqual(after_second.hits, after_first.hits + 1)
|
|
self.assertNotEqual(changed_pressure, second)
|
|
self.assertEqual(
|
|
after_pressure_change.misses,
|
|
after_second.misses + 1,
|
|
)
|
|
|
|
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()
|