From 33c8bfae3d66f463668f9387988e1ed5fed7c8f8 Mon Sep 17 00:00:00 2001 From: huojiarong Date: Wed, 15 Jul 2026 09:43:34 +0000 Subject: [PATCH] =?UTF-8?q?=E8=A3=85=E9=85=8Dtest=5Fmql=E6=B0=94=E5=8A=A8?= =?UTF-8?q?=E5=85=83=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AmesimModels/test_mql/README.md | 12 +- PythonModels/systems/test_mql_pneumatic.py | 142 +++++++++++++++++++++ tests/test_test_mql_pneumatic.py | 68 ++++++++++ 3 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 PythonModels/systems/test_mql_pneumatic.py create mode 100644 tests/test_test_mql_pneumatic.py diff --git a/AmesimModels/test_mql/README.md b/AmesimModels/test_mql/README.md index 5e8bfc0..51fde86 100644 --- a/AmesimModels/test_mql/README.md +++ b/AmesimModels/test_mql/README.md @@ -63,6 +63,14 @@ - `tests/test_test_mql_comparison.py` - 保护 AMESim 原始序列零误差对齐、粗时间步插值、偏移误差统计、缺失变量报错和 CSV 输出。 +- `PythonModels/systems/test_mql_pneumatic.py` + - 把 AMESim 配置中的 `PNCH023`、`PNCH012`、`PNOR001`、`PNVO001` 装配成 Python 气动对象。 + - 当前覆盖 4 个固定气室、8 个变容气室、8 个固定孔口、8 个可变孔口,alias 沿用 AMESim 原名。 + - 初始压力按 AMESim 全局参数 `P0=153 bar` 和大气压 `1.013 bar` 转成 `15198700 Pa`。 + +- `tests/test_test_mql_pneumatic.py` + - 保护气动装配数量、alias、氦气工质、气室体积、孔口面积/流量系数和基础质量流量计算。 + ## 物性约定 AMESim 模型中 `test_mql` 使用氦气,Python 侧当前通过 `HELIUM_PR` 使用 Peng-Robinson 状态方程计算气体压缩因子和密度。当前物性层先覆盖状态方程相关量,完整焓/内能偏差函数后续在接气室能量方程时再补。 @@ -109,12 +117,12 @@ AMESim 模型中 `test_mql` 使用氦气,Python 侧当前通过 `HELIUM_PR` ## 验证方式 ```bash -python3 -m py_compile PythonModels/components/amesim_pneumatic.py PythonModels/core/peng_robinson.py PythonModels/reporting/amesim_results.py PythonModels/reporting/test_mql_comparison.py PythonModels/systems/test_mql.py PythonModels/systems/test_mql_config.py PythonModels/scripts/run_test_mql.py +python3 -m py_compile PythonModels/components/amesim_pneumatic.py PythonModels/core/peng_robinson.py PythonModels/reporting/amesim_results.py PythonModels/reporting/test_mql_comparison.py PythonModels/systems/test_mql.py PythonModels/systems/test_mql_config.py PythonModels/systems/test_mql_pneumatic.py PythonModels/scripts/run_test_mql.py python3 -m PythonModels.scripts.run_test_mql python3 -m unittest discover -s tests -t . ``` -当前测试覆盖的是结构、配置解析、气动原语、AMESim 结果读取和 AMESim/Python 时序对齐工具,不代表已经完成 AMESim 物理结果复刻。 +当前测试覆盖的是结构、配置解析、气动原语、气动件装配、AMESim 结果读取和 AMESim/Python 时序对齐工具,不代表已经完成 AMESim 物理结果复刻。 ## 后续方向 diff --git a/PythonModels/systems/test_mql_pneumatic.py b/PythonModels/systems/test_mql_pneumatic.py new file mode 100644 index 0000000..bc35412 --- /dev/null +++ b/PythonModels/systems/test_mql_pneumatic.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from PythonModels.components.amesim_pneumatic import ( + HELIUM_PNEUMATIC_GAS, + AmesimPneumaticGas, + AmesimPneumaticOrifice, + AmesimPneumaticVolume, +) +from PythonModels.systems.test_mql_config import TestMqlConfig, TestMqlResolvedComponent + + +ATMOSPHERIC_PRESSURE_BAR = 1.013 +BAR_TO_PA = 1.0e5 +DEFAULT_TEST_MQL_TEMPERATURE_K = 293.15 + + +@dataclass(frozen=True) +class TestMqlPneumaticAssembly: + fixed_chambers: dict[str, AmesimPneumaticVolume] + variable_chambers: dict[str, AmesimPneumaticVolume] + fixed_orifices: dict[str, AmesimPneumaticOrifice] + variable_orifices: dict[str, AmesimPneumaticOrifice] + initial_pressure_pa: float + + @property + def chamber_count(self) -> int: + return len(self.fixed_chambers) + len(self.variable_chambers) + + @property + def orifice_count(self) -> int: + return len(self.fixed_orifices) + len(self.variable_orifices) + + @property + def component_count(self) -> int: + return self.chamber_count + self.orifice_count + + @property + def aliases(self) -> tuple[str, ...]: + return tuple( + [ + *self.fixed_chambers, + *self.variable_chambers, + *self.fixed_orifices, + *self.variable_orifices, + ] + ) + + +def build_test_mql_pneumatic_assembly( + config: TestMqlConfig | None = None, + gas: AmesimPneumaticGas = HELIUM_PNEUMATIC_GAS, +) -> TestMqlPneumaticAssembly: + config = config or TestMqlConfig.from_amesim_specs() + initial_pressure_pa = pressure_from_amesim_bar_parameter(config.global_parameters["P0"]) + fixed_chambers = { + component.alias: _build_chamber( + component, + volume_parameter="cvol", + gas=gas, + initial_pressure_pa=initial_pressure_pa, + ) + for component in config.components_by_submodel("PNCH023") + } + variable_chambers = { + component.alias: _build_chamber( + component, + volume_parameter="cvol0", + gas=gas, + initial_pressure_pa=initial_pressure_pa, + ) + for component in config.components_by_submodel("PNCH012") + } + fixed_orifices = { + component.alias: _build_orifice( + component, + area_parameter="area", + gas=gas, + opening=1.0, + ) + for component in config.components_by_submodel("PNOR001") + } + variable_orifices = { + component.alias: _build_orifice( + component, + area_parameter="area0", + gas=gas, + opening=1.0, + ) + for component in config.components_by_submodel("PNVO001") + } + return TestMqlPneumaticAssembly( + fixed_chambers=fixed_chambers, + variable_chambers=variable_chambers, + fixed_orifices=fixed_orifices, + variable_orifices=variable_orifices, + initial_pressure_pa=initial_pressure_pa, + ) + + +def pressure_from_amesim_bar_parameter(pressure_bar: float) -> float: + return (pressure_bar - ATMOSPHERIC_PRESSURE_BAR) * BAR_TO_PA + + +def _build_chamber( + component: TestMqlResolvedComponent, + *, + volume_parameter: str, + gas: AmesimPneumaticGas, + initial_pressure_pa: float, +) -> AmesimPneumaticVolume: + return AmesimPneumaticVolume.from_liters( + name=component.alias, + volume_liters=component.parameter_value(volume_parameter), + gas=gas, + p0=initial_pressure_pa, + T0=_component_temperature(component), + ) + + +def _build_orifice( + component: TestMqlResolvedComponent, + *, + area_parameter: str, + gas: AmesimPneumaticGas, + opening: float, +) -> AmesimPneumaticOrifice: + return AmesimPneumaticOrifice.from_mm2( + name=component.alias, + area_mm2=component.parameter_value(area_parameter), + flow_coefficient=component.parameter_value("cq"), + gas=gas, + opening=opening, + ) + + +def _component_temperature(component: TestMqlResolvedComponent) -> float: + parameter = component.parameters.get("extemp") + if parameter is None or parameter.value is None: + return DEFAULT_TEST_MQL_TEMPERATURE_K + return parameter.value diff --git a/tests/test_test_mql_pneumatic.py b/tests/test_test_mql_pneumatic.py new file mode 100644 index 0000000..501335b --- /dev/null +++ b/tests/test_test_mql_pneumatic.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import unittest + +from PythonModels.components.amesim_pneumatic import HELIUM_PNEUMATIC_GAS +from PythonModels.systems.test_mql_pneumatic import ( + build_test_mql_pneumatic_assembly, + pressure_from_amesim_bar_parameter, +) + + +class TestMqlPneumaticAssemblyTests(unittest.TestCase): + def test_assembles_expected_amesim_pneumatic_component_counts(self) -> None: + assembly = build_test_mql_pneumatic_assembly() + + self.assertEqual(len(assembly.fixed_chambers), 4) + self.assertEqual(len(assembly.variable_chambers), 8) + self.assertEqual(len(assembly.fixed_orifices), 8) + self.assertEqual(len(assembly.variable_orifices), 8) + self.assertEqual(assembly.component_count, 28) + self.assertIn("pn_general_chamber", assembly.aliases) + self.assertIn("pn_c1_8", assembly.aliases) + self.assertIn("pn_orifice_18", assembly.aliases) + self.assertIn("pn_morifice_11", assembly.aliases) + + def test_uses_amesim_global_initial_pressure_convention(self) -> None: + assembly = build_test_mql_pneumatic_assembly() + + self.assertAlmostEqual( + pressure_from_amesim_bar_parameter(153.0), + 15198700.0, + ) + self.assertAlmostEqual(assembly.initial_pressure_pa, 15198700.0) + + def test_chamber_parameters_are_resolved_in_si_units(self) -> None: + assembly = build_test_mql_pneumatic_assembly() + fixed_chamber = assembly.fixed_chambers["pn_general_chamber"] + variable_chamber = assembly.variable_chambers["pn_c1_8"] + + self.assertAlmostEqual(fixed_chamber.volume, 0.057) + self.assertAlmostEqual(variable_chamber.volume, 0.015) + self.assertIs(fixed_chamber.gas, HELIUM_PNEUMATIC_GAS) + self.assertIs(variable_chamber.gas, HELIUM_PNEUMATIC_GAS) + self.assertAlmostEqual(fixed_chamber.properties().T, 293.15) + self.assertAlmostEqual(variable_chamber.properties().T, 293.15) + + def test_orifice_parameters_are_resolved_in_si_units(self) -> None: + assembly = build_test_mql_pneumatic_assembly() + fixed_orifice = assembly.fixed_orifices["pn_orifice_18"] + variable_orifice = assembly.variable_orifices["pn_morifice_11"] + + self.assertAlmostEqual(fixed_orifice.area, 78.5e-6) + self.assertAlmostEqual(fixed_orifice.flow_coefficient, 0.9) + self.assertAlmostEqual(variable_orifice.area, 78.5e-6) + self.assertAlmostEqual(variable_orifice.flow_coefficient, 0.45) + self.assertAlmostEqual(variable_orifice.opening, 1.0) + + def test_assembled_orifice_can_compute_helium_mass_flow(self) -> None: + assembly = build_test_mql_pneumatic_assembly() + fixed_orifice = assembly.fixed_orifices["pn_orifice_18"] + + flow = fixed_orifice.mass_flow(15.0e6, 10.0e6, 293.15) + + self.assertGreater(flow, 0.0) + + +if __name__ == "__main__": + unittest.main()