diff --git a/PythonModels/systems/test_mql.py b/PythonModels/systems/test_mql.py index a8635ad..bf4f414 100644 --- a/PythonModels/systems/test_mql.py +++ b/PythonModels/systems/test_mql.py @@ -3841,6 +3841,7 @@ class TestMqlSimulationResult: class TestMqlFullStateSnapshot: pneumatic: object mechanical: object + mechanical_node_total_force_by_alias: dict[str, float] @property def state_count(self) -> int: @@ -3906,9 +3907,17 @@ class TestMqlFullStateClosure: def snapshot(self, state_vector: list[float] | None = None) -> TestMqlFullStateSnapshot: values = self.initial_state_vector() if state_vector is None else list(state_vector) pneumatic_state, mechanical_state = self._split_state(values) + pneumatic_snapshot = self.pneumatic_closure.snapshot(pneumatic_state) + mechanical_snapshot = self.mechanical_closure.snapshot(mechanical_state) return TestMqlFullStateSnapshot( - pneumatic=self.pneumatic_closure.snapshot(pneumatic_state), - mechanical=self.mechanical_closure.snapshot(mechanical_state), + pneumatic=pneumatic_snapshot, + mechanical=mechanical_snapshot, + mechanical_node_total_force_by_alias=( + self._mechanical_node_total_force_by_alias( + pneumatic_snapshot, + mechanical_snapshot, + ) + ), ) def rhs(self, state_vector: list[float]) -> list[float]: @@ -3931,6 +3940,31 @@ class TestMqlFullStateClosure: pneumatic_snapshot: object, mechanical_snapshot: object, ) -> dict[str, float]: + force_by_mass_alias = self._piston_force_by_mass_alias(pneumatic_snapshot) + elastic_force_by_mass_alias = self._elastic_endstop_force_by_mass_alias( + mechanical_snapshot + ) + for mass_alias, contact_force in elastic_force_by_mass_alias.items(): + force_by_mass_alias[mass_alias] = ( + force_by_mass_alias.get(mass_alias, 0.0) - contact_force + ) + return force_by_mass_alias + + def _mechanical_node_total_force_by_alias( + self, + pneumatic_snapshot: object, + mechanical_snapshot: object, + ) -> dict[str, float]: + return { + "dynamic_mechanical_node_alternative_2": sum( + self._elastic_endstop_force_by_mass_alias(mechanical_snapshot).values() + ), + "dynamic_mechanical_node_alternative_3": sum( + self._piston_force_by_mass_alias(pneumatic_snapshot).values() + ), + } + + def _piston_force_by_mass_alias(self, pneumatic_snapshot: object) -> dict[str, float]: from PythonModels.systems.test_mql_pneumatic import pressure_to_amesim_gauge_pa force_by_mass_alias: dict[str, float] = {} @@ -3940,8 +3974,14 @@ class TestMqlFullStateClosure: force_by_mass_alias[mass_alias] = ( pressure_to_amesim_gauge_pa(chamber.p) * piston.annulus_area_m2 ) + return force_by_mass_alias + def _elastic_endstop_force_by_mass_alias( + self, + mechanical_snapshot: object, + ) -> dict[str, float]: state_by_alias = {state.alias: state for state in mechanical_snapshot.states} + force_by_mass_alias: dict[str, float] = {} for mass_alias, support_mass_alias, endstop_alias in _ELASTIC_ENDSTOP_FORCE_BINDINGS: mass_state = state_by_alias[mass_alias] support_state = state_by_alias[support_mass_alias] @@ -3952,9 +3992,9 @@ class TestMqlFullStateClosure: penetration_velocity_m_s = ( mass_state.velocity_m_s - support_state.velocity_m_s ) - force_by_mass_alias[mass_alias] = ( - force_by_mass_alias.get(mass_alias, 0.0) - - endstop.contact_force(gap_mm, penetration_velocity_m_s) + force_by_mass_alias[mass_alias] = endstop.contact_force( + gap_mm, + penetration_velocity_m_s, ) return force_by_mass_alias diff --git a/tests/test_test_mql_pnl0001_segment.py b/tests/test_test_mql_pnl0001_segment.py index 5ed27ec..d817264 100644 --- a/tests/test_test_mql_pnl0001_segment.py +++ b/tests/test_test_mql_pnl0001_segment.py @@ -552,6 +552,18 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase): snapshot.mechanical.states[0].alias, "mass_friction_endstops_10", ) + self.assertAlmostEqual( + snapshot.mechanical_node_total_force_by_alias[ + "dynamic_mechanical_node_alternative_2" + ], + 0.0, + ) + self.assertAlmostEqual( + snapshot.mechanical_node_total_force_by_alias[ + "dynamic_mechanical_node_alternative_3" + ], + -326.7174678324367, + ) self.assertAlmostEqual(rhs[112], -0.8167936695810917) def test_full_state_closure_applies_elastic_endstop_force_to_small_masses(self) -> None: @@ -563,6 +575,7 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase): state = closure.initial_state_vector() state[113] = 1.0e-3 + snapshot = closure.snapshot(state) rhs = closure.rhs(state) initial_rhs = closure.rhs(closure.initial_state_vector()) endstop = closure.mechanical_closure.assembly.elastic_endstops[ @@ -577,6 +590,12 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase): rhs[112], initial_rhs[112] - expected_contact_force / mass, ) + self.assertAlmostEqual( + snapshot.mechanical_node_total_force_by_alias[ + "dynamic_mechanical_node_alternative_2" + ], + expected_contact_force, + ) def test_simulates_full_132_state_closure(self) -> None: solution = self.system.simulate_full_state_from_spec(