diff --git a/.gitignore b/.gitignore index 24547ee..4377796 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .Python build/ develop-eggs/ -# dist/ +dist/ downloads/ eggs/ .eggs/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index ec661f6..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the OS, Python version and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.10" - # You can also specify other tool versions: - # nodejs: "19" - # rust: "1.64" - # golang: "1.19" - -# Build documentation in the "docs/" directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -# formats: -# - pdf -# - epub - -# Optional but recommended, declare the Python requirements required -# to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: docs/requirements.txt \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 509a043..84623b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,4 +31,6 @@ Adding `.finalize()` method - clears up the directory. Especially useful for DA. #### 1.5.1 - fix bug/implementation error with time indexing, docker image version 1.3.1 #### 1.5.2 -- typo in bmi implementation: docker image 1.3.2 \ No newline at end of file +- typo in bmi implementation: docker image 1.3.2 +#### 1.6.0 + - now compatible with ewatercycle V2.1 `LumpedMakkinkForcing` which generates evaporation from era5/CMIP. \ No newline at end of file diff --git a/dist/ewatercycle_hbv-1.4.1-py2.py3-none-any.whl b/dist/ewatercycle_hbv-1.4.1-py2.py3-none-any.whl deleted file mode 100644 index ba924d1..0000000 Binary files a/dist/ewatercycle_hbv-1.4.1-py2.py3-none-any.whl and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.4.1.tar.gz b/dist/ewatercycle_hbv-1.4.1.tar.gz deleted file mode 100644 index 9ceed57..0000000 Binary files a/dist/ewatercycle_hbv-1.4.1.tar.gz and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.4.2-py2.py3-none-any.whl b/dist/ewatercycle_hbv-1.4.2-py2.py3-none-any.whl deleted file mode 100644 index 92fd2ba..0000000 Binary files a/dist/ewatercycle_hbv-1.4.2-py2.py3-none-any.whl and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.4.2.tar.gz b/dist/ewatercycle_hbv-1.4.2.tar.gz deleted file mode 100644 index 1c9816b..0000000 Binary files a/dist/ewatercycle_hbv-1.4.2.tar.gz and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.5.0-py2.py3-none-any.whl b/dist/ewatercycle_hbv-1.5.0-py2.py3-none-any.whl deleted file mode 100644 index b06c305..0000000 Binary files a/dist/ewatercycle_hbv-1.5.0-py2.py3-none-any.whl and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.5.0.tar.gz b/dist/ewatercycle_hbv-1.5.0.tar.gz deleted file mode 100644 index 097a94b..0000000 Binary files a/dist/ewatercycle_hbv-1.5.0.tar.gz and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.5.1-py2.py3-none-any.whl b/dist/ewatercycle_hbv-1.5.1-py2.py3-none-any.whl deleted file mode 100644 index afdd03f..0000000 Binary files a/dist/ewatercycle_hbv-1.5.1-py2.py3-none-any.whl and /dev/null differ diff --git a/dist/ewatercycle_hbv-1.5.1.tar.gz b/dist/ewatercycle_hbv-1.5.1.tar.gz deleted file mode 100644 index c2eca6d..0000000 Binary files a/dist/ewatercycle_hbv-1.5.1.tar.gz and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml index 160c121..06d7369 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ name = "ewatercycle-HBV" description = "Implementation of HBV for eWaterCycle" readme = "README.md" license = "Apache-2.0" -version = "1.5.2" +version = "1.6.0" authors = [ { name = "David Haasnoot", email = "davidhaasnoot@gmail.com" }, ] diff --git a/src/ewatercycle_HBV/__init__.py b/src/ewatercycle_HBV/__init__.py index ead0287..a1f60f7 100644 --- a/src/ewatercycle_HBV/__init__.py +++ b/src/ewatercycle_HBV/__init__.py @@ -1 +1 @@ -__version__ = "1.5.2" +__version__ = "1.6.0" diff --git a/src/ewatercycle_HBV/forcing.py b/src/ewatercycle_HBV/forcing.py index fd20237..bca0204 100644 --- a/src/ewatercycle_HBV/forcing.py +++ b/src/ewatercycle_HBV/forcing.py @@ -103,7 +103,7 @@ def from_test_txt(self) -> xr.Dataset: Dataset with forcing data. """ if self.directory is None or self.camels_file is None: - raise ValueError("Directory or camels_file is not set") + self.file_not_found_error() fn = self.directory / self.camels_file forcing = np.loadtxt(fn, delimiter=" ") names = ["year", "month", "day", "pr","Q", "pev"] @@ -144,7 +144,7 @@ def from_camels_txt(self) -> xr.Dataset: Dataset with forcing data. """ if self.directory is None or self.camels_file is None: - raise ValueError("Directory or camels_file is not set") + self.file_not_found_error() fn = self.directory / self.camels_file data = {} with open(fn, 'r') as fin: @@ -206,8 +206,9 @@ def from_camels_txt(self) -> xr.Dataset: return ds def from_external_source(self): + """Runs checks on externally provided forcing""" if None in [self.directory, self.pr, self.pev]: - raise ValueError("Directory or camels_file is not set") + self.file_not_found_error() # often same file if self.pr == self.pev: @@ -228,7 +229,7 @@ def from_external_source(self): 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) - if not sum([param in combined_data_vars for param in REQUIRED_PARAMS]) == len(REQUIRED_PARAMS): + 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") ds_pr, ds_name_pr = self.crop_ds(ds_pr, "external") @@ -253,6 +254,9 @@ def crop_ds(self, ds: xr.Dataset, name: str): return ds, ds_name + def file_not_found_error(self): + raise ValueError("Directory, camels_file or pr & pev values is not set correctly") + def calc_pet(s_rad, t_min, t_max, doy, alpha, elev, lat) -> np.ndarray: """Calculates Potential Evaporation using Priestly–Taylor PET estimate, callibrated with longterm P-T trends from the camels data set (alpha). diff --git a/src/ewatercycle_HBV/model.py b/src/ewatercycle_HBV/model.py index 892d9ea..ff0c697 100644 --- a/src/ewatercycle_HBV/model.py +++ b/src/ewatercycle_HBV/model.py @@ -1,12 +1,14 @@ """eWaterCycle wrapper for the HBV model.""" import json -import os.path +import xarray as xr import warnings from collections.abc import ItemsView from pathlib import Path from typing import Any, Type -from ewatercycle.base.forcing import GenericLumpedForcing # or later Use custom forcing instead? +from ewatercycle.forcing import LumpedMakkinkForcing +from ewatercycle.forcing import GenericLumpedForcing + from ewatercycle_HBV.forcing import HBVForcing # Use custom forcing instead from ewatercycle.base.model import ContainerizedModel, eWaterCycleModel from ewatercycle.container import ContainerImage @@ -32,10 +34,9 @@ class HBVMethods(eWaterCycleModel): """ The eWatercycle HBV model. - - """ - forcing: HBVForcing # The model requires forcing. + + forcing: LumpedMakkinkForcing|HBVForcing|GenericLumpedForcing # The model requires forcing. parameter_set: None # The model has no parameter set. _config: dict = { @@ -49,22 +50,52 @@ def _make_cfg_file(self, **kwargs) -> Path: """Write model configuration file.""" # do some basic test to check on forcing - if self.forcing.test_data_bool: - self.forcing.from_test_txt() - elif self.forcing.camels_txt_defined(): - self.forcing.from_camels_txt() - elif self.forcing.forcing_nc_defined(): - self.forcing.from_external_source() - else: - raise UserWarning("Ensure either a txt file with camels data or an(/set of) xarrays is defined") - - self._config["precipitation_file"] = str( - self.forcing.directory / self.forcing.pr - ) - - self._config["potential_evaporation_file"] = str( - self.forcing.directory / self.forcing.pev - ) + if type(self.forcing).__name__ == 'HBVForcing': + if self.forcing.test_data_bool: + self.forcing.from_test_txt() + elif self.forcing.camels_txt_defined(): + self.forcing.from_camels_txt() + elif self.forcing.forcing_nc_defined(): + self.forcing.from_external_source() + else: + raise UserWarning("Ensure either a txt file with camels data or an(/set of) xarrays is defined") + + self._config["precipitation_file"] = str( + self.forcing.directory / self.forcing.pr + ) + + self._config["potential_evaporation_file"] = str( + self.forcing.directory / self.forcing.pev + ) + + elif type(self.forcing).__name__ == 'GenericLumpedForcing': + raise UserWarning("Generic Lumped Forcing does not provide potential evaporation, which this model needs") + + elif type(self.forcing).__name__ == 'LumpedMakkinkForcing': + ds = xr.open_dataset(self.forcing.directory / self.forcing.filenames['evspsblpot']) + attributes = ds['evspsblpot'].attrs + attributes['units'] = 'mm' + ds = ds.rename({'evspsblpot': 'pev'}) + ds['pev'] = ds['pev'] * 86400 + ds['pev'].attrs = attributes + temporary_pev_file = self.forcing.directory / self.forcing.filenames['evspsblpot'].replace('evspsblpot', 'pev_mm') + ds.to_netcdf(temporary_pev_file) + + ds = xr.open_dataset(self.forcing.directory / self.forcing.filenames['pr']) + attributes = ds['pr'].attrs + attributes['units'] = 'mm' + ds['pr'] = ds['pr'] * 86400 + ds['pr'].attrs = attributes + temporary_pr_file = self.forcing.directory / self.forcing.filenames['pr'].replace('pr', 'pr_mm') + ds.to_netcdf(temporary_pr_file) + + 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 @@ -160,15 +191,22 @@ def finalize(self) -> None: except FileNotFoundError: warnings.warn(message=f'Config folder not found at {self._cfg_dir.rmdir()}',category=UserWarning) - - # NetCDF files created are timestamped and running them a lot creates many files, remove these - if self.forcing.camels_txt_defined() or self.forcing.test_data_bool: - for file in ["potential_evaporation_file", "precipitation_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() - else: - pass + if type(self.forcing).__name__ == 'HBVForcing': + # NetCDF files created are timestamped and running them a lot creates many files, remove these + if self.forcing.camels_txt_defined() or self.forcing.test_data_bool: + self.unlink() + + elif type(self.forcing).__name__ == 'LumpedMakkinkForcing': + # we created a temporary file so let's unlink that + self.unlink() + + def unlink(self): + for file in ["potential_evaporation_file", "precipitation_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() + else: + pass class HBV(ContainerizedModel, HBVMethods): """The HBV eWaterCycle model, with the Container Registry docker image."""