@@ -190,7 +190,15 @@ def __init__(
190
190
assert (Q_initial [self .waterbody_id != - 1 ] == 0 ).all ()
191
191
self .Q_prev = Q_initial
192
192
193
+ def get_total_storage (self ):
194
+ """
195
+ Get the total storage of the river network, which is the sum of the
196
+ available storage in each cell.
197
+ """
198
+ return self .get_available_storage (maximum_abstraction_ratio = 1.0 )
193
199
200
+
201
+ @njit (cache = True )
194
202
def update_node_kinematic (
195
203
Qin , Qold , q , alpha , beta , deltaT , deltaX , epsilon = np .float64 (0.0001 )
196
204
):
@@ -265,7 +273,25 @@ def calculate_discharge_from_storage(
265
273
# The momentum equation (solved for Q), see eq. 18 in https://gmd.copernicus.org/articles/13/3267/2020/
266
274
return (river_storage / (river_length * river_alpha )) ** (1 / river_beta )
267
275
276
+ def get_available_storage (self , maximum_abstraction_ratio = 0.9 ):
277
+ """
278
+ Get the available storage of the river network, which is the sum of the
279
+ available storage in each cell.
280
+ """
281
+ assert not np .isnan (self .Q_prev ).any ()
282
+ assert (self .Q_prev >= 0.0 ).all ()
283
+
284
+ river_storage = self .calculate_river_storage_from_discharge (
285
+ discharge = self .Q_prev ,
286
+ river_alpha = self .river_alpha ,
287
+ river_length = self .river_length ,
288
+ river_beta = self .river_beta ,
289
+ waterbody_id = self .waterbody_id ,
290
+ )
291
+ return river_storage * maximum_abstraction_ratio
292
+
268
293
@staticmethod
294
+ @njit (cache = True )
269
295
def _step (
270
296
Qold ,
271
297
sideflow_m3 ,
@@ -290,14 +316,15 @@ def _step(
290
316
river_length: np.ndarray
291
317
Array of floats containing the channel length, must be > 0
292
318
"""
293
- Qkx = np .full_like (Qold , np .nan )
319
+ Qnew = np .full_like (Qold , np .nan )
320
+ over_abstraction_m3 = np .zeros_like (Qold , dtype = np .float32 )
294
321
295
322
for i in range (upstream_matrix_from_up_to_downstream .shape [0 ]):
296
323
node = idxs_up_to_downstream [i ]
297
324
upstream_nodes = upstream_matrix_from_up_to_downstream [i ]
298
325
299
326
Qin = np .float32 (0.0 )
300
- sideflow_node = sideflow_m3 [ node ] / dt / river_length [node ]
327
+ sideflow_node_m3 = sideflow_m3 [node ]
301
328
302
329
for upstream_node in upstream_nodes :
303
330
if upstream_node == - 1 :
@@ -306,46 +333,50 @@ def _step(
306
333
if is_waterbody_outflow [upstream_node ]:
307
334
# if upstream node is an outflow add the outflow of the waterbody
308
335
# to the sideflow
309
- node_waterbody_id = waterbody_id [upstream_node ]
336
+ upstream_node_waterbody_id = waterbody_id [upstream_node ]
310
337
311
338
# make sure that the waterbody ID is valid
312
- assert node_waterbody_id != - 1
313
- waterbody_outflow_m3 = outflow_per_waterbody_m3 [node_waterbody_id ]
339
+ assert upstream_node_waterbody_id != - 1
340
+ waterbody_outflow_m3 = outflow_per_waterbody_m3 [
341
+ upstream_node_waterbody_id
342
+ ]
314
343
315
- waterbody_storage_m3 [node_waterbody_id ] -= waterbody_outflow_m3
344
+ waterbody_storage_m3 [upstream_node_waterbody_id ] -= (
345
+ waterbody_outflow_m3
346
+ )
316
347
317
348
# make sure that the waterbody storage does not go below 0
318
- assert waterbody_storage_m3 [node_waterbody_id ] >= 0
349
+ assert waterbody_storage_m3 [upstream_node_waterbody_id ] >= 0
319
350
320
- sideflow_node += waterbody_outflow_m3 / dt / river_length [ node ]
351
+ sideflow_node_m3 += waterbody_outflow_m3
321
352
322
353
elif (
323
354
waterbody_id [upstream_node ] != - 1
324
355
): # if upstream node is a waterbody, but not an outflow
325
356
assert sideflow_m3 [upstream_node ] == 0
326
357
327
358
else : # in normal case, just take the inflow from upstream
328
- assert not np .isnan (Qkx [upstream_node ])
329
- Qin += Qkx [upstream_node ]
359
+ assert not np .isnan (Qnew [upstream_node ])
360
+ Qin += Qnew [upstream_node ]
330
361
331
- Qkx [node ] = update_node_kinematic (
362
+ Qnew [node ] = update_node_kinematic (
332
363
Qin ,
333
364
Qold [node ],
334
- sideflow_node ,
365
+ sideflow_node_m3 / dt / river_length [ node ] ,
335
366
river_alpha [node ],
336
367
river_beta ,
337
368
dt ,
338
369
river_length [node ],
339
370
)
340
- return Qkx
371
+ return Qnew , over_abstraction_m3
341
372
342
373
def step (
343
374
self ,
344
375
sideflow_m3 ,
345
376
waterbody_storage_m3 ,
346
377
outflow_per_waterbody_m3 ,
347
378
):
348
- Q = self ._step (
379
+ Q , over_abstraction_m3 = self ._step (
349
380
Qold = self .Q_prev ,
350
381
sideflow_m3 = sideflow_m3 ,
351
382
waterbody_storage_m3 = waterbody_storage_m3 ,
@@ -364,7 +395,7 @@ def step(
364
395
365
396
outflow_at_pits_m3 = (Q [self .is_pit ] * self .dt ).sum ()
366
397
367
- return Q , waterbody_storage_m3 , outflow_at_pits_m3
398
+ return Q , over_abstraction_m3 , waterbody_storage_m3 , outflow_at_pits_m3
368
399
369
400
370
401
class Accuflux (Router ):
@@ -376,13 +407,6 @@ def get_available_storage(self, maximum_abstraction_ratio=0.9):
376
407
assert (self .Q_prev >= 0.0 ).all ()
377
408
return self .Q_prev * self .dt * maximum_abstraction_ratio
378
409
379
- def get_total_storage (self ):
380
- """
381
- Get the total storage of the river network, which is the sum of the
382
- available storage in each cell.
383
- """
384
- return self .get_available_storage (maximum_abstraction_ratio = 1.0 )
385
-
386
410
@staticmethod
387
411
@njit (cache = True )
388
412
def _step (
@@ -398,6 +422,7 @@ def _step(
398
422
):
399
423
Qold += sideflow_m3 / dt
400
424
Qnew = np .full_like (Qold , 0.0 )
425
+ over_abstraction_m3 = np .zeros_like (Qold , dtype = np .float32 )
401
426
for i in range (upstream_matrix_from_up_to_downstream .shape [0 ]):
402
427
node = idxs_up_to_downstream [i ]
403
428
upstream_nodes = upstream_matrix_from_up_to_downstream [i ]
@@ -440,9 +465,14 @@ def _step(
440
465
if node_waterbody_id != - 1 :
441
466
waterbody_storage_m3 [node_waterbody_id ] += inflow_volume
442
467
else :
443
- Qnew [node ] = inflow_volume / dt
468
+ Qnew_node = inflow_volume / dt
469
+ if Qnew_node < 0.0 :
470
+ # if the new discharge is negative, we have over-abstraction
471
+ over_abstraction_m3 [node ] = - Qnew_node * dt
472
+ Qnew_node = 0.0
473
+ Qnew [node ] = Qnew_node
444
474
assert Qnew [node ] >= 0.0 , "Discharge cannot be negative"
445
- return Qnew
475
+ return Qnew , over_abstraction_m3
446
476
447
477
def step (
448
478
self ,
@@ -453,7 +483,7 @@ def step(
453
483
outflow_at_pits_m3 = (
454
484
self .get_total_storage ()[self .is_pit ].sum () + sideflow_m3 [self .is_pit ].sum ()
455
485
)
456
- Q = self ._step (
486
+ Q , over_abstraction_m3 = self ._step (
457
487
dt = self .dt ,
458
488
Qold = self .Q_prev ,
459
489
sideflow_m3 = sideflow_m3 ,
@@ -465,7 +495,7 @@ def step(
465
495
waterbody_id = self .waterbody_id ,
466
496
)
467
497
self .Q_prev = Q
468
- return Q , waterbody_storage_m3 , outflow_at_pits_m3
498
+ return Q , over_abstraction_m3 , waterbody_storage_m3 , outflow_at_pits_m3
469
499
470
500
471
501
class Routing (Module ):
@@ -539,12 +569,31 @@ def __init__(self, model, hydrology):
539
569
/ np .sqrt (river_slope )
540
570
) ** self .var .river_beta
541
571
542
- self .router = Accuflux (
543
- dt = self .var .routing_step_length_seconds ,
544
- ldd = ldd ,
545
- mask = ~ self .grid .mask ,
546
- Q_initial = self .grid .var .discharge_m3_s ,
547
- )
572
+ self .routing_algorithm = "kinematic_wave"
573
+
574
+ if self .routing_algorithm == "kinematic_wave" :
575
+ self .router = KinematicWave (
576
+ dt = self .var .routing_step_length_seconds ,
577
+ ldd = ldd ,
578
+ mask = ~ self .grid .mask ,
579
+ Q_initial = self .grid .var .discharge_m3_s ,
580
+ river_width = self .grid .var .river_width ,
581
+ river_length = self .grid .var .river_length ,
582
+ river_alpha = self .grid .var .river_alpha ,
583
+ river_beta = self .var .river_beta ,
584
+ )
585
+ elif self .routing_algorithm == "accuflux" :
586
+ self .router = Accuflux (
587
+ dt = self .var .routing_step_length_seconds ,
588
+ ldd = ldd ,
589
+ mask = ~ self .grid .mask ,
590
+ Q_initial = self .grid .var .discharge_m3_s ,
591
+ )
592
+ else :
593
+ raise ValueError (
594
+ f"Unknown routing algorithm: { self .routing_algorithm } . "
595
+ "Available algorithms are 'kinematic_wave' and 'accuflux'."
596
+ )
548
597
549
598
def spinup (self ):
550
599
# Initialize discharge with zero
@@ -557,9 +606,15 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
557
606
pre_storage = self .hydrology .lakes_reservoirs .var .storage .copy ()
558
607
pre_river_storage_m3 = self .router .get_total_storage ()
559
608
560
- self .router .Q_prev = (
561
- self .router .Q_prev
562
- - channel_abstraction_m3 / self .var .routing_step_length_seconds
609
+ channel_abstraction_m3_per_routing_step = (
610
+ channel_abstraction_m3 / self .var .n_routing_substeps
611
+ )
612
+ assert (
613
+ channel_abstraction_m3_per_routing_step [self .grid .var .waterBodyID != - 1 ]
614
+ == 0.0
615
+ ).all (), (
616
+ "Channel abstraction must be zero for water bodies, "
617
+ "but found non-zero value."
563
618
)
564
619
565
620
return_flow_m3_per_routing_step = (
@@ -618,6 +673,7 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
618
673
evaporation_in_rivers_m3 = 0
619
674
waterbody_evaporation_m3 = 0
620
675
outflow_at_pits_m3 = 0
676
+ over_abstraction_m3 = 0
621
677
622
678
for subrouting_step in range (self .var .n_routing_substeps ):
623
679
self .hydrology .lakes_reservoirs .var .storage += (
@@ -647,16 +703,17 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
647
703
).all (), "outflow cannot be smaller or equal to storage"
648
704
649
705
side_flow_channel_m3_per_routing_step = (
650
- runoff_m3_per_routing_step + return_flow_m3_per_routing_step
706
+ runoff_m3_per_routing_step
707
+ + return_flow_m3_per_routing_step
708
+ - channel_abstraction_m3_per_routing_step
651
709
)
652
710
assert (
653
711
side_flow_channel_m3_per_routing_step [self .grid .var .waterBodyID != - 1 ]
654
712
== 0
655
713
).all ()
656
714
657
715
evaporation_in_rivers_m3_per_routing_step = np .minimum (
658
- self .router .get_available_storage ()
659
- + side_flow_channel_m3_per_routing_step ,
716
+ self .router .get_total_storage () + side_flow_channel_m3_per_routing_step ,
660
717
potential_evaporation_in_rivers_m3_per_routing_step ,
661
718
)
662
719
assert (
@@ -672,6 +729,7 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
672
729
673
730
(
674
731
self .grid .var .discharge_m3_s ,
732
+ over_abstraction_m3_routing_step ,
675
733
self .hydrology .lakes_reservoirs .var .storage ,
676
734
outflow_at_pits_m3_routing_step ,
677
735
) = self .router .step (
@@ -689,6 +747,7 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
689
747
actual_evaporation_from_water_bodies_per_routing_step_m3
690
748
)
691
749
evaporation_in_rivers_m3 += evaporation_in_rivers_m3_per_routing_step
750
+ over_abstraction_m3 += over_abstraction_m3_routing_step
692
751
693
752
assert not np .isnan (self .grid .var .discharge_m3_s ).any ()
694
753
@@ -700,6 +759,7 @@ def step(self, total_runoff, channel_abstraction_m3, return_flow):
700
759
influxes = [
701
760
total_runoff * self .grid .var .cell_area ,
702
761
return_flow * self .grid .var .cell_area ,
762
+ over_abstraction_m3 ,
703
763
],
704
764
outfluxes = [
705
765
channel_abstraction_m3 ,
0 commit comments