149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
"""Simulation options and progress data; no numerical solver implementation."""
|
|
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
from typing import Sequence
|
|
|
|
@dataclass(frozen=True)
|
|
class SolverActivitySnapshot:
|
|
"""Low-cost, additive view of work inside an integration task.
|
|
|
|
``accepted_time`` deliberately changes only after an accepted solver step.
|
|
Trial evaluations may continue to advance ``activity_sequence`` and
|
|
``current_trial_time`` while that public progress value stays fixed.
|
|
"""
|
|
|
|
activity_sequence: int
|
|
activity_kind: str
|
|
current_trial_time: float | None
|
|
rhs_call_count: int
|
|
accepted_step_sequence: int
|
|
accepted_time: float | None
|
|
solver_step_sequence: int
|
|
jacobian_evaluation_count: int
|
|
thermofluid_closure_count: int
|
|
|
|
def as_dict(self) -> dict[str, object]:
|
|
return {
|
|
"activitySequence": self.activity_sequence,
|
|
"activityKind": self.activity_kind,
|
|
"currentTrialTime": self.current_trial_time,
|
|
"rhsCallCount": self.rhs_call_count,
|
|
"acceptedStepSequence": self.accepted_step_sequence,
|
|
"acceptedTime": self.accepted_time,
|
|
"solverStepSequence": self.solver_step_sequence,
|
|
"jacobianEvaluationCount": self.jacobian_evaluation_count,
|
|
"thermofluidClosureCount": self.thermofluid_closure_count,
|
|
}
|
|
|
|
class SolverActivityTracker:
|
|
"""Single-writer activity telemetry for a solver worker.
|
|
|
|
The solver thread is the only writer and the stream thread only snapshots
|
|
scalar attributes. The sequence is published last, so a reader never
|
|
treats partially published fields as a newer completed activity update.
|
|
The tracker receives aggregate counters from the independent C worker.
|
|
"""
|
|
|
|
__slots__ = (
|
|
"_accepted_step_sequence",
|
|
"_accepted_time",
|
|
"_activity_kind",
|
|
"_activity_sequence",
|
|
"_current_trial_time",
|
|
"_jacobian_evaluation_count",
|
|
"_rhs_call_count",
|
|
"_solver_step_sequence",
|
|
"_thermofluid_closure_count",
|
|
)
|
|
|
|
def __init__(self) -> None:
|
|
self._activity_sequence = 0
|
|
self._activity_kind = "idle"
|
|
self._current_trial_time: float | None = None
|
|
self._rhs_call_count = 0
|
|
self._accepted_step_sequence = 0
|
|
self._accepted_time: float | None = None
|
|
self._solver_step_sequence = 0
|
|
self._jacobian_evaluation_count = 0
|
|
self._thermofluid_closure_count = 0
|
|
|
|
def _publish(self, kind: str, time: float | None = None) -> None:
|
|
self._activity_kind = kind
|
|
if time is not None:
|
|
self._current_trial_time = float(time)
|
|
self._activity_sequence += 1
|
|
|
|
def start_integration(self, time: float) -> None:
|
|
self._accepted_time = float(time)
|
|
self._publish("solver_initialization", time)
|
|
|
|
def record_phase(self, kind: str, time: float | None = None) -> None:
|
|
self._publish(kind, time)
|
|
|
|
def record_solver_step(self, time: float) -> None:
|
|
self._solver_step_sequence += 1
|
|
self._publish("solver_step", time)
|
|
|
|
def record_rhs(self, time: float) -> None:
|
|
self._rhs_call_count += 1
|
|
self._publish("rhs", time)
|
|
|
|
def record_native_progress(self, time: float, rhs_count: int, accepted_count: int) -> None:
|
|
"""Publish aggregate counters from an isolated C worker without per-RHS callbacks."""
|
|
self._rhs_call_count = max(self._rhs_call_count, rhs_count)
|
|
self._accepted_step_sequence = max(self._accepted_step_sequence, accepted_count)
|
|
self._accepted_time = max(self._accepted_time or time, time)
|
|
self._publish("native_solver", time)
|
|
|
|
def record_jacobian(self, time: float) -> None:
|
|
self._jacobian_evaluation_count += 1
|
|
self._publish("jacobian", time)
|
|
|
|
def record_thermofluid_closure(self, time: float) -> None:
|
|
self._thermofluid_closure_count += 1
|
|
self._publish("thermofluid_closure", time)
|
|
|
|
def record_accepted_step(self, time: float) -> None:
|
|
accepted_time = float(time)
|
|
if (
|
|
self._accepted_time is not None
|
|
and accepted_time <= self._accepted_time
|
|
):
|
|
return
|
|
self._accepted_step_sequence += 1
|
|
self._accepted_time = accepted_time
|
|
self._publish("accepted_step", accepted_time)
|
|
|
|
def snapshot(self) -> SolverActivitySnapshot:
|
|
# ``activity_sequence`` is read last because writers publish it last.
|
|
activity_kind = self._activity_kind
|
|
current_trial_time = self._current_trial_time
|
|
rhs_call_count = self._rhs_call_count
|
|
accepted_step_sequence = self._accepted_step_sequence
|
|
accepted_time = self._accepted_time
|
|
solver_step_sequence = self._solver_step_sequence
|
|
jacobian_evaluation_count = self._jacobian_evaluation_count
|
|
thermofluid_closure_count = self._thermofluid_closure_count
|
|
activity_sequence = self._activity_sequence
|
|
return SolverActivitySnapshot(
|
|
activity_sequence=activity_sequence,
|
|
activity_kind=activity_kind,
|
|
current_trial_time=current_trial_time,
|
|
rhs_call_count=rhs_call_count,
|
|
accepted_step_sequence=accepted_step_sequence,
|
|
accepted_time=accepted_time,
|
|
solver_step_sequence=solver_step_sequence,
|
|
jacobian_evaluation_count=jacobian_evaluation_count,
|
|
thermofluid_closure_count=thermofluid_closure_count,
|
|
)
|
|
|
|
@dataclass(frozen=True)
|
|
class SolveIVPConfig:
|
|
t_start: float = 0.0
|
|
t_stop: float = 20.0
|
|
method: str = "BDF"
|
|
rtol: float = 1e-6
|
|
atol: float | Sequence[float] = 1e-8
|
|
max_step: float = 1e-3
|
|
first_step: float | None = None
|