HBV-Lab 0.2.3__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/HBV_model.py +40 -21
- hbv_lab-1.1.0/HBV_Lab/__init__.py +36 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/calibration.py +16 -11
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/snow.py +0 -7
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/soil.py +5 -4
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab.egg-info/PKG-INFO +65 -19
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab.egg-info/SOURCES.txt +6 -1
- hbv_lab-1.1.0/HBV_Lab.egg-info/requires.txt +9 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/PKG-INFO +65 -19
- hbv_lab-1.1.0/README.md +95 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/setup.py +6 -1
- hbv_lab-1.1.0/tests/test_hbv_model.py +75 -0
- hbv_lab-1.1.0/tests/test_hbv_step.py +18 -0
- hbv_lab-1.1.0/tests/test_response.py +23 -0
- hbv_lab-1.1.0/tests/test_snow.py +38 -0
- hbv_lab-1.1.0/tests/test_soil.py +28 -0
- hbv_lab-0.2.3/HBV_Lab/__init__.py +0 -5
- hbv_lab-0.2.3/HBV_Lab.egg-info/requires.txt +0 -5
- hbv_lab-0.2.3/README.md +0 -53
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/hbv_step.py +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/response.py +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/routing.py +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab/uncertainty.py +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab.egg-info/dependency_links.txt +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/HBV_Lab.egg-info/top_level.txt +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/LICENSE +0 -0
- {hbv_lab-0.2.3 → hbv_lab-1.1.0}/setup.cfg +0 -0
|
@@ -6,7 +6,7 @@ HBV-like hydrological model. It handles parameter management, data reading, mode
|
|
|
6
6
|
execution, and output visualization.
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
|
-
from
|
|
9
|
+
from HBV_Lab import HBVModel
|
|
10
10
|
model = HBVModel()
|
|
11
11
|
model.load_data("path/to/data.csv")
|
|
12
12
|
model.set_parameters(params)
|
|
@@ -24,8 +24,6 @@ import datetime
|
|
|
24
24
|
from types import MethodType
|
|
25
25
|
from .uncertainty import uncertainty
|
|
26
26
|
from .calibration import calibration
|
|
27
|
-
## from calibration import calibrate_hbv_model
|
|
28
|
-
## from uncertainty import evaluate_uncertainty
|
|
29
27
|
from .hbv_step import hbv_step
|
|
30
28
|
from .routing import route_with_maxbas
|
|
31
29
|
|
|
@@ -71,7 +69,7 @@ class HBVModel(uncertainty, calibration):
|
|
|
71
69
|
'lower_storage': 20.0 # Lower zone storage (mm)
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
## Initialize time tracking
|
|
75
73
|
self.start_date = None
|
|
76
74
|
self.end_date = None
|
|
77
75
|
self.time_step = 'D' # Default: daily
|
|
@@ -424,25 +422,42 @@ class HBVModel(uncertainty, calibration):
|
|
|
424
422
|
sim_q_valid = sim_q[valid_idx]
|
|
425
423
|
obs_q_valid = obs_q[valid_idx]
|
|
426
424
|
|
|
427
|
-
# Calculate Nash-Sutcliffe Efficiency (NSE)
|
|
425
|
+
# Calculate Nash-Sutcliffe Efficiency (NSE).
|
|
426
|
+
# NSE is undefined when the observations have zero variance (constant
|
|
427
|
+
# series); guard the division so we return NaN instead of raising a
|
|
428
|
+
# RuntimeWarning / producing +/-inf.
|
|
428
429
|
mean_obs = np.mean(obs_q_valid)
|
|
429
430
|
nse_numerator = np.sum((obs_q_valid - sim_q_valid) ** 2)
|
|
430
431
|
nse_denominator = np.sum((obs_q_valid - mean_obs) ** 2)
|
|
431
|
-
nse = 1 - (nse_numerator / nse_denominator)
|
|
432
|
-
|
|
433
|
-
# Calculate Kling-Gupta Efficiency (KGE)
|
|
432
|
+
nse = 1 - (nse_numerator / nse_denominator) if nse_denominator > 0 else np.nan
|
|
433
|
+
|
|
434
|
+
# Calculate Kling-Gupta Efficiency (KGE).
|
|
435
|
+
# This uses the modified KGE' (Kling et al., 2012) in which the
|
|
436
|
+
# variability ratio is based on the coefficient of variation
|
|
437
|
+
# (CV_sim / CV_obs) rather than the plain standard-deviation ratio.
|
|
434
438
|
mean_sim = np.mean(sim_q_valid)
|
|
435
439
|
std_obs = np.std(obs_q_valid)
|
|
436
440
|
std_sim = np.std(sim_q_valid)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
|
|
442
|
+
# Correlation is undefined if either series is constant.
|
|
443
|
+
if std_obs == 0 or std_sim == 0:
|
|
444
|
+
r = np.nan
|
|
445
|
+
else:
|
|
446
|
+
r = np.corrcoef(obs_q_valid, sim_q_valid)[0, 1]
|
|
447
|
+
|
|
448
|
+
if mean_obs == 0 or mean_sim == 0 or std_obs == 0:
|
|
449
|
+
# KGE components cannot be formed (division by zero); flag as NaN.
|
|
450
|
+
alpha = np.nan
|
|
451
|
+
beta = np.nan
|
|
452
|
+
kge = np.nan
|
|
453
|
+
else:
|
|
454
|
+
alpha = (std_sim / mean_sim) / (std_obs / mean_obs) # variability ratio
|
|
455
|
+
beta = mean_sim / mean_obs # bias ratio
|
|
456
|
+
kge = 1 - np.sqrt((r - 1) ** 2 + (alpha - 1) ** 2 + (beta - 1) ** 2)
|
|
457
|
+
|
|
458
|
+
# Calculate percent bias (undefined if observations sum to zero).
|
|
459
|
+
sum_obs = np.sum(obs_q_valid)
|
|
460
|
+
pbias = 100 * (np.sum(sim_q_valid - obs_q_valid) / sum_obs) if sum_obs != 0 else np.nan
|
|
446
461
|
|
|
447
462
|
# Calculate RMSE and MAE
|
|
448
463
|
rmse = np.sqrt(np.mean((sim_q_valid - obs_q_valid) ** 2))
|
|
@@ -766,23 +781,27 @@ class HBVModel(uncertainty, calibration):
|
|
|
766
781
|
|
|
767
782
|
return None
|
|
768
783
|
|
|
784
|
+
@staticmethod
|
|
769
785
|
def load_model(model_path):
|
|
770
|
-
|
|
771
786
|
"""
|
|
772
787
|
Load a saved HBV model from a file.
|
|
773
|
-
|
|
788
|
+
|
|
789
|
+
Can be called either on the class or on an instance::
|
|
790
|
+
|
|
791
|
+
model = HBVModel.load_model("path/to/model")
|
|
792
|
+
|
|
774
793
|
Parameters:
|
|
775
794
|
-----------
|
|
776
795
|
model_path : str
|
|
777
796
|
Path to the saved model file.
|
|
778
|
-
|
|
797
|
+
|
|
779
798
|
Returns:
|
|
780
799
|
--------
|
|
781
800
|
HBVModel
|
|
782
801
|
The loaded model instance.
|
|
783
802
|
"""
|
|
784
803
|
import pickle
|
|
785
|
-
|
|
804
|
+
|
|
786
805
|
with open(model_path, 'rb') as f:
|
|
787
806
|
model = pickle.load(f)
|
|
788
807
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HBV_Lab — an intuitive, object-oriented Python implementation of a lumped
|
|
3
|
+
conceptual HBV hydrological model for education and research.
|
|
4
|
+
|
|
5
|
+
Typical usage::
|
|
6
|
+
|
|
7
|
+
from HBV_Lab import HBVModel
|
|
8
|
+
model = HBVModel()
|
|
9
|
+
model.load_data(data=df, ...)
|
|
10
|
+
model.run()
|
|
11
|
+
model.calibrate()
|
|
12
|
+
model.evaluate_uncertainty()
|
|
13
|
+
|
|
14
|
+
The individual process routines are also importable for advanced use::
|
|
15
|
+
|
|
16
|
+
from HBV_Lab import snow_routine, soil_routine, response_routine_two_tanks
|
|
17
|
+
from HBV_Lab import hbv_step, route_with_maxbas
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .HBV_model import HBVModel
|
|
21
|
+
from .hbv_step import hbv_step
|
|
22
|
+
from .snow import snow_routine
|
|
23
|
+
from .soil import soil_routine
|
|
24
|
+
from .response import response_routine_two_tanks
|
|
25
|
+
from .routing import route_with_maxbas
|
|
26
|
+
|
|
27
|
+
__version__ = "1.1.0"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"HBVModel",
|
|
31
|
+
"hbv_step",
|
|
32
|
+
"snow_routine",
|
|
33
|
+
"soil_routine",
|
|
34
|
+
"response_routine_two_tanks",
|
|
35
|
+
"route_with_maxbas",
|
|
36
|
+
]
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
class calibration:
|
|
2
2
|
|
|
3
|
-
def calibrate(self, method='
|
|
3
|
+
def calibrate(self, method='Nelder-Mead', objective='NSE', iterations=100,
|
|
4
4
|
verbose=True, plot_results=True):
|
|
5
5
|
"""
|
|
6
6
|
Calibrate an HBV model's parameters to optimize the objective function.
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
Parameters:
|
|
9
9
|
-----------
|
|
10
10
|
self : HBVmodel
|
|
11
11
|
The HBV model instance to calibrate
|
|
12
|
-
method : str, default '
|
|
12
|
+
method : str, default 'Nelder-Mead'
|
|
13
13
|
Optimization method to use (see scipy.optimize.minimize).
|
|
14
|
-
|
|
14
|
+
A gradient-free method is used by default because the HBV objective
|
|
15
|
+
surface is piecewise-constant (many min/max thresholds), which makes
|
|
16
|
+
finite-difference gradients ~0 and causes gradient-based methods such
|
|
17
|
+
as 'SLSQP' or 'L-BFGS-B' to terminate immediately without improving
|
|
18
|
+
on the starting values. Gradient-free options that respect the
|
|
19
|
+
parameter bounds include 'Nelder-Mead' and 'Powell'.
|
|
15
20
|
objective : str, default 'NSE'
|
|
16
21
|
Objective function to maximize. Options are:
|
|
17
22
|
- 'NSE': Nash-Sutcliffe Efficiency (higher is better)
|
|
@@ -103,13 +108,13 @@ class calibration:
|
|
|
103
108
|
self.params = param_dict
|
|
104
109
|
|
|
105
110
|
|
|
106
|
-
# Run the model
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
# Run the model (run() computes performance metrics internally when
|
|
112
|
+
# observed discharge is available, honouring the warmup period)
|
|
113
|
+
self.run(verbose=False)
|
|
114
|
+
|
|
115
|
+
# Ensure performance metrics reflect the current parameter set
|
|
116
|
+
self.calculate_performance_metrics(verbose=False)
|
|
117
|
+
|
|
113
118
|
# Calculate objective function value
|
|
114
119
|
if objective == 'NSE':
|
|
115
120
|
# Nash-Sutcliffe Efficiency (to be maximized)
|
|
@@ -13,8 +13,6 @@ def snow_routine(precipitation, temperature, snowpack, liquid_water, params):
|
|
|
13
13
|
- CFR (float): Refreezing factor (-).
|
|
14
14
|
- CWH (float): Water holding capacity of snowpack (-, e.g., 0.1).
|
|
15
15
|
- SFCF (float): Snowfall correction factor (-).
|
|
16
|
-
- PCF (float): Precipitation correction factor——to account for bias in precipitation (-).
|
|
17
|
-
|
|
18
16
|
|
|
19
17
|
Returns:
|
|
20
18
|
new_snowpack (float): Updated snowpack (mm).
|
|
@@ -27,11 +25,6 @@ def snow_routine(precipitation, temperature, snowpack, liquid_water, params):
|
|
|
27
25
|
CFR = params['CFR']['default']
|
|
28
26
|
CWH = params['CWH']['default']
|
|
29
27
|
SFCF = params['SFCF']['default']
|
|
30
|
-
#PCF = params['PCF']['default']
|
|
31
|
-
|
|
32
|
-
# Give the model a room to account for any biases in the estimation of precipitation
|
|
33
|
-
|
|
34
|
-
precipitation= precipitation
|
|
35
28
|
|
|
36
29
|
# Initialize snowfall and rainfall
|
|
37
30
|
snowfall = 0.0
|
|
@@ -9,8 +9,7 @@ def soil_routine(runoff_from_snow, temperature, potential_et, soil_moisture, par
|
|
|
9
9
|
Parameters:
|
|
10
10
|
-----------
|
|
11
11
|
runoff_from_snow : float
|
|
12
|
-
Incoming liquid
|
|
13
|
-
ater from snow routine (mm/day).
|
|
12
|
+
Incoming liquid water from the snow routine (mm/day).
|
|
14
13
|
temperature : float
|
|
15
14
|
Daily average temperature (°C). (Currently unused but kept for possible extensions).
|
|
16
15
|
potential_et : float
|
|
@@ -27,12 +26,14 @@ def soil_routine(runoff_from_snow, temperature, potential_et, soil_moisture, par
|
|
|
27
26
|
--------
|
|
28
27
|
soil_moisture : float
|
|
29
28
|
Updated soil moisture storage (mm).
|
|
29
|
+
out_to_response : float
|
|
30
|
+
Total water passed to the response routine (recharge + surface runoff) (mm/day).
|
|
30
31
|
recharge : float
|
|
31
32
|
Water recharging to the groundwater (mm/day).
|
|
32
|
-
actual_et : float
|
|
33
|
-
Actual evapotranspiration (mm/day).
|
|
34
33
|
runoff : float
|
|
35
34
|
Surface runoff due to soil overflow (mm/day).
|
|
35
|
+
actual_et : float
|
|
36
|
+
Actual evapotranspiration (mm/day).
|
|
36
37
|
"""
|
|
37
38
|
|
|
38
39
|
FC = params['FC'] ['default'] # Maximum soil moisture capacity
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HBV_Lab
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.
|
|
5
5
|
Home-page: https://github.com/abdallaox/HBV_python_implementation
|
|
6
6
|
Author: Abdalla Mohammed
|
|
@@ -20,6 +20,9 @@ Requires-Dist: pandas
|
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: scipy
|
|
22
22
|
Requires-Dist: tqdm
|
|
23
|
+
Requires-Dist: openpyxl
|
|
24
|
+
Provides-Extra: playground
|
|
25
|
+
Requires-Dist: bokeh>=2.4.0; extra == "playground"
|
|
23
26
|
Dynamic: author
|
|
24
27
|
Dynamic: author-email
|
|
25
28
|
Dynamic: classifier
|
|
@@ -29,6 +32,7 @@ Dynamic: home-page
|
|
|
29
32
|
Dynamic: keywords
|
|
30
33
|
Dynamic: license
|
|
31
34
|
Dynamic: license-file
|
|
35
|
+
Dynamic: provides-extra
|
|
32
36
|
Dynamic: requires-dist
|
|
33
37
|
Dynamic: requires-python
|
|
34
38
|
Dynamic: summary
|
|
@@ -39,14 +43,29 @@ HBV is a simple conceptual hydrological model that simulates the main hydrologic
|
|
|
39
43
|
|
|
40
44
|
I've been experimenting with the model lately and—in an endeavour to better understand the logic behind it—I decided to implement my own version—in Python, following an intuitive object-oriented programming approach.
|
|
41
45
|
|
|
42
|
-
This
|
|
46
|
+
This version implements the snow, soil, response and routing routines—controlled by 14 calibratable parameters as shown below—in addition to calibration and uncertainty analysis modules. See the [documentation for one step of the model.](https://lucid.app/publicSegments/view/a0edb3b6-8eba-4db5-9984-bfd23cc004ef/image.png)
|
|
47
|
+
|
|
48
|
+
Conceptually the parameters belong to four routines:
|
|
49
|
+
|
|
43
50
|
```python
|
|
44
|
-
parameters
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
parameters = {
|
|
52
|
+
'snow': ['TT', 'CFMAX', 'SFCF', 'CFR', 'CWH'],
|
|
53
|
+
'soil': ['FC', 'LP', 'BETA'],
|
|
54
|
+
'response': ['K0', 'K1', 'K2', 'UZL', 'PERC'],
|
|
55
|
+
'routing': ['MAXBAS'],
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
In the model object the parameters are stored in three groups—`snow`, `soil`, and
|
|
60
|
+
`response`—and the routing parameter `MAXBAS` lives inside the `response` group.
|
|
61
|
+
Each parameter is a dict with `min` / `max` / `default` values, so a custom update
|
|
62
|
+
passed to `set_parameters` looks like:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
model.set_parameters({
|
|
66
|
+
'soil': {'FC': {'min': 50, 'max': 500, 'default': 250}},
|
|
67
|
+
'response': {'MAXBAS': {'default': 4}},
|
|
68
|
+
})
|
|
50
69
|
```
|
|
51
70
|
|
|
52
71
|
|
|
@@ -55,29 +74,56 @@ This can be flexibly used for different modelling tasks, but can also be used in
|
|
|
55
74
|
## Get Started
|
|
56
75
|
|
|
57
76
|
### Install the Package
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
From PyPI:
|
|
78
|
+
```bash
|
|
79
|
+
pip install HBV_Lab
|
|
80
|
+
```
|
|
81
|
+
Or, inside a notebook:
|
|
82
|
+
```python
|
|
83
|
+
!pip install HBV_Lab
|
|
84
|
+
```
|
|
85
|
+
To work from the source / latest version:
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/abdallaox/HBV_python_implementation.git
|
|
88
|
+
cd HBV_python_implementation
|
|
89
|
+
pip install -e .
|
|
61
90
|
```
|
|
91
|
+
|
|
62
92
|
### How to Use
|
|
63
93
|
It is very intuitive—you create a model like an object which has attributes (data, parameters, initial conditions, etc.) that you can assign and access. The object also performs functions (calibration, uncertainty estimation, save, load, etc.)
|
|
64
94
|
```python
|
|
95
|
+
import pandas as pd
|
|
65
96
|
from HBV_Lab import HBVModel
|
|
97
|
+
|
|
98
|
+
# 1. Load forcing data (a DataFrame with date, precipitation, temperature,
|
|
99
|
+
# potential ET and—optionally—observed discharge columns)
|
|
100
|
+
df = pd.read_excel("data/test_data_2.xlsx")
|
|
66
101
|
model = HBVModel()
|
|
67
|
-
model.load_data(
|
|
68
|
-
|
|
102
|
+
model.load_data(
|
|
103
|
+
data=df,
|
|
104
|
+
date_column="Date", precip_column="P", temp_column="T",
|
|
105
|
+
pet_column="PET", obs_q_column="Q",
|
|
106
|
+
date_format="%Y%m%d", warmup_end="19811231",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# 2. (Optional) override default parameter ranges/values
|
|
110
|
+
# model.set_parameters({'soil': {'FC': {'default': 250}}})
|
|
111
|
+
|
|
112
|
+
# 3. Run, calibrate and analyse
|
|
69
113
|
model.run()
|
|
70
|
-
model.calibrate()
|
|
71
|
-
model.
|
|
114
|
+
model.calibrate() # gradient-free optimisation by default
|
|
115
|
+
model.evaluate_uncertainty() # Monte-Carlo uncertainty analysis
|
|
72
116
|
model.plot_results()
|
|
73
|
-
|
|
74
|
-
model
|
|
75
|
-
model.
|
|
117
|
+
|
|
118
|
+
# 4. Persist results and the model itself
|
|
119
|
+
model.save_results("results/run.csv")
|
|
120
|
+
model.save_model("models/my_model")
|
|
121
|
+
model = HBVModel.load_model("models/my_model")
|
|
76
122
|
```
|
|
77
123
|
### Tutorial
|
|
78
124
|
Start by following a simple case study in the notebook: [**quick_start_guide.ipynb**](https://github.com/abdallaox/HBV_python_implementation/blob/main/quick_start_guide.ipynb)
|
|
79
125
|
### Play with HBV
|
|
80
|
-
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://
|
|
126
|
+
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://hbvpythonimplementation-production.up.railway.app/HBV_playground)—a playground that uses a model developed with this implementation.
|
|
81
127
|
### References
|
|
82
128
|
|
|
83
129
|
**[1]** Bergström, S., & Forsman, A. (1973). Development of a conceptual deterministic rainfall-runoff model. *Hydrology Research*, *4*, 147-170.
|
|
@@ -14,4 +14,9 @@ HBV_Lab.egg-info/PKG-INFO
|
|
|
14
14
|
HBV_Lab.egg-info/SOURCES.txt
|
|
15
15
|
HBV_Lab.egg-info/dependency_links.txt
|
|
16
16
|
HBV_Lab.egg-info/requires.txt
|
|
17
|
-
HBV_Lab.egg-info/top_level.txt
|
|
17
|
+
HBV_Lab.egg-info/top_level.txt
|
|
18
|
+
tests/test_hbv_model.py
|
|
19
|
+
tests/test_hbv_step.py
|
|
20
|
+
tests/test_response.py
|
|
21
|
+
tests/test_snow.py
|
|
22
|
+
tests/test_soil.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HBV_Lab
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.
|
|
5
5
|
Home-page: https://github.com/abdallaox/HBV_python_implementation
|
|
6
6
|
Author: Abdalla Mohammed
|
|
@@ -20,6 +20,9 @@ Requires-Dist: pandas
|
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: scipy
|
|
22
22
|
Requires-Dist: tqdm
|
|
23
|
+
Requires-Dist: openpyxl
|
|
24
|
+
Provides-Extra: playground
|
|
25
|
+
Requires-Dist: bokeh>=2.4.0; extra == "playground"
|
|
23
26
|
Dynamic: author
|
|
24
27
|
Dynamic: author-email
|
|
25
28
|
Dynamic: classifier
|
|
@@ -29,6 +32,7 @@ Dynamic: home-page
|
|
|
29
32
|
Dynamic: keywords
|
|
30
33
|
Dynamic: license
|
|
31
34
|
Dynamic: license-file
|
|
35
|
+
Dynamic: provides-extra
|
|
32
36
|
Dynamic: requires-dist
|
|
33
37
|
Dynamic: requires-python
|
|
34
38
|
Dynamic: summary
|
|
@@ -39,14 +43,29 @@ HBV is a simple conceptual hydrological model that simulates the main hydrologic
|
|
|
39
43
|
|
|
40
44
|
I've been experimenting with the model lately and—in an endeavour to better understand the logic behind it—I decided to implement my own version—in Python, following an intuitive object-oriented programming approach.
|
|
41
45
|
|
|
42
|
-
This
|
|
46
|
+
This version implements the snow, soil, response and routing routines—controlled by 14 calibratable parameters as shown below—in addition to calibration and uncertainty analysis modules. See the [documentation for one step of the model.](https://lucid.app/publicSegments/view/a0edb3b6-8eba-4db5-9984-bfd23cc004ef/image.png)
|
|
47
|
+
|
|
48
|
+
Conceptually the parameters belong to four routines:
|
|
49
|
+
|
|
43
50
|
```python
|
|
44
|
-
parameters
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
parameters = {
|
|
52
|
+
'snow': ['TT', 'CFMAX', 'SFCF', 'CFR', 'CWH'],
|
|
53
|
+
'soil': ['FC', 'LP', 'BETA'],
|
|
54
|
+
'response': ['K0', 'K1', 'K2', 'UZL', 'PERC'],
|
|
55
|
+
'routing': ['MAXBAS'],
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
In the model object the parameters are stored in three groups—`snow`, `soil`, and
|
|
60
|
+
`response`—and the routing parameter `MAXBAS` lives inside the `response` group.
|
|
61
|
+
Each parameter is a dict with `min` / `max` / `default` values, so a custom update
|
|
62
|
+
passed to `set_parameters` looks like:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
model.set_parameters({
|
|
66
|
+
'soil': {'FC': {'min': 50, 'max': 500, 'default': 250}},
|
|
67
|
+
'response': {'MAXBAS': {'default': 4}},
|
|
68
|
+
})
|
|
50
69
|
```
|
|
51
70
|
|
|
52
71
|
|
|
@@ -55,29 +74,56 @@ This can be flexibly used for different modelling tasks, but can also be used in
|
|
|
55
74
|
## Get Started
|
|
56
75
|
|
|
57
76
|
### Install the Package
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
From PyPI:
|
|
78
|
+
```bash
|
|
79
|
+
pip install HBV_Lab
|
|
80
|
+
```
|
|
81
|
+
Or, inside a notebook:
|
|
82
|
+
```python
|
|
83
|
+
!pip install HBV_Lab
|
|
84
|
+
```
|
|
85
|
+
To work from the source / latest version:
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/abdallaox/HBV_python_implementation.git
|
|
88
|
+
cd HBV_python_implementation
|
|
89
|
+
pip install -e .
|
|
61
90
|
```
|
|
91
|
+
|
|
62
92
|
### How to Use
|
|
63
93
|
It is very intuitive—you create a model like an object which has attributes (data, parameters, initial conditions, etc.) that you can assign and access. The object also performs functions (calibration, uncertainty estimation, save, load, etc.)
|
|
64
94
|
```python
|
|
95
|
+
import pandas as pd
|
|
65
96
|
from HBV_Lab import HBVModel
|
|
97
|
+
|
|
98
|
+
# 1. Load forcing data (a DataFrame with date, precipitation, temperature,
|
|
99
|
+
# potential ET and—optionally—observed discharge columns)
|
|
100
|
+
df = pd.read_excel("data/test_data_2.xlsx")
|
|
66
101
|
model = HBVModel()
|
|
67
|
-
model.load_data(
|
|
68
|
-
|
|
102
|
+
model.load_data(
|
|
103
|
+
data=df,
|
|
104
|
+
date_column="Date", precip_column="P", temp_column="T",
|
|
105
|
+
pet_column="PET", obs_q_column="Q",
|
|
106
|
+
date_format="%Y%m%d", warmup_end="19811231",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# 2. (Optional) override default parameter ranges/values
|
|
110
|
+
# model.set_parameters({'soil': {'FC': {'default': 250}}})
|
|
111
|
+
|
|
112
|
+
# 3. Run, calibrate and analyse
|
|
69
113
|
model.run()
|
|
70
|
-
model.calibrate()
|
|
71
|
-
model.
|
|
114
|
+
model.calibrate() # gradient-free optimisation by default
|
|
115
|
+
model.evaluate_uncertainty() # Monte-Carlo uncertainty analysis
|
|
72
116
|
model.plot_results()
|
|
73
|
-
|
|
74
|
-
model
|
|
75
|
-
model.
|
|
117
|
+
|
|
118
|
+
# 4. Persist results and the model itself
|
|
119
|
+
model.save_results("results/run.csv")
|
|
120
|
+
model.save_model("models/my_model")
|
|
121
|
+
model = HBVModel.load_model("models/my_model")
|
|
76
122
|
```
|
|
77
123
|
### Tutorial
|
|
78
124
|
Start by following a simple case study in the notebook: [**quick_start_guide.ipynb**](https://github.com/abdallaox/HBV_python_implementation/blob/main/quick_start_guide.ipynb)
|
|
79
125
|
### Play with HBV
|
|
80
|
-
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://
|
|
126
|
+
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://hbvpythonimplementation-production.up.railway.app/HBV_playground)—a playground that uses a model developed with this implementation.
|
|
81
127
|
### References
|
|
82
128
|
|
|
83
129
|
**[1]** Bergström, S., & Forsman, A. (1973). Development of a conceptual deterministic rainfall-runoff model. *Hydrology Research*, *4*, 147-170.
|
hbv_lab-1.1.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# HBV_Lab (Python implementation of a lumped conceptual HBV model)
|
|
2
|
+
|
|
3
|
+
HBV is a simple conceptual hydrological model that simulates the main hydrological processes related to snow, soil, groundwater, and routing [[1]](https://iwaponline.com/hr/article/4/3/147/1357/DEVELOPMENT-OF-A-CONCEPTUAL-DETERMINISTIC-RAINFALL). There are many software packages and off-the-shelf products that implement different versions of it [[2]](https://www.geo.uzh.ch/en/units/h2k/Services/HBV-Model.html) [[3]](https://hess.copernicus.org/articles/17/445/2013/).
|
|
4
|
+
|
|
5
|
+
I've been experimenting with the model lately and—in an endeavour to better understand the logic behind it—I decided to implement my own version—in Python, following an intuitive object-oriented programming approach.
|
|
6
|
+
|
|
7
|
+
This version implements the snow, soil, response and routing routines—controlled by 14 calibratable parameters as shown below—in addition to calibration and uncertainty analysis modules. See the [documentation for one step of the model.](https://lucid.app/publicSegments/view/a0edb3b6-8eba-4db5-9984-bfd23cc004ef/image.png)
|
|
8
|
+
|
|
9
|
+
Conceptually the parameters belong to four routines:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
parameters = {
|
|
13
|
+
'snow': ['TT', 'CFMAX', 'SFCF', 'CFR', 'CWH'],
|
|
14
|
+
'soil': ['FC', 'LP', 'BETA'],
|
|
15
|
+
'response': ['K0', 'K1', 'K2', 'UZL', 'PERC'],
|
|
16
|
+
'routing': ['MAXBAS'],
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
In the model object the parameters are stored in three groups—`snow`, `soil`, and
|
|
21
|
+
`response`—and the routing parameter `MAXBAS` lives inside the `response` group.
|
|
22
|
+
Each parameter is a dict with `min` / `max` / `default` values, so a custom update
|
|
23
|
+
passed to `set_parameters` looks like:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
model.set_parameters({
|
|
27
|
+
'soil': {'FC': {'min': 50, 'max': 500, 'default': 250}},
|
|
28
|
+
'response': {'MAXBAS': {'default': 4}},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
This can be flexibly used for different modelling tasks, but can also be used in a classroom setup—to explain hydrological concepts (processes, calibration, uncertainty analysis, etc.).
|
|
34
|
+
|
|
35
|
+
## Get Started
|
|
36
|
+
|
|
37
|
+
### Install the Package
|
|
38
|
+
From PyPI:
|
|
39
|
+
```bash
|
|
40
|
+
pip install HBV_Lab
|
|
41
|
+
```
|
|
42
|
+
Or, inside a notebook:
|
|
43
|
+
```python
|
|
44
|
+
!pip install HBV_Lab
|
|
45
|
+
```
|
|
46
|
+
To work from the source / latest version:
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/abdallaox/HBV_python_implementation.git
|
|
49
|
+
cd HBV_python_implementation
|
|
50
|
+
pip install -e .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### How to Use
|
|
54
|
+
It is very intuitive—you create a model like an object which has attributes (data, parameters, initial conditions, etc.) that you can assign and access. The object also performs functions (calibration, uncertainty estimation, save, load, etc.)
|
|
55
|
+
```python
|
|
56
|
+
import pandas as pd
|
|
57
|
+
from HBV_Lab import HBVModel
|
|
58
|
+
|
|
59
|
+
# 1. Load forcing data (a DataFrame with date, precipitation, temperature,
|
|
60
|
+
# potential ET and—optionally—observed discharge columns)
|
|
61
|
+
df = pd.read_excel("data/test_data_2.xlsx")
|
|
62
|
+
model = HBVModel()
|
|
63
|
+
model.load_data(
|
|
64
|
+
data=df,
|
|
65
|
+
date_column="Date", precip_column="P", temp_column="T",
|
|
66
|
+
pet_column="PET", obs_q_column="Q",
|
|
67
|
+
date_format="%Y%m%d", warmup_end="19811231",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 2. (Optional) override default parameter ranges/values
|
|
71
|
+
# model.set_parameters({'soil': {'FC': {'default': 250}}})
|
|
72
|
+
|
|
73
|
+
# 3. Run, calibrate and analyse
|
|
74
|
+
model.run()
|
|
75
|
+
model.calibrate() # gradient-free optimisation by default
|
|
76
|
+
model.evaluate_uncertainty() # Monte-Carlo uncertainty analysis
|
|
77
|
+
model.plot_results()
|
|
78
|
+
|
|
79
|
+
# 4. Persist results and the model itself
|
|
80
|
+
model.save_results("results/run.csv")
|
|
81
|
+
model.save_model("models/my_model")
|
|
82
|
+
model = HBVModel.load_model("models/my_model")
|
|
83
|
+
```
|
|
84
|
+
### Tutorial
|
|
85
|
+
Start by following a simple case study in the notebook: [**quick_start_guide.ipynb**](https://github.com/abdallaox/HBV_python_implementation/blob/main/quick_start_guide.ipynb)
|
|
86
|
+
### Play with HBV
|
|
87
|
+
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://hbvpythonimplementation-production.up.railway.app/HBV_playground)—a playground that uses a model developed with this implementation.
|
|
88
|
+
### References
|
|
89
|
+
|
|
90
|
+
**[1]** Bergström, S., & Forsman, A. (1973). Development of a conceptual deterministic rainfall-runoff model. *Hydrology Research*, *4*, 147-170.
|
|
91
|
+
|
|
92
|
+
**[2]** Seibert, J., & Vis, M. J. P. (2012). Teaching hydrological modeling with a user-friendly catchment-runoff-model software package. *Hydrology and Earth System Sciences*, *16*(9), 3315-3325. [doi:10.5194/hess-16-3315-2012](https://doi.org/10.5194/hess-16-3315-2012)
|
|
93
|
+
|
|
94
|
+
**[3]** AghaKouchak, A., Nakhjiri, N., & Habib, E. (2013). An educational model for ensemble streamflow simulation and uncertainty analysis. *Hydrology and Earth System Sciences*, *17*(2), 445-452. [doi:10.5194/hess-17-445-2013](https://doi.org/10.5194/hess-17-445-2013)
|
|
95
|
+
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as f:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='HBV_Lab',
|
|
8
|
-
version='
|
|
8
|
+
version='1.1.0',
|
|
9
9
|
packages=find_packages(include=['HBV_Lab', 'HBV_Lab.*']),
|
|
10
10
|
install_requires=[
|
|
11
11
|
'numpy',
|
|
@@ -13,7 +13,12 @@ setup(
|
|
|
13
13
|
'matplotlib',
|
|
14
14
|
'scipy',
|
|
15
15
|
'tqdm',
|
|
16
|
+
'openpyxl', # required by pandas to read the .xlsx example data
|
|
16
17
|
],
|
|
18
|
+
extras_require={
|
|
19
|
+
# Optional dependencies for the interactive Bokeh playground
|
|
20
|
+
'playground': ['bokeh>=2.4.0'],
|
|
21
|
+
},
|
|
17
22
|
author='Abdalla Mohammed',
|
|
18
23
|
author_email='abdalla.mohammed.ox@gmail.com',
|
|
19
24
|
description='An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.',
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import pytest
|
|
6
|
+
from HBV_Lab import HBVModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def make_sample_df(n_days=10):
|
|
10
|
+
dates = pd.date_range(start='2000-01-01', periods=n_days, freq='D')
|
|
11
|
+
df = pd.DataFrame({
|
|
12
|
+
'Date': dates.strftime('%Y%m%d'),
|
|
13
|
+
'Precipitation': np.ones(n_days) * 1.0,
|
|
14
|
+
'Temperature': np.ones(n_days) * 2.0,
|
|
15
|
+
'PotentialET': np.ones(n_days) * 0.5,
|
|
16
|
+
'ObservedQ': np.ones(n_days) * 0.2
|
|
17
|
+
})
|
|
18
|
+
return df
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_load_data_and_time_step_and_pet_expansion():
|
|
22
|
+
model = HBVModel()
|
|
23
|
+
df = make_sample_df(n_days=20)
|
|
24
|
+
|
|
25
|
+
model.load_data(data=df, date_column='Date', date_format='%Y%m%d', precip_column='Precipitation', temp_column='Temperature', pet_column='PotentialET', obs_q_column='ObservedQ')
|
|
26
|
+
|
|
27
|
+
assert model.data is not None
|
|
28
|
+
assert model.start_date is not None
|
|
29
|
+
assert model.time_step == 'D'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_set_parameters_validation():
|
|
33
|
+
model = HBVModel()
|
|
34
|
+
with pytest.raises(ValueError):
|
|
35
|
+
model.set_parameters(None)
|
|
36
|
+
|
|
37
|
+
# valid update
|
|
38
|
+
custom = {'soil': {'FC': {'default': 200.0}}}
|
|
39
|
+
model.set_parameters(custom)
|
|
40
|
+
assert model.params['soil']['FC']['default'] == 200.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_set_initial_conditions_and_run(tmp_path):
|
|
44
|
+
model = HBVModel()
|
|
45
|
+
df = make_sample_df(n_days=15)
|
|
46
|
+
model.load_data(data=df, date_column='Date', date_format='%Y%m%d', precip_column='Precipitation', temp_column='Temperature', pet_column='PotentialET', obs_q_column='ObservedQ')
|
|
47
|
+
|
|
48
|
+
model.set_initial_conditions(snowpack=5.0, soil_moisture=40.0)
|
|
49
|
+
results = model.run(verbose=False)
|
|
50
|
+
|
|
51
|
+
assert results['discharge'].shape[0] == 15
|
|
52
|
+
assert np.all(results['discharge'] >= 0.0)
|
|
53
|
+
|
|
54
|
+
# Test save_results
|
|
55
|
+
out_file = tmp_path / "results_test.csv"
|
|
56
|
+
model.results = results
|
|
57
|
+
model.save_results(str(out_file))
|
|
58
|
+
assert os.path.exists(out_file)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_performance_metrics_calculation():
|
|
62
|
+
model = HBVModel()
|
|
63
|
+
df = make_sample_df(n_days=12)
|
|
64
|
+
model.load_data(data=df, date_column='Date', date_format='%Y%m%d', precip_column='Precipitation', temp_column='Temperature', pet_column='PotentialET', obs_q_column='ObservedQ')
|
|
65
|
+
|
|
66
|
+
model.set_initial_conditions()
|
|
67
|
+
results = model.run(verbose=False)
|
|
68
|
+
# attach observed into results
|
|
69
|
+
results['observed_q'] = np.ones(len(results['discharge'])) * 0.2
|
|
70
|
+
model.results = results
|
|
71
|
+
|
|
72
|
+
model.calculate_performance_metrics(verbose=False)
|
|
73
|
+
assert hasattr(model, 'performance_metrics')
|
|
74
|
+
keys = {'NSE','KGE','PBIAS','RMSE','MAE'}
|
|
75
|
+
assert keys.issubset(set(model.performance_metrics.keys()))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from HBV_Lab import hbv_step
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_hbv_step_returns_expected_keys():
|
|
5
|
+
params = {
|
|
6
|
+
'snow': {'TT': {'default': 0.0}, 'CFMAX': {'default': 2.0}, 'CFR': {'default': 0.0}, 'CWH': {'default': 0.1}, 'SFCF': {'default': 1.0}},
|
|
7
|
+
'soil': {'FC': {'default': 100.0}, 'BETA': {'default': 2.0}, 'LP': {'default': 0.5}},
|
|
8
|
+
'response': {'K0': {'default': 0.5}, 'K1': {'default': 0.2}, 'K2': {'default': 0.01}, 'UZL': {'default': 20.0}, 'PERC': {'default': 1.0}}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
initial_conditions = {'snowpack': 0.0, 'liquid_water': 0.0, 'soil_moisture': 50.0, 'upper_storage': 10.0, 'lower_storage': 20.0}
|
|
12
|
+
|
|
13
|
+
new_states, fluxes = hbv_step(precipitation=5.0, temperature=5.0, potential_et=2.0, params=params, initial_conditions=initial_conditions)
|
|
14
|
+
|
|
15
|
+
# new_states contains required keys
|
|
16
|
+
assert set(new_states.keys()) == {'snowpack','liquid_water','soil_moisture','upper_storage','lower_storage'}
|
|
17
|
+
# fluxes contain discharge and flow components
|
|
18
|
+
assert 'discharge' in fluxes and 'quick_flow' in fluxes and 'baseflow' in fluxes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from HBV_Lab import response_routine_two_tanks
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_response_basic_mass_balance():
|
|
5
|
+
params = {'K0': {'default': 0.5}, 'K1': {'default': 0.2}, 'K2': {'default': 0.01}, 'UZL': {'default': 20.0}, 'PERC': {'default': 1.0}}
|
|
6
|
+
|
|
7
|
+
upper, lower, discharge, q0, q1, q2 = response_routine_two_tanks(out_to_response=5.0, upper_storage=10.0, lower_storage=20.0, params=params)
|
|
8
|
+
|
|
9
|
+
# discharge is sum of flow components
|
|
10
|
+
assert discharge == q0 + q1 + q2
|
|
11
|
+
assert upper >= 0.0
|
|
12
|
+
assert lower >= 0.0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_response_percolation_and_threshold():
|
|
16
|
+
params = {'K0': {'default': 0.5}, 'K1': {'default': 0.2}, 'K2': {'default': 0.01}, 'UZL': {'default': 0.0}, 'PERC': {'default': 100.0}}
|
|
17
|
+
|
|
18
|
+
# Large PERC means most incoming water should percolate to lower zone
|
|
19
|
+
upper, lower, discharge, q0, q1, q2 = response_routine_two_tanks(out_to_response=3.0, upper_storage=1.0, lower_storage=0.0, params=params)
|
|
20
|
+
|
|
21
|
+
# Ensure percolation did not make storages negative and produced baseflow
|
|
22
|
+
assert lower >= 0.0
|
|
23
|
+
assert q2 >= 0.0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from HBV_Lab import snow_routine
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_snow_accumulates_and_runs_off():
|
|
5
|
+
params = {
|
|
6
|
+
'TT': {'default': 0.0},
|
|
7
|
+
'CFMAX': {'default': 2.0},
|
|
8
|
+
'CFR': {'default': 0.0},
|
|
9
|
+
'CWH': {'default': 0.1},
|
|
10
|
+
'SFCF': {'default': 1.0}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Cold day: precipitation becomes snowfall and increases snowpack
|
|
14
|
+
snowpack, liquid, runoff = snow_routine(precipitation=10.0, temperature=-5.0,
|
|
15
|
+
snowpack=0.0, liquid_water=0.0, params=params)
|
|
16
|
+
|
|
17
|
+
assert snowpack > 0.0
|
|
18
|
+
assert liquid >= 0.0
|
|
19
|
+
assert runoff == 0.0 # no holding capacity exceeded immediately
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_snow_melts_when_warm():
|
|
23
|
+
params = {
|
|
24
|
+
'TT': {'default': 0.0},
|
|
25
|
+
'CFMAX': {'default': 5.0},
|
|
26
|
+
'CFR': {'default': 0.0},
|
|
27
|
+
'CWH': {'default': 0.2},
|
|
28
|
+
'SFCF': {'default': 1.0}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Warm day with existing snowpack should generate melt and liquid water
|
|
32
|
+
snowpack, liquid, runoff = snow_routine(precipitation=0.0, temperature=5.0,
|
|
33
|
+
snowpack=20.0, liquid_water=0.0, params=params)
|
|
34
|
+
|
|
35
|
+
assert snowpack < 20.0
|
|
36
|
+
assert liquid >= 0.0
|
|
37
|
+
# since holding capacity = CWH * snowpack after melt, liquid might be <= holding capacity
|
|
38
|
+
assert runoff >= 0.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from HBV_Lab import soil_routine
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_soil_et_and_recharge():
|
|
6
|
+
params = {'FC': {'default': 100.0}, 'BETA': {'default': 2.0}, 'LP': {'default': 0.5}}
|
|
7
|
+
|
|
8
|
+
# With moderate soil moisture and incoming runoff, we expect some recharge and ET
|
|
9
|
+
soil_moisture, out_to_response, recharge, runoff, actual_et = soil_routine(
|
|
10
|
+
runoff_from_snow=10.0, temperature=5.0, potential_et=2.0, soil_moisture=50.0, params=params
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
assert soil_moisture >= 0.0
|
|
14
|
+
assert actual_et <= 2.0
|
|
15
|
+
assert recharge >= 0.0
|
|
16
|
+
assert out_to_response == pytest.approx(recharge + runoff)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_soil_overflow_produces_runoff():
|
|
20
|
+
params = {'FC': {'default': 20.0}, 'BETA': {'default': 1.0}, 'LP': {'default': 0.5}}
|
|
21
|
+
|
|
22
|
+
# Large incoming water should cause soil to exceed FC and produce runoff
|
|
23
|
+
soil_moisture, out_to_response, recharge, runoff, actual_et = soil_routine(
|
|
24
|
+
runoff_from_snow=100.0, temperature=5.0, potential_et=0.0, soil_moisture=10.0, params=params
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert runoff > 0.0
|
|
28
|
+
assert soil_moisture <= params['FC']['default']
|
hbv_lab-0.2.3/README.md
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# HBV_Lab (Python implementation of a lumped conceptual HBV model)
|
|
2
|
-
|
|
3
|
-
HBV is a simple conceptual hydrological model that simulates the main hydrological processes related to snow, soil, groundwater, and routing [[1]](https://iwaponline.com/hr/article/4/3/147/1357/DEVELOPMENT-OF-A-CONCEPTUAL-DETERMINISTIC-RAINFALL). There are many software packages and off-the-shelf products that implement different versions of it [[2]](https://www.geo.uzh.ch/en/units/h2k/Services/HBV-Model.html) [[3]](https://hess.copernicus.org/articles/17/445/2013/).
|
|
4
|
-
|
|
5
|
-
I've been experimenting with the model lately and—in an endeavour to better understand the logic behind it—I decided to implement my own version—in Python, following an intuitive object-oriented programming approach.
|
|
6
|
-
|
|
7
|
-
This versioin implements the snow, soil, response and routing routines—controled by 14 calibratable parameters as shown below. In addition to calibration and uncertainty analysis modules.
|
|
8
|
-
```python
|
|
9
|
-
parameters = {
|
|
10
|
-
'snow': ['TT', 'CFMAX', 'SFCF', 'CFR', 'CWH'],
|
|
11
|
-
'soil': ['FC', 'LP', 'BETA'],
|
|
12
|
-
'response': ['K0', 'K1', 'K2', 'UZL', 'PERC']
|
|
13
|
-
'routing' : [ 'MAXBAS'],
|
|
14
|
-
}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
This can be flexibly used for different modelling tasks, but can also be used in a classroom setup—to explain hydrological concepts (processes, calibration, uncertainty analysis, etc.).
|
|
19
|
-
|
|
20
|
-
## Get Started
|
|
21
|
-
|
|
22
|
-
### Install the Package
|
|
23
|
-
```python
|
|
24
|
-
pip install HBV_Lab or
|
|
25
|
-
! pip install HBV_Lab
|
|
26
|
-
```
|
|
27
|
-
### How to Use
|
|
28
|
-
It is very intuitive—you create a model like an object which has attributes (data, parameters, initial conditions, etc.) that you can assign and access. The object also performs functions (calibration, uncertainty estimation, save, load, etc.)
|
|
29
|
-
```python
|
|
30
|
-
from HBV_Lab import HBVModel
|
|
31
|
-
model = HBVModel()
|
|
32
|
-
model.load_data("pandas dataframe")
|
|
33
|
-
model.set_parameters(params)
|
|
34
|
-
model.run()
|
|
35
|
-
model.calibrate()
|
|
36
|
-
model.evaluate_uncertainity()
|
|
37
|
-
model.plot_results()
|
|
38
|
-
model.save_results()
|
|
39
|
-
model.save_model("path")
|
|
40
|
-
model.load_model("path")
|
|
41
|
-
```
|
|
42
|
-
### Tutorial
|
|
43
|
-
Start by following a simple case study in the notebook: [**quick_start_guide.ipynb**](https://github.com/abdallaox/HBV_python_implementation/blob/main/quick_start_guide.ipynb)
|
|
44
|
-
### Play with HBV
|
|
45
|
-
Get a feeling of how the model works and the role of the different parameters in [**HBVLAB**](https://hbv-playground.onrender.com/HBV_playground)—a playground that uses a model developed with this implementation.
|
|
46
|
-
### References
|
|
47
|
-
|
|
48
|
-
**[1]** Bergström, S., & Forsman, A. (1973). Development of a conceptual deterministic rainfall-runoff model. *Hydrology Research*, *4*, 147-170.
|
|
49
|
-
|
|
50
|
-
**[2]** Seibert, J., & Vis, M. J. P. (2012). Teaching hydrological modeling with a user-friendly catchment-runoff-model software package. *Hydrology and Earth System Sciences*, *16*(9), 3315-3325. [doi:10.5194/hess-16-3315-2012](https://doi.org/10.5194/hess-16-3315-2012)
|
|
51
|
-
|
|
52
|
-
**[3]** AghaKouchak, A., Nakhjiri, N., & Habib, E. (2013). An educational model for ensemble streamflow simulation and uncertainty analysis. *Hydrology and Earth System Sciences*, *17*(2), 445-452. [doi:10.5194/hess-17-445-2013](https://doi.org/10.5194/hess-17-445-2013)
|
|
53
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|