from __future__ import annotations import unittest from pathlib import Path from PythonModels.reporting.amesim_results import load_test_mql_amesim_results from PythonModels.reporting.test_mql_variables import build_test_mql_variable_catalog from PythonModels.systems.test_mql_mechanical import ( build_test_mql_mechanical_assembly, circular_area, mm_to_m, n_per_mm_per_s_to_n_per_m_per_s, n_per_mm_to_n_per_m, ) REPO_ROOT = Path(__file__).resolve().parents[1] TEST_MQL_AME = REPO_ROOT / "AmesimModels" / "test_mql.ame" class TestMqlMechanicalAssemblyTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.amesim_results = load_test_mql_amesim_results(TEST_MQL_AME) cls.variable_catalog = build_test_mql_variable_catalog(cls.amesim_results) cls.assembly = build_test_mql_mechanical_assembly( amesim_results=cls.amesim_results, variable_catalog=cls.variable_catalog, ) def test_assembles_expected_mechanical_component_counts(self) -> None: self.assertEqual(len(self.assembly.pistons), 8) self.assertEqual(len(self.assembly.masses), 10) self.assertEqual(len(self.assembly.elastic_endstops), 8) self.assertEqual(len(self.assembly.mechanical_nodes), 2) self.assertEqual(len(self.assembly.zero_force_sources), 16) self.assertEqual(len(self.assembly.force_connectors), 2) self.assertEqual(self.assembly.component_count, 46) def test_piston_geometry_is_converted_to_si_units(self) -> None: piston = self.assembly.pistons["pn_brp2_8"] self.assertAlmostEqual(piston.piston_diameter_m, 0.2) self.assertAlmostEqual(piston.rod_diameter_m, 0.001) self.assertAlmostEqual(piston.zero_displacement_m, 0.0) self.assertAlmostEqual(piston.piston_area_m2, circular_area(0.2)) self.assertAlmostEqual(piston.rod_area_m2, circular_area(0.001)) self.assertAlmostEqual(piston.annulus_area_m2, piston.piston_area_m2 - piston.rod_area_m2) self.assertIn("vvol1@pn_brp2_8", piston.data_paths) self.assertIn("f2@pn_brp2_8", piston.data_paths) def test_mass_endstop_parameters_are_converted_to_si_units(self) -> None: mass = self.assembly.masses["mass_friction_endstops_10"] self.assertEqual(mass.mass_kg, 50.0) self.assertEqual(mass.xmin_m, -1.0) self.assertEqual(mass.xmax_m, 0.8) self.assertEqual(mass.min_stiffness_n_per_m, 1.0e9) self.assertEqual(mass.max_stiffness_n_per_m, 1.0e9) self.assertEqual(mass.min_damping_n_per_m_per_s, 1.0e4) self.assertEqual(mass.max_damping_n_per_m_per_s, 1.0e4) self.assertAlmostEqual(mass.min_penetration_m, 1.0e-4) self.assertAlmostEqual(mass.max_penetration_m, 1.0e-4) self.assertEqual(mass.rest_coeff, 0.65) self.assertIn("v1@mass_friction_endstops_10", mass.data_paths) self.assertIn("x1@mass_friction_endstops_10", mass.data_paths) def test_large_load_masses_keep_distinct_limits(self) -> None: front = self.assembly.masses["mass_friction_endstops_18"] rear = self.assembly.masses["mass_friction_endstops_19"] self.assertEqual(front.mass_kg, 170000.0) self.assertEqual(front.xmin_m, 0.0) self.assertEqual(front.xmax_m, 0.37) self.assertEqual(rear.mass_kg, 90000.0) self.assertEqual(rear.xmin_m, -0.72) self.assertEqual(rear.xmax_m, 0.0) def test_elastic_endstop_parameters_are_converted_to_si_units(self) -> None: endstop = self.assembly.elastic_endstops["elasticendstop_8"] self.assertEqual(endstop.gap_m, 0.0) self.assertEqual(endstop.contact_stiffness_n_per_m, 1.0e11) self.assertEqual(endstop.contact_damping_n_per_m_per_s, 1.0e11) self.assertAlmostEqual(endstop.spring_diameter_m, 0.02) self.assertAlmostEqual(endstop.wire_diameter_m, 0.002) def test_mechanical_nodes_keep_port_count(self) -> None: node = self.assembly.mechanical_nodes["dynamic_mechanical_node_alternative_2"] self.assertEqual(node.port_count, 8) self.assertEqual(node.sum_mode, 1) self.assertIn("dynamic_mechanical_node_alternative_2", self.assembly.aliases) def test_all_piston_geometry_observables_match_amesim_final_sample(self) -> None: for alias, piston in self.assembly.pistons.items(): with self.subTest(alias=alias): geometry = piston.geometry() x4 = self.amesim_results.series(f"x4@{alias}")[-1] x5 = self.amesim_results.series(f"x5@{alias}")[-1] v4 = self.amesim_results.series(f"v4@{alias}")[-1] v5 = self.amesim_results.series(f"v5@{alias}")[-1] self.assertAlmostEqual( geometry.chamber_length_mm(x4, x5), self.amesim_results.series(f"length@{alias}")[-1], ) self.assertAlmostEqual( geometry.chamber_volume_cm3(x4, x5), self.amesim_results.series(f"vol1@{alias}")[-1], delta=1.0e-6, ) self.assertAlmostEqual( geometry.chamber_volume_rate_l_min(v4, v5), self.amesim_results.series(f"vvol1@{alias}")[-1], delta=1.0e-8, ) def test_all_elastic_endstop_forces_match_amesim_final_sample(self) -> None: for endstop_alias, endstop_spec in self.assembly.elastic_endstops.items(): piston_alias = endstop_alias.replace("elasticendstop", "pn_brp2") with self.subTest(alias=endstop_alias): endstop = endstop_spec.endstop() gap_mm = self.amesim_results.series(f"gap@{endstop_alias}")[-1] penetration_velocity_m_s = self.amesim_results.series(f"v5@{piston_alias}")[-1] self.assertAlmostEqual( endstop.contact_force(gap_mm, penetration_velocity_m_s), self.amesim_results.series(f"f1@{endstop_alias}")[-1], delta=1.0e-3, ) def test_unit_helpers(self) -> None: self.assertAlmostEqual(mm_to_m(20.0), 0.02) self.assertAlmostEqual(n_per_mm_to_n_per_m(1.0e6), 1.0e9) self.assertAlmostEqual(n_per_mm_per_s_to_n_per_m_per_s(10.0), 1.0e4) if __name__ == "__main__": unittest.main()