A Python rainfall-runoff model library for hydrologic simulations
Project description
pyrrmod
pyrrmod is a Python rainfall-runoff model library for running conceptual
hydrologic simulations with a clean validation boundary, compatibility-focused
solver behavior, optional Numba acceleration paths, and MCP-friendly outputs.
The library is currently simulation-only:
- validate inputs with Pydantic
- convert validated payloads into ndarray runtime state
- run models with Python or Numba kernels
- export grouped outputs, internal states/fluxes, and JSON-safe MCP payloads
Installation
pip install -e .
Or with uv:
uv sync
Core Ideas
- Pydantic is used only at the input boundary.
- Runtime execution uses only
ndarray, scalars, and simple containers. - Numba kernels never receive Pydantic models.
run_from_payload()is the recommended MCP entrypoint.
Internal Layout
The public model interface stays the same, but the internal structure is split by responsibility:
pyrrmod/schemas.py: input models, array coercion, and runtime dataclassespyrrmod/core.py: shared model execution corepyrrmod/models/__init__.py: thin model package export surfacepyrrmod/catalog.py: model registry, metadata, and factory wiringpyrrmod/unithydro/core.py: unit-hydro state, hydrograph builders, and update opspyrrmod/unithydro/unit_hydro_mixins.py: unit-hydrograph mixinspyrrmod/unithydro/routed_newton_mixins.py: routed numba-newton mixinspyrrmod/solvers/newton.py: Newton helperpyrrmod/solvers/root_fallback.py: Newton + fsolve + least-squares retry chainpyrrmod/solvers/routed_numba.py: Numba-backed routed kernels
Main Interfaces
Input models
ClimateInputSolverOptionsInputModelConfigInputModelRunInput
Main model methods
configure(...)run(...)validate_run_payload(...)run_from_payload(...)get_output(nargout)get_output_dict(...)to_mcp_payload(...)check_water_balance()get_streamflow()compile_model_fun(target="auto" | "python" | "numba")
Create a Model
from pyrrmod import create_model
model = create_model("collie1")
You can also instantiate models directly:
from pyrrmod.models import collie1
model = collie1()
Example Dataset
This repository includes data/03604000.csv with columns:
prcp(mm/day)tmean(C)pet(mm)flow(mm)
Example loader:
from pathlib import Path
import numpy as np
data_file = Path("data/03604000.csv")
data = np.loadtxt(data_file, delimiter=",", skiprows=1)
climate = {
"precip": data[:, 0],
"temp": data[:, 1],
"pet": data[:, 2],
}
q_obs = data[:, 3]
Recommended Structured Run
from pathlib import Path
import numpy as np
from pyrrmod import ModelRunInput
from pyrrmod.models import collie1
data = np.loadtxt(Path("data/03604000.csv"), delimiter=",", skiprows=1)
run_input = ModelRunInput(
theta=[500.0],
delta_t=1.0,
S0=[100.0],
climate={
"precip": data[:, 0],
"temp": data[:, 1],
"pet": data[:, 2],
},
solver_options={
"resnorm_tolerance": 0.1,
"resnorm_maxiter": 6,
},
compile_target="numba",
)
model = collie1()
model.run(config=run_input)
q_sim = model.get_streamflow()
water_balance_error = model.check_water_balance()
print(q_sim[:5])
print(water_balance_error)
Direct Run API
You can also pass raw inputs directly to run().
from pyrrmod.models import collie1
model = collie1()
model.run(
theta=[500.0],
delta_t=1.0,
S0=[100.0],
input_climate={
"precip": [10.0, 0.0, 5.0],
"pet": [1.0, 1.0, 1.0],
"temp": [12.0, 13.0, 14.0],
},
solver_opts={"resnorm_tolerance": 0.1},
compile_target="python",
)
MCP-Friendly Payload Run
Recommended MCP flow:
- call
validate_run_payload(payload)if you want preflight validation - call
run_from_payload(payload)to execute - use
include_execution=Trueif the caller needs compiler metadata
run_from_payload() accepts alias-friendly payloads and returns compact
JSON-safe data by default.
If an MCP adapter needs structured error payloads, catch the exception and use
model._format_mcp_error(exc) to normalize it into one of:
payload_errorconfiguration_errorsimulation_errorcompile_error
from pathlib import Path
import json
import numpy as np
from pyrrmod.models import collie1
data = np.loadtxt(Path("data/03604000.csv"), delimiter=",", skiprows=1, max_rows=30)
payload = {
"theta": [500.0],
"delta_t": 1.0,
"initial_stores": [100.0],
"climate": {
"rainfall": data[:, 0],
"temperature": data[:, 1],
"PET": data[:, 2],
},
"solver_options": {"resnorm_tolerance": 0.1},
"compile_target": "numba",
}
model = collie1()
validated = model.validate_run_payload(payload)
result = model.run_from_payload(
validated,
include_execution=True,
)
print(json.dumps(result, indent=2)[:500])
Output APIs
get_streamflow()
Returns simulated streamflow as a NumPy array.
get_output(nargout)
Legacy-style interface. Keep this for compatibility, but prefer
get_output_dict(...) for structured consumers and to_mcp_payload(...) for MCP.
nargout=3:(flux_output, flux_internal, store_internal)nargout=4: plus water balance errornargout=5: plus solver diagnostics
get_output_dict(...)
This is the canonical structured output source. By default it stays lightweight and returns:
modelstatusflux_groupsstreamflowwater_balance_error
Optional fields:
- optional
flux_internal - optional
store_internal - optional
solver_data - optional
execution
to_mcp_payload(...)
This is a thin JSON-safe wrapper over get_output_dict(...).
Conversions:
numpy.ndarray -> listnumpy scalar -> Python scalar
Solver Modes
Primary solver names:
fsolve: compatibility-focused implicit solve with retry chainnumba_newton: model-specific accelerated path (falls back safely when unsupported)
When solver_options.retry_solver=true (default), the fsolve path uses a
three-stage retry sequence for difficult steps:
- Newton step
- SciPy
fsolverestarts - SciPy
least_squaresbounded fallback
This sequence is used to keep numerical behavior aligned with the reference implementation while preserving a single public solver interface.
Compiler Targets
Available targets:
python: always use Python kernelsnumba: request lazy Numba compilationauto: request compiled path if available, otherwise fall back to Python
Compilation is lazy. The model is not compiled at import time. Runtime arrays are
constructed first, and the kernel is compiled only when needed. If a
model-specific compiled kernel is unavailable, the library falls back safely to
the Python kernel and records the reason in model.compiler.detail.
For model-to-model consistency checks against the reference implementation, prefer:
solver="fsolve"compile_target="python"solver_options={"legacy_state_update": True, "retry_solver": True}
Full Configuration Pattern
from pyrrmod import ModelConfigInput
from pyrrmod.models import collie1
model = collie1()
model.configure(
config=ModelConfigInput(
delta_t=1.0,
S0=[100.0],
input_climate={
"precip": [10.0, 0.0, 5.0],
"pet": [1.0, 1.0, 1.0],
"temp": [12.0, 13.0, 14.0],
},
solver_opts={"resnorm_tolerance": 0.1},
compile_target="numba",
)
)
model.run(theta=[500.0])
Test
The repository includes pytest coverage for:
- Pydantic input validation
- simulation output structure
- MCP-safe payload output
- Python/Numba consistency on
data/03604000.csv
Run tests with:
pytest
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrrmod-0.2.3.tar.gz.
File metadata
- Download URL: pyrrmod-0.2.3.tar.gz
- Upload date:
- Size: 131.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f2d05196db5fc8cef48df56948e8dbc6000116ec5d7845d149eb0fd2aee4cbb
|
|
| MD5 |
90de9c1bb999bcf3f3f6c09df19725ab
|
|
| BLAKE2b-256 |
289495f0efa722ac39b2e3d2032ddb242948175616cf4ecdaf45e58cadcc703f
|
File details
Details for the file pyrrmod-0.2.3-py3-none-any.whl.
File metadata
- Download URL: pyrrmod-0.2.3-py3-none-any.whl
- Upload date:
- Size: 115.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b55986fe3b012efca365b528a3cc92a1e73bae107ee3086cfd030569f9c9301
|
|
| MD5 |
c464e74d4ab00fb39cc8b91cc5089f05
|
|
| BLAKE2b-256 |
dbb90615083ac21aec1257ee206475f2b7e48582e4e9cd8697275c72ff14f92b
|