对齐test_mql保存时刻与PNVO事件边界

This commit is contained in:
huojiarong committed 2026-07-21 07:56:25 +00:00
1 parent 7c68caed27
commit a0855018d5
5 files changed
+345 -35

No files matched your search

+39 -30
View File
@@ -60,43 +60,46 @@ Python 输出只有通过 AMESim baseline 对比后才能作为数值一致性
## 当前对比结果 ## 当前对比结果
默认 comparison 仍使用 `0 -> 1e-5 s` 的短时域,并已新增 默认 comparison 已从区间内插值的 `t=1e-5 s` 改为与 AMESim
`dm1@pneumatic_69` 流量诊断。 首个保存时刻精确对齐的 `t=0.01 s`,当前比较 13 个关键信号,并
新增 `xv@pn_morifice_1`、`dm2@pn_morifice_1` 的结构化 PNVO 诊断。
本轮确认原最大偏差的主因不是 PNL0001 Darcy 阻力,而是 8 个 事件前 `t=0.01 s` 对比:
`PNVO001` 在 Python 中被固定为 `opening=1`,忽略了各自的
`STEP0` 控制。AMESim baseline 显示 `step_8..15` 均在
`t=0.04 s` 从 0 跳到 1,对应 PNVO 在此前保持关闭。
接入 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` 降为 新增 `--pnvo-event-boundary` 诊断:先积分到 `0.04 s` 的左极限,
`0.0117249 Pa`。 再按 STEP0 的右连续语义在事件时刻读取开度和流量。结果为:
- 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`,符号和量级一致。
当前剩余 `1e-5 s` 误差不能直接解释为物理方程误差:AMESim - `xv@pn_morifice_1`:Python/AMESim 均为 `1`。
baseline 只按 `0.01 s` 保存,而 comparison 在首个保存区间内做 - `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 保存时刻对齐的诊断,优先比较事件前的 1. 优先定位 `0 -> 0.04 s` 累积的 `pneumatic_69` 流量与
`t=0.01 s`,避免把 `1e-5 s` 线性插值误差当成物理偏差。 `pn_c1_8` 压力偏差,区分 PNL0001 阻力和 PNCH012 能量方程。
2. 导出 `xv@pn_morifice_1` 和 `dm2@pn_morifice_1`,检查 2. 采用显式事件分段或针对事件后的局部数值策略,解决跨越
`t=0.04 -> 0.05 s` 开启窗口的 PNVO 流量及事件传播。 `t=0.04 s` 后积分进程被终止的问题,再验证 `t=0.05 s` 保存点。
3. 在控制时序对齐后,再判断是否需要校准 PNVO 流量系数、 3. 保留 `press@pn_c1_8`、`dm1@pneumatic_69`、PNVO `xv/dm2`
`pneumatic_69` 的 PNL0001 阻力或 PNCH012 能量方程。 和 chamber RHS breakdown 作为同一条诊断链。
4. 继续保留 `press@pn_c1_8`、`dm1@pneumatic_69` 和 chamber 4. 在完整开启窗口稳定后,再判断是否需要校准 PNVO 流量系数。
RHS breakdown 作为同一条诊断链。
`pn_c1_8` 的直接主线是 `pneumatic_69`;`pneumatic_96` `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 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 ```bash
@@ -1,7 +1,9 @@
from __future__ import annotations from __future__ import annotations
import argparse
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import UTC, datetime from datetime import UTC, datetime
from math import nextafter
from pathlib import Path from pathlib import Path
from PythonModels.core.solver import SolveIVPConfig from PythonModels.core.solver import SolveIVPConfig
@@ -35,6 +37,8 @@ DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = (
"v1@mass_friction_endstops_18", "v1@mass_friction_endstops_18",
"acc1@mass_friction_endstops_18", "acc1@mass_friction_endstops_18",
"dm1@pneumatic_69", "dm1@pneumatic_69",
"xv@pn_morifice_1",
"dm2@pn_morifice_1",
) )
@@ -68,6 +72,22 @@ class TestMqlFullStateFlowDiagnostic:
final_canonical_abs_error_kg_s: float 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) @dataclass(frozen=True)
class TestMqlFullStateComparisonRun: class TestMqlFullStateComparisonRun:
system: TestMqlSystem 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): def diagnostics_by_final_abs_error(self):
return tuple( return tuple(
sorted( 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) @dataclass(frozen=True)
class TestMqlFullStateComparisonPathConfig: class TestMqlFullStateComparisonPathConfig:
archive_path: Path = field( archive_path: Path = field(
@@ -201,9 +264,9 @@ class TestMqlFullStateComparisonExecutionConfig:
write_comparison_csv: bool = True write_comparison_csv: bool = True
data_paths: tuple[str, ...] | None = DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS data_paths: tuple[str, ...] | None = DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS
solver: SolveIVPConfig = field( 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 inlet_node_pressure_pa: float = 15.31e6
resistance_boundary_pressure_pa: float = 15.29e6 resistance_boundary_pressure_pa: float = 15.29e6
inlet_node_temperature_k: float = 293.15 inlet_node_temperature_k: float = 293.15
@@ -306,6 +369,94 @@ def run_test_mql_full_state_comparison(
return run, output_dir 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( def format_test_mql_full_state_comparison_summary(
run: TestMqlFullStateComparisonRun, run: TestMqlFullStateComparisonRun,
) -> str: ) -> str:
@@ -315,6 +466,10 @@ def format_test_mql_full_state_comparison_summary(
f"Samples: {run.sample_count}", f"Samples: {run.sample_count}",
f"Compared signals: {run.signal_count}", f"Compared signals: {run.signal_count}",
f"Output schema signals: {run.output_schema.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 absolute error: {run.comparison.max_abs_error}",
f"Max relative error: {run.comparison.max_rel_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}", 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) chamber_diagnostic = _largest_chamber_rhs_diagnostic(run)
if chamber_diagnostic is not None: if chamber_diagnostic is not None:
lines.append( lines.append(
@@ -389,6 +565,16 @@ def format_test_mql_full_state_comparison_summary(
return "\n".join(lines) + "\n" 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): def _pneumatic_69_flow_diagnostic(run: TestMqlFullStateComparisonRun):
try: try:
return run.pnl0001_mass_flow_diagnostic() return run.pnl0001_mass_flow_diagnostic()
@@ -396,6 +582,13 @@ def _pneumatic_69_flow_diagnostic(run: TestMqlFullStateComparisonRun):
return None 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): def _largest_chamber_rhs_diagnostic(run: TestMqlFullStateComparisonRun):
diagnostic = run.largest_final_abs_error_diagnostic diagnostic = run.largest_final_abs_error_diagnostic
if diagnostic is None or "@" not in diagnostic.data_path: if diagnostic is None or "@" not in diagnostic.data_path:
@@ -408,6 +601,18 @@ def _largest_chamber_rhs_diagnostic(run: TestMqlFullStateComparisonRun):
def main() -> None: 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() run, output_dir = run_test_mql_full_state_comparison()
print(format_test_mql_full_state_comparison_summary(run), end="") print(format_test_mql_full_state_comparison_summary(run), end="")
print(f"Output directory: {output_dir}") print(f"Output directory: {output_dir}")
+27 -1
View File
@@ -3909,7 +3909,14 @@ def _default_full_state_data_paths() -> tuple[str, ...]:
for signal in ("x1", "v1", "acc1") for signal in ("x1", "v1", "acc1")
] ]
return tuple( 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", "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: class TestMqlFullStateClosure:
def __init__( def __init__(
@@ -4162,6 +4173,21 @@ class TestMqlFullStateClosure:
_PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS[alias], _PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS[alias],
) )
values_by_data_path[data_path] = -canonical_flow_kg_s * 1.0e3 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: elif alias in chamber_properties_by_alias:
chamber_properties = chamber_properties_by_alias[alias] chamber_properties = chamber_properties_by_alias[alias]
chamber = chamber_by_alias[alias] chamber = chamber_by_alias[alias]
@@ -9,7 +9,9 @@ from PythonModels.scripts.run_test_mql_full_state_comparison import (
TestMqlFullStateComparisonExecutionConfig, TestMqlFullStateComparisonExecutionConfig,
TestMqlFullStateComparisonPathConfig, TestMqlFullStateComparisonPathConfig,
TestMqlFullStateComparisonScriptConfig, TestMqlFullStateComparisonScriptConfig,
TestMqlPnvoEventBoundaryDiagnostic,
format_test_mql_full_state_comparison_summary, format_test_mql_full_state_comparison_summary,
format_test_mql_pnvo_event_boundary_summary,
run_test_mql_full_state_comparison, run_test_mql_full_state_comparison,
) )
@@ -33,6 +35,8 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
"press@pn_c1_8", "press@pn_c1_8",
"vol1@pn_brp2_8", "vol1@pn_brp2_8",
"dm1@pneumatic_69", "dm1@pneumatic_69",
"xv@pn_morifice_1",
"dm2@pn_morifice_1",
), ),
solver=SolveIVPConfig(t_start=0.0, t_stop=0.0), solver=SolveIVPConfig(t_start=0.0, t_stop=0.0),
t_eval=(0.0,), t_eval=(0.0,),
@@ -48,7 +52,7 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
self.assertEqual(returned_output_dir, output_dir) self.assertEqual(returned_output_dir, output_dir)
self.assertEqual(run.sample_count, 1) 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.assertAlmostEqual(run.comparison.max_abs_error, 0.0, delta=1.0e-8)
self.assertEqual( self.assertEqual(
run.metrics_by_max_abs_error()[0].data_path, 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_canonical_kg_s,
-flow_diagnostic.initial_python_dm1_g_s * 1.0e-3, -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") chamber_diagnostic = run.chamber_rhs_diagnostic("pn_c1_8")
self.assertEqual(chamber_diagnostic.chamber_alias, "pn_c1_8") self.assertEqual(chamber_diagnostic.chamber_alias, "pn_c1_8")
self.assertEqual(chamber_diagnostic.piston_alias, "pn_brp2_8") self.assertEqual(chamber_diagnostic.piston_alias, "pn_brp2_8")
@@ -83,7 +93,9 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
) )
self.assertTrue(csv_summary_exists) self.assertTrue(csv_summary_exists)
self.assertIn("Mode: Python 132 full-state closure comparison", summary) 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 absolute error: press@pn_c1_8=", summary)
self.assertIn("Largest final endpoint 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) self.assertIn("Metrics by max absolute error:", summary)
@@ -93,6 +105,9 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
summary, summary,
) )
self.assertIn("equals -AMESim dm1 * 1e-3", 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("Largest endpoint chamber RHS breakdown: pn_c1_8", summary)
self.assertIn("piston_alias=pn_brp2_8", summary) self.assertIn("piston_alias=pn_brp2_8", summary)
self.assertIn("mass_derivative_kg_s=", 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("python.press@pn_c1_8", csv_header)
self.assertIn("amesim.vol1@pn_brp2_8", csv_header) self.assertIn("amesim.vol1@pn_brp2_8", csv_header)
self.assertIn("python.dm1@pneumatic_69", 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: def test_full_state_script_can_skip_artifact_files(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
+6
View File
@@ -757,6 +757,9 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
"x1@mass_friction_endstops_18", "x1@mass_friction_endstops_18",
"v1@mass_friction_endstops_18", "v1@mass_friction_endstops_18",
"acc1@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( result = self.system.simulate_full_state_series_from_spec(
@@ -788,6 +791,9 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
-0.8167936695810917, -0.8167936695810917,
) )
self.assertAlmostEqual(result.series["acc1@mass_friction_endstops_18"][0], 0.0) 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: def test_simulates_full_132_state_closure(self) -> None:
solution = self.system.simulate_full_state_from_spec( solution = self.system.simulate_full_state_from_spec(