Appendix B: Prove for weights objection function#
The parameters and objective value for the calibration period for the different weight ratios are shown below.
General code#
#Loading packages
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
import numpy as np
from pathlib import Path
import pandas as pd
import matplotlib.pyplot as plt
import xarray as xr
import ewatercycle
import ewatercycle.models
import ewatercycle.forcing
from scipy.stats import qmc
#Loading shapefile of the catchment area of Mohembo
shapefile_path = Path.home() / "BEP-beau/book/thesis_projects/BSc/2026_Q4_BeauBuijtenhuijs_CEG/Data/Shapefile" / "CatchmentArea_4326.shp"
#Defining start and end date for ERA5 data
start_date = "1970-01-01T00:00:00Z"
end_date = "2020-12-31T00:00:00Z"
#Creating path for ERA5 data
forcing_path_ERA5 = Path.home() / "BEP-beau/book/thesis_projects/BSc/2026_Q4_BeauBuijtenhuijs_CEG/Data/ERA5_HBV/own_shapefile_3"
forcing_path_ERA5.mkdir(exist_ok=True, parents=True)
#Downloading ERA5 data
#ERA5_forcing = ewatercycle.forcing.sources["LumpedMakkinkForcing"].generate(
# dataset="ERA5",
# start_time=start_date,
# end_time=end_date,
# shape=shapefile_path,
# directory=forcing_path_ERA5,
#)
#Loading downloaded ERA5 data
load_location = forcing_path_ERA5 / "work" / "diagnostic" / "script"
ERA5_forcing = ewatercycle.forcing.sources["LumpedMakkinkForcing"].load(directory=load_location)
#Defining start and end date for the calibration period
calibration_start_date = "1975-01-01T00:00:00Z"
calibration_end_date = "2010-12-31T00:00:00Z"
#Loading observed discharge data
data = pd.read_csv(Path.home() / "BEP-beau/book/thesis_projects/BSc/2026_Q4_BeauBuijtenhuijs_CEG/Data/mohembo_daily_water_discharge_data.csv",
index_col='date', parse_dates=True, dayfirst=True)
data_daily = data.resample('D').interpolate()
data_daily.columns = ['Discharge (m$^3$/s)']
data_daily = data_daily[~data_daily.index.year.isin([1974, 2021])]
#The model returns data in mm/day, while observed data is in m^3/s
Area_km2 = 173696.852
def mmday_to_m3s(mmday_data, area):
return (mmday_data * area) / 86.4
#Objective function
def objective(modelled, observed, start, end, w_kge, w_lognse):
start = pd.to_datetime(start).tz_localize(None)
end = pd.to_datetime(end).tz_localize(None)
modelled.index = pd.to_datetime(modelled.index)
observed.index = pd.to_datetime(observed.index)
df = pd.concat([modelled.reindex(observed.index, method='ffill'), observed], axis=1, keys=['Modelled', 'Observed'])
df = df.dropna()
df = df[(df.index > start) & (df.index < end)]
#KGE
r = np.corrcoef(df['Observed'], df['Modelled'])[0, 1]
beta = np.mean(df['Modelled']) / np.mean(df['Observed'])
CV_modelled = np.std(df['Modelled']) / np.mean(df['Modelled'])
CV_observed = np.std(df['Observed']) / np.mean(df['Observed'])
gamma = CV_modelled / CV_observed
kge = 1 - np.sqrt((r - 1)**2 + (beta - 1)**2 + (gamma - 1)**2)
#logNSE
a = (np.log(df['Modelled']) - np.log(df['Observed']))**2
a1 = a.sum()
b = (np.log(df['Observed']) - np.log(np.mean(df['Observed'])))**2
b1 = b.sum()
lognse = 1 - (a1/b1)
score = w_kge * kge + w_lognse * lognse
return score
#Making sets of parameters
N = 10 #2000 were used to calibrate the parameters, but this takes a long time
s_0 = np.array([0, 100, 0, 5, 0])
param_names = ["Imax", "Ce", "Sumax", "Beta", "Pmax", "Tlag", "Kf", "Ks", "Fm"]
param_mins = np.array([0, 0.2, 1000, 0.5, 0.001, 1, 0.0005, 0.00005, 0.00001])
param_maxs = np.array([8, 1.5, 3000, 4, 0.3, 90, 0.1, 0.002, 1])
sampler = qmc.LatinHypercube(d=len(param_names))
sample = sampler.random(n=N)
parameters = qmc.scale(sample, param_mins, param_maxs)
#Setting up the HBV model
ensemble = []
for counter in range(N):
ensemble.append(ewatercycle.models.HBVLocal(forcing=ERA5_forcing))
config_file, _ = ensemble[counter].setup(parameters = parameters[counter], initial_storage=s_0)
ensemble[counter].initialize(config_file)
#Running the model with every generated parameters set
values = []
for ensembleMember in ensemble:
Q_m = []
time = []
while ensembleMember.time < ensembleMember.end_time:
ensembleMember.update()
discharge_this_timestep = ensembleMember.get_value("Q")
Q_m.append(discharge_this_timestep[0])
time.append(ensembleMember.time_as_datetime)
Q_m = mmday_to_m3s(np.array(Q_m), Area_km2)
discharge_dataframe = pd.DataFrame({'model output': Q_m}, index=pd.to_datetime(time))
#Calculating fit using the objective function
#By changing the weights, results of different ratio can be computed
obj_value = objective(discharge_dataframe['model output'], data_daily['Discharge (m$^3$/s)'],
calibration_start_date, calibration_end_date, 0.3, 0.7)
values.append(obj_value)
del Q_m, time, discharge_dataframe, obj_value
for ensembleMember in ensemble:
ensembleMember.finalize()
#Retrieving the best parameter set and best objective value for calibration period
best_index = np.argmax(values)
best_parameters = parameters[best_index]
best_value = values[best_index]
Weight KGE = 0.3, weight log-NSE = 0.7:#
Results |
|
|---|---|
Imax |
7.170 mm |
Ce |
0.589 (-) |
Sumax |
2653.448 mm |
Beta |
3.706 (-) |
Pmax |
0.214 mm/day |
Tlag |
85.687 days |
Kf |
0.078 day\(^{-1}\) |
Ks |
0.001 day\(^{-1}\) |
Fm |
0.468 (-) |
Objective value |
0.811 |
Weight KGE = 0.4, weight log-NSE = 0.6:#
Results |
|
|---|---|
Imax |
5.021 mm |
Ce |
0.594 (-) |
Sumax |
2012.102 mm |
Beta |
3.954 (-) |
Pmax |
0.219 mm/day |
Tlag |
87.626 days |
Kf |
0.067 day\(^{-1}\) |
Ks |
0.002 day\(^{-1}\) |
Fm |
0.731 (-) |
Objective value |
0.816 |
Weight KGE = 0.5, weight log-NSE = 0.5:#
Results |
|
|---|---|
Imax |
7.304 mm |
Ce |
0.546 (-) |
Sumax |
2683.134 mm |
Beta |
3.592 (-) |
Pmax |
0.182 mm/day |
Tlag |
82.451 days |
Kf |
0.042 day\(^{-1}\) |
Ks |
0.002 day\(^{-1}\) |
Fm |
0.845 (-) |
Objective value |
0.836 |
Weight KGE = 0.6, weight log-NSE = 0.4:#
Results |
|
|---|---|
Imax |
3.160 mm |
Ce |
0.518 (-) |
Sumax |
2141.544 mm |
Beta |
3.651 (-) |
Pmax |
0.256 mm/day |
Tlag |
67.745 days |
Kf |
0.082 day\(^{-1}\) |
Ks |
0.001 day\(^{-1}\) |
Fm |
0.061 (-) |
Objective value |
0.815 |
Weight KGE = 0.7, weight log-NSE = 0.3:#
Results |
|
|---|---|
Imax |
7.003 mm |
Ce |
0.412 (-) |
Sumax |
2248.938 mm |
Beta |
2.738 (-) |
Pmax |
0.185 mm/day |
Tlag |
22.48 days |
Kf |
0.015 day\(^{-1}\) |
Ks |
0.001 day\(^{-1}\) |
Fm |
0.372 (-) |
Objective value |
0.854 |
Weight KGE = 0.8, weight log-NSE = 0.2:#
Results |
|
|---|---|
Imax |
3.428 mm |
Ce |
0.211 (-) |
Sumax |
1180.128 mm |
Beta |
2.129 (-) |
Pmax |
0.276 mm/day |
Tlag |
15.057 days |
Kf |
0.014 day\(^{-1}\) |
Ks |
0.001 day\(^{-1}\) |
Fm |
0.669 (-) |
Objective value |
0.821 |