from __future__ import annotations import unittest from app.simulation.components.amesim.mechanical.translational import ( AmesimF000, AmesimForc, AmesimMecmas21, AmesimLstp00a, AmesimLmechn1, ) from app.simulation.core.medium import IdealGasMedium from app.simulation.registry import COMPONENT_MODEL_REGISTRY class AmesimMechanicalPublicComponentTests(unittest.TestCase): def setUp(self) -> None: self.medium = IdealGasMedium() def test_mecmas21_catalog_ports_follow_amesim_icon_sides(self) -> None: self.assertEqual( tuple((port.name, port.side) for port in AmesimMecmas21.DISPLAY.ports), (("port_2", "left"), ("port_1", "right")), ) def test_f000_constrains_mechanical_port_force_to_zero(self) -> None: source = AmesimF000("zero_1") source.port_1.f = 12.5 residuals = source.pressure_flow_equation_residuals() self.assertEqual(len(residuals), 1) self.assertEqual(residuals[0].variables, ("zero_1.port_1.f",)) self.assertAlmostEqual(residuals[0].value, 12.5) def test_forc_converts_signal_to_opposing_source_port_force(self) -> None: converter = AmesimForc("force_1") converter.res.signal = 20.0 converter.port_2.f = -20.0 residuals = converter.pressure_flow_equation_residuals() self.assertAlmostEqual(converter.output_force, 20.0) self.assertAlmostEqual(residuals[0].value, 0.0) self.assertEqual(converter.component_result_values(), {"force": 20.0}) converter.apply_layout_transform(rotation=180, mirrored=False) converter.port_2.f = 20.0 self.assertAlmostEqual( converter.pressure_flow_equation_residuals()[0].value, 0.0, ) self.assertEqual(converter.component_result_values(), {"force": 20.0}) def test_mecmas21_acceleration_uses_connected_port_forces(self) -> None: mass = AmesimMecmas21( "mass_1", self.medium, mass=2.0, fcoul=0.0, rvisc=0.0, wind=0.0, x0=0.1, v0=0.2, ) mass.port_1.f = 10.0 mass.port_2.f = -2.0 self.assertEqual(mass.get_state_vector(), [0.2, 0.1]) self.assertAlmostEqual(mass.acceleration(), 4.0) self.assertEqual(mass.state_derivative_from_ports({}), [4.0, 0.2]) self.assertAlmostEqual(mass.port_1.x, 0.1) self.assertAlmostEqual(mass.port_2.v, 0.2) def test_lmechn1_ties_active_port_kinematics_and_balances_force(self) -> None: node = AmesimLmechn1("node_1", self.medium, v1=2.0, sum=1.0) node.port_1.x = 0.2 node.port_1.v = 0.3 node.port_1.f = 4.0 node.port_2.x = 0.2 node.port_2.v = 0.3 node.port_2.f = 6.0 node.port_9.x = 0.2 node.port_9.v = 0.3 node.port_9.f = -10.0 residuals = node.pressure_flow_equation_residuals() self.assertEqual(node.active_ports, ("port_1", "port_2", "port_9")) self.assertAlmostEqual(node.total_force, 0.0) self.assertEqual(len(residuals), 5) self.assertTrue(all(abs(residual.value) <= 1.0e-12 for residual in residuals)) self.assertEqual(node.component_result_values(), {"tforce": 0.0}) def test_lmechn1_registry_rejects_fractional_integer_options(self) -> None: with self.assertRaisesRegex(ValueError, "v1 must be an integer"): COMPONENT_MODEL_REGISTRY["amesim_lmechn1"].create( "node_1", self.medium, {"v1": 2.5}, ) def test_lstp00a_generates_opposing_contact_forces(self) -> None: contact = AmesimLstp00a( "contact_1", self.medium, gap0=0.0, kcont=1000.0, rcont=10.0, ) contact.port_1.x = 0.002 contact.port_2.x = 0.0 contact.port_1.v = 0.1 contact.port_2.v = 0.0 contact.port_1.f = 3.0 contact.port_2.f = -3.0 self.assertAlmostEqual(contact.gap, -0.002) self.assertAlmostEqual(contact.penetration, 0.002) self.assertAlmostEqual(contact.contact_force, 3.0) self.assertEqual( contact.component_result_values(), {"gap": -0.002, "penetration": 0.002, "force": 3.0}, ) residuals = contact.pressure_flow_equation_residuals() self.assertAlmostEqual(residuals[0].value, 0.0) self.assertAlmostEqual(residuals[1].value, 0.0) def test_lstp00a_returns_zero_before_contact(self) -> None: contact = AmesimLstp00a("contact_1", self.medium, gap0=0.001, kcont=1000.0) contact.port_1.x = 0.0005 contact.port_2.x = 0.0 self.assertAlmostEqual(contact.gap, 0.0005) self.assertAlmostEqual(contact.contact_force, 0.0) def test_mecmas21_registry_rejects_fractional_integer_options(self) -> None: with self.assertRaisesRegex(ValueError, "useFriction must be an integer"): COMPONENT_MODEL_REGISTRY["amesim_mecmas21"].create( "mass_1", self.medium, {"useFriction": 0.5}, ) if __name__ == "__main__": unittest.main()