Source code for tidal.solver.progress
"""Simulation progress bar for TIDAL solvers (tqdm-based).
Provides a thin wrapper around tqdm that tracks simulation time ``t`` and
displays elapsed time, ETA, and percentage completion. Designed to be
called from solver step loops or RHS functions with negligible overhead.
Key features:
- ``disable=True`` makes tqdm a complete no-op (zero overhead when quiet).
- ``mininterval=0.5`` throttles terminal writes to ~2 Hz.
- Monotonic ``t`` tracking prevents backwards progress from IDA Jacobian
finite-difference evaluations.
- Output goes to ``sys.stderr`` so it doesn't interfere with piped stdout.
"""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING
from tqdm import tqdm # pyright: ignore[reportMissingTypeStubs]
if TYPE_CHECKING:
from tqdm import tqdm as TqdmType # noqa: N812
[docs]
class SimulationProgress:
"""tqdm-based progress bar driven by simulation time.
Parameters
----------
t_start : float
Simulation start time.
t_end : float
Simulation end time.
solver_name : str
Label for the progress bar (e.g. ``"IDA"``, ``"CVODE"``).
disable : bool
If ``True``, completely disables output (zero overhead).
"""
def __init__(
self,
t_start: float,
t_end: float,
*,
solver_name: str = "",
disable: bool = False,
) -> None:
self._t_start = t_start
self._max_t = t_start
self._pbar: TqdmType[None] = tqdm( # type: ignore[assignment] # tqdm stub overload
total=t_end - t_start,
desc=f" {solver_name}" if solver_name else " Simulating",
unit="t",
bar_format=(
"{desc}: {percentage:3.0f}%|{bar}| "
"t={n:.3g}/{total:.3g} [{elapsed}<{remaining}]"
),
disable=disable,
file=sys.stderr,
mininterval=0.5,
leave=True,
)
[docs]
def set_phase(self, phase: str) -> None:
"""Update the progress bar description to show current phase.
Useful for indicating solver initialization before the step loop
begins, so users know the solver hasn't hung on large systems.
"""
self._pbar.set_description(f" {phase}")
self._pbar.refresh()
[docs]
def update(self, t: float) -> None:
"""Report current simulation time.
Monotonic: ignores ``t`` values less than the previous maximum,
which can occur during IDA Jacobian finite-difference evaluations.
Clamps to total to prevent tqdm warnings when leapfrog overshoots.
"""
if t > self._max_t:
self._max_t = t
n = t - self._t_start
if self._pbar.total is not None:
n = min(n, self._pbar.total)
self._pbar.n = n
self._pbar.refresh()
[docs]
def finish(self) -> None:
"""Set progress to 100% and close the bar."""
if self._pbar.total is not None:
self._pbar.n = self._pbar.total
self._pbar.refresh()
self._pbar.close()