-
Notifications
You must be signed in to change notification settings - Fork 0
/
mhm.py
151 lines (125 loc) · 5.67 KB
/
mhm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import numpy as np
import pandas as pd
from typing import Union, List, Tuple, Dict
from .basemodel import Reservoir
class mHM(Reservoir):
"""Representation of reservoir routing in the Mesoscale Hydrological Model (mHM) as explained in Shrestha et al (2024)"""
def __init__(self,
Vmin: float,
Vtot: float,
Qmin: float,
avg_inflow: float,
avg_demand: float,
w: float = 0.1, # Shin et al. (2019)
alpha: float = 0.5, # called c* in Shrestha et al. (2024). Default value from Hanasaki et al. (2006)
beta: float = 1, # Shin et al. (2019)
gamma: float = 0.85, # Shin et al. (2019)
lambda_: float = 1,
At: int = 86400):
"""
Parameters:
-----------
Vmin: float
Volume (m3) associated to the conservative storage
Vtot: float
Total reservoir storage capacity (m3)
Qmin: float
Minimum outflow (m3/s)
avg_inflow: float
Average reservoir inflow (m3/s)
avg_demand: float
Average demand (m3/s)
w: float
Dimensionless parameter that controls the demand hedging
alpha: float
Dimensionless parameter that is a threshold that defines reservoirs whose releases are only based on demand (degree of regulation greater than alpha), or a combination of demand and inflow (otherwise)
beta: float
Dimensionless parameter that indirectly controls the proportion of inflow and demand in the releases
gamma: float
Dimensionless parameter that defines the normal storage: Vn = gamma * Vtot
lambda_: float
Dimensionless parameter that further controls the hedging in relation to the current reservoir filling
At: int
Simulation time step in seconds.
"""
assert 0 <= w <= 1, 'ERROR. Parameter "w" must be a value between 0 and 1'
assert 0 <= alpha, 'ERROR. Parameter "alpha" (degree of regulation) must be positive'
assert 0 <= gamma <= 1, 'ERROR. Parameter "gamma" must be a value between 0 and 1, as it represents the normal reservoir filling'
# make sure that Vmin is not smaller than Vn
Vmin = min(Vmin, gamma * Vtot)
super().__init__(Vmin, Vtot, Qmin, Qf=None, At=At)
# demand and degree of regulation
self.avg_inflow = avg_inflow
self.avg_demand = avg_demand
self.dor = Vtot / (avg_inflow * 365 *24 * 3600) # called c in Shrestha et al. (2024)
# reservoir parameters
self.w = w
self.alpha = alpha
self.beta = beta
self.gamma = gamma
self.lambda_ = lambda_
# normal storage
self.Vn = gamma * Vtot
# partition coefficient betweee demand-controlled (rho == 1) and non-demand-controlled reservoirs
self.rho = min(1, (self.dor / alpha)**beta)
def timestep(self,
I: float,
V: float,
D: float = 0.0
) -> List[float]:
"""Given an inflow and an initial storage values, it computes the corresponding outflow
Parameters:
-----------
I: float
Inflow (m3/s)
V: float
Volume stored in the reservoir (m3)
D: float
Water demand (m3). It defaults to zero
Returns:
--------
Q, V: List[float]
Outflow (m3/s) and updated storage (m3)
"""
eps = 1e-3
# hedged demand
exploitation = self.avg_demand / self.avg_inflow
if exploitation >= 1 - self.w:
hedged_demand = self.w * self.avg_inflow + (1 - self.w) * D / exploitation
else:
hedged_demand = D + self.avg_inflow - self.avg_demand
# outflow
kappa = (V / self.Vn)**self.lambda_
Q = self.rho * kappa * hedged_demand + (1 - self.rho) * I
# update reservoir storage with the inflow volume
V += I * self.At
# ouflow depending on the minimum outflow and storage level
Q = max(self.Qmin, Q)
if V - Q * self.At > self.Vtot:
Q = (V - self.Vtot) / self.At + eps
elif V - Q * self.At < self.Vmin:
Q = (V - self.Vmin) / self.At - eps
Q = max(0, Q)
# # ouflow depending on the inflow and storage level
# Qmin = min(self.Qmin, (V - self.Vmin) / self.At - eps)
# Qmax = max(Q, (V - self.Vtot) / self.At + eps)
# update reservoir storage with the inflow volume
V -= Q * self.At
assert 0 <= V, f'The volume at the end of the timestep is negative: {V:.0f} m3'
assert V <= self.Vtot, f'The volume at the end of the timestep is larger than the total reservoir capacity: {V:.0f} m3 > {self.Vtot:.0f} m3'
assert 0 <= Q, f'The simulated outflow is negative: {Q:.6f} m3/s'
return Q, V
def get_params(self):
"""It generates a dictionary with the reservoir paramenters in the model."""
params = {'Vmin': self.Vmin,
'Vn': self.Vn,
'Vtot': self.Vtot,
'Qmin': self.Qmin,
'w': self.w,
'alpha': self.alpha,
'beta': self.beta,
'gamma': self.gamma,
'lambda': self.lambda_,
'rho': self.rho}
params = {key: float(value) for key, value in params.items()}
return params