1use hydro_core::{Forcing, HydroModel};
16use serde::{Deserialize, Serialize};
17
18const ZERO: f64 = 0.0000001;
19
20#[derive(Clone, Debug, Serialize, Deserialize, Default)]
24pub struct TopidxHistogram {
25 pub atb: Vec<f64>,
26 pub Aatb_r: Vec<f64>,
27}
28
29#[derive(Clone, Debug, Serialize, Deserialize, Default)]
31pub struct ChannelDelay {
32 pub d: Vec<f64>,
33 pub Ad_r: Vec<f64>,
34}
35
36#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct TopmodelParams {
39 pub qs0: f64, pub lnTe: f64, pub m: f64, pub Sr0: f64, pub Srmax: f64, pub td: f64, pub vch: f64, pub vr: f64, pub K0: f64, pub CD: f64, pub dt: f64, #[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 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#[derive(Clone, Debug, Default)]
75pub struct TopmodelOutput {
76 pub Qt: Vec<f64>, pub qs: Vec<f64>, pub qo: Vec<f64>, pub S_mean: Vec<f64>, pub f: Vec<f64>, pub fex: Vec<f64>, pub Ea: Vec<f64>, }
84
85#[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 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 self.cnst = Self::compute_cnst(self.f_, c, m);
130 self.f_ += r * (t - self.pt) / 2.0;
131 } else {
133 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 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 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
264fn 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
276fn 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 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
318struct 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
354pub 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]; let mut suz = vec![0.0; d.nidxclass]; let mut infl = Infiltration::new();
370
371 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 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 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 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 let mut s = out.S_mean[i] + p.m * (d.lambda - d.atb[j]);
414 if s < 0.0 { s = 0.0; }
415
416 srz[j] -= fi;
418 if srz[j] < 0.0 {
419 suz[j] -= srz[j];
420 srz[j] = 0.0;
421 }
422
423 let mut ex = 0.0;
425 if suz[j] > s {
426 ex = suz[j] - s;
427 suz[j] = s;
428 }
429
430 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 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 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 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 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 out.Qt[i] += qt;
480 }
481 }
482 out
483}
484
485
486pub 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>, 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(¶ms);
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 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 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 assert!(get_lambda(&[0.0], &[1.0]).abs() < 1e-12);
633 }
634
635 #[test]
636 fn lambda_two_class() {
637 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}