AquaGrid
Pixel-wise AquaCrop on rasters: zarr in, Numba kernels out — CPU or GPU.
Quick start · Config · Architecture · Schema · Testing · Citing · License
Climate as zarr cubes (time, y, x), per-pixel sowing dates (y, x), zarr
outputs. Daily-step kernels from
AquaCrop-OSPy 3.x are vendored and
recompiled with Numba — njit + prange on CPU (one thread per pixel) and
numba.cuda on GPU — with bit-exact parity against AquaCrop-OSPy
(tests/test_parity.py).
This is not official FAO AquaCrop and not an aquacropos extension. It started in CyMP (Unioeste-LEA).
| Layer | Stack |
|---|---|
| Language | Python 3.11+ |
| Kernels | Numba (njit + prange / numba.cuda) |
| Arrays | NumPy, xarray, zarr, Dask |
| Reference | AquaCrop-OSPy ≥ 3.0.11 (crop/soil params + parity tests) |
| CLI | Typer (aquagrid) |
| Tests | pytest 8 — Ubuntu & Windows, Python 3.11 / 3.12 |
Current scope: rainfed (no irrigation, no groundwater), one season per pixel. Soil: a single AquaCrop preset or a per-pixel zarr raster (HiHydroSoil hydraulics or sand/silt/clay texture).
Prerequisites
- Python 3.11+
- GPU (optional): NVIDIA GPU with a CUDA driver — Numba talks to the driver; the full CUDA toolkit is not required
Quick start
pip install aquagrid
From Git (no clone, before or besides PyPI):
pip install "git+https://github.com/Paloschi/aquagrid.git"
For development, clone and install editable:
git clone https://github.com/Paloschi/aquagrid.git
cd aquagrid
pip install -e ".[dev]"
# 1. synthetic 10×10 pixels / 540 days
aquagrid synth --out ./data
# 2. config
cp examples/config.example.yaml ./data/config.yaml
# (adjust paths if needed)
# 3. run
aquagrid run --config ./data/config.yaml # CPU
aquagrid run --config ./data/config.yaml -b gpu # GPU
Output: output.zarr with final yield/biomass (y, x) and, with
save_daily: true, daily series (time, y, x) in group daily.
Full schema: docs/zarr-schema.md.
Programmatic
from aquagrid.pipeline import run_grid
run_grid("climate.zarr", "sowing.zarr", "output.zarr",
crop_name="Maize", soil_name="SandyLoam",
backend="cpu", save_daily=False)
CLI
| Command | Purpose |
|---|---|
aquagrid synth -o ./data |
Synthetic climate + sowing zarr for tests |
aquagrid run -c config.yaml |
Gridded simulation from YAML (-b cpu|gpu) |
aquagrid bench |
Throughput (pixels/s), excluding JIT compile |
YAML config
climate: data/climate.zarr # cube (time, y, x): tmin, tmax, precip, eto
sowing: data/sowing.zarr # grid (y, x) int32 YYYYDDD; <=0 = masked
output: data/output.zarr
crop:
name: Maize # any AquaCrop-OSPy crop
# zarr: data/crop.zarr # optional (param, y, x) overrides per pixel
soil:
name: SandyLoam # AquaCrop preset (xor with zarr below)
# zarr: data/soil.zarr # ksat/wcsat/wcpf2/wcpf3 or sand/silt/clay
# ksat_unit: cm/d
backend: cpu # cpu | gpu
options:
save_daily: false
tile: 128 # spatial tile size (pixels)
max_season_days: 400
Copy from examples/config.example.yaml.
Project structure
aquagrid/
├── docs/ # zarr I/O schema
├── examples/ # sample YAML config
├── src/aquagrid/
│ ├── cli.py # Typer: synth / run / bench
│ ├── pipeline.py # zarr in → tiles → zarr out
│ ├── params.py # Crop / Soil → kernel arrays
│ ├── soil_grid.py # PTF + HiHydro layers → compartments
│ ├── bench.py
│ ├── engine/
│ │ ├── cpu.py # njit + prange
│ │ ├── gpu.py # numba.cuda
│ │ └── run.py # shared runner over flat arrays
│ ├── kernels/
│ │ ├── impl.py # daily step (njit / cuda.jit subset)
│ │ └── loader.py # compile one source for both backends
│ └── io/ # schema, validation, synthetic data
└── tests/ # pytest: parity, grid, soil, io, gpu
Where to look first
| Concern | Location |
|---|---|
| Daily AquaCrop step | src/aquagrid/kernels/impl.py |
| CPU / GPU backends | src/aquagrid/engine/ |
| Raster pipeline | src/aquagrid/pipeline.py |
| Crop / soil params | src/aquagrid/params.py |
| Soil rasters / PTF | src/aquagrid/soil_grid.py |
| Zarr schema | docs/zarr-schema.md |
| Bit-exact parity | tests/test_parity.py |
Architecture
kernels/impl.py— AquaCrop daily step ported to a commonnjit/cuda.jitsubset (no allocation in kernels, scalar state per pixel + 1-D compartment views). One source compiled for both backends bykernels/loader.py.params.py— flattens aquacropCrop/Soilinto kernel arrays (weather-independent init, including deepening the profile toZmax + 0.1).- GDD phenology is computed inside the kernel per pixel (sowing date
changes each pixel's thermal accumulation), mirroring aquacrop
compute_crop_calendar.
I/O schema
| Store | Shape | Role |
|---|---|---|
| Climate | (time, y, x) |
tmin, tmax, precip, eto (daily, gap-free) |
| Sowing | (y, x) |
int32 YYYYDDD; <= 0 = mask |
| Soil | name or zarr | AquaCrop preset, or hydraulic / texture raster |
| Output | (y, x) |
yield, biomass, status; optional daily/ group |
Grids must already be aligned — AquaGrid does not reproject.
Details, units, and soil bands: docs/zarr-schema.md.
Benchmark
aquagrid bench --pixels 65536 --days 540 (excluding JIT compile):
| Backend | Reference hardware | Throughput |
|---|---|---|
CPU (njit + prange) |
Ryzen (all threads) | ~58k pixels/s |
GPU (numba.cuda) |
RTX 3060 | ~61k pixels/s |
One full season (540 days) per pixel. On larger grids the GPU scales better (climate transfer dominates on small grids).
Testing
pytest # parity vs AquaCrop-OSPy, io, grid driver, gpu (if present)
| File | What it covers |
|---|---|
test_parity.py |
Single pixel vs AquaCrop-OSPy, 1e-12 (maize/soybean, calendar and GDD, 3 soils) |
test_grid.py |
Mask, per-pixel sowing, tiles, grid vs single-pixel equality |
test_soil.py |
Hydraulic/texture zarr, Saxton–Rawls PTF, per-pixel soil |
test_io.py |
Synthetic I/O, sowing → plant index |
test_gpu.py |
CPU vs GPU parity (skipped without CUDA) |
CI (GitHub Actions) runs on every pull request against main, and again
on push to main: Ubuntu and Windows × Python 3.11 / 3.12. Hosted runners
are CPU-only; test_gpu.py is skipped.
Contributing
- Use conventional commits (
feat:,fix:,chore:,ci:,docs:). - Put tests in the same change as the behaviour they cover.
- Open a pull request against
main— CI must pass before merge.
Citing this project
If you use AquaGrid in research or operational work, please cite this repository:
@software{paloschi_aquagrid,
author = {Paloschi, Rennan Andres},
title = {AquaGrid: pixel-wise AquaCrop on rasters (Numba CPU/GPU)},
year = {2026},
url = {https://github.com/Paloschi/aquagrid},
version = {0.1.0}
}
Also acknowledge AquaCrop-OSPy (and FAO AquaCrop) for the underlying crop water-productivity model that the kernels follow.
License and attribution
MIT. Kernels in src/aquagrid/kernels/impl.py are derived from
AquaCrop-OSPy (Apache-2.0).
Metadata
Release files for aquagrid 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aquagrid-0.3.0.tar.gz | 62.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aquagrid-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 115.3 kB
Release files / aquagrid-0.3.0.tar.gz
| Download URL | aquagrid-0.3.0.tar.gz |
|---|---|
| Size | 62.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d433bf3732233839ee577af990203cc84934a4cc6c169d576b3d8654b4aeaca9
|
|
BLAKE2b-256 checksum How to use checksums |
b4ae068e0e65bfa0cb504f2a42ce4c45ae6f3af3c9a282fa257b362660d32dc7
|
| 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 Oct 7, 2026.
Transparency logRelease files / aquagrid-0.3.0-py3-none-any.whl
| Download URL | aquagrid-0.3.0-py3-none-any.whl |
|---|---|
| Size | 53.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7d826b77255bf2a4b07183979dea29f000ddb67f7c98b0c582ab7c317ab413e1
|
|
BLAKE2b-256 checksum How to use checksums |
2d90463fd9f0fa383980e63677310419b82cab16c8d3d4d2649511fb129d0229
|
| 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 Oct 7, 2026.
Transparency log