补充test_mql质量端止动原语
This commit is contained in:
1 parent
a63fda313b
commit
f6dda509a2
5 files changed
+142
No files matched your search
@@ -267,6 +267,29 @@ print(result.used_modelica_reference)
|
|||||||
|
|
||||||
这说明在当前基线工况下,Python 版主变量已经能较好贴近 OpenModelica 结果。
|
这说明在当前基线工况下,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` 做全变量误差统计。
|
||||||
|
|
||||||
## 当前架构判断
|
## 当前架构判断
|
||||||
|
|
||||||
如果按“组件正确 -> 网络闭合 -> 积分可跑 -> 结果对齐 -> 去近似”来看,当前大致处于:
|
如果按“组件正确 -> 网络闭合 -> 积分可跑 -> 结果对齐 -> 去近似”来看,当前大致处于:
|
||||||
|
|||||||
@@ -92,3 +92,36 @@ class AmesimElasticEndstop:
|
|||||||
return 0.0
|
return 0.0
|
||||||
damping_force = self.contact_damping_n_per_m_per_s * penetration_velocity_m_s
|
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)
|
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
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
from PythonModels.components.amesim_mechanical import (
|
from PythonModels.components.amesim_mechanical import (
|
||||||
AmesimElasticEndstop,
|
AmesimElasticEndstop,
|
||||||
|
AmesimMassFrictionEndstops,
|
||||||
AmesimPistonGeometry,
|
AmesimPistonGeometry,
|
||||||
circular_area,
|
circular_area,
|
||||||
mm_to_m,
|
mm_to_m,
|
||||||
@@ -52,9 +53,33 @@ class TestMqlMassEndstopSpec:
|
|||||||
max_damping_n_per_m_per_s: float
|
max_damping_n_per_m_per_s: float
|
||||||
min_penetration_m: float
|
min_penetration_m: float
|
||||||
max_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
|
rest_coeff: float
|
||||||
|
stribeck_constant_m_s: float
|
||||||
|
use_friction: bool
|
||||||
|
stop_type: int
|
||||||
data_paths: tuple[str, ...]
|
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)
|
@dataclass(frozen=True)
|
||||||
class TestMqlElasticEndstopSpec:
|
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")),
|
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")),
|
min_penetration_m=mm_to_m(component.parameter_value("Pdmin")),
|
||||||
max_penetration_m=mm_to_m(component.parameter_value("Pdmax")),
|
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"),
|
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),
|
data_paths=_data_paths(variable_catalog, component.alias),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from PythonModels.components.amesim_mechanical import (
|
from PythonModels.components.amesim_mechanical import (
|
||||||
AmesimElasticEndstop,
|
AmesimElasticEndstop,
|
||||||
|
AmesimMassFrictionEndstops,
|
||||||
AmesimPistonGeometry,
|
AmesimPistonGeometry,
|
||||||
circular_area,
|
circular_area,
|
||||||
m_to_mm,
|
m_to_mm,
|
||||||
@@ -76,6 +77,22 @@ class AmesimMechanicalComponentsTest(unittest.TestCase):
|
|||||||
self.assertEqual(endstop.static_contact_force(0.1), 0.0)
|
self.assertEqual(endstop.static_contact_force(0.1), 0.0)
|
||||||
self.assertEqual(endstop.penetration_m_from_gap_mm(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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -61,7 +61,16 @@ class TestMqlMechanicalAssemblyTests(unittest.TestCase):
|
|||||||
self.assertEqual(mass.max_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.min_penetration_m, 1.0e-4)
|
||||||
self.assertAlmostEqual(mass.max_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.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("v1@mass_friction_endstops_10", mass.data_paths)
|
||||||
self.assertIn("x1@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,
|
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:
|
def test_unit_helpers(self) -> None:
|
||||||
self.assertAlmostEqual(mm_to_m(20.0), 0.02)
|
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_to_n_per_m(1.0e6), 1.0e9)
|
||||||
|
|||||||
Reference in new issue
Block a user