2737 lines
113 KiB
Python
2737 lines
113 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
from bisect import bisect_left
|
|
from dataclasses import dataclass, field
|
|
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_port_2_enthalpy_error_w: float
|
|
python_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
|
|
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
|
|
python_total_mass_derivative_kg_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
|
|
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_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 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)
|
|
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 TestMqlFullStateComparisonPathConfig:
|
|
archive_path: Path = field(
|
|
default_factory=lambda: Path(__file__).resolve().parents[2]
|
|
/ "AmesimModels"
|
|
/ "test_mql.ame"
|
|
)
|
|
output_dir: Path | None = None
|
|
|
|
|
|
@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
|
|
|
|
|
|
@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.archive_path)
|
|
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
|
|
),
|
|
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
|
|
),
|
|
)
|
|
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
|
|
),
|
|
}
|
|
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.archive_path)
|
|
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 _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 _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_u = (
|
|
p4_node_port_2_connected_h / line.gas.gamma
|
|
if rhs_diagnostic.node_to_line_flow_kg_s > 0.0
|
|
else line_properties.u
|
|
)
|
|
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_u
|
|
+ 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
|
|
|
|
def pn2vol2_reference_dtemp(
|
|
*,
|
|
mass_kg: float,
|
|
temperature_k: float,
|
|
mass_derivative_kg_s: float,
|
|
enthalpy_flow_w: float,
|
|
heat_flow_w: float,
|
|
) -> float:
|
|
storage_reference_h = reference_h(temperature_k)
|
|
return (
|
|
enthalpy_flow_w
|
|
- storage_reference_h * mass_derivative_kg_s
|
|
+ heat_flow_w
|
|
) / (mass_kg * line.gas.cv)
|
|
|
|
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 = pn2vol2_reference_dtemp(
|
|
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 = pn2vol2_reference_dtemp(
|
|
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 = pn2vol2_reference_dtemp(
|
|
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 = pn2vol2_reference_dtemp(
|
|
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
|
|
|
|
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_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
|
|
),
|
|
)
|
|
|
|
|
|
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 _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
|
|
|
|
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")
|
|
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_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,
|
|
)
|
|
)
|
|
|
|
current_derivative_1, current_derivative_2 = line.derivatives_from_connections(
|
|
port_1_m_flow=port_1_flow,
|
|
connected_h_1=port_1.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_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,
|
|
)
|
|
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_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,
|
|
)
|
|
|
|
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,
|
|
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,
|
|
python_total_mass_derivative_kg_s=(
|
|
current_derivative_1.m + current_derivative_2.m
|
|
),
|
|
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
|
|
),
|
|
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_temperature_error_to_amesim_k=port_2.T - amesim_t2,
|
|
)
|
|
|
|
|
|
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",
|
|
) -> 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
|
|
final_time_s = 0.05
|
|
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
|
|
),
|
|
}
|
|
closure = system.full_state_closure_from_spec(spec, **closure_kwargs)
|
|
state_vector = closure.initial_state_vector()
|
|
solver_template = config.execution.solver
|
|
segments = (
|
|
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=final_time_s,
|
|
method="BDF",
|
|
rtol=1.0e-5,
|
|
atol=1.0e-8,
|
|
max_step=1.0e-5,
|
|
),
|
|
)
|
|
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 = (event_time_s, 0.041, 0.042, 0.045, 0.048, 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.archive_path)
|
|
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,
|
|
),
|
|
)
|
|
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,
|
|
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 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 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"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"python_dmgas_dt={pnl0003_energy.python_total_mass_derivative_kg_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"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_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_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}"
|
|
)
|
|
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_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",
|
|
)
|
|
args = parser.parse_args()
|
|
if args.pnvo_event_boundary:
|
|
diagnostic = run_test_mql_pnvo_event_boundary_diagnostic()
|
|
print(format_test_mql_pnvo_event_boundary_summary(diagnostic), end="")
|
|
return
|
|
if args.pnvo_event_window:
|
|
diagnostic = run_test_mql_pnvo_event_window_diagnostic()
|
|
print(format_test_mql_pnvo_event_window_summary(diagnostic), end="")
|
|
return
|
|
|
|
run, output_dir = run_test_mql_full_state_comparison()
|
|
print(format_test_mql_full_state_comparison_summary(run), end="")
|
|
print(f"Output directory: {output_dir}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|