Skip to main content
AquaGrid

AquaGrid

Pixel-wise AquaCrop on rasters: zarr in, Numba kernels out — CPU or GPU.

PyPI Tests Python License Visitors

Numba NumPy xarray zarr Dask pytest AquaCrop-OSPy

Scope Parity Conventional Commits

Quick start · Config · Architecture · Schema · Testing · Citing · License

Soybean field at 30 m: MapBiomas Solo collection 3 clay on the left, AquaCrop dry yield on the right


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

AquaGrid architecture: climate, sowing and soil zarr in, tiled into crop and soil parameters, one AquaCrop simulation per pixel on Numba CPU or CUDA, written to a zarr output grid

  • kernels/impl.py — AquaCrop daily step ported to a common njit / cuda.jit subset (no allocation in kernels, scalar state per pixel + 1-D compartment views). One source compiled for both backends by kernels/loader.py.
  • params.py — flattens aquacrop Crop / Soil into kernel arrays (weather-independent init, including deepening the profile to Zmax + 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

Tests

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

  1. Use conventional commits (feat:, fix:, chore:, ci:, docs:).
  2. Put tests in the same change as the behaviour they cover.
  3. 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)

Source distribution for aquagrid 0.3.0
File Size Uploaded
aquagrid-0.3.0.tar.gz 62.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aquagrid 0.3.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page