接入PNL0001事件窗口RHS诊断
This commit is contained in:
1 parent
2120901909
commit
f0f8f40c7a
4 files changed
+597
-11
No files matched your search
@@ -6,7 +6,7 @@ from datetime import UTC, datetime
|
|||||||
from math import nextafter
|
from math import nextafter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PythonModels.core.solver import SolveIVPConfig
|
from PythonModels.core.solver import SolveIVPConfig, integrate_ode
|
||||||
from PythonModels.reporting.amesim_results import AmesimResults, load_test_mql_amesim_results
|
from PythonModels.reporting.amesim_results import AmesimResults, load_test_mql_amesim_results
|
||||||
from PythonModels.reporting.test_mql_comparison import (
|
from PythonModels.reporting.test_mql_comparison import (
|
||||||
TestMqlComparisonResult,
|
TestMqlComparisonResult,
|
||||||
@@ -22,7 +22,11 @@ from PythonModels.reporting.test_mql_output_validation import (
|
|||||||
compare_validated_test_mql_output,
|
compare_validated_test_mql_output,
|
||||||
validate_test_mql_output,
|
validate_test_mql_output,
|
||||||
)
|
)
|
||||||
from PythonModels.systems.test_mql import TestMqlSimulationResult, TestMqlSystem
|
from PythonModels.systems.test_mql import (
|
||||||
|
TestMqlPnl0001LineRhsDiagnostic,
|
||||||
|
TestMqlSimulationResult,
|
||||||
|
TestMqlSystem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = (
|
DEFAULT_FULL_STATE_COMPARISON_DATA_PATHS = (
|
||||||
@@ -248,6 +252,54 @@ class TestMqlPnvoEventBoundaryDiagnostic:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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 TestMqlPnvoEventWindowSampleDiagnostic:
|
||||||
|
time_s: float
|
||||||
|
data_paths: tuple[str, ...]
|
||||||
|
python_values_by_data_path: dict[str, float]
|
||||||
|
amesim_values_by_data_path: dict[str, float]
|
||||||
|
pnl0001_rhs_diagnostics: tuple[TestMqlPnl0001LineRhsDiagnostic, ...] = field(
|
||||||
|
default_factory=tuple
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
@dataclass(frozen=True)
|
||||||
class TestMqlFullStateComparisonPathConfig:
|
class TestMqlFullStateComparisonPathConfig:
|
||||||
archive_path: Path = field(
|
archive_path: Path = field(
|
||||||
@@ -457,6 +509,257 @@ def format_test_mql_pnvo_event_boundary_summary(
|
|||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
pnl0001_rhs_diagnostics=pnl0001_rhs_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:
|
||||||
|
lines.append(f" t={sample.time_s}")
|
||||||
|
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}"
|
||||||
|
)
|
||||||
|
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(
|
def format_test_mql_full_state_comparison_summary(
|
||||||
run: TestMqlFullStateComparisonRun,
|
run: TestMqlFullStateComparisonRun,
|
||||||
) -> str:
|
) -> str:
|
||||||
@@ -609,11 +912,20 @@ def main() -> None:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="compare the STEP0 left-state/right-opening boundary at t=0.04 s",
|
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()
|
args = parser.parse_args()
|
||||||
if args.pnvo_event_boundary:
|
if args.pnvo_event_boundary:
|
||||||
diagnostic = run_test_mql_pnvo_event_boundary_diagnostic()
|
diagnostic = run_test_mql_pnvo_event_boundary_diagnostic()
|
||||||
print(format_test_mql_pnvo_event_boundary_summary(diagnostic), end="")
|
print(format_test_mql_pnvo_event_boundary_summary(diagnostic), end="")
|
||||||
return
|
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()
|
run, output_dir = run_test_mql_full_state_comparison()
|
||||||
print(format_test_mql_full_state_comparison_summary(run), end="")
|
print(format_test_mql_full_state_comparison_summary(run), end="")
|
||||||
|
|||||||
@@ -3874,6 +3874,23 @@ class TestMqlVariableChamberRhsDiagnostic:
|
|||||||
energy_derivative_w: float
|
energy_derivative_w: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TestMqlPnl0001LineRhsDiagnostic:
|
||||||
|
line_alias: str
|
||||||
|
chamber_alias: str
|
||||||
|
line_pressure_pa: float
|
||||||
|
line_temperature_k: float
|
||||||
|
chamber_pressure_pa: float
|
||||||
|
chamber_temperature_k: float
|
||||||
|
chamber_to_line_flow_kg_s: float
|
||||||
|
node_to_line_flow_kg_s: float
|
||||||
|
mass_derivative_kg_s: float
|
||||||
|
port_1_energy_flow_w: float
|
||||||
|
port_2_energy_flow_w: float
|
||||||
|
thermal_energy_flow_w: float
|
||||||
|
energy_derivative_w: float
|
||||||
|
|
||||||
|
|
||||||
_PISTON_FORCE_BINDINGS = (
|
_PISTON_FORCE_BINDINGS = (
|
||||||
("mass_friction_endstops_10", "pn_brp2_8", "p4_port3_remote_primary_chamber"),
|
("mass_friction_endstops_10", "pn_brp2_8", "p4_port3_remote_primary_chamber"),
|
||||||
("mass_friction_endstops_11", "pn_brp2_9", "p4_primary_chamber"),
|
("mass_friction_endstops_11", "pn_brp2_9", "p4_primary_chamber"),
|
||||||
@@ -3977,8 +3994,13 @@ _PRIMARY_CHAMBER_DIAGNOSTIC_FIELDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS = {
|
_PNL0001_LINE_DIAGNOSTIC_FIELDS = {
|
||||||
"pneumatic_69": "p4_port3_remote_chamber_to_line_flow",
|
"pneumatic_69": (
|
||||||
|
"p4_port3_remote_primary_line",
|
||||||
|
"p4_port3_remote_primary_chamber",
|
||||||
|
"p4_port3_remote_chamber_to_line_flow",
|
||||||
|
"p4_port3_remote_node_to_primary_line_flow",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
_VARIABLE_ORIFICE_MASS_FLOW_DIAGNOSTIC_FIELDS = {
|
_VARIABLE_ORIFICE_MASS_FLOW_DIAGNOSTIC_FIELDS = {
|
||||||
@@ -4146,6 +4168,65 @@ class TestMqlFullStateClosure:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def pnl0001_line_rhs_diagnostic(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
line_alias: str,
|
||||||
|
state_vector: list[float],
|
||||||
|
time_s: float = 0.0,
|
||||||
|
) -> TestMqlPnl0001LineRhsDiagnostic:
|
||||||
|
if line_alias not in _PNL0001_LINE_DIAGNOSTIC_FIELDS:
|
||||||
|
raise KeyError(line_alias)
|
||||||
|
(
|
||||||
|
line_field,
|
||||||
|
chamber_field,
|
||||||
|
chamber_flow_field,
|
||||||
|
node_flow_field,
|
||||||
|
) = _PNL0001_LINE_DIAGNOSTIC_FIELDS[line_alias]
|
||||||
|
chamber_alias = _PNCH012_ALIAS_BY_SNAPSHOT_FIELD[chamber_field]
|
||||||
|
snapshot = self.snapshot_at(time_s, state_vector)
|
||||||
|
line = getattr(self.pneumatic_closure.components, line_field)
|
||||||
|
line_properties = getattr(snapshot.pneumatic, line_field)
|
||||||
|
chamber_properties = getattr(snapshot.pneumatic, chamber_field)
|
||||||
|
chamber_to_line_flow = getattr(snapshot.pneumatic, chamber_flow_field)
|
||||||
|
node_to_line_flow = getattr(snapshot.pneumatic, node_flow_field)
|
||||||
|
line_derivative = line.derivatives_from_connections(
|
||||||
|
port_1_m_flow=chamber_to_line_flow,
|
||||||
|
connected_h_1=chamber_properties.h,
|
||||||
|
port_2_m_flow=node_to_line_flow,
|
||||||
|
connected_h_2=line_properties.h,
|
||||||
|
)
|
||||||
|
port_1_inlet_u = (
|
||||||
|
chamber_properties.h / line.gas.gamma
|
||||||
|
if chamber_to_line_flow > 0.0
|
||||||
|
else line_properties.u
|
||||||
|
)
|
||||||
|
port_2_inlet_u = (
|
||||||
|
line_properties.h / line.gas.gamma
|
||||||
|
if node_to_line_flow > 0.0
|
||||||
|
else line_properties.u
|
||||||
|
)
|
||||||
|
thermal_energy_flow = (
|
||||||
|
line.heat_transfer_coefficient
|
||||||
|
* line.heat_transfer_area
|
||||||
|
* (line.external_temperature - line_properties.T)
|
||||||
|
)
|
||||||
|
return TestMqlPnl0001LineRhsDiagnostic(
|
||||||
|
line_alias=line_alias,
|
||||||
|
chamber_alias=chamber_alias,
|
||||||
|
line_pressure_pa=line_properties.p,
|
||||||
|
line_temperature_k=line_properties.T,
|
||||||
|
chamber_pressure_pa=chamber_properties.p,
|
||||||
|
chamber_temperature_k=chamber_properties.T,
|
||||||
|
chamber_to_line_flow_kg_s=chamber_to_line_flow,
|
||||||
|
node_to_line_flow_kg_s=node_to_line_flow,
|
||||||
|
mass_derivative_kg_s=line_derivative.m,
|
||||||
|
port_1_energy_flow_w=chamber_to_line_flow * port_1_inlet_u,
|
||||||
|
port_2_energy_flow_w=node_to_line_flow * port_2_inlet_u,
|
||||||
|
thermal_energy_flow_w=thermal_energy_flow,
|
||||||
|
energy_derivative_w=line_derivative.U,
|
||||||
|
)
|
||||||
|
|
||||||
def data_path_values(
|
def data_path_values(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -4173,14 +4254,43 @@ class TestMqlFullStateClosure:
|
|||||||
values_by_data_path = {}
|
values_by_data_path = {}
|
||||||
for data_path in selected_paths:
|
for data_path in selected_paths:
|
||||||
signal, alias = self._split_data_path(data_path)
|
signal, alias = self._split_data_path(data_path)
|
||||||
if alias in _PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS:
|
if alias in _PNL0001_LINE_DIAGNOSTIC_FIELDS:
|
||||||
if signal != "dm1":
|
(
|
||||||
raise KeyError(data_path)
|
line_field,
|
||||||
canonical_flow_kg_s = getattr(
|
_chamber_field,
|
||||||
snapshot.pneumatic,
|
chamber_flow_field,
|
||||||
_PNL0001_MASS_FLOW_DIAGNOSTIC_FIELDS[alias],
|
_node_flow_field,
|
||||||
)
|
) = _PNL0001_LINE_DIAGNOSTIC_FIELDS[alias]
|
||||||
|
if signal == "dm1":
|
||||||
|
canonical_flow_kg_s = getattr(snapshot.pneumatic, chamber_flow_field)
|
||||||
values_by_data_path[data_path] = -canonical_flow_kg_s * 1.0e3
|
values_by_data_path[data_path] = -canonical_flow_kg_s * 1.0e3
|
||||||
|
elif signal == "p2":
|
||||||
|
line_properties = getattr(snapshot.pneumatic, line_field)
|
||||||
|
values_by_data_path[data_path] = pressure_to_amesim_gauge_pa(
|
||||||
|
line_properties.p
|
||||||
|
)
|
||||||
|
elif signal == "t2":
|
||||||
|
line_properties = getattr(snapshot.pneumatic, line_field)
|
||||||
|
values_by_data_path[data_path] = line_properties.T
|
||||||
|
elif signal == "mgas":
|
||||||
|
line = getattr(self.pneumatic_closure.components, line_field)
|
||||||
|
values_by_data_path[data_path] = line.gas_mass_g()
|
||||||
|
elif signal in {"re", "v", "ff"}:
|
||||||
|
line = getattr(self.pneumatic_closure.components, line_field)
|
||||||
|
line_properties = getattr(snapshot.pneumatic, line_field)
|
||||||
|
canonical_flow_kg_s = getattr(snapshot.pneumatic, chamber_flow_field)
|
||||||
|
diagnostics = line.diagnostics(
|
||||||
|
mass_flow_kg_s=-canonical_flow_kg_s,
|
||||||
|
temperature_k=line_properties.T,
|
||||||
|
)
|
||||||
|
if signal == "re":
|
||||||
|
values_by_data_path[data_path] = diagnostics.reynolds_number
|
||||||
|
elif signal == "v":
|
||||||
|
values_by_data_path[data_path] = diagnostics.gas_velocity_m_s
|
||||||
|
else:
|
||||||
|
values_by_data_path[data_path] = diagnostics.friction_factor
|
||||||
|
else:
|
||||||
|
raise KeyError(data_path)
|
||||||
elif alias in self.pneumatic_assembly.variable_orifices:
|
elif alias in self.pneumatic_assembly.variable_orifices:
|
||||||
orifice = self.pneumatic_assembly.variable_orifices[alias]
|
orifice = self.pneumatic_assembly.variable_orifices[alias]
|
||||||
if signal == "xv":
|
if signal == "xv":
|
||||||
@@ -4207,6 +4317,8 @@ class TestMqlFullStateClosure:
|
|||||||
values_by_data_path[data_path] = chamber_properties.T
|
values_by_data_path[data_path] = chamber_properties.T
|
||||||
elif signal == "vol":
|
elif signal == "vol":
|
||||||
values_by_data_path[data_path] = chamber.volume_cm3()
|
values_by_data_path[data_path] = chamber.volume_cm3()
|
||||||
|
elif signal in {"mgas", "mgas1"}:
|
||||||
|
values_by_data_path[data_path] = chamber.gas_mass_g()
|
||||||
else:
|
else:
|
||||||
raise KeyError(data_path)
|
raise KeyError(data_path)
|
||||||
elif alias in self.mechanical_closure.assembly.pistons:
|
elif alias in self.mechanical_closure.assembly.pistons:
|
||||||
|
|||||||
@@ -5,13 +5,19 @@ import unittest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PythonModels.core.solver import SolveIVPConfig
|
from PythonModels.core.solver import SolveIVPConfig
|
||||||
|
from PythonModels.systems.test_mql import TestMqlPnl0001LineRhsDiagnostic
|
||||||
|
|
||||||
from PythonModels.scripts.run_test_mql_full_state_comparison import (
|
from PythonModels.scripts.run_test_mql_full_state_comparison import (
|
||||||
TestMqlFullStateComparisonExecutionConfig,
|
TestMqlFullStateComparisonExecutionConfig,
|
||||||
TestMqlFullStateComparisonPathConfig,
|
TestMqlFullStateComparisonPathConfig,
|
||||||
TestMqlFullStateComparisonScriptConfig,
|
TestMqlFullStateComparisonScriptConfig,
|
||||||
TestMqlPnvoEventBoundaryDiagnostic,
|
TestMqlPnvoEventBoundaryDiagnostic,
|
||||||
|
TestMqlPnvoEventWindowDiagnostic,
|
||||||
|
TestMqlPnvoEventWindowSampleDiagnostic,
|
||||||
|
TestMqlPnvoEventWindowSegmentDiagnostic,
|
||||||
format_test_mql_full_state_comparison_summary,
|
format_test_mql_full_state_comparison_summary,
|
||||||
format_test_mql_pnvo_event_boundary_summary,
|
format_test_mql_pnvo_event_boundary_summary,
|
||||||
|
format_test_mql_pnvo_event_window_summary,
|
||||||
run_test_mql_full_state_comparison,
|
run_test_mql_full_state_comparison,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -169,6 +175,109 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
|
|||||||
self.assertIn("Observation side: right-continuous STEP0 opening", summary)
|
self.assertIn("Observation side: right-continuous STEP0 opening", summary)
|
||||||
self.assertTrue(summary.endswith("\n"))
|
self.assertTrue(summary.endswith("\n"))
|
||||||
|
|
||||||
|
def test_formats_pnvo_event_window_diagnostic_with_samples(self) -> None:
|
||||||
|
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",
|
||||||
|
"xv@pn_morifice_1",
|
||||||
|
"dm2@pn_morifice_1",
|
||||||
|
)
|
||||||
|
sample = TestMqlPnvoEventWindowSampleDiagnostic(
|
||||||
|
time_s=0.05,
|
||||||
|
data_paths=data_paths,
|
||||||
|
python_values_by_data_path={
|
||||||
|
"press@pn_c1_8": 219127.9,
|
||||||
|
"temp@pn_c1_8": 372.3,
|
||||||
|
"vol@pn_c1_8": 15000.0,
|
||||||
|
"mgas1@pn_c1_8": 5.46,
|
||||||
|
"p2@pneumatic_69": 93250.0,
|
||||||
|
"t2@pneumatic_69": 293.15,
|
||||||
|
"mgas@pneumatic_69": 0.53,
|
||||||
|
"re@pneumatic_69": 1.41e6,
|
||||||
|
"v@pneumatic_69": 832.0,
|
||||||
|
"ff@pneumatic_69": 0.0243,
|
||||||
|
"dm1@pneumatic_69": 443.2,
|
||||||
|
"xv@pn_morifice_1": 1.0,
|
||||||
|
"dm2@pn_morifice_1": 463.7,
|
||||||
|
},
|
||||||
|
amesim_values_by_data_path={
|
||||||
|
"press@pn_c1_8": 187060.0,
|
||||||
|
"temp@pn_c1_8": 296.1,
|
||||||
|
"vol@pn_c1_8": 15000.0,
|
||||||
|
"mgas1@pn_c1_8": 5.47,
|
||||||
|
"p2@pneumatic_69": 1484689.6,
|
||||||
|
"t2@pneumatic_69": 296.1,
|
||||||
|
"mgas@pneumatic_69": 0.81,
|
||||||
|
"re@pneumatic_69": 1.35e6,
|
||||||
|
"v@pneumatic_69": 519.8,
|
||||||
|
"ff@pneumatic_69": 0.0241,
|
||||||
|
"dm1@pneumatic_69": 419.9,
|
||||||
|
"xv@pn_morifice_1": 1.0,
|
||||||
|
"dm2@pn_morifice_1": 456.8,
|
||||||
|
},
|
||||||
|
pnl0001_rhs_diagnostics=(
|
||||||
|
TestMqlPnl0001LineRhsDiagnostic(
|
||||||
|
line_alias="pneumatic_69",
|
||||||
|
chamber_alias="pn_c1_8",
|
||||||
|
line_pressure_pa=1034250.0,
|
||||||
|
line_temperature_k=293.15,
|
||||||
|
chamber_pressure_pa=320427.9,
|
||||||
|
chamber_temperature_k=379.9,
|
||||||
|
chamber_to_line_flow_kg_s=-0.4432,
|
||||||
|
node_to_line_flow_kg_s=0.4210,
|
||||||
|
mass_derivative_kg_s=-0.0222,
|
||||||
|
port_1_energy_flow_w=-404000.0,
|
||||||
|
port_2_energy_flow_w=384000.0,
|
||||||
|
thermal_energy_flow_w=0.0,
|
||||||
|
energy_derivative_w=-20000.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
diagnostic = TestMqlPnvoEventWindowDiagnostic(
|
||||||
|
orifice_alias="pn_morifice_1",
|
||||||
|
event_time_s=0.04,
|
||||||
|
final_time_s=0.05,
|
||||||
|
data_paths=data_paths,
|
||||||
|
segment_diagnostics=(
|
||||||
|
TestMqlPnvoEventWindowSegmentDiagnostic(
|
||||||
|
t_start=0.048,
|
||||||
|
t_stop=0.05,
|
||||||
|
method="BDF",
|
||||||
|
rtol=1.0e-5,
|
||||||
|
atol=1.0e-8,
|
||||||
|
max_step=1.0e-5,
|
||||||
|
rhs_evaluations=8154,
|
||||||
|
success=True,
|
||||||
|
message="success",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sample_diagnostics=(sample,),
|
||||||
|
python_values_by_data_path=sample.python_values_by_data_path,
|
||||||
|
amesim_values_by_data_path=sample.amesim_values_by_data_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = format_test_mql_pnvo_event_window_summary(diagnostic)
|
||||||
|
|
||||||
|
self.assertAlmostEqual(diagnostic.abs_error("dm2@pn_morifice_1"), 6.9)
|
||||||
|
self.assertAlmostEqual(sample.abs_error("press@pn_c1_8"), 32067.9)
|
||||||
|
self.assertIn("PNVO event window diagnostic (pn_morifice_1)", summary)
|
||||||
|
self.assertIn("Sample comparisons:", summary)
|
||||||
|
self.assertIn("t=0.05", summary)
|
||||||
|
self.assertIn("rhs=8154", summary)
|
||||||
|
self.assertIn("rhs@pneumatic_69", summary)
|
||||||
|
self.assertIn("dm_dt=-0.0222", summary)
|
||||||
|
self.assertIn("Final comparison:", summary)
|
||||||
|
self.assertTrue(summary.endswith("\n"))
|
||||||
|
|
||||||
def test_full_state_script_can_skip_artifact_files(self) -> None:
|
def test_full_state_script_can_skip_artifact_files(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
output_dir = Path(tmpdir)
|
output_dir = Path(tmpdir)
|
||||||
|
|||||||
@@ -713,6 +713,37 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
|
|||||||
+ diagnostic.thermal_energy_flow_w,
|
+ diagnostic.thermal_energy_flow_w,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_full_state_closure_diagnoses_pnl0001_line_rhs_terms(self) -> None:
|
||||||
|
closure = self.system.full_state_closure_from_spec(
|
||||||
|
self.spec,
|
||||||
|
inlet_node_pressure_pa=15.31e6,
|
||||||
|
resistance_boundary_pressure_pa=15.29e6,
|
||||||
|
)
|
||||||
|
state = closure.initial_state_vector()
|
||||||
|
|
||||||
|
diagnostic = closure.pnl0001_line_rhs_diagnostic(
|
||||||
|
line_alias="pneumatic_69",
|
||||||
|
state_vector=state,
|
||||||
|
time_s=0.04,
|
||||||
|
)
|
||||||
|
rhs = closure.rhs_at(0.04, state)
|
||||||
|
|
||||||
|
self.assertEqual(diagnostic.chamber_alias, "pn_c1_8")
|
||||||
|
self.assertGreater(diagnostic.node_to_line_flow_kg_s, 0.0)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
diagnostic.mass_derivative_kg_s,
|
||||||
|
diagnostic.chamber_to_line_flow_kg_s
|
||||||
|
+ diagnostic.node_to_line_flow_kg_s,
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(diagnostic.mass_derivative_kg_s, rhs[44])
|
||||||
|
self.assertAlmostEqual(diagnostic.energy_derivative_w, rhs[45])
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
diagnostic.energy_derivative_w,
|
||||||
|
diagnostic.port_1_energy_flow_w
|
||||||
|
+ diagnostic.port_2_energy_flow_w
|
||||||
|
+ diagnostic.thermal_energy_flow_w,
|
||||||
|
)
|
||||||
|
|
||||||
def test_full_state_closure_applies_step_controls_to_variable_orifices(self) -> None:
|
def test_full_state_closure_applies_step_controls_to_variable_orifices(self) -> None:
|
||||||
closure = self.system.full_state_closure_from_spec(
|
closure = self.system.full_state_closure_from_spec(
|
||||||
self.spec,
|
self.spec,
|
||||||
@@ -771,6 +802,7 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
|
|||||||
data_paths = (
|
data_paths = (
|
||||||
"press@pn_c1_8",
|
"press@pn_c1_8",
|
||||||
"vol@pn_c1_8",
|
"vol@pn_c1_8",
|
||||||
|
"mgas1@pn_c1_8",
|
||||||
"vol1@pn_brp2_8",
|
"vol1@pn_brp2_8",
|
||||||
"vvol1@pn_brp2_8",
|
"vvol1@pn_brp2_8",
|
||||||
"x1@mass_friction_endstops_10",
|
"x1@mass_friction_endstops_10",
|
||||||
@@ -779,6 +811,12 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
|
|||||||
"x1@mass_friction_endstops_18",
|
"x1@mass_friction_endstops_18",
|
||||||
"v1@mass_friction_endstops_18",
|
"v1@mass_friction_endstops_18",
|
||||||
"acc1@mass_friction_endstops_18",
|
"acc1@mass_friction_endstops_18",
|
||||||
|
"p2@pneumatic_69",
|
||||||
|
"t2@pneumatic_69",
|
||||||
|
"mgas@pneumatic_69",
|
||||||
|
"re@pneumatic_69",
|
||||||
|
"v@pneumatic_69",
|
||||||
|
"ff@pneumatic_69",
|
||||||
"dm1@pneumatic_69",
|
"dm1@pneumatic_69",
|
||||||
"xv@pn_morifice_1",
|
"xv@pn_morifice_1",
|
||||||
"dm2@pn_morifice_1",
|
"dm2@pn_morifice_1",
|
||||||
@@ -804,6 +842,11 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(comparison.metrics), len(data_paths))
|
self.assertEqual(len(comparison.metrics), len(data_paths))
|
||||||
self.assertAlmostEqual(result.series["press@pn_c1_8"][0], -1300.0)
|
self.assertAlmostEqual(result.series["press@pn_c1_8"][0], -1300.0)
|
||||||
self.assertAlmostEqual(result.series["vol@pn_c1_8"][0], 15000.0)
|
self.assertAlmostEqual(result.series["vol@pn_c1_8"][0], 15000.0)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
result.series["mgas1@pn_c1_8"][0],
|
||||||
|
self.amesim_results.series("mgas1@pn_c1_8")[0],
|
||||||
|
delta=1.0e-4,
|
||||||
|
)
|
||||||
self.assertAlmostEqual(result.series["vol1@pn_brp2_8"][0], 0.0)
|
self.assertAlmostEqual(result.series["vol1@pn_brp2_8"][0], 0.0)
|
||||||
self.assertAlmostEqual(result.series["vvol1@pn_brp2_8"][0], 0.0)
|
self.assertAlmostEqual(result.series["vvol1@pn_brp2_8"][0], 0.0)
|
||||||
self.assertAlmostEqual(result.series["x1@mass_friction_endstops_10"][0], 0.0)
|
self.assertAlmostEqual(result.series["x1@mass_friction_endstops_10"][0], 0.0)
|
||||||
@@ -813,6 +856,16 @@ class TestMqlPn3P4NodeChamberSegmentTests(unittest.TestCase):
|
|||||||
-0.8167936695810917,
|
-0.8167936695810917,
|
||||||
)
|
)
|
||||||
self.assertAlmostEqual(result.series["acc1@mass_friction_endstops_18"][0], 0.0)
|
self.assertAlmostEqual(result.series["acc1@mass_friction_endstops_18"][0], 0.0)
|
||||||
|
self.assertAlmostEqual(result.series["p2@pneumatic_69"][0], -1300.0)
|
||||||
|
self.assertAlmostEqual(result.series["t2@pneumatic_69"][0], 293.15)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
result.series["mgas@pneumatic_69"][0],
|
||||||
|
self.amesim_results.series("mgas@pneumatic_69")[0],
|
||||||
|
delta=1.0e-5,
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(result.series["re@pneumatic_69"][0], 0.0)
|
||||||
|
self.assertAlmostEqual(result.series["v@pneumatic_69"][0], 0.0)
|
||||||
|
self.assertGreater(result.series["ff@pneumatic_69"][0], 0.0)
|
||||||
self.assertAlmostEqual(result.series["dm1@pneumatic_69"][0], 0.0)
|
self.assertAlmostEqual(result.series["dm1@pneumatic_69"][0], 0.0)
|
||||||
self.assertAlmostEqual(result.series["xv@pn_morifice_1"][0], 0.0)
|
self.assertAlmostEqual(result.series["xv@pn_morifice_1"][0], 0.0)
|
||||||
self.assertAlmostEqual(result.series["dm2@pn_morifice_1"][0], 0.0)
|
self.assertAlmostEqual(result.series["dm2@pn_morifice_1"][0], 0.0)
|
||||||
|
|||||||
Reference in new issue
Block a user