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.

argminax: optimisation with uncertainty for JAX

argminax is the stack’s optimisation library with uncertainty at the optimum: it finds x̂ = argmin J(x), and then says how sharp that minimum is (posterior covariance, averaging kernel, degrees of freedom) and how x̂ depends on what went in. It sits at Core tier 1 on gaussx, extends optimistix with the stack’s solvers and adjoints, and takes over the curvature code now rebuilt in seven places. Status: proposed, at design-sketch depth.

At a glance

Layer, tierCore, tier 1
Importsgaussx; extends optimistix; JAX, Equinox, lineax
Imported byradtrax, plumax, vardax, pyrox-gp, pyrox-lgm, pyrox-nn
Takes overvardax’s adjoint strategies and posterior adapters; radtrax’s retrieval diagnostics; the Newton loops of gaussx, pyrox-gp and pyrox-lgm; plumax’s 4D-Var posterior
Does not owntraining over batches (optax, optax_bayes); sampling (NumPyro, pyrox); adjoints through time (integrix)
Statusproposed; no repository yet
Tracked inthe layer map; the companion functax and integrix designs
where argminax sits · Core tier 1

Where argminax sits · core tier 1.

argminax sits one tier above gaussx, whose single steps it iterates, so every library that minimises can import it. This page is the design; core types, algorithms and the code survey are in argminax: design details.

The problem

A methane retrieval is not finished when the optimiser stops. radtrax’s 3D-Var (retrieval/var3d) finds the column enhancement that best fits a hyperspectral pixel, and then needs three more things at that minimum: how uncertain the answer is, how much of it came from the measurement rather than the prior, and how its gradient flows back to the plume model that called it. Those three are the curvature at the minimum, and every library that optimises rebuilds them:

LibraryMinimisesRebuilds at the minimum
radtrax retrieval/var3d3D-Var by L-BFGS, Gauss–Newton or dual PSAS (optimistix)degrees_of_freedom_for_signal, posterior_covariance_proxy (the averaging kernel appears only in docstrings)
plumax les_fvm/fourdvar.py4D-Var for an emission field (optimistix)PosteriorCovariance, _gauss_newton_hessian
plumax assimilation/solve.pythe same 3D-Var solvers radtrax ported from it—
vardax3D-Var, strong, weak and incremental 4D-Var (optimistix)GaussNewtonHessian, LaplaceCovariance; KStepAdjoint, OneStepAdjoint
pyrox-gpnon-Gaussian GP likelihoodsthe Newton iterations of LaplaceInference, GaussNewtonInference, QuasiNewtonInference
pyrox-lgminla(): an inner Newton fit, an outer optax.lbfgs searchthe Laplace approximation per hyperparameter
gaussxlaplace_modeits own Newton loop
pyrox (main)last_layer_laplaceGGN precision, evidence, prior-precision search

The solvers mostly come from optimistix already. What each library rewrites is what optimistix leaves out: Gauss–Newton curvature as a structured operator, its inverse as a posterior covariance, the degrees of freedom of a retrieval, and adjoints for differentiating through a minimiser that stops early. Seven copies of that machinery, across GeoModels and Core, with plumax and radtrax each holding the same 3D-Var solvers.

The cost, its minimum, and the curvature there

A retrieval minimises a cost that balances the measurement against the prior. Here y is the measured radiance, F the forward model, x the state (the methane column), xₐ the prior state, Sε the measurement-error covariance and Sₐ the prior covariance:

J(x)=12(y−F(x))⊤Sε−1(y−F(x))+12(x−xa)⊤Sa−1(x−xa)J(x) = \frac{1}{2} (y - F(x))^\top S_\varepsilon^{-1} (y - F(x)) + \frac{1}{2} (x - x_a)^\top S_a^{-1} (x - x_a)

Three jobs share the word optimisation, and only one belongs here:

JobQuestionHome
Solvewhere is the minimum x̂?optimistix, extended by argminax’s solvers
Curvaturehow sharp is the minimum, and what does that say about x̂?argminax
Trainhow do I fit a network’s weights over many batches?optax and optax_bayes

At the minimum x̂, with K = ∂F/∂x the Jacobian there, the Gauss–Newton curvature gives the posterior covariance Ŝ and the averaging kernel A:

S^=(K⊤Sε−1K+Sa−1)−1\hat S = (K^\top S_\varepsilon^{-1} K + S_a^{-1})^{-1}
A=S^K⊤Sε−1K=I−S^Sa−1A = \hat S K^\top S_\varepsilon^{-1} K = I - \hat S S_a^{-1}

A says how much a change in the true state moves the retrieved one: A = I means the measurement decides everything, A = 0 means the prior does. Its trace is the retrieval’s degrees of freedom for signal. The same Ŝ is the inverse Gauss–Newton Hessian of 4D-Var, the Laplace approximation in pyrox and the precision in gaussx; only the names differ.

The last piece is the derivative of x̂ with respect to what went in (the prior, the parameters of F). At an exact minimum it comes from the implicit function theorem; for a solver stopped early, as in learned 4D-Var, it comes from differentiating the last k iterations. Both are adjoint strategies of the minimiser.

Modules and boundaries

Four modules: the solvers optimistix lacks, the curvature at the minimum, the retrieval diagnostics built on it, and the adjoints of a minimiser. argminax extends optimistix as gaussx extends lineax: its solvers are optimistix solvers and its adjoints are optimistix adjoints, so optx.minimise and optx.least_squares keep working.

ModuleOwnsComes from
argminax.solveGauss–Newton and Levenberg–Marquardt with a matrix-free inner CG (incremental 4D-Var), trust regions, the dual (observation-space) solve, positivity and bounds by reparametrisationradtrax and plumax run_gauss_newton, run_dual_psas; pyrox-gp’s and gaussx’s Newton loops
argminax.curvatureGauss–Newton, Fisher and quasi-Newton curvature, Laplace covariance Ŝ, its low-rank and diagonal forms, all as gaussx operatorsvardax GaussNewtonHessian, LaplaceCovariance; plumax PosteriorCovariance; pyrox last_layer_laplace’s GGN
argminax.diagnoseaveraging kernel A, degrees of freedom for signal, information content, evidence at the minimumradtrax var3d/diagnostics.py; pyrox’s evidence and prior-precision search
argminax.adjointadjoints of a minimiser stopped early: k-step and one-step. At a converged minimum optimistix’s own ImplicitAdjoint already does the job, so argminax adds none.vardax KStepAdjoint, OneStepAdjoint, to_optimistix_adjoint

What argminax owns, and what it hands off

The test for every name: does it find a minimum, or say something about the curvature at one? Then argminax owns it.

Neighbourargminax ownsThe neighbour ownsThe line
optimistixthe stack’s solvers and adjoints, as optimistix subclassesminimise, least_squares, root_find, BFGS, its solver and adjoint protocolsargminax extends optimistix
gaussxthe iteration to a toleranceone step’s algebra (newton_update, gauss_newton_precision), structured solves, its Chandrupatla root findera single step is gaussx’s; the loop around it is argminax’s
integrixadjoints of a minimiseradjoints of a march: checkpointed backward sweeps through a forward runthrough the iterations of an optimiser is argminax’s; through time is integrix’s
optax, optax_bayesdeterministic solves to a tolerancestochastic training over batches; the Bayesian learning rulemany batches and a learning rate are optax’s
pyrox, NumPyrothe Laplace approximation as curvaturesampling, variational inference, priorsa Gaussian at the mode is argminax’s; anything that samples is pyrox’s
radtrax, plumax, vardaxthe solver, curvature and diagnosticsthe cost: forward model, observation operator, B and Rthe library passes J (or its residuals) in

What moves in

FromWhatIts users then import
vardaxKStepAdjoint, OneStepAdjoint, to_optimistix_adjoint; GaussNewtonHessian, LaplaceCovarianceargminax: vardax, plumax
radtrax retrieval/var3drun_gauss_newton, run_dual_psas; degrees_of_freedom_for_signal, posterior_covariance_proxyargminax: radtrax; plumax drops its copy in assimilation/solve.py
plumax les_fvm/fourdvar.pyPosteriorCovariance, _gauss_newton_hessianargminax
pyrox-gpthe iterations of LaplaceInference, GaussNewtonInference, QuasiNewtonInference and their Markov formsargminax; pyrox-gp keeps the likelihoods and the GP algebra
pyrox-lgmthe inner Newton fit and the outer search of inla()argminax
gaussxlaplace_mode, the loop and its entry point; gaussx keeps one step’s algebra and its root finderargminax (amx.solve.laplace_mode); gaussx’s copy is deprecated without forwarding
pyrox (after pyrox#332, via pyrox-nn)last_layer_laplace’s curvature, evidence and prior-precision searchargminax; pyrox-nn keeps the last-layer wrapper

In use

The methane retrieval end to end: solve for the column, then read the uncertainty, the averaging kernel and the degrees of freedom off the curvature at the minimum, and differentiate the answer with respect to the prior. Every argminax name is proposed; the solve itself is ordinary optimistix.

# Shapes: W spectral channels; L layers in the state; Sε is (W, W), Sₐ is (L, L)

import argminax as amx
import jax
import optimistix as optx


# user
# r(x) = (Sε^(-1/2) (y − F(x)), Sₐ^(-1/2) (x − xₐ)), so J(x) = ½ ‖r(x)‖²
def residuals(state, args): ...  # (L,) → (W + L,)


# proposed (argminax)
# Gauss–Newton with an inner CG on (KᵀSε⁻¹K + Sₐ⁻¹) δ = −∇J
solver = amx.solve.GaussNewtonCG(rtol=1e-6, atol=1e-6)
# not pinned (optimistix)
# d x̂ / d(xₐ, θ) by the implicit function theorem at x̂
adjoint = optx.ImplicitAdjoint()
# not pinned (optimistix)
# x̂ = argmin J(x)
solution = optx.least_squares(  # (L,) → (L,)
    residuals, solver, prior_state, args=problem, adjoint=adjoint
)
retrieved = solution.value  # → (L,)

# --- curvature at the minimum ---
# proposed (argminax)
# Ŝ = (KᵀSε⁻¹K + Sₐ⁻¹)⁻¹, a gaussx operator, never formed densely
posterior = amx.curvature.gauss_newton(residuals, retrieved, problem)  # → (L, L) op
# proposed (argminax)
# A = I − Ŝ Sₐ⁻¹
kernel = amx.diagnose.averaging_kernel(posterior, prior_covariance)  # → (L, L) op
# proposed (argminax)
# d_s = tr(A), the degrees of freedom for signal
dofs = amx.diagnose.degrees_of_freedom(kernel)  # → ()

# --- the retrieval inside a larger model ---
# user: retrieve(xₐ) wraps the least_squares call above
# x̂ is differentiable: ∂x̂/∂xₐ flows through the implicit adjoint
sensitivity = jax.jacrev(retrieve)(prior_state)  # (L,) → (L, L)

A plume inversion calls retrieve per pixel and differentiates through it; the implicit adjoint costs one more linear solve with the same curvature, not a pass through every iteration. A learned 4D-Var that stops after a fixed number of steps swaps in amx.adjoint.KStep(k=3).

Integration across the stack

Six libraries would import argminax. For radtrax, plumax and vardax the solve call barely changes, because they already use optimistix: what changes is that the curvature, diagnostics and adjoints come from one place.

ConsumerUses todayWith argminax
radtraxretrieval/var3d: run_lbfgs, run_gauss_newton, run_dual_psas on optimistix, but each returns NumPy arrays, so no retrieval is differentiable today; its own degrees of freedom and posterior proxy, with no averaging-kernel functionamx.solve for Gauss–Newton and the dual solve; amx.curvature and amx.diagnose for Ŝ, A and d_s; keeps the cost and the control transforms
plumaxles_fvm/fourdvar.py with its own PosteriorCovariance; assimilation/solve.py, a copy of radtrax’s solversamx.curvature for the 4D-Var posterior; drops assimilation/solve.py in favour of argminax (or radtrax)
vardaxoptimistix BFGS and Gauss–Newton; its own adjoint strategies and posterior adaptersimports them from argminax; keeps the 3D-Var and 4D-Var costs and models
pyrox-gpNewton, Gauss–Newton and quasi-Newton loops inside its non-Gaussian inferenceamx.solve for the loop; keeps likelihoods, sites and GP algebra
pyrox-lgminla(): an inner Newton fit and an outer optax.lbfgs searchamx.solve for both; keeps the GMRF components and PC priors
pyrox-nnlast_layer_laplace (after pyrox#332)amx.curvature and amx.diagnose for the GGN posterior and evidence; keeps the last-layer selection

vardax’s adjoint strategies are the clearest move: they are already optimistix.AbstractAdjoint subclasses, so for vardax only the import changes. They are not yet usable elsewhere: their apply() raises NotImplementedError, and they work only inside vardax’s own unrolled solver. Wave 1 implements apply, so they work under any optimistix solve.

# Today:
# today (vardax)
from vardax.adjoints import KStepAdjoint

# After:
# proposed (argminax)
from argminax.adjoint import KStep

vardax keeps a deprecated alias for one release, and FourDVarNet1D(solver_adjoint=...) call sites do not change.

Decisions and open questions

Five decided, three open. Each entry gives the position first, then the trade-off.

1. Name: argminax

Decided (5 Nov 2026). argminax, free on PyPI when checked. It extends optimistix, as gaussx extends lineax.

2. Scope: solve and curvature, not training

Decided (5 Nov 2026). argminax owns deterministic solves to a tolerance and what the curvature at the minimum says. Stochastic training over batches stays with optax and optax_bayes; sampling and variational inference stay with NumPyro and pyrox.

3. Layer: Core tier 1

Decided (5 Nov 2026, on the layer map). argminax imports gaussx (Core tier 0) and extends optimistix. vardax, pyrox-gp, pyrox-lgm, pyrox-nn, radtrax and plumax sit above it.

4. gaussx keeps one step; argminax owns the loop

Decided (5 Nov 2026; revised 6 Nov 2026). gaussx keeps newton_update, gauss_newton_precision and its Chandrupatla root finder, because they are single-step algebra a tier below. laplace_mode, the loop that iterates them, moves to argminax whole, entry point included, and its callers (pyrox-gp, pyrox-lgm) import it from there. gaussx never calls argminax: a laplace_mode left in gaussx that forwarded to argminax would be an edge from Core tier 0 up to tier 1, and the repository fixes such an edge by moving code, never by a re-export (AGENTS.md, “Boundaries”). gaussx’s own laplace_mode keeps its current loop for one release behind a DeprecationWarning that names argminax.solve.laplace_mode, then goes.

5. vardax’s adjoint strategies move here

Decided (5 Nov 2026). KStepAdjoint and OneStepAdjoint are optimistix.AbstractAdjoint subclasses: they differentiate through a minimiser, so they are argminax’s. Today their apply() raises NotImplementedError and they work only inside vardax’s unrolled solver; the move implements apply, so they work under any optx.minimise or optx.least_squares call. Backward sweeps through a forward run stay integrix’s (integrix decision 7).

6. Where radtrax’s 3D-Var solvers live

Open. run_gauss_newton and run_dual_psas are general (any sum of squared residuals; any linear observation operator), so they fit amx.solve. run_lbfgs is a thin wrapper over optimistix and may simply go. The cost, control transforms and whitening stay in radtrax. Either way, plumax’s copy in assimilation/solve.py goes.

7. Positivity and bounds

Leaning: by reparametrisation. Methane enhancements and emission rates are positive. A log or softplus transform keeps the solve unconstrained and the curvature well defined; the posterior is then reported in the transformed space, with a note on how to map it back. Projected methods stay out until a case needs them.

8. Diagnostics: argminax or radtrax?

Open. The averaging kernel, degrees of freedom and information content come from atmospheric retrieval Rodgers, 2000, but they are properties of any linear-Gaussian posterior: the same A describes a 4D-Var analysis or a GP posterior. Leaning: argminax, with the retrieval vocabulary in its docstrings.

Roadmap

Five waves, each a GitHub issue epic in the argminax repository once it exists. The adjoints and curvature come first: they already exist in vardax and need a home, plus the apply method they lack, and every later wave uses them.

WaveDeliversEndsNeeds
0. Scaffoldthe repository from the library template; CI, capability index; optimistix and SciPy reference tests——
1. Adjoints and curvature move inKStep, OneStep, the optimistix mapping; Gauss–Newton and Laplace curvature as gaussx operatorsvardax’s copies, with deprecation aliases there0
2. Retrieval diagnosticsaveraging kernel, degrees of freedom, information content, evidenceradtrax’s var3d/diagnostics.py; pyrox’s evidence code1
3. SolversGauss–Newton and Levenberg–Marquardt with inner CG, the dual solve, trust regions, reparametrised boundsradtrax’s and plumax’s 3D-Var solvers (decision 6); gaussx’s Newton loop1
4. Consumers switchradtrax, plumax, vardax, pyrox-gp, pyrox-lgm and pyrox-nn onto argminaxthe remaining Newton loops and posterior adapters1–3

Wave 4 is one issue per consumer, filed in that consumer’s repository and linked to the argminax epic. pyrox-nn’s issue follows pyrox#332, which moves last_layer_laplace out of the pyrox core.

Sources

References
  1. Rodgers, C. D. (2000). Inverse Methods for Atmospheric Sounding: Theory and Practice. World Scientific.
  2. Bolte, J., Pauwels, E., & Vaiter, S. (2023). One-step differentiation of iterative algorithms. Advances in Neural Information Processing Systems, 36.