diff --git a/AmesimModels/help/P4NODE2.pdf b/AmesimModels/help/P4NODE2.pdf new file mode 100644 index 0000000..2f3aa13 Binary files /dev/null and b/AmesimModels/help/P4NODE2.pdf differ diff --git a/AmesimModels/help/PN3NODE2.pdf b/AmesimModels/help/PN3NODE2.pdf new file mode 100644 index 0000000..cf5c414 Binary files /dev/null and b/AmesimModels/help/PN3NODE2.pdf differ diff --git a/AmesimModels/help/PNCH012.pdf b/AmesimModels/help/PNCH012.pdf new file mode 100644 index 0000000..92170cf Binary files /dev/null and b/AmesimModels/help/PNCH012.pdf differ diff --git a/AmesimModels/help/PNL0001.pdf b/AmesimModels/help/PNL0001.pdf new file mode 100644 index 0000000..ba4077a Binary files /dev/null and b/AmesimModels/help/PNL0001.pdf differ diff --git a/AmesimModels/help/PNL0002.pdf b/AmesimModels/help/PNL0002.pdf new file mode 100644 index 0000000..d2ad34b Binary files /dev/null and b/AmesimModels/help/PNL0002.pdf differ diff --git a/AmesimModels/help/PNL0003.pdf b/AmesimModels/help/PNL0003.pdf new file mode 100644 index 0000000..fc07a9a Binary files /dev/null and b/AmesimModels/help/PNL0003.pdf differ diff --git a/AmesimModels/help/PNL00R.pdf b/AmesimModels/help/PNL00R.pdf new file mode 100644 index 0000000..4cf7a74 Binary files /dev/null and b/AmesimModels/help/PNL00R.pdf differ diff --git a/AmesimModels/help/PNOR001.pdf b/AmesimModels/help/PNOR001.pdf new file mode 100644 index 0000000..70a418c Binary files /dev/null and b/AmesimModels/help/PNOR001.pdf differ diff --git a/AmesimModels/help/PNVO001.pdf b/AmesimModels/help/PNVO001.pdf new file mode 100644 index 0000000..1c347a0 Binary files /dev/null and b/AmesimModels/help/PNVO001.pdf differ diff --git a/PythonModels/components/amesim_pneumatic.py b/PythonModels/components/amesim_pneumatic.py index 98cec55..a72c8b1 100644 --- a/PythonModels/components/amesim_pneumatic.py +++ b/PythonModels/components/amesim_pneumatic.py @@ -332,7 +332,8 @@ class AmesimPneumaticOrifice(AlgebraicComponent): @property def effective_area(self) -> float: - return self.area * max(self.opening, 0.0) + opening = min(max(self.opening, 0.0), 1.0) + return self.area * opening def mass_flow(self, p_a: float, p_b: float, upstream_temperature: float) -> float: if p_a == p_b or self.effective_area == 0.0 or self.flow_coefficient == 0.0: diff --git a/PythonModels/components/amesim_pneumatic_line.py b/PythonModels/components/amesim_pneumatic_line.py index b4b1d37..ab9c92d 100644 --- a/PythonModels/components/amesim_pneumatic_line.py +++ b/PythonModels/components/amesim_pneumatic_line.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from math import log10, pi +from math import log10, pi, sqrt from PythonModels.components.amesim_pneumatic import ( HELIUM_PNEUMATIC_GAS, @@ -112,10 +112,11 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): resistance. Both connection mass flows use the PythonModels convention: positive values enter the pipe storage. - AMESim's proprietary pressure-loss calibration is not available in the - archive. This implementation therefore uses an explicit Darcy-Weisbach - law while preserving the real geometry, state count, mass/energy balance, - heat-transfer parameter, and observable diagnostics. + AMESim's proprietary ``pn2pipefr`` utility is represented by an + optional calibrated linear conductance when a model-specific baseline + supports it; otherwise the component falls back to an auditable + Darcy-Weisbach law. Both paths preserve the real geometry, state count, + mass/energy balance, heat-transfer parameter, and observable diagnostics. """ def __init__( @@ -128,6 +129,7 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): polytropic_constant: float = 1.35, heat_transfer_coefficient: float = 0.0, external_temperature_k: float = 293.15, + calibrated_linear_conductance: float | None = None, gas: AmesimPneumaticGas = HELIUM_PNEUMATIC_GAS, p0: float = 101_325.0, T0: float = 293.15, @@ -144,6 +146,11 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): raise ValueError("heat_transfer_coefficient must be non-negative") if external_temperature_k <= 0.0: raise ValueError("external_temperature_k must be positive") + if ( + calibrated_linear_conductance is not None + and calibrated_linear_conductance <= 0.0 + ): + raise ValueError("calibrated_linear_conductance must be positive") super().__init__(name=name) self.diameter = diameter_mm * 1.0e-3 @@ -152,6 +159,7 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): self.polytropic_constant = polytropic_constant self.heat_transfer_coefficient = heat_transfer_coefficient self.external_temperature = external_temperature_k + self.calibrated_linear_conductance = calibrated_linear_conductance self.gas = gas self.area = diameter_mm_to_area_m2(diameter_mm) self.volume = self.area * self.length @@ -210,6 +218,12 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): pressure_difference = port_1_pressure_pa - internal.p if pressure_difference == 0.0: return 0.0 + if self.calibrated_linear_conductance is not None: + return ( + self.calibrated_linear_conductance + * pressure_difference + / sqrt(internal.T) + ) upstream_pressure = max(port_1_pressure_pa, internal.p) upstream_temperature = ( port_1_temperature_k if pressure_difference > 0.0 else internal.T @@ -246,6 +260,24 @@ class AmesimPnl0001Pipe(_DarcyPipeResistanceMixin, DynamicComponent): pressure_drop_pa=pressure_drop, ) + def darcy_pressure_drop_for_state( + self, + *, + mass_flow_kg_s: float, + pressure_pa: float, + temperature_k: float, + ) -> float: + if pressure_pa <= 0.0: + raise ValueError("pressure_pa must be positive") + if temperature_k <= 0.0: + raise ValueError("temperature_k must be positive") + density = self.gas.density(pressure_pa, temperature_k) + return self._darcy_pressure_drop( + mass_flow_kg_s, + density=density, + temperature=temperature_k, + ) + def derivatives_from_connections( self, *, diff --git a/PythonModels/scripts/run_test_mql_full_state_comparison.py b/PythonModels/scripts/run_test_mql_full_state_comparison.py index 048be70..8f438c9 100644 --- a/PythonModels/scripts/run_test_mql_full_state_comparison.py +++ b/PythonModels/scripts/run_test_mql_full_state_comparison.py @@ -3,7 +3,7 @@ from __future__ import annotations import argparse from dataclasses import dataclass, field from datetime import UTC, datetime -from math import nextafter +from math import nextafter, sqrt from pathlib import Path from PythonModels.core.solver import SolveIVPConfig, integrate_ode @@ -27,6 +27,7 @@ from PythonModels.systems.test_mql import ( TestMqlSimulationResult, TestMqlSystem, ) +from PythonModels.systems.test_mql_pneumatic import AMESIM_REFERENCE_PRESSURE_PA DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = ( @@ -265,6 +266,45 @@ class TestMqlPnvoEventWindowSegmentDiagnostic: message: str +@dataclass(frozen=True) +class TestMqlPnl0001PressureLossCalibrationDiagnostic: + line_alias: str + chamber_alias: str + time_s: float + amesim_cm: float + amesim_dm1_g_s: float + amesim_mass_flow_magnitude_kg_s: float + amesim_line_gauge_pressure_pa: float + amesim_chamber_gauge_pressure_pa: float + amesim_line_temperature_k: float + amesim_pressure_drop_pa: float + current_darcy_pressure_drop_pa: float + pressure_drop_multiplier: float + amesim_linear_conductance_kg_s_sqrt_k_per_pa: float + python_linear_conductance_kg_s_sqrt_k_per_pa: float + python_to_amesim_linear_conductance_ratio: float + + +@dataclass(frozen=True) +class TestMqlPnvoFlowParameterDiagnostic: + orifice_alias: str + time_s: float + amesim_cm: float + amesim_dm2_g_s: float + amesim_opening: float + amesim_gas_velocity_m_s: float + python_opening: float + python_flow_coefficient: float + python_effective_area_m2: float + python_line_pressure_pa: float + python_boundary_pressure_pa: float + python_upstream_pressure_pa: float + python_upstream_temperature_k: float + python_mass_flow_kg_s: float + python_cm: float + python_to_amesim_cm_ratio: float + + @dataclass(frozen=True) class TestMqlPnvoEventWindowSampleDiagnostic: time_s: float @@ -274,6 +314,12 @@ class TestMqlPnvoEventWindowSampleDiagnostic: pnl0001_rhs_diagnostics: tuple[TestMqlPnl0001LineRhsDiagnostic, ...] = field( default_factory=tuple ) + pnl0001_pressure_loss_diagnostics: tuple[ + TestMqlPnl0001PressureLossCalibrationDiagnostic, ... + ] = field(default_factory=tuple) + pnvo_flow_parameter_diagnostics: tuple[ + TestMqlPnvoFlowParameterDiagnostic, ... + ] = field(default_factory=tuple) def abs_error(self, data_path: str) -> float: return abs( @@ -509,6 +555,166 @@ def format_test_mql_pnvo_event_boundary_summary( return "\n".join(lines) + "\n" +def _pnl0001_pressure_loss_calibration_diagnostic( + *, + closure: object, + amesim_results: AmesimResults, + python_values_by_data_path: dict[str, float], + line_alias: str, + chamber_alias: str, + time_s: float, +) -> TestMqlPnl0001PressureLossCalibrationDiagnostic: + if line_alias != "pneumatic_69" or chamber_alias != "pn_c1_8": + raise KeyError(f"Unsupported PNL0001 pressure-loss diagnostic: {line_alias}") + line = closure.pneumatic_closure.components.p4_port3_remote_primary_line + + def amesim_value(data_path: str) -> float: + return interpolate_series_value( + amesim_results.times, + amesim_results.series(data_path), + time_s, + ) + + amesim_cm = amesim_value(f"cm@{line_alias}") + amesim_dm1_g_s = amesim_value(f"dm1@{line_alias}") + amesim_line_gauge_pressure_pa = amesim_value(f"p2@{line_alias}") + amesim_chamber_gauge_pressure_pa = amesim_value(f"press@{chamber_alias}") + amesim_line_temperature_k = amesim_value(f"t2@{line_alias}") + mass_flow_magnitude_kg_s = abs(amesim_dm1_g_s) * 1.0e-3 + amesim_pressure_drop_pa = abs( + amesim_line_gauge_pressure_pa - amesim_chamber_gauge_pressure_pa + ) + current_darcy_pressure_drop_pa = abs( + line.darcy_pressure_drop_for_state( + mass_flow_kg_s=mass_flow_magnitude_kg_s, + pressure_pa=( + amesim_line_gauge_pressure_pa + AMESIM_REFERENCE_PRESSURE_PA + ), + temperature_k=amesim_line_temperature_k, + ) + ) + if current_darcy_pressure_drop_pa > 0.0: + pressure_drop_multiplier = ( + amesim_pressure_drop_pa / current_darcy_pressure_drop_pa + ) + else: + pressure_drop_multiplier = ( + float("inf") if amesim_pressure_drop_pa > 0.0 else 1.0 + ) + amesim_linear_conductance = _pnl0001_linear_conductance( + dm1_g_s=amesim_dm1_g_s, + temperature_k=amesim_line_temperature_k, + pressure_drop_pa=amesim_pressure_drop_pa, + ) + python_pressure_drop_pa = abs( + python_values_by_data_path[f"p2@{line_alias}"] + - python_values_by_data_path[f"press@{chamber_alias}"] + ) + python_linear_conductance = _pnl0001_linear_conductance( + dm1_g_s=python_values_by_data_path[f"dm1@{line_alias}"], + temperature_k=python_values_by_data_path[f"t2@{line_alias}"], + pressure_drop_pa=python_pressure_drop_pa, + ) + if amesim_linear_conductance > 0.0: + conductance_ratio = python_linear_conductance / amesim_linear_conductance + else: + conductance_ratio = ( + float("inf") if python_linear_conductance > 0.0 else 1.0 + ) + return TestMqlPnl0001PressureLossCalibrationDiagnostic( + line_alias=line_alias, + chamber_alias=chamber_alias, + time_s=time_s, + amesim_cm=amesim_cm, + amesim_dm1_g_s=amesim_dm1_g_s, + amesim_mass_flow_magnitude_kg_s=mass_flow_magnitude_kg_s, + amesim_line_gauge_pressure_pa=amesim_line_gauge_pressure_pa, + amesim_chamber_gauge_pressure_pa=amesim_chamber_gauge_pressure_pa, + amesim_line_temperature_k=amesim_line_temperature_k, + amesim_pressure_drop_pa=amesim_pressure_drop_pa, + current_darcy_pressure_drop_pa=current_darcy_pressure_drop_pa, + pressure_drop_multiplier=pressure_drop_multiplier, + amesim_linear_conductance_kg_s_sqrt_k_per_pa=( + amesim_linear_conductance + ), + python_linear_conductance_kg_s_sqrt_k_per_pa=( + python_linear_conductance + ), + python_to_amesim_linear_conductance_ratio=conductance_ratio, + ) + + +def _pnl0001_linear_conductance( + *, + dm1_g_s: float, + temperature_k: float, + pressure_drop_pa: float, +) -> float: + if temperature_k <= 0.0: + raise ValueError("temperature_k must be positive") + if pressure_drop_pa <= 0.0: + return 0.0 + return abs(dm1_g_s) * 1.0e-3 * sqrt(temperature_k) / pressure_drop_pa + + +def _pnvo_flow_parameter_diagnostic( + *, + closure: object, + amesim_results: AmesimResults, + state_vector: list[float], + orifice_alias: str, + time_s: float, +) -> TestMqlPnvoFlowParameterDiagnostic: + if orifice_alias != "pn_morifice_1": + raise KeyError(f"Unsupported PNVO flow parameter diagnostic: {orifice_alias}") + snapshot = closure.snapshot_at(time_s, state_vector).pneumatic + orifice = closure.pneumatic_closure.components.p4_port3_remote_orifice + if orifice.name != orifice_alias: + raise KeyError(f"Unexpected PNVO diagnostic orifice: {orifice.name}") + line_properties = snapshot.p4_port3_remote_orifice_line_port_2 + boundary_properties = snapshot.p4_port3_remote_primary_line + upstream_properties = ( + line_properties if line_properties.p >= boundary_properties.p else boundary_properties + ) + python_mass_flow_kg_s = snapshot.p4_port3_remote_orifice_to_node_flow + denominator = ( + orifice.flow_coefficient * orifice.effective_area * upstream_properties.p + ) + python_cm = ( + abs(python_mass_flow_kg_s) * sqrt(upstream_properties.T) / denominator + if denominator > 0.0 and upstream_properties.T > 0.0 + else 0.0 + ) + + def amesim_value(data_path: str) -> float: + return interpolate_series_value( + amesim_results.times, + amesim_results.series(data_path), + time_s, + ) + + amesim_cm = amesim_value(f"cm@{orifice_alias}") + cm_ratio = python_cm / amesim_cm if amesim_cm != 0.0 else 0.0 + return TestMqlPnvoFlowParameterDiagnostic( + orifice_alias=orifice_alias, + time_s=time_s, + amesim_cm=amesim_cm, + amesim_dm2_g_s=amesim_value(f"dm2@{orifice_alias}"), + amesim_opening=amesim_value(f"xv@{orifice_alias}"), + amesim_gas_velocity_m_s=amesim_value(f"gasvel@{orifice_alias}"), + python_opening=orifice.opening, + python_flow_coefficient=orifice.flow_coefficient, + python_effective_area_m2=orifice.effective_area, + python_line_pressure_pa=line_properties.p, + python_boundary_pressure_pa=boundary_properties.p, + python_upstream_pressure_pa=upstream_properties.p, + python_upstream_temperature_k=upstream_properties.T, + python_mass_flow_kg_s=python_mass_flow_kg_s, + python_cm=python_cm, + python_to_amesim_cm_ratio=cm_ratio, + ) + + def run_test_mql_pnvo_event_window_diagnostic( config: TestMqlFullStateComparisonScriptConfig | None = None, *, @@ -679,6 +885,25 @@ def run_test_mql_pnvo_event_window_diagnostic( time_s=sample_time, ), ) + pnl0001_pressure_loss_diagnostics = ( + _pnl0001_pressure_loss_calibration_diagnostic( + closure=closure, + amesim_results=amesim_results, + python_values_by_data_path=python_sample_values, + line_alias="pneumatic_69", + chamber_alias="pn_c1_8", + time_s=sample_time, + ), + ) + pnvo_flow_parameter_diagnostics = ( + _pnvo_flow_parameter_diagnostic( + closure=closure, + amesim_results=amesim_results, + state_vector=state_vector_by_sample_time[sample_time], + orifice_alias=orifice_alias, + time_s=sample_time, + ), + ) sample_diagnostics.append( TestMqlPnvoEventWindowSampleDiagnostic( time_s=sample_time, @@ -686,6 +911,8 @@ def run_test_mql_pnvo_event_window_diagnostic( python_values_by_data_path=python_sample_values, amesim_values_by_data_path=amesim_sample_values, pnl0001_rhs_diagnostics=pnl0001_rhs_diagnostics, + pnl0001_pressure_loss_diagnostics=pnl0001_pressure_loss_diagnostics, + pnvo_flow_parameter_diagnostics=pnvo_flow_parameter_diagnostics, ) ) python_values = closure.data_path_values( @@ -749,6 +976,35 @@ def format_test_mql_pnvo_event_window_summary( f"dm_dt={rhs.mass_derivative_kg_s}, " f"dU_dt={rhs.energy_derivative_w}" ) + for pressure_loss in sample.pnl0001_pressure_loss_diagnostics: + lines.append( + f" - pressure_loss@{pressure_loss.line_alias}: " + f"cm={pressure_loss.amesim_cm}, " + f"amesim_dm1={pressure_loss.amesim_dm1_g_s}, " + f"amesim_dp={pressure_loss.amesim_pressure_drop_pa}, " + f"darcy_dp={pressure_loss.current_darcy_pressure_drop_pa}, " + f"dp_multiplier={pressure_loss.pressure_drop_multiplier}, " + f"amesim_linear_k=" + f"{pressure_loss.amesim_linear_conductance_kg_s_sqrt_k_per_pa}, " + f"python_linear_k=" + f"{pressure_loss.python_linear_conductance_kg_s_sqrt_k_per_pa}, " + f"linear_k_ratio=" + f"{pressure_loss.python_to_amesim_linear_conductance_ratio}" + ) + for flow_parameter in sample.pnvo_flow_parameter_diagnostics: + lines.append( + f" - flow_parameter@{flow_parameter.orifice_alias}: " + f"amesim_cm={flow_parameter.amesim_cm}, " + f"python_cm={flow_parameter.python_cm}, " + f"cm_ratio={flow_parameter.python_to_amesim_cm_ratio}, " + f"amesim_dm2={flow_parameter.amesim_dm2_g_s}, " + f"python_m={flow_parameter.python_mass_flow_kg_s}, " + f"opening={flow_parameter.python_opening}, " + f"effective_area={flow_parameter.python_effective_area_m2}, " + f"upstream_p={flow_parameter.python_upstream_pressure_pa}, " + f"upstream_t={flow_parameter.python_upstream_temperature_k}, " + f"amesim_gasvel={flow_parameter.amesim_gas_velocity_m_s}" + ) lines.append("Final comparison:") for data_path in diagnostic.data_paths: lines.append( diff --git a/PythonModels/systems/test_mql_pneumatic_lines.py b/PythonModels/systems/test_mql_pneumatic_lines.py index 18b42ed..850991b 100644 --- a/PythonModels/systems/test_mql_pneumatic_lines.py +++ b/PythonModels/systems/test_mql_pneumatic_lines.py @@ -25,6 +25,9 @@ from PythonModels.systems.test_mql_line_parameters import ( ) +TEST_MQL_PNL0001_D20_L1_LINEAR_CONDUCTANCE = 5.5636e-6 + + @dataclass(frozen=True) class TestMqlPnl0001Assembly: specs: tuple[TestMqlPnl0001Spec, ...] @@ -88,6 +91,9 @@ def build_test_mql_pnl0001_assembly( polytropic_constant=spec.polytropic_constant, heat_transfer_coefficient=spec.heat_transfer_coefficient, external_temperature_k=spec.external_temperature_k, + calibrated_linear_conductance=( + _test_mql_pnl0001_calibrated_linear_conductance(spec) + ), gas=gas, p0=spec.initial_absolute_pressure_pa, T0=spec.initial_temperature_k, @@ -97,6 +103,28 @@ def build_test_mql_pnl0001_assembly( return TestMqlPnl0001Assembly(specs=specs, lines=lines) +def _test_mql_pnl0001_calibrated_linear_conductance( + spec: TestMqlPnl0001Spec, +) -> float | None: + if spec.target_component.startswith("pn_c1_") and _matches_geometry( + spec, diameter_mm=20.0, length_m=1.0 + ): + return TEST_MQL_PNL0001_D20_L1_LINEAR_CONDUCTANCE + return None + + +def _matches_geometry( + spec: TestMqlPnl0001Spec, + *, + diameter_mm: float, + length_m: float, +) -> bool: + return ( + abs(spec.diameter_mm - diameter_mm) < 1.0e-12 + and abs(spec.length_m - length_m) < 1.0e-12 + ) + + def build_test_mql_pnl0002_assembly( archive_path: str | Path, *, diff --git a/tests/test_amesim_pneumatic_components.py b/tests/test_amesim_pneumatic_components.py index 1238be7..827e99d 100644 --- a/tests/test_amesim_pneumatic_components.py +++ b/tests/test_amesim_pneumatic_components.py @@ -137,6 +137,17 @@ class AmesimPneumaticComponentsTest(unittest.TestCase): self.assertAlmostEqual(derivative.m, 0.0) self.assertAlmostEqual(derivative.U, -props.p * 2.0e-6) + def test_orifice_effective_area_clamps_opening(self) -> None: + orifice = AmesimPneumaticOrifice.from_mm2( + name="pn_variable_orifice", + area_mm2=78.5, + opening=1.4, + ) + self.assertAlmostEqual(orifice.effective_area, 78.5e-6) + + orifice.opening = -0.25 + self.assertAlmostEqual(orifice.effective_area, 0.0) + def test_orifice_returns_signed_mass_flow(self) -> None: orifice = AmesimPneumaticOrifice.from_mm2( name="pn_orifice_18", diff --git a/tests/test_amesim_pnl0001_pipe.py b/tests/test_amesim_pnl0001_pipe.py index fc326a5..1bf4c27 100644 --- a/tests/test_amesim_pnl0001_pipe.py +++ b/tests/test_amesim_pnl0001_pipe.py @@ -39,6 +39,26 @@ class AmesimPnl0001PipeTests(unittest.TestCase): self.assertLess(reverse, 0.0) self.assertAlmostEqual(abs(forward), abs(reverse), delta=abs(forward) * 0.01) + def test_calibrated_linear_conductance_overrides_darcy_flow(self) -> None: + pipe = AmesimPnl0001Pipe( + name="linear", + diameter_mm=20.0, + length_m=1.0, + relative_roughness=0.045 / 20.0, + calibrated_linear_conductance=5.5636e-6, + p0=200000.0, + T0=293.15, + ) + flow = pipe.resistance_mass_flow( + port_1_pressure_pa=180000.0, + port_1_temperature_k=293.15, + ) + + self.assertAlmostEqual( + flow, + 5.5636e-6 * (180000.0 - 200000.0) / (293.15 ** 0.5), + ) + def test_diagnostics_reproduce_laminar_friction_contract(self) -> None: diagnostics = self.pipe.diagnostics(mass_flow_kg_s=8.90603914774626e-6) @@ -51,6 +71,16 @@ class AmesimPnl0001PipeTests(unittest.TestCase): self.assertGreater(diagnostics.gas_velocity_m_s, 0.0) self.assertGreater(diagnostics.pressure_drop_pa, 0.0) + def test_darcy_pressure_drop_for_state_uses_supplied_density_state(self) -> None: + diagnostics = self.pipe.diagnostics(mass_flow_kg_s=8.90603914774626e-6) + pressure_drop = self.pipe.darcy_pressure_drop_for_state( + mass_flow_kg_s=8.90603914774626e-6, + pressure_pa=self.pipe.properties().p, + temperature_k=self.pipe.properties().T, + ) + + self.assertAlmostEqual(pressure_drop, diagnostics.pressure_drop_pa) + def test_connection_derivative_preserves_mass_and_stream_direction(self) -> None: internal = self.pipe.properties() derivative = self.pipe.derivatives_from_connections( diff --git a/tests/test_run_test_mql_full_state_comparison.py b/tests/test_run_test_mql_full_state_comparison.py index 18986b1..3b19c65 100644 --- a/tests/test_run_test_mql_full_state_comparison.py +++ b/tests/test_run_test_mql_full_state_comparison.py @@ -11,9 +11,11 @@ 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, @@ -241,6 +243,45 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): 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, + 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", @@ -275,6 +316,17 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase): 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("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")) diff --git a/tests/test_test_mql_pnl0001.py b/tests/test_test_mql_pnl0001.py index 0d2730c..d8a2057 100644 --- a/tests/test_test_mql_pnl0001.py +++ b/tests/test_test_mql_pnl0001.py @@ -6,6 +6,7 @@ from pathlib import Path from PythonModels.reporting.amesim_results import load_test_mql_amesim_results from PythonModels.systems.test_mql_line_parameters import load_test_mql_pnl0001_specs from PythonModels.systems.test_mql_pneumatic_lines import ( + TEST_MQL_PNL0001_D20_L1_LINEAR_CONDUCTANCE, build_test_mql_pnl0001_assembly, ) @@ -44,6 +45,38 @@ class TestMqlPnl0001Tests(unittest.TestCase): all(len(line.get_state_vector()) == 2 for line in self.assembly.lines.values()) ) + def test_calibrates_primary_d20_chamber_lines_only(self) -> None: + calibrated_aliases = { + alias + for alias, line in self.assembly.lines.items() + if line.calibrated_linear_conductance is not None + } + + self.assertEqual( + calibrated_aliases, + { + "pneumatic_65", + "pneumatic_66", + "pneumatic_68", + "pneumatic_69", + "pneumatic_71", + "pneumatic_72", + "pneumatic_73", + "pneumatic_74", + }, + ) + for alias in calibrated_aliases: + self.assertAlmostEqual( + self.assembly.lines[alias].calibrated_linear_conductance, + TEST_MQL_PNL0001_D20_L1_LINEAR_CONDUCTANCE, + ) + self.assertIsNone( + self.assembly.lines["pneumatic_70"].calibrated_linear_conductance + ) + self.assertIsNone( + self.assembly.lines["pneumatic_96"].calibrated_linear_conductance + ) + def test_pneumatic_96_initial_observables_match_amesim_baseline(self) -> None: pipe = self.assembly.lines["pneumatic_96"]