Skip to content
This repository has been archived by the owner on Feb 18, 2025. It is now read-only.

Adding snow model #49

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ Adding `.finalize()` method - clears up the directory. Especially useful for DA.
### 1.6.0
- now compatible with ewatercycle V2.1 `LumpedMakkinkForcing` which generates evaporation from era5/CMIP.
#### 1.6.1
- bug fix occuring when loading makkink data
- bug fix occuring when loading makkink data
### 1.7.0
- new version of HBV bmi which adds snow
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ name = "ewatercycle-HBV"
description = "Implementation of HBV for eWaterCycle"
readme = "README.md"
license = "Apache-2.0"
version = "1.6.1"
version = "1.7.0"
authors = [
{ name = "David Haasnoot", email = "[email protected]" },
]
Expand Down
2 changes: 1 addition & 1 deletion src/ewatercycle_HBV/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.6.1"
__version__ = "1.7.0"
33 changes: 26 additions & 7 deletions src/ewatercycle_HBV/forcing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@


RENAME_CAMELS = {'total_precipitation_sum':'pr',
'potential_evaporation_sum':'pev',
'streamflow':'Q'}
'potential_evaporation_sum':'pev',
'streamflow':'Q',
'temperature_2m_min':'tasmin',
'temperature_2m_max':'tasmax',
}

REQUIRED_PARAMS = ["pr", "pev"]
REQUIRED_PARAMS = ["pr", "pev", "tasmean"]
class HBVForcing(DefaultForcing):
"""Container for HBV forcing data.

Expand Down Expand Up @@ -71,6 +74,7 @@ class HBVForcing(DefaultForcing):
# or pr and pev are supplied seperately - can also be the same dataset
pr: Optional[str] = ".nc"
pev: Optional[str] = ".nc"
tasmean: Optional[str] = ".nc"
alpha: Optional[float] = 1.26 # varies per catchment, mostly 1.26?
test_data_bool: bool = False # allows to use self.from_test_txt()

Expand Down Expand Up @@ -111,6 +115,9 @@ def from_test_txt(self) -> xr.Dataset:
df_in.index = df_in.apply(lambda x: pd.Timestamp(f'{int(x.year)}-{int(x.month)}-{int(x.day)}'), axis=1)
df_in = df_in.drop(columns=["year", "month", "day"])
df_in.index.name = "time"
# test data has no snow but let's add in synthetic temperatures to ensure there's no snow:
df_in['tasmean'] = 25

# TODO use netcdf-cf conventions
ds = xr.Dataset(data_vars=df_in,
attrs={
Expand All @@ -121,6 +128,8 @@ def from_test_txt(self) -> xr.Dataset:
ds, ds_name = self.crop_ds(ds, "test")
self.pev = ds_name
self.pr = ds_name
self.tasmean = ds_name

return ds

def from_camels_txt(self) -> xr.Dataset:
Expand Down Expand Up @@ -183,7 +192,8 @@ def from_camels_txt(self) -> xr.Dataset:
# add attributes
attrs = {"title": "HBV forcing data",
"history": "Created by ewatercycle_HBV.forcing.HBVForcing.from_camels_txt()",
"units": "daylight(s), precipitation(mm/day), mean radiation(W/m2), snow water equivalen(mm), temperature max(C), temperature min(C), vapour pressure(Pa)", }
"units": "daylight(s), precipitation(mm/day), mean radiation(W/m2), snow water equivalen(mm), temperature max(C), temperature min(C), temperature mean(c),vapour pressure(Pa)",
}

# add the data lines with catchment characteristics to the description
attrs.update(data)
Expand All @@ -200,9 +210,11 @@ def from_camels_txt(self) -> xr.Dataset:
ds.attrs['elevation(m)'],
ds.attrs['lat']
)
ds['tasmean'] = (ds["tasmin"] + ds["tasmax"]) / 2
ds, ds_name= self.crop_ds(ds, "CAMELS")
self.pev = ds_name
self.pr = ds_name
self.tasmean = ds_name
return ds

def from_external_source(self):
Expand All @@ -211,24 +223,28 @@ def from_external_source(self):
self.file_not_found_error()

# often same file
if self.pr == self.pev:
if self.pr == self.pev == self.tasmean:
ds = xr.open_dataset(self.directory / self.pr)

# make compatile with CARAVAN data style:
if sum([key in ds.data_vars for key in RENAME_CAMELS.keys()]) == len(RENAME_CAMELS):
ds = ds.rename(RENAME_CAMELS)
ds = ds.rename_dims({'date': 'time'})
ds = ds.rename({'date': 'time'})
ds['tasmean'] = (ds["tasmin"] + ds["tasmax"]) / 2

ds, ds_name = self.crop_ds(ds, "external")
self.pev = ds_name
self.pr = ds_name
self.tasmean = ds_name
return ds

else:
# but can also seperate
ds_pr = xr.open_dataset(self.directory / self.pr)
ds_pev = xr.open_dataset(self.directory / self.pev)
combined_data_vars = list(ds_pr.data_vars) + list(ds_pev.data_vars)
ds_tasmean = xr.open_dataset(self.directory / self.tasmean)
combined_data_vars = list(ds_pr.data_vars) + list(ds_pev.data_vars) + list(ds_tasmean.data_vars)
if sum([param in combined_data_vars for param in REQUIRED_PARAMS]) != len(REQUIRED_PARAMS):
raise UserWarning(f"Supplied NetCDF files must contain {REQUIRED_PARAMS} respectively")

Expand All @@ -238,7 +254,10 @@ def from_external_source(self):
ds_pev, ds_name_pev = self.crop_ds(ds_pev, "external")
self.pev = ds_name_pev

return ds_pr, ds_pev
ds_tasmean, ds_name_tasmean = self.crop_ds(ds_tasmean, "external")
self.tasmean = ds_name_tasmean

return ds_pr, ds_pev, ds_tasmean

def crop_ds(self, ds: xr.Dataset, name: str):
start = np.datetime64(self.start_time)
Expand Down
35 changes: 23 additions & 12 deletions src/ewatercycle_HBV/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class HBVMethods(eWaterCycleModel):
_config: dict = {
"precipitation_file": "",
"potential_evaporation_file": "",
"mean_temperature_file": "",
"parameters": "",
"initial_storage": "",
}
Expand All @@ -68,6 +69,9 @@ def _make_cfg_file(self, **kwargs) -> Path:
self._config["potential_evaporation_file"] = str(
self.forcing.directory / self.forcing.pev
)
self._config["mean_temperature_file"] = str(
self.forcing.directory / self.forcing.tasmean)


elif type(self.forcing).__name__ == 'GenericLumpedForcing':
raise UserWarning("Generic Lumped Forcing does not provide potential evaporation, which this model needs")
Expand Down Expand Up @@ -95,23 +99,27 @@ def _make_cfg_file(self, **kwargs) -> Path:
ds.to_netcdf(temporary_pr_file)
ds.close()

temporary_tasmean_file = self.forcing.directory / self.forcing.filenames['tas'].replace('tas', 'tasmean')
if not temporary_tasmean_file.is_file():
ds = xr.open_dataset(self.forcing.directory / self.forcing.filenames['tas'])
attributes = ds['tas'].attrs
ds['tasmean'] = ds['tas']
if ds['tasmean'].mean().values > 200: # adjust for kelvin units
ds['tasmean'] -= 273.15
ds['tasmean'].attrs = attributes
ds.to_netcdf(temporary_tasmean_file)
ds.close()

self._config["precipitation_file"] = str(
temporary_pr_file
)
self._config["potential_evaporation_file"] = str(
temporary_pev_file
)

## possibly add later for snow?
# self._config["temperature_file"] = str(
# self.forcing.directory / self.forcing.tas
# )
# self._config["temperature_min_file"] = str(
# self.forcing.directory / self.forcing.tasmin
# )
# self._config["temperature_max_file"] = str(
# self.forcing.directory / self.forcing.tasmax
# )
self._config["mean_temperature_file"] = str(
temporary_tasmean_file
)

for kwarg in kwargs: # Write any kwargs to the config. - doesn't overwrite config?
self._config[kwarg] = kwargs[kwarg]
Expand Down Expand Up @@ -153,6 +161,8 @@ def parameters(self) -> ItemsView[str, Any]:

Ks (−): Similarly the slow flow is also modelled as 𝑄𝑆=𝐾𝑠∗𝑆𝑆.

FM (mm/deg/d): Melt Factor: mm of melt per deg per day

"""
pars: dict[str, Any] = dict(zip(HBV_PARAMS, self._config["parameters"].split(',')))
return pars.items()
Expand All @@ -166,6 +176,7 @@ def states(self) -> ItemsView[str, Any]:
Su (mm): Unsaturated rootzone storage, water stored accessible to plants
Sf (mm): Fastflow storage, moving Fast through the soil - preferential flow paths, upper level
Ss (mm): Groundwater storage, moving Slowly through the soil - deeper grounds water.
Sp (mm): SnowPack Storage, amount of snow stored

"""
pars: dict[str, Any] = dict(zip(HBV_STATES, self._config["initial_storage"].split(',')))
Expand Down Expand Up @@ -207,7 +218,7 @@ def finalize(self) -> None:
self.unlink()

def unlink(self):
for file in ["potential_evaporation_file", "precipitation_file"]:
for file in ["potential_evaporation_file", "precipitation_file","mean_temperature_file"]:
path = self.forcing.directory / self._config[file]
if path.is_file(): # often both with be the same, e.g. with camels data.
path.unlink()
Expand All @@ -217,5 +228,5 @@ def unlink(self):
class HBV(ContainerizedModel, HBVMethods):
"""The HBV eWaterCycle model, with the Container Registry docker image."""
bmi_image: ContainerImage = ContainerImage(
"ghcr.io/daafip/hbv-bmi-grpc4bmi:v1.3.2"
"ghcr.io/daafip/hbv-bmi-grpc4bmi:v1.4.0"
)
Loading