diff --git a/.github/workflows/github-pages-mkdocs.yml b/.github/workflows/github-pages-mkdocs.yml
index 127982f00..0de666f36 100644
--- a/.github/workflows/github-pages-mkdocs.yml
+++ b/.github/workflows/github-pages-mkdocs.yml
@@ -27,7 +27,7 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@38c111084f99d5ff5f4a1e63bedce48fe33a10c0 # mkdocs/v1.3.1
@@ -41,7 +41,7 @@ jobs:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@38c111084f99d5ff5f4a1e63bedce48fe33a10c0 # mkdocs/v1.3.1
@@ -57,7 +57,7 @@ jobs:
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@38c111084f99d5ff5f4a1e63bedce48fe33a10c0 # mkdocs/v1.3.1
diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml
index c8577bb5b..079dcf698 100644
--- a/.github/workflows/github-release.yml
+++ b/.github/workflows/github-release.yml
@@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ inputs.release-branch }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 3546ae530..648247a1b 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -39,15 +39,15 @@ jobs:
timeout-minutes: 15
steps:
# No fetch-depth: `pre-commit run --all-files` scans the working tree, not history.
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: actions/setup-python@v6
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
# Cache the hook toolchains (each hook repo builds an isolated env on first run).
# Keyed on the config so a hook or rev bump rebuilds; restore-keys reuse the rest.
- - uses: actions/cache@v4
+ - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
diff --git a/.github/workflows/pip-audit.yml b/.github/workflows/pip-audit.yml
index 872c036ee..77b536164 100644
--- a/.github/workflows/pip-audit.yml
+++ b/.github/workflows/pip-audit.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
diff --git a/.github/workflows/pure-wheel-test.yml b/.github/workflows/pure-wheel-test.yml
index 8dff06b0c..a7212caa4 100644
--- a/.github/workflows/pure-wheel-test.yml
+++ b/.github/workflows/pure-wheel-test.yml
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ inputs.branch || github.ref }}
@@ -55,7 +55,7 @@ jobs:
pixi run -e dev python -m zipfile -l dist/*.whl | grep "hapi/parameters/parameters.py"
- name: Upload wheel artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheel
path: dist/*.whl
@@ -74,18 +74,18 @@ jobs:
python-version: ["3.11", "3.12", "3.13"]
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ inputs.branch || github.ref }}
- name: Download wheel artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: wheel
path: dist/
- - uses: actions/setup-python@v6
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml
index e1f883f01..3d8916e76 100644
--- a/.github/workflows/pypi-release.yml
+++ b/.github/workflows/pypi-release.yml
@@ -17,7 +17,7 @@ jobs:
github.event.workflow_run.conclusion == 'success' }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b758fd853..148bbbf8a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,7 +20,7 @@ jobs:
OS: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -38,4 +38,4 @@ jobs:
HAPI_DATA_DIR: ${{ github.workspace }}/src/hapi/parameters
- name: Upload coverage reports to Codecov with GitHub Action
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
diff --git a/README.md b/README.md
index cd4647150..9d5d4ad17 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,24 @@
-
-[](https://doi.org/10.5281/zenodo.5758979)
-[](https://mybinder.org/v2/gh/MAfarrag/HAPI/master)
-[](https://img.shields.io/pypi/pyversions/HAPI-Nile)
-[](https://hapi-hm.readthedocs.io/en/latest/?badge=latest)
-[](https://www.gnu.org/licenses/gpl-3.0)
-[](https://github.com/pre-commit/pre-commit)
-[](https://lgtm.com/projects/g/MAfarrag/Hapi/context:python)
-
-
-[](https://github.com/MShawon/github-clone-count-badge) [](https://saythanks.io/to/MAfarrag)
-
-Current build status
-====================
-
-
-
| All platforms: |
-
-
-
-
- |
-
-
-
-[](https://ci.appveyor.com/project/MAfarrag/hapi)
+[](https://github.com/serapeum-org/Hapi/actions/workflows/tests.yml)
+[](https://github.com/serapeum-org/Hapi/actions/workflows/lint.yml)
[](https://codecov.io/gh/serapeum-org/Hapi)
-
-
-
-
-
-
-[](https://GitHub.com/Naereen/StrapDown.js/releases/)
-
-
-
-
-Current release info
-====================
+[](https://serapeum-org.github.io/Hapi)
+[](https://github.com/pre-commit/pre-commit)
+[](https://www.gnu.org/licenses/gpl-3.0)
+[](https://doi.org/10.5281/zenodo.5758979)
-| Name | Downloads | Version | Platforms |
-| --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | --- |
-| [](https://anaconda.org/conda-forge/hapi) | [](https://anaconda.org/conda-forge/hapi) [](https://pepy.tech/project/hapi-nile) [](https://pepy.tech/project/hapi-nile) [](https://pepy.tech/project/hapi-nile)   | [](https://anaconda.org/conda-forge/hapi) [](https://badge.fury.io/py/HAPI-Nile) [](https://anaconda.org/conda-forge/hapi) | [](https://anaconda.org/conda-forge/hapi) [](https://gitter.im/Hapi-Nile/Hapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) |
+[](https://pypi.org/project/HAPI-Nile/)
+[](https://anaconda.org/conda-forge/hapi)
+[](https://pypi.org/project/HAPI-Nile/)
+[](https://anaconda.org/conda-forge/hapi)
+[](https://pepy.tech/project/hapi-nile)
+[](https://pepy.tech/project/hapi-nile)
+[](https://anaconda.org/conda-forge/hapi)
+[](https://github.com/serapeum-org/Hapi/commits/main)
+[](https://github.com/serapeum-org/Hapi/stargazers)
+[](https://github.com/serapeum-org/Hapi/network/members)
- 
+ 
Hapi - Hydrological library for Python
@@ -54,7 +28,7 @@ model & Muskingum routing method at a catchment scale (Farrag & Corzo, 2021), Ha
(spatial discretization - cell size, temporal resolution, parameterization approaches and calibration (Farrag et al., 2021)).
- 
+ 
Hapi
@@ -73,8 +47,8 @@ Main Features
- Visualization module for animating the results of the distributed model, and the meteorological inputs
- Optimization module, for calibrating the model based on the Harmony search method
-The recent version of Hapi (Hapi 1.0.1) integrates the global hydrological parameters obtained by Beck et al., (2016),
-to reduce model complexity and uncertainty of parameters.
+Hapi integrates the global hydrological parameters obtained by Beck et al., (2016), to reduce model complexity
+and uncertainty of parameters.
Future work
-------------
@@ -105,6 +79,16 @@ Rusli, S. R., Yudianto, D. & Liu, J. tao. (2015) Effects of temporal variability
Installing hapi
===============
+## pip
+
+To install the last release, use pip. The distribution is named `HAPI-Nile` and the import package is `hapi`.
+
+```
+pip install HAPI-Nile
+```
+
+## conda
+
Installing `hapi` from the `conda-forge` channel can be achieved by:
```
@@ -117,29 +101,28 @@ It is possible to list all of the versions of `hapi` available on your platform
conda search hapi --channel conda-forge
```
-## Install from Github
-to install the last development to time you can install the library from github
-```
-pip install git+https://github.com/MAfarrag/HAPI
-```
+## Install from GitHub
+
+To install the latest development version, install the library from GitHub:
-## pip
-to install the last release you can easly use pip
```
-pip install HAPI-Nile==1.6.0
+pip install git+https://github.com/serapeum-org/Hapi
```
Quick start
===========
```
- >>> import Hapi
+ >>> import hapi
```
-[other code samples](https://hapi-hm.readthedocs.io/en/latest/?badge=latest)
+[other code samples](https://serapeum-org.github.io/Hapi)
## Naming Convention
[PEP8](https://peps.python.org/pep-0008/#naming-conventions)
- module names: lower case word, preferably one word if not, separate words with underscores (module.py, my_module.py).
- class names: PascalCase (Model, MyClass).
-- class method/function: CamelCase(getFile, readConfig).should have a verb one them, because they perform some action
+- class method/function: snake_case (get_file, read_config). They should have a verb in them, because they perform some action.
+
+Some CamelCase entry points survive from earlier releases (for example `Run.RunHapi` and `Wrapper.RRMModel`)
+because examples and downstream code still call them. New methods are written in snake_case.
diff --git a/docs/api/distrrm.md b/docs/api/distrrm.md
new file mode 100644
index 000000000..d2b256811
--- /dev/null
+++ b/docs/api/distrrm.md
@@ -0,0 +1,4 @@
+# Distributed Rainfall-Runoff Model
+
+## DistributedRRM
+::: hapi.rrm.distrrm.DistributedRRM
diff --git a/docs/dev/Installation.md b/docs/dev/Installation.md
index 020b9aa40..10c6b324f 100644
--- a/docs/dev/Installation.md
+++ b/docs/dev/Installation.md
@@ -2,141 +2,113 @@
## Stable release
-Please install Hapi in a Virtual environment so that its requirements don't tamper with your system's python
-`Hapi` works with all Python versions
+Please install Hapi in a virtual environment so that its requirements do not tamper with your system's Python.
+Hapi requires **Python 3.11 or newer**.
-## conda
-the easiest way to install `Hapi` is using `conda` package manager. `Hapi` is available in the [conda-forge](https://conda-forge.org/) channel. To install
-you can use the following command:
+The distribution is published as `HAPI-Nile`, and the import package is `hapi`.
+
+## pip
```shell
-conda install -c conda-forge hapi
+pip install HAPI-Nile
```
-If this works it will install Hapi with all dependencies including Python and gdal,
-and you skip the rest of the installation instructions.
+To install a specific release:
-## Installing Python and gdal dependencies
-
-The main dependencies for Hapi are an installation of Python 2.7+, and gdal
+```shell
+pip install HAPI-Nile=={release}
+```
-## Installing Python
+## conda
-For Python we recommend using the Anaconda Distribution for Python 3, which is available
-for download from https://www.anaconda.com/download/. The installer gives the option to
-add `python` to your `PATH` environment variable. We will assume in the instructions
-below that it is available in the path, such that `python`, `pip`, and `conda` are
-all available from the command line.
+`Hapi` is also available in the [conda-forge](https://conda-forge.org/) channel:
-Note that there is no hard requirement specifically for Anaconda's Python, but often it
-makes installation of required dependencies easier using the conda package manager.
+```shell
+conda install -c conda-forge hapi
+```
-## Install as a conda environment
+## Dependencies
-The easiest and most robust way to install Hapi is by installing it in a separate
-conda environment. In the root repository directory there is an `environment.yml` file.
-This file lists all dependencies. Either use the `environment.yml` file from the master branch
-(please note that the master branch can change rapidly and break functionality without warning),
-or from one of the releases {release}.
+Hapi installs its dependencies automatically. GDAL does **not** need to be installed
+separately: it ships vendored inside the `pyramids-gis` wheel, which Hapi depends on.
-Run this command to start installing all Hapi dependencies:
+You can check the versions of the libraries Hapi depends on at
+[libraries.io](https://libraries.io/github/serapeum-org/Hapi).
-```shell
-conda env create -f environment.yml
-```
+## Install from GitHub
-This creates a new environment with the name `hapi`. To activate this environment in
-a session, run:
+To install the latest development version (the HEAD of the `main` branch):
```shell
-activate hapi
+pip install git+https://github.com/serapeum-org/Hapi.git
```
-For the installation of Hapi there are two options (from the Python Package Index (PyPI)
-or from Github). To install a release of Hapi from the PyPI (available from release 2018.1):
+
+Or a specific release:
```shell
-pip install HAPI-Nile=={release}
+pip install git+https://github.com/serapeum-org/Hapi.git@{release}
```
## From sources
-The sources for HapiSM can be downloaded from the
-[Github repo](https://github.com/serapeum-org/Hapi).
+The sources can be downloaded from the [GitHub repo](https://github.com/serapeum-org/Hapi).
-You can either clone the public repository:
+Clone the public repository:
```shell
-$ git clone git://github.com/serapeum-org/Hapi
+git clone https://github.com/serapeum-org/Hapi.git
```
-Or download the [tarball](https://github.com/serapeum-org/Hapi/tarball/master):
-```shell
-$ curl -OJL https://github.com/serapeum-org/Hapi/tarball/master
-```
-Once you have a copy of the source, you can install it with:
+Or download the [tarball](https://github.com/serapeum-org/Hapi/tarball/main):
```shell
-$ python setup.py install
+curl -OJL https://github.com/serapeum-org/Hapi/tarball/main
```
+Once you have a copy of the source, install it with:
-To install directly from GitHub (from the HEAD of the master branch):
-
-+ `pip install git+https://github.com/serapeum-org/Hapi.git`
-
-or from Github from a specific release:
-
-+ `pip install git+https://github.com/serapeum-org/Hapi.git@{release}`
-
-Now you should be able to start this environment's Python with `python`, try
-`import Hapi` to see if the package is installed.
-
-
-More details on how to work with conda environments can be found here:
-https://conda.io/docs/user-guide/tasks/manage-environments.html
-
+```shell
+cd Hapi
+pip install .
+```
-If you are planning to make changes and contribute to the development of Hapi, it is
-best to make a git clone of the repository, and do a editable install in the location
-of you clone. This will not move a copy to your Python installation directory, but
-instead create a link in your Python installation pointing to the folder you installed
-it from, such that any changes you make there are directly reflected in your install.
+## Development install
-+ `git clone https://github.com/serapeum-org/Hapi.git`
-+ `cd Hapi`
-+ `activate Hapi`
-+ `pip install -e .`
+If you are planning to make changes and contribute to the development of Hapi, make a
+git clone of the repository and do an editable install, so that any change you make is
+directly reflected in your environment:
-Alternatively, if you want to avoid using `git` and simply want to test the latest
-version from the `master` branch, you can replace the first line with downloading
-a zip archive from GitHub: https://github.com/serapeum-org/Hapi/archive/master.zip
-[libraries.io](https://libraries.io/github/serapeum-org/Hapi).
+```shell
+git clone https://github.com/serapeum-org/Hapi.git
+cd Hapi
+pip install -e .
+```
-## Install using pip
+### Using pixi
-Besides the recommended conda environment setup described above, you can also install
-Hapi with `pip`. For the more difficult to install Python dependencies, it is best to
-use the conda package manager:
+The repository is managed with [pixi](https://pixi.sh), which resolves the whole
+environment (including the test and documentation tooling) from `pyproject.toml` and
+`pixi.lock`. This is what CI runs, so it is the most reliable way to reproduce a
+development environment:
```shell
-conda install numpy scipy gdal netcdf4 pyproj
+pixi install -e dev
+pixi run -e dev test-all
```
-you can check [libraries.io](https://libraries.io/github/serapeum-org/Hapi). to check versions of the libraries
-
+Useful tasks:
-Then install a release {release} of Hapi (available from release 2018.1) with pip:
-
-```shell
-pip install HAPI-Nile=={release}
-````
+| Task | Command |
+| --- | --- |
+| Run the main test suite | `pixi run -e dev main` |
+| Run the whole test suite | `pixi run -e dev test-all` |
+| Type check | `pixi run -e dev mypy` |
+| Serve the documentation locally | `pixi run -e docs mkdocs serve` |
## Check if the installation is successful
-To check it the install is successful, go to the examples directory and run the following command:
-
```shell
-`python -m Hapi.*******`
+python -c "import hapi; print(hapi.__name__)"
```
This should run without errors.
@@ -144,10 +116,5 @@ This should run without errors.
> **Note:**
- This documentation was generated on today
-
- Documentation for the development version:
- https://Hapi.readthedocs.org/en/latest/
-
- Documentation for the stable version:
- https://Hapi.readthedocs.org/en/stable/
+ The documentation is built with MkDocs and published to GitHub Pages:
+ https://serapeum-org.github.io/Hapi
diff --git a/docs/examples/distributed-model-calib.md b/docs/examples/distributed-model-calib.md
index 42c38bdb0..4e4c0dd82 100644
--- a/docs/examples/distributed-model-calib.md
+++ b/docs/examples/distributed-model-calib.md
@@ -35,7 +35,7 @@ class Catchment:
- To instantiate the object you need to provide the `name`, `statedate`, `enddate`, and the `SpatialResolution`
```python
-from Hapi.catchment import Catchment
+from hapi.catchment import Catchment
start = "2009-01-01"
end = "2011-12-31"
@@ -61,17 +61,17 @@ ParPathRun = Path + "/Parameter set-Avg/"
- Then use the each method in the object to read the coresponding data
```python
-Coello.readRainfall(PrecPath)
-Coello.readTemperature(TempPath)
-Coello.readET(Evap_Path)
-Coello.readFlowAcc(FlowAccPath)
-Coello.readFlowDir(FlowDPath)
+Coello.read_rainfall(PrecPath)
+Coello.read_temperature(TempPath)
+Coello.read_et(Evap_Path)
+Coello.read_flow_acc(FlowAccPath)
+Coello.read_flow_dir(FlowDPath)
```
- To read the parameters you need to provide whether you need to consider the snow subroutine or not
```python
Snow = 0
-Coello.readParameters(ParPathRun, Snow)
+Coello.read_parameters(ParPathRun, Snow)
```
## 2- Lumped Model
@@ -80,23 +80,23 @@ Coello.readParameters(ParPathRun, Snow)
and define the initial condition, and catchment area.
```python
-from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 as HBV
+from hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 as HBV
CatchmentArea = 1530
InitialCond = [0,5,5,5,0]
-Coello.readLumpedModel(HBV, CatchmentArea, InitialCond)
+Coello.read_lumped_model(HBV, CatchmentArea, InitialCond)
```
- If the Inpus are consistent in dimensions you will get a the following message
-
+
- to check the performance of the model we need to read the gauge hydrographs
```python
-Coello.readGaugeTable("Hapi/Data/00inputs/Discharge/stations/gauges.csv", FlowAccPath)
+Coello.read_gauge_table("Hapi/Data/00inputs/Discharge/stations/gauges.csv", FlowAccPath)
GaugesPath = "Hapi/Data/00inputs/Discharge/stations/"
-Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d")
+Coello.read_discharge_gauges(GaugesPath, column='id', fmt="%Y-%m-%d")
```
## 3-Run Object
@@ -105,7 +105,7 @@ Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d")
- import the Run object and use the `Catchment` object as a parameter to the `Run` object, then call the RunHapi method to start the simulation
```python
-from Hapi.run import Run
+from hapi.run import Run
Run.RunHapi(Coello)
```
- the result of the simulation will be stored as attributes in the Catchment object as follow
@@ -132,10 +132,10 @@ Outputs:
## 4-Extract Hydrographs
- The final step is to extract the simulated Hydrograph from the cells at the location of the gauges to compare
-- The `extractDischarge` method extracts the hydrographs, however you have to provide in the gauge file the coordinates of the gauges with the same coordinate system of the `FlowAcc` raster
+- The `extract_discharge` method extracts the hydrographs, however you have to provide in the gauge file the coordinates of the gauges with the same coordinate system of the `FlowAcc` raster
```python
-Coello.extractDischarge(Factor=Coello.GaugesTable['area ratio'].tolist())
+Coello.extract_discharge(factor=Coello.GaugesTable['area ratio'].tolist())
for i in range(len(Coello.GaugesTable)):
gaugeid = Coello.GaugesTable.loc[i,'id']
@@ -149,155 +149,92 @@ for i in range(len(Coello.GaugesTable)):
print("Pearson CC= " + str(round(Coello.Metrics.loc['Pearson-CC',gaugeid],2)))
print("R2 = " + str(round(Coello.Metrics.loc['R2',gaugeid],2)))
```
-- The `extractDischarge` will print the performance metics
+- The `extract_discharge` will print the performance metics
## 5-Visualization
- Firts type of visualization we can do with the results is to compare the gauge hydrograph with the simulatied hydrographs
-- Call the `plotHydrograph` method and provide the period you want to visualize with the order of the gauge
+- Call the `plot_hydrograph` method and provide the period you want to visualize with the order of the gauge
```python
gaugei = 5
plotstart = "2009-01-01"
plotend = "2011-12-31"
-Coello.plotHydrograph(plotstart, plotend, gaugei)
+Coello.plot_hydrograph(plotstart, plotend, gaugei)
```

## 6-Animation
-- the best way to visualize time series of distributed data is through visualization, for theis reason, The `Catchment` object has `plotDistributedResults` method which can animate all the results of the model
+- The best way to visualize a time series of distributed data is an animation. The `Catchment` object
+ has a `plot_distributed_results` method which animates any of the model results.
-```python
-"""
-=============================================================================
-AnimateArray(Arr, Time, NoElem, TicksSpacing = 2, Figsize=(8,8), PlotNumbers=True,
- NumSize= 8, Title = 'Total Discharge',titlesize = 15, Backgroundcolorthreshold=None,
- cbarlabel = 'Discharge m3/s', cbarlabelsize = 12, textcolors=("white","black"),
- Cbarlength = 0.75, Interval = 200,cmap='coolwarm_r', Textloc=[0.1,0.2],
- Gaugecolor='red',Gaugesize=100, ColorScale = 1,gamma=1./2.,linthresh=0.0001,
- linscale=0.001, midpoint=0, orientation='vertical', rotation=-90,IDcolor = "blue",
- IDsize =10, **kwargs)
-=============================================================================
-Parameters
-----------
-Arr : [array]
- the array you want to animate.
-Time : [dataframe]
- dataframe contains the date of values.
-NoElem : [integer]
- Number of the cells that has values.
-TicksSpacing : [integer], optional
- Spacing in the colorbar ticks. The default is 2.
-Figsize : [tuple], optional
- figure size. The default is (8,8).
-PlotNumbers : [bool], optional
- True to plot the values intop of each cell. The default is True.
-NumSize : integer, optional
- size of the numbers plotted intop of each cells. The default is 8.
-Title : [str], optional
- title of the plot. The default is 'Total Discharge'.
-titlesize : [integer], optional
- title size. The default is 15.
-Backgroundcolorthreshold : [float/integer], optional
- threshold value if the value of the cell is greater, the plotted
- numbers will be black and if smaller the plotted number will be white
- if None given the maxvalue/2 will be considered. The default is None.
-textcolors : TYPE, optional
- Two colors to be used to plot the values i top of each cell. The default is ("white","black").
-cbarlabel : str, optional
- label of the color bar. The default is 'Discharge m3/s'.
-cbarlabelsize : integer, optional
- size of the color bar label. The default is 12.
-Cbarlength : [float], optional
- ratio to control the height of the colorbar. The default is 0.75.
-Interval : [integer], optional
- number to controlthe speed of the animation. The default is 200.
-cmap : [str], optional
- color style. The default is 'coolwarm_r'.
-Textloc : [list], optional
- location of the date text. The default is [0.1,0.2].
-Gaugecolor : [str], optional
- color of the points. The default is 'red'.
-Gaugesize : [integer], optional
- size of the points. The default is 100.
-IDcolor : [str]
- the ID of the Point.The default is "blue".
-IDsize : [integer]
- size of the ID text. The default is 10.
-ColorScale : integer, optional
- there are 5 options to change the scale of the colors. The default is 1.
- 1- ColorScale 1 is the normal scale
- 2- ColorScale 2 is the power scale
- 3- ColorScale 3 is the SymLogNorm scale
- 4- ColorScale 4 is the PowerNorm scale
- 5- ColorScale 5 is the BoundaryNorm scale
- ------------------------------------------------------------------
- gamma : [float], optional
- value needed for option 2 . The default is 1./2..
- linthresh : [float], optional
- value needed for option 3. The default is 0.0001.
- linscale : [float], optional
- value needed for option 3. The default is 0.001.
- midpoint : [float], optional
- value needed for option 5. The default is 0.
- ------------------------------------------------------------------
-orientation : [string], optional
- orintation of the colorbar horizontal/vertical. The default is 'vertical'.
-rotation : [number], optional
- rotation of the colorbar label. The default is -90.
-**kwargs : [dict]
- keys:
- Points : [dataframe].
- dataframe contains two columns 'cell_row', and cell_col to
- plot the point at this location
-
-Returns
--------
-animation.FuncAnimation.
+The keyword arguments are forwarded to `cleopatra.array_glyph.ArrayGlyph.animate`; see its
+documentation for the full list. The commonly used ones are `figsize`, `interval`, `cmap`,
+`ticks_spacing`, `color_scale` (`"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`,
+`"midpoint"`), `display_cell_value`, `background_color_threshold`, `text_loc`, `point_color`,
+`pid_color` and `pid_size`.
-"""
-```
-- choose the period of time you want to animate and the result (total discharge, upper zone discharge, soil moisture,...)
+`option` selects the variable to animate:
+
+| option | variable | option | variable |
+| --- | --- | --- | --- |
+| 1 | Total discharge | 7 | Lower zone |
+| 2 | Upper zone discharge | 8 | Water content |
+| 3 | Ground water | 9 | Precipitation |
+| 4 | Snow pack | 10 | Evapotranspiration |
+| 5 | Soil moisture | 11 | Temperature |
+| 6 | Upper zone | | |
```python
plotstart = "2009-01-01"
-plotend = "2009-02-01"
-
-Anim = Coello.plotDistributedResults(plotstart, plotend, Figsize=(9,9), Option = 1,threshold=160, PlotNumbers=True,
- TicksSpacing = 5,Interval = 200, Gauges=True, cmap='inferno', Textloc=[0.1,0.2],
- Gaugecolor='red',ColorScale = 1, IDcolor='blue', IDsize=25)
+plotend = "2009-04-20"
+
+anim = Coello.plot_distributed_results(
+ plotstart,
+ plotend,
+ option=1,
+ gauges=True,
+ figsize=(9, 9),
+ ticks_spacing=5,
+ interval=200,
+ cmap="inferno",
+ color_scale="linear",
+ display_cell_value=True,
+ text_loc=[0.1, 0.2],
+ point_color="red",
+ pid_color="blue",
+ pid_size=25,
+)
```

-- to save the animation
+- To save the animation, the output format is taken from the file extension. GIF is written with
+ Pillow; `mov`, `avi` and `mp4` need [FFmpeg](https://ffmpeg.org/) installed and available on your
+ system.
- - Please visit https://ffmpeg.org/ and download a version of ffmpeg compitable with your operating system
- - Copy the content of the folder and paste it in the "c:/user/.matplotlib/ffmpeg-static/"
- or
-
- - define the path where the downloaded folder "ffmpeg-static" exist to matplotlib using the following lines
-
-```python
-import matplotlib as mpl
-mpl.rcParams['animation.ffmpeg_path'] = "path where you saved the ffmpeg.exe/ffmpeg.exe"
-```
```python
-Path = SaveTo + "anim.gif"
-Coello.saveAnimation(VideoFormat="gif",Path=Path,SaveFrames=3)
+Coello.save_animation("results/anim.gif", fps=2)
```
## 7-Save the result into rasters
- To save the results as rasters provide the period and the path
```python
-StartDate = "2009-01-01"
-EndDate = "2010-04-20"
-Prefix = 'Qtot_'
-
-Coello.saveResults(FlowAccPath, Result=1, StartDate=StartDate, EndDate=EndDate, Path="F:/02Case studies/Coello/Hapi/Model/results/", Prefix=Prefix)
+start = "2009-01-01"
+end = "2010-04-20"
+prefix = "Qtot_"
+
+Coello.save_results(
+ FlowAccPath,
+ result=1,
+ start=start,
+ end=end,
+ path="results/",
+ prefix=prefix,
+)
```
diff --git a/docs/examples/distributed-model-run.md b/docs/examples/distributed-model-run.md
index 7e0f5dcff..7513163d0 100644
--- a/docs/examples/distributed-model-run.md
+++ b/docs/examples/distributed-model-run.md
@@ -5,8 +5,8 @@ After preparing all the meteorological, GIS inputs required for the model, and E
import numpy as np
import datetime as dt
from osgeo import gdal
-from Hapi.calibration import Calibration
-import Hapi.rrm.hbv_bergestrom92 as HBV
+from hapi.calibration import Calibration
+import hapi.rrm.hbv_bergestrom92 as HBV
import statista.descriptors as metrics
@@ -32,20 +32,20 @@ name = "Coello"
Coello = Calibration(name, Sdate, Edate, SpatialResolution = "Distributed")
# Meteorological & GIS Data
-Coello.readRainfall(PrecPath)
-Coello.readTemperature(TempPath)
-Coello.readET(Evap_Path)
+Coello.read_rainfall(PrecPath)
+Coello.read_temperature(TempPath)
+Coello.read_et(Evap_Path)
-Coello.readFlowAcc(FlowAccPath)
-Coello.readFlowDir(FlowDPath)
+Coello.read_flow_acc(FlowAccPath)
+Coello.read_flow_dir(FlowDPath)
# Lumped Model
-Coello.readLumpedModel(HBV, AreaCoeff, InitialCond)
+Coello.read_lumped_model(HBV, AreaCoeff, InitialCond)
# Gauges Data
-Coello.readGaugeTable(Path+"/stations/gauges.csv", FlowAccPath)
+Coello.read_gauge_table(Path+"/stations/gauges.csv", FlowAccPath)
GaugesPath = Path+"/stations/"
-Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d")
+Coello.read_discharge_gauges(GaugesPath, column='id', fmt="%Y-%m-%d")
@@ -55,7 +55,7 @@ Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d")
- The `DistParameters` distribute the parameter vector on the cells following some spatial logic (same set of parameters for all cells, different parameters for each cell, HRU, different parameters for each class in an additional map)
```python
-from Hapi.rrm.distparameters import DistParameters as DP
+from hapi.rrm.parameters import Parameters as DP
raster = gdal.Open(FlowAccPath)
#-------------
@@ -84,7 +84,7 @@ coordinates = Coello.GaugesTable[['id','x','y','weight']][:]
OF_args = [coordinates]
def objective_function(Qobs, Qout, q_uz_routed, q_lz_trans, coordinates):
- Coello.extractDischarge()
+ Coello.extract_discharge()
all_errors=[]
# error for all internal stations
for i in range(len(coordinates)):
@@ -121,12 +121,12 @@ OptimizationArgs=[ApiObjArgs, pll_type, ApiSolveArgs]
## Run Calibration algorithm
```python
-cal_parameters = Coello.runCalibration(SpatialVarFun, OptimizationArgs,printError=0)
+cal_parameters = Coello.run_calibration(SpatialVarFun, OptimizationArgs,printError=0)
```
## Save results
```python
SpatialVarFun.Function(Coello.Parameters, kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb)
-SpatialVarFun.saveParameters(SaveTo)
+SpatialVarFun.save_parameters(SaveTo)
```
diff --git a/docs/examples/lumped-model-calibration.md b/docs/examples/lumped-model-calibration.md
index ab871238d..ca76a97b6 100644
--- a/docs/examples/lumped-model-calibration.md
+++ b/docs/examples/lumped-model-calibration.md
@@ -6,10 +6,10 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps
```python
import pandas as pd
import datetime as dt
- import Hapi.rrm.hbv_bergestrom92 as HBVLumped
- from Hapi.calibration import Calibration
- from Hapi.routing import Routing
- from Hapi.run import Run
+ import hapi.rrm.hbv_bergestrom92 as HBVLumped
+ from hapi.calibration import Calibration
+ from hapi.routing import Routing
+ from hapi.run import Run
import statista.metrics as metrics
Parameterpath = Comp + "/data/lumped/Coello_Lumped2021-03-08_muskingum.txt"
@@ -21,7 +21,7 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps
name = "Coello"
Coello = Calibration(name, start, end)
- Coello.readLumpedInputs(MeteoDataPath)
+ Coello.read_lumped_inputs(MeteoDataPath)
# catchment area
@@ -31,7 +31,7 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps
InitialCond = [0,10,10,10,0]
# no snow subroutine
Snow = 0
- Coello.readLumpedModel(HBVLumped, AreaCoeff, InitialCond)
+ Coello.read_lumped_model(HBVLumped, AreaCoeff, InitialCond)
# Calibration boundaries
UB = pd.read_csv(Path + "/lumped/UB-3.txt", index_col = 0, header = None)
@@ -41,7 +41,7 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps
LB = LB[1].tolist()
Maxbas = True
- Coello.readParametersBounds(UB, LB, Snow, Maxbas=Maxbas)
+ Coello.read_parameters_bound(UB, LB, Snow, Maxbas=Maxbas)
parameters = []
# Routing
@@ -52,7 +52,7 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps
### Objective function
# outlet discharge
- Coello.readDischargeGauges(Path+"Qout_c.csv", fmt="%Y-%m-%d")
+ Coello.read_discharge_gauges(Path+"Qout_c.csv", fmt="%Y-%m-%d")
OF_args=[]
OF=metrics.rmse
diff --git a/docs/examples/lumped-model-run.md b/docs/examples/lumped-model-run.md
index b26e15c22..45466ba65 100644
--- a/docs/examples/lumped-model-run.md
+++ b/docs/examples/lumped-model-run.md
@@ -4,10 +4,10 @@ To run the HBV lumped model inside Hapi you need to prepare the meteorological i
- First load the prepared lumped version of the HBV module inside Hapi, the triangular routing function and the wrapper function that runs the lumped model `RUN`.
```python
-import Hapi.rrm.hbv_bergestrom92 as HBVLumped
-from Hapi.run import Run
-from Hapi.catchment import Catchment
-from Hapi.routing import Routing
+import hapi.rrm.hbv_bergestrom92 as HBVLumped
+from hapi.run import Run
+from hapi.catchment import Catchment
+from hapi.routing import Routing
```
- read the meteorological data, data has be in the form of numpy array with the following order [rainfall, ET, Temp, Tm], ET is the potential evapotranspiration, Temp is the temperature (C), and Tm is the long term monthly average temperature.
@@ -20,7 +20,7 @@ start = "2009-01-01"
end = "2011-12-31"
name = "Coello"
Coello = Catchment(name, start, end)
-Coello.readLumpedInputs(MeteoDataPath)
+Coello.read_lumped_inputs(MeteoDataPath)
```
- Meteorological data
@@ -29,7 +29,7 @@ start = "2009-01-01"
end = "2011-12-31"
name = "Coello"
Coello = Catchment(name, start, end)
-Coello.readLumpedInputs(MeteoDataPath)
+Coello.read_lumped_inputs(MeteoDataPath)
```
- Lumped model
prepare the initial conditions, cathcment area and the lumped model.
@@ -40,7 +40,7 @@ AreaCoeff = 1530
# [Snow pack, Soil moisture, Upper zone, Lower Zone, Water content]
InitialCond = [0,10,10,10,0]
-Coello.readLumpedModel(HBVLumped, AreaCoeff, InitialCond)
+Coello.read_lumped_model(HBVLumped, AreaCoeff, InitialCond)
```
- Load the pre-estimated parameters
snow option (if you want to simulate snow accumulation and snow melt or not)
@@ -48,7 +48,7 @@ Coello.readLumpedModel(HBVLumped, AreaCoeff, InitialCond)
```python
Snow = 0 # no snow subroutine
# if routing using Maxbas True, if Muskingum False
-Coello.readParameters(Parameterpath, Snow)
+Coello.read_parameters(Parameterpath, Snow)
```
- Prepare the routing options.
@@ -92,16 +92,16 @@ To plot the calculated and measured discharge import matplotlib
gaugei = 0
plotstart = "2009-01-01"
plotend = "2011-12-31"
-Coello.plotHydrograph(plotstart, plotend, gaugei, Title= "Lumped Model")
+Coello.plot_hydrograph(plotstart, plotend, gaugei, Title= "Lumped Model")
```

- To save the results
```python
-StartDate = "2009-01-01"
-EndDate = "2010-04-20"
+start = "2009-01-01"
+end = "2010-04-20"
Path = SaveTo + "Results-Lumped-Model" + str(dt.datetime.now())[0:10] + ".txt"
-Coello.saveResults(Result=5, StartDate=StartDate, EndDate=EndDate, Path=Path)
+Coello.save_results(result=5, start=start, end=end, path=Path)
```
diff --git a/docs/examples/meteo-inputs.md b/docs/examples/meteo-inputs.md
index dc8bc1bb6..34723115d 100644
--- a/docs/examples/meteo-inputs.md
+++ b/docs/examples/meteo-inputs.md
@@ -15,99 +15,25 @@ To be able to run the hydrologic simulation with Hapi the following meteorologic
Distributed meteorological data can be obtain from gauge data with some interpolation method or from remote sensing data
-# Remote Sensing Module
+# Remote Sensing Data
-The remote sensing module includes two classes to download ECMWF, and CHRIPS data
+The remote sensing module that used to download CHIRPS and ECMWF data was moved out of
+Hapi into its own package, [earth2observe](https://pypi.org/project/earth2observe/):
-# CHRIPS
-The Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a quasi-global rainfall data set. As its title suggests it combines data from real-time observing meteorological stations with infra-red data to estimate precipitation. The data set runs from 1981 to the near present.
-
-CHIRPS incorporates 0.05° resolution satellite imagery with in-situ station data to create gridded rainfall time series for trend analysis and seasonal drought monitoring
-
-There are two main data sets. The first is quasi-global and covers the whole world from 50°N to 50°S. The second covers Africa and parts of the Middle-East. It covers the area from 40°N to 40°S and from 20°W to 55°E. The global data set has data on a 0.05° grid at monthly, pentad and daily times steps. This is equivalent to 31 km2. The ‘Africa’ data set also includes data at a 0.10° grid at a 6-hour time step.
-
-CHRIPS data are uploaded into a ftp server therefore and can be downloaded through the `CHRIPS` class in the `remotesensing` module
-
-- First import the class from the remotesensing module
-
-```python
-from Hapi.remotesensing import CHIRPS
-```
-
-- Create the object with the following information
- - Period of time (start and end date)
- - Temporal resolution (daily/monthy)
- - Extend (Longitude/Latitude)
- - Path (directory to save the downloaded data)
-
-```python
-StartDate = '2009-01-01'
-EndDate = '2009-01-10'
-Time = 'daily'
-lat = [4.190755,4.643963]
-lon = [-75.649243,-74.727286]
-Path = "directory to save the data"
-Coello = CHIRPS(StartDate=StartDate, EndDate=EndDate, Time=Time,
- latlim=lat , lonlim=lon, Path=Path)
-```
-
-- Call the `Download` method
-
-```python
-Coello.Download()
+```shell
+pip install earth2observe
```
-- A Progress bar will appear and be updated with percent of the download
-
-
-- If the period is long and the Download method can run in parallel, to activate the parallel mode enter the number of cores with the keyword argument `cores`
-
-```python
-Coello.Download(cores=4)
-```
+earth2observe covers the same sources Hapi used to handle:
-# ECMWF
-ERA-Interim data set is a global atmospheric reanalysis that is available from 1 January 1979 to 31 August 2019
+- **CHIRPS** — the Climate Hazards Group InfraRed Precipitation with Station data, a
+ quasi-global rainfall data set combining satellite imagery with in-situ station data
+ on a 0.05 degree grid, from 1981 to near present.
+- **ECMWF** — the ERA-Interim archive, which requires a registered account and an API
+ key set up on your machine (see the
+ [ECMWF registration](https://apps.ecmwf.int/registration/) and the
+ [API key instructions](https://confluence.ecmwf.int/display/WEBAPI/Access+ECMWF+Public+Datasets#AccessECMWFPublicDatasets-key)).
-The ERA-Interim data assimilation and forecast suite produces:
-• four analyses per day, at 00, 06, 12 and 18 UTC;
-• two 10-day forecasts per day, initialized from analyses at 00 and 12 UTC
-
-- Most archived ERA-Interim data can be downloaded from the ECMWF Data Server at [http://data.ecmwf.int/data](http://data.ecmwf.int/data).
-
-- The ERA-Interim Archive is part of ECMWF’s Meteorological Archive and Retrieval System (MARS), which is accessible to registered users
-- The RemoteSensing and the ECMWF classes can retrieve the data from the ECMWF servers, if you are registered and setup the API Key in your machine
-
-
-so inorder to be able to use the following code to download ECMWF data you need to
-- register and setup your account on the [ECMWF website](https://apps.ecmwf.int/registration/).
-
-- Install ECMWF key — [instructions here](https://confluence.ecmwf.int/display/WEBAPI/Access+ECMWF+Public+Datasets#AccessECMWFPublicDatasets-key).
-
-- ERA-Interim data set has a lot of meteorological variables which you can download
-- You need to provide the name of the variable using the `Variables` object
-- `Variables` contains the tame of the variable you need to give to the `ECMWF` object to get and the unit and description
-
-```python
-from Hapi.remotesensing import Variables
-Vars = Variables('daily')
-Vars.__str__()
-```
-
-For information about ECMWF data, see [https://apps.ecmwf.int/codes/grib/param-db/](https://apps.ecmwf.int/codes/grib/param-db/).
-
-```python
-StartDate = '2009-01-01'
-EndDate = '2009-01-10'
-Time = 'daily'
-lat = [4.190755,4.643963]
-lon = [-75.649243,-74.727286]
-Path = "/data/satellite_data/"
-# Temperature, Evapotranspiration
-variables = ['T','E']
-
-Coello = RS(StartDate=StartDate, EndDate=EndDate, Time=Time,
- latlim=lat , lonlim=lon, Path=Path, Vars=variables)
-
-Coello.ECMWF(Waitbar=1)
-```
+Once the rasters are downloaded, prepare them for the model with `hapi.inputs.Inputs`,
+which aligns every raster to the catchment DEM — see
+[GIS inputs](gis-inputs.md) and [Parameters](parameters.md).
diff --git a/docs/examples/parameters.md b/docs/examples/parameters.md
index afad79541..ae905badd 100644
--- a/docs/examples/parameters.md
+++ b/docs/examples/parameters.md
@@ -11,7 +11,7 @@ The only input we need to extract parameters to our catchment is the DEM or any
- import the class from the inputs module
```python
-from Hapi.inputs import Inputs
+from hapi.inputs import Inputs
```
- define the paths to the DEM and the directory to save the parameters
@@ -19,10 +19,10 @@ from Hapi.inputs import Inputs
dem_path = "../../data/GIS/Hapi_GIS_Data/acc4000.tif"
outputpath = "../../data/parameters/03/"
```
-- call the `extractParameters` method
+- call the `extract_parameters` method
```python
-Inputs.extractParameters(dem_path, '03', AsRaster=True, SaveTo=outputpath)
+Inputs.extract_parameters(dem_path, '03', AsRaster=True, SaveTo=outputpath)
```
@@ -36,7 +36,7 @@ To Extract the parameters range needed for the Calibration you have to prepare a
import geopandas as gpd
import numpy as np
import pandas as pd
-import Hapi.inputs as IN
+import hapi.inputs as IN
BasinF = "Path to shapefile"
Basin = gpd.read_file(BasinF)
@@ -45,16 +45,16 @@ ind = ["tt","sfcf","cfmax","cwh","cfr","fc","beta","lp","k0","k1","k2","uzl","pe
Par = pd.DataFrame(index = ind)
```
-the `inputs` module in Hapi has a `extractParametersBoundaries` method to overlay the basin shapefile with the global parameters rasters and extract the max and min parameter values within the basin and plots your basin shapefile in top of the world map to make sure of the projection transformation from whatever projection your basin shapefile to the `WGS64` that the parameters rasters have
+the `inputs` module in Hapi has a `extract_parameters_boundaries` method to overlay the basin shapefile with the global parameters rasters and extract the max and min parameter values within the basin and plots your basin shapefile in top of the world map to make sure of the projection transformation from whatever projection your basin shapefile to the `WGS64` that the parameters rasters have
```python
# extract parameters boundaries
-Par['UB'], Par['LB'] = IN.extractParametersBoundaries(Basin)
+Par['UB'], Par['LB'] = IN.extract_parameters_boundaries(Basin)
```
-To extract the parameters from one of the ten scenarios developed to derive the Global model `extractParameters` method takes the number of the scenario as a string and return the parameters
+To extract the parameters from one of the ten scenarios developed to derive the Global model `extract_parameters` method takes the number of the scenario as a string and return the parameters
```python
# extract parameters in a specific scenarion from the 10 scenarios
-Par['1'] = IN.extractParameters(Basin,"01")
+Par['1'] = IN.extract_parameters(Basin,"01")
```
the extracted parameters needs to be modified incase you are not considering the snow bucket the first 5 parameters are disregarded
diff --git a/docs/img/check_inputs.PNG b/docs/img/check_inputs.png
similarity index 100%
rename from docs/img/check_inputs.PNG
rename to docs/img/check_inputs.png
diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js
new file mode 100644
index 000000000..0d950d8e0
--- /dev/null
+++ b/docs/javascripts/mathjax.js
@@ -0,0 +1,12 @@
+window.MathJax = {
+ tex: {
+ inlineMath: [[String.raw`\(`, String.raw`\)`]],
+ displayMath: [[String.raw`\[`, String.raw`\]`]],
+ processEscapes: true,
+ processEnvironments: true
+ },
+ options: {
+ ignoreHtmlClass: ".*|",
+ processHtmlClass: "arithmatex"
+ }
+};
diff --git a/mkdocs.yml b/mkdocs.yml
index 232f53ad3..9e10cca62 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -62,6 +62,11 @@ plugins:
group_by_category: false
inherited_members: false
docstring_style: google
+ docstring_options:
+ # The docstrings write returns as "type: description", not
+ # "name: description", so griffe must not read the leading token
+ # as a return value name.
+ returns_named_value: false
- table-reader
- tags
- mike:
diff --git a/pyproject.toml b/pyproject.toml
index fd934ece6..c5da01f32 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -105,45 +105,28 @@ include = ["hapi", "hapi.*"]
[tool.setuptools.package-data]
hapi = ["*.yaml", "include/gdal/*.h"]
-# Ruff replaces black + isort + flake8 with one tool. Because a single engine
-# formats and sorts imports against one line-length, the formatter and the
-# import sorter cannot disagree the way black and isort did. Ruff lints and
-# formats both .py and .ipynb natively.
[tool.ruff]
line-length = 88
-target-version = "py311" # requires-python floors at 3.11
+target-version = "py311"
[tool.ruff.format]
-# black had skip-string-normalization = true → preserve existing quotes.
quote-style = "preserve"
[tool.ruff.lint]
-# The old flake8 select was E/F/W (B and T4 were inert: flake8-bugbear /
-# flake8-print were never installed). Add I (isort) and UP (pyupgrade) to fold
-# in the standalone isort hook and modern-typing upgrades.
select = ["E", "F", "W", "I", "UP"]
-# Mirror the old [tool.flake8].ignore. W503 is dropped — ruff does not
-# implement it (formatter-owned pycodestyle code black conflicted with).
ignore = ["E203", "E226", "E266", "E501", "E701", "E722", "C901", "E741", "E731"]
[tool.ruff.lint.isort]
-# Classify the import roots explicitly so sorting never depends on the filesystem.
-# The legacy examples still import the pre-rename `Hapi.*` package: on a
-# case-insensitive filesystem (Windows/macOS) ruff resolves `Hapi` to `src/hapi` and
-# sorts those imports as first-party, while Linux CI finds no such package and sorts
-# them as third-party — so the same file failed lint on whichever platform had not
-# formatted it last. Naming both spellings pins the answer on every platform.
+# Both spellings are named so sorting does not depend on filesystem case
+# sensitivity: the legacy examples still import the pre-rename `Hapi.*` package.
known-first-party = ["hapi"]
known-third-party = ["Hapi"]
[tool.ruff.lint.per-file-ignores]
-# flake8 excluded examples/ and tests/ from linting — drop E/F/W and UP there,
-# keeping only import-sorting (I), which isort did run everywhere. This keeps
-# the migration's lint churn on shipped src/.
+# Lint shipped src/ only; tests and examples keep import sorting (I) alone.
"tests/**" = ["E", "F", "W", "UP"]
"examples/**" = ["E", "F", "W", "UP"]
-# Tutorial notebooks import next to first use and run setup before imports —
-# both legitimately trip E402, which stays enforced for real modules.
+# Notebooks import next to first use, which legitimately trips E402.
"*.ipynb" = ["E402"]
[tool.bandit]
@@ -156,8 +139,7 @@ source = ["src/hapi"]
[tool.coverage.report]
show_missing = true
-# Current branch coverage is ~52%; the floor locks that level in so it can only
-# move up. Raise it as coverage improves.
+# Locks in the current level; raise it as coverage improves.
fail_under = 50
[tool.commitizen]
@@ -180,10 +162,8 @@ style = [
]
[tool.mypy]
-# Checker target is 3.12 even though `requires-python` floors at 3.11: numpy
-# ships PEP 695 `type` statements in its stubs, which mypy only parses when the
-# target is >= 3.12. This only widens the *typing* target; actual 3.11 runtime
-# compatibility is covered by the py311 leg of the test matrix, not by mypy.
+# 3.12, not the 3.11 floor: numpy's stubs use PEP 695 `type` statements, which
+# mypy only parses at >= 3.12. The py311 test leg covers the runtime floor.
python_version = "3.12"
mypy_path = "src"
packages = ["hapi"]
@@ -209,20 +189,17 @@ module = [
"hapi.calibration",
"hapi.wrapper",
]
-# These modules use a builder pattern: attributes are typed X | None in
-# __init__ and populated by separate read_*() methods before use. Methods
-# assume attributes are non-None but mypy cannot verify call order.
-# Fix: add assert-based narrowing helpers (e.g. _require_spatial_data())
-# that return non-None types, then remove these suppressions one by one.
-# Error count without overrides: ~257 across these 4 modules.
+# These modules are built by successive read_*() calls: attributes are typed
+# X | None and populated before use, which mypy cannot verify. Removing the
+# suppressions needs assert-based narrowing helpers first (~257 errors).
disable_error_code = [
- "union-attr", # .attr on X | None (159 sites)
- "attr-defined", # .attr on None (11 sites)
- "arg-type", # passing X | None where X expected (13 sites)
- "misc", # slice index, None not callable (13 sites)
- "operator", # arithmetic on X | None (4 sites)
- "index", # indexing X | None (2 sites)
- "call-overload", # numpy overload mismatches on X | None (2 sites)
+ "union-attr",
+ "attr-defined",
+ "arg-type",
+ "misc",
+ "operator",
+ "index",
+ "call-overload",
]
[[tool.mypy.overrides]]
@@ -233,15 +210,12 @@ disable_error_code = ["empty-body"]
[tool.pytest.ini_options]
env = [
"MPLBACKEND=Agg",
- # D: sets the value only when the variable is not already set, so an
- # explicitly exported HAPI_DATA_DIR still wins over this default.
+ # D: only sets the default; an exported HAPI_DATA_DIR still wins.
"D:HAPI_DATA_DIR=src/hapi/parameters",
]
pythonpath = ["src"]
testpaths = ["tests"]
-# Fail (don't hang) on a stuck test: pytest-timeout aborts any single test that
-# runs longer than `timeout` and prints its stack; `faulthandler_timeout`
-# (stdlib) dumps an earlier all-thread traceback for diagnosis.
+# Abort a stuck test instead of hanging CI.
timeout = 300
faulthandler_timeout = 120
markers = [
@@ -280,7 +254,7 @@ description = "Run all test suite"
[tool.pixi.tasks.notebooks]
cmd = "pytest --nbval --nbval-lax --verbose -p no:cacheprovider examples"
-description = "Check notebooks — currently red: every notebook still needs migration off the pre-rename Hapi package"
+description = "Check notebooks (they still need migration off the pre-rename Hapi package)"
[tool.pixi.tasks.publish-pypi]
cmd = "twine upload --non-interactive --repository pypi dist/*"
diff --git a/src/hapi/calibration.py b/src/hapi/calibration.py
index 277e98249..1e8cc4380 100644
--- a/src/hapi/calibration.py
+++ b/src/hapi/calibration.py
@@ -78,7 +78,9 @@ def __init__(
self.OFArgs: list | None = None
self.OFvalue: float | None = None
- def read_objective_function(self, objective_function: Callable[..., Any], args):
+ def read_objective_function(
+ self, objective_function: Callable[..., Any], args: list | None
+ ):
"""Read and store the objective function and its arguments.
Takes the objective function and any additional arguments that
@@ -157,12 +159,17 @@ def extract_discharge(
# return error
- def run_calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
+ def run_calibration(
+ self,
+ spatial_var_fun: Callable[..., Any],
+ optimization_args: list,
+ print_error: int | None = None,
+ ):
"""Run the calibration algorithm for the distributed hydrological model.
Executes the Harmony Search optimization algorithm to calibrate
parameters for the conceptual distributed hydrological model.
- The method distributes parameters spatially using ``SpatialVarFun``,
+ The method distributes parameters spatially using ``spatial_var_fun``,
runs the RRM model via ``Wrapper.RRMModel``, and evaluates
performance using the stored objective function.
@@ -178,19 +185,19 @@ def run_calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
gauge metadata.
Args:
- SpatialVarFun: Spatial variable function object with a
+ spatial_var_fun: Spatial variable function object with a
``Function`` method that distributes parameters and a
``Par3d`` attribute holding the 3D parameter array, plus
``no_parameters`` and ``no_elem`` attributes.
- OptimizationArgs: A list of three elements:
- - ``OptimizationArgs[0]`` (dict): Harmony Search API
+ optimization_args: A list of three elements:
+ - ``optimization_args[0]`` (dict): Harmony Search API
objective arguments (e.g., HMS, HMCR, PAR).
- - ``OptimizationArgs[1]``: Parallel type for the
+ - ``optimization_args[1]``: Parallel type for the
optimizer.
- - ``OptimizationArgs[2]`` (dict): Solver arguments with
+ - ``optimization_args[2]`` (dict): Solver arguments with
keys ``"store_sol"``, ``"display_opts"``,
``"store_hst"``, and ``"hot_start"``.
- printError: If not 0, prints the error value and parameters
+ print_error: If not 0, prints the error value and parameters
at each iteration. Default is None.
Returns:
@@ -224,18 +231,18 @@ def run_calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
# basic inputs
# check if all inputs are included
- # assert all(["p2","init_st","UB","LB","snow "][i] in Basic_inputs.keys()
- # for i in range(4)), "Basic_inputs should contain ['p2','init_st','UB','LB']"
+ # assert all(["p2","init_st","UB","LB","snow "][i] in basic_inputs.keys()
+ # for i in range(4)), "basic_inputs should contain ['p2','init_st','UB','LB']"
### optimization
# get arguments
- ApiObjArgs = OptimizationArgs[0]
- pll_type = OptimizationArgs[1]
- ApiSolveArgs = OptimizationArgs[2]
+ api_obj_args = optimization_args[0]
+ pll_type = optimization_args[1]
+ api_solve_args = optimization_args[2]
# check optimization arguement
- assert type(ApiObjArgs) is dict, "store_history should be 0 or 1"
- assert type(ApiSolveArgs) is dict, "history_fname should be of type string "
+ assert type(api_obj_args) is dict, "store_history should be 0 or 1"
+ assert type(api_solve_args) is dict, "history_fname should be of type string "
print("Calibration starts")
@@ -243,10 +250,10 @@ def run_calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
def opt_fun(par):
try:
# distribute the parameters
- SpatialVarFun.Function(
+ spatial_var_fun.Function(
par
- ) # , kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb
- self.Parameters = SpatialVarFun.Par3d
+ ) # , kub=spatial_var_fun.Kub, klb=spatial_var_fun.Klb
+ self.Parameters = spatial_var_fun.Par3d
# run the model
Wrapper.RRMModel(self)
# calculate performance of the model
@@ -254,7 +261,7 @@ def opt_fun(par):
error = self.objective_function(
self.QGauges, *[self.GaugesTable]
) # self.qout, self.quz_routed, self.qlz_translated,
- f = list(range(9, len(par), SpatialVarFun.no_parameters))
+ f = list(range(9, len(par), spatial_var_fun.no_parameters))
g = list()
for i in range(len(f)):
k = par[f[i]]
@@ -267,7 +274,7 @@ def opt_fun(par):
raise ValueError(OBJECTIVE_FN_ARGS_ERROR) from e
# print error
- if printError != 0:
+ if print_error != 0:
print(round(error, 3))
print(par)
@@ -286,18 +293,18 @@ def opt_fun(par):
opt_prob.addObj("f")
- for i in range(SpatialVarFun.no_elem):
+ for i in range(spatial_var_fun.no_elem):
opt_prob.addCon("g" + str(i) + "-1", "i")
opt_prob.addCon("g" + str(i) + "-2", "i")
print(opt_prob)
- opt_engine = HSapi(pll_type=pll_type, options=ApiObjArgs)
+ opt_engine = HSapi(pll_type=pll_type, options=api_obj_args)
- store_sol = ApiSolveArgs["store_sol"]
- display_opts = ApiSolveArgs["display_opts"]
- store_hst = ApiSolveArgs["store_hst"]
- hot_start = ApiSolveArgs["hot_start"]
+ store_sol = api_solve_args["store_sol"]
+ display_opts = api_solve_args["display_opts"]
+ store_hst = api_solve_args["store_hst"]
+ hot_start = api_solve_args["hot_start"]
res = opt_engine(
opt_prob,
@@ -312,7 +319,12 @@ def opt_fun(par):
return res
- def FW1Calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
+ def FW1Calibration(
+ self,
+ spatial_var_fun: Callable[..., Any],
+ optimization_args: list,
+ print_error: int | None = None,
+ ):
"""Run calibration using the FW1 (Focussed Width-1) routing scheme.
Executes the Harmony Search optimization algorithm to calibrate
@@ -330,18 +342,18 @@ def FW1Calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
gauge metadata.
Args:
- SpatialVarFun: Spatial variable function object with a
+ spatial_var_fun: Spatial variable function object with a
``Function`` method that distributes parameters and a
``Par3d`` attribute holding the 3D parameter array.
- OptimizationArgs: A list of three elements:
- - ``OptimizationArgs[0]`` (dict): Harmony Search API
+ optimization_args: A list of three elements:
+ - ``optimization_args[0]`` (dict): Harmony Search API
objective arguments (e.g., HMS, HMCR, PAR).
- - ``OptimizationArgs[1]``: Parallel type for the
+ - ``optimization_args[1]``: Parallel type for the
optimizer.
- - ``OptimizationArgs[2]`` (dict): Solver arguments with
+ - ``optimization_args[2]`` (dict): Solver arguments with
keys ``"store_sol"``, ``"display_opts"``,
``"store_hst"``, and ``"hot_start"``.
- printError: If not 0, prints the error value and parameters
+ print_error: If not 0, prints the error value and parameters
at each iteration. Default is None.
Returns:
@@ -375,18 +387,18 @@ def FW1Calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
# basic inputs
# check if all inputs are included
- # assert all(["p2","init_st","UB","LB","snow "][i] in Basic_inputs.keys()
- # for i in range(4)), "Basic_inputs should contain ['p2','init_st','UB','LB']"
+ # assert all(["p2","init_st","UB","LB","snow "][i] in basic_inputs.keys()
+ # for i in range(4)), "basic_inputs should contain ['p2','init_st','UB','LB']"
### optimization
# get arguments
- ApiObjArgs = OptimizationArgs[0]
- pll_type = OptimizationArgs[1]
- ApiSolveArgs = OptimizationArgs[2]
+ api_obj_args = optimization_args[0]
+ pll_type = optimization_args[1]
+ api_solve_args = optimization_args[2]
# check optimization arguement
- assert type(ApiObjArgs) is dict, "store_history should be 0 or 1"
- assert type(ApiSolveArgs) is dict, "history_fname should be of type string "
+ assert type(api_obj_args) is dict, "store_history should be 0 or 1"
+ assert type(api_solve_args) is dict, "history_fname should be of type string "
print("Calibration starts")
@@ -394,10 +406,10 @@ def FW1Calibration(self, SpatialVarFun, OptimizationArgs, printError=None):
def opt_fun(par):
try:
# distribute the parameters
- SpatialVarFun.Function(
+ spatial_var_fun.Function(
par
- ) # , kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb, Maskingum=SpatialVarFun.Maskingum
- self.Parameters = SpatialVarFun.Par3d
+ ) # , kub=spatial_var_fun.Kub, klb=spatial_var_fun.Klb, Maskingum=spatial_var_fun.Maskingum
+ self.Parameters = spatial_var_fun.Par3d
# run the model
Wrapper.FW1(self)
# calculate performance of the model
@@ -410,7 +422,7 @@ def opt_fun(par):
raise ValueError(OBJECTIVE_FN_ARGS_ERROR) from e
# print error
- if printError != 0:
+ if print_error != 0:
print(round(error, 3))
print(par)
@@ -428,12 +440,12 @@ def opt_fun(par):
print(opt_prob)
- opt_engine = HSapi(pll_type=pll_type, options=ApiObjArgs)
+ opt_engine = HSapi(pll_type=pll_type, options=api_obj_args)
- store_sol = ApiSolveArgs["store_sol"]
- display_opts = ApiSolveArgs["display_opts"]
- store_hst = ApiSolveArgs["store_hst"]
- hot_start = ApiSolveArgs["hot_start"]
+ store_sol = api_solve_args["store_sol"]
+ display_opts = api_solve_args["display_opts"]
+ store_hst = api_solve_args["store_hst"]
+ hot_start = api_solve_args["hot_start"]
res = opt_engine(
opt_prob,
@@ -448,7 +460,12 @@ def opt_fun(par):
return res
- def lumpedCalibration(self, Basic_inputs, OptimizationArgs, printError=None):
+ def lumpedCalibration(
+ self,
+ basic_inputs: dict,
+ optimization_args: list,
+ print_error: int | None = None,
+ ):
"""Run the calibration algorithm for the lumped hydrological model.
Executes the Harmony Search optimization algorithm to calibrate
@@ -467,21 +484,21 @@ def lumpedCalibration(self, Basic_inputs, OptimizationArgs, printError=None):
- ``dt``: Time step duration.
Args:
- Basic_inputs (dict): Dictionary containing:
+ basic_inputs (dict): Dictionary containing:
- ``"Route"`` (int): Routing flag (1 to enable routing).
- ``"RoutingFn"`` (callable): Routing function to use.
- ``"InitialValues"`` (list, optional): Initial parameter
values for the optimizer. Defaults to an empty list if
not provided.
- OptimizationArgs: A list of three elements:
- - ``OptimizationArgs[0]`` (dict): Harmony Search API
+ optimization_args: A list of three elements:
+ - ``optimization_args[0]`` (dict): Harmony Search API
objective arguments (e.g., HMS, HMCR, PAR).
- - ``OptimizationArgs[1]``: Parallel type for the
+ - ``optimization_args[1]``: Parallel type for the
optimizer.
- - ``OptimizationArgs[2]`` (dict): Solver arguments with
+ - ``optimization_args[2]`` (dict): Solver arguments with
keys ``"store_sol"``, ``"display_opts"``,
``"store_hst"``, and ``"hot_start"``.
- printError: If not 0, prints the error value and constraint
+ print_error: If not 0, prints the error value and constraint
values at each iteration. Default is None.
Returns:
@@ -490,32 +507,34 @@ def lumpedCalibration(self, Basic_inputs, OptimizationArgs, printError=None):
- res[1]: The optimal parameter set.
Raises:
- AssertionError: If ``Basic_inputs`` is missing required keys
+ AssertionError: If ``basic_inputs`` is missing required keys
``"Route"`` or ``"RoutingFn"``, or if optimization
arguments are not dictionaries.
"""
# basic inputs
# check if all inputs are included
- assert all(
- ["Route", "RoutingFn"][i] in Basic_inputs.keys() for i in range(2)
- ), "Basic_inputs should contain ['p2','init_st','UB','LB'] "
-
- Route = Basic_inputs["Route"]
- RoutingFn = Basic_inputs["RoutingFn"]
- if "InitialValues" in Basic_inputs.keys():
- InitialValues = Basic_inputs["InitialValues"]
+ assert all(["Route", "RoutingFn"][i] in basic_inputs for i in range(2)), (
+ "basic_inputs should contain ['p2','init_st','UB','LB'] "
+ )
+
+ route = basic_inputs["Route"]
+ routing_fn = basic_inputs["RoutingFn"]
+ if "InitialValues" in basic_inputs:
+ initial_values = basic_inputs["InitialValues"]
else:
- InitialValues = []
+ initial_values = []
### optimization
# get arguments
- ApiObjArgs = OptimizationArgs[0]
- pll_type = OptimizationArgs[1]
- ApiSolveArgs = OptimizationArgs[2]
+ api_obj_args = optimization_args[0]
+ pll_type = optimization_args[1]
+ api_solve_args = optimization_args[2]
# check optimization arguement
- assert isinstance(ApiObjArgs, dict), "store_history should be 0 or 1"
- assert isinstance(ApiSolveArgs, dict), "history_fname should be of type string "
+ assert isinstance(api_obj_args, dict), "store_history should be 0 or 1"
+ assert isinstance(api_solve_args, dict), (
+ "history_fname should be of type string "
+ )
print("Calibration starts")
@@ -525,7 +544,7 @@ def opt_fun(par):
# parameters
self.Parameters = par
# run the model
- Wrapper.Lumped(self, Route, RoutingFn)
+ Wrapper.Lumped(self, route, routing_fn)
# calculate performance of the model
try:
error = self.objective_function(
@@ -539,7 +558,7 @@ def opt_fun(par):
# the objective function received fewer inputs than it needs
raise ValueError(OBJECTIVE_FN_ARGS_ERROR) from e
- if printError != 0:
+ if print_error != 0:
print(
f"Error = {round(error, 3)} Inequality Const = {np.round(g, 2)}"
)
@@ -554,14 +573,14 @@ def opt_fun(par):
### define the optimization components
opt_prob = Optimization("HBV Calibration", opt_fun)
- if InitialValues != []:
+ if initial_values != []:
for i in range(len(self.LB)):
opt_prob.addVar(
f"x{i}",
type="c",
lower=self.LB[i],
upper=self.UB[i],
- value=InitialValues[i],
+ value=initial_values[i],
)
else:
for i in range(len(self.LB)):
@@ -572,19 +591,19 @@ def opt_fun(par):
opt_prob.addCon("g1", "i")
opt_prob.addCon("g2", "i")
# print(opt_prob)
- opt_engine = HSapi(pll_type=pll_type, options=ApiObjArgs)
+ opt_engine = HSapi(pll_type=pll_type, options=api_obj_args)
- # parse the ApiSolveArgs inputs
+ # parse the api_solve_args inputs
# availablekeys = ['store_sol',"display_opts","store_hst","hot_start"]
- store_sol = ApiSolveArgs["store_sol"]
- display_opts = ApiSolveArgs["display_opts"]
- store_hst = ApiSolveArgs["store_hst"]
- hot_start = ApiSolveArgs["hot_start"]
+ store_sol = api_solve_args["store_sol"]
+ display_opts = api_solve_args["display_opts"]
+ store_hst = api_solve_args["store_hst"]
+ hot_start = api_solve_args["hot_start"]
# for i in range(len(availablekeys)):
- # if availablekeys[i] in ApiSolveArgs.keys():
- # exec(availablekeys[i] + "=" + str(ApiSolveArgs[availablekeys[i]]))
- # print(availablekeys[i] + " = " + str(ApiSolveArgs[availablekeys[i]]))
+ # if availablekeys[i] in api_solve_args.keys():
+ # exec(availablekeys[i] + "=" + str(api_solve_args[availablekeys[i]]))
+ # print(availablekeys[i] + " = " + str(api_solve_args[availablekeys[i]]))
res = opt_engine(
opt_prob,
diff --git a/src/hapi/catchment.py b/src/hapi/catchment.py
index 31c9ba6a6..cc4fa1981 100644
--- a/src/hapi/catchment.py
+++ b/src/hapi/catchment.py
@@ -19,7 +19,7 @@
import inspect
import math
import os
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
import geopandas as gpd
import matplotlib.dates as dates
@@ -1197,7 +1197,7 @@ def plot_distributed_results(
fmt: str = "%Y-%m-%d",
option: int = 1,
gauges: bool = False,
- **kwargs,
+ **kwargs: Any,
):
"""Animate distributed model results or meteorological inputs.
diff --git a/src/hapi/inputs.py b/src/hapi/inputs.py
index 6f42d05c1..ac77e6b15 100644
--- a/src/hapi/inputs.py
+++ b/src/hapi/inputs.py
@@ -249,7 +249,7 @@ def extract_parameters(
@staticmethod
def create_lumped_inputs(
path: str,
- regex_string=r"\d{4}.\d{2}.\d{2}",
+ regex_string: str = r"\d{4}.\d{2}.\d{2}",
date: bool = True,
file_name_data_fmt: str | None = None,
start: str | None = None,
diff --git a/src/hapi/routing.py b/src/hapi/routing.py
index 4331f9981..a052cc0f2 100644
--- a/src/hapi/routing.py
+++ b/src/hapi/routing.py
@@ -62,7 +62,7 @@ def muskingum(inflow, Qinitial, k, x, dt):
Returns:
numpy.ndarray: Routed outflow hydrograph with the same
- length as ``inflow``, rounded to four decimal places.
+ length as ``inflow``, rounded to four decimal places.
Examples:
>>> import numpy as np
@@ -119,7 +119,7 @@ def muskingum_v(
Returns:
numpy.ndarray: Routed outflow hydrograph with the same
- length as ``inflow``.
+ length as ``inflow``.
Examples:
>>> import numpy as np
@@ -163,7 +163,7 @@ def tf(maxbas):
Returns:
numpy.ndarray: Array of normalized weights with length
- ``maxbas`` that sum to 1.0.
+ ``maxbas`` that sum to 1.0.
Examples:
>>> from hapi.routing import Routing
@@ -204,7 +204,7 @@ def triangular_routing_2(q, maxbas=1):
Returns:
numpy.ndarray: Routed discharge time series with the same
- length as ``q``.
+ length as ``q``.
Raises:
AssertionError: If ``maxbas`` is less than 1.
@@ -249,8 +249,8 @@ def calculate_weights(maxbas):
Returns:
numpy.ndarray: Array of normalized routing weights. The
- length is ``floor(MAXBAS)`` for integer values, or
- ``floor(MAXBAS) + 1`` for non-integer values.
+ length is ``floor(MAXBAS)`` for integer values, or
+ ``floor(MAXBAS) + 1`` for non-integer values.
Examples:
>>> from hapi.routing import Routing
@@ -344,7 +344,7 @@ def triangular_routing_1(Q, MAXBAS):
Returns:
numpy.ndarray: Routed output hydrograph with the same
- length as ``Q``.
+ length as ``Q``.
Examples:
>>> import numpy as np
diff --git a/src/hapi/rrm/base_model.py b/src/hapi/rrm/base_model.py
index 4e6f72256..281457a29 100644
--- a/src/hapi/rrm/base_model.py
+++ b/src/hapi/rrm/base_model.py
@@ -95,7 +95,7 @@ def precipitation(
Returns:
tuple[float, float]: A tuple of ``(rainfall, snowfall)``
- in mm.
+ in mm.
Examples:
>>> from hapi.rrm.hbv import HBV
@@ -134,10 +134,10 @@ def snow(
Returns:
tuple[float, float, float]: A tuple of
- ``(infiltration, wc_new, sp_new)`` where
- ``infiltration`` is the water draining into the soil
- [mm], ``wc_new`` is the updated liquid water content
- [mm], and ``sp_new`` is the updated snow pack [mm].
+ ``(infiltration, wc_new, sp_new)`` where
+ ``infiltration`` is the water draining into the soil
+ [mm], ``wc_new`` is the updated liquid water content
+ [mm], and ``sp_new`` is the updated snow pack [mm].
Examples:
>>> from hapi.rrm.hbv import HBV
@@ -196,9 +196,9 @@ def soil(
Returns:
tuple[float, float]: A tuple of ``(sm_new, uz_int_1)``
- where ``sm_new`` is the new soil moisture [mm] and
- ``uz_int_1`` is the new direct runoff into the upper
- zone [mm].
+ where ``sm_new`` is the new soil moisture [mm] and
+ ``uz_int_1`` is the new direct runoff into the upper
+ zone [mm].
Examples:
>>> from hapi.rrm.hbv import HBV
@@ -239,10 +239,10 @@ def response(
Returns:
tuple[float, float, float]: A tuple of
- ``(q_new, uz_new, lz_new)`` where ``q_new`` is the total
- discharge [m^3/s], ``uz_new`` is the updated upper zone
- storage [mm], and ``lz_new`` is the updated lower zone
- storage [mm].
+ ``(q_new, uz_new, lz_new)`` where ``q_new`` is the total
+ discharge [m^3/s], ``uz_new`` is the updated upper zone
+ storage [mm], and ``lz_new`` is the updated lower zone
+ storage [mm].
Examples:
>>> from hapi.rrm.hbv import HBV
@@ -269,7 +269,7 @@ def routing(self, q: np.ndarray, maxbas: int = 1) -> np.ndarray:
Returns:
numpy.ndarray: Routed discharge time series with the same
- length as ``q``.
+ length as ``q``.
Raises:
AssertionError: If ``maxbas`` is less than 1.
@@ -321,13 +321,13 @@ def simulate(
Returns:
tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]:
- A tuple of ``(q_uz, q_lz, states)`` where:
+ A tuple of ``(q_uz, q_lz, states)`` where:
- - ``q_uz``: Upper zone discharge array of length
+ - ``q_uz``: Upper zone discharge array of length
``n+1``.
- - ``q_lz``: Lower zone discharge array of length
+ - ``q_lz``: Lower zone discharge array of length
``n+1``.
- - ``states``: Model states array of shape ``(n+1, 5)``.
+ - ``states``: Model states array of shape ``(n+1, 5)``.
Examples:
>>> import numpy as np
diff --git a/src/hapi/rrm/distrrm.py b/src/hapi/rrm/distrrm.py
index 78808f586..b51238ba1 100644
--- a/src/hapi/rrm/distrrm.py
+++ b/src/hapi/rrm/distrrm.py
@@ -43,7 +43,7 @@ def run_lumped_model(Model):
``state_variables``, ``quz``, and ``qlz``.
Args:
- Model: A catchment model object carrying the following
+ Model (Catchment): A catchment model object carrying the following
attributes:
- ``rows`` (int): Number of grid rows.
@@ -120,7 +120,7 @@ def SpatialRouting(Model):
``quz_routed``, ``qlz_translated``, and ``Qtot``.
Args:
- Model: A catchment model object carrying the following
+ Model (Catchment): A catchment model object carrying the following
attributes:
- ``rows`` (int): Number of grid rows.
@@ -225,7 +225,7 @@ def DistMaxbas1(Model):
The ``Model.quz`` array is modified in place.
Args:
- Model: A catchment model object carrying the following
+ Model (Catchment): A catchment model object carrying the following
attributes:
- ``rows`` (int): Number of grid rows.
@@ -257,7 +257,7 @@ def DistMaxbas2(Model):
The ``Model.quz`` array is modified in place.
Args:
- Model: A catchment model object carrying the following
+ Model (Catchment): A catchment model object carrying the following
attributes:
- ``rows`` (int): Number of grid rows.
@@ -319,7 +319,7 @@ def Dist_HBV2(
cells and converted to m3/s.
Args:
- conceptual_model: Lumped model object with a ``simulate``
+ conceptual_model (BaseConceptualModel): Lumped model object with a ``simulate``
method.
lakecell (list[int]): Two-element list ``[row, col]``
giving the grid indices of the lake cell.
diff --git a/src/hapi/rrm/hbv.py b/src/hapi/rrm/hbv.py
index f333ff392..7c4b9959b 100644
--- a/src/hapi/rrm/hbv.py
+++ b/src/hapi/rrm/hbv.py
@@ -133,7 +133,7 @@ def precipitation(temp, ltt, utt, prec, rfcf, sfcf): # type: ignore[override]
Returns:
tuple[float, float]: A tuple of ``(rainfall, snowfall)``
- in mm.
+ in mm.
Examples:
Temperature above the upper threshold produces only
@@ -208,10 +208,10 @@ def snow( # type: ignore[override]
Returns:
tuple[float, float, float]: A tuple of
- ``(infiltration, wc_new, sp_new)`` where
- ``infiltration`` is the water draining into the soil
- [mm], ``wc_new`` is the updated water content [mm],
- and ``sp_new`` is the updated snow pack [mm].
+ ``(infiltration, wc_new, sp_new)`` where
+ ``infiltration`` is the water draining into the soil
+ [mm], ``wc_new`` is the updated water content [mm],
+ and ``sp_new`` is the updated snow pack [mm].
Examples:
When temperature exceeds the melt threshold, snow melts
@@ -312,9 +312,9 @@ def soil( # type: ignore[override] # tfac,
Returns:
tuple[float, float]: A tuple of ``(sm_new, uz_int_1)``
- where ``sm_new`` is the new soil moisture [mm] and
- ``uz_int_1`` is the new direct runoff into the upper
- zone [mm].
+ where ``sm_new`` is the new soil moisture [mm] and
+ ``uz_int_1`` is the new direct runoff into the upper
+ zone [mm].
Examples:
Compute soil moisture update for a warm day with
@@ -382,11 +382,11 @@ def response( # type: ignore[override] # tfac,area,
Returns:
tuple[float, float, float, float]: A tuple of
- ``(q_0, q_1, uz_new, lz_new)`` where ``q_0`` is the
- upper zone discharge [mm], ``q_1`` is the lower zone
- discharge [mm], ``uz_new`` is the updated upper zone
- storage [mm], and ``lz_new`` is the updated lower zone
- storage [mm].
+ ``(q_0, q_1, uz_new, lz_new)`` where ``q_0`` is the
+ upper zone discharge [mm], ``q_1`` is the lower zone
+ discharge [mm], ``uz_new`` is the updated upper zone
+ storage [mm], and ``lz_new`` is the updated lower zone
+ storage [mm].
Examples:
Compute discharge from upper and lower zone storages:
@@ -444,7 +444,7 @@ def tf(maxbas) -> np.ndarray:
Returns:
numpy.ndarray: Array of normalized weights with length
- ``maxbas``.
+ ``maxbas``.
Examples:
>>> from hapi.rrm.hbv import HBV
@@ -482,7 +482,7 @@ def routing(self, q, maxbas=1):
Returns:
numpy.ndarray: Routed discharge time series with the same
- length as ``q``.
+ length as ``q``.
Raises:
AssertionError: If ``maxbas`` is less than 1.
@@ -550,10 +550,10 @@ def step_run(
Returns:
tuple[float, float, list[float]]: A tuple of
- ``(q_uz, q_lz, states)`` where ``q_uz`` is the upper
- zone discharge [mm], ``q_lz`` is the lower zone
- discharge [mm], and ``states`` is a list of five
- updated state variables ``[sp, sm, uz, lz, wc]``.
+ ``(q_uz, q_lz, states)`` where ``q_uz`` is the upper
+ zone discharge [mm], ``q_lz`` is the lower zone
+ discharge [mm], and ``states`` is a list of five
+ updated state variables ``[sp, sm, uz, lz, wc]``.
Raises:
AssertionError: If ``snow=1`` and the parameter vector
diff --git a/src/hapi/rrm/parameters.py b/src/hapi/rrm/parameters.py
index 97abe4225..9d69acc34 100644
--- a/src/hapi/rrm/parameters.py
+++ b/src/hapi/rrm/parameters.py
@@ -28,7 +28,7 @@ class Parameters:
def __init__(
self,
- raster,
+ raster: Dataset,
no_parameters: int,
no_lumped_par: int = 0,
lumped_par_pos: list[int] | None = None,
@@ -191,7 +191,7 @@ def __init__(
pass
- def par3d(self, par_g): # , kub=1,klb=0.5, Maskingum=True
+ def par3d(self, par_g: list | np.ndarray): # , kub=1,klb=0.5, Maskingum=True
"""Distribute parameters horizontally across grid cells.
Takes a list of parameters (saved as one column or generated as a
@@ -278,7 +278,9 @@ def par3d(self, par_g): # , kub=1,klb=0.5, Maskingum=True
# klb
# )
- def par3d_lumped(self, par_g): # , kub=1, klb=0.5, Maskingum = True
+ def par3d_lumped(
+ self, par_g: list | np.ndarray
+ ): # , kub=1, klb=0.5, Maskingum = True
r"""Distribute lumped parameters horizontally across grid cells.
Takes a list of parameters (saved as one column or generated as a
@@ -318,7 +320,9 @@ def par3d_lumped(self, par_g): # , kub=1, klb=0.5, Maskingum = True
# self.Par3d[self.celli[i],self.cellj[i],-1], self.Par3d[self.celli[i],self.cellj[i],-2],kub,klb)
@staticmethod
- def calculate_k(x, position, upper_bound, lower_bound):
+ def calculate_k(
+ x: float, position: int, upper_bound: float, lower_bound: float
+ ) -> float:
"""Calculate K parameter for Muskingum routing.
Takes the value of x parameter and generates 100 random values of
@@ -336,7 +340,7 @@ def calculate_k(x, position, upper_bound, lower_bound):
Returns:
The K parameter value corresponding to the given position
- within the constrained range.
+ within the constrained range.
"""
# k has to be smaller than this constraint
constraint1 = 0.5 * 1 / (1 - x)
@@ -351,9 +355,11 @@ def calculate_k(x, position, upper_bound, lower_bound):
generated_k = np.linspace(constraint1, constraint2, 50)
k = generated_k[int(round(position, 0))]
- return k
+ return float(k)
- def par2d_lumped_k1_lake(self, par_g, no_parameters_lake): # ,kub,klb
+ def par2d_lumped_k1_lake(
+ self, par_g: list | np.ndarray, no_parameters_lake: int
+ ): # ,kub,klb
"""Distribute parameters with a lumped K1 and lake parameters.
Takes a list of parameters and distributes them horizontally on
@@ -412,7 +418,7 @@ def par2d_lumped_k1_lake(self, par_g, no_parameters_lake): # ,kub,klb
# return self.Par3d, lake_par
- def hydrologic_response_units(self, par_g): # ,kub=1,klb=0.5
+ def hydrologic_response_units(self, par_g: list | np.ndarray): # ,kub=1,klb=0.5
"""Distribute parameters using Hydrologic Response Units (HRUs).
Takes a list of parameters (saved as one column or generated as a
@@ -503,7 +509,12 @@ def hydrologic_response_units(self, par_g): # ,kub=1,klb=0.5
self.Par3d[self.raster_array == self.values[i]] = self.Par2d[:, i]
@staticmethod
- def hru_hand(dem, flow_direction, flow_path_length, river):
+ def hru_hand(
+ dem: Dataset,
+ flow_direction: Dataset,
+ flow_path_length: Dataset,
+ river: Dataset,
+ ) -> tuple[np.ndarray, np.ndarray]:
"""Calculate Height Above Nearest Drainage (HAND) for HRU classification.
Calculates inputs for the HAND method for land use
@@ -546,66 +557,107 @@ def hru_hand(dem, flow_direction, flow_path_length, river):
fpl_a = flow_path_length.read_array(band=0)
# trace the flow direction to the nearest river reach and store the location
- # of that nearst reach
+ # of that nearest reach
+ nearest_network = Parameters._trace_nearest_drainage(
+ dem_a, no_val, river_a, fd_index, rows, cols
+ )
+
+ # the elevation difference to the nearest drainage cell is the height above
+ # nearest drainage; the same difference over the flow path length raster is
+ # the distance to that drainage
+ hand = Parameters._difference_to_nearest_drainage(
+ dem_a, dem_a, no_val, nearest_network, rows, cols
+ )
+ dist_to_nearest_drain = Parameters._difference_to_nearest_drainage(
+ fpl_a, dem_a, no_val, nearest_network, rows, cols
+ )
+
+ return hand, dist_to_nearest_drain
+
+ @staticmethod
+ def _trace_nearest_drainage(
+ dem_a: np.ndarray,
+ no_val: float | np.floating,
+ river_a: np.ndarray,
+ fd_index: np.ndarray,
+ rows: int,
+ cols: int,
+ ) -> np.ndarray:
+ """Locate the nearest downstream river cell for every domain cell.
+
+ Args:
+ dem_a (np.ndarray): DEM values, used to identify domain cells.
+ no_val (float): No-data value of the DEM.
+ river_a (np.ndarray): River raster; a value of 1 marks a river cell.
+ fd_index (np.ndarray): Downstream cell indices, shape (rows, cols, 2).
+ rows (int): Number of raster rows.
+ cols (int): Number of raster columns.
+
+ Returns:
+ np.ndarray: Array of shape (rows, cols, 2) holding the row and column
+ of the nearest drainage cell, NaN outside the domain.
+
+ Raises:
+ ValueError: If a flow path runs off the grid, which happens when the
+ catchment boundary has anomalies.
+ """
nearest_network = np.ones((rows, cols, 2)) * np.nan
try:
for i in range(rows):
for j in range(cols):
- if dem_a[i, j] != no_val:
- f = river_a[i, j]
- # a river cell is its own nearest drainage
- new_row = i
- new_cols = j
- old_row = i
- old_cols = j
-
- while f != 1:
- # did not reach the river yet, go to the next downstream cell
- new_row = int(fd_index[old_row, old_cols, 0])
- new_cols = int(fd_index[old_row, old_cols, 1])
- # go to the downstream cell
- f = river_a[new_row, new_cols]
- # the downstream cell becomes the current position
- old_row = new_row
- old_cols = new_cols
- # store the position in the array
- nearest_network[i, j, 0] = new_row
- nearest_network[i, j, 1] = new_cols
-
+ if dem_a[i, j] == no_val:
+ continue
+ # a river cell is its own nearest drainage
+ row, col = i, j
+ while river_a[row, col] != 1:
+ # not at the river yet, step to the downstream cell
+ row, col = (
+ int(fd_index[row, col, 0]),
+ int(fd_index[row, col, 1]),
+ )
+ nearest_network[i, j, 0] = row
+ nearest_network[i, j, 1] = col
except (IndexError, ValueError) as e:
raise ValueError(
"please check the boundaries of your catchment. After cropping the catchment using a polygon, it "
"creates anomalies at the boundary"
) from e
- # calculate the elevation difference between the cell and the nearest drainage cell
- # or height above nearst drainage
- hand = np.ones((rows, cols)) * np.nan
+ return nearest_network
- for i in range(rows):
- for j in range(cols):
- if dem_a[i, j] != no_val:
- hand[i, j] = (
- dem_a[i, j]
- - dem_a[
- int(nearest_network[i, j, 0]), int(nearest_network[i, j, 1])
- ]
- )
+ @staticmethod
+ def _difference_to_nearest_drainage(
+ values: np.ndarray,
+ dem_a: np.ndarray,
+ no_val: float | np.floating,
+ nearest_network: np.ndarray,
+ rows: int,
+ cols: int,
+ ) -> np.ndarray:
+ """Subtract each cell's nearest-drainage value from the cell's own value.
- # calculate the distance to the nearest drainage c ell using flow path length or distance to nearest drainage
- dist_to_nearest_drain = np.ones((rows, cols)) * np.nan
+ Args:
+ values (np.ndarray): Raster to difference (elevation or flow path length).
+ dem_a (np.ndarray): DEM values, used to identify domain cells.
+ no_val (float): No-data value of the DEM.
+ nearest_network (np.ndarray): Nearest drainage indices from
+ `_trace_nearest_drainage`.
+ rows (int): Number of raster rows.
+ cols (int): Number of raster columns.
+ Returns:
+ np.ndarray: The difference for every domain cell, NaN elsewhere.
+ """
+ difference = np.ones((rows, cols)) * np.nan
for i in range(rows):
for j in range(cols):
- if dem_a[i, j] != no_val:
- dist_to_nearest_drain[i, j] = (
- fpl_a[i, j]
- - fpl_a[
- int(nearest_network[i, j, 0]), int(nearest_network[i, j, 1])
- ]
- )
+ if dem_a[i, j] == no_val:
+ continue
+ drain_row = int(nearest_network[i, j, 0])
+ drain_col = int(nearest_network[i, j, 1])
+ difference[i, j] = values[i, j] - values[drain_row, drain_col]
- return hand, dist_to_nearest_drain
+ return difference
def parameters_number(self):
"""Calculate the total number of parameters for the optimization.
@@ -642,7 +694,7 @@ def parameters_number(self):
# if there is no lumped parameters
self.ParametersNO = self.no_elem * self.no_parameters
- def save_parameters(self, path):
+ def save_parameters(self, path: str | None):
"""Save distributed parameters as raster files.
Takes the generated 3D parameter array and saves each parameter
diff --git a/src/hapi/run.py b/src/hapi/run.py
index d8c567d80..0f834537b 100644
--- a/src/hapi/run.py
+++ b/src/hapi/run.py
@@ -8,11 +8,15 @@
from __future__ import annotations
+from collections.abc import Callable
+from typing import Any
+
import numpy as np
import pandas as pd
from loguru import logger
from hapi.catchment import Catchment
+from hapi.catchment import Lake as LakeType
# from hapi.hm.saintvenant import SaintVenant
from hapi.wrapper import Wrapper
@@ -145,7 +149,7 @@ def RunFloodModel(self):
# SV.KinematicRaster(self)
# print("1D model Run has finished")
- def runHAPIwithLake(self, Lake):
+ def runHAPIwithLake(self, lake: LakeType):
"""Run the distributed model with a lake component.
Validates that all input arrays have consistent dimensions and
@@ -154,7 +158,7 @@ def runHAPIwithLake(self, Lake):
the Wrapper.
Args:
- Lake: Lake object containing lake configuration and
+ lake: Lake object containing lake configuration and
meteorological data. Must have a ``MeteoData`` attribute
with shape ``(time_steps, >= 3)`` where columns are
rain, ET, and temperature.
@@ -187,15 +191,15 @@ def runHAPIwithLake(self, Lake):
np.shape(self.Prec)[2] == np.shape(self.ET)[2] == np.shape(self.Temp)[2]
), "all meteorological input data should have the same length"
- assert np.shape(Lake.MeteoData)[0] == np.shape(self.Prec)[2], (
+ assert np.shape(lake.MeteoData)[0] == np.shape(self.Prec)[2], (
"Lake meteorological data has to have the same length as the distributed raster data"
)
- assert np.shape(Lake.MeteoData)[1] >= 3, (
+ assert np.shape(lake.MeteoData)[1] >= 3, (
"Lake Meteo data has to have at least three columns of rain, ET, and Temp"
)
# run the model
- Wrapper.RRMWithlake(self, Lake)
+ Wrapper.RRMWithlake(self, lake)
print("Model Run has finished")
@@ -237,7 +241,7 @@ def runFW1(self):
print("Model Run has finished")
- def RunFW1withLake(self, Lake):
+ def RunFW1withLake(self, lake: LakeType):
"""Run the FW1 distributed model with a lake component.
Validates that all input arrays have consistent dimensions and
@@ -245,7 +249,7 @@ def RunFW1withLake(self, Lake):
then executes the FW1 model with lake routing via the Wrapper.
Args:
- Lake: Lake object containing lake configuration and
+ lake: Lake object containing lake configuration and
meteorological data. Must have a ``MeteoData`` attribute
with shape ``(time_steps, >= 3)`` where columns are
rain, ET, and temperature.
@@ -291,20 +295,20 @@ def RunFW1withLake(self, Lake):
np.shape(self.Prec)[2] == np.shape(self.ET)[2] == np.shape(self.Temp)[2]
), "all meteorological input data should have the same length"
- assert np.shape(Lake.MeteoData)[0] == np.shape(self.Prec)[2], (
+ assert np.shape(lake.MeteoData)[0] == np.shape(self.Prec)[2], (
"Lake meteorological data has to have the same length as the distributed raster data"
)
- assert np.shape(Lake.MeteoData)[1] >= 3, (
+ assert np.shape(lake.MeteoData)[1] >= 3, (
"Lake Meteo data has to have at least three columns rain, ET, and Temp"
)
# run the model
- Wrapper.FW1Withlake(self, Lake)
+ Wrapper.FW1Withlake(self, lake)
def runLumped(
self,
Route: int = 0,
- RoutingFn=None,
+ routing_fn: Callable[..., Any] | None = None,
):
"""Run the lumped conceptual model.
@@ -317,7 +321,7 @@ def runLumped(
Route: Flag to decide whether to route the generated
discharge hydrograph. Use 0 for no routing or 1 to
enable routing. Defaults to 0.
- RoutingFn: Function to route the discharge hydrograph.
+ routing_fn: Function to route the discharge hydrograph.
If None, an empty list is used. Defaults to None.
Note:
@@ -339,8 +343,8 @@ def runLumped(
- ``Snow``: Whether to use the snow subroutine (0 or 1).
- ``q_init``: Initial discharge value.
"""
- if RoutingFn is None and Route != 0:
- raise ValueError("RoutingFn must be a callable when Route != 0")
+ if routing_fn is None and Route != 0:
+ raise ValueError("routing_fn must be a callable when Route != 0")
if self.temporal_resolution.lower() == "daily":
ind = pd.date_range(self.start, self.end, freq="D")
else:
@@ -348,7 +352,7 @@ def runLumped(
Qsim = pd.DataFrame(index=ind)
- Wrapper.Lumped(self, Route, RoutingFn)
+ Wrapper.Lumped(self, Route, routing_fn)
Qsim["q"] = self.Qsim
self.Qsim = Qsim[:]
logger.info("Lumped model run has finished successfully")
diff --git a/tests/calibration/distributed_mode_calib.py b/tests/calibration/distributed_mode_calib.py
index 607d36f53..42059a37e 100644
--- a/tests/calibration/distributed_mode_calib.py
+++ b/tests/calibration/distributed_mode_calib.py
@@ -24,7 +24,7 @@
FlowDPath = Path + "/GIS/fd4000.tif"
CalibPath = Path + "/calibration"
SaveTo = Path + "/results"
-# %% Basic_inputs
+# %% basic_inputs
AreaCoeff = 1530
# [sp,sm,uz,lz,wc]
InitialCond = [0, 5, 5, 5, 0]
@@ -58,7 +58,7 @@
for muskingum parameters k & x include the upper and lower bound in both
UB & LB with the order of Klb then kub
function inside the calibration algorithm is written as following
-par_dist=SpatialVarFun(par,*SpatialVarArgs,kub=kub,klb=klb)
+par_dist=spatial_var_fun(par,*SpatialVarArgs,kub=kub,klb=klb)
"""
raster = Dataset.read_file(FlowAccPath)
@@ -71,7 +71,7 @@
no_lumped_par = 1
lumped_par_pos = [7]
-SpatialVarFun = DP(
+spatial_var_fun = DP(
raster,
no_parameters,
no_lumped_par=no_lumped_par,
@@ -81,7 +81,7 @@
kub=kub,
)
# calculate no of parameters that optimization algorithm is going to generate
-SpatialVarFun.ParametersNO
+spatial_var_fun.ParametersNO
# %% Gauges
Coello.read_gauge_table(Path + "/stations/gauges.csv", FlowAccPath)
GaugesPath = Path + "/stations/"
@@ -134,10 +134,14 @@ def objective_function(Qobs, Qout, q_uz_routed, q_lz_trans, coordinates):
ApiSolveArgs = dict(store_sol=True, display_opts=True, store_hst=True, hot_start=False)
-OptimizationArgs = [ApiObjArgs, pll_type, ApiSolveArgs]
+optimization_args = [ApiObjArgs, pll_type, ApiSolveArgs]
# %% run calibration
-cal_parameters = Coello.runCalibration(SpatialVarFun, OptimizationArgs, printError=0)
+cal_parameters = Coello.runCalibration(
+ spatial_var_fun, optimization_args, print_error=0
+)
# %% convert parameters to rasters
# Coello.Parameters = [0.700, 399, 1.704, 0.1021, 0.4622, 0.6237, 0.1251, 0.005, 59.85, 5.241, 94.91, 0.2075]
-SpatialVarFun.Function(Coello.Parameters, kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb)
-SpatialVarFun.save_parameters(SaveTo)
+spatial_var_fun.Function(
+ Coello.Parameters, kub=spatial_var_fun.Kub, klb=spatial_var_fun.Klb
+)
+spatial_var_fun.save_parameters(SaveTo)
diff --git a/tests/calibration/lumped_calibration.py b/tests/calibration/lumped_calibration.py
index 6dd435e0a..f7b62ba02 100644
--- a/tests/calibration/lumped_calibration.py
+++ b/tests/calibration/lumped_calibration.py
@@ -20,7 +20,7 @@
Coello = Calibration(name, start, end)
Coello.read_lumped_inputs(MeteoDataPath)
-# %% Basic_inputs
+# %% basic_inputs
# catchment area
AreaCoeff = 1530
# temporal resolution
@@ -43,9 +43,9 @@
parameters = []
# Routing
Route = 1
-RoutingFn = Routing.triangular_routing_1
+routing_fn = Routing.triangular_routing_1
-Basic_inputs = dict(Route=Route, RoutingFn=RoutingFn, InitialValues=parameters)
+basic_inputs = dict(Route=Route, RoutingFn=routing_fn, InitialValues=parameters)
# %%
### Objective function
# outlet discharge
@@ -84,11 +84,11 @@
ApiSolveArgs = dict(store_sol=True, display_opts=True, store_hst=True, hot_start=False)
-OptimizationArgs = [ApiObjArgs, pll_type, ApiSolveArgs]
+optimization_args = [ApiObjArgs, pll_type, ApiSolveArgs]
# %%
# run calibration
cal_parameters = Coello.lumpedCalibration(
- Basic_inputs, OptimizationArgs, printError=None
+ basic_inputs, optimization_args, print_error=None
)
print("Objective Function = " + str(round(cal_parameters[0], 2)))
@@ -96,7 +96,7 @@
print("Time = " + str(round(cal_parameters[2]["time"] / 60, 2)) + " min")
# %% run the model
Coello.Parameters = cal_parameters[1]
-Run.runLumped(Coello, Route, RoutingFn)
+Run.runLumped(Coello, Route, routing_fn)
# %% calculate performance criteria
Metrics = dict()
diff --git a/tests/rrm/calibration/test_rrm_calibration.py b/tests/rrm/calibration/test_rrm_calibration.py
index 73fc2b847..432bfb99f 100644
--- a/tests/rrm/calibration/test_rrm_calibration.py
+++ b/tests/rrm/calibration/test_rrm_calibration.py
@@ -45,9 +45,9 @@ def test_lumped_calibration(
parameters = []
# Routing
Route = 1
- RoutingFn = Routing.triangular_routing_1
+ routing_fn = Routing.triangular_routing_1
- Basic_inputs = dict(Route=Route, RoutingFn=RoutingFn, InitialValues=parameters)
+ basic_inputs = dict(Route=Route, RoutingFn=routing_fn, InitialValues=parameters)
# discharge gauges
Coello.read_discharge_gauges(lumped_gauges_path, fmt=coello_gauges_date_fmt)
@@ -77,9 +77,9 @@ def test_lumped_calibration(
store_sol=True, display_opts=True, store_hst=False, hot_start=False
)
- OptimizationArgs = [ApiObjArgs, pll_type, ApiSolveArgs]
+ optimization_args = [ApiObjArgs, pll_type, ApiSolveArgs]
- # cal_parameters = Coello.lumpedCalibration(Basic_inputs, OptimizationArgs, printError=None)
+ # cal_parameters = Coello.lumpedCalibration(basic_inputs, optimization_args, print_error=None)
# assert len(Coello.Qsim) == 1095 and Coello.Qsim.columns.to_list() == ['q']
diff --git a/tests/run/lumped_run.py b/tests/run/lumped_run.py
index 6dc05871f..3916bdbd6 100644
--- a/tests/run/lumped_run.py
+++ b/tests/run/lumped_run.py
@@ -20,7 +20,7 @@
Coello = Catchment(name, start, end)
Coello.read_lumped_inputs(MeteoDataPath)
# %%
-### Basic_inputs
+### basic_inputs
# catchment area
AreaCoeff = 1530
# [Snow pack, Soil moisture, Upper zone, Lower Zone, Water content]
@@ -34,11 +34,11 @@
# %% observed flow
Coello.read_discharge_gauges(Path + "Qout_c.csv", fmt="%Y-%m-%d")
# %% Routing
-# RoutingFn = Routing.triangular_routing_2
-RoutingFn = Routing.muskingum_v
+# routing_fn = Routing.triangular_routing_2
+routing_fn = Routing.muskingum_v
Route = 1
### run the model
-Run.runLumped(Coello, Route, RoutingFn)
+Run.runLumped(Coello, Route, routing_fn)
# %% calculate performance criteria
Metrics = dict()
diff --git a/tests/sensitivity_analysis.py b/tests/sensitivity_analysis.py
index 7b3dd3f16..4e2e624e2 100644
--- a/tests/sensitivity_analysis.py
+++ b/tests/sensitivity_analysis.py
@@ -19,7 +19,7 @@
Coello = Catchment(name, start, end)
Coello.read_lumped_inputs(MeteoDataPath)
-### Basic_inputs
+### basic_inputs
# catchment area
CatArea = 1530
# temporal resolution
@@ -49,11 +49,11 @@
)
### Routing
Route = 1
-# RoutingFn=Routing.triangular_routing_2
-RoutingFn = Routing.muskingum
+# routing_fn=Routing.triangular_routing_2
+routing_fn = Routing.muskingum
# %%
### run the model
-Run.runLumped(Coello, Route, RoutingFn)
+Run.runLumped(Coello, Route, routing_fn)
# %%
Metrics = dict()
@@ -88,7 +88,7 @@
for the lumped HBV model and the RMSE of the calculated discharge.
the first function "Run.runLumped" takes some arguments we need to pass through
- the one_at_a_time method [ConceptualModel,data,p2,init_st,snow,Routing, RoutingFn]
+ the one_at_a_time method [ConceptualModel,data,p2,init_st,snow,Routing, routing_fn]
with the same order in the defined function "wrapper"
the second function is RMSE takes the calculated discharge from the first function
@@ -110,19 +110,19 @@
# For Type 1
-def WrapperType1(Randpar, Route, RoutingFn, Qobs):
+def WrapperType1(Randpar, Route, routing_fn, Qobs):
Coello.Parameters = Randpar
- Run.runLumped(Coello, Route, RoutingFn)
+ Run.runLumped(Coello, Route, routing_fn)
rmse = metrics.rmse(Qobs, Coello.Qsim["q"])
return rmse
# For Type 2
-def WrapperType2(Randpar, Route, RoutingFn, Qobs):
+def WrapperType2(Randpar, Route, routing_fn, Qobs):
Coello.Parameters = Randpar
- Run.runLumped(Coello, Route, RoutingFn)
+ Run.runLumped(Coello, Route, routing_fn)
rmse = metrics.rmse(Qobs, Coello.Qsim["q"])
return rmse, Coello.Qsim["q"]
@@ -136,7 +136,7 @@ def WrapperType2(Randpar, Route, RoutingFn, Qobs):
Sen = SA(parameters, Coello.LB, Coello.UB, fn, n_values=5, return_values=Type)
-Sen.one_at_a_time(Route, RoutingFn, Qobs)
+Sen.one_at_a_time(Route, routing_fn, Qobs)
# %%
From = ""
To = ""