hydra-engine-wds 1.0.2

Hydra water distribution engine — data model, hydraulic solver, quality engine, session API, analytics
Documentation
use std::{sync::OnceLock, time::Duration};

pub(super) fn solve_timing_enabled() -> bool {
    static ENABLED: OnceLock<bool> = OnceLock::new();
    *ENABLED.get_or_init(|| std::env::var_os("HYDRA_SOLVE_TIMING").is_some())
}

#[derive(Default)]
pub(super) struct SparsePhaseTimings {
    pub(super) reset: Duration,
    pub(super) factor: Duration,
    pub(super) forward: Duration,
    pub(super) backward: Duration,
}

#[derive(Default)]
pub(super) struct SolvePhaseTimings {
    pub(super) setup: Duration,
    pub(super) demand: Duration,
    pub(super) mapping: Duration,
    pub(super) py: Duration,
    pub(super) assembly: Duration,
    pub(super) linear_solve: Duration,
    pub(super) sparse_reset: Duration,
    pub(super) sparse_factor: Duration,
    pub(super) sparse_forward: Duration,
    pub(super) sparse_backward: Duration,
    pub(super) head_extract: Duration,
    pub(super) updates: Duration,
    pub(super) status_checks: Duration,
    pub(super) post: Duration,
    pub(super) iterations: usize,
}

impl SolvePhaseTimings {
    pub(super) fn enabled() -> bool {
        solve_timing_enabled()
    }

    pub(super) fn emit(&self, step_time: f64, junctions: usize, links: usize) {
        if self.iterations == 0 {
            return;
        }

        let total = self.setup
            + self.demand
            + self.mapping
            + self.py
            + self.assembly
            + self.linear_solve
            + self.head_extract
            + self.updates
            + self.status_checks
            + self.post;
        let total_ms = total.as_secs_f64() * 1000.0;
        let iter_scale = 1.0 / self.iterations as f64;

        eprintln!(
            "[hydra:solve] t={step_time:.0}s iter={} nodes={} links={} total={total_ms:.2}ms \
setup={:.2} demand={:.2} map={:.2} py={:.2} assembly={:.2} solve={:.2} sreset={:.2} chol={:.2} fwd={:.2} bwd={:.2} heads={:.2} update={:.2} status={:.2} post={:.2}",
            self.iterations,
            junctions,
            links,
            self.setup.as_secs_f64() * 1000.0,
            self.demand.as_secs_f64() * 1000.0,
            self.mapping.as_secs_f64() * 1000.0,
            self.py.as_secs_f64() * 1000.0 * iter_scale,
            self.assembly.as_secs_f64() * 1000.0 * iter_scale,
            self.linear_solve.as_secs_f64() * 1000.0 * iter_scale,
            self.sparse_reset.as_secs_f64() * 1000.0 * iter_scale,
            self.sparse_factor.as_secs_f64() * 1000.0 * iter_scale,
            self.sparse_forward.as_secs_f64() * 1000.0 * iter_scale,
            self.sparse_backward.as_secs_f64() * 1000.0 * iter_scale,
            self.head_extract.as_secs_f64() * 1000.0 * iter_scale,
            self.updates.as_secs_f64() * 1000.0 * iter_scale,
            self.status_checks.as_secs_f64() * 1000.0 * iter_scale,
            self.post.as_secs_f64() * 1000.0,
        );
    }
}