Source code for turbo_moc.progress

"""
turbo_moc.progress -- combines each solver stage's own progress callback
(fraction: float, stage_label: str) into ONE overall (fraction, message)
stream, for a UI progress bar -- e.g. Dash's set_progress(...) inside a
background=True callback.

Stage weights below are rough, from profiling the reference case (Stage 3,
the kernel marching, dominates runtime by far; Stage 1/2/4 are comparatively
quick) -- meant to make the bar move at roughly the rate work is actually
happening, not to be exact. Every turbo_moc solver stage method
(run_stage_1_sauer, run_stage_2_ivp/..., run_stage_3_kernel/_mln,
run_stage_4_reflex) accepts progress_callback=<callable(fraction, label)>,
called with the label it already prints to the terminal ("1 of 4", "2 of
4", "3 of 4", "4 of 4"/"4A of 4") -- only the leading digit is used here to
route to a stage weight, so both solvers' label variants work unchanged.
A parenthesized qualifier in the label -- "(retry 2 of 6)" from
design_nozzle's automatic retries, "(finished, not converged)" on its final
update -- is kept in the displayed message. A retry restarts at Stage 1, so
the overall fraction intentionally drops back for the new attempt; the
reporter itself holds no state between calls.
"""

DEFAULT_STAGE_WEIGHTS = {
    "1": 0.03,
    "2": 0.10,
    "3": 0.80,
    "4": 0.07,
}

__all__ = ["DEFAULT_STAGE_WEIGHTS", "make_progress_reporter"]


[docs] def make_progress_reporter(set_progress, weights=None): """Create a callback that combines progress from all solver stages. Parameters ---------- set_progress : callable Receives the ``(percent_string, message_string)`` tuple expected by a Dash background callback. weights : dict, optional Relative weights for solver stages 1 through 4. The defaults reflect their approximate runtime. Returns ------- callable A reporter accepting ``(fraction, stage_label)``, suitable for a solver's ``progress_callback`` argument. Examples -------- Use the returned reporter inside a Dash background callback:: reporter = make_progress_reporter(set_progress) data = design_nozzle(..., progress_callback=reporter) """ weights = weights or DEFAULT_STAGE_WEIGHTS order = ["1", "2", "3", "4"] cumulative_before = {} acc = 0.0 for key in order: cumulative_before[key] = acc acc += weights.get(key, 0.0) total_weight = acc if acc > 0 else 1.0 def reporter(fraction, stage_label): label = stage_label.strip() stage_key = label[:1] or "1" base = cumulative_before.get(stage_key, 0.0) w = weights.get(stage_key, 0.0) try: local = float(fraction) except (TypeError, ValueError): local = 0.0 local = 0.0 if local != local else max(0.0, min(1.0, local)) # NaN -> 0 (min(1, nan) is 1) overall = min(1.0, (base + w * local) / total_weight) # Keep qualifiers such as "(retry 2 of 6)" or "(finished, not converged)". note = label[label.index("("):] if "(" in label else "" set_progress((f"{overall * 100:.0f}%", f"Stage {stage_key} of 4 {note}".strip())) return reporter