from __future__ import annotations import unittest from app.main import compile_reactflow_network from PythonModels.components.cylinder import Cylinder from PythonModels.components.tank import Tank from PythonModels.components.tee import Tee from PythonModels.core.base import Component from PythonModels.core.medium import IdealGasMedium from PythonModels.core.network import SimulationNetwork from PythonModels.core.ports import PortDefinition, PortState, PortVariableDefinition from PythonModels.systems.testmodel import TestModelSystem from tests.test_system_xml_protocol import physical_connection_project class ComponentInterfaceTests(unittest.TestCase): def test_pneumatic_port_exposes_equation_roles_and_signed_flow(self) -> None: port = PortState.pneumatic("port_a", nominal_role="bidirectional") assert port.definition is not None self.assertEqual( [ (variable.name, variable.role, variable.connection_rule) for variable in port.definition.variables ], [ ("p", "effort", "equal"), ("m_flow", "flow", "sumToZero"), ("h_outflow", "stream", "streamMix"), ], ) port.m_flow = 10.0 self.assertEqual(port.actual_direction(), "in") self.assertEqual(port.inflow_rate, 10.0) self.assertEqual(port.outflow_rate, 0.0) port.m_flow = -4.0 self.assertEqual(port.actual_direction(), "out") self.assertEqual(port.inflow_rate, 0.0) self.assertEqual(port.outflow_rate, 4.0) def test_physical_connection_is_neutral_and_canonical(self) -> None: forward = compile_reactflow_network(physical_connection_project()) reverse = compile_reactflow_network( physical_connection_project(reverse_edge=True) ) self.assertEqual( forward.connections[0].undirected_key, reverse.connections[0].undirected_key, ) self.assertEqual( forward.connections[0].as_interface_dict(), reverse.connections[0].as_interface_dict(), ) self.assertNotIn("source", forward.connections[0].as_interface_dict()) def test_compiler_instantiates_parameters_and_actual_edges(self) -> None: project = physical_connection_project() network = compile_reactflow_network(project) self.assertEqual(set(network.components), {"cylinder_1", "tank_1"}) self.assertEqual(network.components["cylinder_1"].V, 0.01) self.assertEqual(network.components["tank_1"].V, 0.1) self.assertEqual(len(network.connections), 1) self.assertEqual(network.connections[0].id, "edge-1") self.assertEqual(network.connections[0].kind, "physical") self.assertEqual(network.connections[0].domain, "pneumatic") equation_system = network.as_interface_dict()["pressureFlowSystem"] self.assertEqual(equation_system["unknownCount"], 4) self.assertEqual(equation_system["equationCount"], 4) self.assertTrue(equation_system["isSquare"]) def test_physical_port_requires_a_tee_for_branching(self) -> None: medium = IdealGasMedium() network = SimulationNetwork("branch-check") network.add_component(Cylinder("cylinder_1", medium)) network.add_component(Tank("tank_1", medium)) network.add_component(Tank("tank_2", medium)) network.connect("cylinder_1", "port_b", "tank_1", "port_a") with self.assertRaisesRegex(ValueError, "Use a junction component"): network.connect("cylinder_1", "port_b", "tank_2", "port_a") def test_connection_requires_the_same_variable_contract(self) -> None: medium = IdealGasMedium() network = SimulationNetwork("contract-check") network.add_component(Cylinder("cylinder_1", medium)) incompatible = Component("incompatible_1") incompatible.register_port( PortState( definition=PortDefinition( name="port_a", kind="physical", domain="pneumatic", nominal_role="bidirectional", positive_flow_direction="intoComponent", variables=(PortVariableDefinition("p", "effort", "equal"),), ) ) ) network.add_component(incompatible) with self.assertRaisesRegex(ValueError, "variable contracts do not match"): network.connect("cylinder_1", "port_b", "incompatible_1", "port_a") def test_connection_equations_are_executable_and_endpoint_neutral(self) -> None: medium = IdealGasMedium() network = SimulationNetwork("residual-check") cylinder = Cylinder("cylinder_1", medium) tank = Tank("tank_1", medium) network.add_component(cylinder) network.add_component(tank) network.connect("tank_1", "port_a", "cylinder_1", "port_b") cylinder.port_b.p = 2.0e5 cylinder.port_b.m_flow = -3.0 tank.port_a.p = 2.0e5 tank.port_a.m_flow = 3.0 residuals = { residual.variables[0].rsplit(".", 1)[-1]: residual for residual in network.connection_equation_residuals() } self.assertEqual(set(residuals), {"p", "m_flow"}) self.assertEqual(residuals["p"].relation, "equal") self.assertEqual(residuals["m_flow"].relation, "sumToZero") self.assertEqual(residuals["p"].value, 0.0) self.assertEqual(residuals["m_flow"].value, 0.0) def test_tee_ports_are_physical_and_bidirectional(self) -> None: tee = Tee("tee_1") self.assertEqual( {definition.name for definition in tee.port_definitions}, {"port_in", "port_out1", "port_out2"}, ) self.assertTrue( all( definition.kind == "physical" and definition.nominal_role == "bidirectional" for definition in tee.port_definitions ) ) def test_existing_testmodel_topology_still_builds(self) -> None: system = TestModelSystem() self.assertEqual(len(system.network.components), 8) self.assertEqual(len(system.network.connections), 8) self.assertTrue( all(connection.kind == "physical" for connection in system.network.connections) ) self.assertEqual(len(system.initial_state_vector()), 8) def test_testmodel_snapshot_satisfies_acausal_connection_equations(self) -> None: system = TestModelSystem() state = system.consistent_initial_state_vector() system.snapshot(state) residuals = system.network.pressure_flow_equation_residuals() self.assertEqual(len(residuals), 4 * len(system.network.connections)) self.assertEqual(len(system.network.pressure_flow_unknowns()), len(residuals)) for residual in residuals: tolerance = 1e-6 self.assertLessEqual( abs(residual.value), tolerance, msg=f"{residual.id} residual is {residual.value}", ) upstream_tee = system.mytee self.assertAlmostEqual( upstream_tee.port_in.m_flow + upstream_tee.port_out1.m_flow + upstream_tee.port_out2.m_flow, 0.0, places=12, ) self.assertAlmostEqual(upstream_tee.port_in.p, upstream_tee.port_out1.p) self.assertAlmostEqual(upstream_tee.port_in.p, upstream_tee.port_out2.p) if __name__ == "__main__": unittest.main()