完善仿真交互、结果展示与模型元数据
This commit is contained in:
1 parent
f1256a121d
commit
f7f1078911
26 files changed
+9042
-575
No files matched your search
+133
-28
@@ -1,15 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from math import floor, isfinite
|
||||
from typing import Literal
|
||||
|
||||
from PythonModels.core.algebraic import PressureFlowSolver
|
||||
from PythonModels.core.base import DynamicComponent
|
||||
from PythonModels.core.metadata import ResultVariableMetadata
|
||||
from PythonModels.core.network import Endpoint, SimulationNetwork
|
||||
from PythonModels.core.solver import SolveIVPConfig, integrate_ode
|
||||
from PythonModels.core.solver import ODESolution, SolveIVPConfig, integrate_ode
|
||||
from PythonModels.core.stream import StreamResolver
|
||||
|
||||
|
||||
SimulationProgressCallback = Callable[[float, str], None]
|
||||
SimulationCancellationCheck = Callable[[], bool]
|
||||
SimulationRunStatus = Literal["completed", "cancelled", "failed"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SimulationPreparationIssue:
|
||||
code: str
|
||||
@@ -28,7 +36,11 @@ class SimulationPreparationError(ValueError):
|
||||
@dataclass(frozen=True)
|
||||
class GenericSimulationResult:
|
||||
success: bool
|
||||
status: SimulationRunStatus
|
||||
message: str
|
||||
simulated_until: float
|
||||
requested_stop_time: float
|
||||
variables: tuple[ResultVariableMetadata, ...]
|
||||
series: dict[str, list[float]]
|
||||
final: dict[str, float]
|
||||
diagnostics: dict[str, object]
|
||||
@@ -36,7 +48,12 @@ class GenericSimulationResult:
|
||||
def as_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"success": self.success,
|
||||
"status": self.status,
|
||||
"partial": self.status != "completed",
|
||||
"message": self.message,
|
||||
"simulatedUntil": self.simulated_until,
|
||||
"requestedStopTime": self.requested_stop_time,
|
||||
"variables": [variable.as_dict() for variable in self.variables],
|
||||
"series": self.series,
|
||||
"final": self.final,
|
||||
"diagnostics": self.diagnostics,
|
||||
@@ -276,51 +293,121 @@ class GenericFluidSystem:
|
||||
return derivatives
|
||||
|
||||
def _append_current_state(self, series: dict[str, list[float]]) -> None:
|
||||
for component in self.dynamic_components:
|
||||
properties = component.refresh_thermodynamic_ports()
|
||||
state = component.get_state_vector()
|
||||
if len(state) >= 2:
|
||||
series.setdefault(f"{component.name}.m", []).append(float(state[0]))
|
||||
series.setdefault(f"{component.name}.U", []).append(float(state[1]))
|
||||
for name in ("p", "T", "rho", "u", "h"):
|
||||
if hasattr(properties, name):
|
||||
series.setdefault(f"{component.name}.{name}", []).append(
|
||||
float(getattr(properties, name))
|
||||
)
|
||||
|
||||
for component in self.network.components.values():
|
||||
for port_name, port in component.ports.items():
|
||||
prefix = f"{component.name}.{port_name}"
|
||||
series.setdefault(f"{prefix}.p", []).append(float(port.p))
|
||||
series.setdefault(f"{prefix}.m_flow", []).append(float(port.m_flow))
|
||||
series.setdefault(f"{prefix}.h_outflow", []).append(
|
||||
float(port.h_outflow)
|
||||
)
|
||||
for relative_key, value in component.result_values().items():
|
||||
series.setdefault(
|
||||
f"{component.name}.{relative_key}", []
|
||||
).append(value)
|
||||
|
||||
def simulate(
|
||||
self,
|
||||
config: SolveIVPConfig,
|
||||
*,
|
||||
sample_step: float,
|
||||
progress_callback: SimulationProgressCallback | None = None,
|
||||
cancel_check: SimulationCancellationCheck | None = None,
|
||||
) -> GenericSimulationResult:
|
||||
last_reported_progress = -1.0
|
||||
last_reported_phase = ""
|
||||
|
||||
def report_progress(
|
||||
progress: float,
|
||||
phase: str,
|
||||
*,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
nonlocal last_reported_phase, last_reported_progress
|
||||
if progress_callback is None:
|
||||
return
|
||||
bounded_progress = min(1.0, max(0.0, progress))
|
||||
if (
|
||||
force
|
||||
or phase != last_reported_phase
|
||||
or bounded_progress - last_reported_progress >= 0.0025
|
||||
):
|
||||
last_reported_phase = phase
|
||||
last_reported_progress = max(
|
||||
last_reported_progress,
|
||||
bounded_progress,
|
||||
)
|
||||
progress_callback(last_reported_progress, phase)
|
||||
|
||||
report_progress(0.0, "initializing", force=True)
|
||||
t_eval = simulation_sample_times(config, sample_step)
|
||||
initial_state = self.consistent_initial_state_vector()
|
||||
report_progress(0.0, "integrating", force=True)
|
||||
duration = config.t_stop - config.t_start
|
||||
furthest_solver_time = config.t_start
|
||||
|
||||
def report_solver_time(time: float) -> None:
|
||||
nonlocal furthest_solver_time
|
||||
furthest_solver_time = max(furthest_solver_time, float(time))
|
||||
time_fraction = (
|
||||
(furthest_solver_time - config.t_start) / duration
|
||||
if duration > 0.0
|
||||
else 1.0
|
||||
)
|
||||
report_progress(time_fraction, "integrating")
|
||||
|
||||
def monitored_rhs(time: float, state_vector: list[float]) -> list[float]:
|
||||
if cancel_check is None:
|
||||
report_solver_time(time)
|
||||
return self.rhs(time, state_vector)
|
||||
|
||||
solution = integrate_ode(
|
||||
rhs=self.rhs,
|
||||
rhs=monitored_rhs,
|
||||
initial_state=initial_state,
|
||||
config=config,
|
||||
t_eval=t_eval,
|
||||
cancel_check=cancel_check,
|
||||
accepted_step_callback=(
|
||||
report_solver_time if cancel_check is not None else None
|
||||
),
|
||||
)
|
||||
if isinstance(solution, ODESolution):
|
||||
run_status: SimulationRunStatus = solution.status
|
||||
integration_error = solution.error
|
||||
else:
|
||||
run_status = "completed" if bool(solution.success) else "failed"
|
||||
integration_error = None
|
||||
result_message = str(solution.message)
|
||||
postprocess_progress = (
|
||||
1.0
|
||||
if run_status == "completed"
|
||||
else max(0.0, last_reported_progress)
|
||||
)
|
||||
report_progress(postprocess_progress, "postprocessing", force=True)
|
||||
times = [float(value) for value in solution.t]
|
||||
series: dict[str, list[float]] = {"time": times}
|
||||
series: dict[str, list[float]] = {"time": []}
|
||||
postprocessing_error: Exception | None = None
|
||||
for time_index in range(len(times)):
|
||||
if (
|
||||
run_status == "completed"
|
||||
and cancel_check is not None
|
||||
and cancel_check()
|
||||
):
|
||||
run_status = "cancelled"
|
||||
result_message = "Simulation was stopped while preparing partial results."
|
||||
break
|
||||
state = [
|
||||
float(solution.y[state_index][time_index])
|
||||
for state_index in range(len(solution.y))
|
||||
]
|
||||
self.apply_state_vector(state)
|
||||
self._close_current_state()
|
||||
self._append_current_state(series)
|
||||
try:
|
||||
self.apply_state_vector(state)
|
||||
self._close_current_state()
|
||||
self._append_current_state(series)
|
||||
series["time"].append(times[time_index])
|
||||
except Exception as exc:
|
||||
run_status = "failed"
|
||||
result_message = str(exc)
|
||||
postprocessing_error = exc
|
||||
break
|
||||
if len(series["time"]) < 2:
|
||||
if postprocessing_error is not None:
|
||||
raise postprocessing_error
|
||||
if integration_error is not None:
|
||||
raise integration_error
|
||||
|
||||
final = {
|
||||
key: values[-1]
|
||||
@@ -347,11 +434,29 @@ class GenericFluidSystem:
|
||||
),
|
||||
},
|
||||
"stateCount": len(initial_state),
|
||||
"sampleCount": len(times),
|
||||
"sampleCount": len(series["time"]),
|
||||
}
|
||||
variables = tuple(
|
||||
variable
|
||||
for variable in self.network.result_variable_metadata()
|
||||
if variable.key in series
|
||||
)
|
||||
report_progress(
|
||||
1.0 if run_status == "completed" else max(0.0, last_reported_progress),
|
||||
"complete" if run_status == "completed" else run_status,
|
||||
force=True,
|
||||
)
|
||||
return GenericSimulationResult(
|
||||
success=bool(solution.success),
|
||||
message=str(solution.message),
|
||||
success=run_status == "completed" and bool(solution.success),
|
||||
status=run_status,
|
||||
message=result_message,
|
||||
simulated_until=(
|
||||
float(series["time"][-1])
|
||||
if series["time"]
|
||||
else float(config.t_start)
|
||||
),
|
||||
requested_stop_time=float(config.t_stop),
|
||||
variables=variables,
|
||||
series=series,
|
||||
final=final,
|
||||
diagnostics=diagnostics,
|
||||
|
||||
Reference in new issue
Block a user