From f6dda509a2068c7415489899771b6fdd52e1592c Mon Sep 17 00:00:00 2001 From: huojiarong Date: Wed, 15 Jul 2026 10:19:24 +0000 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85test=5Fmql=E8=B4=A8=E9=87=8F?= =?UTF-8?q?=E7=AB=AF=E6=AD=A2=E5=8A=A8=E5=8E=9F=E8=AF=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PythonModels/README.md | 23 +++++++++++++ PythonModels/components/amesim_mechanical.py | 33 ++++++++++++++++++ PythonModels/systems/test_mql_mechanical.py | 34 +++++++++++++++++++ tests/test_amesim_mechanical_components.py | 17 ++++++++++ tests/test_test_mql_mechanical.py | 35 ++++++++++++++++++++ 5 files changed, 142 insertions(+) diff --git a/PythonModels/README.md b/PythonModels/README.md index 35ccea7..e661d2b 100644 --- a/PythonModels/README.md +++ b/PythonModels/README.md @@ -267,6 +267,29 @@ print(result.used_modelica_reference) 这说明在当前基线工况下,Python 版主变量已经能较好贴近 OpenModelica 结果。 +## AMESim test_mql 当前进度 + +`test_mql` 是从 `AmesimModels/test_mql.ame` 新增迁移的 AMESim 模型,当前只在独立路径下推进,不修改旧 `testmodel`。新增命名保持 AMESim 原始别名和 `Data_Path`,方便后续逐变量对齐。 + +当前已经完成: + +- 结构目录:组件清单、连接拓扑、全局参数、仿真设置和变量目录。 +- 结果读取:可直接解析 `.ame` 包内 `test_mql_.var` 和 `test_mql_.results`,并按 AMESim `Data_Path` 索引时序数据。 +- 物性:`test_mql` 使用氦气,物性库已补 Peng-Robinson 状态方程入口。 +- 气动装配:已装配 `PNCH023 / PNCH012 / PNOR001 / PNVO001` 参数目录和首版气动原语。 +- 管路装配:已整理 `PNL0001 / PNL0002 / PNL0003 / PNL00R` 连接目录,保留源/目标组件和端口。 +- 机械装配:已整理 `PNRP17 / MECMAS21 / LSTP00A / LMECHN1 / F000 / FORC` 参数目录。 +- 机械原语:已补 `AmesimPistonGeometry`、`AmesimElasticEndstop`、`AmesimMassFrictionEndstops`,并用 AMESim 结果校验活塞几何、弹性端止动力和质量端口观测方向。 + +当前确认的关键细节: + +- `PNRP17` 活塞腔体积使用环形有效面积 `piston_area - rod_area`。 +- `LSTP00A` 的 `gap` 观测单位是 mm,计算接触力前必须转换为 m。 +- `MECMAS21` 的 `x1dup / v1dup / acc1dup` 是第二机械端口观测,相对 `x1 / v1 / acc1` 为反号,不是重复同值。 +- 本算例中 `MECMAS21` 的 `Fmin / Fmax / Fvisc / Ffric` 在 AMESim 结果里为零;当前只把这一工况能验证的部分写入测试,没有硬猜未激活碰撞/摩擦状态机。 + +当前还不能宣称 `test_mql` 的 Python 时域仿真已经和 AMESim 全局一致。下一步应继续补气动网络动态方程和机械网络闭合,再用 `reporting/test_mql_comparison.py` 对 AMESim `Data_Path` 做全变量误差统计。 + ## 当前架构判断 如果按“组件正确 -> 网络闭合 -> 积分可跑 -> 结果对齐 -> 去近似”来看,当前大致处于: diff --git a/PythonModels/components/amesim_mechanical.py b/PythonModels/components/amesim_mechanical.py index b294a33..1669d22 100644 --- a/PythonModels/components/amesim_mechanical.py +++ b/PythonModels/components/amesim_mechanical.py @@ -92,3 +92,36 @@ class AmesimElasticEndstop: return 0.0 damping_force = self.contact_damping_n_per_m_per_s * penetration_velocity_m_s return max(self.static_contact_force(gap_mm) + damping_force, 0.0) + + +@dataclass(frozen=True) +class AmesimMassFrictionEndstops: + """Parameter and observable helpers for AMESim MECMAS21 translation masses.""" + + mass_kg: float + lower_limit_m: float + upper_limit_m: float + lower_stiffness_n_per_m: float + upper_stiffness_n_per_m: float + lower_damping_n_per_m_per_s: float = 0.0 + upper_damping_n_per_m_per_s: float = 0.0 + viscous_friction_n_per_m_per_s: float = 0.0 + coulomb_friction_n: float = 0.0 + stiction_force_n: float = 0.0 + windage_n_per_m2_per_s2: float = 0.0 + + def lower_penetration_m(self, displacement_m: float) -> float: + return max(self.lower_limit_m - displacement_m, 0.0) + + def upper_penetration_m(self, displacement_m: float) -> float: + return max(displacement_m - self.upper_limit_m, 0.0) + + def lower_static_force_magnitude(self, displacement_m: float) -> float: + return self.lower_stiffness_n_per_m * self.lower_penetration_m(displacement_m) + + def upper_static_force_magnitude(self, displacement_m: float) -> float: + return self.upper_stiffness_n_per_m * self.upper_penetration_m(displacement_m) + + def viscous_friction_force(self, velocity_m_s: float) -> float: + return -self.viscous_friction_n_per_m_per_s * velocity_m_s + diff --git a/PythonModels/systems/test_mql_mechanical.py b/PythonModels/systems/test_mql_mechanical.py index 5d054bb..9c96fca 100644 --- a/PythonModels/systems/test_mql_mechanical.py +++ b/PythonModels/systems/test_mql_mechanical.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from PythonModels.components.amesim_mechanical import ( AmesimElasticEndstop, + AmesimMassFrictionEndstops, AmesimPistonGeometry, circular_area, mm_to_m, @@ -52,9 +53,33 @@ class TestMqlMassEndstopSpec: max_damping_n_per_m_per_s: float min_penetration_m: float max_penetration_m: float + stiction_force_n: float + coulomb_friction_n: float + viscous_friction_n_per_m_per_s: float + windage_n_per_m2_per_s2: float + stick_velocity_threshold_m_s: float + reset_velocity_threshold_m_s: float rest_coeff: float + stribeck_constant_m_s: float + use_friction: bool + stop_type: int data_paths: tuple[str, ...] + def endstop(self) -> AmesimMassFrictionEndstops: + return AmesimMassFrictionEndstops( + mass_kg=self.mass_kg, + lower_limit_m=self.xmin_m, + upper_limit_m=self.xmax_m, + lower_stiffness_n_per_m=self.min_stiffness_n_per_m, + upper_stiffness_n_per_m=self.max_stiffness_n_per_m, + lower_damping_n_per_m_per_s=self.min_damping_n_per_m_per_s, + upper_damping_n_per_m_per_s=self.max_damping_n_per_m_per_s, + viscous_friction_n_per_m_per_s=self.viscous_friction_n_per_m_per_s, + coulomb_friction_n=self.coulomb_friction_n, + stiction_force_n=self.stiction_force_n, + windage_n_per_m2_per_s2=self.windage_n_per_m2_per_s2, + ) + @dataclass(frozen=True) class TestMqlElasticEndstopSpec: @@ -189,7 +214,16 @@ def _build_mass( max_damping_n_per_m_per_s=n_per_mm_per_s_to_n_per_m_per_s(component.parameter_value("Dbmax")), min_penetration_m=mm_to_m(component.parameter_value("Pdmin")), max_penetration_m=mm_to_m(component.parameter_value("Pdmax")), + stiction_force_n=component.parameter_value("fstick"), + coulomb_friction_n=component.parameter_value("fcoul"), + viscous_friction_n_per_m_per_s=component.parameter_value("rvisc"), + windage_n_per_m2_per_s2=component.parameter_value("wind"), + stick_velocity_threshold_m_s=component.parameter_value("dvel"), + reset_velocity_threshold_m_s=component.parameter_value("restdvel"), rest_coeff=component.parameter_value("restcoeff"), + stribeck_constant_m_s=component.parameter_value("astrib"), + use_friction=bool(int(component.parameter_value("useFriction"))), + stop_type=int(component.parameter_value("stoptype")), data_paths=_data_paths(variable_catalog, component.alias), ) diff --git a/tests/test_amesim_mechanical_components.py b/tests/test_amesim_mechanical_components.py index dab4563..1589672 100644 --- a/tests/test_amesim_mechanical_components.py +++ b/tests/test_amesim_mechanical_components.py @@ -5,6 +5,7 @@ from pathlib import Path from PythonModels.components.amesim_mechanical import ( AmesimElasticEndstop, + AmesimMassFrictionEndstops, AmesimPistonGeometry, circular_area, m_to_mm, @@ -76,6 +77,22 @@ class AmesimMechanicalComponentsTest(unittest.TestCase): self.assertEqual(endstop.static_contact_force(0.1), 0.0) self.assertEqual(endstop.penetration_m_from_gap_mm(0.1), 0.0) + def test_mass_endstop_penetration_and_viscous_friction(self) -> None: + mass = AmesimMassFrictionEndstops( + mass_kg=50.0, + lower_limit_m=-1.0, + upper_limit_m=0.8, + lower_stiffness_n_per_m=1.0e9, + upper_stiffness_n_per_m=1.0e9, + viscous_friction_n_per_m_per_s=12.0, + ) + + self.assertEqual(mass.lower_penetration_m(0.0), 0.0) + self.assertEqual(mass.upper_penetration_m(0.0), 0.0) + self.assertAlmostEqual(mass.lower_static_force_magnitude(-1.001), 1.0e6, delta=1.0e-6) + self.assertAlmostEqual(mass.upper_static_force_magnitude(0.801), 1.0e6, delta=1.0e-6) + self.assertAlmostEqual(mass.viscous_friction_force(0.25), -3.0) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_test_mql_mechanical.py b/tests/test_test_mql_mechanical.py index 54ed555..e0268bf 100644 --- a/tests/test_test_mql_mechanical.py +++ b/tests/test_test_mql_mechanical.py @@ -61,7 +61,16 @@ class TestMqlMechanicalAssemblyTests(unittest.TestCase): 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.stiction_force_n, 0.0) + self.assertEqual(mass.coulomb_friction_n, 0.0) + self.assertEqual(mass.viscous_friction_n_per_m_per_s, 0.0) + self.assertEqual(mass.windage_n_per_m2_per_s2, 0.0) + self.assertEqual(mass.stick_velocity_threshold_m_s, 1.0e-6) + self.assertEqual(mass.reset_velocity_threshold_m_s, 1.0e-6) self.assertEqual(mass.rest_coeff, 0.65) + self.assertEqual(mass.stribeck_constant_m_s, 1.0e-3) + self.assertTrue(mass.use_friction) + self.assertEqual(mass.stop_type, 4) self.assertIn("v1@mass_friction_endstops_10", mass.data_paths) self.assertIn("x1@mass_friction_endstops_10", mass.data_paths) @@ -131,6 +140,32 @@ class TestMqlMechanicalAssemblyTests(unittest.TestCase): delta=1.0e-3, ) + def test_mass_endstop_second_port_observables_are_opposite_sign(self) -> None: + for alias in self.assembly.masses: + with self.subTest(alias=alias): + for signal in ("x1", "v1", "acc1"): + primary = self.amesim_results.series(f"{signal}@{alias}") + second_port = self.amesim_results.series(f"{signal}dup@{alias}") + for index in (0, len(primary) // 2, -1): + self.assertAlmostEqual(primary[index], -second_port[index]) + + def test_mass_endstop_force_observables_match_inactive_amesim_contacts(self) -> None: + for alias, mass_spec in self.assembly.masses.items(): + endstop = mass_spec.endstop() + with self.subTest(alias=alias): + x1 = self.amesim_results.series(f"x1@{alias}")[-1] + v1 = self.amesim_results.series(f"v1@{alias}")[-1] + + self.assertAlmostEqual( + endstop.viscous_friction_force(v1), + self.amesim_results.series(f"Fvisc@{alias}")[-1], + ) + self.assertAlmostEqual(self.amesim_results.series(f"Ffric@{alias}")[-1], 0.0) + self.assertAlmostEqual(self.amesim_results.series(f"Fmin@{alias}")[-1], 0.0) + self.assertAlmostEqual(self.amesim_results.series(f"Fmax@{alias}")[-1], 0.0) + self.assertLessEqual(endstop.lower_penetration_m(x1), 1.0e-12) + self.assertLessEqual(endstop.upper_penetration_m(x1), 1.0e-12) + 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)