Catalog PyPI
Back to packageRELEASE DETAILS

0.0.1

PyPI

agrijax

Differentiable, batch-parallel field-scale crop-soil process models in JAX (CERES-Maize + RZWQM-style soil water and PET).

agricultureceres-maizecropdifferentiabledssatjaxmodelrzwqm
Published24 Sept 2026
Direct dependencies31
Transitive dependenciesNot calculated

README

Version 0.0.1 documentation

Agri-JAX

Differentiable, batch-parallel field-scale crop–soil process models in JAX.

中文 · Showcase

Status (September 2026): early implementation. The core runtime, file readers, reference-model runners and the potential-evapotranspiration processes are in place and tested. The Richards soil-water solver and the CERES-Maize crop module are the next two pieces. The PyPI package is a name-reserving placeholder.

Acknowledgements

This research was enabled in part by support provided by Calcul Québec and the Digital Research Alliance of Canada. All GPU work runs on the rorqual cluster operated by Calcul Québec.

What it is

Agri-JAX writes a field-scale crop–soil model as pure JAX functions. The crop part is an independent implementation of CERES-Maize from the open-source DSSAT-CSM (BSD-3). Soil water and potential evapotranspiration follow the published RZWQM2 equations: a Brooks–Corey Richards equation, Shuttleworth–Wallace evapotranspiration and tile drainage (Ahuja et al., 2000; Farahani & Ahuja, 1996; Shuttleworth & Wallace, 1985).

Because every process is a pure function, a multi-year field simulation can be run for 10⁵ parameter sets in one vmap, differentiated with jax.grad, and placed inside gradient-based calibration, Hamiltonian Monte Carlo, ensemble data assimilation or a hybrid process–ML training loop. Correctness is checked against the outputs of the DSSAT-CSM and RZWQM2 reference models.

Speed is not the goal in itself. The aim is a model with layered soil physics and management events that is as cheap to run in bulk and as easy to differentiate as the simpler differentiable crop models, while still reading the DSSAT and RZWQM parameter files that agronomists already have.

Implementation status

ComponentImplementedCompared against a reference
Core: state pytrees, @process, scan/vmap runtime, three-rules lintyesruntime vs an independent Python day loop (1e-12); gradients vs finite differences
RZWQM2 and DSSAT-CSM file readers and writersyesbyte-identical round trips on 15 scenarios; DSSAT example outputs
Reference-model runners (RZWQM2, DSSAT-CSM)yesall 15 RZWQM2 scenarios and all DSSAT maize examples run
Brooks–Corey hydraulicsyesreference-model field capacity and wilting point on 100+ horizons; gradient checks
Shuttleworth–Wallace, ASCE and Priestley–Taylor PETyesASCE to 5e-6 mm/d; Shuttleworth–Wallace growing-season RMSE 0.02 / 0.17 mm/d vs RZWQM2 on one site-year
Organ queue, event table, AmeriFlux loaderyesconservation properties; AmeriFlux CA-TPA data
Richards soil-water solvernext—
CERES-Maize crop modulenext—
Coupled water–crop model, calibration, uncertainty quantificationafter the two above—

New modules are added only after the existing ones pass automated, deterministic comparisons against an independent reference.

Why

TaskRuns neededNeeds gradients
Calibration and uncertainty quantification (LHS, MCMC, HMC)10⁴–10⁶yes, for HMC and variational inference
Regional gridded simulation (pixels × years × scenarios)10⁶–10⁸no
Ensemble data assimilation (EnKF, particle filters)10³–10⁴ members in lockstepno
Hybrid process–ML modelsone batch per training stepyes

DSSAT, RZWQM2, APSIM, STICS and WOFOST are sequential Fortran or C# codes built for one field at a time. Their implementations do not natively support array-style batch execution and are not differentiable. Differentiable crop models do exist: diffWOFOST and torchcrop for crop growth, JAX-CanVeg for the canopy and land surface. Agri-JAX differs in what it couples differentiably: layered soil-water dynamics, crop growth and management events, with the DSSAT and RZWQM parameter files usable as they are. Comparisons between these tools are best made on soil layering, the flow equation, management processes, gradient handling and validation scope.

Design

A process is a pure function (state, params, forcing) -> state, declared with @process(reads=..., writes=...). Process authors follow three rules and never write scan, vmap, jit or lax.cond:

  1. Everything read is an argument; everything changed is in the return value.
  2. Branch with jnp.where or jnp.select, never with a Python if on state.
  3. Never write a loop over layers, days or samples. The runtime owns time and batch.

The rules are enforced by an AST lint and, with AGRI_JAX_CHECK=1, by a runtime check of declared writes. They bind process authors; numerical kernels such as the tridiagonal solver are written directly in JAX.

A model is a state definition plus an ordered list of processes, and any process can be swapped. Soil water will come in two interchangeable forms, a DSSAT-style tipping bucket and an RZWQM-style Richards solver, so one crop module can be compared under both. Crops are a state dimension, so intercropping is a change of array shape rather than a new code path. Organs such as leaves are held in a fixed-size queue, which leaves room for crops like tobacco that are harvested leaf by leaf.

Preliminary throughput

These numbers come from a computational skeleton, not the full model. The skeleton has the planned per-day shape: a 37-node implicit Richards step with 24 sub-steps and 3 Newton iterations, a tridiagonal solve, Shuttleworth–Wallace-shaped PET and a CERES-shaped crop step, over 3287 days. On one NVIDIA H100:

Configuration10⁵ nine-year runsThroughput
float64, 24 sub-steps × 3 Newton243 s411 runs/s
float32, 24 × 3101 s993 runs/s
float64, 12 × 282 s1220 runs/s

The RZWQM2 reference binary takes 24 s per run on one CPU core. The same 10⁵ runs cost 667 core-hours, roughly 10 wall-clock hours on a cluster. At this batch size the GPU is saturated, so run time follows the number of implicit solves per day. How far the scheme can be thinned while staying in agreement with the reference model, and while keeping gradients trustworthy, is the question the first paper will answer.

Roadmap

  1. Richards solver and CERES-Maize. Each is validated day by day against RZWQM2 and DSSAT-CSM outputs before coupling.
  2. Coupled model on one site. AmeriFlux CA-TPA maize, 10⁵-sample timing, gradient checks at three levels: plausible outputs, correct derivatives, and derivatives fit for inference.
  3. Calibration and uncertainty. Gradient-based and HMC calibration, Sobol sensitivity, multi-site joint calibration and parameter identifiability.
  4. Extensions. Intercropping, nitrogen and carbon cycling, hybrid process–ML models, and a report interface for agricultural economists with Stata and R front ends.

Development

git clone https://github.com/juksentang/agri-jax && cd agri-jax
uv sync --all-extras
uv run pytest -q tests/unit tests/integration
uv run ruff check . && uv run pyright
uv run python -m agrijax.core.lint src --strict

Data-backed tests read from --data-dir or AGRI_JAX_DATA. The unit tier needs no data. The PyPI release (pip install agrijax) stays a placeholder until the coupled model works.

DirectoryContents
src/agrijax/corestate, process decorator, runtime, events, organ queue, units, lint
src/agrijax/processessoil water, PET, crop, canopy, arbitration
src/agrijax/modelsassembled models
src/agrijax/ioRZWQM2, DSSAT, AmeriFlux and CA-TPA readers
src/agrijax/portreference-model runners and comparison reports
docs/showcasesource of the showcase page

Licensing and provenance

Apache-2.0. CERES-Maize is implemented independently from the open-source DSSAT-CSM (BSD-3), whose attribution is retained. Soil water and PET are implemented from the published RZWQM2 equations. No RZWQM2 code is included or redistributed. RZWQM2 is used only as a reference model: its outputs are compared privately and only the resulting numbers are reported. The comparison against DSSAT-CSM is public and reproducible.

Citation

See CITATION.cff. A design note and a validation report will follow as preprints.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 32

Native requirements
31 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
equinox
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
equinox>=0.13runtime
jax
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jax<0.12,>=0.8runtime
jaxtyping
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jaxtypingruntime
numpy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
numpyruntime
pandas
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pandasruntime
pyyaml
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyyamlruntime
scipy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
scipyruntime
xarray
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
xarrayruntime
arviz
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
arviz; extra == "calib"runtime
blackjax
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
blackjax; extra == "calib"runtime
lineax
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
lineax; extra == "calib"runtime
numpyro
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
numpyro; extra == "calib"runtime
optax
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
optax; extra == "calib"runtime
optimistix
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
optimistix; extra == "calib"runtime
salib
Environment
{
  "extras": "",
  "marker": "extra == \"calib\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
salib; extra == "calib"runtime
jax
Environment
{
  "extras": "cuda12",
  "marker": "extra == \"cuda\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jax[cuda12]<0.12,>=0.8; extra == "cuda"runtime
beartype
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
beartype; extra == "dev"runtime
chex
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
chex; extra == "dev"runtime
hypothesis
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
hypothesis; extra == "dev"runtime
pre-commit
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pre-commit; extra == "dev"runtime
pyarrow
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyarrow; extra == "dev"runtime
pyet
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyet; extra == "dev"runtime
pyright
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyright; extra == "dev"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest; extra == "dev"runtime
pytest-benchmark
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest-benchmark; extra == "dev"runtime
pytest-cov
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest-cov; extra == "dev"runtime
pytest-regressions
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest-regressions; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
ruff; extra == "dev"runtime
mkdocs-material
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
mkdocs-material; extra == "docs"runtime
mkdocstrings
Environment
{
  "extras": "python",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
mkdocstrings[python]; extra == "docs"runtime
fparser
Environment
{
  "extras": "",
  "marker": "extra == \"port\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
fparser>=0.2.5; extra == "port"runtime
jinja2
Environment
{
  "extras": "",
  "marker": "extra == \"report\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jinja2; extra == "report"runtime

Distribution files

2 collected
agrijax-0.0.1-py3-none-any.whl

bdist_wheel · 172.57 KB · Python py3

Source file
agrijax-0.0.1.tar.gz

sdist · 152.68 KB · Python source

Source file

Repository

PENDING
juksentang/agri-jax

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 09:26 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:ed2b761cd8398576e61ec398171aec0800ae10fe014d35d3c6eadd8e2d5db47a

Other collected versions

View all collected versions →