diff --git a/AmesimModels/test_mql/README.md b/AmesimModels/test_mql/README.md index 1687048..0ba836e 100644 --- a/AmesimModels/test_mql/README.md +++ b/AmesimModels/test_mql/README.md @@ -60,43 +60,46 @@ Python 输出只有通过 AMESim baseline 对比后才能作为数值一致性 ## 当前对比结果 -默认 comparison 仍使用 `0 -> 1e-5 s` 的短时域,并已新增 -`dm1@pneumatic_69` 流量诊断。 +默认 comparison 已从区间内插值的 `t=1e-5 s` 改为与 AMESim +首个保存时刻精确对齐的 `t=0.01 s`,当前比较 13 个关键信号,并 +新增 `xv@pn_morifice_1`、`dm2@pn_morifice_1` 的结构化 PNVO 诊断。 -本轮确认原最大偏差的主因不是 PNL0001 Darcy 阻力,而是 8 个 -`PNVO001` 在 Python 中被固定为 `opening=1`,忽略了各自的 -`STEP0` 控制。AMESim baseline 显示 `step_8..15` 均在 -`t=0.04 s` 从 0 跳到 1,对应 PNVO 在此前保持关闭。 +事件前 `t=0.01 s` 对比: -接入 STEP0 控制后的默认短时对比: +- `press@pn_c1_8`:Python `-1286.601221 Pa`,AMESim + `-1288.253185 Pa`,绝对误差约 `1.651964 Pa`。 +- `dm1@pneumatic_69`:Python `-0.002485112 g/s`,AMESim + `-0.002054255 g/s`;换算后的 canonical 流量绝对误差约 + `4.30857e-7 kg/s`。 +- `xv@pn_morifice_1` 和 `dm2@pn_morifice_1` 在两侧均为 0,确认 + STEP0 事件前 PNVO 保持关闭。 +- `vol1@pn_brp2_8` 绝对误差约 `8.76e-5`,机械位移、速度和加速度 + 仍保持较小误差;当前较明显的累计差异集中在 PNL0001 流量和 + PNCH012 压力/能量链路。 -- `press@pn_c1_8` 末值误差从约 `9.22849 Pa` 降为 - `0.0117249 Pa`。 -- Python `dm1@pneumatic_69` 末值约 - `-4.97737e-7 g/s`,AMESim 线性插值约 - `-2.05425e-6 g/s`。 -- canonical chamber-to-line flow 定义为 - `-dm1 * 1e-3`,因此两者分别约为 - `4.97737e-10 kg/s` 和 `2.05425e-9 kg/s`,绝对误差约 - `1.55652e-9 kg/s`。 -- AMESim 首个 `0.01 s` 区间的 `mgas1@pn_c1_8` 有限差分约 - `-9.86349e-7 kg/s`;用该区间首末 `dm1` 做梯形平均得到的 - chamber 质量变化率约 `-1.02713e-6 kg/s`,符号和量级一致。 +新增 `--pnvo-event-boundary` 诊断:先积分到 `0.04 s` 的左极限, +再按 STEP0 的右连续语义在事件时刻读取开度和流量。结果为: -当前剩余 `1e-5 s` 误差不能直接解释为物理方程误差:AMESim -baseline 只按 `0.01 s` 保存,而 comparison 在首个保存区间内做 -线性插值;位移、体积和压力的初始响应并不一定是线性的。 +- `xv@pn_morifice_1`:Python/AMESim 均为 `1`。 +- `dm2@pn_morifice_1`:Python `502.945005 g/s`,AMESim + `497.823823 g/s`,绝对误差约 `5.121182 g/s`,相对约 `1.03%`。 +- `press@pn_c1_8` 绝对误差约 `55.4815 Pa`; + `dm1@pneumatic_69` 绝对误差约 `0.00281880 g/s`。 + +这说明 PNVO 开启瞬间的开度语义和主流量量级已经对齐,但事件前 +累积压力/支路流量仍有偏差。常规积分直接跨越事件到 `0.05 s` 时, +当前进程会被系统终止,尚未形成可信的事件后结果;不能据此声明 +完整 `0.04 -> 0.05 s` 窗口已经可运行。 ## 下一步 -1. 增加 AMESim 保存时刻对齐的诊断,优先比较事件前的 - `t=0.01 s`,避免把 `1e-5 s` 线性插值误差当成物理偏差。 -2. 导出 `xv@pn_morifice_1` 和 `dm2@pn_morifice_1`,检查 - `t=0.04 -> 0.05 s` 开启窗口的 PNVO 流量及事件传播。 -3. 在控制时序对齐后,再判断是否需要校准 PNVO 流量系数、 - `pneumatic_69` 的 PNL0001 阻力或 PNCH012 能量方程。 -4. 继续保留 `press@pn_c1_8`、`dm1@pneumatic_69` 和 chamber - RHS breakdown 作为同一条诊断链。 +1. 优先定位 `0 -> 0.04 s` 累积的 `pneumatic_69` 流量与 + `pn_c1_8` 压力偏差,区分 PNL0001 阻力和 PNCH012 能量方程。 +2. 采用显式事件分段或针对事件后的局部数值策略,解决跨越 + `t=0.04 s` 后积分进程被终止的问题,再验证 `t=0.05 s` 保存点。 +3. 保留 `press@pn_c1_8`、`dm1@pneumatic_69`、PNVO `xv/dm2` + 和 chamber RHS breakdown 作为同一条诊断链。 +4. 在完整开启窗口稳定后,再判断是否需要校准 PNVO 流量系数。 `pn_c1_8` 的直接主线是 `pneumatic_69`;`pneumatic_96` 属于另一条固定气室支路,不是该诊断对象。 @@ -109,6 +112,12 @@ baseline 只按 `0.01 s` 保存,而 comparison 在首个保存区间内做 python3 -m PythonModels.scripts.run_test_mql_full_state_comparison ``` +运行 PNVO 事件边界诊断: + +```bash +python3 -m PythonModels.scripts.run_test_mql_full_state_comparison --pnvo-event-boundary +``` + 运行相关测试: ```bash diff --git a/PythonModels/scripts/run_test_mql_full_state_comparison.py b/PythonModels/scripts/run_test_mql_full_state_comparison.py index 2b65f5d..131a412 100644 --- a/PythonModels/scripts/run_test_mql_full_state_comparison.py +++ b/PythonModels/scripts/run_test_mql_full_state_comparison.py @@ -1,7 +1,9 @@ from __future__ import annotations +import argparse from dataclasses import dataclass, field from datetime import UTC, datetime +from math import nextafter from pathlib import Path from PythonModels.core.solver import SolveIVPConfig @@ -35,6 +37,8 @@ DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = ( "v1@mass_friction_endstops_18", "acc1@mass_friction_endstops_18", "dm1@pneumatic_69", + "xv@pn_morifice_1", + "dm2@pn_morifice_1", ) @@ -68,6 +72,22 @@ class TestMqlFullStateFlowDiagnostic: final_canonical_abs_error_kg_s: float +@dataclass(frozen=True) +class TestMqlFullStatePnvoDiagnostic: + alias: str + initial_time_s: float + final_time_s: float + initial_python_opening: float + initial_amesim_opening: float + final_python_opening: float + final_amesim_opening: float + initial_python_mass_flow_kg_s: float + initial_amesim_mass_flow_kg_s: float + final_python_mass_flow_kg_s: float + final_amesim_mass_flow_kg_s: float + final_mass_flow_abs_error_kg_s: float + + @dataclass(frozen=True) class TestMqlFullStateComparisonRun: system: TestMqlSystem @@ -159,6 +179,33 @@ class TestMqlFullStateComparisonRun: ), ) + def pnvo_diagnostic( + self, + alias: str = "pn_morifice_1", + ) -> TestMqlFullStatePnvoDiagnostic: + opening = self.signal_diagnostic(f"xv@{alias}") + mass_flow = self.signal_diagnostic(f"dm2@{alias}") + initial_python_mass_flow = mass_flow.initial_python_value * 1.0e-3 + initial_amesim_mass_flow = mass_flow.initial_amesim_value * 1.0e-3 + final_python_mass_flow = mass_flow.final_python_value * 1.0e-3 + final_amesim_mass_flow = mass_flow.final_amesim_value * 1.0e-3 + return TestMqlFullStatePnvoDiagnostic( + alias=alias, + initial_time_s=opening.initial_time_s, + final_time_s=opening.final_time_s, + initial_python_opening=opening.initial_python_value, + initial_amesim_opening=opening.initial_amesim_value, + final_python_opening=opening.final_python_value, + final_amesim_opening=opening.final_amesim_value, + initial_python_mass_flow_kg_s=initial_python_mass_flow, + initial_amesim_mass_flow_kg_s=initial_amesim_mass_flow, + final_python_mass_flow_kg_s=final_python_mass_flow, + final_amesim_mass_flow_kg_s=final_amesim_mass_flow, + final_mass_flow_abs_error_kg_s=abs( + final_python_mass_flow - final_amesim_mass_flow + ), + ) + def diagnostics_by_final_abs_error(self): return tuple( sorted( @@ -185,6 +232,22 @@ class TestMqlFullStateComparisonRun: ) +@dataclass(frozen=True) +class TestMqlPnvoEventBoundaryDiagnostic: + orifice_alias: str + event_time_s: float + integration_stop_time_s: float + data_paths: tuple[str, ...] + python_values_by_data_path: dict[str, float] + amesim_values_by_data_path: dict[str, float] + + def abs_error(self, data_path: str) -> float: + return abs( + self.python_values_by_data_path[data_path] + - self.amesim_values_by_data_path[data_path] + ) + + @dataclass(frozen=True) class TestMqlFullStateComparisonPathConfig: archive_path: Path = field( @@ -201,9 +264,9 @@ class TestMqlFullStateComparisonExecutionConfig: write_comparison_csv: bool = True data_paths: tuple[str, ...] | None = DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS solver: SolveIVPConfig = field( - default_factory=lambda: SolveIVPConfig(t_stop=1.0e-5, max_step=1.0e-6) + default_factory=lambda: SolveIVPConfig(t_stop=1.0e-2, max_step=1.0e-3) ) - t_eval: tuple[float, ...] | None = (0.0, 1.0e-5) + t_eval: tuple[float, ...] | None = (0.0, 1.0e-2) inlet_node_pressure_pa: float = 15.31e6 resistance_boundary_pressure_pa: float = 15.29e6 inlet_node_temperature_k: float = 293.15 @@ -306,6 +369,94 @@ def run_test_mql_full_state_comparison( return run, output_dir +def run_test_mql_pnvo_event_boundary_diagnostic( + config: TestMqlFullStateComparisonScriptConfig | None = None, + *, + orifice_alias: str = "pn_morifice_1", +) -> TestMqlPnvoEventBoundaryDiagnostic: + config = config or TestMqlFullStateComparisonScriptConfig() + system = TestMqlSystem(archive_path=config.paths.archive_path) + control = system.pneumatic_assembly.variable_orifice_controls[orifice_alias] + event_time_s = control.step.step_time_s + integration_stop_time_s = nextafter(event_time_s, 0.0) + solver_template = config.execution.solver + solver = SolveIVPConfig( + t_start=solver_template.t_start, + t_stop=integration_stop_time_s, + method=solver_template.method, + rtol=solver_template.rtol, + atol=solver_template.atol, + max_step=solver_template.max_step, + ) + spec = system.discover_pneumatic_branch_topology().chamber_segment_specs[0] + closure_kwargs = { + "inlet_node_pressure_pa": config.execution.inlet_node_pressure_pa, + "resistance_boundary_pressure_pa": ( + config.execution.resistance_boundary_pressure_pa + ), + "inlet_node_temperature_k": config.execution.inlet_node_temperature_k, + "resistance_boundary_temperature_k": ( + config.execution.resistance_boundary_temperature_k + ), + } + solution = system.simulate_full_state_from_spec( + spec, + config=solver, + t_eval=[solver.t_start, integration_stop_time_s], + **closure_kwargs, + ) + state_vector = [row[-1] for row in solution.y] + closure = system.full_state_closure_from_spec(spec, **closure_kwargs) + data_paths = ( + "press@pn_c1_8", + "dm1@pneumatic_69", + f"xv@{orifice_alias}", + f"dm2@{orifice_alias}", + ) + python_values = closure.data_path_values( + time_s=event_time_s, + state_vector=state_vector, + data_paths=data_paths, + ) + amesim_results = load_test_mql_amesim_results(config.paths.archive_path) + amesim_values = { + data_path: interpolate_series_value( + amesim_results.times, + amesim_results.series(data_path), + event_time_s, + ) + for data_path in data_paths + } + return TestMqlPnvoEventBoundaryDiagnostic( + orifice_alias=orifice_alias, + event_time_s=event_time_s, + integration_stop_time_s=integration_stop_time_s, + data_paths=data_paths, + python_values_by_data_path=python_values, + amesim_values_by_data_path=amesim_values, + ) + + +def format_test_mql_pnvo_event_boundary_summary( + diagnostic: TestMqlPnvoEventBoundaryDiagnostic, +) -> str: + lines = [ + "Model: test_mql", + f"Mode: PNVO event boundary diagnostic ({diagnostic.orifice_alias})", + f"Event time: {diagnostic.event_time_s}", + f"Integrated left limit: {diagnostic.integration_stop_time_s}", + "Observation side: right-continuous STEP0 opening", + ] + for data_path in diagnostic.data_paths: + lines.append( + f" - {data_path}: " + f"python={diagnostic.python_values_by_data_path[data_path]}, " + f"amesim={diagnostic.amesim_values_by_data_path[data_path]}, " + f"abs_error={diagnostic.abs_error(data_path)}" + ) + return "\n".join(lines) + "\n" + + def format_test_mql_full_state_comparison_summary( run: TestMqlFullStateComparisonRun, ) -> str: @@ -315,6 +466,10 @@ def format_test_mql_full_state_comparison_summary( f"Samples: {run.sample_count}", f"Compared signals: {run.signal_count}", f"Output schema signals: {run.output_schema.signal_count}", + f"AMESim first saved interval: " + f"{run.amesim_results.times[1] - run.amesim_results.times[0]}", + f"Final time aligns with AMESim sample: " + f"{_matches_amesim_sample(run, run.output.times[-1])}", f"Max absolute error: {run.comparison.max_abs_error}", f"Max relative error: {run.comparison.max_rel_error}", ] @@ -368,6 +523,27 @@ def format_test_mql_full_state_comparison_summary( f"{flow_diagnostic.final_canonical_abs_error_kg_s}", ] ) + pnvo_diagnostic = _pn_morifice_1_diagnostic(run) + if pnvo_diagnostic is not None: + lines.append(f"PNVO diagnostic: {pnvo_diagnostic.alias}") + lines.extend( + [ + f" - initial_python_opening=" + f"{pnvo_diagnostic.initial_python_opening}", + f" - initial_amesim_opening=" + f"{pnvo_diagnostic.initial_amesim_opening}", + f" - final_python_opening=" + f"{pnvo_diagnostic.final_python_opening}", + f" - final_amesim_opening=" + f"{pnvo_diagnostic.final_amesim_opening}", + f" - final_python_mass_flow_kg_s=" + f"{pnvo_diagnostic.final_python_mass_flow_kg_s}", + f" - final_amesim_mass_flow_kg_s=" + f"{pnvo_diagnostic.final_amesim_mass_flow_kg_s}", + f" - final_mass_flow_abs_error_kg_s=" + f"{pnvo_diagnostic.final_mass_flow_abs_error_kg_s}", + ] + ) chamber_diagnostic = _largest_chamber_rhs_diagnostic(run) if chamber_diagnostic is not None: lines.append( @@ -389,6 +565,16 @@ def format_test_mql_full_state_comparison_summary( return "\n".join(lines) + "\n" +def _matches_amesim_sample( + run: TestMqlFullStateComparisonRun, + time_s: float, +) -> bool: + return any( + abs(float(sample_time) - float(time_s)) <= 1.0e-12 + for sample_time in run.amesim_results.times + ) + + def _pneumatic_69_flow_diagnostic(run: TestMqlFullStateComparisonRun): try: return run.pnl0001_mass_flow_diagnostic() @@ -396,6 +582,13 @@ def _pneumatic_69_flow_diagnostic(run: TestMqlFullStateComparisonRun): return None +def _pn_morifice_1_diagnostic(run: TestMqlFullStateComparisonRun): + try: + return run.pnvo_diagnostic() + except KeyError: + return None + + def _largest_chamber_rhs_diagnostic(run: TestMqlFullStateComparisonRun): diagnostic = run.largest_final_abs_error_diagnostic if diagnostic is None or "@" not in diagnostic.data_path: @@ -408,6 +601,18 @@ def _largest_chamber_rhs_diagnostic(run: TestMqlFullStateComparisonRun): def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--pnvo-event-boundary", + action="store_true", + help="compare the STEP0 left-state/right-opening boundary at t=0.04 s", + ) + args = parser.parse_args() + if args.pnvo_event_boundary: + diagnostic = run_test_mql_pnvo_event_boundary_diagnostic() + print(format_test_mql_pnvo_event_boundary_summary(diagnostic), end="") + return + run, output_dir = run_test_mql_full_state_comparison() print(format_test_mql_full_state_comparison_summary(run), end="") print(f"Output directory: {output_dir}") diff --git a/PythonModels/systems/test_mql.py b/PythonModels/systems/test_mql.py index 069c42a..55aabcd 100644 --- a/PythonModels/systems/test_mql.py +++ b/PythonModels/systems/test_mql.py @@ -3909,7 +3909,14 @@ def _default_full_state_data_paths() -> tuple[str, ...]: for signal in ("x1", "v1", "acc1") ] return tuple( - [*chamber_paths, *piston_paths, *mass_paths, "dm1@pneumatic_69"] + [ + *chamber_paths, + *piston_paths, + *mass_paths, + "dm1@pneumatic_69", + "xv@pn_morifice_1", + "dm2@pn_morifice_1", + ] ) @@ -3973,6 +3980,10 @@ _PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS = { "pneumatic_69": "p4_port3_remote_chamber_to_line_flow", } +_VARIABLE_ORIFICE_MASS_FLOW_DIAGNOSTIC_FIELDS = { + "pn_morifice_1": "p4_port3_remote_orifice_to_node_flow", +} + class TestMqlFullStateClosure: def __init__( @@ -4162,6 +4173,21 @@ class TestMqlFullStateClosure: _PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS[alias], ) values_by_data_path[data_path] = -canonical_flow_kg_s * 1.0e3 + elif alias in self.pneumatic_assembly.variable_orifices: + orifice = self.pneumatic_assembly.variable_orifices[alias] + if signal == "xv": + values_by_data_path[data_path] = orifice.opening + elif ( + signal == "dm2" + and alias in _VARIABLE_ORIFICE_MASS_FLOW_DIAGNOSTIC_FIELDS + ): + flow_kg_s = getattr( + snapshot.pneumatic, + _VARIABLE_ORIFICE_MASS_FLOW_DIAGNOSTIC_FIELDS[alias], + ) + values_by_data_path[data_path] = flow_kg_s * 1.0e3 + else: + raise KeyError(data_path) elif alias in chamber_properties_by_alias: chamber_properties = chamber_properties_by_alias[alias] chamber = chamber_by_alias[alias] diff --git a/tests/test_run_test_mql_full_state_comparison.py b/tests/test_run_test_mql_full_state_comparison.py index eb4fb4a..e04aa2f 100644 --- a/tests/test_run_test_mql_full_state_comparison.py +++ b/tests/test_run_test_mql_full_state_comparison.py @@ -9,7 +9,9 @@ from PythonModels.scripts.run_test_mql_full_state_comparison import ( TestMqlFullStateComparisonExecutionConfig, TestMqlFullStateComparisonPathConfig, TestMqlFullStateComparisonScriptConfig, + TestMqlPnvoEventBoundaryDiagnostic, format_test_mql_full_state_comparison_summary, + format_test_mql_pnvo_event_boundary_summary, run_test_mql_full_state_comparison, ) @@ -33,6 +35,8 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): "press@pn_c1_8", "vol1@pn_brp2_8", "dm1@pneumatic_69", + "xv@pn_morifice_1", + "dm2@pn_morifice_1", ), solver=SolveIVPConfig(t_start=0.0, t_stop=0.0), t_eval=(0.0,), @@ -48,7 +52,7 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): self.assertEqual(returned_output_dir, output_dir) self.assertEqual(run.sample_count, 1) - self.assertEqual(run.signal_count, 3) + self.assertEqual(run.signal_count, 5) self.assertAlmostEqual(run.comparison.max_abs_error, 0.0, delta=1.0e-8) self.assertEqual( run.metrics_by_max_abs_error()[0].data_path, @@ -71,6 +75,12 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): flow_diagnostic.initial_python_canonical_kg_s, -flow_diagnostic.initial_python_dm1_g_s * 1.0e-3, ) + pnvo_diagnostic = run.pnvo_diagnostic() + self.assertEqual(pnvo_diagnostic.alias, "pn_morifice_1") + self.assertAlmostEqual(pnvo_diagnostic.initial_python_opening, 0.0) + self.assertAlmostEqual(pnvo_diagnostic.initial_amesim_opening, 0.0) + self.assertAlmostEqual(pnvo_diagnostic.initial_python_mass_flow_kg_s, 0.0) + self.assertAlmostEqual(pnvo_diagnostic.initial_amesim_mass_flow_kg_s, 0.0) chamber_diagnostic = run.chamber_rhs_diagnostic("pn_c1_8") self.assertEqual(chamber_diagnostic.chamber_alias, "pn_c1_8") self.assertEqual(chamber_diagnostic.piston_alias, "pn_brp2_8") @@ -83,7 +93,9 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): ) self.assertTrue(csv_summary_exists) self.assertIn("Mode: Python 132 full-state closure comparison", summary) - self.assertIn("Compared signals: 3", summary) + self.assertIn("Compared signals: 5", summary) + self.assertIn("AMESim first saved interval: 0.010000000000000023", summary) + self.assertIn("Final time aligns with AMESim sample: True", summary) self.assertIn("Largest absolute error: press@pn_c1_8=", summary) self.assertIn("Largest final endpoint error: press@pn_c1_8=", summary) self.assertIn("Metrics by max absolute error:", summary) @@ -93,6 +105,9 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): summary, ) self.assertIn("equals -AMESim dm1 * 1e-3", summary) + self.assertIn("PNVO diagnostic: pn_morifice_1", summary) + self.assertIn("final_python_opening=", summary) + self.assertIn("final_python_mass_flow_kg_s=", summary) self.assertIn("Largest endpoint chamber RHS breakdown: pn_c1_8", summary) self.assertIn("piston_alias=pn_brp2_8", summary) self.assertIn("mass_derivative_kg_s=", summary) @@ -102,6 +117,55 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): self.assertIn("python.press@pn_c1_8", csv_header) self.assertIn("amesim.vol1@pn_brp2_8", csv_header) self.assertIn("python.dm1@pneumatic_69", csv_header) + self.assertIn("python.xv@pn_morifice_1", csv_header) + self.assertIn("python.dm2@pn_morifice_1", csv_header) + + def test_default_execution_aligns_first_amesim_saved_sample(self) -> None: + execution = TestMqlFullStateComparisonExecutionConfig() + + self.assertEqual(execution.solver.t_stop, 0.01) + self.assertEqual(execution.solver.max_step, 1.0e-3) + self.assertEqual(execution.t_eval, (0.0, 0.01)) + self.assertIn("xv@pn_morifice_1", execution.data_paths) + self.assertIn("dm2@pn_morifice_1", execution.data_paths) + + def test_formats_pnvo_event_boundary_diagnostic(self) -> None: + data_paths = ( + "press@pn_c1_8", + "dm1@pneumatic_69", + "xv@pn_morifice_1", + "dm2@pn_morifice_1", + ) + diagnostic = TestMqlPnvoEventBoundaryDiagnostic( + orifice_alias="pn_morifice_1", + event_time_s=0.04, + integration_stop_time_s=0.039999999999999994, + data_paths=data_paths, + python_values_by_data_path={ + "press@pn_c1_8": -1090.9, + "dm1@pneumatic_69": -0.00949, + "xv@pn_morifice_1": 1.0, + "dm2@pn_morifice_1": 502.945, + }, + amesim_values_by_data_path={ + "press@pn_c1_8": -1146.38, + "dm1@pneumatic_69": -0.00667, + "xv@pn_morifice_1": 1.0, + "dm2@pn_morifice_1": 497.824, + }, + ) + + self.assertEqual(diagnostic.orifice_alias, "pn_morifice_1") + self.assertLess(diagnostic.integration_stop_time_s, diagnostic.event_time_s) + self.assertEqual(diagnostic.abs_error("xv@pn_morifice_1"), 0.0) + self.assertAlmostEqual( + diagnostic.abs_error("dm2@pn_morifice_1"), + 5.121, + ) + summary = format_test_mql_pnvo_event_boundary_summary(diagnostic) + self.assertIn("PNVO event boundary diagnostic (pn_morifice_1)", summary) + self.assertIn("Observation side: right-continuous STEP0 opening", summary) + self.assertTrue(summary.endswith("\n")) def test_full_state_script_can_skip_artifact_files(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: diff --git a/tests/test_test_mql_pnl0001_segment.py b/tests/test_test_mql_pnl0001_segment.py index ca80e0d..bf06d55 100644 --- a/tests/test_test_mql_pnl0001_segment.py +++ b/tests/test_test_mql_pnl0001_segment.py @@ -757,6 +757,9 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase): "x1@mass_friction_endstops_18", "v1@mass_friction_endstops_18", "acc1@mass_friction_endstops_18", + "dm1@pneumatic_69", + "xv@pn_morifice_1", + "dm2@pn_morifice_1", ) result = self.system.simulate_full_state_series_from_spec( @@ -788,6 +791,9 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase): -0.8167936695810917, ) self.assertAlmostEqual(result.series["acc1@mass_friction_endstops_18"][0], 0.0) + self.assertAlmostEqual(result.series["dm1@pneumatic_69"][0], 0.0) + self.assertAlmostEqual(result.series["xv@pn_morifice_1"][0], 0.0) + self.assertAlmostEqual(result.series["dm2@pn_morifice_1"][0], 0.0) def test_simulates_full_132_state_closure(self) -> None: solution = self.system.simulate_full_state_from_spec(