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.

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:

  1. Schema layer (load time) — Pydantic models validate the YAML and produce a clean spec object. Pydantic never appears on the hot path.

  2. Runtime layer — a CompiledModel dataclass, 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.