Skip to main content

hydro_topmodel/
lib.rs

1//! TOPMODEL(Beven & Kirkby 1979;1995 Fortran 版)——忠实分布式实现。
2//!
3//! Clean-room 从 R `topmodel` 包 v0.7.5 的 C 数值核心(`core_topmodel.c`/`get_f.c`/
4//! `param_init.c`/`misc.c`)移植:算法(Beven 公式)不可版权,C 源仅作行为 oracle。
5//! 11 参数 + TWI 直方图 + 河道延迟;单位 mm / hour(与 R 参考一致,便于 bit-exact 校验)。
6//! 交叉验证:`tests/topmodel_crosscheck.rs`(待加)vs R `topmodel` on huagrahuma 数据集。
7//!
8//! 关键方程(eq 号同 Beven HESS 2021 / R 源注释):
9//! - 基流退水 `qs = qss·exp(-S_mean/m)`(eq 6.33),`qss = exp(lnTe + ln(dt) - λ)`
10//! - 局部亏缺 `S[j] = S_mean + m·(λ - atb[j])`(eq 18.8)
11//! - 非饱和排水 `qv = Suz/(S·td)·dt`(eq 6.26)
12//! - 下渗 Green-Ampt/Morel-Seytoux(Newton-Raphson,`get_f`)
13//! - 路由:河道延迟 `Qt[k] += qt·Ad[j]`,k = i + j + ndelay
14
15use hydro_core::{Forcing, HydroModel};
16use serde::{Deserialize, Serialize};
17
18const ZERO: f64 = 0.0000001;
19
20// ── 流域几何(TWI 直方图 + 河道延迟)──
21
22/// TWI 直方图:`atb` = ln(a/tanβ) 值(**降序**),`Aatb_r` = 累积面积比例(到该类下限)。
23#[derive(Clone, Debug, Serialize, Deserialize, Default)]
24pub struct TopidxHistogram {
25    pub atb: Vec<f64>,
26    pub Aatb_r: Vec<f64>,
27}
28
29/// 河道延迟:`d` = 河道距离(增序),`Ad_r` = 对应累积面积比例。
30#[derive(Clone, Debug, Serialize, Deserialize, Default)]
31pub struct ChannelDelay {
32    pub d: Vec<f64>,
33    pub Ad_r: Vec<f64>,
34}
35
36/// TOPMODEL 11 参数 + 流域几何。
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct TopmodelParams {
39    pub qs0: f64,    // 初始基流 [m]
40    pub lnTe: f64,   // areal avg ln(T0) [m²/h]
41    pub m: f64,      // 饱和亏缺衰减 [m]
42    pub Sr0: f64,    // 初始根区亏缺 [m]
43    pub Srmax: f64,  // 最大根区亏缺 [m]
44    pub td: f64,     // 非饱和带时间延迟 [h/m]
45    pub vch: f64,    // 河道波速 [m/h]
46    pub vr: f64,     // 坡面波速 [m/h]
47    pub K0: f64,     // 饱和导水率 [m/h]
48    pub CD: f64,     // 毛管驱动 [m]
49    pub dt: f64,     // 时步 [h]
50    #[serde(default)]
51    pub topidx: Option<TopidxHistogram>,
52    #[serde(default)]
53    pub channel: Option<ChannelDelay>,
54    #[serde(default = "default_area")]
55    pub area_km2: f64,
56}
57
58fn default_area() -> f64 { 1000.0 }
59
60impl Default for TopmodelParams {
61    fn default() -> Self {
62        // 默认:单类 TWI 直方图(集总退化,供平台多模型对比;真实运行需提供 topidx)。
63        Self {
64            qs0: 0.001, lnTe: 5.0, m: 0.01, Sr0: 0.0, Srmax: 0.05,
65            td: 30.0, vch: 100.0, vr: 100.0, K0: 3.0, CD: 1.0, dt: 1.0,
66            topidx: Some(TopidxHistogram { atb: vec![0.0], Aatb_r: vec![1.0] }),
67            channel: None,
68            area_km2: 1000.0,
69        }
70    }
71}
72
73/// 完整运行输出(逐时步序列,mm;供 oracle 逐变量比对)。
74#[derive(Clone, Debug, Default)]
75pub struct TopmodelOutput {
76    pub Qt: Vec<f64>,      // 出流(路由后)[mm/时步]
77    pub qs: Vec<f64>,      // 基流
78    pub qo: Vec<f64>,      // 地表产流(含 fex)
79    pub S_mean: Vec<f64>,  // 平均饱和亏缺
80    pub f: Vec<f64>,       // 下渗
81    pub fex: Vec<f64>,     // 超渗
82    pub Ea: Vec<f64>,      // 实际蒸散发(面平均)
83}
84
85// ── 下渗:Green-Ampt/Morel-Seytoux(Newton-Raphson;跨时步状态)──
86
87/// 下渗计算器(对应 get_f.c 的 static 变量 cumf/f_/pt/cnst/ponding)。
88#[derive(Clone)]
89struct Infiltration {
90    cumf: f64,
91    f_: f64,
92    pt: f64,
93    cnst: f64,
94    ponding: bool,
95}
96
97impl Infiltration {
98    fn new() -> Self {
99        Self { cumf: 0.0, f_: 0.0, pt: 0.0, cnst: 0.0, ponding: false }
100    }
101    fn reset(&mut self) {
102        self.cumf = 0.0; self.f_ = 0.0; self.pt = 0.0; self.cnst = 0.0; self.ponding = false;
103    }
104
105    /// 对应 get_f(t, R, C, K0, m, dt)。R = 雨强 [m/h],t = 累计时间 [h]。
106    fn get_f(&mut self, t: f64, r: f64, c: f64, k0: f64, m: f64, dt: f64) -> f64 {
107        const TOLERANCE: f64 = 0.00001;
108        const MAXITER: usize = 2000;
109        const NTERMS: usize = 10;
110
111        if t / dt == 1.0 {
112            self.reset();
113        }
114        if r <= 0.0 {
115            self.cumf = 0.0; self.ponding = false; self.f_ = 0.0; self.pt = 0.0;
116            return 0.0;
117        }
118
119        let mut f1 = 0.0;
120        if !self.ponding {
121            if self.cumf > 0.0 {
122                f1 = self.cumf;
123                let mut r2 = -k0 / m * (c + f1) / (1.0 - (f1 / m).exp());
124                if r > r2 {
125                    self.f_ = self.cumf;
126                    self.pt = t - dt;
127                    self.ponding = true;
128                    // goto cont1
129                    self.cnst = Self::compute_cnst(self.f_, c, m);
130                    self.f_ += r * (t - self.pt) / 2.0;
131                    // fall through to Newton-Raphson below
132                } else {
133                    // no ponding path continues below
134                    let f2 = self.cumf + r * dt;
135                    r2 = -k0 / m * (c + f2) / (1.0 - (f2 / m).exp());
136                    if f2 == 0.0 || r < r2 {
137                        let f = r;
138                        self.cumf += f * dt;
139                        self.ponding = false;
140                        return f;
141                    }
142                    self.f_ = self.cumf + r2 * dt;
143                    let mut f2m = f2;
144                    let mut f1m = f1;
145                    let mut i = 0;
146                    while i < MAXITER {
147                        r2 = -k0 / m * (c + self.f_) / (1.0 - (self.f_ / m).exp());
148                        let diff;
149                        if r2 > r {
150                            f1m = self.f_;
151                            self.f_ = (self.f_ + f2m) / 2.0;
152                            diff = self.f_ - f1m;
153                        } else {
154                            f2m = self.f_;
155                            self.f_ = (self.f_ + f1m) / 2.0;
156                            diff = self.f_ - f2m;
157                        }
158                        if diff.abs() < TOLERANCE { break; }
159                        i += 1;
160                    }
161                    if i == MAXITER { return -9999.0; }
162                    self.pt = t - dt + (self.f_ - self.cumf) / r;
163                    if self.pt > t {
164                        let f = r;
165                        self.cumf += f * dt;
166                        self.ponding = false;
167                        return f;
168                    }
169                    self.cnst = Self::compute_cnst(self.f_, c, m);
170                    self.f_ += r * (t - self.pt) / 2.0;
171                    self.ponding = true;
172                }
173            } else {
174                // cumf == 0 path
175                let f2 = self.cumf + r * dt;
176                let r2 = -k0 / m * (c + f2) / (1.0 - (f2 / m).exp());
177                if f2 == 0.0 || r < r2 {
178                    let f = r;
179                    self.cumf += f * dt;
180                    self.ponding = false;
181                    return f;
182                }
183                self.f_ = self.cumf + r2 * dt;
184                let mut f2m = f2;
185                let mut f1m = f1;
186                let mut i = 0;
187                while i < MAXITER {
188                    let r2 = -k0 / m * (c + self.f_) / (1.0 - (self.f_ / m).exp());
189                    let diff;
190                    if r2 > r {
191                        f1m = self.f_;
192                        self.f_ = (self.f_ + f2m) / 2.0;
193                        diff = self.f_ - f1m;
194                    } else {
195                        f2m = self.f_;
196                        self.f_ = (self.f_ + f1m) / 2.0;
197                        diff = self.f_ - f2m;
198                    }
199                    if diff.abs() < TOLERANCE { break; }
200                    i += 1;
201                }
202                if i == MAXITER { return -9999.0; }
203                self.pt = t - dt + (self.f_ - self.cumf) / r;
204                if self.pt > t {
205                    let f = r;
206                    self.cumf += f * dt;
207                    self.ponding = false;
208                    return f;
209                }
210                self.cnst = Self::compute_cnst(self.f_, c, m);
211                self.f_ += r * (t - self.pt) / 2.0;
212                self.ponding = true;
213            }
214        }
215
216        // Newton-Raphson(ponding 发生后)
217        let mut i = 0;
218        while i < MAXITER {
219            let fc = self.f_ + c;
220            let mut sum = 0.0;
221            let mut factorial = 1.0;
222            for j in 1..=NTERMS {
223                factorial *= j as f64;
224                sum += (fc / m).powi(j as i32) / (j as f64 * factorial);
225            }
226            let g1 = -((fc.ln() - (fc.ln() + sum) / (c / m).exp() - self.cnst) / (k0 / m)) - (t - self.pt);
227            let g2 = ((self.f_ / m).exp() - 1.0) / (fc * k0 / m);
228            let diff = -g1 / g2;
229            self.f_ += diff;
230            if diff.abs() < TOLERANCE { break; }
231            i += 1;
232        }
233        if i == MAXITER { return -9999.0; }
234
235        if self.f_ - self.cumf < r * dt {
236            let f = (self.f_ - self.cumf) / dt;
237            self.cumf = self.f_;
238            self.f_ += f * dt;
239            f
240        } else {
241            let f = r;
242            self.cumf += f * dt;
243            self.ponding = false;
244            self.pt = 0.0;
245            f
246        }
247    }
248}
249
250impl Infiltration {
251    fn compute_cnst(f_: f64, c: f64, m: f64) -> f64 {
252        const NTERMS: usize = 10;
253        let fc = f_ + c;
254        let mut cnst = 0.0;
255        let mut factorial = 1.0;
256        for j in 1..=NTERMS {
257            factorial *= j as f64;
258            cnst += (fc / m).powi(j as i32) / (j as f64 * factorial);
259        }
260        fc.ln() - (fc.ln() + cnst) / (c / m).exp()
261    }
262}
263
264// ── 派生量:λ(地形指数面平均)+ Ad(河道延迟面积分布)──
265
266/// λ = areal integral of ln(a/tanβ) = Σ Aatb_r[i]·(atb[i]+atb[i-1])/2(对应 get_lambda)。
267fn get_lambda(atb: &[f64], aatb_r: &[f64]) -> f64 {
268    let n = atb.len().min(aatb_r.len());
269    let mut ret = 0.0;
270    for i in 1..n {
271        ret += aatb_r[i] * (atb[i] + atb[i - 1]) / 2.0;
272    }
273    ret
274}
275
276/// 河道延迟 → (tch, ndelay, nreach, Ad)。对应 get_Ad。
277fn compute_ad(d: &[f64], ad_r: &[f64], vch_dt: f64, vr_dt: f64) -> (Vec<f64>, usize, usize, Vec<f64>) {
278    let nch = d.len().min(ad_r.len());
279    if nch == 0 {
280        return (Vec::new(), 0, 0, Vec::new());
281    }
282    let mut tch = vec![0.0; nch];
283    tch[0] = d[0] / vch_dt;
284    for i in 1..nch {
285        tch[i] = tch[0] + (d[i] - d[0]) / vr_dt;
286    }
287    let mut nreach = tch[nch - 1] as usize;
288    if (nreach as f64) < tch[nch - 1] { nreach += 1; }
289    let ndelay = tch[0] as usize;
290    nreach = nreach.saturating_sub(ndelay);
291    if nreach == 0 {
292        return (tch, ndelay, 0, Vec::new());
293    }
294    let mut ad = vec![0.0; nreach];
295    for i in 0..nreach {
296        let t = (ndelay + i + 1) as f64;
297        if t > tch[nch - 1] {
298            ad[i] = 1.0;
299        } else {
300            for j in 1..nch {
301                if t <= tch[j] {
302                    ad[i] = ad_r[j - 1] + (ad_r[j] - ad_r[j - 1]) * (t - tch[j - 1]) / (tch[j] - tch[j - 1]);
303                    break;
304                }
305            }
306        }
307    }
308    // 差分:累积 → 单步
309    let mut a1 = ad[0];
310    for i in 1..nreach {
311        let a2 = ad[i];
312        ad[i] = a2 - a1;
313        a1 = a2;
314    }
315    (tch, ndelay, nreach, ad)
316}
317
318// ── 派生状态(对应 param_init)──
319
320struct Derived {
321    lambda: f64,
322    qss: f64,
323    ndelay: usize,
324    nreach: usize,
325    ad: Vec<f64>,
326    nidxclass: usize,
327    atb: Vec<f64>,
328    aatb_r: Vec<f64>,
329}
330
331impl Derived {
332    fn from_params(p: &TopmodelParams) -> Self {
333        let (atb, aatb_r) = match &p.topidx {
334            Some(h) if !h.atb.is_empty() => (h.atb.clone(), h.Aatb_r.clone()),
335            _ => (vec![0.0], vec![1.0]),
336        };
337        let nidxclass = atb.len();
338        let lambda = get_lambda(&atb, &aatb_r);
339        let ln_te_dt = p.lnTe + p.dt.ln();
340        let qss = (ln_te_dt - lambda).exp();
341        let (ndelay, nreach, ad) = match &p.channel {
342            Some(ch) if !ch.d.is_empty() => {
343                let vch_dt = p.vch * p.dt;
344                let vr_dt = p.vr * p.dt;
345                let (_tch, nd, nr, ad) = compute_ad(&ch.d, &ch.Ad_r, vch_dt, vr_dt);
346                (nd, nr, ad)
347            }
348            _ => (0, 0, Vec::new()),
349        };
350        Self { lambda, qss, ndelay, nreach, ad, nidxclass, atb, aatb_r }
351    }
352}
353
354// ── 忠实批量核心:run_topmodel_full(供 oracle 比对)──
355
356/// 完整运行 TOPMODEL(逐时步循环,对应 R 的 run_topmodel 调用序列)。
357/// 输入:参数 + 雨量(mm/时步)+ ETp(mm/时步);输出:全序列(mm)。
358pub fn run_topmodel_full(p: &TopmodelParams, rain: &[f64], etp: &[f64]) -> TopmodelOutput {
359    let n = rain.len().max(etp.len());
360    let d = Derived::from_params(p);
361    let qs0_dt = p.qs0 * p.dt;
362
363    let mut out = TopmodelOutput {
364        Qt: vec![0.0; n], qs: vec![0.0; n], qo: vec![0.0; n],
365        S_mean: vec![0.0; n], f: vec![0.0; n], fex: vec![0.0; n], Ea: vec![0.0; n],
366    };
367    let mut srz = vec![p.Sr0; d.nidxclass]; // 根区亏缺
368    let mut suz = vec![0.0; d.nidxclass];   // 非饱和蓄水
369    let mut infl = Infiltration::new();
370
371    // S_mean[0] = -m·log(qs0_dt/qss)(对应 param_init)
372    out.S_mean[0] = if d.qss > 0.0 && qs0_dt > 0.0 {
373        -p.m * (qs0_dt / d.qss).ln()
374    } else { 0.0 };
375
376    // Qt 初值(对应 param_init):Qt[0..ndelay]=qs0;reach 段退水 qs0·(1-ΣAd)
377    for i in 0..n.min(d.ndelay) {
378        out.Qt[i] = qs0_dt;
379    }
380    {
381        let mut a = 0.0;
382        for i in 0..d.nreach {
383            a += d.ad[i];
384            let k = d.ndelay + i;
385            if k < n { out.Qt[k] = qs0_dt * (1.0 - a); }
386        }
387    }
388
389    for i in 0..n {
390        let r = rain.get(i).copied().unwrap_or(0.0);
391        let e = etp.get(i).copied().unwrap_or(0.0);
392
393        // 下渗 + 超渗
394        let t = (i as f64 + 1.0) * p.dt;
395        let mut fi = p.dt * infl.get_f(t, r / p.dt, p.CD, p.K0, p.m, p.dt);
396        if fi < 0.0 { fi = r; }
397        out.f[i] = fi;
398        out.fex[i] = r - fi;
399
400        // 基流
401        out.qs[i] = d.qss * (-out.S_mean[i] / p.m).exp();
402
403        let mut qo_total = 0.0;
404        let mut qv_total = 0.0;
405        let mut ea_total = 0.0;
406        let mut ex_prev = 0.0;
407
408        for j in 0..d.nidxclass {
409            let aatb_local = (d.aatb_r[j]
410                + if j < d.nidxclass - 1 { d.aatb_r[j + 1] } else { 0.0 }) / 2.0;
411
412            // 局部亏缺 eq 18.8
413            let mut s = out.S_mean[i] + p.m * (d.lambda - d.atb[j]);
414            if s < 0.0 { s = 0.0; }
415
416            // 根区:雨进根区(亏减少),溢出补非饱和
417            srz[j] -= fi;
418            if srz[j] < 0.0 {
419                suz[j] -= srz[j];
420                srz[j] = 0.0;
421            }
422
423            // 饱和溢流 ex(Suz 超过 S)
424            let mut ex = 0.0;
425            if suz[j] > s {
426                ex = suz[j] - s;
427                suz[j] = s;
428            }
429
430            // 非饱和排水 eq 6.26
431            let mut qv = 0.0;
432            if s > 0.0 {
433                qv = suz[j] / (s * p.td) * p.dt;
434                if qv > suz[j] { qv = suz[j]; }
435                suz[j] -= qv;
436                if suz[j] < ZERO { suz[j] = 0.0; }
437                qv *= aatb_local;
438            }
439            qv_total += qv;
440
441            // ET eq 6.27(从根区抽取,亏缺增加)
442            let mut ea = 0.0;
443            if e > 0.0 {
444                ea = e * (1.0 - srz[j] / p.Srmax);
445                if ea > p.Srmax - srz[j] { ea = p.Srmax - srz[j]; }
446            }
447            srz[j] += ea;
448            ea_total += aatb_local * ea;
449
450            // 饱和面产流 qo(相邻类 ex;分支1用原始 Aatb_r[j],分支2用 local)
451            if j > 0 {
452                let qo = if ex > 0.0 {
453                    d.aatb_r[j] * (ex_prev + ex) / 2.0
454                } else if ex_prev > 0.0 {
455                    aatb_local * ex_prev / (ex_prev - ex) * ex_prev / 2.0
456                } else { 0.0 };
457                qo_total += qo;
458            }
459            ex_prev = ex;
460        }
461
462        out.qo[i] = qo_total + out.fex[i];
463        let qt = out.qo[i] + out.qs[i];
464
465        // S_mean 更新(qs 出、qv 入)
466        out.S_mean[i] += out.qs[i] - qv_total;
467        if i + 1 < n { out.S_mean[i + 1] = out.S_mean[i]; }
468        out.Ea[i] = ea_total;
469
470        // 路由:Qt[k] += qt·Ad[j]
471        if d.nreach > 0 {
472            for j in 0..d.nreach {
473                let k = i + j + d.ndelay;
474                if k > n - 1 { break; }
475                out.Qt[k] += qt * d.ad[j];
476            }
477        } else {
478            // 无河道延迟:即时出流
479            out.Qt[i] += qt;
480        }
481    }
482    out
483}
484
485
486// ── HydroModel trait 包装(平台集成;逐时步)──
487
488pub struct TopmodelModel {
489    params: TopmodelParams,
490    derived: Derived,
491    srz: Vec<f64>,
492    suz: Vec<f64>,
493    infl: Infiltration,
494    s_mean: f64,
495    qt_buf: Vec<f64>, // 路由缓冲(Qt[i])
496    step_i: usize,
497    discharge_m3s: f64,
498}
499
500fn mm_to_m3s(mm: f64, area_km2: f64, dt_h: f64) -> f64 {
501    mm * area_km2 / (dt_h * 3.6)
502}
503
504impl HydroModel for TopmodelModel {
505    type Params = TopmodelParams;
506
507    fn new(params: Self::Params) -> Self {
508        let derived = Derived::from_params(&params);
509        let qs0_dt = params.qs0 * params.dt;
510        let s_mean0 = if derived.qss > 0.0 && qs0_dt > 0.0 {
511            -params.m * (qs0_dt / derived.qss).ln()
512        } else { 0.0 };
513        Self {
514            srz: vec![params.Sr0; derived.nidxclass],
515            suz: vec![0.0; derived.nidxclass],
516            infl: Infiltration::new(),
517            s_mean: s_mean0,
518            derived,
519            params,
520            qt_buf: Vec::new(),
521            step_i: 0,
522            discharge_m3s: 0.0,
523        }
524    }
525
526    fn step(&mut self, f: &Forcing, dt_h: f64) {
527        let p = &self.params;
528        let i = self.step_i;
529        let r = f.p_mm.max(0.0);
530        let e = f.pet_mm.max(0.0);
531        let nidx = self.derived.nidxclass;
532
533        let t = (i as f64 + 1.0) * p.dt;
534        let mut fi = p.dt * self.infl.get_f(t, r / p.dt, p.CD, p.K0, p.m, p.dt);
535        if fi < 0.0 { fi = r; }
536        let fex = r - fi;
537
538        let qs = self.derived.qss * (-self.s_mean / p.m).exp();
539
540        let mut qo_total = 0.0;
541        let mut qv_total = 0.0;
542        let mut ea_total = 0.0;
543        let mut ex_prev = 0.0;
544
545        for j in 0..nidx {
546            let aatb_local = (self.derived.aatb_r[j]
547                + if j < nidx - 1 { self.derived.aatb_r[j + 1] } else { 0.0 }) / 2.0;
548            let mut s = self.s_mean + p.m * (self.derived.lambda - self.derived.atb[j]);
549            if s < 0.0 { s = 0.0; }
550
551            self.srz[j] -= fi;
552            if self.srz[j] < 0.0 {
553                self.suz[j] -= self.srz[j];
554                self.srz[j] = 0.0;
555            }
556            let mut ex = 0.0;
557            if self.suz[j] > s {
558                ex = self.suz[j] - s;
559                self.suz[j] = s;
560            }
561            let mut qv = 0.0;
562            if s > 0.0 {
563                qv = self.suz[j] / (s * p.td) * p.dt;
564                if qv > self.suz[j] { qv = self.suz[j]; }
565                self.suz[j] -= qv;
566                if self.suz[j] < ZERO { self.suz[j] = 0.0; }
567                qv *= aatb_local;
568            }
569            qv_total += qv;
570
571            let mut ea = 0.0;
572            if e > 0.0 {
573                ea = e * (1.0 - self.srz[j] / p.Srmax);
574                if ea > p.Srmax - self.srz[j] { ea = p.Srmax - self.srz[j]; }
575            }
576            self.srz[j] += ea;
577            ea_total += aatb_local * ea;
578
579            // 饱和面产流 qo(相邻类 ex)
580            if j > 0 {
581                let qo = if ex > 0.0 {
582                    self.derived.aatb_r[j] * (ex_prev + ex) / 2.0
583                } else if ex_prev > 0.0 {
584                    aatb_local * ex_prev / (ex_prev - ex) * ex_prev / 2.0
585                } else { 0.0 };
586                qo_total += qo;
587            }
588            ex_prev = ex;
589        }
590
591        qo_total += fex;
592        let qt = qo_total + qs;
593        self.s_mean += qs - qv_total;
594
595        // 路由到 Qt 缓冲
596        if self.derived.nreach > 0 {
597            for j in 0..self.derived.nreach {
598                let k = i + j + self.derived.ndelay;
599                if k >= self.qt_buf.len() { self.qt_buf.resize(k + 1, 0.0); }
600                self.qt_buf[k] += qt * self.derived.ad[j];
601            }
602        } else {
603            if i >= self.qt_buf.len() { self.qt_buf.resize(i + 1, 0.0); }
604            self.qt_buf[i] += qt;
605        }
606
607        let qt_i = self.qt_buf.get(i).copied().unwrap_or(0.0);
608        self.discharge_m3s = mm_to_m3s(qt_i, p.area_km2, dt_h);
609        self.step_i += 1;
610    }
611
612    fn discharge(&self) -> f64 { self.discharge_m3s }
613    fn state(&self) -> serde_json::Value {
614        serde_json::json!({ "S_mean": self.s_mean, "step": self.step_i })
615    }
616    fn reset(&mut self) {
617        let p = self.params.clone();
618        *self = Self::new(p);
619    }
620    fn name(&self) -> &'static str { "TOPMODEL" }
621    fn params(&self) -> &Self::Params { &self.params }
622    fn params_mut(&mut self) -> &mut Self::Params { &mut self.params }
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628
629    #[test]
630    fn lambda_single_class() {
631        // 单类 atb=[0], Aatb_r=[1] → λ=0(get_lambda 从 i=1 起,无项)
632        assert!(get_lambda(&[0.0], &[1.0]).abs() < 1e-12);
633    }
634
635    #[test]
636    fn lambda_two_class() {
637        // atb=[2,0], Aatb_r=[0.3,1.0] → λ = Aatb_r[1]*(atb[1]+atb[0])/2 = 1.0*(0+2)/2 = 1.0
638        let l = get_lambda(&[2.0, 0.0], &[0.3, 1.0]);
639        assert!((l - 1.0).abs() < 1e-12, "λ={}", l);
640    }
641
642    #[test]
643    fn rain_produces_flow() {
644        let mut p = TopmodelParams::default();
645        p.m = 0.01; p.lnTe = (5.0_f64).ln(); p.qs0 = 0.01; p.Srmax = 0.05; p.Sr0 = 0.0;
646        p.K0 = 0.0; p.CD = 0.0; p.dt = 1.0;
647        let mut m = TopmodelModel::new(p);
648        for _ in 0..10 { m.step(&Forcing { p_mm: 30.0, pet_mm: 0.0, t_c: 20.0 }, 1.0); }
649        assert!(m.discharge() > 0.0, "rain should produce flow: {}", m.discharge());
650    }
651
652    #[test]
653    fn no_mass_creation_constant_rain() {
654        let params = TopmodelParams::default();
655        let area = params.area_km2;
656        let mut m = TopmodelModel::new(params);
657        let n = 200;
658        let mut sum_q_mm = 0.0;
659        for _ in 0..n {
660            m.step(&Forcing { p_mm: 5.0, pet_mm: 0.0, t_c: 20.0 }, 1.0);
661            sum_q_mm += m.discharge() * 1.0 * 3.6 / area;
662        }
663        let sum_rain = 5.0 * n as f64;
664        assert!(sum_q_mm <= sum_rain * 1.05, "出流>降雨(质量凭空):{:.1}>{:.1}", sum_q_mm, sum_rain);
665    }
666
667    #[test]
668    fn dyn_dispatch() {
669        let mut m: Box<dyn hydro_core::DynHydroModel> = Box::new(TopmodelModel::new(TopmodelParams::default()));
670        m.step(&Forcing { p_mm: 50.0, pet_mm: 1.0, t_c: 20.0 }, 1.0);
671        assert!(m.discharge() >= 0.0);
672        assert_eq!(m.name(), "TOPMODEL");
673    }
674}