验收四路模型并优化拓扑求解性能
This commit is contained in:
1 parent
caca32a513
commit
456c29b3b6
20 files changed
+1085
-137
No files matched your search
@@ -0,0 +1,263 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Mapping
|
||||
|
||||
from app.simulation.components.amesim.flow.pipes import AmesimPnl0002
|
||||
from app.simulation.reporting.amesim_results import (
|
||||
AmesimResults,
|
||||
load_test_mql_amesim_results,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Pnl0002ReplayPaths:
|
||||
"""AMESim data paths needed to replay one PNL0002 resistance."""
|
||||
|
||||
center_pressure: str
|
||||
center_temperature: str
|
||||
port_1_pressure: str
|
||||
port_1_temperature: str
|
||||
port_1_mass_flow: str
|
||||
port_2_pressure: str
|
||||
port_2_temperature: str
|
||||
port_2_mass_flow: str
|
||||
reynolds: str
|
||||
friction_factor: str
|
||||
|
||||
|
||||
PNL83_REPLAY_PATHS = Pnl0002ReplayPaths(
|
||||
center_pressure="pctr@pneumatic_83",
|
||||
center_temperature="tctr@pneumatic_83",
|
||||
port_1_pressure="press1@pnnode4_16",
|
||||
port_1_temperature="temp1@pnnode4_16",
|
||||
port_1_mass_flow="dm1@pneumatic_83",
|
||||
port_2_pressure="press3@pnnode4_17",
|
||||
port_2_temperature="temp3@pnnode4_17",
|
||||
port_2_mass_flow="dm2@pneumatic_83",
|
||||
reynolds="re@pneumatic_83",
|
||||
friction_factor="ff@pneumatic_83",
|
||||
)
|
||||
|
||||
|
||||
def _required_series(
|
||||
results: AmesimResults,
|
||||
path: str,
|
||||
) -> tuple[float, ...]:
|
||||
try:
|
||||
values = results.series(path)
|
||||
except KeyError as exc:
|
||||
raise ValueError(f"AMESim replay variable is not saved: {path}") from exc
|
||||
if len(values) != len(results.times):
|
||||
raise ValueError(f"AMESim replay variable has an invalid length: {path}")
|
||||
return values
|
||||
|
||||
|
||||
def _metric_summary(
|
||||
rows: list[dict[str, float]],
|
||||
key: str,
|
||||
) -> dict[str, float]:
|
||||
values = [float(row[key]) for row in rows]
|
||||
max_index = max(range(len(values)), key=lambda index: abs(values[index]))
|
||||
return {
|
||||
"maxAbs": abs(values[max_index]),
|
||||
"maxAbsTime": rows[max_index]["time"],
|
||||
"finalSigned": values[-1],
|
||||
}
|
||||
|
||||
|
||||
def replay_pnl0002_amesim_states(
|
||||
pipe: AmesimPnl0002,
|
||||
results: AmesimResults,
|
||||
paths: Pnl0002ReplayPaths,
|
||||
*,
|
||||
amesim_mass_flow_scale: float = -1.0e-3,
|
||||
) -> dict[str, object]:
|
||||
"""Replay saved AMESim states through current PNL0002 flow functions.
|
||||
|
||||
This is a calibration-only, no-integration calculation. It does not write
|
||||
states into the pipe or alter the production simulation path.
|
||||
"""
|
||||
|
||||
series_by_field = {
|
||||
field: _required_series(results, getattr(paths, field))
|
||||
for field in paths.__dataclass_fields__
|
||||
}
|
||||
rows: list[dict[str, float]] = []
|
||||
for index, time_s in enumerate(results.times):
|
||||
center_pressure = series_by_field["center_pressure"][index]
|
||||
center_temperature = series_by_field["center_temperature"][index]
|
||||
port_1_pressure = series_by_field["port_1_pressure"][index]
|
||||
port_2_pressure = series_by_field["port_2_pressure"][index]
|
||||
observed_flow_1 = (
|
||||
amesim_mass_flow_scale
|
||||
* series_by_field["port_1_mass_flow"][index]
|
||||
)
|
||||
observed_flow_2 = (
|
||||
amesim_mass_flow_scale
|
||||
* series_by_field["port_2_mass_flow"][index]
|
||||
)
|
||||
upstream_temperature_1 = (
|
||||
series_by_field["port_1_temperature"][index]
|
||||
if observed_flow_1 >= 0.0
|
||||
else center_temperature
|
||||
)
|
||||
upstream_temperature_2 = (
|
||||
series_by_field["port_2_temperature"][index]
|
||||
if observed_flow_2 >= 0.0
|
||||
else center_temperature
|
||||
)
|
||||
predicted_flow_1 = pipe.mass_flow(
|
||||
port_1_pressure,
|
||||
center_pressure,
|
||||
upstream_temperature_1,
|
||||
)
|
||||
predicted_flow_2 = pipe.mass_flow(
|
||||
port_2_pressure,
|
||||
center_pressure,
|
||||
upstream_temperature_2,
|
||||
)
|
||||
reynolds_1 = pipe.reynolds_number(
|
||||
observed_flow_1,
|
||||
upstream_temperature_1,
|
||||
)
|
||||
reynolds_2 = pipe.reynolds_number(
|
||||
observed_flow_2,
|
||||
upstream_temperature_2,
|
||||
)
|
||||
friction_1 = pipe.friction_factor(reynolds_1)
|
||||
friction_2 = pipe.friction_factor(reynolds_2)
|
||||
replay_reynolds = 0.5 * (reynolds_1 + reynolds_2)
|
||||
replay_friction = 0.5 * (friction_1 + friction_2)
|
||||
rows.append(
|
||||
{
|
||||
"time": float(time_s),
|
||||
"centerPressure": center_pressure,
|
||||
"centerTemperature": center_temperature,
|
||||
"port1Pressure": port_1_pressure,
|
||||
"port2Pressure": port_2_pressure,
|
||||
"observedPort1MassFlow": observed_flow_1,
|
||||
"observedPort2MassFlow": observed_flow_2,
|
||||
"predictedPort1MassFlow": predicted_flow_1,
|
||||
"predictedPort2MassFlow": predicted_flow_2,
|
||||
"port1MassFlowError": predicted_flow_1 - observed_flow_1,
|
||||
"port2MassFlowError": predicted_flow_2 - observed_flow_2,
|
||||
"amesimReynolds": series_by_field["reynolds"][index],
|
||||
"replayReynolds": replay_reynolds,
|
||||
"reynoldsError": (
|
||||
replay_reynolds - series_by_field["reynolds"][index]
|
||||
),
|
||||
"amesimFrictionFactor": series_by_field["friction_factor"][index],
|
||||
"replayFrictionFactor": replay_friction,
|
||||
"frictionFactorError": (
|
||||
replay_friction
|
||||
- series_by_field["friction_factor"][index]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
metric_keys = (
|
||||
"port1MassFlowError",
|
||||
"port2MassFlowError",
|
||||
"reynoldsError",
|
||||
"frictionFactorError",
|
||||
)
|
||||
return {
|
||||
"mode": "amesim-state-replay-no-integration",
|
||||
"component": pipe.name,
|
||||
"pointCount": len(rows),
|
||||
"massFlowScale": amesim_mass_flow_scale,
|
||||
"paths": {
|
||||
field: getattr(paths, field)
|
||||
for field in paths.__dataclass_fields__
|
||||
},
|
||||
"parameters": dict(pipe.parameter_values),
|
||||
"summary": {
|
||||
key: _metric_summary(rows, key)
|
||||
for key in metric_keys
|
||||
},
|
||||
"rows": rows,
|
||||
}
|
||||
|
||||
|
||||
def _compile_project_pipe(
|
||||
project_path: Path,
|
||||
component_name: str,
|
||||
) -> AmesimPnl0002:
|
||||
from app.main import (
|
||||
ReactFlowProjectPayload,
|
||||
_compile_xml_document_or_422,
|
||||
_validated_xml_document_or_422,
|
||||
build_reactflow_system_xml,
|
||||
validate_system_xml_document,
|
||||
)
|
||||
|
||||
payload = ReactFlowProjectPayload.model_validate_json(
|
||||
project_path.read_text(encoding="utf-8")
|
||||
)
|
||||
xml_bytes = build_reactflow_system_xml(payload)
|
||||
document = _validated_xml_document_or_422(
|
||||
validate_system_xml_document(xml_bytes)
|
||||
)
|
||||
_project, network = _compile_xml_document_or_422(document)
|
||||
try:
|
||||
component = network.components[component_name]
|
||||
except KeyError as exc:
|
||||
raise ValueError(f"Project component does not exist: {component_name}") from exc
|
||||
if not isinstance(component, AmesimPnl0002):
|
||||
raise ValueError(f"Project component is not PNL0002: {component_name}")
|
||||
return component
|
||||
|
||||
|
||||
def _paths_from_arguments(arguments: argparse.Namespace) -> Pnl0002ReplayPaths:
|
||||
values: Mapping[str, str] = {
|
||||
field: getattr(arguments, field)
|
||||
for field in PNL83_REPLAY_PATHS.__dataclass_fields__
|
||||
}
|
||||
return Pnl0002ReplayPaths(**values)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Replay saved AMESim p/T/m_flow through a PNL0002 model without integration."
|
||||
)
|
||||
parser.add_argument("project", type=Path)
|
||||
parser.add_argument("amesim_archive", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
parser.add_argument("--component", default="pneumatic_83")
|
||||
parser.add_argument("--mass-flow-scale", type=float, default=-1.0e-3)
|
||||
for field in PNL83_REPLAY_PATHS.__dataclass_fields__:
|
||||
parser.add_argument(
|
||||
"--" + field.replace("_", "-"),
|
||||
dest=field,
|
||||
default=getattr(PNL83_REPLAY_PATHS, field),
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
pipe = _compile_project_pipe(arguments.project, arguments.component)
|
||||
results = load_test_mql_amesim_results(arguments.amesim_archive)
|
||||
report = replay_pnl0002_amesim_states(
|
||||
pipe,
|
||||
results,
|
||||
_paths_from_arguments(arguments),
|
||||
amesim_mass_flow_scale=arguments.mass_flow_scale,
|
||||
)
|
||||
arguments.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
arguments.output.write_text(
|
||||
json.dumps(report, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(json.dumps({key: report[key] for key in (
|
||||
"mode",
|
||||
"component",
|
||||
"pointCount",
|
||||
"parameters",
|
||||
"summary",
|
||||
)}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user