Files
SystemSimulationApp/tests/test_run_test_mql_full_state_comparison.py
T

390 lines
17 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.systems.test_mql import TestMqlPnl0001LineRhsDiagnostic
from PythonModels.scripts.run_test_mql_full_state_comparison import (
TestMqlFullStateComparisonExecutionConfig,
TestMqlFullStateComparisonPathConfig,
TestMqlFullStateComparisonScriptConfig,
TestMqlPnl0001PressureLossCalibrationDiagnostic,
TestMqlPnvoEventBoundaryDiagnostic,
TestMqlPnvoEventWindowDiagnostic,
TestMqlPnvoEventWindowSampleDiagnostic,
TestMqlPnvoFlowParameterDiagnostic,
TestMqlPnvoEventWindowSegmentDiagnostic,
format_test_mql_full_state_comparison_summary,
format_test_mql_pnvo_event_boundary_summary,
format_test_mql_pnvo_event_window_summary,
run_test_mql_full_state_comparison,
)
REPO_ROOT = Path(__file__).resolve().parents[1]
TEST_MQL_AME = REPO_ROOT / "AmesimModels" / "test_mql.ame"
class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
def test_full_state_script_writes_summary_and_comparison_csv(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir)
run, returned_output_dir = run_test_mql_full_state_comparison(
TestMqlFullStateComparisonScriptConfig(
paths=TestMqlFullStateComparisonPathConfig(
archive_path=TEST_MQL_AME,
output_dir=output_dir,
),
execution=TestMqlFullStateComparisonExecutionConfig(
data_paths=(
"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,),
),
)
)
summary_path = output_dir / "test_mql_full_state_comparison_summary.txt"
csv_path = output_dir / "test_mql_amesim_comparison.csv"
csv_summary_path = output_dir / "test_mql_amesim_comparison_summary.txt"
summary = summary_path.read_text(encoding="utf-8")
csv_header = csv_path.read_text(encoding="utf-8").splitlines()[0]
csv_summary_exists = csv_summary_path.exists()
self.assertEqual(returned_output_dir, output_dir)
self.assertEqual(run.sample_count, 1)
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,
"press@pn_c1_8",
)
diagnostic = run.signal_diagnostic("press@pn_c1_8")
self.assertEqual(diagnostic.data_path, "press@pn_c1_8")
self.assertAlmostEqual(diagnostic.initial_python_value, -1300.0)
self.assertAlmostEqual(diagnostic.final_python_value, -1300.0)
self.assertAlmostEqual(diagnostic.final_abs_error, 0.0, delta=1.0e-8)
self.assertEqual(
run.diagnostics_by_final_abs_error()[0].data_path,
"press@pn_c1_8",
)
flow_diagnostic = run.pnl0001_mass_flow_diagnostic()
self.assertEqual(flow_diagnostic.data_path, "dm1@pneumatic_69")
self.assertAlmostEqual(flow_diagnostic.initial_python_dm1_g_s, 0.0)
self.assertAlmostEqual(flow_diagnostic.initial_amesim_dm1_g_s, 0.0)
self.assertAlmostEqual(
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")
self.assertAlmostEqual(chamber_diagnostic.chamber_volume_m3, 0.015)
self.assertAlmostEqual(
chamber_diagnostic.energy_derivative_w,
chamber_diagnostic.port_a_energy_flow_w
+ chamber_diagnostic.port_b_energy_flow_w
+ chamber_diagnostic.boundary_work_w
+ chamber_diagnostic.thermal_energy_flow_w,
)
self.assertTrue(csv_summary_exists)
self.assertIn("Mode: Python 132 full-state closure comparison", 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)
self.assertIn("Endpoint diagnostics by final absolute error:", summary)
self.assertIn(
"PNL0001 canonical mass-flow diagnostic: dm1@pneumatic_69",
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)
self.assertIn("boundary_work_w=", summary)
self.assertIn("thermal_energy_flow_w=", summary)
self.assertIn("final_python=", summary)
self.assertIn("final_amesim=", summary)
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_formats_pnvo_event_window_diagnostic_with_samples(self) -> None:
data_paths = (
"press@pn_c1_8",
"temp@pn_c1_8",
"vol@pn_c1_8",
"mgas1@pn_c1_8",
"p2@pneumatic_69",
"t2@pneumatic_69",
"mgas@pneumatic_69",
"re@pneumatic_69",
"v@pneumatic_69",
"ff@pneumatic_69",
"dm1@pneumatic_69",
"xv@pn_morifice_1",
"dm2@pn_morifice_1",
)
sample = TestMqlPnvoEventWindowSampleDiagnostic(
time_s=0.05,
data_paths=data_paths,
python_values_by_data_path={
"press@pn_c1_8": 219127.9,
"temp@pn_c1_8": 372.3,
"vol@pn_c1_8": 15000.0,
"mgas1@pn_c1_8": 5.46,
"p2@pneumatic_69": 93250.0,
"t2@pneumatic_69": 293.15,
"mgas@pneumatic_69": 0.53,
"re@pneumatic_69": 1.41e6,
"v@pneumatic_69": 832.0,
"ff@pneumatic_69": 0.0243,
"dm1@pneumatic_69": 443.2,
"xv@pn_morifice_1": 1.0,
"dm2@pn_morifice_1": 463.7,
},
amesim_values_by_data_path={
"press@pn_c1_8": 187060.0,
"temp@pn_c1_8": 296.1,
"vol@pn_c1_8": 15000.0,
"mgas1@pn_c1_8": 5.47,
"p2@pneumatic_69": 1484689.6,
"t2@pneumatic_69": 296.1,
"mgas@pneumatic_69": 0.81,
"re@pneumatic_69": 1.35e6,
"v@pneumatic_69": 519.8,
"ff@pneumatic_69": 0.0241,
"dm1@pneumatic_69": 419.9,
"xv@pn_morifice_1": 1.0,
"dm2@pn_morifice_1": 456.8,
},
pnl0001_rhs_diagnostics=(
TestMqlPnl0001LineRhsDiagnostic(
line_alias="pneumatic_69",
chamber_alias="pn_c1_8",
line_pressure_pa=1034250.0,
line_temperature_k=293.15,
chamber_pressure_pa=320427.9,
chamber_temperature_k=379.9,
chamber_to_line_flow_kg_s=-0.4432,
node_to_line_flow_kg_s=0.4210,
mass_derivative_kg_s=-0.0222,
port_1_energy_flow_w=-404000.0,
port_2_energy_flow_w=384000.0,
thermal_energy_flow_w=0.0,
energy_derivative_w=-20000.0,
),
),
pnl0001_pressure_loss_diagnostics=(
TestMqlPnl0001PressureLossCalibrationDiagnostic(
line_alias="pneumatic_69",
chamber_alias="pn_c1_8",
time_s=0.05,
amesim_cm=0.0159,
amesim_dm1_g_s=419.9,
amesim_mass_flow_magnitude_kg_s=0.4199,
amesim_line_gauge_pressure_pa=1484689.6,
amesim_chamber_gauge_pressure_pa=187060.0,
amesim_line_temperature_k=296.1,
amesim_pressure_drop_pa=1297629.6,
current_darcy_pressure_drop_pa=422633.2,
pressure_drop_multiplier=3.07,
candidate_pn2pipefr_dm1_g_s=424.2,
candidate_pn2pipefr_to_amesim_dm1_ratio=1.01,
amesim_linear_conductance_kg_s_sqrt_k_per_pa=5.56e-6,
python_linear_conductance_kg_s_sqrt_k_per_pa=1.06e-5,
python_to_amesim_linear_conductance_ratio=1.91,
),
),
pnvo_flow_parameter_diagnostics=(
TestMqlPnvoFlowParameterDiagnostic(
orifice_alias="pn_morifice_1",
time_s=0.05,
amesim_cm=0.0158,
amesim_dm2_g_s=456.8,
amesim_opening=1.0,
amesim_gas_velocity_m_s=885.9,
python_opening=1.0,
python_flow_coefficient=0.45,
python_effective_area_m2=78.5e-6,
python_line_pressure_pa=15310000.0,
python_boundary_pressure_pa=1586000.0,
python_upstream_pressure_pa=15310000.0,
python_upstream_temperature_k=293.15,
python_mass_flow_kg_s=0.4637,
python_cm=0.0147,
python_to_amesim_cm_ratio=0.93,
),
),
)
diagnostic = TestMqlPnvoEventWindowDiagnostic(
orifice_alias="pn_morifice_1",
event_time_s=0.04,
final_time_s=0.05,
data_paths=data_paths,
segment_diagnostics=(
TestMqlPnvoEventWindowSegmentDiagnostic(
t_start=0.048,
t_stop=0.05,
method="BDF",
rtol=1.0e-5,
atol=1.0e-8,
max_step=1.0e-5,
rhs_evaluations=8154,
success=True,
message="success",
),
),
sample_diagnostics=(sample,),
python_values_by_data_path=sample.python_values_by_data_path,
amesim_values_by_data_path=sample.amesim_values_by_data_path,
)
summary = format_test_mql_pnvo_event_window_summary(diagnostic)
self.assertAlmostEqual(diagnostic.abs_error("dm2@pn_morifice_1"), 6.9)
self.assertAlmostEqual(sample.abs_error("press@pn_c1_8"), 32067.9)
self.assertIn("PNVO event window diagnostic (pn_morifice_1)", summary)
self.assertIn("Sample comparisons:", summary)
self.assertIn("t=0.05", summary)
self.assertIn("rhs=8154", summary)
self.assertIn("rhs@pneumatic_69", summary)
self.assertIn("dm_dt=-0.0222", summary)
self.assertIn("pressure_loss@pneumatic_69", summary)
self.assertIn("cm=0.0159", summary)
self.assertIn("dp_multiplier=3.07", summary)
self.assertIn("candidate_pn2pipefr_dm1=424.2", summary)
self.assertIn("candidate_pn2pipefr_ratio=1.01", summary)
self.assertIn("amesim_linear_k=5.56e-06", summary)
self.assertIn("python_linear_k=1.06e-05", summary)
self.assertIn("linear_k_ratio=1.91", summary)
self.assertIn("flow_parameter@pn_morifice_1", summary)
self.assertIn("amesim_cm=0.0158", summary)
self.assertIn("python_cm=0.0147", summary)
self.assertIn("cm_ratio=0.93", summary)
self.assertIn("amesim_gasvel=885.9", summary)
self.assertIn("Final comparison:", summary)
self.assertTrue(summary.endswith("\n"))
def test_full_state_script_can_skip_artifact_files(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir)
run, returned_output_dir = run_test_mql_full_state_comparison(
TestMqlFullStateComparisonScriptConfig(
paths=TestMqlFullStateComparisonPathConfig(
archive_path=TEST_MQL_AME,
output_dir=output_dir,
),
execution=TestMqlFullStateComparisonExecutionConfig(
write_summary=False,
write_comparison_csv=False,
data_paths=("press@pn_c1_8",),
solver=SolveIVPConfig(t_start=0.0, t_stop=0.0),
t_eval=(0.0,),
),
)
)
self.assertEqual(returned_output_dir, output_dir)
self.assertEqual(run.signal_count, 1)
self.assertFalse((output_dir / "test_mql_full_state_comparison_summary.txt").exists())
self.assertFalse((output_dir / "test_mql_amesim_comparison.csv").exists())
def test_format_test_mql_full_state_comparison_summary(self) -> None:
run, _ = run_test_mql_full_state_comparison(
TestMqlFullStateComparisonScriptConfig(
paths=TestMqlFullStateComparisonPathConfig(archive_path=TEST_MQL_AME),
execution=TestMqlFullStateComparisonExecutionConfig(
write_summary=False,
write_comparison_csv=False,
data_paths=("press@pn_c1_8",),
solver=SolveIVPConfig(t_start=0.0, t_stop=0.0),
t_eval=(0.0,),
),
)
)
summary = format_test_mql_full_state_comparison_summary(run)
self.assertIn("Model: test_mql", summary)
self.assertIn("Output schema signals: 858", summary)
self.assertIn("Compared signals: 1", 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)
self.assertIn("Endpoint diagnostics by final absolute error:", summary)
self.assertIn("Largest endpoint chamber RHS breakdown: pn_c1_8", summary)
self.assertTrue(summary.endswith("\n"))
if __name__ == "__main__":
unittest.main()