GRsuite
An unofficial, independent Python port of the airGR suite of GR rainfall-runoff models — a personal project, checked value by value against airGR.
This is not an official package, and it is not airGR. GRsuite is one person's work, developed independently and on personal time. It is not produced, endorsed, reviewed or supported by INRAE, and it carries no institutional guarantee. The models, the science and the reference implementation belong to INRAE's HYCAR unit; what is mine is the translation into Python and the mistakes in it.
If your work needs the reference implementation, use airGR — and please report problems found here to this repository's issue tracker, never to the airGR maintainers.
GR4J, GR5J, GR6J, GR2M, GR1A, GR4H, GR5H and the CemaNeige snow module — the models hydrologists have been running in R for twenty years, now in Python with no R dependency, no Fortran toolchain, and no reimplementation risk.
Every model output, every criterion and every calibrated parameter set in this package has been checked against airGR 1.7.9 itself: 502 quantities — 1.23 million individual values — agree to 2 × 10⁻¹³, and across 500 calibrations on 100 French catchments the largest deviation is 1.9 × 10⁻¹³.
pip install grsuite
Sixty seconds
import grsuite as gr
catchment = gr.Catchment(dates, precip=P, pot_evap=E, obs_discharge=Q)
fit = catchment.calibrate("GR4J", period=("2000-01-01", "2009-12-31"))
print(fit.describe())
check = fit.evaluate(period=("2010-01-01", "2019-12-31"))
print(check.nse(), check.kge())
GR4J calibrated on KGE = 0.8694
X1 170.7158 production store capacity [mm]
X2 0.5897 groundwater exchange coefficient [mm/step]
X3 78.2571 routing store capacity [mm]
X4 2.2568 unit hydrograph time constant [step]
17 iterations, 202 model runs
0.799 0.776
A split-sample test is one call:
fit, validation = catchment.split_sample(
"GR6J",
calibration=("2000-01-01", "2009-12-31"),
validation=("2010-01-01", "2019-12-31"),
criterion="KGE",
)
Snow, on five elevation bands, needs only temperature and a hypsometric curve:
alpine = gr.Catchment(dates, precip=P, pot_evap=E, obs_discharge=Q,
temperature=T, hypsometry=elevation_quantiles)
fit = alpine.calibrate("CemaNeigeGR4J")
Every internal flux is available, under airGR's own names:
sim = fit.simulate()
sim.to_dataframe()[["Qobs", "Qsim", "Prod", "Rout", "Perc", "Exch"]]
sim.plot(log=True)
Why this exists
airGR is the reference implementation of the GR models: careful, peer-reviewed, maintained at INRAE. Its one constraint is that it lives in R. Python users have had to choose between calling R over a bridge, or trusting a reimplementation nobody checked.
GRsuite removes that choice. It was written by translating airGR's Fortran kernels and R logic line by line, and it is continuously verified against airGR's own outputs, which ship with the test suite. If GRsuite and airGR ever disagree, CI fails.
| Same numbers | 502 quantities compared value by value against airGR — every internal variable, not just discharge |
| Same calibration | Michel's algorithm reproduced step for step — identical iteration counts on 500 calibrations |
| No R needed | pure Python + NumPy, JIT-compiled with Numba |
| Faster | 3.2× quicker than airGR on the same 500 calibrations |
| Actually usable | a one-line calibration API on top of the faithful airGR-mirroring one |
What is covered
| Component | Models and functions | Published as |
|---|---|---|
| Daily | GR4J, GR5J, GR6J | Perrin et al. (2003); Le Moine (2008); Pushpalatha et al. (2011) |
| Hourly | GR4H, GR5H (with or without the interception store) | Mathevet (2005); Ficchì (2017); Ficchì et al. (2019) |
| Monthly / annual | GR2M, GR1A | Mouelhi et al. (2006a, 2006b) |
| Snow | CemaNeige, with or without linear hysteresis | Valéry (2010); Valéry et al. (2014); Riboust et al. (2019) |
| Coupled | CemaNeige + GR4J / GR5J / GR6J / GR4H / GR5H | as above |
| Evapotranspiration | Oudin formula | Oudin et al. (2005) |
| Elevation bands | Valéry extrapolation, daily temperature gradients | Valéry (2010) |
| Criteria | NSE, KGE, KGE′, RMSE, weighted composites | Nash & Sutcliffe (1970); Gupta et al. (2009); Kling et al. (2012) |
| Transformations | sqrt, log, inv, sort, boxcox, powers |
Santos et al. (2018) |
| Calibration | Michel's algorithm (grid screening then steepest descent) | Michel (1991) |
| Data assimilation | EnKF and particle filter state updating, perturbed forcing ensembles | Piazzi et al. (2021); airGRdatassim 0.1.4 |
| Utilities | time-series aggregation, semi-distributed routing, interception capacity | Lobligeois (2014); Ficchì et al. (2019) |
Full bibliographic entries, with DOIs, are in docs/REFERENCES.md.
CreateErrorCrit_GAPX (de Lavenne et al., 2019) and airGR's plotting helpers are
the only pieces left out. GR6H does not exist in airGR 1.7.9, so it does not
exist here either.
Validation
The brief was to stay within 5 %. The measured agreement is eleven orders of magnitude better than that, and sits at the precision limit of the reference files themselves.
Across 500 independent calibrations — five model configurations on 100 CAMELS-FR catchments — 34 parameter sets came out bit-identical to airGR's, and the rest differ only in the last significant digit of a double.
Read the full validation report →
Two implementation details decide whether a re-implementation of airGR is merely
correct or actually identical. Both are documented in
docs/FIDELITY.md; the short version is that airGR's Fortran
evaluates 0.9 in single precision before promoting it, and that its automatic
warm-up period carries a leap-year correction. Miss either and your discharge is
off by 2.4 × 10⁻⁷ or 3 × 10⁻⁶ respectively.
Two APIs, on purpose
The high-level one — for getting work done.
catchment = gr.Catchment(dates, precip=P, pot_evap=E, obs_discharge=Q)
fit = catchment.calibrate("GR6J", criterion="KGE", transfo="sqrt")
The faithful one — a direct translation of airGR, function by function, for porting an existing R workflow without rethinking it.
inputs = gr.InputsModel(dates, precip=P, pot_evap=E)
options = gr.RunOptions(inputs, "GR4J", ind_period_run=index)
crit = gr.InputsCrit("NSE", obs=Q[index])
result = gr.calibration_michel(inputs, options, crit, gr.CalibOptions("GR4J"))
The mapping from every airGR function to its GRsuite counterpart is in docs/MIGRATING_FROM_AIRGR.md.
Examples
| File | What it shows |
|---|---|
examples/01_simulate.py |
Run GR4J with known parameters, inspect the internal fluxes |
examples/02_calibrate.py |
Calibrate, then validate on an independent period |
examples/03_compare_models.py |
GR4J vs GR5J vs GR6J on the same catchment |
examples/04_snow.py |
CemaNeige on elevation bands, snow pack dynamics |
examples/05_hourly.py |
GR4H and GR5H with an interception store |
examples/06_low_flows.py |
Objective functions for low-flow performance |
examples/07_semi_distributed.py |
Routing upstream sub-catchments to an outlet |
examples/08_from_csv.py |
Going from a plain CSV to a calibrated model |
examples/09_data_assimilation.py |
EnKF and particle filter assimilation on a calibrated GR4J |
All of them run against the demonstration catchment shipped in tests/data, so
they work straight after a clone.
Installation
pip install grsuite # runtime: numpy, numba
pip install "grsuite[io]" # adds pandas for the DataFrame helpers
pip install "grsuite[plot]" # adds matplotlib for Simulation.plot()
From source:
git clone https://github.com/Najim33/GRsuite
cd GRsuite
pip install -e ".[dev]"
pytest
Python 3.9 to 3.13, on Linux, macOS and Windows.
Performance
The kernels are JIT-compiled by Numba, so the first call to a model pays a compilation cost of a second or two and everything afterwards runs at compiled speed. Recompilation is cached on disk between sessions.
On the 500-calibration benchmark (5 configurations × 100 catchments, 20 years of daily data each):
| Wall clock | |
|---|---|
| airGR 1.7.9 (R + Fortran) | 17.5 min |
| GRsuite (Python + Numba) | 5.4 min |
Same parameters, same criteria, same iteration counts.
Citing
GRsuite introduces no new hydrology. The models, the calibration algorithm and the reference implementation belong to their authors, at Cemagref / Irstea / INRAE. If GRsuite supports published work, cite airGR:
Coron, L., Thirel, G., Delaigue, O., Perrin, C., Andréassian, V. (2017). The suite of lumped GR hydrological models in an R package. Environmental Modelling & Software, 94, 166–171. doi:10.1016/j.envsoft.2017.05.002
Coron, L., Delaigue, O., Thirel, G., Dorchies, D., Perrin, C., Michel, C. airGR: Suite of GR Hydrological Models for Precipitation-Runoff Modelling. R package version 1.7.9, INRAE, HYCAR Research Unit, Antony, France. doi:10.15454/EX11NA
and the model you actually ran — GR4J is Perrin et al. (2003), CemaNeige is Valéry (2010), and so on. Every one of them, with its DOI, is listed in docs/REFERENCES.md.
If the port itself is relevant to your methods, cite this repository too — see CITATION.cff.
Contributing
Bug reports, models, criteria and examples are all welcome. The one hard rule: anything that changes model numerics must keep the airGR comparison tests green. See CONTRIBUTING.md.
Licence
GPL-2.0-or-later. GRsuite is a derivative work of airGR (INRAE, GPL-2) and carries the same licence — see NOTICE for the full attribution.
GRsuite is an independent, unofficial project. It is not produced, endorsed or supported by INRAE, and it is not a release of airGR.
Ce README existe aussi en français.
Metadata
Release files for grsuite 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| grsuite-1.1.1.tar.gz | 13.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grsuite-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 13.8 MB
Release files / grsuite-1.1.1.tar.gz
| Download URL | grsuite-1.1.1.tar.gz |
|---|---|
| Size | 13.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
12a43c50a44402dde48727be8a6cb6d008c1a81169dd1643e65f3d9cf55a40ca
|
|
BLAKE2b-256 checksum How to use checksums |
b97474d4e9cd10b5e7701f8813e760536805cb76abda2cab6ddc32187cf35192
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency logRelease files / grsuite-1.1.1-py3-none-any.whl
| Download URL | grsuite-1.1.1-py3-none-any.whl |
|---|---|
| Size | 75.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e8c0c13d76495d7453f4e4682e1497814baab7040825e84cd8565dd6e22a183f
|
|
BLAKE2b-256 checksum How to use checksums |
a76730d7af51bada60a47a01833e2b368f6a9bb1d3a32bfd884df1d8cae7864a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency log