Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

integrix: integration for JAX

integrix is the stack’s integration library: one integral, I = ∫ f dμ, computed three ways (a rule, a scan or a march), with shared adaptivity and adjoints. It sits at Core tier 1 on functax’s rules and gaussx’s solves, extends diffrax with the stack’s steppers, and takes over finitevolX’s steppers and pipekit-jax’s adjoints. Status: proposed, at design-sketch depth.

At a glance

Layer, tierCore, tier 1
Importsfunctax, gaussx; extends diffrax; JAX, Equinox, lineax, einx
Imported byfinitevolX, tracerax, radiomax, vardax, sesmax-core, plumax, radtrax
Takes overfinitevolX’s 11 generic steppers (0.1.0 added AB3Solver) and their pure step functions; pipekit-jax’s truncated_scan and to_diffrax_adjoint; generic step-size control from sesmax-core (copied from somax)
Draws onquadax’s adaptive rules, without a dependency
Statusproposed; no repository yet
Tracked inthe layer map; the companion functax design
where integrix sits · Core tier 1

Where integrix sits · core tier 1.

integrix sits one tier above the rules it sums with, so every model, numerics library and assimilation library that steps or integrates can import it. This page is the design; core types, algorithms and the code survey are in integrix: design details.

The problem

Every library in the stack integrates something, and each writes its own integrator. Turning one satellite methane plume into an emission rate takes six integrals; four are coded in different places today, and two are not written yet:

Step in the retrievalIntegralOverWhere it is coded today
Band radiance from a spectrumL_b = ∫ L(λ) R(λ) dλspectrumplumax radtran/srf.py, radtrax instrument/srf.py, geoops ApplySRF
Optical depth down a columnτ(z) = ∫₀ᶻ k(z′) n(z′) dz′space, cumulativenot yet: radtrax’s slab model has one layer; a layered column needs it
Radiance over directions∫ I(μ) μ dμ, Gauss in μ = cos θanglenot yet: radtrax’s multi-stream solvers will need it
Integrated mass enhancementIME = ∬ ΔΩ(x, y) dAspaceplumax gauss_plume, gauss_puff
Transport of the plumedc/dt = F(c, t), stepped from t₀ to t₁timefinitevolX steppers, somax, plumax les_fvm
Uncertainty of the rate𝔼[Q] under the posteriorprobabilityplumax population: averages over NumPyro posterior draws

The rate is then Q = IME · U / L, with U the wind speed and L the plume length. Each row is a sum of weights against samples, or a step that repeats one; each library has rebuilt the weights, the stepping or the adjoint on its own.

Three problems follow. The same hand-written trapezoid sums sit in plumax, radtrax and xrtoolz. finitevolX holds eleven generic stepper classes that have nothing to do with finite volumes, so a spectral or particle model that wants an SSP Runge–Kutta step has to import a finite-volume library. And the adjoints that make 4D-Var and plume inversions affordable (checkpointed and truncated backward sweeps) live in pipekit-jax, the pipeline library, rather than next to the steppers they differentiate.

One integral, three operations

Every row of the table is an integral of a quantity f over a domain Ω with a measure μ:

I=∫Ωf(x)dμ(x)I = \int_\Omega f(x) \mathrm{d}\mu(x)

Here Ω is a time interval, a column, a band, a set of directions, an image or a probability space, and μ is length, area, a spectral response or a distribution. The problems differ, but they reduce to three operations:

OperationComputesLinear in f?Examples
RuleI ≈ wᵀf, weights against samplesyesband radiance, column amounts, integrated mass
ScanFₖ = Fₖ₋₁ + wₖ fₖ, a cumulative integralyes, and parallel with jax.lax.associative_scanoptical depth, CDFs, accumulations
Marchyₙ₊₁ = Φ(yₙ, tₙ, h), where the integrand depends on the solutionno: each step solves rather than sumsODE and PDE time stepping

A march is still a rule in time. A Butcher tableau’s weights bᵢ are quadrature weights over one step, and Gauss–Legendre collocation is an implicit Runge–Kutta method. That is why quadrature procedures and time steppers belong in one library.

Two pieces of machinery are shared by all three:

The weights w of a rule come from functax: they are a property of a basis on a domain. integrix owns what is done with them: summing, scanning, marching, adapting and differentiating.

Modules and boundaries

Six modules: one per operation, plus adaptivity, adjoints and probability. integrix owns the machinery, not the domains: spectral responses stay in radtrax and plumax, mass enhancement in plumax and exceedance events in xtremax; they pass in weights, measures or integrands.

ModuleOwnsComes from
integrix.ruleI = wᵀf over any axis or set of axes, with a functax rule or caller’s weights; batched over leading dimsnew; ends the hand-written trapezoid sums in plumax, radtrax and xrtoolz
integrix.scancumulative integrals, forward and reverse, parallel by associative_scannew
integrix.marchtime steppers, eight as diffrax solvers and three (AB2, leapfrog, split-explicit) in a loop of their own: forward Euler, RK2 to RK4, SSP-RK, IMEX, Adams–Bashforth, leapfrog with Robert–Asselin filter, split-explicit; step-size controlfinitevolX timestepping/diffrax_solvers.py (11 classes) and the pure step functions behind them; sesmax-core’s imex_stepsize_controller (copied from somax)
integrix.adaptadaptive quadrature: Gauss–Kronrod, tanh–sinh, Romberg, cumulative; error estimates shared with step controlnew, on quadax’s design without depending on it
integrix.adjointadjoints of scans and marches: reverse scans, checkpointing, truncated backpropagation through timepipekit-jax truncated_scan, to_diffrax_adjoint
integrix.expectexpectations 𝔼[f] by Monte Carlo and quasi-Monte Carlo (Sobol, Halton), importance samplingnew

What integrix owns, and what it hands off

The test for every name: does it sum, scan, march or adapt over a domain, or differentiate one of those? Then integrix owns it.

Neighbourintegrix ownsThe neighbour ownsThe line
functaxprocedures that use rulesquadrature rules: nodes and weights of a basis on a domaina rule is data; a procedure that sums, adapts or marches is integrix’s
gaussxexpectations by samplingGaussian expectations: Gauss–Hermite, unscented, sigma points; the linear solves an implicit step needsan integral under a Gaussian with a closed rule is gaussx’s
diffraxthe stack’s steppers, as diffrax solvers, and its adjointsdiffeqsolve, its solver and adjoint protocolsintegrix extends diffrax, as gaussx extends lineax
argminaxadjoints of marches and scansadjoints at an optimum: implicit differentiation, Gauss–Newton curvaturea backward sweep through time is integrix’s; a derivative at a fixed point is argminax’s
finitevolX, spectraldiffxgeneric steppingspatial discretisation, and semi-Lagrangian advection (field in, field out)a scheme that steps any right-hand side is integrix’s
pipekit-jaxthe adjoint mathscomposition: JaxOperator, Rollout, DiffraxForwardModelpipekit composes steps; integrix computes them
the modelsthe integratorintegrands and measures: spectral responses, plume fields, turbulencethe model passes f and μ in

What moves in

FromWhatIts users then import
finitevolX11 stepper classes: ForwardEulerDfx, RK2Heun, RK3SSP, RK4Classic, SSP_RK2, SSP_RK104, IMEX_SSP2, AB2Solver, AB3Solver, LeapfrogRAFSolver, SplitExplicitRKSolver, with their pure step functions; SemiLagrangianSolver and solve_ocean_pde stayintegrix: finitevolX, spectraldiffx, sesmax-core
pipekit-jaxtruncated_scan, to_diffrax_adjointintegrix; pipekit keeps composition
somax / sesmax-coregeneric step-size control (imex_stepsize_controller); model terms stayintegrix
plumax, radtrax, xrtoolzhand-written trapezoid sumsintegrix.rule with functax rules

In use

The methane example end to end: a rule for band radiance and integrated mass, a scan for optical depth, and a march for transport with a checkpointed adjoint. Every name is proposed; the rules come from functax and the Runge–Kutta steppers subclass diffrax’s.

# Shapes: W wavelengths; Z layers; Ny × Nx plume pixels; K steps; C state size

import diffrax as dfx
import functax as fx
import integrix as ix

# --- rule: band radiance ---
# proposed (functax)
# wₗ for ∫ g(λ) dλ on the sampled wavelengths
band_rule = fx.quad.trapezoid(wavelength)  # (W,) → rule
# proposed (integrix)
# L_b = ∫ L(λ) R(λ) dλ / ∫ R(λ) dλ
band_radiance = ix.rule.weighted_mean(  # (…, W) → (…,)
    band_rule, radiance, weight=response
)

# --- scan: optical depth ---
# proposed (functax)
layer_rule = fx.quad.trapezoid(altitude)  # (Z,) → rule
# proposed (integrix)
# τ(z) = ∫₀ᶻ k(z′) n(z′) dz′
optical_depth = ix.scan.cumulative(layer_rule, absorption * density)  # (Z,) → (Z,)

# --- rule: integrated mass enhancement ---
# proposed (functax)
pixel_rule = fx.quad.cell_area(plume_grid)  # (Ny, Nx) → rule
# proposed (integrix)
# IME = ∬ ΔΩ(x, y) dA
mass = ix.rule.integrate(pixel_rule, enhancement)  # (Ny, Nx) → ()

# --- march: plume transport, differentiable ---
# not pinned (diffrax)
terms = dfx.ODETerm(advection_diffusion)
# proposed (integrix)
# yₙ₊₁ = Φ(yₙ, tₙ, h), three-stage SSP Runge–Kutta
stepper = ix.march.RK3SSP()
# proposed (integrix)
# backward sweep, storing every 50th state
adjoint = ix.adjoint.Checkpointed(every=50)
# not pinned (diffrax)
solution = dfx.diffeqsolve(  # (C,) → (K, C)
    terms,
    stepper,
    t0=0.0,
    t1=duration,
    dt0=step,
    y0=initial,
    adjoint=adjoint,
    saveat=dfx.SaveAt(ts=times),
)

The rules are weights from functax, so the same ix.rule call serves a band, a column and an image. The march is ordinary diffrax: integrix supplies the stepper and the adjoint, not a new solve loop.

Integration across the stack

Seven libraries would import integrix, pipekit-jax would hand code to it, and spectraldiffx’s examples could use it. Most changes are an import: the steppers keep their diffrax interface, and a rule replaces a hand-written sum.

ConsumerUses todayWith integrix
finitevolXits own 11 diffrax solver classesimports 11 of them from integrix.march; keeps SemiLagrangianSolver, which takes its interpolation from functax
spectraldiffxnothing in the library; its example QG model (scripts/qg_model.py) steps with diffrax’s KenCarp4no library change; examples may use integrix’s IMEX steppers
sesmax-coregeneric stepping and imex_stepsize_controller inherited from somaxintegrix steppers and step control; model terms stay
plumaxtrapezoid sums for IME and SRFs; finite-volume stepping in les_fvm; NumPyro posterior averagesix.rule with functax rules; integrix steppers; ix.expect
radtraxSRF convolution by hand; a single-slab τix.rule for bands; ix.scan for layered columns; angular rules when multi-stream lands
vardax4D-Var forward runs differentiated through pipekit-jax’s helpers; its optimiser adjoints go to argminaxintegrix.adjoint for the backward sweep through its forward model (decision 7)
tracerax, radiomaxplannedparticle stepping (ix.march, SDE terms through diffrax); geometric integrals over a pixel
pipekit-jaxtruncated_scan, to_diffrax_adjointhands them to integrix; keeps Rollout and DiffraxForwardModel

The largest change is finitevolX’s, and it is an import swap:

# Today:
# today (finitevolX)
from finitevolx import RK3SSP

# After:
# proposed (integrix)
from integrix.march import RK3SSP

The class keeps its diffrax interface, so call sites do not change; finitevolX keeps a deprecated alias for one release.

Decisions and open questions

Seven decided, two open. Each entry gives the position first, then the trade-off.

Decisions 10 to 13, on integrals over space, are in integrix: integrals over space; all four were decided on 10 Nov 2026.

1. Name: integrix

Decided (5 Nov 2026). integrix, free on PyPI when checked. integrax is taken; quadrax, marchax, cumulax and evolvax were the other candidates.

2. Scope: all integration

Decided (5 Nov 2026). Time, space, spectrum, angle, exceedance and probability, not only quadrature and time stepping. All six reduce to the rule, scan and march above, and share adaptivity and adjoints.

3. Rules in functax, procedures here

Decided (5 Nov 2026). The nodes and weights of a rule are a property of a basis on a domain, so functax owns them. integrix owns what sums, scans, adapts or marches with them. Gaussian rules (Gauss–Hermite, unscented, sigma points) stay in gaussx.

4. Layer: Core tier 1

Decided (5 Nov 2026, on the layer map). integrix imports functax and gaussx, both Core tier 0, and extends diffrax. Every library that steps or integrates sits above it.

5. Adaptive quadrature: written here, on quadax’s design

Decided (5 Nov 2026). Adaptive Gauss–Kronrod, tanh–sinh, Romberg and cumulative integrals are written in integrix, drawing on quadax’s design, with no dependency on quadax. quadax’s results serve as references in tests.

6. Semi-Lagrangian advection stays in finitevolX

Decided (5 Nov 2026). It discards its departure points after each step, so it is a field scheme, not a stepper. 11 of finitevolX’s 12 stepper classes move (0.1.0 added AB3Solver); SemiLagrangianSolver stays and takes its interpolation from functax.

7. vardax’s adjoints: argminax’s, not integrix’s

Decided (5 Nov 2026), after reading vardax. vardax’s KStepAdjoint and OneStepAdjoint subclass optimistix.AbstractAdjoint: they differentiate through an inner minimiser (warm-up iterations under stop_gradient, then k live steps), not backward through time. They go to argminax, as the layer map says. integrix keeps the time adjoints of the forward run that 4D-Var’s cost contains: checkpointed backward marches and truncated scans.

8. Steppers: extend diffrax or own a loop?

Leaning: extend diffrax, with a small loop for multistep schemes. Eight of the eleven steppers moving from finitevolX (AB3Solver among them) already subclass diffrax’s solvers, so diffeqsolve, its step-size controllers and its adjoints keep working. The other three, AB2Solver, LeapfrogRAFSolver and SplitExplicitRKSolver, are plain eqx.Modules today, because multistep and split-explicit schemes carry state between steps that diffrax’s solver protocol handles awkwardly. integrix keeps them as its own stepping loop, with the same adjoint options.

9. Monte Carlo here or in pyrox?

Open. Plain Monte Carlo and quasi-Monte Carlo expectations are integration and fit integrix.expect. Sampling from a posterior (MCMC, SVI) stays in NumPyro and pyrox; integrix only averages what they draw.

Roadmap

Six waves, each a GitHub issue epic in the integrix repository once it exists. integrix needs functax’s rules first, so wave 0 waits on functax’s wave 0 (geometry) and its rules wave.

WaveDeliversEndsNeeds
0. Scaffoldthe repository from the library template; integrix.rule and integrix.scan on functax rules; CI, capability index, scipy.integrate reference tests—functax rules
1. Steppers move inthe 11 steppers from finitevolX, with deprecation aliases there; step-size control from sesmax-corefinitevolX’s steppers; sesmax-core’s generic step control0
2. Adjointstruncated_scan and to_diffrax_adjoint from pipekit-jax; checkpointed backward marches; reverse scansadjoint maths in pipekit-jax1
3. Adaptive quadratureGauss–Kronrod, tanh–sinh, Romberg and cumulative, on quadax’s design, with quadax as a test reference—0
4. ExpectationsMonte Carlo, quasi-Monte Carlo (Sobol, Halton) and importance samplingad hoc averages in plumax0
5. Consumers switchplumax, radtrax and xrtoolz sums onto ix.rule; sesmax-core and plumax les_fvm onto integrix steppers; vardax’s forward runs onto integrix.adjointthe hand-written sums and stepping in the stack1–4

Waves 1 and 3 can run side by side. Wave 5 is one issue per consumer, filed in that consumer’s repository and linked to the integrix epic.

Sources