hydro-core 0.1.0

Core types, traits, and evaluation metrics for the multi-model hydrology platform
Documentation
//! hydro-core: shared types, the `HydroModel` trait, and evaluation metrics
//! for the multi-model hydrology platform (新安江 / TOPMODEL / SWAT).
//!
//! Each model crate (`hydro-xinanjiang`, `hydro-topmodel`, `hydro-swat`) depends
//! only on this crate and implements `HydroModel`. The platform crate
//! (`hydro-platform`) uses `DynHydroModel` for heterogeneous comparison.

pub mod forcing;
pub mod metrics;

pub use forcing::{Forcing, BasinInfo, Outlet};
pub use metrics::{
    MetricsReport, nse, kge, rmse, pbias, r2, dc_grade, compute_metrics,
    GbtEventErrors, QualificationTolerance, QualificationReport,
    event_errors, event_qualified, qualified_rate, qualification_grade, scheme_grade,
    compute_metrics_gbt,
    ProbabilisticReport, crps, brier, pod_far_csi, ensemble_exceedance, compute_probabilistic,
    rank_histogram,
};

use serde::{Serialize, Deserialize};

/// Which hydrological model to use.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ModelType {
    Xinanjiang,
    Topmodel,
    Swat,
    Apicont,
}

impl ModelType {
    pub fn name(&self) -> &'static str {
        match self {
            ModelType::Xinanjiang => "新安江",
            ModelType::Topmodel => "TOPMODEL",
            ModelType::Swat => "SWAT",
            ModelType::Apicont => "API-CONT",
        }
    }
}

/// The core trait every distributed hydrological model implements.
///
/// Each model owns its parameter struct (associated type `Params`) and its
/// internal state. The platform calls `step()` per time-step and reads
/// `discharge()` for the outflow hydrograph.
pub trait HydroModel: Send {
    /// Model-specific parameters (serializable for config persistence).
    type Params: Serialize + for<'de> Deserialize<'de> + Clone + Send;

    /// Construct a model instance with the given parameters (default state).
    fn new(params: Self::Params) -> Self;

    /// Advance one time-step given the forcing (precipitation, PET, temperature).
    fn step(&mut self, forcing: &Forcing, dt_h: f64);

    /// Current discharge at the basin outlet (m³/s).
    fn discharge(&self) -> f64;

    /// Serialize internal state for warm-restart across events.
    fn state(&self) -> serde_json::Value;

    /// Reset internal state to default (cold start).
    fn reset(&mut self);

    /// Model display name (e.g. "新安江", "TOPMODEL").
    fn name(&self) -> &'static str;

    /// Read-only access to parameters.
    fn params(&self) -> &Self::Params;

    /// Mutable access to parameters (for calibration).
    fn params_mut(&mut self) -> &mut Self::Params;
}

/// Object-safe wrapper so the platform can hold `Vec<Box<dyn DynHydroModel>>`
/// for heterogeneous multi-model comparison.
pub trait DynHydroModel: Send {
    fn step(&mut self, forcing: &Forcing, dt_h: f64);
    fn discharge(&self) -> f64;
    fn name(&self) -> &'static str;
    fn params_json(&self) -> serde_json::Value;
    fn set_params_json(&mut self, v: &serde_json::Value);
    fn reset(&mut self);
}

/// Blanket impl: any `HydroModel` is automatically a `DynHydroModel`.
impl<M: HydroModel> DynHydroModel for M {
    fn step(&mut self, forcing: &Forcing, dt_h: f64) {
        HydroModel::step(self, forcing, dt_h);
    }
    fn discharge(&self) -> f64 {
        HydroModel::discharge(self)
    }
    fn name(&self) -> &'static str {
        HydroModel::name(self)
    }
    fn params_json(&self) -> serde_json::Value {
        serde_json::to_value(self.params()).unwrap_or(serde_json::Value::Null)
    }
    fn set_params_json(&mut self, v: &serde_json::Value) {
        if let Ok(p) = serde_json::from_value::<M::Params>(v.clone()) {
            *self.params_mut() = p;
        }
    }
    fn reset(&mut self) {
        HydroModel::reset(self);
    }
}