merge/model-development-into-main #2

Merged
lujingze merged 126 commits from merge/model-development-into-main into main 2026-07-31 09:52:44 +08:00
8 changed files with 1164 additions and 20 deletions
Showing only changes of commit 331435e78a - Show all commits

No files matched your search

@@ -38,6 +38,38 @@ class AmesimPneumaticGas:
def specific_enthalpy(self, temperature: float) -> float:
return self.cp * temperature
def specific_reference_enthalpy(
self,
temperature: float,
reference_temperature: float = 298.15,
) -> float:
return self.cp * (temperature - reference_temperature)
def reference_temperature_from_specific_enthalpy(
self,
specific_enthalpy: float,
reference_temperature: float = 298.15,
) -> float:
if self.cp <= 0.0:
raise ValueError("cp must be positive.")
return reference_temperature + specific_enthalpy / self.cp
def pressure_reference_enthalpy(
self,
pressure: float,
temperature: float,
reference_pressure: float = 101_300.0,
reference_temperature: float = 298.15,
) -> float:
return (
self.specific_reference_enthalpy(temperature, reference_temperature)
+ self.fluid.residual_specific_enthalpy(pressure, temperature)
- self.fluid.residual_specific_enthalpy(
reference_pressure,
reference_temperature,
)
)
def temperature_from_internal_energy(self, specific_internal_energy: float) -> float:
if self.cv <= 0.0:
raise ValueError("cv must be positive.")
+46 -4
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from math import acos, cos, isfinite, pi, sqrt
from math import acos, cos, isfinite, log, pi, sqrt
UNIVERSAL_GAS_CONSTANT = 8.31446261815324
@@ -10,9 +10,8 @@ UNIVERSAL_GAS_CONSTANT = 8.31446261815324
class PengRobinsonFluid:
"""Pure-fluid Peng-Robinson equation-of-state helper.
The class intentionally covers the equation-of-state layer first: pressure,
compressibility factor, molar volume, and density. Caloric departure
properties are left out until the test_mql energy equations need them.
The class covers the equation-of-state layer plus the enthalpy departure
needed to compare AMESim pneumatic ``pn2hpti`` reference enthalpy flows.
"""
name: str
@@ -50,9 +49,23 @@ class PengRobinsonFluid:
reduced_temperature = temperature / self.critical_temperature
return (1.0 + self.kappa * (1.0 - sqrt(reduced_temperature))) ** 2.0
def alpha_temperature_derivative(self, temperature: float) -> float:
self._validate_temperature(temperature)
reduced_temperature = temperature / self.critical_temperature
sqrt_reduced_temperature = sqrt(reduced_temperature)
alpha_base = 1.0 + self.kappa * (1.0 - sqrt_reduced_temperature)
return -(
alpha_base
* self.kappa
/ (self.critical_temperature * sqrt_reduced_temperature)
)
def attractive_parameter(self, temperature: float) -> float:
return self.a_parameter * self.alpha(temperature)
def attractive_parameter_temperature_derivative(self, temperature: float) -> float:
return self.a_parameter * self.alpha_temperature_derivative(temperature)
def pressure_from_molar_volume(self, temperature: float, molar_volume: float) -> float:
self._validate_temperature(temperature)
if molar_volume <= self.b_parameter:
@@ -121,6 +134,35 @@ class PengRobinsonFluid:
) -> float:
return self.molar_mass / self.molar_volume(pressure, temperature, phase=phase)
def residual_specific_enthalpy(
self,
pressure: float,
temperature: float,
phase: str = "vapor",
) -> float:
"""Return Peng-Robinson enthalpy departure from ideal gas, J/kg."""
self._validate_pressure_temperature(pressure, temperature)
z = self.compressibility_factor(pressure, temperature, phase=phase)
_, B = self.reduced_parameters(pressure, temperature)
b = self.b_parameter
attractive = self.attractive_parameter(temperature)
d_attractive_d_temperature = (
self.attractive_parameter_temperature_derivative(temperature)
)
log_argument = (z + (1.0 + sqrt(2.0)) * B) / (
z + (1.0 - sqrt(2.0)) * B
)
residual_molar_enthalpy = (
UNIVERSAL_GAS_CONSTANT * temperature * (z - 1.0)
+ (
temperature * d_attractive_d_temperature
- attractive
)
* log(log_argument)
/ (2.0 * sqrt(2.0) * b)
)
return residual_molar_enthalpy / self.molar_mass
@staticmethod
def _validate_temperature(temperature: float) -> None:
if temperature <= 0.0:
@@ -6,6 +6,7 @@ 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 (
@@ -353,6 +354,113 @@ class TestMqlPnl0001EnergyFlowDiagnostic:
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
@@ -396,6 +504,15 @@ class TestMqlPnvoEventWindowSampleDiagnostic:
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)
@@ -786,6 +903,51 @@ def _series_finite_difference_at(
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 _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,
@@ -854,18 +1016,10 @@ def _pnl0001_energy_flow_diagnostic(
reference_temperature_k = 298.15
def reference_h(temperature_k: float) -> float:
return line.gas.cp * (temperature_k - reference_temperature_k)
def reference_enthalpy_flow(
*,
flow_kg_s: float,
node_temperature_k: float,
connected_temperature_k: float,
) -> float:
stream_temperature_k = (
node_temperature_k if flow_kg_s >= 0.0 else connected_temperature_k
return line.gas.specific_reference_enthalpy(
temperature_k,
reference_temperature_k,
)
return flow_kg_s * reference_h(stream_temperature_k)
amesim_stream_temperature = (
amesim_value(f"t2@{line_alias}")
@@ -884,20 +1038,29 @@ def _pnl0001_energy_flow_diagnostic(
)
python_reference_node_port_2 = sum(
(
reference_enthalpy_flow(
_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(
_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(
_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,
),
)
)
@@ -1034,6 +1197,545 @@ def _pnl0001_energy_flow_diagnostic(
),
)
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(
@@ -1407,6 +2109,35 @@ def run_test_mql_pnvo_event_window_diagnostic(
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",
@@ -1436,6 +2167,13 @@ def run_test_mql_pnvo_event_window_diagnostic(
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
),
@@ -1535,6 +2273,104 @@ def format_test_mql_pnvo_event_window_summary(
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}: "
@@ -1603,6 +2439,38 @@ def format_test_mql_pnvo_event_window_summary(
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}: "
+6 -1
View File
@@ -16,6 +16,8 @@ AMESIM_REFERENCE_PRESSURE_PA = 101_300.0
BAR_TO_PA = 1.0e5
DEFAULT_TEST_MQL_TEMPERATURE_K = 293.15
DEFAULT_VARIABLE_CHAMBER_PRESSURE_BAR = 1.0
# Matched to PNVO001 event-window cm_ratio near the 0.04 s opening event.
TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER = 0.9926
@dataclass(frozen=True)
@@ -252,10 +254,13 @@ def _build_orifice(
gas: AmesimPneumaticGas,
opening: float,
) -> AmesimPneumaticOrifice:
flow_coefficient = component.parameter_value("cq")
if component.submodel == "PNVO001":
flow_coefficient *= TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER
return AmesimPneumaticOrifice.from_mm2(
name=component.alias,
area_mm2=component.parameter_value(area_parameter),
flow_coefficient=component.parameter_value("cq"),
flow_coefficient=flow_coefficient,
gas=gas,
opening=opening,
)
+35
View File
@@ -4,6 +4,7 @@ import unittest
from PythonModels.components.amesim_pneumatic import (
HELIUM_PNEUMATIC_GAS,
AmesimPneumaticGas,
AmesimPneumaticOrifice,
AmesimPneumaticVolume,
AmesimVariablePneumaticVolume,
@@ -26,6 +27,40 @@ class AmesimPneumaticComponentsTest(unittest.TestCase):
self.assertAlmostEqual(mm2_to_m2(78.5), 78.5e-6)
self.assertAlmostEqual(diameter_mm_to_area_m2(10.0), 7.853981633974483e-5)
def test_reference_enthalpy_uses_amesim_operating_temperature_baseline(self) -> None:
gas = AmesimPneumaticGas(cp=5193.0, cv=3116.0)
self.assertAlmostEqual(gas.specific_reference_enthalpy(298.15), 0.0)
self.assertAlmostEqual(
gas.specific_reference_enthalpy(290.2036),
5193.0 * (290.2036 - 298.15),
)
self.assertAlmostEqual(
gas.reference_temperature_from_specific_enthalpy(
gas.specific_reference_enthalpy(287.7322),
),
287.7322,
)
def test_pressure_reference_enthalpy_includes_real_gas_departure(self) -> None:
gas = AmesimPneumaticGas(cp=5193.0, cv=3116.0)
ideal_reference_h = gas.specific_reference_enthalpy(287.7322)
pressure_reference_h = gas.pressure_reference_enthalpy(13_839_965.0, 287.7322)
self.assertAlmostEqual(ideal_reference_h, -54099.6354, delta=0.001)
self.assertAlmostEqual(
pressure_reference_h - ideal_reference_h,
11936.1,
delta=0.1,
)
def test_reference_temperature_from_enthalpy_rejects_non_positive_cp(self) -> None:
gas = AmesimPneumaticGas(cp=0.0, cv=3116.0)
with self.assertRaisesRegex(ValueError, "cp must be positive"):
gas.reference_temperature_from_specific_enthalpy(42.0)
def test_volume_initial_state_matches_requested_pressure_temperature(self) -> None:
volume = AmesimPneumaticVolume.from_liters(
name="pn_general_chamber",
+14
View File
@@ -25,6 +25,20 @@ class PengRobinsonTest(unittest.TestCase):
delta=pressure * 1.0e-12,
)
def test_helium_residual_enthalpy_is_small_at_atmosphere(self) -> None:
self.assertAlmostEqual(
HELIUM_PR.residual_specific_enthalpy(101_300.0, 298.15),
17.834,
delta=0.01,
)
def test_helium_residual_enthalpy_captures_high_pressure_departure(self) -> None:
self.assertAlmostEqual(
HELIUM_PR.residual_specific_enthalpy(13_839_965.0, 287.7322),
11953.9,
delta=0.1,
)
def test_air_reference_remains_available_for_other_models(self) -> None:
z = AIR_PR.compressibility_factor(101_325.0, 300.0)
density = AIR_PR.density(101_325.0, 300.0)
@@ -12,7 +12,10 @@ from PythonModels.scripts.run_test_mql_full_state_comparison import (
TestMqlFullStateComparisonPathConfig,
TestMqlFullStateComparisonScriptConfig,
TestMqlPnl0001EnergyFlowDiagnostic,
TestMqlP4NodeEnthalpyBreakdownDiagnostic,
TestMqlPnl0003EnergyDiagnostic,
TestMqlPnch012EnergyEquationDiagnostic,
TestMqlPnvoUpstreamEnthalpyDiagnostic,
TestMqlPnl0001PressureLossCalibrationDiagnostic,
TestMqlPnvoEventBoundaryDiagnostic,
TestMqlPnvoEventWindowDiagnostic,
@@ -286,6 +289,86 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
python_to_amesim_cm_ratio=0.93,
),
),
pnvo_upstream_enthalpy_diagnostics=(
TestMqlPnvoUpstreamEnthalpyDiagnostic(
orifice_alias="pn_morifice_1",
upstream_line_alias="pneumatic_87",
time_s=0.05,
amesim_orifice_port_2_enthalpy_flow_w=-18851.7,
amesim_orifice_port_2_mass_flow_g_s=456.8,
amesim_orifice_port_3_enthalpy_flow_w=18851.7,
amesim_orifice_port_3_mass_flow_g_s=-456.8,
amesim_upstream_line_center_enthalpy_flow_w=15823.7,
amesim_upstream_line_center_mass_flow_g_s=-454.2,
amesim_upstream_line_port_2_temperature_k=287.7,
amesim_upstream_line_port_2_pressure_pa=13.871e6,
amesim_orifice_port_2_implied_reference_temperature_k=290.2,
amesim_orifice_port_2_implied_temperature_delta_to_line_k=2.5,
amesim_upstream_line_port_2_reference_enthalpy_flow_w=-24714.8,
amesim_upstream_line_port_2_reference_enthalpy_error_w=-5863.1,
amesim_upstream_line_port_2_real_gas_reference_enthalpy_flow_w=-19240.8,
amesim_upstream_line_port_2_real_gas_reference_enthalpy_error_w=-389.1,
python_orifice_to_node_flow_kg_s=0.4637,
python_upstream_line_port_2_temperature_k=282.2,
python_upstream_line_port_2_pressure_pa=13.84e6,
python_current_port_2_enthalpy_flow_w=679490.8,
python_reference_port_2_enthalpy_flow_w=-38478.5,
python_real_gas_reference_port_2_enthalpy_flow_w=-32998.5,
python_current_port_2_enthalpy_error_w=698342.5,
python_reference_port_2_enthalpy_error_w=-19626.8,
python_real_gas_reference_port_2_enthalpy_error_w=-14146.8,
),
),
pnl0003_energy_diagnostics=(
TestMqlPnl0003EnergyDiagnostic(
line_alias="pneumatic_87",
time_s=0.05,
amesim_port_1_temperature_k=293.1,
amesim_port_2_temperature_k=287.7,
amesim_port_1_pressure_pa=15.2e6,
amesim_port_2_pressure_pa=13.9e6,
amesim_port_1_mass_flow_g_s=-8.5,
amesim_port_1_enthalpy_flow_w=85.0,
amesim_center_mass_flow_g_s=-454.2,
amesim_center_enthalpy_flow_w=15823.7,
amesim_port_2_mass_flow_g_s=-456.8,
amesim_port_2_enthalpy_flow_w=18851.7,
amesim_port_1_storage_mass_derivative_g_s=-462.7,
amesim_port_1_storage_enthalpy_sum_w=15908.7,
amesim_port_2_storage_mass_derivative_g_s=-2.6,
amesim_port_2_storage_enthalpy_sum_w=3028.0,
amesim_total_mass_derivative_fd_g_s=-470.0,
amesim_total_mass_derivative_backward_g_s=-540.0,
amesim_total_mass_derivative_forward_g_s=-400.0,
amesim_total_storage_mass_derivative_g_s=-465.3,
amesim_total_storage_mass_derivative_residual_g_s=4.7,
amesim_center_flow_python_sign_equivalent_g_s=454.2,
amesim_center_flow_python_sign_error_g_s=-0.2,
python_center_mass_flow_g_s=454.0,
python_total_mass_derivative_kg_s=-0.47,
amesim_port_1_temperature_derivative_fd_k_s=-210.0,
amesim_port_1_temperature_derivative_backward_k_s=-420.0,
amesim_port_1_temperature_derivative_forward_k_s=100.0,
amesim_port_2_temperature_derivative_fd_k_s=-540.0,
amesim_port_2_temperature_derivative_backward_k_s=-600.0,
amesim_port_2_temperature_derivative_forward_k_s=120.0,
amesim_port_1_pn2vol_dtemp_k_s=-220.0,
amesim_port_2_pn2vol_dtemp_k_s=-530.0,
amesim_port_1_pn2vol_dtemp_residual_k_s=-10.0,
amesim_port_2_pn2vol_dtemp_residual_k_s=10.0,
python_port_1_temperature_k=293.15,
python_port_2_temperature_k=282.2,
python_port_1_pressure_pa=15.3e6,
python_port_2_pressure_pa=13.8e6,
python_port_1_mass_derivative_kg_s=-0.46,
python_port_2_mass_derivative_kg_s=-0.01,
python_port_1_current_dtemp_k_s=0.0,
python_port_2_current_dtemp_k_s=-1200.0,
python_port_1_real_gas_reference_dtemp_k_s=-100.0,
python_port_2_real_gas_reference_dtemp_k_s=-650.0,
python_port_2_temperature_error_to_amesim_k=-5.5,
),
),
pnl0001_energy_flow_diagnostics=(
TestMqlPnl0001EnergyFlowDiagnostic(
line_alias="pneumatic_69",
@@ -331,6 +414,34 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
python_reference_pn2vol2_dtemp_node_minus_dh1_k_s=-11200.0,
),
),
p4_node_enthalpy_breakdown_diagnostics=(
TestMqlP4NodeEnthalpyBreakdownDiagnostic(
node_alias="pnnode4_16",
time_s=0.05,
amesim_port_1_enthalpy_flow_w=-1000.0,
amesim_port_1_mass_flow_g_s=-30.0,
amesim_port_3_enthalpy_flow_w=-2000.0,
amesim_port_3_mass_flow_g_s=-40.0,
amesim_port_4_enthalpy_flow_w=-15664.2,
amesim_port_4_mass_flow_g_s=508.3,
amesim_port_2_enthalpy_flow_w=-18664.2,
amesim_port_2_mass_flow_g_s=438.3,
python_port_1_enthalpy_flow_w=-900.0,
python_port_1_mass_flow_g_s=-29.0,
python_port_3_enthalpy_flow_w=-2100.0,
python_port_3_mass_flow_g_s=-41.0,
python_port_4_enthalpy_flow_w=643000.0,
python_port_4_mass_flow_g_s=500.0,
python_port_2_enthalpy_flow_w=640000.0,
python_port_2_mass_flow_g_s=430.0,
python_reference_port_1_enthalpy_flow_w=-800.0,
python_reference_port_3_enthalpy_flow_w=-1700.0,
python_reference_port_4_enthalpy_flow_w=-16000.0,
python_reference_port_2_enthalpy_flow_w=-18500.0,
python_port_2_enthalpy_error_w=658664.2,
python_reference_port_2_enthalpy_error_w=164.2,
),
),
pnch012_energy_equation_diagnostics=(
TestMqlPnch012EnergyEquationDiagnostic(
chamber_alias="pn_c1_8",
@@ -403,6 +514,34 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
self.assertIn("python_cm=0.0147", summary)
self.assertIn("cm_ratio=0.93", summary)
self.assertIn("amesim_gasvel=885.9", summary)
self.assertIn("pnvo_upstream_enthalpy@pn_morifice_1", summary)
self.assertIn("amesim_dh2=-18851.7", summary)
self.assertIn("amesim_line_dhctr=15823.7", summary)
self.assertIn("amesim_implied_t2=290.2", summary)
self.assertIn("amesim_implied_t2_delta=2.5", summary)
self.assertIn("amesim_ref_dh2=-24714.8", summary)
self.assertIn("amesim_real_ref_dh2=-19240.8", summary)
self.assertIn("amesim_real_ref_dh2_error=-389.1", summary)
self.assertIn("python_current_dh2=679490.8", summary)
self.assertIn("python_ref_dh2=-38478.5", summary)
self.assertIn("python_real_ref_dh2=-32998.5", summary)
self.assertIn("pnl0003_energy@pneumatic_87", summary)
self.assertIn("amesim_sdm1=-462.7", summary)
self.assertIn("amesim_sdh2=3028.0", summary)
self.assertIn("amesim_fd_dmgas=-470.0", summary)
self.assertIn("amesim_back_dmgas=-540.0", summary)
self.assertIn("amesim_fwd_dmgas=-400.0", summary)
self.assertIn("amesim_storage_dmgas=-465.3", summary)
self.assertIn("amesim_storage_dmgas_residual=4.7", summary)
self.assertIn("amesim_dmctr_py_sign=454.2", summary)
self.assertIn("python_center_dm=454.0", summary)
self.assertIn("python_center_dm_error=-0.2", summary)
self.assertIn("python_dmgas_dt=-0.47", summary)
self.assertIn("amesim_back_dT2=-600.0", summary)
self.assertIn("amesim_fwd_dT2=120.0", summary)
self.assertIn("amesim_pn2vol_dT2=-530.0", summary)
self.assertIn("python_real_ref_dT2=-650.0", summary)
self.assertIn("python_t2_error=-5.5", summary)
self.assertIn("energy_flow@pneumatic_69", summary)
self.assertIn("amesim_dh1=-4248.6", summary)
self.assertIn("amesim_node_dh2=-18664.2", summary)
@@ -427,6 +566,11 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
self.assertIn("amesim_fd_dT=277.3", summary)
self.assertIn("amesim_pn2vol2_dT_node_plus_dh1=-9000.0", summary)
self.assertIn("python_ref_pn2vol2_dT_node_minus_dh1=-11200.0", summary)
self.assertIn("p4_node_enthalpy@pnnode4_16", summary)
self.assertIn("amesim_p4_dh=-15664.2", summary)
self.assertIn("python_p4_dh=643000.0", summary)
self.assertIn("python_ref_p2_dh=-18500.0", summary)
self.assertIn("python_ref_p2_dh_error=164.2", summary)
self.assertIn("pnch012_energy@pn_c1_8", summary)
self.assertIn("amesim_fd_dmgas=370.8", summary)
self.assertIn("amesim_dmgas_residual=49.1", summary)
+5 -1
View File
@@ -6,6 +6,7 @@ from pathlib import Path
from PythonModels.components.amesim_pneumatic import HELIUM_PNEUMATIC_GAS, cm3_to_m3
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql_pneumatic import (
TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
absolute_pressure_from_amesim_bar_parameter,
build_test_mql_pneumatic_assembly,
pressure_from_amesim_bar_parameter,
@@ -71,7 +72,10 @@ class TestMqlPneumaticAssemblyTests(unittest.TestCase):
self.assertAlmostEqual(fixed_orifice.area, 78.5e-6)
self.assertAlmostEqual(fixed_orifice.flow_coefficient, 0.9)
self.assertAlmostEqual(variable_orifice.area, 78.5e-6)
self.assertAlmostEqual(variable_orifice.flow_coefficient, 0.45)
self.assertAlmostEqual(
variable_orifice.flow_coefficient,
0.45 * TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
)
self.assertAlmostEqual(variable_orifice.opening, 0.0)
self.assertEqual(assembly.variable_orifice_control_count, 8)
control = assembly.variable_orifice_controls["pn_morifice_11"]