Proposal · 5 November 2026

GeoStack layer map

The same number of layers as today, cut by a different test. Core holds the maths and plumbing that knows no geography, so GeoML dissolves into it. GeoNumerics holds geophysical maths on plain arrays; the data toolkits sit above it and wrap its kernels in carriers; the physics models sit on top.

Beneath Core sit the third-party foundations the whole stack is built on: JAX, Equinox, lineax, optimistix, diffrax, optax, NumPyro and the array libraries. The function-approximation core, functax, goes in Core tier 0, the one placement where geotoolz and finitevolX can both import it; integrix (integration over time, space and spectrum) and argminax (optimisation with uncertainty at the optimum) sit just above it.

The stack at a glance

Layers, tiers and libraries only. The interactive graph below draws every import between them.

Colour by
Dependency graph of the GeoStack libraries in the proposed layers. Third-party foundations at the bottom: JAX, Equinox, jaxtyping, lineax, optimistix, diffrax, einx, optax, NumPyro, FlowJAX, interpax, NumPy, SciPy, xarray, pyproj, Shapely, rasterio and georeader. Core above them holds the pipekit workspace (pipekit, pipekit-array, pipekit-train, pipekit-cycle, pipekit-jax, pipekit-evaluate), gaussx, functax, geonnax, xtremax, kernellib, optax_bayes, gauss_flows, filterax, vardax and the pyrox workspace (pyrox, pyrox-gp, pyrox-lgm, pyrox-nn). GeoNumerics above Core holds geophysix, gridax, spectraldiffx, diagnostix, radiomax, finitevolX and tracerax. GeoStack sit above GeoNumerics: the geotoolz workspace (geoarray, geocloud, geopatcher, geoops, geocatalog, geoproducts) and the xrtoolz workspace (xrproducts, xrops), with georeader among the third-party foundations. GeoModels (radtrax, sesmax-core, plumax, and the planned sesmax-ocean and sesmax-atmos) sit above both, and applications on top. functax, integrix and argminax are proposed.

Arrows point from a library to what it imports. Solid arrows are imports found in each library's source on 4 November 2026, at geostack's submodule pins, with tests, docs and notebooks left out; arrows into the foundations are thicker where a library imports them in more places. Dotted red arrows are imports today that are planned to go away. Dashed arrows are planned: the users of the three new libraries, functax, integrix and argminax. Arrows between the foundations themselves show their main published dependencies, which were not scanned. Foundation arrows appear when you select a library, or all at once with the toggle.

What we build on each foundation

Each row pairs a third-party library with the stack library that builds on it. Extends means our classes subclass the foundation's abstract types; replaces means one design of our own for the same job, and the stack stops depending on the original; wraps means calling it behind our own API. Counts come from the class scan of 4 November 2026.

Linear algebra
lineax
extends
gaussx
19 operator classes and a solver on lineax's abstract types, plus Gaussians and exponential families
Function approximation
interpaxorthaxquadax rulesscipy.interpolate
replaces
functax
one basis-and-plan design for interpolation, orthogonal polynomials and quadrature rules, borrowing from all three. No import and no dependency: interpax, orthax and quadax drop out of the stack, and gauss_flows (the one importer of interpax today) moves to functax
Integration
diffraxquadax adaptivescipy.integrate
extends
integrix
the 10 diffrax time steppers now in finitevolX (its semi-Lagrangian scheme stays there) and the adjoint strategies now in pipekit-jax; adaptive quadrature written here, drawing on quadax's design (adaptive Gauss–Kronrod, tanh–sinh, Romberg, cumulative integrals) without depending on it
Nonlinear solves
optimistix
extends
argminax
Gauss–Newton and Levenberg–Marquardt solvers, curvature at the optimum (Laplace covariance, averaging kernels) and adjoints; now spread over gaussx, pyrox and vardax
Optimisers
optax
extends
optax_bayes
the Bayesian learning rule as optax gradient transformations
Probabilistic programming
numpyro
extends
xtremaxgaussxpyrox
28 distribution classes: 18 in xtremax, 6 in gaussx, 4 in pyrox, each a different family
Normalising flows
flowjax
extends
gauss_flows
RBIG density estimation and information measures
Neural networks
equinox
extends
geonnaxpipekit-ops
geonnax's layer zoo; pipekit-jax's JaxOperator is an Equinox module that is also a pipekit operator
Raster data
rasterio / GDALgeoreader
wraps
geoarraygeoops
GDAL reads and a few warps behind GeoArray, reached only through geoarray.interop.georeader; georeader is spaceml-org's, read-only, and an engine rather than a layer
Labelled arrays
xarray
extends
xrops
operators on DataArray and Dataset

Core, reorganised

Every foundation gets at most one home in Core, and a new library is created only once a second consumer exists. Three new libraries meet that bar today: functax, integrix and argminax. Each card says what the library is, which foundation it extends (◇) or replaces outright (↺, no longer a dependency), what it builds on in the stack (↓) and who builds on it (↑). pyrox (four packages) and pipekit (six in Core, one in the apps) are workspaces, drawn one card per package.

imports todayplannedfoundation it extendsreplaced: design borrowed, no dependency◇ extends↺ replaces↓ builds on↑ used by

What moves

FromToWhat
finitevolXintegrix10 diffrax solver classes: forward Euler, RK2 to RK4, SSP-RK, IMEX, Adams–Bashforth, leapfrog with Robert–Asselin filter and split-explicit. SemiLagrangianSolver and semi_lagrangian_step stay: an advection scheme, field in and field out, that takes its interpolation from functax
pipekit-experimentGeoBenchthe whole package: tracking adapters, runs, the model registry, Hydra and DVC glue; nothing in the stack imports it
pipekit-trainnonedrop the [experiment] extra: declared, never imported, and it would point Core up at the apps
pipekit-jaxintegrixtruncated_scan, to_diffrax_adjoint; pipekit keeps composition and JaxOperator
geonnaxfunctaxthe whole _basis zoo, sphere grids and meshes, random-feature evaluation, diffops, the spherical-harmonic transform and non-uniform FFT, sphere geometry (section "functax and geonnax" below)
geonnaxgeophysixWGS84 constants and the ellipsoid
spectraldiffxfunctaxChebyshev and spherical grids with their quadrature; differentiation stays
kernellibfunctaxLegendre series
plumax, xropsfunctaxtrapezoid and Simpson rules
geoops, vardaxfunctaxbilinear samplers rebuilt on plans: interp_at, bilinear_stencil, InterpObs
somaxsesmax-corestate, terms, forcing, DA adapters and I/O, moved at somax 9cb19b3; the ocean models follow into sesmax-ocean, then somax is archived
sesmax-coreintegrixgeneric stepping and step-size control (stable_step, imex_stepsize_controller); model terms stay
pyrox-lgmargminaxthe inner Newton fit and the outer L-BFGS search of inla()
pyrox-gpkernellibits duplicate KernelOperator (one object, one name); spectral_density and funk_hecke_coefficients, which duplicate kernellib's public ones; pyrox-nn calls kernellib's directly (pyrox#331)
pyrox-gpfunctaxthe re-export of geonnax's bases in _basis; pyrox-gp and pyrox-nn import bases from functax directly
pyrox-gppyroxfreezing a module's priors before a call (_kernel_context, _freeze): PyroxModule machinery, made public in pyrox
gauss_flowsfunctaxits two interpax calls; interpax leaves the stack
pyrox-gpargminaxthe iteration in LaplaceInference, GaussNewtonInference, QuasiNewtonInference and their Markov versions
vardaxargminaxadjoint strategies (k-step, one-step, mapping), GaussNewtonHessian, LaplaceCovariance
pyroxpyrox-nnlast_layer_laplace, which is the core's only gaussx; unreleased, so it moves without a deprecation (pyrox#332). Its GGN curvature and evidence then go to argminax
gaussxargminaxthe Newton loop inside laplace_mode; gaussx keeps one step's algebra (newton_update, gauss_newton_precision) and its root finder
gaussxstaysGauss–Hermite, unscented and sigma-point rules (Gaussian expectations); the FEM projector, rebuilt on functax geometry

GeoNumerics, GeoStack and GeoModels, reorganised

The same view as Core above, one layer at a time. Each card says what the library is, what it builds on and who builds on it. Solid chips are imports in the code today; dashed chips are planned. A dashed outline around a name marks a proposed library.

imports todayplanned↓ builds on↑ used by

GeoNumerics

Numerical methods on geophysical domains, on plain arrays: the grids, spheres and physics geophysics uses, but no CRS and no data files. Seven libraries, five of them new; most of what fills them is already written, spread over geotoolz, xrtoolz and the models.

What moves in

FromToWhat
geoops (radiometry, geom), geoproductsradiomaxsolar and viewing geometry, radiometric conversions, Planck and brightness temperature (written three times in geotoolz today), parallax and geostationary navigation
geoarray, geoops (geom), xrops (grad)gridaxcurvilinear and swath cell geometry, diagnostic finite differences, metric arrays
geoarray, geoopsgeophysixpixel areas and the Earth's radius (defined four ways in geotoolz today)
radtrax, plumaxradiomaxper-pixel geometry, geometric air-mass factor, radiometric conversions
xrops (ocn, atm, budgets, transforms, anomaly, metrics)diagnostixvorticity, strain, Okubo–Weiss, geostrophy, N², column integrals, budgets; spectra, climatologies and anomalies, trends, metrics; the DataArray API stays in xrops
plumax (lagrangian), xropstraceraxthe particle machinery: ParticleState, langevin_step, integrate_particles, bin_positions, compute_footprint; turbulence closures (HannaTurbulence) and source inversions stay in plumax, and particles take σw(z) as an argument. xrops: the planned xrtoolz.lagrangian epic
sesmax-core, finitevolXgridaxvertical modes
sesmax-core, plumax, xropsgeophysixphysical constants, thermodynamics, seawater equations of state
finitevolXintegrix (Core)moves out: 10 diffrax time-stepper classes; the semi-Lagrangian scheme stays
spectraldiffxfunctax (Core)moves out: Chebyshev and spherical grids with their quadrature

GeoStack

GeoStack here means exactly geotoolz + xrtoolz, the two workspaces below; the whole project keeps the name too, and pipekit, which today's geostack dependency graph puts in its GeoStack layer, moves to Core. Real data: carriers, CRS, I/O and products. Two uv workspaces, eight packages, about 150k lines: the geotoolz workspace (six packages, raster and swath data on GeoArray) and the xrtoolz workspace (two packages, data cubes on xarray, with a third proposed below). Each package installs on its own; the geotoolz and xrtoolz umbrellas hold no code. georeader is not one of them: it is spaceml-org's GDAL reader, reached only through geoarray.interop.georeader as an engine behind a few readers and warps, so it sits with the third-party foundations.

WorkspacePackage (import)LinesWhat it isNamespaces
geotoolzgeotoolz-array (geoarray)7.5kthe carriers and their geometryGeoArray, GeoSwath, GeoPoints, Grid, PixelWindow, RasterSource, interop
geotoolz-cloud (geocloud)6.5kobject storagestore, files, credentials, cog, hdf, cache, fs
geotoolz-patcher (geopatcher)18.6ksplit, process, stitchspatial, temporal, integrations
geotoolz-ops (geoops)43.3kOperators on GeoArraygeom, radiometry, indices, mask, normalize, compositing, restore, segment, measure, spectral, feature, learn, augment, qa, viz, io, plume, matched_filter, einx
geotoolz-catalog (geocatalog)15.8kfind, index, load filesbuild, sources, backends, grid, load, matchup, patch, staging, storage
geotoolz-products (geoproducts)13.0kEO product readersgoes, himawari, carbonmapper, toy_sensor
xrtoolzxrtoolz-ops (xrops)35.7kOperators on DataArray, Dataset, DataTreegeom, mask, cf, anomaly, extremes, grad, transforms, wavelets, interpolate, ocn, atm, budgets, metrics, learn, inference, viz; lift
xrtoolz-products (xrproducts)9.1karchive readersCMEMS, CDS, AEMET, local methane L2 (TROPOMI, EMIT, GHGSat), inventories; types

Mirrored by role, not by workspace. The geotoolz and xrtoolz workspaces are how the code is released; the roles are the same on both sides, and the plumbing is shared. GeoArray carries observations (L0 to L2); xarray carries model states and analyses (L3 and up). Read across a row:

RoleObservations · GeoArrayStates · xarray
composepipekit (Core): Operators, graphs, cycles, training
transformgeoopsxrops
patchgeopatcher: GeoArray, xarray, rioxarray, dask and xvec fields
carrygeoarray: our carrier, NumPy or JAXxarray, third-party (coordax is in the GeoStack hero figure but not yet in code); its conventions have no package of their own: the gap
readgeoproducts (below geoops once its presets become data)xrproducts (below xrops)
indexgeocatalog, with geocatalog.states for analyses, forecasts and ensembles
storegeocloud: COG for observations, Zarr and NetCDF (geocloud.fs, geocloud.hdf) for states

The gap: a floor for the xarray side. On the GeoArray side the carrier's vocabulary lives at the bottom, in geoarray, and geotoolz has a rule that geoops never depends on geoproducts. On the xarray side the same vocabulary is split between a reader and the operators: the CF Variable registry and the typed vocabulary live in xrproducts.types, CF validation in xrops.cf, CRS and subsetting in xrops.geom. So xrops imports a reader package (eight imports of xrproducts.types) to name a variable, which is exactly the edge geotoolz forbids. The proposal is one small package at the bottom of the xarray side, here called xrcf (xrtoolz-cf; the name is open): CF conventions and the Variable registry, coordinate and CRS conventions, validation, subsetting and the typed vocabulary. It is not a new carrier (xarray is the carrier) but the conventions that make a DataArray a carrier. xrproducts and xrops both import it, and xrops stops importing xrproducts. The workspace has folded packages before (xrcore, xrgrad, xreinx and xrsklearn went into xrops), so this would be the one package that earns its place by holding the floor.

xrtoolz still sits on top of geotoolz: xrops's calculus runs on geoops's difference kernels, and xrops.lift runs any geoops Operator item by item over a cube.

What moves

FromToWhat
geoarray, geoopsfunctax (Core)affine grid maths (to_pixel, pixel centres, window shifts) and the bilinear samplers
geoarray, geoops, geoproductsGeoNumericsradiometry and geometry → radiomax; grid geometry → gridax; areas and constants → geophysix (table above)
geoops (plume, matched_filter)plumax, radtraxplume and matched-filter code (#482)
xrproducts (types), xrops (cf, geom CRS and subsetting)xrcf (proposed)the CF Variable registry, the typed vocabulary, coordinate and CRS conventions; xrops then drops its import of xrproducts
xropsGeoNumericsdiagnostics, spectra and metrics → diagnostix; Lagrangian → tracerax (table above)
geoproducts (presets)geoopssensor presets become plain data specs and geoops builds the Operator (RGBRecipe.from_spec); the [operators] extra goes, and geoproducts drops from tier 4 to tier 2, below geoops, mirroring xrproducts below xrops
xtremaxxropsmoves in: extraction and simulations on xarray
vardaxxropsmoves in: the xarray halves of extract_patches and the observation masks
geoops, xropsfunctax (Core)the gap-fill maths duplicated in geoops and xrops moves to one functax plan; each toolkit keeps a thin Operator on its own carrier

GeoModels

Physical models, on plain arrays, with a geo extra each that wraps the model in Operators on carriers. sesmax replaces somax and splits by component.

What moves

FromToWhat
somaxsesmax-corestate, terms, forcing, DA adapters and I/O (moved at somax 9cb19b3)
somaxsesmax-oceanthe shallow-water, QG and spherical ocean models; then somax is archived
geoops (plume, matched_filter)plumax, radtraxplume and matched-filter code (#482)
plumaxradtraxspectroscopy copies
sesmax-coreintegrix (Core)moves out: stable_step, imex_stepsize_controller; the model terms stay
radtraxargminax (Core)moves out: optimal-estimation loops
plumaxtraceraxmoves out: particle state, drift–diffusion stepping, binning and footprints; turbulence closures and inversions stay

radiomax and radtrax: where the line is

radiomax converts and locates what the sensor saw; radtrax explains why it saw it.

The test: does the computation need a model of radiation moving through the atmosphere (absorption and scattering along a path, spectroscopy, an atmospheric state)? Then it is radtrax. Is it geometry, a unit conversion, or an empirical formula with fitted coefficients applied pixel by pixel? Then it is radiomax.

Why they sit in different layers. radtrax is a forward model: it has a state you can retrieve (profiles, surface, gas columns), which makes it GeoModels. radiomax has no state; it is maths on arrays, which makes it GeoNumerics. The import goes one way, radtrax → radiomax, so anything in radiomax that seems to need radiative transfer belongs in radtrax instead.

Topicradiomax GeoNumericsradtrax GeoModels
Geometrysolar and viewing angles, Earth–Sun distance, parallax, geostationary navigation, swath and bowtie geometrytakes radiomax's angles as inputs
RadiometryDN → radiance → top-of-atmosphere reflectance, Planck, brightness temperaturenone
Air-mass factorgeometric: 1/cos θₛ + 1/cos θᵥfull: scattering weights, box air-mass factors
Spectral responsePlanck and brightness temperature with a band's effective coefficientsconvolving a modelled high-resolution spectrum with the SRF (the instrument module); the SRF tables stay in georeader
SST and LSTsplit-window formulas with fitted coefficientsphysical retrieval through the RTM
Atmospheric correctionsimple: dark-object subtraction, empirical linephysical (6S-style): aerosol and gas transmittance
Surfacekernel BRDF (Ross–Li), NBARsurface reflectance as part of the forward model
Methanenoneabsorption cross sections, unit-absorption targets, the matched filter, optimal estimation (its loops go to argminax)
SAR and altimetrycalibration, despeckling, tropospheric and ionospheric correctionsnone

Grey zones, settled by the same test

Today

The boundary is not clean in the code yet. Geometry and radiometry live in geoops, with copies in radtrax and plumax; brightness temperature alone is written three times in geotoolz. Building radiomax is mostly collecting those copies into one place, then pointing geoops, radtrax and plumax at it.

functax and geonnax: where the line is

functax says what a function family is and how to compute with it; geonnax says how to stack function families into a network.

The test: is it a function of coordinates used as an expansion, f(x) = Σⱼ cⱼ φⱼ(x; θ), together with its maths (evaluate, differentiate, transform, integrate, project, solve for c)? Then it is functax. Is it a stack of nonlinear layers, a map between fields, or a neural field, where the architecture is the point? Then it is geonnax.

Learned parameters are allowed on both sides. A basis can carry trainable θ: RBF centres and widths, Gabor and Fourier-feature frequencies, a Slepian region. functax keeps θ as ordinary pytree leaves, so gradients flow and anyone can fit them; it ships no network conventions (initialisation schemes, layer specs, depth). Whether θ is learned does not decide the side; whether the thing is an expansion or a network does.

Why split now. geonnax describes itself as a network zoo, but a third of it (about 3.5k lines in _basis) is bases "shared by NN spectral layers and GP inducing features", and that is what most of its users import. kernellib imports only bases, sphere grids, random features and lon/lat helpers; vardax and sesmax-core import only geonnax.basis; pyrox-gp re-exports geonnax's bases and keeps only the kernel-aware pieces. pyrox-nn is the one library that uses geonnax as a network library. Four of five users depend on a network zoo for maths.

geonnax todayGoes toWhy
bases: Legendre and Gauss–Legendre, scalar and vector spherical harmonics, Slepian (caps and regions), needlets, box Fourier and divergence-free, RBF and Wendland, Gabor frames, DWT matrices, shell productsfunctaxexpansions and their maths; trainable θ stays possible
sphere grids and quadrature, icosphere and meshes, Laplace–Beltrami eigenbasisfunctaxCRS-free geometry and quadrature
EOF basis; graph-Laplacian eigenpairsfunctaxa basis from data (weights as an argument) or from a given operator; diagnostix adds area weights and the analysis, kernellib builds the graph
randfeat: Fourier features at given frequenciesfunctaxdrawing frequencies from a kernel's spectral density goes to kernellib, with pyrox-gp's _spectral_density
diffops: grad, Laplacian, surface gradient, Laplace–Beltrami of continuous fieldsfunctaxderivatives of functions; derivatives on grids belong to gridax, finitevolX and spectraldiffx
spherical-harmonic transform, non-uniform FFTfunctaxbasis transforms; spectraldiffx uses the same SHT
geo and _frames: lon/lat ↔ unit vectors, rotations, tangent framesfunctaxsphere geometry
WGS84 constants and the ellipsoidgeophysixCore functions take a and f as plain arguments; geophysix supplies them
layers: spectral and spherical spectral convolution, FNO, SFNO, GeoFNO, WNO, LNO, CNO, U-Net, attention, norms, basis convolutiongeonnaxnetworks over fields; they call functax transforms and bases
neural fields: SIREN, MFN, multi-scale SIREN, separable INR, NeuralFieldMLP, NCPgeonnaxstacks of nonlinear layers
SNGP and VSSGP cores, conditioning and hypernetworks, mixture and heteroscedastic heads, CRF, ensembles, spectral normalisationgeonnaxnetwork components
encoders: SphericalHarmonicEncoder, SlepianEncoder, GeoContextEncodergeonnaxthin Modules over functax features that carry network conventions (feature axis, named specs)

What it changes in the graph

Grey zones

The four layers, and the test for each

Three kinds of function, three kinds of data

This part now has its own working page, Kinds and roles. It holds the problem (why geo code is hard to write well), the objects in symbols, the role ladder from ⓪ structures to ⑥ recipes with orchestration above it, who works where, one vocabulary per axis, and the four rules: the cell decides the layer, the tier decides what a library may import, the kind and the data decide what a name may take and call, and each library declares its cells.

In short: code is packaged as a function, a Module or an Operator, and sees arrays, named arrays or carriers. Functions and Modules live as low as their imports allow, on plain arrays; Operators on carriers live where the carriers are. A carrier never goes below GeoStack, and a config never reaches a function. The rung chips on the cards above (⓪ to ⑥) come from that page's ladder.

Boundaries, library by library

What each library owns, what it hands to another library, and what it imports from the stack. Solid chips are imports in the code today; dashed chips are planned. The rule underneath all of it: functions and Modules live as low as they can, on plain arrays; Operators on carriers live where the carriers are. Under each library are the levels and data it exports.

LibraryOwnsHands offImports from the stack

Every library, today and proposed

LibraryTodayProposedImports from the stackWhy there

Which foundations each library leans on

Each cell counts the import statements of that foundation in the library's source. lineax, optimistix and diffrax are core to the numerics and models; GeoStack lean on NumPy, rasterio, Shapely and pyproj instead. The functax row is the plan, not code.

Judgement calls

  1. Data assimilation. vardax and filterax import only Core, so the test puts them there. If you read DA as geophysical state estimation, they move up to GeoNumerics.
  2. geonnax. The name says geo, but its encoders and sphere grids are maths on the sphere, which passes the Core test.
  3. xtremax. Its "spatiotemporal data extraction" belongs in GeoStack if it reads products or CRS data; the modelling stays in Core.
  4. One diagnostics library, not two. Physical diagnostics need statistics (eddy kinetic energy and eddy fluxes are built on anomalies) and spectral statistics need kinematics (spectral fluxes use velocities), so a split would make each import the other. diagnostix holds both, in submodules: kinematics, column, budgets, stats, spectral, modes, skill. What goes in: a quantity derived from a geophysical field, physical or statistical, that needs area weights, a calendar or a constant; signal processing that needs none of these is functax's.
  5. Eulerian or Lagrangian. finitevolX and spectraldiffx hold operators on fields, whose state stays on the grid; tracerax holds positions carried forward in time and the analysis of their trajectories. Semi-Lagrangian advection traces a departure point for one step and discards it, so it stays with the field schemes. FTLE needs the flow map, so it is tracerax's; Okubo–Weiss uses the instantaneous field, so it is diagnostix's. The two sides never import each other: a model couples them through velocity in and concentration out.
  6. spectraldiffx. Its package description is stale ("Pre-, Geo-, and ML processing for ocean applications") and should say what it does.

What re-layering costs

  • The layer table and tiers in scripts/dependency_graph.py, and the diagram it generates.
  • One capability index per layer: Core replaces GeoML.
  • The pixi environments: geoml becomes core, or merges into the default.
  • Five imports go away, each by a move planned on this page: kernellib and pyrox-gp take bases from functax instead of geonnax (the geonnax split); pyrox-nn takes bases, spectral densities and prior freezing from functax, kernellib and pyrox instead of pyrox-gp; the pyrox core stops importing gaussx, because last-layer Laplace moves to pyrox-nn; and geoproducts stops importing geoops (its presets become data). Every other import found today fits as it is, with vardax at Core tier 4, above pipekit-jax, and gridax at GeoNumerics tier 1, above geophysix, whose cell areas it uses.