Skip to main content

hydro_core/
lib.rs

1//! hydro-core: shared types, the `HydroModel` trait, and evaluation metrics
2//! for the multi-model hydrology platform (新安江 / TOPMODEL / SWAT).
3//!
4//! Each model crate (`hydro-xinanjiang`, `hydro-topmodel`, `hydro-swat`) depends
5//! only on this crate and implements `HydroModel`. The platform crate
6//! (`hydro-platform`) uses `DynHydroModel` for heterogeneous comparison.
7
8pub mod forcing;
9pub mod metrics;
10
11pub use forcing::{Forcing, BasinInfo, Outlet};
12pub use metrics::{
13    MetricsReport, nse, kge, rmse, pbias, r2, dc_grade, compute_metrics,
14    GbtEventErrors, QualificationTolerance, QualificationReport,
15    event_errors, event_qualified, qualified_rate, qualification_grade, scheme_grade,
16    compute_metrics_gbt,
17    ProbabilisticReport, crps, brier, pod_far_csi, ensemble_exceedance, compute_probabilistic,
18    rank_histogram,
19};
20
21use serde::{Serialize, Deserialize};
22
23/// Which hydrological model to use.
24#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
25pub enum ModelType {
26    Xinanjiang,
27    Topmodel,
28    Swat,
29    Apicont,
30}
31
32impl ModelType {
33    pub fn name(&self) -> &'static str {
34        match self {
35            ModelType::Xinanjiang => "新安江",
36            ModelType::Topmodel => "TOPMODEL",
37            ModelType::Swat => "SWAT",
38            ModelType::Apicont => "API-CONT",
39        }
40    }
41}
42
43/// The core trait every distributed hydrological model implements.
44///
45/// Each model owns its parameter struct (associated type `Params`) and its
46/// internal state. The platform calls `step()` per time-step and reads
47/// `discharge()` for the outflow hydrograph.
48pub trait HydroModel: Send {
49    /// Model-specific parameters (serializable for config persistence).
50    type Params: Serialize + for<'de> Deserialize<'de> + Clone + Send;
51
52    /// Construct a model instance with the given parameters (default state).
53    fn new(params: Self::Params) -> Self;
54
55    /// Advance one time-step given the forcing (precipitation, PET, temperature).
56    fn step(&mut self, forcing: &Forcing, dt_h: f64);
57
58    /// Current discharge at the basin outlet (m³/s).
59    fn discharge(&self) -> f64;
60
61    /// Serialize internal state for warm-restart across events.
62    fn state(&self) -> serde_json::Value;
63
64    /// Reset internal state to default (cold start).
65    fn reset(&mut self);
66
67    /// Model display name (e.g. "新安江", "TOPMODEL").
68    fn name(&self) -> &'static str;
69
70    /// Read-only access to parameters.
71    fn params(&self) -> &Self::Params;
72
73    /// Mutable access to parameters (for calibration).
74    fn params_mut(&mut self) -> &mut Self::Params;
75}
76
77/// Object-safe wrapper so the platform can hold `Vec<Box<dyn DynHydroModel>>`
78/// for heterogeneous multi-model comparison.
79pub trait DynHydroModel: Send {
80    fn step(&mut self, forcing: &Forcing, dt_h: f64);
81    fn discharge(&self) -> f64;
82    fn name(&self) -> &'static str;
83    fn params_json(&self) -> serde_json::Value;
84    fn set_params_json(&mut self, v: &serde_json::Value);
85    fn reset(&mut self);
86}
87
88/// Blanket impl: any `HydroModel` is automatically a `DynHydroModel`.
89impl<M: HydroModel> DynHydroModel for M {
90    fn step(&mut self, forcing: &Forcing, dt_h: f64) {
91        HydroModel::step(self, forcing, dt_h);
92    }
93    fn discharge(&self) -> f64 {
94        HydroModel::discharge(self)
95    }
96    fn name(&self) -> &'static str {
97        HydroModel::name(self)
98    }
99    fn params_json(&self) -> serde_json::Value {
100        serde_json::to_value(self.params()).unwrap_or(serde_json::Value::Null)
101    }
102    fn set_params_json(&mut self, v: &serde_json::Value) {
103        if let Ok(p) = serde_json::from_value::<M::Params>(v.clone()) {
104            *self.params_mut() = p;
105        }
106    }
107    fn reset(&mut self) {
108        HydroModel::reset(self);
109    }
110}