aquakin#
aquakin models reactive scalar transport in aqueous environmental systems.
Reaction models are declared at runtime in YAML, parsed into an AST, and
compiled to JAX-native, automatically-differentiable rate functions integrated
by Diffrax.
It ships a library of ready-to-use models — ozonation and UV/H₂O₂ chemistry, the ASM activated-sludge family, ADM1 anaerobic digestion, WATS sewer processes, and mineral precipitation — and the tools to simulate them in batch, plug-flow, biofilm, and full plant-wide flowsheets, with automatic differentiation throughout for sensitivity analysis and parameter calibration.
New here? Start with Getting started.
Authoring models
Reference
Installation#
pip install aquakin
Note
import aquakin enables JAX 64-bit (x64) mode process-wide — the stiff implicit
ODE solves require double precision. This is global JAX state: other JAX code in
the same process will use float64 afterward. aquakin emits a one-time warning if
it overrides an explicit float32 preference, so the side effect is never silent.
Quickstart#
import jax.numpy as jnp
import aquakin
model = aquakin.load_model("ozone_bromate")
conditions = model.default_conditions().with_(pH=7.5, T=293.15) # 0-D batch case
reactor = aquakin.BatchReactor(model, conditions)
sol = reactor.solve(
model.default_concentrations(),
params=model.default_parameters(),
t_span=(0.0, 600.0),
t_eval=jnp.linspace(0.0, 600.0, 121),
)
print("[BrO3-] at 10 min:", float(sol.C_named("BrO3-")[-1]))
See Getting started for a step-by-step walkthrough.
Architecture#
aquakin uses a two-layer data model:
Schema layer (load time) — Pydantic models validate the YAML and produce a clean spec object. Pydantic never appears on the hot path.
Runtime layer — a
CompiledModeldataclass, built once from the spec, holds the stoichiometry matrix, the per-reaction compiled rate callables, and the parameter index map. This is what the integrators operate on.
Each rate callable has the signature rates(C, params, condition_arrays, loc_idx) returning a (n_reactions,) vector, and the reaction right-hand side
is stoich.T @ rates(...). Rate constants are always passed in via params,
never baked in — which is what makes the whole solve differentiable for
sensitivity analysis and calibration.