Files
SystemSimulationApp/tests/test_component_interfaces.py
T

186 lines
7.5 KiB
Python

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()