from __future__ import annotations import unittest from app.main import ( ReactFlowProjectPayload, build_reactflow_system_xml, compile_reactflow_network, run_system_xml_simulation, ) from app.system_xml import validate_system_xml_document from tests.test_amesim_pnvo001_signal_xml import signal_edge, signal_port from tests.test_generic_system_xml_simulation import component_node, physical_edge from tests.test_system_xml_protocol import physical_port LSTP00A_DEFAULTS = { "na": 10.0, "gap0": 0.0, "kcont": 1000.0, "G": 8.57e10, "sdiam": 0.02, "wdiam": 0.002, "rcont": 0.0, "Pdis": 1.0e-7, "stiffmode": 1.0, "discContactOption": 1.0, } MECMAS21_DEFAULTS = { "mass": 2.0, "fstick": 0.0, "fcoul": 0.0, "rvisc": 0.0, "wind": 0.0, "dvel": 1.0e-6, "restdvel": 1.0e-6, "restcoeff": 0.65, "astrib": 1.0e-3, "xmin": -1.0, "Kbmin": 1.0e9, "Dbmin": 1.0e4, "Pdmin": 1.0e-4, "xmax": 1.0, "Kbmax": 1.0e9, "Dbmax": 1.0e4, "Pdmax": 1.0e-4, "theta": 0.0, "useFriction": 1.0, "stoptype": 4.0, "discContactOption": 1.0, "strib": 1.0, "frictionType": 1.0, "v0": 0.0, "x0": 0.0, } def mechanical_port(name: str, side: str) -> dict[str, str]: return physical_port(name, "bidirectional", side, domain="mechanical") def zero_force_mass_project() -> ReactFlowProjectPayload: return ReactFlowProjectPayload( name="amesim-mechanical-zero-force-smoke", nodes=[ component_node("zero_left", "amesim_f000", [mechanical_port("port_1", "right")]), component_node( "mass_1", "amesim_mecmas21", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], MECMAS21_DEFAULTS, ), component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]), ], edges=[ physical_edge("edge-1", "zero_left", "port_1", "mass_1", "port_1"), physical_edge("edge-2", "mass_1", "port_2", "zero_right", "port_1"), ], simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.01, "method": "BDF"}, ) def signal_force_mass_project() -> ReactFlowProjectPayload: parameters = dict(MECMAS21_DEFAULTS) parameters["mass"] = 2.0 return ReactFlowProjectPayload( name="amesim-mechanical-signal-force-smoke", nodes=[ component_node( "force_signal", "amesim_ud00", [signal_port("out", "output", "right")], { "tstart": 0.0, "start1": 10.0, "end1": 10.0, "t1": 1.0, "start2": 10.0, "end2": 10.0, "t2": 0.0, "start3": 10.0, "end3": 10.0, "t3": 0.0, "start4": 10.0, "end4": 10.0, "t4": 0.0, "start5": 10.0, "end5": 10.0, "t5": 0.0, "start6": 10.0, "end6": 10.0, "t6": 0.0, "start7": 10.0, "end7": 10.0, "t7": 0.0, "start8": 10.0, "end8": 10.0, "t8": 0.0, "nstages": 1.0, "iscyclic": 0.0, }, ), component_node( "force_1", "amesim_forc", [signal_port("res", "input", "left"), mechanical_port("port_2", "right")], ), component_node( "mass_1", "amesim_mecmas21", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], parameters, ), component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]), ], edges=[ signal_edge("signal-1", "force_signal", "out", "force_1", "res"), physical_edge("edge-1", "force_1", "port_2", "mass_1", "port_1"), physical_edge("edge-2", "mass_1", "port_2", "zero_right", "port_1"), ], simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.005, "method": "BDF"}, ) def elastic_contact_project() -> ReactFlowProjectPayload: left_parameters = dict(MECMAS21_DEFAULTS) left_parameters["x0"] = 0.001 right_parameters = dict(MECMAS21_DEFAULTS) right_parameters["x0"] = 0.0 return ReactFlowProjectPayload( name="amesim-mechanical-elastic-contact-smoke", nodes=[ component_node("zero_left", "amesim_f000", [mechanical_port("port_1", "right")]), component_node( "mass_left", "amesim_mecmas21", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], left_parameters, ), component_node( "contact_1", "amesim_lstp00a", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], LSTP00A_DEFAULTS, ), component_node( "mass_right", "amesim_mecmas21", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], right_parameters, ), component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]), ], edges=[ physical_edge("edge-1", "zero_left", "port_1", "mass_left", "port_1"), physical_edge("edge-2", "mass_left", "port_2", "contact_1", "port_1"), physical_edge("edge-3", "contact_1", "port_2", "mass_right", "port_1"), physical_edge("edge-4", "mass_right", "port_2", "zero_right", "port_1"), ], simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.005, "method": "BDF"}, ) def force_node_mass_project() -> ReactFlowProjectPayload: node_ports = [mechanical_port(f"port_{index}", "left") for index in range(1, 9)] node_ports.append(mechanical_port("port_9", "right")) zero_nodes = [ component_node(f"zero_{index}", "amesim_f000", [mechanical_port("port_1", "right")]) for index in range(2, 9) ] zero_edges = [ physical_edge(f"edge-zero-{index}", f"zero_{index}", "port_1", "node_1", f"port_{index}") for index in range(2, 9) ] return ReactFlowProjectPayload( name="amesim-mechanical-node-smoke", nodes=[ component_node( "step_1", "amesim_step0", [signal_port("out", "output", "right")], {"initial": 10.0, "final": 10.0, "time": 0.0}, ), component_node( "force_1", "amesim_forc", [signal_port("res", "input", "left"), mechanical_port("port_2", "right")], ), component_node("node_1", "amesim_lmechn1", node_ports, {"v1": 8.0, "sum": 1.0}), *zero_nodes, component_node( "mass_1", "amesim_mecmas21", [mechanical_port("port_1", "left"), mechanical_port("port_2", "right")], MECMAS21_DEFAULTS, ), component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]), ], edges=[ signal_edge("signal-1", "step_1", "out", "force_1", "res"), physical_edge("edge-force", "force_1", "port_2", "node_1", "port_1"), *zero_edges, physical_edge("edge-mass", "node_1", "port_9", "mass_1", "port_1"), physical_edge("edge-right", "mass_1", "port_2", "zero_right", "port_1"), ], simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.005, "method": "BDF"}, ) class AmesimMechanicalXmlTests(unittest.TestCase): def test_force_node_mass_project_compiles_and_simulates(self) -> None: xml = build_reactflow_system_xml(force_node_mass_project()) report = validate_system_xml_document(xml) self.assertTrue(report.valid, report.as_dict()) network = compile_reactflow_network(force_node_mass_project()) self.assertTrue(network.pressure_flow_structure_dict()["isSquare"]) result = run_system_xml_simulation(xml) self.assertTrue(result["success"], result["message"]) self.assertEqual(result["series"]["step_1.out.signal"], [10.0, 10.0, 10.0]) self.assertEqual(result["series"]["force_1.res.signal"], [10.0, 10.0, 10.0]) self.assertAlmostEqual(result["series"]["mass_1.a"][0], 5.0) self.assertGreater(result["series"]["mass_1.v"][-1], 0.0) self.assertGreater(result["series"]["mass_1.x"][-1], 0.0) def test_elastic_contact_project_compiles_and_simulates(self) -> None: xml = build_reactflow_system_xml(elastic_contact_project()) report = validate_system_xml_document(xml) self.assertTrue(report.valid, report.as_dict()) network = compile_reactflow_network(elastic_contact_project()) self.assertTrue(network.pressure_flow_structure_dict()["isSquare"]) result = run_system_xml_simulation(xml) self.assertTrue(result["success"], result["message"]) self.assertEqual(result["series"]["time"], [0.0, 0.01, 0.02]) self.assertAlmostEqual(result["series"]["contact_1.force"][0], 1.0) self.assertAlmostEqual(result["series"]["mass_left.a"][0], -0.5) self.assertAlmostEqual(result["series"]["mass_right.a"][0], 0.5) self.assertLess(result["series"]["mass_left.x"][-1], 0.001) self.assertGreater(result["series"]["mass_right.x"][-1], 0.0) def test_zero_force_mechanical_project_compiles_and_simulates(self) -> None: xml = build_reactflow_system_xml(zero_force_mass_project()) report = validate_system_xml_document(xml) self.assertTrue(report.valid, report.as_dict()) network = compile_reactflow_network(zero_force_mass_project()) self.assertTrue(network.pressure_flow_structure_dict()["isSquare"]) result = run_system_xml_simulation(xml) self.assertTrue(result["success"], result["message"]) self.assertEqual(result["series"]["time"], [0.0, 0.01, 0.02]) self.assertEqual(result["series"]["mass_1.v"], [0.0, 0.0, 0.0]) self.assertEqual(result["series"]["mass_1.x"], [0.0, 0.0, 0.0]) def test_signal_force_mechanical_project_compiles_and_simulates(self) -> None: xml = build_reactflow_system_xml(signal_force_mass_project()) report = validate_system_xml_document(xml) self.assertTrue(report.valid, report.as_dict()) network = compile_reactflow_network(signal_force_mass_project()) self.assertTrue(network.pressure_flow_structure_dict()["isSquare"]) result = run_system_xml_simulation(xml) self.assertTrue(result["success"], result["message"]) self.assertEqual(result["series"]["force_signal.out.signal"], [10.0, 10.0, 10.0]) self.assertEqual(result["series"]["force_1.res.signal"], [10.0, 10.0, 10.0]) self.assertAlmostEqual(result["series"]["mass_1.a"][0], 5.0) self.assertGreater(result["series"]["mass_1.v"][-1], 0.0) self.assertGreater(result["series"]["mass_1.x"][-1], 0.0) if __name__ == "__main__": unittest.main()