Files
SystemSimulationApp/app/simulation/examples/test_mql/run_full_state_comparison.py
T

4622 lines
200 KiB
Python

from __future__ import annotations
import argparse
from bisect import bisect_left
from dataclasses import dataclass, field, replace
from datetime import UTC, datetime
from math import nextafter, sqrt
from pathlib import Path
from app.simulation.examples.test_mql.primitives.pneumatic import AmesimPneumaticGas
from app.simulation.examples.test_mql.solver import SolveIVPConfig, integrate_ode
from app.simulation.reporting.amesim_results import AmesimResults, load_test_mql_amesim_results
from app.simulation.reporting.test_mql_comparison import (
TestMqlComparisonResult,
interpolate_series_value,
write_test_mql_comparison_csv,
)
from app.simulation.reporting.test_mql_output_schema import (
TestMqlOutputSchema,
build_test_mql_output_schema,
)
from app.simulation.reporting.test_mql_output_validation import (
TestMqlValidatedOutput,
compare_validated_test_mql_output,
validate_test_mql_output,
)
from app.simulation.examples.test_mql.system import (
TestMqlPnl0001LineRhsDiagnostic,
TestMqlSimulationResult,
TestMqlSystem,
TestMqlVariableChamberRhsDiagnostic,
)
from app.simulation.examples.test_mql.pneumatic import AMESIM_REFERENCE_PRESSURE_PA
from app.simulation.paths import (
AMESIM_TEST_MQL_ARCHIVE_PATH,
SIMULATION_RUNS_DIR,
)
DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = (
"press@pn_c1_8",
"vol@pn_c1_8",
"vol1@pn_brp2_8",
"vvol1@pn_brp2_8",
"x1@mass_friction_endstops_10",
"v1@mass_friction_endstops_10",
"acc1@mass_friction_endstops_10",
"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",
)
@dataclass(frozen=True)
class TestMqlFullStateSignalDiagnostic:
data_path: str
initial_time_s: float
final_time_s: float
initial_python_value: float
initial_amesim_value: float
initial_abs_error: float
final_python_value: float
final_amesim_value: float
final_abs_error: float
@dataclass(frozen=True)
class TestMqlFullStateFlowDiagnostic:
data_path: str
initial_time_s: float
final_time_s: float
initial_python_dm1_g_s: float
initial_amesim_dm1_g_s: float
initial_python_canonical_kg_s: float
initial_amesim_canonical_kg_s: float
initial_canonical_abs_error_kg_s: float
final_python_dm1_g_s: float
final_amesim_dm1_g_s: float
final_python_canonical_kg_s: float
final_amesim_canonical_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)
class TestMqlFullStateComparisonRun:
system: TestMqlSystem
closure: object
amesim_results: AmesimResults
output_schema: TestMqlOutputSchema
result: TestMqlSimulationResult
output: TestMqlValidatedOutput
comparison: TestMqlComparisonResult
@property
def sample_count(self) -> int:
return len(self.output.times)
@property
def signal_count(self) -> int:
return len(self.output.data_paths)
def metrics_by_max_abs_error(self):
return tuple(
sorted(
self.comparison.metrics,
key=lambda metric: metric.max_abs_error,
reverse=True,
)
)
@property
def largest_abs_error_metric(self):
metrics = self.metrics_by_max_abs_error()
return metrics[0] if metrics else None
def signal_diagnostic(self, data_path: str) -> TestMqlFullStateSignalDiagnostic:
times = self.output.times
python_values = self.output.series_by_data_path[data_path]
amesim_values = self.amesim_results.series(data_path)
initial_time = float(times[0])
final_time = float(times[-1])
initial_python = float(python_values[0])
final_python = float(python_values[-1])
initial_amesim = interpolate_series_value(
self.amesim_results.times,
amesim_values,
initial_time,
)
final_amesim = interpolate_series_value(
self.amesim_results.times,
amesim_values,
final_time,
)
return TestMqlFullStateSignalDiagnostic(
data_path=data_path,
initial_time_s=initial_time,
final_time_s=final_time,
initial_python_value=initial_python,
initial_amesim_value=initial_amesim,
initial_abs_error=abs(initial_python - initial_amesim),
final_python_value=final_python,
final_amesim_value=final_amesim,
final_abs_error=abs(final_python - final_amesim),
)
def pnl0001_mass_flow_diagnostic(
self,
data_path: str = "dm1@pneumatic_69",
) -> TestMqlFullStateFlowDiagnostic:
diagnostic = self.signal_diagnostic(data_path)
initial_python_canonical = -diagnostic.initial_python_value * 1.0e-3
initial_amesim_canonical = -diagnostic.initial_amesim_value * 1.0e-3
final_python_canonical = -diagnostic.final_python_value * 1.0e-3
final_amesim_canonical = -diagnostic.final_amesim_value * 1.0e-3
return TestMqlFullStateFlowDiagnostic(
data_path=data_path,
initial_time_s=diagnostic.initial_time_s,
final_time_s=diagnostic.final_time_s,
initial_python_dm1_g_s=diagnostic.initial_python_value,
initial_amesim_dm1_g_s=diagnostic.initial_amesim_value,
initial_python_canonical_kg_s=initial_python_canonical,
initial_amesim_canonical_kg_s=initial_amesim_canonical,
initial_canonical_abs_error_kg_s=abs(
initial_python_canonical - initial_amesim_canonical
),
final_python_dm1_g_s=diagnostic.final_python_value,
final_amesim_dm1_g_s=diagnostic.final_amesim_value,
final_python_canonical_kg_s=final_python_canonical,
final_amesim_canonical_kg_s=final_amesim_canonical,
final_canonical_abs_error_kg_s=abs(
final_python_canonical - final_amesim_canonical
),
)
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(
(
self.signal_diagnostic(data_path)
for data_path in self.output.data_paths
),
key=lambda diagnostic: diagnostic.final_abs_error,
reverse=True,
)
)
@property
def largest_final_abs_error_diagnostic(self):
diagnostics = self.diagnostics_by_final_abs_error()
return diagnostics[0] if diagnostics else None
def chamber_rhs_diagnostic(self, chamber_alias: str, sample_index: int = -1):
state_vector = [row[sample_index] for row in self.result.y]
return self.closure.variable_chamber_rhs_diagnostic(
chamber_alias=chamber_alias,
state_vector=state_vector,
time_s=float(self.result.t[sample_index]),
)
@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 TestMqlPnvoEventWindowSegmentDiagnostic:
t_start: float
t_stop: float
method: str
rtol: float
atol: float
max_step: float
rhs_evaluations: int
success: bool
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
candidate_pn2pipefr_dm1_g_s: float
candidate_pn2pipefr_to_amesim_dm1_ratio: 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 TestMqlPnl0001EnergyFlowDiagnostic:
line_alias: str
chamber_alias: str
node_alias: str
time_s: float
amesim_port_1_enthalpy_flow_w: float
amesim_port_1_mass_flow_g_s: float
amesim_node_port_2_enthalpy_flow_w: float
amesim_node_port_2_mass_flow_g_s: float
amesim_observed_port_enthalpy_sum_w: float
python_port_1_internal_energy_flow_w: float
python_port_2_internal_energy_flow_w: float
python_current_energy_derivative_w: float
python_direct_enthalpy_port_1_flow_w: float
python_direct_enthalpy_port_2_flow_w: float
python_direct_enthalpy_energy_derivative_w: float
python_direct_minus_current_energy_derivative_w: float
python_p4_node_port_2_enthalpy_flow_w: float
python_p4_node_port_2_mass_flow_g_s: float
python_p4_node_port_2_connected_h_j_kg: float
python_p4_node_port_2_connected_temperature_k: float
python_p4_node_port_2_connected_h_delta_to_line_h_j_kg: float
python_p4_node_port_2_counterfactual_dtemp_k_s: float
reference_temperature_k: float
amesim_port_1_reference_enthalpy_estimate_w: float
amesim_port_1_reference_enthalpy_error_w: float
amesim_candidate_pn2pipefr_dm2i_g_s: float
amesim_candidate_pn2pipefr_dh2i_w: float
amesim_candidate_storage_enthalpy_sum_w: float
amesim_candidate_pn2pipefr_dtemp_k_s: float
amesim_candidate_pn2pipefr_dtemp_residual_k_s: float
python_reference_port_1_enthalpy_flow_w: float
python_reference_node_port_2_enthalpy_flow_w: float
python_reference_observed_port_enthalpy_sum_w: float
python_reference_port_1_to_amesim_error_w: float
python_reference_node_port_2_to_amesim_error_w: float
python_reference_sum_to_amesim_error_w: float
python_current_line_temperature_derivative_k_s: float
amesim_line_temperature_derivative_fd_k_s: float
amesim_pn2vol2_dtemp_node_plus_dh1_k_s: float
amesim_pn2vol2_dtemp_node_minus_dh1_k_s: float
python_reference_pn2vol2_dtemp_node_minus_dh1_k_s: float
@dataclass(frozen=True)
class TestMqlP4NodeEnthalpyBreakdownDiagnostic:
node_alias: str
time_s: float
amesim_port_1_enthalpy_flow_w: float
amesim_port_1_mass_flow_g_s: float
amesim_port_3_enthalpy_flow_w: float
amesim_port_3_mass_flow_g_s: float
amesim_port_4_enthalpy_flow_w: float
amesim_port_4_mass_flow_g_s: float
amesim_port_2_enthalpy_flow_w: float
amesim_port_2_mass_flow_g_s: float
python_port_1_enthalpy_flow_w: float
python_port_1_mass_flow_g_s: float
python_port_3_enthalpy_flow_w: float
python_port_3_mass_flow_g_s: float
python_port_4_enthalpy_flow_w: float
python_port_4_mass_flow_g_s: float
python_port_2_enthalpy_flow_w: float
python_port_2_mass_flow_g_s: float
python_reference_port_1_enthalpy_flow_w: float
python_reference_port_3_enthalpy_flow_w: float
python_reference_port_4_enthalpy_flow_w: float
python_reference_port_2_enthalpy_flow_w: float
python_real_gas_reference_port_1_enthalpy_flow_w: float
python_real_gas_reference_port_3_enthalpy_flow_w: float
python_real_gas_reference_port_4_enthalpy_flow_w: float
python_real_gas_reference_port_2_enthalpy_flow_w: float
python_port_2_enthalpy_error_w: float
python_reference_port_2_enthalpy_error_w: float
python_real_gas_reference_port_2_enthalpy_error_w: float
@dataclass(frozen=True)
class TestMqlPnvoUpstreamEnthalpyDiagnostic:
orifice_alias: str
upstream_line_alias: str
time_s: float
amesim_orifice_port_2_enthalpy_flow_w: float
amesim_orifice_port_2_mass_flow_g_s: float
amesim_orifice_port_3_enthalpy_flow_w: float
amesim_orifice_port_3_mass_flow_g_s: float
amesim_upstream_line_center_enthalpy_flow_w: float
amesim_upstream_line_center_mass_flow_g_s: float
amesim_upstream_line_port_2_temperature_k: float
amesim_upstream_line_port_2_pressure_pa: float
amesim_orifice_port_2_implied_reference_temperature_k: float
amesim_orifice_port_2_implied_temperature_delta_to_line_k: float
amesim_upstream_line_port_2_reference_enthalpy_flow_w: float
amesim_upstream_line_port_2_reference_enthalpy_error_w: float
amesim_upstream_line_port_2_real_gas_reference_enthalpy_flow_w: float
amesim_upstream_line_port_2_real_gas_reference_enthalpy_error_w: float
python_orifice_to_node_flow_kg_s: float
python_upstream_line_port_2_temperature_k: float
python_upstream_line_port_2_pressure_pa: float
python_current_port_2_enthalpy_flow_w: float
python_reference_port_2_enthalpy_flow_w: float
python_real_gas_reference_port_2_enthalpy_flow_w: float
python_current_port_2_enthalpy_error_w: float
python_reference_port_2_enthalpy_error_w: float
python_real_gas_reference_port_2_enthalpy_error_w: float
@dataclass(frozen=True)
class TestMqlPnl0003EnergyDiagnostic:
line_alias: str
time_s: float
amesim_port_1_temperature_k: float
amesim_port_2_temperature_k: float
amesim_port_1_pressure_pa: float
amesim_port_2_pressure_pa: float
amesim_port_1_mass_flow_g_s: float
amesim_port_1_enthalpy_flow_w: float
amesim_center_mass_flow_g_s: float
amesim_center_enthalpy_flow_w: float
amesim_port_2_mass_flow_g_s: float
amesim_port_2_enthalpy_flow_w: float
amesim_port_1_storage_mass_derivative_g_s: float
amesim_port_1_storage_enthalpy_sum_w: float
amesim_port_2_storage_mass_derivative_g_s: float
amesim_port_2_storage_enthalpy_sum_w: float
python_pn3_node_port_1_mass_flow_g_s: float
python_pn3_node_port_3_mass_flow_g_s: float
python_pn3_node_to_line_mass_flow_g_s: float
python_pn3_node_to_line_mass_flow_error_g_s: float
amesim_port_1_mass_estimate_g: float
amesim_port_2_mass_estimate_g: float
python_port_1_mass_g: float
python_port_2_mass_g: float
python_port_1_mass_error_to_amesim_g: float
python_port_2_mass_error_to_amesim_g: float
amesim_total_mass_derivative_fd_g_s: float
amesim_total_mass_derivative_backward_g_s: float
amesim_total_mass_derivative_forward_g_s: float
amesim_total_storage_mass_derivative_g_s: float
amesim_total_storage_mass_derivative_residual_g_s: float
amesim_center_flow_python_sign_equivalent_g_s: float
amesim_center_flow_python_sign_error_g_s: float
python_center_mass_flow_g_s: float
amesim_port_1_center_mass_derivative_g_s: float
amesim_port_2_center_mass_derivative_g_s: float
python_port_1_external_mass_flow_g_s: float
python_port_1_external_mass_flow_error_g_s: float
python_port_1_center_mass_derivative_g_s: float
python_port_1_center_mass_derivative_error_g_s: float
python_port_1_storage_mass_derivative_g_s: float
python_port_1_storage_mass_derivative_error_g_s: float
python_port_2_external_mass_flow_g_s: float
python_port_2_external_mass_flow_error_g_s: float
python_port_2_center_mass_derivative_g_s: float
python_port_2_center_mass_derivative_error_g_s: float
python_port_2_storage_mass_derivative_g_s: float
python_port_2_storage_mass_derivative_error_g_s: float
python_amesim_state_darcy_center_mass_flow_g_s: float
python_amesim_state_darcy_center_mass_flow_error_g_s: float
python_amesim_state_pn2pipefr_center_mass_flow_g_s: float
python_amesim_state_pn2pipefr_center_mass_flow_error_g_s: float
amesim_center_real_gas_reference_port_1_estimate_w: float
amesim_center_real_gas_reference_port_1_error_w: float
python_center_ideal_reference_port_1_estimate_w: float
python_center_ideal_reference_port_1_error_w: float
python_center_ideal_reference_port_2_estimate_w: float
python_center_ideal_reference_port_2_error_w: float
python_center_real_gas_reference_port_1_estimate_w: float
python_center_real_gas_reference_port_1_error_w: float
python_center_real_gas_reference_port_2_estimate_w: float
python_center_real_gas_reference_port_2_error_w: float
python_total_mass_derivative_kg_s: float
python_port_1_reference_external_enthalpy_flow_w: float
python_port_1_reference_center_enthalpy_flow_w: float
python_port_1_reference_storage_enthalpy_sum_w: float
python_port_1_reference_storage_enthalpy_error_w: float
python_port_2_reference_external_enthalpy_flow_w: float
python_port_2_reference_center_enthalpy_flow_w: float
python_port_2_reference_storage_enthalpy_sum_w: float
python_port_2_reference_storage_enthalpy_error_w: float
amesim_port_1_external_enthalpy_dtemp_component_k_s: float
amesim_port_1_center_enthalpy_dtemp_component_k_s: float
amesim_port_1_mass_offset_dtemp_component_k_s: float
amesim_port_1_heat_dtemp_component_k_s: float
python_port_1_external_enthalpy_dtemp_component_k_s: float
python_port_1_center_enthalpy_dtemp_component_k_s: float
python_port_1_mass_offset_dtemp_component_k_s: float
python_port_1_heat_dtemp_component_k_s: float
amesim_port_2_external_enthalpy_dtemp_component_k_s: float
amesim_port_2_center_enthalpy_dtemp_component_k_s: float
amesim_port_2_mass_offset_dtemp_component_k_s: float
amesim_port_2_heat_dtemp_component_k_s: float
python_port_2_external_enthalpy_dtemp_component_k_s: float
python_port_2_center_enthalpy_dtemp_component_k_s: float
python_port_2_mass_offset_dtemp_component_k_s: float
python_port_2_heat_dtemp_component_k_s: float
amesim_port_1_temperature_derivative_fd_k_s: float
amesim_port_1_temperature_derivative_backward_k_s: float
amesim_port_1_temperature_derivative_forward_k_s: float
amesim_port_2_temperature_derivative_fd_k_s: float
amesim_port_2_temperature_derivative_backward_k_s: float
amesim_port_2_temperature_derivative_forward_k_s: float
amesim_port_1_pn2vol_dtemp_k_s: float
amesim_port_2_pn2vol_dtemp_k_s: float
amesim_port_1_pn2vol_dtemp_residual_k_s: float
amesim_port_2_pn2vol_dtemp_residual_k_s: float
amesim_port_1_pressure_derivative_fd_pa_s: float
amesim_port_2_pressure_derivative_fd_pa_s: float
amesim_port_1_pressure_derivative_mass_component_pa_s: float
amesim_port_1_pressure_derivative_temperature_component_pa_s: float
amesim_port_1_pressure_derivative_eos_sum_pa_s: float
amesim_port_1_pressure_derivative_eos_residual_pa_s: float
amesim_port_2_pressure_derivative_mass_component_pa_s: float
amesim_port_2_pressure_derivative_temperature_component_pa_s: float
amesim_port_2_pressure_derivative_eos_sum_pa_s: float
amesim_port_2_pressure_derivative_eos_residual_pa_s: float
python_port_1_pressure_derivative_mass_component_pa_s: float
python_port_1_pressure_derivative_temperature_component_pa_s: float
python_port_1_pressure_derivative_eos_sum_pa_s: float
python_port_2_pressure_derivative_mass_component_pa_s: float
python_port_2_pressure_derivative_temperature_component_pa_s: float
python_port_2_pressure_derivative_eos_sum_pa_s: float
python_port_1_temperature_k: float
python_port_2_temperature_k: float
python_port_1_pressure_pa: float
python_port_2_pressure_pa: float
python_port_1_mass_derivative_kg_s: float
python_port_2_mass_derivative_kg_s: float
python_port_1_current_dtemp_k_s: float
python_port_2_current_dtemp_k_s: float
python_port_1_real_gas_reference_dtemp_k_s: float
python_port_2_real_gas_reference_dtemp_k_s: float
python_port_2_amesim_center_counterfactual_dtemp_k_s: float
python_port_2_amesim_external_counterfactual_dtemp_k_s: float
python_port_2_amesim_external_center_counterfactual_dtemp_k_s: float
python_port_2_temperature_error_to_amesim_k: float
@dataclass(frozen=True)
class TestMqlPnch012EnergyEquationDiagnostic:
chamber_alias: str
piston_alias: str
line_alias: str
time_s: float
amesim_port_1_enthalpy_flow_w: float
amesim_port_1_mass_flow_g_s: float
amesim_chamber_mass_derivative_fd_g_s: float
amesim_chamber_mass_derivative_residual_g_s: float
amesim_chamber_temperature_derivative_fd_k_s: float
amesim_chamber_volume_rate_fd_m3_s: float
amesim_piston_volume_rate_m3_s: float
amesim_heat_flow_w: float
amesim_boundary_work_fd_volume_w: float
amesim_pn2vol_reference_dtemp_fd_volume_k_s: float
amesim_pn2vol_reference_dtemp_fd_volume_residual_k_s: float
amesim_pn2vol_reference_dtemp_piston_volume_k_s: float
amesim_pn2vol_reference_dtemp_piston_volume_residual_k_s: float
python_port_a_mass_flow_kg_s: float
python_current_energy_derivative_w: float
python_current_chamber_temperature_derivative_k_s: float
reference_temperature_k: float
@dataclass(frozen=True)
class TestMqlPneumatic96InletLineDiagnostic:
line_alias: str
node_alias: str
orifice_alias: str
chamber_alias: str
time_s: float
amesim_line_pressure_pa: float
amesim_line_temperature_k: float
amesim_line_mass_g: float
amesim_line_mass_derivative_fd_g_s: float
amesim_line_to_node_mass_flow_g_s: float
amesim_orifice_to_line_mass_flow_g_s: float
amesim_storage_mass_derivative_from_ports_g_s: float
amesim_storage_mass_derivative_residual_g_s: float
amesim_chamber_pressure_pa: float
amesim_chamber_temperature_k: float
amesim_node_pressure_pa: float
amesim_node_temperature_k: float
amesim_line_temperature_derivative_fd_k_s: float
amesim_line_pressure_derivative_fd_pa_s: float
amesim_port_1_enthalpy_dtemp_component_k_s: float
amesim_port_2_enthalpy_dtemp_component_k_s: float
amesim_mass_offset_dtemp_component_k_s: float
amesim_reference_temperature_derivative_sum_k_s: float
amesim_reference_temperature_derivative_residual_k_s: float
amesim_pressure_derivative_mass_component_pa_s: float
amesim_pressure_derivative_temperature_component_pa_s: float
amesim_pressure_derivative_eos_sum_pa_s: float
amesim_pressure_derivative_eos_residual_pa_s: float
python_line_pressure_pa: float
python_line_temperature_k: float
python_line_mass_g: float
python_line_pressure_error_pa: float
python_line_temperature_error_k: float
python_line_mass_error_g: float
python_line_to_node_mass_flow_g_s: float
python_line_to_node_mass_flow_error_g_s: float
python_orifice_to_line_mass_flow_g_s: float
python_orifice_to_line_mass_flow_error_g_s: float
python_storage_mass_derivative_g_s: float
python_storage_mass_derivative_error_g_s: float
python_line_to_node_amesim_node_mass_flow_g_s: float
python_line_to_node_amesim_node_mass_flow_error_g_s: float
python_line_to_node_amesim_line_mass_flow_g_s: float
python_line_to_node_amesim_line_mass_flow_error_g_s: float
python_line_to_node_amesim_node_line_mass_flow_g_s: float
python_line_to_node_amesim_node_line_mass_flow_error_g_s: float
python_orifice_to_line_amesim_line_mass_flow_g_s: float
python_orifice_to_line_amesim_line_mass_flow_error_g_s: float
python_orifice_to_line_amesim_chamber_mass_flow_g_s: float
python_orifice_to_line_amesim_chamber_mass_flow_error_g_s: float
python_orifice_to_line_amesim_line_chamber_mass_flow_g_s: float
python_orifice_to_line_amesim_line_chamber_mass_flow_error_g_s: float
python_storage_amesim_boundary_states_mass_derivative_g_s: float
python_storage_amesim_boundary_states_mass_derivative_error_g_s: float
python_chamber_pressure_pa: float
python_chamber_temperature_k: float
python_current_temperature_derivative_k_s: float
python_port_1_current_dtemp_component_k_s: float
python_port_2_current_dtemp_component_k_s: float
python_heat_dtemp_component_k_s: float
python_temperature_derivative_error_k_s: float
python_pressure_derivative_mass_component_pa_s: float
python_pressure_derivative_temperature_component_pa_s: float
python_pressure_derivative_eos_sum_pa_s: float
python_pressure_derivative_eos_error_pa_s: float
python_reference_port_1_enthalpy_flow_w: float
python_reference_port_2_enthalpy_flow_w: float
python_reference_port_1_dtemp_component_k_s: float
python_reference_port_2_dtemp_component_k_s: float
python_reference_mass_offset_dtemp_component_k_s: float
python_reference_temperature_derivative_k_s: float
python_reference_temperature_derivative_error_k_s: float
python_reference_pressure_derivative_mass_component_pa_s: float
python_reference_pressure_derivative_temperature_component_pa_s: float
python_reference_pressure_derivative_eos_sum_pa_s: float
python_reference_pressure_derivative_eos_error_pa_s: float
python_reference_amesim_storage_mass_offset_dtemp_component_k_s: float
python_reference_amesim_storage_temperature_derivative_k_s: float
python_reference_amesim_storage_temperature_derivative_error_k_s: float
python_reference_amesim_storage_pressure_derivative_mass_component_pa_s: float
python_reference_amesim_storage_pressure_derivative_temperature_component_pa_s: float
python_reference_amesim_storage_pressure_derivative_eos_sum_pa_s: float
python_reference_amesim_storage_pressure_derivative_eos_error_pa_s: float
python_active_temperature_derivative_k_s: float
python_active_temperature_derivative_error_k_s: float
python_active_pressure_derivative_eos_sum_pa_s: float
python_active_pressure_derivative_eos_error_pa_s: float
python_chamber_pressure_error_pa: float
python_chamber_temperature_error_k: float
@dataclass(frozen=True)
class TestMqlPnvoEventWindowSampleDiagnostic:
time_s: float
data_paths: tuple[str, ...]
python_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(
default_factory=tuple
)
pnl0001_pressure_loss_diagnostics: tuple[
TestMqlPnl0001PressureLossCalibrationDiagnostic, ...
] = field(default_factory=tuple)
pnvo_flow_parameter_diagnostics: tuple[
TestMqlPnvoFlowParameterDiagnostic, ...
] = field(default_factory=tuple)
pnl0001_energy_flow_diagnostics: tuple[
TestMqlPnl0001EnergyFlowDiagnostic, ...
] = field(default_factory=tuple)
p4_node_enthalpy_breakdown_diagnostics: tuple[
TestMqlP4NodeEnthalpyBreakdownDiagnostic, ...
] = field(default_factory=tuple)
pnvo_upstream_enthalpy_diagnostics: tuple[
TestMqlPnvoUpstreamEnthalpyDiagnostic, ...
] = field(default_factory=tuple)
pnl0003_energy_diagnostics: tuple[
TestMqlPnl0003EnergyDiagnostic, ...
] = field(default_factory=tuple)
pneumatic_96_inlet_line_diagnostics: tuple[
TestMqlPneumatic96InletLineDiagnostic, ...
] = field(default_factory=tuple)
pnch012_energy_equation_diagnostics: tuple[
TestMqlPnch012EnergyEquationDiagnostic, ...
] = 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:
return abs(
self.python_values_by_data_path[data_path]
- self.amesim_values_by_data_path[data_path]
)
@dataclass(frozen=True)
class TestMqlPnvoEventWindowDiagnostic:
orifice_alias: str
event_time_s: float
final_time_s: float
data_paths: tuple[str, ...]
segment_diagnostics: tuple[TestMqlPnvoEventWindowSegmentDiagnostic, ...]
sample_diagnostics: tuple[TestMqlPnvoEventWindowSampleDiagnostic, ...]
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 TestMqlPnvoEventWindowCandidateMetricComparison:
time_s: float
metric_name: str
default_abs_error: float
candidate_abs_error: float
@property
def abs_error_delta(self) -> float:
return self.candidate_abs_error - self.default_abs_error
@dataclass(frozen=True)
class TestMqlPnvoEventWindowCandidateComparisonDiagnostic:
orifice_alias: str
event_time_s: float
final_time_s: float
default_diagnostic: TestMqlPnvoEventWindowDiagnostic
candidate_diagnostic: TestMqlPnvoEventWindowDiagnostic
metric_comparisons: tuple[
TestMqlPnvoEventWindowCandidateMetricComparison, ...
]
@dataclass(frozen=True)
class TestMqlFullStateComparisonPathConfig:
archive_path: Path = field(
default_factory=lambda: AMESIM_TEST_MQL_ARCHIVE_PATH
)
amesim_results_archive_path: Path | None = None
output_dir: Path | None = None
@property
def resolved_amesim_results_archive_path(self) -> Path:
return self.amesim_results_archive_path or self.archive_path
@dataclass(frozen=True)
class TestMqlFullStateComparisonExecutionConfig:
write_summary: bool = True
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-2, max_step=1.0e-3)
)
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
resistance_boundary_temperature_k: float = 293.15
use_pneumatic_96_reference_rhs: bool = False
use_pneumatic_69_reference_rhs: bool = False
@dataclass(frozen=True)
class TestMqlFullStateComparisonScriptConfig:
paths: TestMqlFullStateComparisonPathConfig = field(
default_factory=TestMqlFullStateComparisonPathConfig
)
execution: TestMqlFullStateComparisonExecutionConfig = field(
default_factory=TestMqlFullStateComparisonExecutionConfig
)
def _default_output_dir() -> Path:
timestamp = datetime.now(UTC).strftime("test_mql_full_state_%Y%m%d_%H%M%S_%f")
return SIMULATION_RUNS_DIR / timestamp
def run_test_mql_full_state_comparison(
config: TestMqlFullStateComparisonScriptConfig | None = None,
) -> tuple[TestMqlFullStateComparisonRun, Path]:
config = config or TestMqlFullStateComparisonScriptConfig()
system = TestMqlSystem(archive_path=config.paths.archive_path)
amesim_results = load_test_mql_amesim_results(
config.paths.resolved_amesim_results_archive_path,
time_stop_s=config.execution.solver.t_stop,
)
output_schema = build_test_mql_output_schema(amesim_results)
selected_paths = config.execution.data_paths
spec = system.discover_pneumatic_branch_topology().chamber_segment_specs[0]
result = system.simulate_full_state_series_from_spec(
spec,
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
),
use_pneumatic_96_reference_rhs=(
config.execution.use_pneumatic_96_reference_rhs
),
use_pneumatic_69_reference_rhs=(
config.execution.use_pneumatic_69_reference_rhs
),
config=config.execution.solver,
t_eval=list(config.execution.t_eval) if config.execution.t_eval is not None else None,
data_paths=selected_paths,
)
closure = system.full_state_closure_from_spec(
spec,
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
),
use_pneumatic_96_reference_rhs=(
config.execution.use_pneumatic_96_reference_rhs
),
use_pneumatic_69_reference_rhs=(
config.execution.use_pneumatic_69_reference_rhs
),
)
series_by_data_path = {
data_path: result.series[data_path]
for data_path in result.series
if data_path != "time"
}
output = validate_test_mql_output(
times=result.t,
series_by_data_path=series_by_data_path,
schema=output_schema,
data_paths=selected_paths,
)
comparison = compare_validated_test_mql_output(
times=output.times,
series_by_data_path=output.series_by_data_path,
schema=output_schema,
amesim_results=amesim_results,
data_paths=output.data_paths,
)
run = TestMqlFullStateComparisonRun(
system=system,
closure=closure,
amesim_results=amesim_results,
output_schema=output_schema,
result=result,
output=output,
comparison=comparison,
)
output_dir = config.paths.output_dir or _default_output_dir()
if config.execution.write_summary or config.execution.write_comparison_csv:
output_dir.mkdir(parents=True, exist_ok=True)
if config.execution.write_summary:
(output_dir / "test_mql_full_state_comparison_summary.txt").write_text(
format_test_mql_full_state_comparison_summary(run),
encoding="utf-8",
)
if config.execution.write_comparison_csv:
write_test_mql_comparison_csv(
output_dir=output_dir,
python_times=output.times,
python_series_by_data_path=output.series_by_data_path,
amesim_results=amesim_results,
data_paths=output.data_paths,
)
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
),
"use_pneumatic_96_reference_rhs": (
config.execution.use_pneumatic_96_reference_rhs
),
"use_pneumatic_69_reference_rhs": (
config.execution.use_pneumatic_69_reference_rhs
),
}
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.resolved_amesim_results_archive_path,
time_stop_s=event_time_s,
)
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 _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}")
amesim_chamber_temperature_k = amesim_value(f"temp@{chamber_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
)
candidate_port1_to_port2_kg_s = line.pn2pipefr_mass_flow(
port_1_pressure_pa=(
amesim_chamber_gauge_pressure_pa + AMESIM_REFERENCE_PRESSURE_PA
),
port_1_temperature_k=amesim_chamber_temperature_k,
port_2_pressure_pa=(
amesim_line_gauge_pressure_pa + AMESIM_REFERENCE_PRESSURE_PA
),
port_2_temperature_k=amesim_line_temperature_k,
)
candidate_pn2pipefr_dm1_g_s = -candidate_port1_to_port2_kg_s * 1.0e3
if amesim_dm1_g_s != 0.0:
candidate_pn2pipefr_to_amesim_dm1_ratio = (
candidate_pn2pipefr_dm1_g_s / amesim_dm1_g_s
)
else:
candidate_pn2pipefr_to_amesim_dm1_ratio = (
float("inf") if candidate_pn2pipefr_dm1_g_s != 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,
candidate_pn2pipefr_dm1_g_s=candidate_pn2pipefr_dm1_g_s,
candidate_pn2pipefr_to_amesim_dm1_ratio=(
candidate_pn2pipefr_to_amesim_dm1_ratio
),
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 _ideal_pn2vol_reference_dtemp(
*,
gas: AmesimPneumaticGas,
mass_kg: float,
temperature_k: float,
mass_derivative_kg_s: float,
enthalpy_flow_w: float,
heat_flow_w: float,
reference_temperature_k: float = 298.15,
) -> float:
reference_offset_flow_w = (
gas.cp * reference_temperature_k - gas.cv * temperature_k
) * mass_derivative_kg_s
return (
enthalpy_flow_w + reference_offset_flow_w + heat_flow_w
) / (mass_kg * gas.cv)
def _series_finite_difference_at(
*,
times: tuple[float, ...] | list[float],
values: tuple[float, ...] | list[float],
time_s: float,
) -> float:
if len(times) != len(values):
raise ValueError("times and values must have equal length")
if len(times) < 2:
raise ValueError("at least two samples are required")
index = min(range(len(times)), key=lambda idx: abs(times[idx] - time_s))
if index == 0:
left = 0
right = 1
elif index == len(times) - 1:
left = len(times) - 2
right = len(times) - 1
else:
left = index - 1
right = index + 1
dt = times[right] - times[left]
if dt == 0.0:
raise ValueError("finite difference time interval must be non-zero")
return (values[right] - values[left]) / dt
def _series_one_sided_differences_at(
*,
times: tuple[float, ...] | list[float],
values: tuple[float, ...] | list[float],
time_s: float,
) -> tuple[float, float]:
if len(times) != len(values):
raise ValueError("times and values must have equal length")
if len(times) < 2:
raise ValueError("at least two samples are required")
index = min(range(len(times)), key=lambda idx: abs(times[idx] - time_s))
left = max(index - 1, 0)
right = min(index + 1, len(times) - 1)
backward_dt = times[index] - times[left]
forward_dt = times[right] - times[index]
if backward_dt == 0.0:
backward = (values[right] - values[index]) / forward_dt
else:
backward = (values[index] - values[left]) / backward_dt
if forward_dt == 0.0:
forward = (values[index] - values[left]) / backward_dt
else:
forward = (values[right] - values[index]) / forward_dt
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(
*,
gas: AmesimPneumaticGas,
reference_temperature_k: float,
flow_kg_s: float,
node_temperature_k: float,
connected_temperature_k: float,
port_mass_flow_kg_s: float,
) -> float:
stream_temperature_k = (
node_temperature_k if flow_kg_s >= 0.0 else connected_temperature_k
)
reference_h = gas.specific_reference_enthalpy(
stream_temperature_k,
reference_temperature_k,
)
return port_mass_flow_kg_s * reference_h
def _real_gas_reference_enthalpy_flow_for_node_port(
*,
gas: AmesimPneumaticGas,
reference_temperature_k: float,
flow_kg_s: float,
node_pressure_pa: float,
node_temperature_k: float,
connected_pressure_pa: float,
connected_temperature_k: float,
port_mass_flow_kg_s: float,
) -> float:
stream_pressure_pa = node_pressure_pa if flow_kg_s >= 0.0 else connected_pressure_pa
stream_temperature_k = (
node_temperature_k if flow_kg_s >= 0.0 else connected_temperature_k
)
reference_h = gas.pressure_reference_enthalpy(
stream_pressure_pa,
stream_temperature_k,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
return port_mass_flow_kg_s * reference_h
def _pnl0001_energy_flow_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
rhs_diagnostic: TestMqlPnl0001LineRhsDiagnostic,
line_alias: str,
chamber_alias: str,
node_alias: str,
time_s: float,
) -> TestMqlPnl0001EnergyFlowDiagnostic:
if line_alias != "pneumatic_69" or chamber_alias != "pn_c1_8":
raise KeyError(f"Unsupported PNL0001 energy diagnostic: {line_alias}")
if node_alias != "pnnode4_16":
raise KeyError(f"Unsupported PNL0001 node diagnostic: {node_alias}")
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
line = closure.pneumatic_closure.components.p4_port3_remote_primary_line
line_properties = snapshot.p4_port3_remote_primary_line
chamber_properties = snapshot.p4_port3_remote_primary_chamber
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_dh1 = amesim_value(f"dh1@{line_alias}")
amesim_dm1 = amesim_value(f"dm1@{line_alias}")
amesim_node_dh2 = amesim_value(f"dh2@{node_alias}")
amesim_node_dm2 = amesim_value(f"dm2@{node_alias}")
direct_port_1 = rhs_diagnostic.chamber_to_line_flow_kg_s * (
chamber_properties.h
if rhs_diagnostic.chamber_to_line_flow_kg_s > 0.0
else line_properties.h
)
direct_port_2 = rhs_diagnostic.node_to_line_flow_kg_s * line_properties.h
direct_energy_derivative = (
direct_port_1 + direct_port_2 + rhs_diagnostic.thermal_energy_flow_w
)
p4_node_port_2_mass_flow_kg_s = (
snapshot.p4_port3_remote_balance.port_2_mass_flow_g_s * 1.0e-3
)
if abs(p4_node_port_2_mass_flow_kg_s) <= 1.0e-12:
p4_node_port_2_connected_h = line_properties.h
else:
p4_node_port_2_connected_h = (
snapshot.p4_port3_remote_balance.port_2_enthalpy_flow_w
/ p4_node_port_2_mass_flow_kg_s
)
p4_node_port_2_inlet_h = (
p4_node_port_2_connected_h
if rhs_diagnostic.node_to_line_flow_kg_s > 0.0
else line_properties.h
)
p4_node_port_2_counterfactual_energy_derivative = (
rhs_diagnostic.port_1_energy_flow_w
+ rhs_diagnostic.node_to_line_flow_kg_s * p4_node_port_2_inlet_h
+ rhs_diagnostic.thermal_energy_flow_w
)
p4_node_port_2_counterfactual_dtemp = (
p4_node_port_2_counterfactual_energy_derivative
- line_properties.u * rhs_diagnostic.mass_derivative_kg_s
) / (line.state.m * line.gas.cv)
reference_temperature_k = 298.15
def reference_h(temperature_k: float) -> float:
return line.gas.specific_reference_enthalpy(
temperature_k,
reference_temperature_k,
)
amesim_stream_temperature = (
amesim_value(f"t2@{line_alias}")
if amesim_dm1 >= 0.0
else amesim_value(f"temp@{chamber_alias}")
)
reference_enthalpy_estimate = (
amesim_dm1 * 1.0e-3 * reference_h(amesim_stream_temperature)
)
python_dm1_kg_s = -rhs_diagnostic.chamber_to_line_flow_kg_s
python_port_1_stream_temperature_k = (
line_properties.T if python_dm1_kg_s >= 0.0 else chamber_properties.T
)
python_reference_port_1 = python_dm1_kg_s * reference_h(
python_port_1_stream_temperature_k
)
python_reference_node_port_2 = sum(
(
_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_node_to_line_flow,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_temperature_k=snapshot.p4_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_node_to_line_flow,
),
_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_to_port3_line_flow,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_temperature_k=snapshot.p4_port3_remote_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_to_port3_line_flow,
),
_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
node_temperature_k=snapshot.p4_port3_remote_orifice_line_port_2.T,
connected_temperature_k=snapshot.p4_port3_remote_primary_line.T,
port_mass_flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
),
)
)
python_reference_sum = python_reference_port_1 + python_reference_node_port_2
amesim_mgas_kg = amesim_value(f"mgas@{line_alias}") * 1.0e-3
amesim_line_temperature = amesim_value(f"t2@{line_alias}")
amesim_mass_derivative = (amesim_node_dm2 - amesim_dm1) * 1.0e-3
amesim_heat_flow = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* (line.external_temperature - amesim_line_temperature)
)
current_line_dtemp = (
rhs_diagnostic.energy_derivative_w
- line_properties.u * rhs_diagnostic.mass_derivative_kg_s
) / (line.state.m * line.gas.cv)
amesim_line_dtemp_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"t2@{line_alias}"),
time_s=time_s,
)
candidate_pn2pipefr_dm2i_g_s = -amesim_dm1
candidate_pn2pipefr_dh2i = -reference_enthalpy_estimate
candidate_storage_sdh = amesim_node_dh2 + candidate_pn2pipefr_dh2i
candidate_pn2pipefr_dtemp = _ideal_pn2vol_reference_dtemp(
gas=line.gas,
mass_kg=amesim_mgas_kg,
temperature_k=amesim_line_temperature,
mass_derivative_kg_s=amesim_mass_derivative,
enthalpy_flow_w=candidate_storage_sdh,
heat_flow_w=amesim_heat_flow,
)
amesim_node_plus_dh1_dtemp = _ideal_pn2vol_reference_dtemp(
gas=line.gas,
mass_kg=amesim_mgas_kg,
temperature_k=amesim_line_temperature,
mass_derivative_kg_s=amesim_mass_derivative,
enthalpy_flow_w=amesim_node_dh2 + amesim_dh1,
heat_flow_w=amesim_heat_flow,
)
amesim_node_minus_dh1_dtemp = _ideal_pn2vol_reference_dtemp(
gas=line.gas,
mass_kg=amesim_mgas_kg,
temperature_k=amesim_line_temperature,
mass_derivative_kg_s=amesim_mass_derivative,
enthalpy_flow_w=amesim_node_dh2 - amesim_dh1,
heat_flow_w=amesim_heat_flow,
)
python_reference_node_minus_dh1_dtemp = _ideal_pn2vol_reference_dtemp(
gas=line.gas,
mass_kg=line.state.m,
temperature_k=line_properties.T,
mass_derivative_kg_s=rhs_diagnostic.mass_derivative_kg_s,
enthalpy_flow_w=python_reference_node_port_2 - python_reference_port_1,
heat_flow_w=rhs_diagnostic.thermal_energy_flow_w,
)
return TestMqlPnl0001EnergyFlowDiagnostic(
line_alias=line_alias,
chamber_alias=chamber_alias,
node_alias=node_alias,
time_s=time_s,
amesim_port_1_enthalpy_flow_w=amesim_dh1,
amesim_port_1_mass_flow_g_s=amesim_dm1,
amesim_node_port_2_enthalpy_flow_w=amesim_node_dh2,
amesim_node_port_2_mass_flow_g_s=amesim_node_dm2,
amesim_observed_port_enthalpy_sum_w=amesim_dh1 + amesim_node_dh2,
python_port_1_internal_energy_flow_w=rhs_diagnostic.port_1_energy_flow_w,
python_port_2_internal_energy_flow_w=rhs_diagnostic.port_2_energy_flow_w,
python_current_energy_derivative_w=rhs_diagnostic.energy_derivative_w,
python_direct_enthalpy_port_1_flow_w=direct_port_1,
python_direct_enthalpy_port_2_flow_w=direct_port_2,
python_direct_enthalpy_energy_derivative_w=direct_energy_derivative,
python_direct_minus_current_energy_derivative_w=(
direct_energy_derivative - rhs_diagnostic.energy_derivative_w
),
python_p4_node_port_2_enthalpy_flow_w=(
snapshot.p4_port3_remote_balance.port_2_enthalpy_flow_w
),
python_p4_node_port_2_mass_flow_g_s=(
snapshot.p4_port3_remote_balance.port_2_mass_flow_g_s
),
python_p4_node_port_2_connected_h_j_kg=p4_node_port_2_connected_h,
python_p4_node_port_2_connected_temperature_k=(
p4_node_port_2_connected_h / line.gas.cp
),
python_p4_node_port_2_connected_h_delta_to_line_h_j_kg=(
p4_node_port_2_connected_h - line_properties.h
),
python_p4_node_port_2_counterfactual_dtemp_k_s=(
p4_node_port_2_counterfactual_dtemp
),
reference_temperature_k=reference_temperature_k,
amesim_port_1_reference_enthalpy_estimate_w=reference_enthalpy_estimate,
amesim_port_1_reference_enthalpy_error_w=(
reference_enthalpy_estimate - amesim_dh1
),
amesim_candidate_pn2pipefr_dm2i_g_s=candidate_pn2pipefr_dm2i_g_s,
amesim_candidate_pn2pipefr_dh2i_w=candidate_pn2pipefr_dh2i,
amesim_candidate_storage_enthalpy_sum_w=candidate_storage_sdh,
amesim_candidate_pn2pipefr_dtemp_k_s=candidate_pn2pipefr_dtemp,
amesim_candidate_pn2pipefr_dtemp_residual_k_s=(
candidate_pn2pipefr_dtemp - amesim_line_dtemp_fd
),
python_reference_port_1_enthalpy_flow_w=python_reference_port_1,
python_reference_node_port_2_enthalpy_flow_w=python_reference_node_port_2,
python_reference_observed_port_enthalpy_sum_w=python_reference_sum,
python_reference_port_1_to_amesim_error_w=(
python_reference_port_1 - amesim_dh1
),
python_reference_node_port_2_to_amesim_error_w=(
python_reference_node_port_2 - amesim_node_dh2
),
python_reference_sum_to_amesim_error_w=(
python_reference_sum - (amesim_dh1 + amesim_node_dh2)
),
python_current_line_temperature_derivative_k_s=current_line_dtemp,
amesim_line_temperature_derivative_fd_k_s=amesim_line_dtemp_fd,
amesim_pn2vol2_dtemp_node_plus_dh1_k_s=amesim_node_plus_dh1_dtemp,
amesim_pn2vol2_dtemp_node_minus_dh1_k_s=amesim_node_minus_dh1_dtemp,
python_reference_pn2vol2_dtemp_node_minus_dh1_k_s=(
python_reference_node_minus_dh1_dtemp
),
)
def _p4_node_enthalpy_breakdown_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
node_alias: str,
time_s: float,
) -> TestMqlP4NodeEnthalpyBreakdownDiagnostic:
if node_alias != "pnnode4_16":
raise KeyError(f"Unsupported P4 node enthalpy diagnostic: {node_alias}")
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
line = closure.pneumatic_closure.components.p4_port3_remote_primary_line
balance = snapshot.p4_port3_remote_balance
reference_temperature_k = 298.15
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_port_1_enthalpy = amesim_value("dh1@pneumatic_83")
amesim_port_1_mass = amesim_value("dm1@pneumatic_83")
amesim_port_3_enthalpy = amesim_value("dh2@pneumatic_95")
amesim_port_3_mass = amesim_value("dm2@pneumatic_95")
amesim_port_4_enthalpy = amesim_value("dh2@pn_morifice_1")
amesim_port_4_mass = amesim_value("dm2@pn_morifice_1")
amesim_port_2_enthalpy = amesim_value(f"dh2@{node_alias}")
amesim_port_2_mass = amesim_value(f"dm2@{node_alias}")
reference_port_1 = _reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_node_to_line_flow,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_temperature_k=snapshot.p4_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_node_to_line_flow,
)
reference_port_3 = _reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_to_port3_line_flow,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_temperature_k=snapshot.p4_port3_remote_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_to_port3_line_flow,
)
reference_port_4 = _reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
node_temperature_k=snapshot.p4_port3_remote_orifice_line_port_2.T,
connected_temperature_k=snapshot.p4_port3_remote_primary_line.T,
port_mass_flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
)
reference_port_2 = reference_port_1 + reference_port_3 + reference_port_4
real_gas_reference_port_1 = _real_gas_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_node_to_line_flow,
node_pressure_pa=snapshot.p4_port3_remote_primary_line.p,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_pressure_pa=snapshot.p4_port3_line.p,
connected_temperature_k=snapshot.p4_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_node_to_line_flow,
)
real_gas_reference_port_3 = _real_gas_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_to_port3_line_flow,
node_pressure_pa=snapshot.p4_port3_remote_primary_line.p,
node_temperature_k=snapshot.p4_port3_remote_primary_line.T,
connected_pressure_pa=snapshot.p4_port3_remote_port3_line.p,
connected_temperature_k=snapshot.p4_port3_remote_port3_line.T,
port_mass_flow_kg_s=-snapshot.p4_port3_remote_to_port3_line_flow,
)
real_gas_reference_port_4 = _real_gas_reference_enthalpy_flow_for_node_port(
gas=line.gas,
reference_temperature_k=reference_temperature_k,
flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
node_pressure_pa=snapshot.p4_port3_remote_orifice_line_port_2.p,
node_temperature_k=snapshot.p4_port3_remote_orifice_line_port_2.T,
connected_pressure_pa=snapshot.p4_port3_remote_primary_line.p,
connected_temperature_k=snapshot.p4_port3_remote_primary_line.T,
port_mass_flow_kg_s=snapshot.p4_port3_remote_orifice_to_node_flow,
)
real_gas_reference_port_2 = (
real_gas_reference_port_1
+ real_gas_reference_port_3
+ real_gas_reference_port_4
)
return TestMqlP4NodeEnthalpyBreakdownDiagnostic(
node_alias=node_alias,
time_s=time_s,
amesim_port_1_enthalpy_flow_w=amesim_port_1_enthalpy,
amesim_port_1_mass_flow_g_s=amesim_port_1_mass,
amesim_port_3_enthalpy_flow_w=amesim_port_3_enthalpy,
amesim_port_3_mass_flow_g_s=amesim_port_3_mass,
amesim_port_4_enthalpy_flow_w=amesim_port_4_enthalpy,
amesim_port_4_mass_flow_g_s=amesim_port_4_mass,
amesim_port_2_enthalpy_flow_w=amesim_port_2_enthalpy,
amesim_port_2_mass_flow_g_s=amesim_port_2_mass,
python_port_1_enthalpy_flow_w=balance.port_1_enthalpy_flow_w,
python_port_1_mass_flow_g_s=balance.port_1_mass_flow_g_s,
python_port_3_enthalpy_flow_w=balance.port_3_enthalpy_flow_w,
python_port_3_mass_flow_g_s=balance.port_3_mass_flow_g_s,
python_port_4_enthalpy_flow_w=balance.port_4_enthalpy_flow_w,
python_port_4_mass_flow_g_s=balance.port_4_mass_flow_g_s,
python_port_2_enthalpy_flow_w=balance.port_2_enthalpy_flow_w,
python_port_2_mass_flow_g_s=balance.port_2_mass_flow_g_s,
python_reference_port_1_enthalpy_flow_w=reference_port_1,
python_reference_port_3_enthalpy_flow_w=reference_port_3,
python_reference_port_4_enthalpy_flow_w=reference_port_4,
python_reference_port_2_enthalpy_flow_w=reference_port_2,
python_real_gas_reference_port_1_enthalpy_flow_w=real_gas_reference_port_1,
python_real_gas_reference_port_3_enthalpy_flow_w=real_gas_reference_port_3,
python_real_gas_reference_port_4_enthalpy_flow_w=real_gas_reference_port_4,
python_real_gas_reference_port_2_enthalpy_flow_w=real_gas_reference_port_2,
python_port_2_enthalpy_error_w=(
balance.port_2_enthalpy_flow_w - amesim_port_2_enthalpy
),
python_reference_port_2_enthalpy_error_w=(
reference_port_2 - amesim_port_2_enthalpy
),
python_real_gas_reference_port_2_enthalpy_error_w=(
real_gas_reference_port_2 - amesim_port_2_enthalpy
),
)
def _pnvo_upstream_enthalpy_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
orifice_alias: str,
upstream_line_alias: str,
downstream_node_alias: str,
time_s: float,
) -> TestMqlPnvoUpstreamEnthalpyDiagnostic:
if orifice_alias != "pn_morifice_1":
raise KeyError(f"Unsupported PNVO upstream diagnostic: {orifice_alias}")
if upstream_line_alias != "pneumatic_87":
raise KeyError(f"Unsupported PNVO upstream line diagnostic: {upstream_line_alias}")
if downstream_node_alias != "pnnode4_16":
raise KeyError(f"Unsupported PNVO downstream node diagnostic: {downstream_node_alias}")
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
line = closure.pneumatic_closure.components.p4_port3_remote_orifice_line
flow_kg_s = snapshot.p4_port3_remote_orifice_to_node_flow
reference_temperature_k = 298.15
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_orifice_dh2 = amesim_value(f"dh2@{orifice_alias}")
amesim_orifice_dm2 = amesim_value(f"dm2@{orifice_alias}")
amesim_line_t2 = amesim_value(f"t2@{upstream_line_alias}")
if amesim_orifice_dm2 >= 0.0:
amesim_stream_temperature = amesim_line_t2
amesim_stream_pressure = (
amesim_value(f"p2@{upstream_line_alias}")
+ AMESIM_REFERENCE_PRESSURE_PA
)
else:
amesim_stream_temperature = amesim_value(f"temp1@{downstream_node_alias}")
amesim_stream_pressure = (
amesim_value(f"press1@{downstream_node_alias}")
+ AMESIM_REFERENCE_PRESSURE_PA
)
amesim_dm2_kg_s = amesim_orifice_dm2 * 1.0e-3
amesim_reference_dh2 = amesim_dm2_kg_s * line.gas.specific_reference_enthalpy(
amesim_stream_temperature,
reference_temperature_k,
)
amesim_real_gas_reference_dh2 = (
amesim_dm2_kg_s
* line.gas.pressure_reference_enthalpy(
amesim_stream_pressure,
amesim_stream_temperature,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
)
if abs(amesim_orifice_dm2) <= 1.0e-12:
amesim_implied_temperature = amesim_stream_temperature
else:
amesim_implied_temperature = line.gas.reference_temperature_from_specific_enthalpy(
amesim_orifice_dh2 / (amesim_orifice_dm2 * 1.0e-3),
reference_temperature_k,
)
python_stream_properties = (
snapshot.p4_port3_remote_orifice_line_port_2
if flow_kg_s >= 0.0
else snapshot.p4_port3_remote_primary_line
)
python_current_dh2 = flow_kg_s * python_stream_properties.h
python_reference_dh2 = flow_kg_s * line.gas.specific_reference_enthalpy(
python_stream_properties.T,
reference_temperature_k,
)
python_real_gas_reference_dh2 = flow_kg_s * line.gas.pressure_reference_enthalpy(
python_stream_properties.p,
python_stream_properties.T,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
return TestMqlPnvoUpstreamEnthalpyDiagnostic(
orifice_alias=orifice_alias,
upstream_line_alias=upstream_line_alias,
time_s=time_s,
amesim_orifice_port_2_enthalpy_flow_w=amesim_orifice_dh2,
amesim_orifice_port_2_mass_flow_g_s=amesim_orifice_dm2,
amesim_orifice_port_3_enthalpy_flow_w=amesim_value(
f"dh3@{orifice_alias}"
),
amesim_orifice_port_3_mass_flow_g_s=amesim_value(
f"dm3@{orifice_alias}"
),
amesim_upstream_line_center_enthalpy_flow_w=amesim_value(
f"dhctr@{upstream_line_alias}"
),
amesim_upstream_line_center_mass_flow_g_s=amesim_value(
f"dmctr@{upstream_line_alias}"
),
amesim_upstream_line_port_2_temperature_k=amesim_line_t2,
amesim_upstream_line_port_2_pressure_pa=amesim_stream_pressure,
amesim_orifice_port_2_implied_reference_temperature_k=(
amesim_implied_temperature
),
amesim_orifice_port_2_implied_temperature_delta_to_line_k=(
amesim_implied_temperature - amesim_line_t2
),
amesim_upstream_line_port_2_reference_enthalpy_flow_w=amesim_reference_dh2,
amesim_upstream_line_port_2_reference_enthalpy_error_w=(
amesim_reference_dh2 - amesim_orifice_dh2
),
amesim_upstream_line_port_2_real_gas_reference_enthalpy_flow_w=(
amesim_real_gas_reference_dh2
),
amesim_upstream_line_port_2_real_gas_reference_enthalpy_error_w=(
amesim_real_gas_reference_dh2 - amesim_orifice_dh2
),
python_orifice_to_node_flow_kg_s=flow_kg_s,
python_upstream_line_port_2_temperature_k=(
snapshot.p4_port3_remote_orifice_line_port_2.T
),
python_upstream_line_port_2_pressure_pa=python_stream_properties.p,
python_current_port_2_enthalpy_flow_w=python_current_dh2,
python_reference_port_2_enthalpy_flow_w=python_reference_dh2,
python_real_gas_reference_port_2_enthalpy_flow_w=(
python_real_gas_reference_dh2
),
python_current_port_2_enthalpy_error_w=(
python_current_dh2 - amesim_orifice_dh2
),
python_reference_port_2_enthalpy_error_w=(
python_reference_dh2 - amesim_orifice_dh2
),
python_real_gas_reference_port_2_enthalpy_error_w=(
python_real_gas_reference_dh2 - amesim_orifice_dh2
),
)
def _pn2vol_reference_dtemp(
*,
gas: AmesimPneumaticGas,
temperature_k: float,
mass_kg: float,
mass_derivative_kg_s: float,
enthalpy_flow_w: float,
heat_flow_w: float,
reference_temperature_k: float = 298.15,
) -> float:
reference_offset_flow_w = (
gas.cp * reference_temperature_k - gas.cv * temperature_k
) * mass_derivative_kg_s
return (enthalpy_flow_w + reference_offset_flow_w + heat_flow_w) / (
mass_kg * gas.cv
)
def _actual_reference_enthalpy_flow(
*,
gas: AmesimPneumaticGas,
port_m_flow: float,
connected_pressure_pa: float,
connected_temperature_k: float,
internal_pressure_pa: float,
internal_temperature_k: float,
reference_temperature_k: float = 298.15,
) -> float:
if port_m_flow > 0.0:
pressure = connected_pressure_pa
temperature = connected_temperature_k
else:
pressure = internal_pressure_pa
temperature = internal_temperature_k
return port_m_flow * gas.pressure_reference_enthalpy(
pressure,
temperature,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
def _pressure_derivative_components_from_density_temperature(
*,
gas: AmesimPneumaticGas,
density_kg_m3: float,
temperature_k: float,
density_derivative_kg_m3_s: float,
temperature_derivative_k_s: float,
) -> tuple[float, float, float]:
density_step = max(abs(density_kg_m3) * 1.0e-6, 1.0e-9)
if density_kg_m3 <= density_step:
density_step = density_kg_m3 * 0.5
temperature_step = max(abs(temperature_k) * 1.0e-6, 1.0e-4)
dp_drho = (
gas.pressure(density_kg_m3 + density_step, temperature_k)
- gas.pressure(density_kg_m3 - density_step, temperature_k)
) / (2.0 * density_step)
dp_dtemp = (
gas.pressure(density_kg_m3, temperature_k + temperature_step)
- gas.pressure(density_kg_m3, temperature_k - temperature_step)
) / (2.0 * temperature_step)
mass_component = dp_drho * density_derivative_kg_m3_s
temperature_component = dp_dtemp * temperature_derivative_k_s
return mass_component, temperature_component, mass_component + temperature_component
def _pnl0003_energy_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
line_alias: str,
time_s: float,
) -> TestMqlPnl0003EnergyDiagnostic:
if line_alias != "pneumatic_87":
raise KeyError(f"Unsupported PNL0003 energy diagnostic: {line_alias}")
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
line = closure.pneumatic_closure.components.p4_port3_remote_orifice_line
port_1 = snapshot.p4_port3_remote_orifice_line_port_1
port_2 = snapshot.p4_port3_remote_orifice_line_port_2
connected_port_2 = snapshot.p4_port3_remote_primary_line
center_flow = snapshot.p4_port3_remote_orifice_line_center_flow
port_1_flow = snapshot.p4_port3_remote_node_to_orifice_line_flow
port_2_flow = -snapshot.p4_port3_remote_orifice_to_node_flow
reference_temperature_k = 298.15
pn3_port_2_mass_flow_kg_s = (
snapshot.p4_port3_remote_orifice_node_balance.port_2_mass_flow_g_s * 1.0e-3
)
if abs(pn3_port_2_mass_flow_kg_s) <= 1.0e-12:
port_1_connected_h = port_1.h
else:
port_1_connected_h = (
snapshot.p4_port3_remote_orifice_node_balance.port_2_enthalpy_flow_w
/ pn3_port_2_mass_flow_kg_s
)
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_t1 = amesim_value(f"t1@{line_alias}")
amesim_t2 = amesim_value(f"t2@{line_alias}")
amesim_p1 = amesim_value(f"p1@{line_alias}") + AMESIM_REFERENCE_PRESSURE_PA
amesim_p2 = amesim_value(f"p2@{line_alias}") + AMESIM_REFERENCE_PRESSURE_PA
amesim_dm1 = amesim_value("dm2@pn_node3_8")
amesim_dh1 = amesim_value("dh2@pn_node3_8")
python_node_dm1 = snapshot.p4_port3_remote_orifice_node_balance.port_1_mass_flow_g_s
python_node_dm3 = snapshot.p4_port3_remote_orifice_node_balance.port_3_mass_flow_g_s
python_node_dm2_sum = python_node_dm1 + python_node_dm3
python_node_to_line_dm = -python_node_dm2_sum
amesim_dm2 = amesim_value("dm3@pn_morifice_1")
amesim_dh2 = amesim_value("dh3@pn_morifice_1")
amesim_dmctr = amesim_value(f"dmctr@{line_alias}")
amesim_dhctr = amesim_value(f"dhctr@{line_alias}")
amesim_sdm1_kg_s = (amesim_dm1 + amesim_dmctr) * 1.0e-3
amesim_sdm2_kg_s = (amesim_dm2 - amesim_dmctr) * 1.0e-3
amesim_sdh1 = amesim_dh1 + amesim_dhctr
amesim_sdh2 = amesim_dh2 - amesim_dhctr
amesim_mgas_series = amesim_results.series(f"mgas@{line_alias}")
amesim_total_mass_derivative_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_mgas_series,
time_s=time_s,
)
(
amesim_total_mass_derivative_backward,
amesim_total_mass_derivative_forward,
) = _series_one_sided_differences_at(
times=amesim_results.times,
values=amesim_mgas_series,
time_s=time_s,
)
amesim_total_storage_mass_derivative = (
amesim_sdm1_kg_s + amesim_sdm2_kg_s
) * 1.0e3
python_center_mass_flow_g_s = center_flow * 1.0e3
amesim_center_python_sign_equivalent_g_s = -amesim_dmctr
amesim_center_mass_flow_python_sign_kg_s = (
amesim_center_python_sign_equivalent_g_s * 1.0e-3
)
amesim_port_1_center_mass_derivative_g_s = amesim_dmctr
amesim_port_2_center_mass_derivative_g_s = -amesim_dmctr
python_port_1_external_mass_flow_g_s = port_1_flow * 1.0e3
python_port_1_center_mass_derivative_g_s = -center_flow * 1.0e3
python_port_1_storage_mass_derivative_g_s = (
python_port_1_external_mass_flow_g_s
+ python_port_1_center_mass_derivative_g_s
)
python_port_2_external_mass_flow_g_s = port_2_flow * 1.0e3
python_port_2_center_mass_derivative_g_s = center_flow * 1.0e3
python_port_2_storage_mass_derivative_g_s = (
python_port_2_external_mass_flow_g_s
+ python_port_2_center_mass_derivative_g_s
)
def current_darcy_center_flow_for_state(
port_1_pressure_pa: float,
port_1_temperature_k: float,
port_2_pressure_pa: float,
port_2_temperature_k: float,
) -> float:
pressure_difference = port_1_pressure_pa - port_2_pressure_pa
if pressure_difference == 0.0:
return 0.0
upstream_pressure = (
port_1_pressure_pa if pressure_difference > 0.0 else port_2_pressure_pa
)
upstream_temperature = (
port_1_temperature_k if pressure_difference > 0.0 else port_2_temperature_k
)
upstream_density = line.gas.density(upstream_pressure, upstream_temperature)
magnitude = line._mass_flow_for_pressure_drop(
abs(pressure_difference),
density=upstream_density,
temperature=upstream_temperature,
)
return magnitude if pressure_difference > 0.0 else -magnitude
amesim_state_darcy_center_flow = current_darcy_center_flow_for_state(
amesim_p1,
amesim_t1,
amesim_p2,
amesim_t2,
)
amesim_state_pn2pipefr_center_flow = line.pn2pipefr_mass_flow(
port_1_pressure_pa=amesim_p1,
port_1_temperature_k=amesim_t1,
port_2_pressure_pa=amesim_p2,
port_2_temperature_k=amesim_t2,
)
amesim_center_real_port_1 = (
-amesim_center_mass_flow_python_sign_kg_s
* line.gas.pressure_reference_enthalpy(
amesim_p1,
amesim_t1,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
)
amesim_mass_1 = line.gas.density(amesim_p1, amesim_t1) * line.compliance_volume
amesim_mass_2 = line.gas.density(amesim_p2, amesim_t2) * line.compliance_volume
heat_flow_1 = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* 0.5
* (line.external_temperature - amesim_t1)
)
heat_flow_2 = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* 0.5
* (line.external_temperature - amesim_t2)
)
amesim_pn2vol_dtemp_1 = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=amesim_t1,
mass_kg=amesim_mass_1,
mass_derivative_kg_s=amesim_sdm1_kg_s,
enthalpy_flow_w=amesim_sdh1,
heat_flow_w=heat_flow_1,
reference_temperature_k=reference_temperature_k,
)
amesim_pn2vol_dtemp_2 = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=amesim_t2,
mass_kg=amesim_mass_2,
mass_derivative_kg_s=amesim_sdm2_kg_s,
enthalpy_flow_w=amesim_sdh2,
heat_flow_w=heat_flow_2,
reference_temperature_k=reference_temperature_k,
)
amesim_t1_series = amesim_results.series(f"t1@{line_alias}")
amesim_t2_series = amesim_results.series(f"t2@{line_alias}")
amesim_fd_dtemp_1 = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_t1_series,
time_s=time_s,
)
amesim_fd_dtemp_2 = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_t2_series,
time_s=time_s,
)
amesim_backward_dtemp_1, amesim_forward_dtemp_1 = (
_series_one_sided_differences_at(
times=amesim_results.times,
values=amesim_t1_series,
time_s=time_s,
)
)
amesim_backward_dtemp_2, amesim_forward_dtemp_2 = (
_series_one_sided_differences_at(
times=amesim_results.times,
values=amesim_t2_series,
time_s=time_s,
)
)
amesim_p1_series = amesim_results.series(f"p1@{line_alias}")
amesim_p2_series = amesim_results.series(f"p2@{line_alias}")
amesim_fd_dpressure_1 = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_p1_series,
time_s=time_s,
)
amesim_fd_dpressure_2 = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_p2_series,
time_s=time_s,
)
(
amesim_dpressure_1_mass,
amesim_dpressure_1_temperature,
amesim_dpressure_1_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=amesim_mass_1 / line.compliance_volume,
temperature_k=amesim_t1,
density_derivative_kg_m3_s=amesim_sdm1_kg_s / line.compliance_volume,
temperature_derivative_k_s=amesim_fd_dtemp_1,
)
(
amesim_dpressure_2_mass,
amesim_dpressure_2_temperature,
amesim_dpressure_2_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=amesim_mass_2 / line.compliance_volume,
temperature_k=amesim_t2,
density_derivative_kg_m3_s=amesim_sdm2_kg_s / line.compliance_volume,
temperature_derivative_k_s=amesim_fd_dtemp_2,
)
current_derivative_1, current_derivative_2 = line.derivatives_from_connections(
port_1_m_flow=port_1_flow,
connected_h_1=port_1_connected_h,
port_2_m_flow=port_2_flow,
connected_h_2=connected_port_2.h,
)
python_current_dtemp_1 = (
current_derivative_1.U - port_1.u * current_derivative_1.m
) / (line.state_1.m * line.gas.cv)
python_current_dtemp_2 = (
current_derivative_2.U - port_2.u * current_derivative_2.m
) / (line.state_2.m * line.gas.cv)
(
python_dpressure_1_mass,
python_dpressure_1_temperature,
python_dpressure_1_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=line.state_1.m / line.compliance_volume,
temperature_k=port_1.T,
density_derivative_kg_m3_s=current_derivative_1.m / line.compliance_volume,
temperature_derivative_k_s=python_current_dtemp_1,
)
(
python_dpressure_2_mass,
python_dpressure_2_temperature,
python_dpressure_2_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=line.state_2.m / line.compliance_volume,
temperature_k=port_2.T,
density_derivative_kg_m3_s=current_derivative_2.m / line.compliance_volume,
temperature_derivative_k_s=python_current_dtemp_2,
)
python_center_dh_1 = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=-center_flow,
connected_pressure_pa=port_2.p,
connected_temperature_k=port_2.T,
internal_pressure_pa=port_1.p,
internal_temperature_k=port_1.T,
reference_temperature_k=reference_temperature_k,
)
python_center_dh_2 = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=center_flow,
connected_pressure_pa=port_1.p,
connected_temperature_k=port_1.T,
internal_pressure_pa=port_2.p,
internal_temperature_k=port_2.T,
reference_temperature_k=reference_temperature_k,
)
if port_1_flow > 0.0:
python_port_1_dh = port_1_flow * (
port_1_connected_h - line.gas.cp * reference_temperature_k
)
else:
python_port_1_dh = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=port_1_flow,
connected_pressure_pa=port_1.p,
connected_temperature_k=port_1.T,
internal_pressure_pa=port_1.p,
internal_temperature_k=port_1.T,
reference_temperature_k=reference_temperature_k,
)
python_port_2_dh = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=port_2_flow,
connected_pressure_pa=connected_port_2.p,
connected_temperature_k=connected_port_2.T,
internal_pressure_pa=port_2.p,
internal_temperature_k=port_2.T,
reference_temperature_k=reference_temperature_k,
)
python_center_ideal_port_1 = -center_flow * line.gas.specific_reference_enthalpy(
port_1.T,
reference_temperature_k,
)
python_center_ideal_port_2 = -center_flow * line.gas.specific_reference_enthalpy(
port_2.T,
reference_temperature_k,
)
python_center_real_port_1 = -center_flow * line.gas.pressure_reference_enthalpy(
port_1.p,
port_1.T,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
python_center_real_port_2 = -center_flow * line.gas.pressure_reference_enthalpy(
port_2.p,
port_2.T,
reference_pressure=AMESIM_REFERENCE_PRESSURE_PA,
reference_temperature=reference_temperature_k,
)
python_heat_flow_1 = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* 0.5
* (line.external_temperature - port_1.T)
)
python_heat_flow_2 = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* 0.5
* (line.external_temperature - port_2.T)
)
python_ref_dtemp_1 = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=port_1.T,
mass_kg=line.state_1.m,
mass_derivative_kg_s=port_1_flow - center_flow,
enthalpy_flow_w=python_port_1_dh + python_center_dh_1,
heat_flow_w=python_heat_flow_1,
reference_temperature_k=reference_temperature_k,
)
python_ref_dtemp_2 = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=port_2.T,
mass_kg=line.state_2.m,
mass_derivative_kg_s=port_2_flow + center_flow,
enthalpy_flow_w=python_port_2_dh + python_center_dh_2,
heat_flow_w=python_heat_flow_2,
reference_temperature_k=reference_temperature_k,
)
def dtemp_component(flow_w: float, mass_kg: float) -> float:
return flow_w / (mass_kg * line.gas.cv)
amesim_port_1_mass_offset_flow_w = (
line.gas.cp * reference_temperature_k - line.gas.cv * amesim_t1
) * amesim_sdm1_kg_s
amesim_port_2_mass_offset_flow_w = (
line.gas.cp * reference_temperature_k - line.gas.cv * amesim_t2
) * amesim_sdm2_kg_s
python_port_1_mass_offset_flow_w = (
line.gas.cp * reference_temperature_k - line.gas.cv * port_1.T
) * (port_1_flow - center_flow)
python_port_2_mass_offset_flow_w = (
line.gas.cp * reference_temperature_k - line.gas.cv * port_2.T
) * (port_2_flow + center_flow)
amesim_port_1_external_dtemp_component = dtemp_component(
amesim_dh1,
amesim_mass_1,
)
amesim_port_1_center_dtemp_component = dtemp_component(
amesim_dhctr,
amesim_mass_1,
)
amesim_port_1_mass_offset_dtemp_component = dtemp_component(
amesim_port_1_mass_offset_flow_w,
amesim_mass_1,
)
amesim_port_1_heat_dtemp_component = dtemp_component(
heat_flow_1,
amesim_mass_1,
)
amesim_port_2_external_dtemp_component = dtemp_component(
amesim_dh2,
amesim_mass_2,
)
amesim_port_2_center_dtemp_component = dtemp_component(
-amesim_dhctr,
amesim_mass_2,
)
amesim_port_2_mass_offset_dtemp_component = dtemp_component(
amesim_port_2_mass_offset_flow_w,
amesim_mass_2,
)
amesim_port_2_heat_dtemp_component = dtemp_component(
heat_flow_2,
amesim_mass_2,
)
python_port_1_external_dtemp_component = dtemp_component(
python_port_1_dh,
line.state_1.m,
)
python_port_1_center_dtemp_component = dtemp_component(
python_center_dh_1,
line.state_1.m,
)
python_port_1_mass_offset_dtemp_component = dtemp_component(
python_port_1_mass_offset_flow_w,
line.state_1.m,
)
python_port_1_heat_dtemp_component = dtemp_component(
python_heat_flow_1,
line.state_1.m,
)
python_port_2_external_dtemp_component = dtemp_component(
python_port_2_dh,
line.state_2.m,
)
python_port_2_center_dtemp_component = dtemp_component(
python_center_dh_2,
line.state_2.m,
)
python_port_2_mass_offset_dtemp_component = dtemp_component(
python_port_2_mass_offset_flow_w,
line.state_2.m,
)
python_port_2_heat_dtemp_component = dtemp_component(
python_heat_flow_2,
line.state_2.m,
)
amesim_center_flow_python_sign_kg_s = (
amesim_center_python_sign_equivalent_g_s * 1.0e-3
)
amesim_external_flow_kg_s = amesim_dm2 * 1.0e-3
python_dtemp_2_amesim_center = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=port_2.T,
mass_kg=line.state_2.m,
mass_derivative_kg_s=port_2_flow + amesim_center_flow_python_sign_kg_s,
enthalpy_flow_w=python_port_2_dh - amesim_dhctr,
heat_flow_w=python_heat_flow_2,
reference_temperature_k=reference_temperature_k,
)
python_dtemp_2_amesim_external = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=port_2.T,
mass_kg=line.state_2.m,
mass_derivative_kg_s=amesim_external_flow_kg_s + center_flow,
enthalpy_flow_w=amesim_dh2 + python_center_dh_2,
heat_flow_w=python_heat_flow_2,
reference_temperature_k=reference_temperature_k,
)
python_dtemp_2_amesim_external_center = _pn2vol_reference_dtemp(
gas=line.gas,
temperature_k=port_2.T,
mass_kg=line.state_2.m,
mass_derivative_kg_s=(
amesim_external_flow_kg_s + amesim_center_flow_python_sign_kg_s
),
enthalpy_flow_w=amesim_dh2 - amesim_dhctr,
heat_flow_w=python_heat_flow_2,
reference_temperature_k=reference_temperature_k,
)
return TestMqlPnl0003EnergyDiagnostic(
line_alias=line_alias,
time_s=time_s,
amesim_port_1_temperature_k=amesim_t1,
amesim_port_2_temperature_k=amesim_t2,
amesim_port_1_pressure_pa=amesim_p1,
amesim_port_2_pressure_pa=amesim_p2,
amesim_port_1_mass_flow_g_s=amesim_dm1,
amesim_port_1_enthalpy_flow_w=amesim_dh1,
amesim_center_mass_flow_g_s=amesim_dmctr,
amesim_center_enthalpy_flow_w=amesim_dhctr,
amesim_port_2_mass_flow_g_s=amesim_dm2,
amesim_port_2_enthalpy_flow_w=amesim_dh2,
amesim_port_1_storage_mass_derivative_g_s=amesim_sdm1_kg_s * 1.0e3,
amesim_port_1_storage_enthalpy_sum_w=amesim_sdh1,
amesim_port_2_storage_mass_derivative_g_s=amesim_sdm2_kg_s * 1.0e3,
amesim_port_2_storage_enthalpy_sum_w=amesim_sdh2,
python_pn3_node_port_1_mass_flow_g_s=python_node_dm1,
python_pn3_node_port_3_mass_flow_g_s=python_node_dm3,
python_pn3_node_to_line_mass_flow_g_s=python_node_to_line_dm,
python_pn3_node_to_line_mass_flow_error_g_s=(
python_node_to_line_dm - amesim_dm1
),
amesim_port_1_mass_estimate_g=amesim_mass_1 * 1.0e3,
amesim_port_2_mass_estimate_g=amesim_mass_2 * 1.0e3,
python_port_1_mass_g=line.state_1.m * 1.0e3,
python_port_2_mass_g=line.state_2.m * 1.0e3,
python_port_1_mass_error_to_amesim_g=(
line.state_1.m * 1.0e3 - amesim_mass_1 * 1.0e3
),
python_port_2_mass_error_to_amesim_g=(
line.state_2.m * 1.0e3 - amesim_mass_2 * 1.0e3
),
amesim_total_mass_derivative_fd_g_s=amesim_total_mass_derivative_fd,
amesim_total_mass_derivative_backward_g_s=(
amesim_total_mass_derivative_backward
),
amesim_total_mass_derivative_forward_g_s=(
amesim_total_mass_derivative_forward
),
amesim_total_storage_mass_derivative_g_s=(
amesim_total_storage_mass_derivative
),
amesim_total_storage_mass_derivative_residual_g_s=(
amesim_total_storage_mass_derivative - amesim_total_mass_derivative_fd
),
amesim_center_flow_python_sign_equivalent_g_s=(
amesim_center_python_sign_equivalent_g_s
),
amesim_center_flow_python_sign_error_g_s=(
python_center_mass_flow_g_s - amesim_center_python_sign_equivalent_g_s
),
python_center_mass_flow_g_s=python_center_mass_flow_g_s,
amesim_port_1_center_mass_derivative_g_s=(
amesim_port_1_center_mass_derivative_g_s
),
amesim_port_2_center_mass_derivative_g_s=(
amesim_port_2_center_mass_derivative_g_s
),
python_port_1_external_mass_flow_g_s=python_port_1_external_mass_flow_g_s,
python_port_1_external_mass_flow_error_g_s=(
python_port_1_external_mass_flow_g_s - amesim_dm1
),
python_port_1_center_mass_derivative_g_s=(
python_port_1_center_mass_derivative_g_s
),
python_port_1_center_mass_derivative_error_g_s=(
python_port_1_center_mass_derivative_g_s
- amesim_port_1_center_mass_derivative_g_s
),
python_port_1_storage_mass_derivative_g_s=(
python_port_1_storage_mass_derivative_g_s
),
python_port_1_storage_mass_derivative_error_g_s=(
python_port_1_storage_mass_derivative_g_s - amesim_sdm1_kg_s * 1.0e3
),
python_port_2_external_mass_flow_g_s=python_port_2_external_mass_flow_g_s,
python_port_2_external_mass_flow_error_g_s=(
python_port_2_external_mass_flow_g_s - amesim_dm2
),
python_port_2_center_mass_derivative_g_s=(
python_port_2_center_mass_derivative_g_s
),
python_port_2_center_mass_derivative_error_g_s=(
python_port_2_center_mass_derivative_g_s
- amesim_port_2_center_mass_derivative_g_s
),
python_port_2_storage_mass_derivative_g_s=(
python_port_2_storage_mass_derivative_g_s
),
python_port_2_storage_mass_derivative_error_g_s=(
python_port_2_storage_mass_derivative_g_s - amesim_sdm2_kg_s * 1.0e3
),
python_amesim_state_darcy_center_mass_flow_g_s=(
amesim_state_darcy_center_flow * 1.0e3
),
python_amesim_state_darcy_center_mass_flow_error_g_s=(
amesim_state_darcy_center_flow * 1.0e3
- amesim_center_python_sign_equivalent_g_s
),
python_amesim_state_pn2pipefr_center_mass_flow_g_s=(
amesim_state_pn2pipefr_center_flow * 1.0e3
),
python_amesim_state_pn2pipefr_center_mass_flow_error_g_s=(
amesim_state_pn2pipefr_center_flow * 1.0e3
- amesim_center_python_sign_equivalent_g_s
),
amesim_center_real_gas_reference_port_1_estimate_w=amesim_center_real_port_1,
amesim_center_real_gas_reference_port_1_error_w=(
amesim_center_real_port_1 - amesim_dhctr
),
python_center_ideal_reference_port_1_estimate_w=python_center_ideal_port_1,
python_center_ideal_reference_port_1_error_w=(
python_center_ideal_port_1 - amesim_dhctr
),
python_center_ideal_reference_port_2_estimate_w=python_center_ideal_port_2,
python_center_ideal_reference_port_2_error_w=(
python_center_ideal_port_2 - amesim_dhctr
),
python_center_real_gas_reference_port_1_estimate_w=python_center_real_port_1,
python_center_real_gas_reference_port_1_error_w=(
python_center_real_port_1 - amesim_dhctr
),
python_center_real_gas_reference_port_2_estimate_w=python_center_real_port_2,
python_center_real_gas_reference_port_2_error_w=(
python_center_real_port_2 - amesim_dhctr
),
python_total_mass_derivative_kg_s=(
current_derivative_1.m + current_derivative_2.m
),
python_port_1_reference_external_enthalpy_flow_w=python_port_1_dh,
python_port_1_reference_center_enthalpy_flow_w=python_center_dh_1,
python_port_1_reference_storage_enthalpy_sum_w=(
python_port_1_dh + python_center_dh_1
),
python_port_1_reference_storage_enthalpy_error_w=(
python_port_1_dh + python_center_dh_1 - amesim_sdh1
),
python_port_2_reference_external_enthalpy_flow_w=python_port_2_dh,
python_port_2_reference_center_enthalpy_flow_w=python_center_dh_2,
python_port_2_reference_storage_enthalpy_sum_w=(
python_port_2_dh + python_center_dh_2
),
python_port_2_reference_storage_enthalpy_error_w=(
python_port_2_dh + python_center_dh_2 - amesim_sdh2
),
amesim_port_1_external_enthalpy_dtemp_component_k_s=(
amesim_port_1_external_dtemp_component
),
amesim_port_1_center_enthalpy_dtemp_component_k_s=(
amesim_port_1_center_dtemp_component
),
amesim_port_1_mass_offset_dtemp_component_k_s=(
amesim_port_1_mass_offset_dtemp_component
),
amesim_port_1_heat_dtemp_component_k_s=amesim_port_1_heat_dtemp_component,
python_port_1_external_enthalpy_dtemp_component_k_s=(
python_port_1_external_dtemp_component
),
python_port_1_center_enthalpy_dtemp_component_k_s=(
python_port_1_center_dtemp_component
),
python_port_1_mass_offset_dtemp_component_k_s=(
python_port_1_mass_offset_dtemp_component
),
python_port_1_heat_dtemp_component_k_s=python_port_1_heat_dtemp_component,
amesim_port_2_external_enthalpy_dtemp_component_k_s=(
amesim_port_2_external_dtemp_component
),
amesim_port_2_center_enthalpy_dtemp_component_k_s=(
amesim_port_2_center_dtemp_component
),
amesim_port_2_mass_offset_dtemp_component_k_s=(
amesim_port_2_mass_offset_dtemp_component
),
amesim_port_2_heat_dtemp_component_k_s=amesim_port_2_heat_dtemp_component,
python_port_2_external_enthalpy_dtemp_component_k_s=(
python_port_2_external_dtemp_component
),
python_port_2_center_enthalpy_dtemp_component_k_s=(
python_port_2_center_dtemp_component
),
python_port_2_mass_offset_dtemp_component_k_s=(
python_port_2_mass_offset_dtemp_component
),
python_port_2_heat_dtemp_component_k_s=python_port_2_heat_dtemp_component,
amesim_port_1_temperature_derivative_fd_k_s=amesim_fd_dtemp_1,
amesim_port_1_temperature_derivative_backward_k_s=amesim_backward_dtemp_1,
amesim_port_1_temperature_derivative_forward_k_s=amesim_forward_dtemp_1,
amesim_port_2_temperature_derivative_fd_k_s=amesim_fd_dtemp_2,
amesim_port_2_temperature_derivative_backward_k_s=amesim_backward_dtemp_2,
amesim_port_2_temperature_derivative_forward_k_s=amesim_forward_dtemp_2,
amesim_port_1_pn2vol_dtemp_k_s=amesim_pn2vol_dtemp_1,
amesim_port_2_pn2vol_dtemp_k_s=amesim_pn2vol_dtemp_2,
amesim_port_1_pn2vol_dtemp_residual_k_s=(
amesim_pn2vol_dtemp_1 - amesim_fd_dtemp_1
),
amesim_port_2_pn2vol_dtemp_residual_k_s=(
amesim_pn2vol_dtemp_2 - amesim_fd_dtemp_2
),
amesim_port_1_pressure_derivative_fd_pa_s=amesim_fd_dpressure_1,
amesim_port_2_pressure_derivative_fd_pa_s=amesim_fd_dpressure_2,
amesim_port_1_pressure_derivative_mass_component_pa_s=(
amesim_dpressure_1_mass
),
amesim_port_1_pressure_derivative_temperature_component_pa_s=(
amesim_dpressure_1_temperature
),
amesim_port_1_pressure_derivative_eos_sum_pa_s=amesim_dpressure_1_sum,
amesim_port_1_pressure_derivative_eos_residual_pa_s=(
amesim_dpressure_1_sum - amesim_fd_dpressure_1
),
amesim_port_2_pressure_derivative_mass_component_pa_s=(
amesim_dpressure_2_mass
),
amesim_port_2_pressure_derivative_temperature_component_pa_s=(
amesim_dpressure_2_temperature
),
amesim_port_2_pressure_derivative_eos_sum_pa_s=amesim_dpressure_2_sum,
amesim_port_2_pressure_derivative_eos_residual_pa_s=(
amesim_dpressure_2_sum - amesim_fd_dpressure_2
),
python_port_1_pressure_derivative_mass_component_pa_s=(
python_dpressure_1_mass
),
python_port_1_pressure_derivative_temperature_component_pa_s=(
python_dpressure_1_temperature
),
python_port_1_pressure_derivative_eos_sum_pa_s=python_dpressure_1_sum,
python_port_2_pressure_derivative_mass_component_pa_s=(
python_dpressure_2_mass
),
python_port_2_pressure_derivative_temperature_component_pa_s=(
python_dpressure_2_temperature
),
python_port_2_pressure_derivative_eos_sum_pa_s=python_dpressure_2_sum,
python_port_1_temperature_k=port_1.T,
python_port_2_temperature_k=port_2.T,
python_port_1_pressure_pa=port_1.p,
python_port_2_pressure_pa=port_2.p,
python_port_1_mass_derivative_kg_s=current_derivative_1.m,
python_port_2_mass_derivative_kg_s=current_derivative_2.m,
python_port_1_current_dtemp_k_s=python_current_dtemp_1,
python_port_2_current_dtemp_k_s=python_current_dtemp_2,
python_port_1_real_gas_reference_dtemp_k_s=python_ref_dtemp_1,
python_port_2_real_gas_reference_dtemp_k_s=python_ref_dtemp_2,
python_port_2_amesim_center_counterfactual_dtemp_k_s=(
python_dtemp_2_amesim_center
),
python_port_2_amesim_external_counterfactual_dtemp_k_s=(
python_dtemp_2_amesim_external
),
python_port_2_amesim_external_center_counterfactual_dtemp_k_s=(
python_dtemp_2_amesim_external_center
),
python_port_2_temperature_error_to_amesim_k=port_2.T - amesim_t2,
)
def _pneumatic_96_inlet_line_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
line_alias: str,
node_alias: str,
orifice_alias: str,
chamber_alias: str,
time_s: float,
) -> TestMqlPneumatic96InletLineDiagnostic:
if line_alias != "pneumatic_96":
raise KeyError(f"Unsupported inlet line diagnostic: {line_alias}")
if node_alias != "pn_node3_8" or orifice_alias != "pn_orifice_18":
raise KeyError(
f"Unsupported inlet path diagnostic: {node_alias}/{orifice_alias}"
)
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
line = closure.pneumatic_closure.components.inlet_line
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_line_pressure = (
amesim_value(f"p2@{line_alias}") + AMESIM_REFERENCE_PRESSURE_PA
)
amesim_line_temperature = amesim_value(f"t2@{line_alias}")
amesim_line_mass = amesim_value(f"mgas@{line_alias}")
amesim_line_mass_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"mgas@{line_alias}"),
time_s=time_s,
)
amesim_line_to_node_flow = amesim_value(f"dm1@{line_alias}")
amesim_orifice_to_line_flow = amesim_value(f"dm2@{orifice_alias}")
amesim_storage_from_ports = (
amesim_orifice_to_line_flow - amesim_line_to_node_flow
)
amesim_chamber_pressure = (
amesim_value(f"press@{chamber_alias}") + AMESIM_REFERENCE_PRESSURE_PA
)
amesim_chamber_temperature = amesim_value(f"temp@{chamber_alias}")
amesim_node_pressure = (
amesim_value(f"press1@{node_alias}") + AMESIM_REFERENCE_PRESSURE_PA
)
amesim_node_temperature = amesim_value(f"temp1@{node_alias}")
python_line_to_node_flow = -snapshot.inlet_node_to_line_flow * 1.0e3
python_orifice_to_line_flow = -snapshot.inlet_line_to_chamber_flow * 1.0e3
python_storage_derivative = (
snapshot.inlet_node_to_line_flow - snapshot.inlet_line_to_chamber_flow
) * 1.0e3
orifice = closure.pneumatic_closure.components.inlet_orifice
def node_to_line_flow_from_states(
*,
node_pressure_pa: float,
node_temperature_k: float,
line_pressure_pa: float,
line_temperature_k: float,
) -> float:
pressure_difference = node_pressure_pa - line_pressure_pa
if pressure_difference == 0.0:
return 0.0
if line.calibrated_linear_conductance is not None:
return (
line.calibrated_linear_conductance
* pressure_difference
/ sqrt(line_temperature_k)
)
upstream_pressure = max(node_pressure_pa, line_pressure_pa)
upstream_temperature = (
node_temperature_k if pressure_difference > 0.0 else line_temperature_k
)
density = line.gas.density(upstream_pressure, upstream_temperature)
magnitude = line._mass_flow_for_pressure_drop(
abs(pressure_difference),
density=density,
temperature=upstream_temperature,
)
return magnitude if pressure_difference > 0.0 else -magnitude
def line_to_node_flow_from_states_g_s(
*,
node_pressure_pa: float,
node_temperature_k: float,
line_pressure_pa: float,
line_temperature_k: float,
) -> float:
return -1.0e3 * node_to_line_flow_from_states(
node_pressure_pa=node_pressure_pa,
node_temperature_k=node_temperature_k,
line_pressure_pa=line_pressure_pa,
line_temperature_k=line_temperature_k,
)
def orifice_to_line_flow_from_states_g_s(
*,
line_pressure_pa: float,
line_temperature_k: float,
chamber_pressure_pa: float,
chamber_temperature_k: float,
) -> float:
upstream_temperature = (
line_temperature_k
if line_pressure_pa >= chamber_pressure_pa
else chamber_temperature_k
)
line_to_chamber = orifice.mass_flow(
line_pressure_pa,
chamber_pressure_pa,
upstream_temperature,
)
return -1.0e3 * line_to_chamber
python_line_to_node_amesim_node_flow = line_to_node_flow_from_states_g_s(
node_pressure_pa=amesim_node_pressure,
node_temperature_k=amesim_node_temperature,
line_pressure_pa=snapshot.inlet_line.p,
line_temperature_k=snapshot.inlet_line.T,
)
python_line_to_node_amesim_line_flow = line_to_node_flow_from_states_g_s(
node_pressure_pa=snapshot.inlet_node.p,
node_temperature_k=snapshot.inlet_node.T,
line_pressure_pa=amesim_line_pressure,
line_temperature_k=amesim_line_temperature,
)
python_line_to_node_amesim_node_line_flow = line_to_node_flow_from_states_g_s(
node_pressure_pa=amesim_node_pressure,
node_temperature_k=amesim_node_temperature,
line_pressure_pa=amesim_line_pressure,
line_temperature_k=amesim_line_temperature,
)
python_orifice_to_line_amesim_line_flow = orifice_to_line_flow_from_states_g_s(
line_pressure_pa=amesim_line_pressure,
line_temperature_k=amesim_line_temperature,
chamber_pressure_pa=snapshot.chamber.p,
chamber_temperature_k=snapshot.chamber.T,
)
python_orifice_to_line_amesim_chamber_flow = orifice_to_line_flow_from_states_g_s(
line_pressure_pa=snapshot.inlet_line.p,
line_temperature_k=snapshot.inlet_line.T,
chamber_pressure_pa=amesim_chamber_pressure,
chamber_temperature_k=amesim_chamber_temperature,
)
python_orifice_to_line_amesim_line_chamber_flow = (
orifice_to_line_flow_from_states_g_s(
line_pressure_pa=amesim_line_pressure,
line_temperature_k=amesim_line_temperature,
chamber_pressure_pa=amesim_chamber_pressure,
chamber_temperature_k=amesim_chamber_temperature,
)
)
python_storage_amesim_boundary_states = (
python_orifice_to_line_amesim_line_chamber_flow
- python_line_to_node_amesim_node_line_flow
)
amesim_line_temperature_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"t2@{line_alias}"),
time_s=time_s,
)
amesim_line_pressure_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"p2@{line_alias}"),
time_s=time_s,
)
amesim_dh1 = amesim_value(f"dh1@{line_alias}")
amesim_dh2 = amesim_value(f"dh2@{orifice_alias}")
amesim_line_mass_kg = amesim_line_mass * 1.0e-3
amesim_storage_kg_s = amesim_storage_from_ports * 1.0e-3
amesim_reference_temperature_k = 298.15
amesim_mass_offset_flow = (
line.gas.cp * amesim_reference_temperature_k
- line.gas.cv * amesim_line_temperature
) * amesim_storage_kg_s
def amesim_dtemp_component(flow_w: float) -> float:
return flow_w / (amesim_line_mass_kg * line.gas.cv)
amesim_port_1_dtemp = amesim_dtemp_component(-amesim_dh1)
amesim_port_2_dtemp = amesim_dtemp_component(amesim_dh2)
amesim_mass_offset_dtemp = amesim_dtemp_component(amesim_mass_offset_flow)
amesim_reference_dtemp_sum = (
amesim_port_1_dtemp + amesim_port_2_dtemp + amesim_mass_offset_dtemp
)
(
amesim_pressure_mass_component,
amesim_pressure_temperature_component,
amesim_pressure_eos_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=amesim_line_mass_kg / line.volume,
temperature_k=amesim_line_temperature,
density_derivative_kg_m3_s=amesim_storage_kg_s / line.volume,
temperature_derivative_k_s=amesim_line_temperature_fd,
)
python_port_1_flow = snapshot.inlet_node_to_line_flow
python_port_2_flow = -snapshot.inlet_line_to_chamber_flow
python_heat_flow = (
line.heat_transfer_coefficient
* line.heat_transfer_area
* (line.external_temperature - snapshot.inlet_line.T)
)
python_port_1_u = (
snapshot.inlet_node.h / line.gas.gamma
if python_port_1_flow > 0.0
else snapshot.inlet_line.u
)
python_port_2_u = (
snapshot.chamber.h / line.gas.gamma
if python_port_2_flow > 0.0
else snapshot.inlet_line.u
)
def python_current_dtemp_component(
*,
mass_flow_kg_s: float,
inlet_u_j_kg: float,
) -> float:
return (
mass_flow_kg_s
* (inlet_u_j_kg - snapshot.inlet_line.u)
/ (line.state.m * line.gas.cv)
)
python_port_1_dtemp = python_current_dtemp_component(
mass_flow_kg_s=python_port_1_flow,
inlet_u_j_kg=python_port_1_u,
)
python_port_2_dtemp = python_current_dtemp_component(
mass_flow_kg_s=python_port_2_flow,
inlet_u_j_kg=python_port_2_u,
)
python_heat_dtemp = python_heat_flow / (line.state.m * line.gas.cv)
python_current_dtemp = (
python_port_1_dtemp + python_port_2_dtemp + python_heat_dtemp
)
python_density_derivative = (
(snapshot.inlet_node_to_line_flow - snapshot.inlet_line_to_chamber_flow)
/ line.volume
)
(
python_pressure_mass_component,
python_pressure_temperature_component,
python_pressure_eos_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=line.state.m / line.volume,
temperature_k=snapshot.inlet_line.T,
density_derivative_kg_m3_s=python_density_derivative,
temperature_derivative_k_s=python_current_dtemp,
)
python_reference_port_1_dh = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=python_port_1_flow,
connected_pressure_pa=snapshot.inlet_node.p,
connected_temperature_k=snapshot.inlet_node.T,
internal_pressure_pa=snapshot.inlet_line.p,
internal_temperature_k=snapshot.inlet_line.T,
reference_temperature_k=amesim_reference_temperature_k,
)
python_reference_port_2_dh = _actual_reference_enthalpy_flow(
gas=line.gas,
port_m_flow=python_port_2_flow,
connected_pressure_pa=snapshot.chamber.p,
connected_temperature_k=snapshot.chamber.T,
internal_pressure_pa=snapshot.inlet_line.p,
internal_temperature_k=snapshot.inlet_line.T,
reference_temperature_k=amesim_reference_temperature_k,
)
python_reference_mass_offset_flow = (
line.gas.cp * amesim_reference_temperature_k
- line.gas.cv * snapshot.inlet_line.T
) * (python_port_1_flow + python_port_2_flow)
def python_reference_dtemp_component(flow_w: float) -> float:
return flow_w / (line.state.m * line.gas.cv)
python_reference_port_1_dtemp = python_reference_dtemp_component(
python_reference_port_1_dh
)
python_reference_port_2_dtemp = python_reference_dtemp_component(
python_reference_port_2_dh
)
python_reference_mass_offset_dtemp = python_reference_dtemp_component(
python_reference_mass_offset_flow
)
python_reference_dtemp = (
python_reference_port_1_dtemp
+ python_reference_port_2_dtemp
+ python_reference_mass_offset_dtemp
+ python_heat_dtemp
)
(
python_reference_pressure_mass_component,
python_reference_pressure_temperature_component,
python_reference_pressure_eos_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=line.state.m / line.volume,
temperature_k=snapshot.inlet_line.T,
density_derivative_kg_m3_s=python_density_derivative,
temperature_derivative_k_s=python_reference_dtemp,
)
python_reference_amesim_storage_mass_offset_flow = (
line.gas.cp * amesim_reference_temperature_k
- line.gas.cv * snapshot.inlet_line.T
) * amesim_storage_kg_s
python_reference_amesim_storage_mass_offset_dtemp = (
python_reference_dtemp_component(
python_reference_amesim_storage_mass_offset_flow
)
)
python_reference_amesim_storage_dtemp = (
python_reference_port_1_dtemp
+ python_reference_port_2_dtemp
+ python_reference_amesim_storage_mass_offset_dtemp
+ python_heat_dtemp
)
use_reference_rhs = bool(
getattr(closure.pneumatic_closure, "use_inlet_line_reference_rhs", False)
)
python_active_dtemp = (
python_reference_dtemp if use_reference_rhs else python_current_dtemp
)
(
python_reference_amesim_storage_pressure_mass_component,
python_reference_amesim_storage_pressure_temperature_component,
python_reference_amesim_storage_pressure_eos_sum,
) = _pressure_derivative_components_from_density_temperature(
gas=line.gas,
density_kg_m3=line.state.m / line.volume,
temperature_k=snapshot.inlet_line.T,
density_derivative_kg_m3_s=amesim_storage_kg_s / line.volume,
temperature_derivative_k_s=python_reference_amesim_storage_dtemp,
)
return TestMqlPneumatic96InletLineDiagnostic(
line_alias=line_alias,
node_alias=node_alias,
orifice_alias=orifice_alias,
chamber_alias=chamber_alias,
time_s=time_s,
amesim_line_pressure_pa=amesim_line_pressure,
amesim_line_temperature_k=amesim_line_temperature,
amesim_line_mass_g=amesim_line_mass,
amesim_line_mass_derivative_fd_g_s=amesim_line_mass_fd,
amesim_line_to_node_mass_flow_g_s=amesim_line_to_node_flow,
amesim_orifice_to_line_mass_flow_g_s=amesim_orifice_to_line_flow,
amesim_storage_mass_derivative_from_ports_g_s=amesim_storage_from_ports,
amesim_storage_mass_derivative_residual_g_s=(
amesim_storage_from_ports - amesim_line_mass_fd
),
amesim_chamber_pressure_pa=amesim_chamber_pressure,
amesim_chamber_temperature_k=amesim_chamber_temperature,
amesim_node_pressure_pa=amesim_node_pressure,
amesim_node_temperature_k=amesim_node_temperature,
amesim_line_temperature_derivative_fd_k_s=amesim_line_temperature_fd,
amesim_line_pressure_derivative_fd_pa_s=amesim_line_pressure_fd,
amesim_port_1_enthalpy_dtemp_component_k_s=amesim_port_1_dtemp,
amesim_port_2_enthalpy_dtemp_component_k_s=amesim_port_2_dtemp,
amesim_mass_offset_dtemp_component_k_s=amesim_mass_offset_dtemp,
amesim_reference_temperature_derivative_sum_k_s=(
amesim_reference_dtemp_sum
),
amesim_reference_temperature_derivative_residual_k_s=(
amesim_reference_dtemp_sum - amesim_line_temperature_fd
),
amesim_pressure_derivative_mass_component_pa_s=(
amesim_pressure_mass_component
),
amesim_pressure_derivative_temperature_component_pa_s=(
amesim_pressure_temperature_component
),
amesim_pressure_derivative_eos_sum_pa_s=amesim_pressure_eos_sum,
amesim_pressure_derivative_eos_residual_pa_s=(
amesim_pressure_eos_sum - amesim_line_pressure_fd
),
python_line_pressure_pa=snapshot.inlet_line.p,
python_line_temperature_k=snapshot.inlet_line.T,
python_line_mass_g=line.state.m * 1.0e3,
python_line_pressure_error_pa=snapshot.inlet_line.p - amesim_line_pressure,
python_line_temperature_error_k=(
snapshot.inlet_line.T - amesim_line_temperature
),
python_line_mass_error_g=line.state.m * 1.0e3 - amesim_line_mass,
python_line_to_node_mass_flow_g_s=python_line_to_node_flow,
python_line_to_node_mass_flow_error_g_s=(
python_line_to_node_flow - amesim_line_to_node_flow
),
python_orifice_to_line_mass_flow_g_s=python_orifice_to_line_flow,
python_orifice_to_line_mass_flow_error_g_s=(
python_orifice_to_line_flow - amesim_orifice_to_line_flow
),
python_storage_mass_derivative_g_s=python_storage_derivative,
python_storage_mass_derivative_error_g_s=(
python_storage_derivative - amesim_storage_from_ports
),
python_line_to_node_amesim_node_mass_flow_g_s=(
python_line_to_node_amesim_node_flow
),
python_line_to_node_amesim_node_mass_flow_error_g_s=(
python_line_to_node_amesim_node_flow - amesim_line_to_node_flow
),
python_line_to_node_amesim_line_mass_flow_g_s=(
python_line_to_node_amesim_line_flow
),
python_line_to_node_amesim_line_mass_flow_error_g_s=(
python_line_to_node_amesim_line_flow - amesim_line_to_node_flow
),
python_line_to_node_amesim_node_line_mass_flow_g_s=(
python_line_to_node_amesim_node_line_flow
),
python_line_to_node_amesim_node_line_mass_flow_error_g_s=(
python_line_to_node_amesim_node_line_flow - amesim_line_to_node_flow
),
python_orifice_to_line_amesim_line_mass_flow_g_s=(
python_orifice_to_line_amesim_line_flow
),
python_orifice_to_line_amesim_line_mass_flow_error_g_s=(
python_orifice_to_line_amesim_line_flow - amesim_orifice_to_line_flow
),
python_orifice_to_line_amesim_chamber_mass_flow_g_s=(
python_orifice_to_line_amesim_chamber_flow
),
python_orifice_to_line_amesim_chamber_mass_flow_error_g_s=(
python_orifice_to_line_amesim_chamber_flow - amesim_orifice_to_line_flow
),
python_orifice_to_line_amesim_line_chamber_mass_flow_g_s=(
python_orifice_to_line_amesim_line_chamber_flow
),
python_orifice_to_line_amesim_line_chamber_mass_flow_error_g_s=(
python_orifice_to_line_amesim_line_chamber_flow
- amesim_orifice_to_line_flow
),
python_storage_amesim_boundary_states_mass_derivative_g_s=(
python_storage_amesim_boundary_states
),
python_storage_amesim_boundary_states_mass_derivative_error_g_s=(
python_storage_amesim_boundary_states - amesim_storage_from_ports
),
python_chamber_pressure_pa=snapshot.chamber.p,
python_chamber_temperature_k=snapshot.chamber.T,
python_current_temperature_derivative_k_s=python_current_dtemp,
python_port_1_current_dtemp_component_k_s=python_port_1_dtemp,
python_port_2_current_dtemp_component_k_s=python_port_2_dtemp,
python_heat_dtemp_component_k_s=python_heat_dtemp,
python_temperature_derivative_error_k_s=(
python_current_dtemp - amesim_line_temperature_fd
),
python_pressure_derivative_mass_component_pa_s=(
python_pressure_mass_component
),
python_pressure_derivative_temperature_component_pa_s=(
python_pressure_temperature_component
),
python_pressure_derivative_eos_sum_pa_s=python_pressure_eos_sum,
python_pressure_derivative_eos_error_pa_s=(
python_pressure_eos_sum - amesim_line_pressure_fd
),
python_reference_port_1_enthalpy_flow_w=python_reference_port_1_dh,
python_reference_port_2_enthalpy_flow_w=python_reference_port_2_dh,
python_reference_port_1_dtemp_component_k_s=(
python_reference_port_1_dtemp
),
python_reference_port_2_dtemp_component_k_s=(
python_reference_port_2_dtemp
),
python_reference_mass_offset_dtemp_component_k_s=(
python_reference_mass_offset_dtemp
),
python_reference_temperature_derivative_k_s=python_reference_dtemp,
python_reference_temperature_derivative_error_k_s=(
python_reference_dtemp - amesim_line_temperature_fd
),
python_reference_pressure_derivative_mass_component_pa_s=(
python_reference_pressure_mass_component
),
python_reference_pressure_derivative_temperature_component_pa_s=(
python_reference_pressure_temperature_component
),
python_reference_pressure_derivative_eos_sum_pa_s=(
python_reference_pressure_eos_sum
),
python_reference_pressure_derivative_eos_error_pa_s=(
python_reference_pressure_eos_sum - amesim_line_pressure_fd
),
python_reference_amesim_storage_mass_offset_dtemp_component_k_s=(
python_reference_amesim_storage_mass_offset_dtemp
),
python_reference_amesim_storage_temperature_derivative_k_s=(
python_reference_amesim_storage_dtemp
),
python_reference_amesim_storage_temperature_derivative_error_k_s=(
python_reference_amesim_storage_dtemp - amesim_line_temperature_fd
),
python_reference_amesim_storage_pressure_derivative_mass_component_pa_s=(
python_reference_amesim_storage_pressure_mass_component
),
python_reference_amesim_storage_pressure_derivative_temperature_component_pa_s=(
python_reference_amesim_storage_pressure_temperature_component
),
python_reference_amesim_storage_pressure_derivative_eos_sum_pa_s=(
python_reference_amesim_storage_pressure_eos_sum
),
python_reference_amesim_storage_pressure_derivative_eos_error_pa_s=(
python_reference_amesim_storage_pressure_eos_sum - amesim_line_pressure_fd
),
python_active_temperature_derivative_k_s=python_active_dtemp,
python_active_temperature_derivative_error_k_s=(
python_active_dtemp - amesim_line_temperature_fd
),
python_active_pressure_derivative_eos_sum_pa_s=(
python_reference_pressure_eos_sum
if use_reference_rhs
else python_pressure_eos_sum
),
python_active_pressure_derivative_eos_error_pa_s=(
(python_reference_pressure_eos_sum if use_reference_rhs else python_pressure_eos_sum)
- amesim_line_pressure_fd
),
python_chamber_pressure_error_pa=(
snapshot.chamber.p - amesim_chamber_pressure
),
python_chamber_temperature_error_k=(
snapshot.chamber.T - amesim_chamber_temperature
),
)
def _pnch012_energy_equation_diagnostic(
*,
closure: object,
amesim_results: AmesimResults,
state_vector: list[float],
rhs_diagnostic: TestMqlVariableChamberRhsDiagnostic,
chamber_alias: str,
piston_alias: str,
line_alias: str,
time_s: float,
) -> TestMqlPnch012EnergyEquationDiagnostic:
if chamber_alias != "pn_c1_8" or piston_alias != "pn_brp2_8":
raise KeyError(f"Unsupported PNCH012 energy diagnostic: {chamber_alias}")
if line_alias != "pneumatic_69":
raise KeyError(f"Unsupported PNCH012 line diagnostic: {line_alias}")
snapshot = closure.snapshot_at(time_s, state_vector).pneumatic
chamber = closure.pneumatic_closure.components.p4_port3_remote_primary_chamber
chamber_properties = snapshot.p4_port3_remote_primary_chamber
reference_temperature_k = 298.15
def amesim_value(data_path: str) -> float:
return interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
time_s,
)
amesim_dh1 = amesim_value(f"dh1@{line_alias}")
amesim_dm1_g_s = amesim_value(f"dm1@{line_alias}")
amesim_chamber_temperature = amesim_value(f"temp@{chamber_alias}")
amesim_chamber_pressure_abs = (
amesim_value(f"press@{chamber_alias}") + AMESIM_REFERENCE_PRESSURE_PA
)
amesim_mgas_kg = amesim_value(f"mgas1@{chamber_alias}") * 1.0e-3
amesim_mass_derivative_fd_g_s = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"mgas1@{chamber_alias}"),
time_s=time_s,
)
amesim_temperature_derivative_fd = _series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"temp@{chamber_alias}"),
time_s=time_s,
)
amesim_volume_rate_fd = (
_series_finite_difference_at(
times=amesim_results.times,
values=amesim_results.series(f"vol@{chamber_alias}"),
time_s=time_s,
)
* 1.0e-6
)
amesim_piston_volume_rate = amesim_value(f"vvol1@{piston_alias}") * (1.0 / 60000.0)
amesim_heat_flow = (
chamber.heat_transfer_coefficient
* chamber.heat_transfer_area
* (chamber.external_temperature - amesim_chamber_temperature)
)
def reference_pn2vol_dtemp(volume_rate_m3_s: float) -> float:
mass_flow_kg_s = amesim_dm1_g_s * 1.0e-3
reference_offset_flow_w = (
chamber.gas.cp * reference_temperature_k
- chamber.gas.cv * amesim_chamber_temperature
) * mass_flow_kg_s
return (
amesim_dh1
+ reference_offset_flow_w
+ amesim_heat_flow
- amesim_chamber_pressure_abs * volume_rate_m3_s
) / (amesim_mgas_kg * chamber.gas.cv)
dtemp_from_fd_volume = reference_pn2vol_dtemp(amesim_volume_rate_fd)
dtemp_from_piston_volume = reference_pn2vol_dtemp(amesim_piston_volume_rate)
python_current_dtemp = (
rhs_diagnostic.energy_derivative_w
- chamber_properties.u * rhs_diagnostic.mass_derivative_kg_s
) / (chamber.state.m * chamber.gas.cv)
return TestMqlPnch012EnergyEquationDiagnostic(
chamber_alias=chamber_alias,
piston_alias=piston_alias,
line_alias=line_alias,
time_s=time_s,
amesim_port_1_enthalpy_flow_w=amesim_dh1,
amesim_port_1_mass_flow_g_s=amesim_dm1_g_s,
amesim_chamber_mass_derivative_fd_g_s=amesim_mass_derivative_fd_g_s,
amesim_chamber_mass_derivative_residual_g_s=(
amesim_dm1_g_s - amesim_mass_derivative_fd_g_s
),
amesim_chamber_temperature_derivative_fd_k_s=amesim_temperature_derivative_fd,
amesim_chamber_volume_rate_fd_m3_s=amesim_volume_rate_fd,
amesim_piston_volume_rate_m3_s=amesim_piston_volume_rate,
amesim_heat_flow_w=amesim_heat_flow,
amesim_boundary_work_fd_volume_w=(
-amesim_chamber_pressure_abs * amesim_volume_rate_fd
),
amesim_pn2vol_reference_dtemp_fd_volume_k_s=dtemp_from_fd_volume,
amesim_pn2vol_reference_dtemp_fd_volume_residual_k_s=(
dtemp_from_fd_volume - amesim_temperature_derivative_fd
),
amesim_pn2vol_reference_dtemp_piston_volume_k_s=dtemp_from_piston_volume,
amesim_pn2vol_reference_dtemp_piston_volume_residual_k_s=(
dtemp_from_piston_volume - amesim_temperature_derivative_fd
),
python_port_a_mass_flow_kg_s=rhs_diagnostic.port_a_mass_flow_kg_s,
python_current_energy_derivative_w=rhs_diagnostic.energy_derivative_w,
python_current_chamber_temperature_derivative_k_s=python_current_dtemp,
reference_temperature_k=reference_temperature_k,
)
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,
*,
orifice_alias: str = "pn_morifice_1",
final_time_s: float = 0.05,
) -> TestMqlPnvoEventWindowDiagnostic:
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
if final_time_s <= event_time_s:
raise ValueError("final_time_s must be greater than the PNVO event time")
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
),
"use_pneumatic_96_reference_rhs": (
config.execution.use_pneumatic_96_reference_rhs
),
"use_pneumatic_69_reference_rhs": (
config.execution.use_pneumatic_69_reference_rhs
),
}
closure = system.full_state_closure_from_spec(spec, **closure_kwargs)
state_vector = closure.initial_state_vector()
solver_template = config.execution.solver
segment_templates = (
SolveIVPConfig(
t_start=solver_template.t_start,
t_stop=nextafter(event_time_s, 0.0),
method=solver_template.method,
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=solver_template.max_step,
),
SolveIVPConfig(
t_start=event_time_s,
t_stop=0.040001,
method="Radau",
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=1.0e-7,
first_step=1.0e-10,
),
SolveIVPConfig(
t_start=0.040001,
t_stop=0.04001,
method="Radau",
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=1.0e-6,
),
SolveIVPConfig(
t_start=0.04001,
t_stop=0.0401,
method="Radau",
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=1.0e-5,
),
SolveIVPConfig(
t_start=0.0401,
t_stop=0.041,
method="Radau",
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=1.0e-5,
),
SolveIVPConfig(
t_start=0.041,
t_stop=0.048,
method="BDF",
rtol=solver_template.rtol,
atol=solver_template.atol,
max_step=1.0e-5,
),
SolveIVPConfig(
t_start=0.048,
t_stop=0.05,
method="BDF",
rtol=1.0e-5,
atol=1.0e-8,
max_step=1.0e-5,
),
)
segments = tuple(
replace(segment, t_stop=min(segment.t_stop, final_time_s))
for segment in segment_templates
if segment.t_start < final_time_s
)
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",
f"xv@{orifice_alias}",
f"dm2@{orifice_alias}",
)
sample_times = tuple(
sorted(
{
sample_time
for sample_time in (
nextafter(event_time_s, 0.0),
0.0401,
0.0402,
0.041,
0.042,
0.045,
0.048,
final_time_s,
)
if sample_time <= final_time_s
}
)
)
state_vector_by_sample_time: dict[float, list[float]] = {}
segment_diagnostics: list[TestMqlPnvoEventWindowSegmentDiagnostic] = []
for segment in segments:
rhs_evaluations = 0
segment_sample_times = tuple(
sample_time
for sample_time in sample_times
if segment.t_start <= sample_time <= segment.t_stop
)
t_eval = tuple(
sorted({segment.t_start, segment.t_stop, *segment_sample_times})
)
def rhs(time_s, values):
nonlocal rhs_evaluations
rhs_evaluations += 1
return closure.rhs_at(time_s, values)
solution = integrate_ode(
rhs=rhs,
initial_state=state_vector,
config=segment,
t_eval=list(t_eval),
)
segment_diagnostics.append(
TestMqlPnvoEventWindowSegmentDiagnostic(
t_start=segment.t_start,
t_stop=segment.t_stop,
method=segment.method,
rtol=segment.rtol,
atol=segment.atol,
max_step=segment.max_step,
rhs_evaluations=rhs_evaluations,
success=bool(solution.success),
message=str(solution.message),
)
)
for sample_time in segment_sample_times:
if sample_time in solution.t:
sample_index = list(solution.t).index(sample_time)
state_vector_by_sample_time[sample_time] = [
row[sample_index] for row in solution.y
]
state_vector = [row[-1] for row in solution.y]
if not solution.success:
break
amesim_results = load_test_mql_amesim_results(
config.paths.resolved_amesim_results_archive_path,
time_stop_s=final_time_s,
)
sample_diagnostics = []
for sample_time in sample_times:
if sample_time not in state_vector_by_sample_time:
continue
python_sample_values = closure.data_path_values(
time_s=sample_time,
state_vector=state_vector_by_sample_time[sample_time],
data_paths=data_paths,
)
amesim_sample_values = {
data_path: interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
sample_time,
)
for data_path in data_paths
}
pnl0001_rhs_diagnostics = (
closure.pnl0001_line_rhs_diagnostic(
line_alias="pneumatic_69",
state_vector=state_vector_by_sample_time[sample_time],
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,
),
)
pnl0001_energy_flow_diagnostics = (
_pnl0001_energy_flow_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
rhs_diagnostic=pnl0001_rhs_diagnostics[0],
line_alias="pneumatic_69",
chamber_alias="pn_c1_8",
node_alias="pnnode4_16",
time_s=sample_time,
),
)
p4_node_enthalpy_breakdown_diagnostics = (
_p4_node_enthalpy_breakdown_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
node_alias="pnnode4_16",
time_s=sample_time,
),
)
pnvo_upstream_enthalpy_diagnostics = (
_pnvo_upstream_enthalpy_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
orifice_alias=orifice_alias,
upstream_line_alias="pneumatic_87",
downstream_node_alias="pnnode4_16",
time_s=sample_time,
),
)
pnl0003_energy_diagnostics = (
_pnl0003_energy_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
line_alias="pneumatic_87",
time_s=sample_time,
),
)
pneumatic_96_inlet_line_diagnostics = (
_pneumatic_96_inlet_line_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
line_alias="pneumatic_96",
node_alias="pn_node3_8",
orifice_alias="pn_orifice_18",
chamber_alias="pn_general_chamber",
time_s=sample_time,
),
)
pnch012_rhs_diagnostics = (
closure.variable_chamber_rhs_diagnostic(
chamber_alias="pn_c1_8",
state_vector=state_vector_by_sample_time[sample_time],
time_s=sample_time,
),
)
pnch012_energy_equation_diagnostics = (
_pnch012_energy_equation_diagnostic(
closure=closure,
amesim_results=amesim_results,
state_vector=state_vector_by_sample_time[sample_time],
rhs_diagnostic=pnch012_rhs_diagnostics[0],
chamber_alias="pn_c1_8",
piston_alias="pn_brp2_8",
line_alias="pneumatic_69",
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(
TestMqlPnvoEventWindowSampleDiagnostic(
time_s=sample_time,
data_paths=data_paths,
python_values_by_data_path=python_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_pressure_loss_diagnostics=pnl0001_pressure_loss_diagnostics,
pnvo_flow_parameter_diagnostics=pnvo_flow_parameter_diagnostics,
pnl0001_energy_flow_diagnostics=pnl0001_energy_flow_diagnostics,
p4_node_enthalpy_breakdown_diagnostics=(
p4_node_enthalpy_breakdown_diagnostics
),
pnvo_upstream_enthalpy_diagnostics=(
pnvo_upstream_enthalpy_diagnostics
),
pnl0003_energy_diagnostics=pnl0003_energy_diagnostics,
pneumatic_96_inlet_line_diagnostics=(
pneumatic_96_inlet_line_diagnostics
),
pnch012_energy_equation_diagnostics=(
pnch012_energy_equation_diagnostics
),
)
)
python_values = closure.data_path_values(
time_s=final_time_s,
state_vector=state_vector,
data_paths=data_paths,
)
amesim_values = {
data_path: interpolate_series_value(
amesim_results.times,
amesim_results.series(data_path),
final_time_s,
)
for data_path in data_paths
}
return TestMqlPnvoEventWindowDiagnostic(
orifice_alias=orifice_alias,
event_time_s=event_time_s,
final_time_s=final_time_s,
data_paths=data_paths,
segment_diagnostics=tuple(segment_diagnostics),
sample_diagnostics=tuple(sample_diagnostics),
python_values_by_data_path=python_values,
amesim_values_by_data_path=amesim_values,
)
def _event_window_config_with_pneumatic_96_reference_rhs(
config: TestMqlFullStateComparisonScriptConfig | None,
*,
enabled: bool,
) -> TestMqlFullStateComparisonScriptConfig:
base = config or TestMqlFullStateComparisonScriptConfig()
return replace(
base,
execution=replace(
base.execution,
use_pneumatic_96_reference_rhs=enabled,
),
)
def _pnvo_event_window_sample_by_time(
diagnostic: TestMqlPnvoEventWindowDiagnostic,
) -> dict[float, TestMqlPnvoEventWindowSampleDiagnostic]:
return {sample.time_s: sample for sample in diagnostic.sample_diagnostics}
def _append_metric_comparison(
comparisons: list[TestMqlPnvoEventWindowCandidateMetricComparison],
*,
time_s: float,
metric_name: str,
default_abs_error: float,
candidate_abs_error: float,
) -> None:
comparisons.append(
TestMqlPnvoEventWindowCandidateMetricComparison(
time_s=time_s,
metric_name=metric_name,
default_abs_error=default_abs_error,
candidate_abs_error=candidate_abs_error,
)
)
def _append_pneumatic_96_candidate_metric_comparisons(
comparisons: list[TestMqlPnvoEventWindowCandidateMetricComparison],
*,
time_s: float,
default_sample: TestMqlPnvoEventWindowSampleDiagnostic,
candidate_sample: TestMqlPnvoEventWindowSampleDiagnostic,
) -> None:
if (
not default_sample.pneumatic_96_inlet_line_diagnostics
or not candidate_sample.pneumatic_96_inlet_line_diagnostics
):
return
default_line = default_sample.pneumatic_96_inlet_line_diagnostics[0]
candidate_line = candidate_sample.pneumatic_96_inlet_line_diagnostics[0]
for metric_name, default_value, candidate_value in (
(
"p96:p2_abs_error_pa",
default_line.python_line_pressure_error_pa,
candidate_line.python_line_pressure_error_pa,
),
(
"p96:t2_abs_error_k",
default_line.python_line_temperature_error_k,
candidate_line.python_line_temperature_error_k,
),
(
"p96:storage_dm_abs_error_g_s",
default_line.python_storage_mass_derivative_error_g_s,
candidate_line.python_storage_mass_derivative_error_g_s,
),
(
"p96:active_dT_abs_error_k_s",
default_line.python_active_temperature_derivative_error_k_s,
candidate_line.python_active_temperature_derivative_error_k_s,
),
(
"p96:active_dP_abs_error_pa_s",
default_line.python_active_pressure_derivative_eos_error_pa_s,
candidate_line.python_active_pressure_derivative_eos_error_pa_s,
),
):
_append_metric_comparison(
comparisons,
time_s=time_s,
metric_name=metric_name,
default_abs_error=abs(default_value),
candidate_abs_error=abs(candidate_value),
)
def _append_pneumatic_87_candidate_metric_comparisons(
comparisons: list[TestMqlPnvoEventWindowCandidateMetricComparison],
*,
time_s: float,
default_sample: TestMqlPnvoEventWindowSampleDiagnostic,
candidate_sample: TestMqlPnvoEventWindowSampleDiagnostic,
) -> None:
if (
not default_sample.pnl0003_energy_diagnostics
or not candidate_sample.pnl0003_energy_diagnostics
):
return
default_line = default_sample.pnl0003_energy_diagnostics[0]
candidate_line = candidate_sample.pnl0003_energy_diagnostics[0]
for metric_name, default_value, candidate_value in (
(
"p87:node_to_line_dm_abs_error_g_s",
default_line.python_pn3_node_to_line_mass_flow_error_g_s,
candidate_line.python_pn3_node_to_line_mass_flow_error_g_s,
),
(
"p87:center_dm_abs_error_g_s",
default_line.amesim_center_flow_python_sign_error_g_s,
candidate_line.amesim_center_flow_python_sign_error_g_s,
),
(
"p87:sdm1_abs_error_g_s",
default_line.python_port_1_storage_mass_derivative_error_g_s,
candidate_line.python_port_1_storage_mass_derivative_error_g_s,
),
(
"p87:sdm2_abs_error_g_s",
default_line.python_port_2_storage_mass_derivative_error_g_s,
candidate_line.python_port_2_storage_mass_derivative_error_g_s,
),
(
"p87:t2_abs_error_k",
default_line.python_port_2_temperature_error_to_amesim_k,
candidate_line.python_port_2_temperature_error_to_amesim_k,
),
):
_append_metric_comparison(
comparisons,
time_s=time_s,
metric_name=metric_name,
default_abs_error=abs(default_value),
candidate_abs_error=abs(candidate_value),
)
def _pnvo_event_window_candidate_metric_comparisons(
default_diagnostic: TestMqlPnvoEventWindowDiagnostic,
candidate_diagnostic: TestMqlPnvoEventWindowDiagnostic,
) -> tuple[TestMqlPnvoEventWindowCandidateMetricComparison, ...]:
candidate_samples = _pnvo_event_window_sample_by_time(candidate_diagnostic)
comparisons: list[TestMqlPnvoEventWindowCandidateMetricComparison] = []
for default_sample in default_diagnostic.sample_diagnostics:
candidate_sample = candidate_samples.get(default_sample.time_s)
if candidate_sample is None:
continue
for data_path in default_sample.data_paths:
_append_metric_comparison(
comparisons,
time_s=default_sample.time_s,
metric_name=f"path:{data_path}",
default_abs_error=default_sample.abs_error(data_path),
candidate_abs_error=candidate_sample.abs_error(data_path),
)
_append_pneumatic_96_candidate_metric_comparisons(
comparisons,
time_s=default_sample.time_s,
default_sample=default_sample,
candidate_sample=candidate_sample,
)
_append_pneumatic_87_candidate_metric_comparisons(
comparisons,
time_s=default_sample.time_s,
default_sample=default_sample,
candidate_sample=candidate_sample,
)
return tuple(comparisons)
def run_test_mql_pnvo_event_window_candidate_comparison(
config: TestMqlFullStateComparisonScriptConfig | None = None,
*,
orifice_alias: str = "pn_morifice_1",
final_time_s: float = 0.041,
) -> TestMqlPnvoEventWindowCandidateComparisonDiagnostic:
default_diagnostic = run_test_mql_pnvo_event_window_diagnostic(
_event_window_config_with_pneumatic_96_reference_rhs(
config,
enabled=False,
),
orifice_alias=orifice_alias,
final_time_s=final_time_s,
)
candidate_diagnostic = run_test_mql_pnvo_event_window_diagnostic(
_event_window_config_with_pneumatic_96_reference_rhs(
config,
enabled=True,
),
orifice_alias=orifice_alias,
final_time_s=final_time_s,
)
return TestMqlPnvoEventWindowCandidateComparisonDiagnostic(
orifice_alias=orifice_alias,
event_time_s=default_diagnostic.event_time_s,
final_time_s=default_diagnostic.final_time_s,
default_diagnostic=default_diagnostic,
candidate_diagnostic=candidate_diagnostic,
metric_comparisons=_pnvo_event_window_candidate_metric_comparisons(
default_diagnostic,
candidate_diagnostic,
),
)
def format_test_mql_pnvo_event_window_summary(
diagnostic: TestMqlPnvoEventWindowDiagnostic,
) -> str:
lines = [
"Model: test_mql",
f"Mode: PNVO event window diagnostic ({diagnostic.orifice_alias})",
f"Event time: {diagnostic.event_time_s}",
f"Final time: {diagnostic.final_time_s}",
"Segments:",
]
for segment in diagnostic.segment_diagnostics:
lines.append(
f" - {segment.t_start} -> {segment.t_stop}: "
f"method={segment.method}, rtol={segment.rtol}, "
f"atol={segment.atol}, max_step={segment.max_step}, "
f"rhs={segment.rhs_evaluations}, success={segment.success}"
)
if diagnostic.sample_diagnostics:
lines.append("Sample comparisons:")
for sample in diagnostic.sample_diagnostics:
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:
lines.append(
f" - {data_path}: "
f"python={sample.python_values_by_data_path[data_path]}, "
f"amesim={sample.amesim_values_by_data_path[data_path]}, "
f"abs_error={sample.abs_error(data_path)}"
)
for rhs in sample.pnl0001_rhs_diagnostics:
lines.append(
f" - rhs@{rhs.line_alias}: "
f"chamber_to_line={rhs.chamber_to_line_flow_kg_s}, "
f"node_to_line={rhs.node_to_line_flow_kg_s}, "
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"candidate_pn2pipefr_dm1="
f"{pressure_loss.candidate_pn2pipefr_dm1_g_s}, "
f"candidate_pn2pipefr_ratio="
f"{pressure_loss.candidate_pn2pipefr_to_amesim_dm1_ratio}, "
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}"
)
for pnvo_enthalpy in sample.pnvo_upstream_enthalpy_diagnostics:
lines.append(
f" - pnvo_upstream_enthalpy@{pnvo_enthalpy.orifice_alias}: "
f"amesim_dh2="
f"{pnvo_enthalpy.amesim_orifice_port_2_enthalpy_flow_w}, "
f"amesim_dm2="
f"{pnvo_enthalpy.amesim_orifice_port_2_mass_flow_g_s}, "
f"amesim_dh3="
f"{pnvo_enthalpy.amesim_orifice_port_3_enthalpy_flow_w}, "
f"amesim_dm3="
f"{pnvo_enthalpy.amesim_orifice_port_3_mass_flow_g_s}, "
f"amesim_line_dhctr="
f"{pnvo_enthalpy.amesim_upstream_line_center_enthalpy_flow_w}, "
f"amesim_line_dmctr="
f"{pnvo_enthalpy.amesim_upstream_line_center_mass_flow_g_s}, "
f"amesim_line_t2="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_temperature_k}, "
f"amesim_line_p2_abs="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_pressure_pa}, "
f"amesim_implied_t2="
f"{pnvo_enthalpy.amesim_orifice_port_2_implied_reference_temperature_k}, "
f"amesim_implied_t2_delta="
f"{pnvo_enthalpy.amesim_orifice_port_2_implied_temperature_delta_to_line_k}, "
f"amesim_ref_dh2="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_reference_enthalpy_flow_w}, "
f"amesim_ref_dh2_error="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_reference_enthalpy_error_w}, "
f"amesim_real_ref_dh2="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_real_gas_reference_enthalpy_flow_w}, "
f"amesim_real_ref_dh2_error="
f"{pnvo_enthalpy.amesim_upstream_line_port_2_real_gas_reference_enthalpy_error_w}, "
f"python_flow="
f"{pnvo_enthalpy.python_orifice_to_node_flow_kg_s}, "
f"python_line_t2="
f"{pnvo_enthalpy.python_upstream_line_port_2_temperature_k}, "
f"python_line_p2_abs="
f"{pnvo_enthalpy.python_upstream_line_port_2_pressure_pa}, "
f"python_current_dh2="
f"{pnvo_enthalpy.python_current_port_2_enthalpy_flow_w}, "
f"python_ref_dh2="
f"{pnvo_enthalpy.python_reference_port_2_enthalpy_flow_w}, "
f"python_real_ref_dh2="
f"{pnvo_enthalpy.python_real_gas_reference_port_2_enthalpy_flow_w}, "
f"python_current_dh2_error="
f"{pnvo_enthalpy.python_current_port_2_enthalpy_error_w}, "
f"python_ref_dh2_error="
f"{pnvo_enthalpy.python_reference_port_2_enthalpy_error_w}, "
f"python_real_ref_dh2_error="
f"{pnvo_enthalpy.python_real_gas_reference_port_2_enthalpy_error_w}"
)
for inlet_line in sample.pneumatic_96_inlet_line_diagnostics:
lines.append(
f" - inlet_line@{inlet_line.line_alias}: "
f"node={inlet_line.node_alias}, "
f"orifice={inlet_line.orifice_alias}, "
f"chamber={inlet_line.chamber_alias}, "
f"amesim_p2_abs={inlet_line.amesim_line_pressure_pa}, "
f"amesim_t2={inlet_line.amesim_line_temperature_k}, "
f"amesim_mgas={inlet_line.amesim_line_mass_g}, "
f"amesim_fd_dmgas={inlet_line.amesim_line_mass_derivative_fd_g_s}, "
f"amesim_line_to_node_dm1={inlet_line.amesim_line_to_node_mass_flow_g_s}, "
f"amesim_orifice_to_line_dm2={inlet_line.amesim_orifice_to_line_mass_flow_g_s}, "
f"amesim_storage_dm_ports={inlet_line.amesim_storage_mass_derivative_from_ports_g_s}, "
f"amesim_storage_dm_residual={inlet_line.amesim_storage_mass_derivative_residual_g_s}, "
f"amesim_chamber_p_abs={inlet_line.amesim_chamber_pressure_pa}, "
f"amesim_chamber_t={inlet_line.amesim_chamber_temperature_k}, "
f"python_p2_abs={inlet_line.python_line_pressure_pa}, "
f"python_t2={inlet_line.python_line_temperature_k}, "
f"python_mgas={inlet_line.python_line_mass_g}, "
f"python_p2_error={inlet_line.python_line_pressure_error_pa}, "
f"python_t2_error={inlet_line.python_line_temperature_error_k}, "
f"python_mgas_error={inlet_line.python_line_mass_error_g}, "
f"python_line_to_node_dm={inlet_line.python_line_to_node_mass_flow_g_s}, "
f"python_line_to_node_dm_error={inlet_line.python_line_to_node_mass_flow_error_g_s}, "
f"python_orifice_to_line_dm={inlet_line.python_orifice_to_line_mass_flow_g_s}, "
f"python_orifice_to_line_dm_error={inlet_line.python_orifice_to_line_mass_flow_error_g_s}, "
f"python_storage_dm={inlet_line.python_storage_mass_derivative_g_s}, "
f"python_storage_dm_error={inlet_line.python_storage_mass_derivative_error_g_s}, "
f"amesim_node_p_abs={inlet_line.amesim_node_pressure_pa}, "
f"amesim_node_t={inlet_line.amesim_node_temperature_k}, "
f"python_line_to_node_amesim_node_dm={inlet_line.python_line_to_node_amesim_node_mass_flow_g_s}, "
f"python_line_to_node_amesim_node_dm_error={inlet_line.python_line_to_node_amesim_node_mass_flow_error_g_s}, "
f"python_line_to_node_amesim_line_dm={inlet_line.python_line_to_node_amesim_line_mass_flow_g_s}, "
f"python_line_to_node_amesim_line_dm_error={inlet_line.python_line_to_node_amesim_line_mass_flow_error_g_s}, "
f"python_line_to_node_amesim_node_line_dm={inlet_line.python_line_to_node_amesim_node_line_mass_flow_g_s}, "
f"python_line_to_node_amesim_node_line_dm_error={inlet_line.python_line_to_node_amesim_node_line_mass_flow_error_g_s}, "
f"python_orifice_to_line_amesim_line_dm={inlet_line.python_orifice_to_line_amesim_line_mass_flow_g_s}, "
f"python_orifice_to_line_amesim_line_dm_error={inlet_line.python_orifice_to_line_amesim_line_mass_flow_error_g_s}, "
f"python_orifice_to_line_amesim_chamber_dm={inlet_line.python_orifice_to_line_amesim_chamber_mass_flow_g_s}, "
f"python_orifice_to_line_amesim_chamber_dm_error={inlet_line.python_orifice_to_line_amesim_chamber_mass_flow_error_g_s}, "
f"python_orifice_to_line_amesim_line_chamber_dm={inlet_line.python_orifice_to_line_amesim_line_chamber_mass_flow_g_s}, "
f"python_orifice_to_line_amesim_line_chamber_dm_error={inlet_line.python_orifice_to_line_amesim_line_chamber_mass_flow_error_g_s}, "
f"python_storage_amesim_boundary_states_dm={inlet_line.python_storage_amesim_boundary_states_mass_derivative_g_s}, "
f"python_storage_amesim_boundary_states_dm_error={inlet_line.python_storage_amesim_boundary_states_mass_derivative_error_g_s}, "
f"python_chamber_p_abs={inlet_line.python_chamber_pressure_pa}, "
f"python_chamber_t={inlet_line.python_chamber_temperature_k}, "
f"amesim_fd_dT={inlet_line.amesim_line_temperature_derivative_fd_k_s}, "
f"amesim_dT_p1_h={inlet_line.amesim_port_1_enthalpy_dtemp_component_k_s}, "
f"amesim_dT_p2_h={inlet_line.amesim_port_2_enthalpy_dtemp_component_k_s}, "
f"amesim_dT_m_offset={inlet_line.amesim_mass_offset_dtemp_component_k_s}, "
f"amesim_ref_dT_sum={inlet_line.amesim_reference_temperature_derivative_sum_k_s}, "
f"amesim_ref_dT_residual={inlet_line.amesim_reference_temperature_derivative_residual_k_s}, "
f"python_current_dT={inlet_line.python_current_temperature_derivative_k_s}, "
f"python_dT_p1={inlet_line.python_port_1_current_dtemp_component_k_s}, "
f"python_dT_p2={inlet_line.python_port_2_current_dtemp_component_k_s}, "
f"python_dT_heat={inlet_line.python_heat_dtemp_component_k_s}, "
f"python_dT_error={inlet_line.python_temperature_derivative_error_k_s}, "
f"amesim_fd_dP={inlet_line.amesim_line_pressure_derivative_fd_pa_s}, "
f"amesim_dP_m={inlet_line.amesim_pressure_derivative_mass_component_pa_s}, "
f"amesim_dP_T={inlet_line.amesim_pressure_derivative_temperature_component_pa_s}, "
f"amesim_dP_sum={inlet_line.amesim_pressure_derivative_eos_sum_pa_s}, "
f"amesim_dP_residual={inlet_line.amesim_pressure_derivative_eos_residual_pa_s}, "
f"python_dP_m={inlet_line.python_pressure_derivative_mass_component_pa_s}, "
f"python_dP_T={inlet_line.python_pressure_derivative_temperature_component_pa_s}, "
f"python_dP_sum={inlet_line.python_pressure_derivative_eos_sum_pa_s}, "
f"python_dP_error={inlet_line.python_pressure_derivative_eos_error_pa_s}, "
f"python_ref_dh1={inlet_line.python_reference_port_1_enthalpy_flow_w}, "
f"python_ref_dh2={inlet_line.python_reference_port_2_enthalpy_flow_w}, "
f"python_ref_dT_p1={inlet_line.python_reference_port_1_dtemp_component_k_s}, "
f"python_ref_dT_p2={inlet_line.python_reference_port_2_dtemp_component_k_s}, "
f"python_ref_dT_m_offset={inlet_line.python_reference_mass_offset_dtemp_component_k_s}, "
f"python_ref_dT={inlet_line.python_reference_temperature_derivative_k_s}, "
f"python_ref_dT_error={inlet_line.python_reference_temperature_derivative_error_k_s}, "
f"python_ref_dP_m={inlet_line.python_reference_pressure_derivative_mass_component_pa_s}, "
f"python_ref_dP_T={inlet_line.python_reference_pressure_derivative_temperature_component_pa_s}, "
f"python_ref_dP_sum={inlet_line.python_reference_pressure_derivative_eos_sum_pa_s}, "
f"python_ref_dP_error={inlet_line.python_reference_pressure_derivative_eos_error_pa_s}, "
f"python_ref_amesim_storage_dT_m_offset={inlet_line.python_reference_amesim_storage_mass_offset_dtemp_component_k_s}, "
f"python_ref_amesim_storage_dT={inlet_line.python_reference_amesim_storage_temperature_derivative_k_s}, "
f"python_ref_amesim_storage_dT_error={inlet_line.python_reference_amesim_storage_temperature_derivative_error_k_s}, "
f"python_ref_amesim_storage_dP_m={inlet_line.python_reference_amesim_storage_pressure_derivative_mass_component_pa_s}, "
f"python_ref_amesim_storage_dP_T={inlet_line.python_reference_amesim_storage_pressure_derivative_temperature_component_pa_s}, "
f"python_ref_amesim_storage_dP_sum={inlet_line.python_reference_amesim_storage_pressure_derivative_eos_sum_pa_s}, "
f"python_ref_amesim_storage_dP_error={inlet_line.python_reference_amesim_storage_pressure_derivative_eos_error_pa_s}, "
f"python_active_dT={inlet_line.python_active_temperature_derivative_k_s}, "
f"python_active_dT_error={inlet_line.python_active_temperature_derivative_error_k_s}, "
f"python_active_dP_sum={inlet_line.python_active_pressure_derivative_eos_sum_pa_s}, "
f"python_active_dP_error={inlet_line.python_active_pressure_derivative_eos_error_pa_s}, "
f"python_chamber_p_error={inlet_line.python_chamber_pressure_error_pa}, "
f"python_chamber_t_error={inlet_line.python_chamber_temperature_error_k}"
)
for pnl0003_energy in sample.pnl0003_energy_diagnostics:
lines.append(
f" - pnl0003_energy@{pnl0003_energy.line_alias}: "
f"amesim_t1={pnl0003_energy.amesim_port_1_temperature_k}, "
f"amesim_t2={pnl0003_energy.amesim_port_2_temperature_k}, "
f"amesim_p1_abs={pnl0003_energy.amesim_port_1_pressure_pa}, "
f"amesim_p2_abs={pnl0003_energy.amesim_port_2_pressure_pa}, "
f"amesim_dm1={pnl0003_energy.amesim_port_1_mass_flow_g_s}, "
f"amesim_dh1={pnl0003_energy.amesim_port_1_enthalpy_flow_w}, "
f"amesim_dmctr={pnl0003_energy.amesim_center_mass_flow_g_s}, "
f"amesim_dhctr={pnl0003_energy.amesim_center_enthalpy_flow_w}, "
f"amesim_dm2={pnl0003_energy.amesim_port_2_mass_flow_g_s}, "
f"amesim_dh2={pnl0003_energy.amesim_port_2_enthalpy_flow_w}, "
f"amesim_sdm1={pnl0003_energy.amesim_port_1_storage_mass_derivative_g_s}, "
f"amesim_sdh1={pnl0003_energy.amesim_port_1_storage_enthalpy_sum_w}, "
f"amesim_sdm2={pnl0003_energy.amesim_port_2_storage_mass_derivative_g_s}, "
f"amesim_sdh2={pnl0003_energy.amesim_port_2_storage_enthalpy_sum_w}, "
f"python_node_dm1={pnl0003_energy.python_pn3_node_port_1_mass_flow_g_s}, "
f"python_node_dm3={pnl0003_energy.python_pn3_node_port_3_mass_flow_g_s}, "
f"python_node_to_line_dm={pnl0003_energy.python_pn3_node_to_line_mass_flow_g_s}, "
f"python_node_to_line_dm_error={pnl0003_energy.python_pn3_node_to_line_mass_flow_error_g_s}, "
f"amesim_m1={pnl0003_energy.amesim_port_1_mass_estimate_g}, "
f"amesim_m2={pnl0003_energy.amesim_port_2_mass_estimate_g}, "
f"python_m1={pnl0003_energy.python_port_1_mass_g}, "
f"python_m2={pnl0003_energy.python_port_2_mass_g}, "
f"python_m1_error={pnl0003_energy.python_port_1_mass_error_to_amesim_g}, "
f"python_m2_error={pnl0003_energy.python_port_2_mass_error_to_amesim_g}, "
f"amesim_fd_dmgas={pnl0003_energy.amesim_total_mass_derivative_fd_g_s}, "
f"amesim_back_dmgas={pnl0003_energy.amesim_total_mass_derivative_backward_g_s}, "
f"amesim_fwd_dmgas={pnl0003_energy.amesim_total_mass_derivative_forward_g_s}, "
f"amesim_storage_dmgas={pnl0003_energy.amesim_total_storage_mass_derivative_g_s}, "
f"amesim_storage_dmgas_residual={pnl0003_energy.amesim_total_storage_mass_derivative_residual_g_s}, "
f"amesim_dmctr_py_sign={pnl0003_energy.amesim_center_flow_python_sign_equivalent_g_s}, "
f"python_center_dm={pnl0003_energy.python_center_mass_flow_g_s}, "
f"python_center_dm_error={pnl0003_energy.amesim_center_flow_python_sign_error_g_s}, "
f"amesim_sdm1_center={pnl0003_energy.amesim_port_1_center_mass_derivative_g_s}, "
f"amesim_sdm2_center={pnl0003_energy.amesim_port_2_center_mass_derivative_g_s}, "
f"python_sdm1_ext={pnl0003_energy.python_port_1_external_mass_flow_g_s}, "
f"python_sdm1_ext_error={pnl0003_energy.python_port_1_external_mass_flow_error_g_s}, "
f"python_sdm1_center={pnl0003_energy.python_port_1_center_mass_derivative_g_s}, "
f"python_sdm1_center_error={pnl0003_energy.python_port_1_center_mass_derivative_error_g_s}, "
f"python_sdm1={pnl0003_energy.python_port_1_storage_mass_derivative_g_s}, "
f"python_sdm1_error={pnl0003_energy.python_port_1_storage_mass_derivative_error_g_s}, "
f"python_sdm2_ext={pnl0003_energy.python_port_2_external_mass_flow_g_s}, "
f"python_sdm2_ext_error={pnl0003_energy.python_port_2_external_mass_flow_error_g_s}, "
f"python_sdm2_center={pnl0003_energy.python_port_2_center_mass_derivative_g_s}, "
f"python_sdm2_center_error={pnl0003_energy.python_port_2_center_mass_derivative_error_g_s}, "
f"python_sdm2={pnl0003_energy.python_port_2_storage_mass_derivative_g_s}, "
f"python_sdm2_error={pnl0003_energy.python_port_2_storage_mass_derivative_error_g_s}, "
f"python_amesim_state_darcy_center_dm="
f"{pnl0003_energy.python_amesim_state_darcy_center_mass_flow_g_s}, "
f"python_amesim_state_darcy_center_dm_error="
f"{pnl0003_energy.python_amesim_state_darcy_center_mass_flow_error_g_s}, "
f"python_amesim_state_pn2pipefr_center_dm="
f"{pnl0003_energy.python_amesim_state_pn2pipefr_center_mass_flow_g_s}, "
f"python_amesim_state_pn2pipefr_center_dm_error="
f"{pnl0003_energy.python_amesim_state_pn2pipefr_center_mass_flow_error_g_s}, "
f"amesim_center_real_p1="
f"{pnl0003_energy.amesim_center_real_gas_reference_port_1_estimate_w}, "
f"amesim_center_real_p1_error="
f"{pnl0003_energy.amesim_center_real_gas_reference_port_1_error_w}, "
f"python_center_ideal_p1="
f"{pnl0003_energy.python_center_ideal_reference_port_1_estimate_w}, "
f"python_center_ideal_p1_error="
f"{pnl0003_energy.python_center_ideal_reference_port_1_error_w}, "
f"python_center_ideal_p2="
f"{pnl0003_energy.python_center_ideal_reference_port_2_estimate_w}, "
f"python_center_ideal_p2_error="
f"{pnl0003_energy.python_center_ideal_reference_port_2_error_w}, "
f"python_center_real_p1="
f"{pnl0003_energy.python_center_real_gas_reference_port_1_estimate_w}, "
f"python_center_real_p1_error="
f"{pnl0003_energy.python_center_real_gas_reference_port_1_error_w}, "
f"python_center_real_p2="
f"{pnl0003_energy.python_center_real_gas_reference_port_2_estimate_w}, "
f"python_center_real_p2_error="
f"{pnl0003_energy.python_center_real_gas_reference_port_2_error_w}, "
f"python_dmgas_dt={pnl0003_energy.python_total_mass_derivative_kg_s}, "
f"python_ref_ext1={pnl0003_energy.python_port_1_reference_external_enthalpy_flow_w}, "
f"python_ref_ctr1={pnl0003_energy.python_port_1_reference_center_enthalpy_flow_w}, "
f"python_ref_sdh1={pnl0003_energy.python_port_1_reference_storage_enthalpy_sum_w}, "
f"python_ref_sdh1_error={pnl0003_energy.python_port_1_reference_storage_enthalpy_error_w}, "
f"python_ref_ext2={pnl0003_energy.python_port_2_reference_external_enthalpy_flow_w}, "
f"python_ref_ctr2={pnl0003_energy.python_port_2_reference_center_enthalpy_flow_w}, "
f"python_ref_sdh2={pnl0003_energy.python_port_2_reference_storage_enthalpy_sum_w}, "
f"python_ref_sdh2_error={pnl0003_energy.python_port_2_reference_storage_enthalpy_error_w}, "
f"amesim_dT1_ext_h={pnl0003_energy.amesim_port_1_external_enthalpy_dtemp_component_k_s}, "
f"amesim_dT1_ctr_h={pnl0003_energy.amesim_port_1_center_enthalpy_dtemp_component_k_s}, "
f"amesim_dT1_m_offset={pnl0003_energy.amesim_port_1_mass_offset_dtemp_component_k_s}, "
f"amesim_dT1_heat={pnl0003_energy.amesim_port_1_heat_dtemp_component_k_s}, "
f"python_dT1_ext_h={pnl0003_energy.python_port_1_external_enthalpy_dtemp_component_k_s}, "
f"python_dT1_ctr_h={pnl0003_energy.python_port_1_center_enthalpy_dtemp_component_k_s}, "
f"python_dT1_m_offset={pnl0003_energy.python_port_1_mass_offset_dtemp_component_k_s}, "
f"python_dT1_heat={pnl0003_energy.python_port_1_heat_dtemp_component_k_s}, "
f"amesim_dT2_ext_h={pnl0003_energy.amesim_port_2_external_enthalpy_dtemp_component_k_s}, "
f"amesim_dT2_ctr_h={pnl0003_energy.amesim_port_2_center_enthalpy_dtemp_component_k_s}, "
f"amesim_dT2_m_offset={pnl0003_energy.amesim_port_2_mass_offset_dtemp_component_k_s}, "
f"amesim_dT2_heat={pnl0003_energy.amesim_port_2_heat_dtemp_component_k_s}, "
f"python_dT2_ext_h={pnl0003_energy.python_port_2_external_enthalpy_dtemp_component_k_s}, "
f"python_dT2_ctr_h={pnl0003_energy.python_port_2_center_enthalpy_dtemp_component_k_s}, "
f"python_dT2_m_offset={pnl0003_energy.python_port_2_mass_offset_dtemp_component_k_s}, "
f"python_dT2_heat={pnl0003_energy.python_port_2_heat_dtemp_component_k_s}, "
f"amesim_fd_dT1={pnl0003_energy.amesim_port_1_temperature_derivative_fd_k_s}, "
f"amesim_back_dT1={pnl0003_energy.amesim_port_1_temperature_derivative_backward_k_s}, "
f"amesim_fwd_dT1={pnl0003_energy.amesim_port_1_temperature_derivative_forward_k_s}, "
f"amesim_fd_dT2={pnl0003_energy.amesim_port_2_temperature_derivative_fd_k_s}, "
f"amesim_back_dT2={pnl0003_energy.amesim_port_2_temperature_derivative_backward_k_s}, "
f"amesim_fwd_dT2={pnl0003_energy.amesim_port_2_temperature_derivative_forward_k_s}, "
f"amesim_pn2vol_dT1={pnl0003_energy.amesim_port_1_pn2vol_dtemp_k_s}, "
f"amesim_pn2vol_dT2={pnl0003_energy.amesim_port_2_pn2vol_dtemp_k_s}, "
f"amesim_pn2vol_dT1_residual={pnl0003_energy.amesim_port_1_pn2vol_dtemp_residual_k_s}, "
f"amesim_pn2vol_dT2_residual={pnl0003_energy.amesim_port_2_pn2vol_dtemp_residual_k_s}, "
f"amesim_fd_dP1={pnl0003_energy.amesim_port_1_pressure_derivative_fd_pa_s}, "
f"amesim_fd_dP2={pnl0003_energy.amesim_port_2_pressure_derivative_fd_pa_s}, "
f"amesim_dP1_m={pnl0003_energy.amesim_port_1_pressure_derivative_mass_component_pa_s}, "
f"amesim_dP1_T={pnl0003_energy.amesim_port_1_pressure_derivative_temperature_component_pa_s}, "
f"amesim_dP1_sum={pnl0003_energy.amesim_port_1_pressure_derivative_eos_sum_pa_s}, "
f"amesim_dP1_residual={pnl0003_energy.amesim_port_1_pressure_derivative_eos_residual_pa_s}, "
f"amesim_dP2_m={pnl0003_energy.amesim_port_2_pressure_derivative_mass_component_pa_s}, "
f"amesim_dP2_T={pnl0003_energy.amesim_port_2_pressure_derivative_temperature_component_pa_s}, "
f"amesim_dP2_sum={pnl0003_energy.amesim_port_2_pressure_derivative_eos_sum_pa_s}, "
f"amesim_dP2_residual={pnl0003_energy.amesim_port_2_pressure_derivative_eos_residual_pa_s}, "
f"python_dP1_m={pnl0003_energy.python_port_1_pressure_derivative_mass_component_pa_s}, "
f"python_dP1_T={pnl0003_energy.python_port_1_pressure_derivative_temperature_component_pa_s}, "
f"python_dP1_sum={pnl0003_energy.python_port_1_pressure_derivative_eos_sum_pa_s}, "
f"python_dP2_m={pnl0003_energy.python_port_2_pressure_derivative_mass_component_pa_s}, "
f"python_dP2_T={pnl0003_energy.python_port_2_pressure_derivative_temperature_component_pa_s}, "
f"python_dP2_sum={pnl0003_energy.python_port_2_pressure_derivative_eos_sum_pa_s}, "
f"python_t1={pnl0003_energy.python_port_1_temperature_k}, "
f"python_t2={pnl0003_energy.python_port_2_temperature_k}, "
f"python_p1_abs={pnl0003_energy.python_port_1_pressure_pa}, "
f"python_p2_abs={pnl0003_energy.python_port_2_pressure_pa}, "
f"python_dm1_dt={pnl0003_energy.python_port_1_mass_derivative_kg_s}, "
f"python_dm2_dt={pnl0003_energy.python_port_2_mass_derivative_kg_s}, "
f"python_current_dT1={pnl0003_energy.python_port_1_current_dtemp_k_s}, "
f"python_current_dT2={pnl0003_energy.python_port_2_current_dtemp_k_s}, "
f"python_real_ref_dT1={pnl0003_energy.python_port_1_real_gas_reference_dtemp_k_s}, "
f"python_real_ref_dT2={pnl0003_energy.python_port_2_real_gas_reference_dtemp_k_s}, "
f"python_amesim_center_dT2="
f"{pnl0003_energy.python_port_2_amesim_center_counterfactual_dtemp_k_s}, "
f"python_amesim_ext_dT2="
f"{pnl0003_energy.python_port_2_amesim_external_counterfactual_dtemp_k_s}, "
f"python_amesim_ext_ctr_dT2="
f"{pnl0003_energy.python_port_2_amesim_external_center_counterfactual_dtemp_k_s}, "
f"python_t2_error={pnl0003_energy.python_port_2_temperature_error_to_amesim_k}"
)
for energy_flow in sample.pnl0001_energy_flow_diagnostics:
lines.append(
f" - energy_flow@{energy_flow.line_alias}: "
f"amesim_dh1={energy_flow.amesim_port_1_enthalpy_flow_w}, "
f"amesim_dm1={energy_flow.amesim_port_1_mass_flow_g_s}, "
f"amesim_node_dh2={energy_flow.amesim_node_port_2_enthalpy_flow_w}, "
f"amesim_node_dm2={energy_flow.amesim_node_port_2_mass_flow_g_s}, "
f"amesim_observed_sdh="
f"{energy_flow.amesim_observed_port_enthalpy_sum_w}, "
f"python_port1_u="
f"{energy_flow.python_port_1_internal_energy_flow_w}, "
f"python_port2_u="
f"{energy_flow.python_port_2_internal_energy_flow_w}, "
f"python_dU_dt={energy_flow.python_current_energy_derivative_w}, "
f"direct_h_dU_dt="
f"{energy_flow.python_direct_enthalpy_energy_derivative_w}, "
f"direct_minus_current="
f"{energy_flow.python_direct_minus_current_energy_derivative_w}, "
f"python_p4_node_dh2="
f"{energy_flow.python_p4_node_port_2_enthalpy_flow_w}, "
f"python_p4_node_dm2="
f"{energy_flow.python_p4_node_port_2_mass_flow_g_s}, "
f"python_p4_node_h2="
f"{energy_flow.python_p4_node_port_2_connected_h_j_kg}, "
f"python_p4_node_t2="
f"{energy_flow.python_p4_node_port_2_connected_temperature_k}, "
f"python_p4_node_h2_delta="
f"{energy_flow.python_p4_node_port_2_connected_h_delta_to_line_h_j_kg}, "
f"python_p4_node_counterfactual_dT="
f"{energy_flow.python_p4_node_port_2_counterfactual_dtemp_k_s}, "
f"href_t={energy_flow.reference_temperature_k}, "
f"ref_dh1_est="
f"{energy_flow.amesim_port_1_reference_enthalpy_estimate_w}, "
f"ref_dh1_error="
f"{energy_flow.amesim_port_1_reference_enthalpy_error_w}, "
f"candidate_dm2i="
f"{energy_flow.amesim_candidate_pn2pipefr_dm2i_g_s}, "
f"candidate_dh2i="
f"{energy_flow.amesim_candidate_pn2pipefr_dh2i_w}, "
f"candidate_storage_sdh="
f"{energy_flow.amesim_candidate_storage_enthalpy_sum_w}, "
f"candidate_pn2pipefr_dT="
f"{energy_flow.amesim_candidate_pn2pipefr_dtemp_k_s}, "
f"candidate_pn2pipefr_dT_residual="
f"{energy_flow.amesim_candidate_pn2pipefr_dtemp_residual_k_s}, "
f"python_ref_dh1="
f"{energy_flow.python_reference_port_1_enthalpy_flow_w}, "
f"python_ref_node_dh2="
f"{energy_flow.python_reference_node_port_2_enthalpy_flow_w}, "
f"python_ref_sdh="
f"{energy_flow.python_reference_observed_port_enthalpy_sum_w}, "
f"python_ref_dh1_error="
f"{energy_flow.python_reference_port_1_to_amesim_error_w}, "
f"python_ref_node_dh2_error="
f"{energy_flow.python_reference_node_port_2_to_amesim_error_w}, "
f"python_ref_sdh_error="
f"{energy_flow.python_reference_sum_to_amesim_error_w}, "
f"python_current_dT="
f"{energy_flow.python_current_line_temperature_derivative_k_s}, "
f"amesim_fd_dT="
f"{energy_flow.amesim_line_temperature_derivative_fd_k_s}, "
f"amesim_pn2vol2_dT_node_plus_dh1="
f"{energy_flow.amesim_pn2vol2_dtemp_node_plus_dh1_k_s}, "
f"amesim_pn2vol2_dT_node_minus_dh1="
f"{energy_flow.amesim_pn2vol2_dtemp_node_minus_dh1_k_s}, "
f"python_ref_pn2vol2_dT_node_minus_dh1="
f"{energy_flow.python_reference_pn2vol2_dtemp_node_minus_dh1_k_s}"
)
for p4_node in sample.p4_node_enthalpy_breakdown_diagnostics:
lines.append(
f" - p4_node_enthalpy@{p4_node.node_alias}: "
f"amesim_p1_dh={p4_node.amesim_port_1_enthalpy_flow_w}, "
f"amesim_p1_dm={p4_node.amesim_port_1_mass_flow_g_s}, "
f"amesim_p3_dh={p4_node.amesim_port_3_enthalpy_flow_w}, "
f"amesim_p3_dm={p4_node.amesim_port_3_mass_flow_g_s}, "
f"amesim_p4_dh={p4_node.amesim_port_4_enthalpy_flow_w}, "
f"amesim_p4_dm={p4_node.amesim_port_4_mass_flow_g_s}, "
f"amesim_p2_dh={p4_node.amesim_port_2_enthalpy_flow_w}, "
f"amesim_p2_dm={p4_node.amesim_port_2_mass_flow_g_s}, "
f"python_p1_dh={p4_node.python_port_1_enthalpy_flow_w}, "
f"python_p1_dm={p4_node.python_port_1_mass_flow_g_s}, "
f"python_p3_dh={p4_node.python_port_3_enthalpy_flow_w}, "
f"python_p3_dm={p4_node.python_port_3_mass_flow_g_s}, "
f"python_p4_dh={p4_node.python_port_4_enthalpy_flow_w}, "
f"python_p4_dm={p4_node.python_port_4_mass_flow_g_s}, "
f"python_p2_dh={p4_node.python_port_2_enthalpy_flow_w}, "
f"python_p2_dm={p4_node.python_port_2_mass_flow_g_s}, "
f"python_ref_p1_dh="
f"{p4_node.python_reference_port_1_enthalpy_flow_w}, "
f"python_ref_p3_dh="
f"{p4_node.python_reference_port_3_enthalpy_flow_w}, "
f"python_ref_p4_dh="
f"{p4_node.python_reference_port_4_enthalpy_flow_w}, "
f"python_ref_p2_dh="
f"{p4_node.python_reference_port_2_enthalpy_flow_w}, "
f"python_real_ref_p1_dh="
f"{p4_node.python_real_gas_reference_port_1_enthalpy_flow_w}, "
f"python_real_ref_p3_dh="
f"{p4_node.python_real_gas_reference_port_3_enthalpy_flow_w}, "
f"python_real_ref_p4_dh="
f"{p4_node.python_real_gas_reference_port_4_enthalpy_flow_w}, "
f"python_real_ref_p2_dh="
f"{p4_node.python_real_gas_reference_port_2_enthalpy_flow_w}, "
f"python_p2_dh_error="
f"{p4_node.python_port_2_enthalpy_error_w}, "
f"python_ref_p2_dh_error="
f"{p4_node.python_reference_port_2_enthalpy_error_w}, "
f"python_real_ref_p2_dh_error="
f"{p4_node.python_real_gas_reference_port_2_enthalpy_error_w}"
)
for chamber_energy in sample.pnch012_energy_equation_diagnostics:
lines.append(
f" - pnch012_energy@{chamber_energy.chamber_alias}: "
f"amesim_dh1={chamber_energy.amesim_port_1_enthalpy_flow_w}, "
f"amesim_dm1={chamber_energy.amesim_port_1_mass_flow_g_s}, "
f"amesim_fd_dmgas="
f"{chamber_energy.amesim_chamber_mass_derivative_fd_g_s}, "
f"amesim_dmgas_residual="
f"{chamber_energy.amesim_chamber_mass_derivative_residual_g_s}, "
f"amesim_fd_dT="
f"{chamber_energy.amesim_chamber_temperature_derivative_fd_k_s}, "
f"amesim_fd_dvol="
f"{chamber_energy.amesim_chamber_volume_rate_fd_m3_s}, "
f"amesim_piston_dvol="
f"{chamber_energy.amesim_piston_volume_rate_m3_s}, "
f"amesim_dq={chamber_energy.amesim_heat_flow_w}, "
f"amesim_boundary_work_fd="
f"{chamber_energy.amesim_boundary_work_fd_volume_w}, "
f"amesim_pn2vol_ref_dT_fdvol="
f"{chamber_energy.amesim_pn2vol_reference_dtemp_fd_volume_k_s}, "
f"amesim_pn2vol_ref_dT_fdvol_residual="
f"{chamber_energy.amesim_pn2vol_reference_dtemp_fd_volume_residual_k_s}, "
f"amesim_pn2vol_ref_dT_pistonvol="
f"{chamber_energy.amesim_pn2vol_reference_dtemp_piston_volume_k_s}, "
f"amesim_pn2vol_ref_dT_pistonvol_residual="
f"{chamber_energy.amesim_pn2vol_reference_dtemp_piston_volume_residual_k_s}, "
f"python_port_a_dm="
f"{chamber_energy.python_port_a_mass_flow_kg_s}, "
f"python_dU_dt="
f"{chamber_energy.python_current_energy_derivative_w}, "
f"python_current_dT="
f"{chamber_energy.python_current_chamber_temperature_derivative_k_s}, "
f"href_t={chamber_energy.reference_temperature_k}"
)
lines.append("Final comparison:")
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_pnvo_event_window_candidate_comparison_summary(
diagnostic: TestMqlPnvoEventWindowCandidateComparisonDiagnostic,
) -> str:
lines = [
"Model: test_mql",
f"Mode: PNVO event window candidate comparison ({diagnostic.orifice_alias})",
f"Event time: {diagnostic.event_time_s}",
f"Final time: {diagnostic.final_time_s}",
"Default RHS: current pneumatic_96 internal-energy RHS",
"Candidate RHS: pneumatic_96 reference-enthalpy/mass-offset RHS",
"Metric comparisons: negative delta means the candidate reduced error",
]
for comparison in diagnostic.metric_comparisons:
lines.append(
f" t={comparison.time_s} - {comparison.metric_name}: "
f"default_abs_error={comparison.default_abs_error}, "
f"candidate_abs_error={comparison.candidate_abs_error}, "
f"delta={comparison.abs_error_delta}"
)
lines.append("Final comparison:")
for data_path in diagnostic.default_diagnostic.data_paths:
lines.append(
f" - {data_path}: "
f"default_abs_error={diagnostic.default_diagnostic.abs_error(data_path)}, "
f"candidate_abs_error={diagnostic.candidate_diagnostic.abs_error(data_path)}, "
f"delta="
f"{diagnostic.candidate_diagnostic.abs_error(data_path) - diagnostic.default_diagnostic.abs_error(data_path)}"
)
return "\n".join(lines) + "\n"
def format_test_mql_full_state_comparison_summary(
run: TestMqlFullStateComparisonRun,
) -> str:
lines = [
"Model: test_mql",
"Mode: Python 132 full-state closure comparison",
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}",
]
largest_metric = run.largest_abs_error_metric
if largest_metric is not None:
lines.append(
"Largest absolute error: "
f"{largest_metric.data_path}={largest_metric.max_abs_error}"
)
largest_diagnostic = run.largest_final_abs_error_diagnostic
if largest_diagnostic is not None:
lines.append(
"Largest final endpoint error: "
f"{largest_diagnostic.data_path}={largest_diagnostic.final_abs_error}"
)
lines.append("Metrics by max absolute error:")
for metric in run.metrics_by_max_abs_error():
lines.append(
f" - {metric.data_path}: max_abs_error={metric.max_abs_error}, "
f"final_abs_error={metric.final_abs_error}"
)
lines.append("Endpoint diagnostics by final absolute error:")
for diagnostic in run.diagnostics_by_final_abs_error():
lines.append(
f" - {diagnostic.data_path}: "
f"initial_python={diagnostic.initial_python_value}, "
f"initial_amesim={diagnostic.initial_amesim_value}, "
f"final_python={diagnostic.final_python_value}, "
f"final_amesim={diagnostic.final_amesim_value}, "
f"final_abs_error={diagnostic.final_abs_error}"
)
flow_diagnostic = _pneumatic_69_flow_diagnostic(run)
if flow_diagnostic is not None:
lines.append(
"PNL0001 canonical mass-flow diagnostic: "
f"{flow_diagnostic.data_path}"
)
lines.extend(
[
" - convention=Python chamber-to-line flow "
"equals -AMESim dm1 * 1e-3",
f" - initial_python_kg_s="
f"{flow_diagnostic.initial_python_canonical_kg_s}",
f" - initial_amesim_kg_s="
f"{flow_diagnostic.initial_amesim_canonical_kg_s}",
f" - final_python_kg_s="
f"{flow_diagnostic.final_python_canonical_kg_s}",
f" - final_amesim_kg_s="
f"{flow_diagnostic.final_amesim_canonical_kg_s}",
f" - final_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)
if chamber_diagnostic is not None:
lines.append(
"Largest endpoint chamber RHS breakdown: "
f"{chamber_diagnostic.chamber_alias}"
)
lines.extend(
[
f" - piston_alias={chamber_diagnostic.piston_alias}",
f" - pressure_pa={chamber_diagnostic.chamber_pressure_pa}",
f" - volume_m3={chamber_diagnostic.chamber_volume_m3}",
f" - volume_rate_m3_s={chamber_diagnostic.chamber_volume_rate_m3_s}",
f" - mass_derivative_kg_s={chamber_diagnostic.mass_derivative_kg_s}",
f" - port_a_energy_flow_w={chamber_diagnostic.port_a_energy_flow_w}",
f" - boundary_work_w={chamber_diagnostic.boundary_work_w}",
f" - thermal_energy_flow_w="
f"{chamber_diagnostic.thermal_energy_flow_w}",
f" - energy_derivative_w={chamber_diagnostic.energy_derivative_w}",
]
)
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()
except KeyError:
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:
return None
_signal, alias = diagnostic.data_path.split("@", 1)
try:
return run.chamber_rhs_diagnostic(alias)
except KeyError:
return 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",
)
parser.add_argument(
"--pnvo-event-window",
action="store_true",
help="run the segmented PNVO opening window through the t=0.05 s save point",
)
parser.add_argument(
"--pnvo-event-window-candidate-comparison",
action="store_true",
help="compare the PNVO event window with and without the pneumatic_96 RHS candidate",
)
parser.add_argument(
"--pnvo-event-window-final-time",
type=float,
help="override PNVO event window final time; candidate comparison defaults to 0.041 s",
)
parser.add_argument(
"--amesim-results-archive",
type=Path,
help="read AMESim observations from a separate .ame results package",
)
parser.add_argument(
"--pneumatic-96-reference-rhs",
action="store_true",
help="enable the local reference/mass-offset RHS candidate for pneumatic_96",
)
parser.add_argument(
"--pneumatic-69-reference-rhs",
action="store_true",
help="enable the local reference/mass-offset RHS candidate for pneumatic_69",
)
args = parser.parse_args()
config = None
if (
args.amesim_results_archive is not None
or args.pneumatic_96_reference_rhs
or args.pneumatic_69_reference_rhs
):
config = TestMqlFullStateComparisonScriptConfig(
paths=TestMqlFullStateComparisonPathConfig(
amesim_results_archive_path=args.amesim_results_archive,
),
execution=TestMqlFullStateComparisonExecutionConfig(
use_pneumatic_96_reference_rhs=args.pneumatic_96_reference_rhs,
use_pneumatic_69_reference_rhs=args.pneumatic_69_reference_rhs,
),
)
if args.pnvo_event_boundary:
diagnostic = run_test_mql_pnvo_event_boundary_diagnostic(config)
print(format_test_mql_pnvo_event_boundary_summary(diagnostic), end="")
return
if args.pnvo_event_window_candidate_comparison:
diagnostic = run_test_mql_pnvo_event_window_candidate_comparison(
config,
final_time_s=args.pnvo_event_window_final_time or 0.041,
)
print(
format_test_mql_pnvo_event_window_candidate_comparison_summary(
diagnostic
),
end="",
)
return
if args.pnvo_event_window:
if args.pnvo_event_window_final_time is None:
diagnostic = run_test_mql_pnvo_event_window_diagnostic(config)
else:
diagnostic = run_test_mql_pnvo_event_window_diagnostic(
config,
final_time_s=args.pnvo_event_window_final_time,
)
print(format_test_mql_pnvo_event_window_summary(diagnostic), end="")
return
run, output_dir = run_test_mql_full_state_comparison(config)
print(format_test_mql_full_state_comparison_summary(run), end="")
print(f"Output directory: {output_dir}")
if __name__ == "__main__":
main()