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 PythonModels.components.amesim_pneumatic import AmesimPneumaticGas from PythonModels.core.solver import SolveIVPConfig, integrate_ode from PythonModels.reporting.amesim_results import AmesimResults, load_test_mql_amesim_results from PythonModels.reporting.test_mql_comparison import ( TestMqlComparisonResult, interpolate_series_value, write_test_mql_comparison_csv, ) from PythonModels.reporting.test_mql_output_schema import ( TestMqlOutputSchema, build_test_mql_output_schema, ) from PythonModels.reporting.test_mql_output_validation import ( TestMqlValidatedOutput, compare_validated_test_mql_output, validate_test_mql_output, ) from PythonModels.systems.test_mql import ( TestMqlPnl0001LineRhsDiagnostic, TestMqlSimulationResult, TestMqlSystem, TestMqlVariableChamberRhsDiagnostic, ) from PythonModels.systems.test_mql_pneumatic import AMESIM_REFERENCE_PRESSURE_PA 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: Path(__file__).resolve().parents[2] / "AmesimModels" / "test_mql.ame" ) 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 @dataclass(frozen=True) class TestMqlFullStateComparisonScriptConfig: paths: TestMqlFullStateComparisonPathConfig = field( default_factory=TestMqlFullStateComparisonPathConfig ) execution: TestMqlFullStateComparisonExecutionConfig = field( default_factory=TestMqlFullStateComparisonExecutionConfig ) def _default_output_dir() -> Path: pythonmodels_root = Path(__file__).resolve().parents[1] timestamp = datetime.now(UTC).strftime("test_mql_full_state_%Y%m%d_%H%M%S_%f") return pythonmodels_root / "runs" / 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 ), 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 ), ) 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 ), } 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 ), } 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", ) args = parser.parse_args() config = None if args.amesim_results_archive is not None or args.pneumatic_96_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, ), ) 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()