merge/model-development-into-main #2

Merged
lujingze merged 126 commits from merge/model-development-into-main into main 2026-07-31 09:52:44 +08:00
5 changed files with 103 additions and 3 deletions
Showing only changes of commit 01b606df30 - Show all commits

No files matched your search

@@ -70,6 +70,24 @@ class AmesimPneumaticGas:
) )
) )
def pressure_transport_enthalpy(
self,
pressure: float,
temperature: float,
reference_pressure: float = 101_300.0,
reference_temperature: float = 298.15,
) -> float:
"""Convert AMESim reference enthalpy to the absolute-energy state basis."""
return (
self.pressure_reference_enthalpy(
pressure,
temperature,
reference_pressure,
reference_temperature,
)
+ self.cp * reference_temperature
)
def temperature_from_internal_energy(self, specific_internal_energy: float) -> float: def temperature_from_internal_energy(self, specific_internal_energy: float) -> float:
if self.cv <= 0.0: if self.cv <= 0.0:
raise ValueError("cv must be positive.") raise ValueError("cv must be positive.")
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from bisect import bisect_left
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import UTC, datetime from datetime import UTC, datetime
from math import nextafter, sqrt from math import nextafter, sqrt
@@ -492,6 +493,8 @@ class TestMqlPnvoEventWindowSampleDiagnostic:
data_paths: tuple[str, ...] data_paths: tuple[str, ...]
python_values_by_data_path: dict[str, float] python_values_by_data_path: dict[str, float]
amesim_values_by_data_path: dict[str, float] amesim_values_by_data_path: dict[str, float]
amesim_sample_left_time_s: float
amesim_sample_right_time_s: float
pnl0001_rhs_diagnostics: tuple[TestMqlPnl0001LineRhsDiagnostic, ...] = field( pnl0001_rhs_diagnostics: tuple[TestMqlPnl0001LineRhsDiagnostic, ...] = field(
default_factory=tuple default_factory=tuple
) )
@@ -517,6 +520,10 @@ class TestMqlPnvoEventWindowSampleDiagnostic:
TestMqlPnch012EnergyEquationDiagnostic, ... TestMqlPnch012EnergyEquationDiagnostic, ...
] = field(default_factory=tuple) ] = field(default_factory=tuple)
@property
def amesim_values_are_interpolated(self) -> bool:
return self.amesim_sample_left_time_s != self.amesim_sample_right_time_s
def abs_error(self, data_path: str) -> float: def abs_error(self, data_path: str) -> float:
return abs( return abs(
self.python_values_by_data_path[data_path] self.python_values_by_data_path[data_path]
@@ -929,6 +936,27 @@ def _series_one_sided_differences_at(
return backward, forward return backward, forward
def _series_sample_bracket(
*,
times: tuple[float, ...] | list[float],
time_s: float,
) -> tuple[float, float]:
if not times:
raise ValueError("at least one sample time is required")
index = bisect_left(times, time_s)
tolerance = 1.0e-12 * max(1.0, abs(time_s))
for candidate_index in (index - 1, index):
if 0 <= candidate_index < len(times):
candidate_time = times[candidate_index]
if abs(candidate_time - time_s) <= tolerance:
return candidate_time, candidate_time
if index <= 0:
return times[0], times[0]
if index >= len(times):
return times[-1], times[-1]
return times[index - 1], times[index]
def _reference_enthalpy_flow_for_node_port( def _reference_enthalpy_flow_for_node_port(
*, *,
gas: AmesimPneumaticGas, gas: AmesimPneumaticGas,
@@ -2157,12 +2185,18 @@ def run_test_mql_pnvo_event_window_diagnostic(
time_s=sample_time, time_s=sample_time,
), ),
) )
(
amesim_sample_left_time_s,
amesim_sample_right_time_s,
) = _series_sample_bracket(times=amesim_results.times, time_s=sample_time)
sample_diagnostics.append( sample_diagnostics.append(
TestMqlPnvoEventWindowSampleDiagnostic( TestMqlPnvoEventWindowSampleDiagnostic(
time_s=sample_time, time_s=sample_time,
data_paths=data_paths, data_paths=data_paths,
python_values_by_data_path=python_sample_values, python_values_by_data_path=python_sample_values,
amesim_values_by_data_path=amesim_sample_values, amesim_values_by_data_path=amesim_sample_values,
amesim_sample_left_time_s=amesim_sample_left_time_s,
amesim_sample_right_time_s=amesim_sample_right_time_s,
pnl0001_rhs_diagnostics=pnl0001_rhs_diagnostics, pnl0001_rhs_diagnostics=pnl0001_rhs_diagnostics,
pnl0001_pressure_loss_diagnostics=pnl0001_pressure_loss_diagnostics, pnl0001_pressure_loss_diagnostics=pnl0001_pressure_loss_diagnostics,
pnvo_flow_parameter_diagnostics=pnvo_flow_parameter_diagnostics, pnvo_flow_parameter_diagnostics=pnvo_flow_parameter_diagnostics,
@@ -2224,7 +2258,17 @@ def format_test_mql_pnvo_event_window_summary(
if diagnostic.sample_diagnostics: if diagnostic.sample_diagnostics:
lines.append("Sample comparisons:") lines.append("Sample comparisons:")
for sample in diagnostic.sample_diagnostics: for sample in diagnostic.sample_diagnostics:
lines.append(f" t={sample.time_s}") if sample.amesim_values_are_interpolated:
amesim_sample_source = (
"linear-interpolation@"
f"{sample.amesim_sample_left_time_s}"
f"..{sample.amesim_sample_right_time_s}"
)
else:
amesim_sample_source = f"exact@{sample.amesim_sample_left_time_s}"
lines.append(
f" t={sample.time_s} (amesim={amesim_sample_source})"
)
for data_path in sample.data_paths: for data_path in sample.data_paths:
lines.append( lines.append(
f" - {data_path}: " f" - {data_path}: "
+11
View File
@@ -55,6 +55,17 @@ class AmesimPneumaticComponentsTest(unittest.TestCase):
delta=0.1, delta=0.1,
) )
def test_pressure_transport_enthalpy_restores_absolute_energy_offset(self) -> None:
gas = HELIUM_PNEUMATIC_GAS
pressure = 13_839_965.0
temperature = 287.7322
self.assertAlmostEqual(
gas.pressure_transport_enthalpy(pressure, temperature),
gas.pressure_reference_enthalpy(pressure, temperature)
+ gas.cp * 298.15,
)
def test_reference_temperature_from_enthalpy_rejects_non_positive_cp(self) -> None: def test_reference_temperature_from_enthalpy_rejects_non_positive_cp(self) -> None:
gas = AmesimPneumaticGas(cp=0.0, cv=3116.0) gas = AmesimPneumaticGas(cp=0.0, cv=3116.0)
@@ -26,6 +26,7 @@ from PythonModels.scripts.run_test_mql_full_state_comparison import (
format_test_mql_pnvo_event_boundary_summary, format_test_mql_pnvo_event_boundary_summary,
format_test_mql_pnvo_event_window_summary, format_test_mql_pnvo_event_window_summary,
run_test_mql_full_state_comparison, run_test_mql_full_state_comparison,
_series_sample_bracket,
) )
@@ -34,6 +35,23 @@ TEST_MQL_AME = REPO_ROOT / "AmesimModels" / "test_mql.ame"
class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
def test_series_sample_bracket_distinguishes_exact_and_interpolated_times(self) -> None:
times = (0.04000000000000009, 0.050000000000000114)
exact_left, exact_right = _series_sample_bracket(
times=times,
time_s=0.04,
)
interpolated_left, interpolated_right = _series_sample_bracket(
times=times,
time_s=0.041,
)
self.assertEqual(exact_left, exact_right)
self.assertAlmostEqual(exact_left, 0.04)
self.assertAlmostEqual(interpolated_left, 0.04)
self.assertAlmostEqual(interpolated_right, 0.05)
def test_full_state_script_writes_summary_and_comparison_csv(self) -> None: def test_full_state_script_writes_summary_and_comparison_csv(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) output_dir = Path(tmpdir)
@@ -231,6 +249,8 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
"xv@pn_morifice_1": 1.0, "xv@pn_morifice_1": 1.0,
"dm2@pn_morifice_1": 456.8, "dm2@pn_morifice_1": 456.8,
}, },
amesim_sample_left_time_s=0.05,
amesim_sample_right_time_s=0.05,
pnl0001_rhs_diagnostics=( pnl0001_rhs_diagnostics=(
TestMqlPnl0001LineRhsDiagnostic( TestMqlPnl0001LineRhsDiagnostic(
line_alias="pneumatic_69", line_alias="pneumatic_69",
@@ -497,7 +517,8 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
self.assertAlmostEqual(sample.abs_error("press@pn_c1_8"), 32067.9) self.assertAlmostEqual(sample.abs_error("press@pn_c1_8"), 32067.9)
self.assertIn("PNVO event window diagnostic (pn_morifice_1)", summary) self.assertIn("PNVO event window diagnostic (pn_morifice_1)", summary)
self.assertIn("Sample comparisons:", summary) self.assertIn("Sample comparisons:", summary)
self.assertIn("t=0.05", summary) self.assertFalse(sample.amesim_values_are_interpolated)
self.assertIn("t=0.05 (amesim=exact@0.05)", summary)
self.assertIn("rhs=8154", summary) self.assertIn("rhs=8154", summary)
self.assertIn("rhs@pneumatic_69", summary) self.assertIn("rhs@pneumatic_69", summary)
self.assertIn("dm_dt=-0.0222", summary) self.assertIn("dm_dt=-0.0222", summary)
+7 -1
View File
@@ -8,6 +8,9 @@ from PythonModels.reporting.test_mql_orifice_observations import (
G_PER_S_TO_KG_PER_S, G_PER_S_TO_KG_PER_S,
build_test_mql_orifice_observation_catalog, build_test_mql_orifice_observation_catalog,
) )
from PythonModels.systems.test_mql_pneumatic import (
TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
)
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -55,7 +58,10 @@ class TestMqlOrificeObservationCatalogTests(unittest.TestCase):
last = binding.observation_at(self.amesim_results, -1) last = binding.observation_at(self.amesim_results, -1)
self.assertAlmostEqual(binding.nominal_area_m2, 78.5e-6) self.assertAlmostEqual(binding.nominal_area_m2, 78.5e-6)
self.assertAlmostEqual(binding.flow_coefficient, 0.45) self.assertAlmostEqual(
binding.flow_coefficient,
0.45 * TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
)
self.assertEqual(first.opening, 0.0) self.assertEqual(first.opening, 0.0)
self.assertEqual(first.effective_area_m2, 0.0) self.assertEqual(first.effective_area_m2, 0.0)
self.assertEqual(last.opening, 1.0) self.assertEqual(last.opening, 1.0)