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.
Layers, tiers and libraries only. The interactive graph below draws every import between them.
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.
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.
geoarray.interop.georeader; georeader is spaceml-org's, read-only, and an engine rather than a layerEvery 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.
| From | To | What |
|---|---|---|
| finitevolX | integrix | 10 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-experiment | GeoBench | the whole package: tracking adapters, runs, the model registry, Hydra and DVC glue; nothing in the stack imports it |
| pipekit-train | none | drop the [experiment] extra: declared, never imported, and it would point Core up at the apps |
| pipekit-jax | integrix | truncated_scan, to_diffrax_adjoint; pipekit keeps composition and JaxOperator |
| geonnax | functax | the 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) |
| geonnax | geophysix | WGS84 constants and the ellipsoid |
| spectraldiffx | functax | Chebyshev and spherical grids with their quadrature; differentiation stays |
| kernellib | functax | Legendre series |
| plumax, xrops | functax | trapezoid and Simpson rules |
| geoops, vardax | functax | bilinear samplers rebuilt on plans: interp_at, bilinear_stencil, InterpObs |
| somax | sesmax-core | state, terms, forcing, DA adapters and I/O, moved at somax 9cb19b3; the ocean models follow into sesmax-ocean, then somax is archived |
| sesmax-core | integrix | generic stepping and step-size control (stable_step, imex_stepsize_controller); model terms stay |
| pyrox-lgm | argminax | the inner Newton fit and the outer L-BFGS search of inla() |
| pyrox-gp | kernellib | its 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-gp | functax | the re-export of geonnax's bases in _basis; pyrox-gp and pyrox-nn import bases from functax directly |
| pyrox-gp | pyrox | freezing a module's priors before a call (_kernel_context, _freeze): PyroxModule machinery, made public in pyrox |
| gauss_flows | functax | its two interpax calls; interpax leaves the stack |
| pyrox-gp | argminax | the iteration in LaplaceInference, GaussNewtonInference, QuasiNewtonInference and their Markov versions |
| vardax | argminax | adjoint strategies (k-step, one-step, mapping), GaussNewtonHessian, LaplaceCovariance |
| pyrox | pyrox-nn | last_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 |
| gaussx | argminax | the Newton loop inside laplace_mode; gaussx keeps one step's algebra (newton_update, gauss_newton_precision) and its root finder |
| gaussx | stays | Gauss–Hermite, unscented and sigma-point rules (Gaussian expectations); the FEM projector, rebuilt on functax geometry |
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.
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.
| From | To | What |
|---|---|---|
geoops (radiometry, geom), geoproducts | radiomax | solar and viewing geometry, radiometric conversions, Planck and brightness temperature (written three times in geotoolz today), parallax and geostationary navigation |
geoarray, geoops (geom), xrops (grad) | gridax | curvilinear and swath cell geometry, diagnostic finite differences, metric arrays |
| geoarray, geoops | geophysix | pixel areas and the Earth's radius (defined four ways in geotoolz today) |
| radtrax, plumax | radiomax | per-pixel geometry, geometric air-mass factor, radiometric conversions |
xrops (ocn, atm, budgets, transforms, anomaly, metrics) | diagnostix | vorticity, strain, Okubo–Weiss, geostrophy, N², column integrals, budgets; spectra, climatologies and anomalies, trends, metrics; the DataArray API stays in xrops |
plumax (lagrangian), xrops | tracerax | the 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, finitevolX | gridax | vertical modes |
| sesmax-core, plumax, xrops | geophysix | physical constants, thermodynamics, seawater equations of state |
| finitevolX | integrix (Core) | moves out: 10 diffrax time-stepper classes; the semi-Lagrangian scheme stays |
| spectraldiffx | functax (Core) | moves out: Chebyshev and spherical grids with their quadrature |
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.
| Workspace | Package (import) | Lines | What it is | Namespaces |
|---|---|---|---|---|
| geotoolz | geotoolz-array (geoarray) | 7.5k | the carriers and their geometry | GeoArray, GeoSwath, GeoPoints, Grid, PixelWindow, RasterSource, interop |
geotoolz-cloud (geocloud) | 6.5k | object storage | store, files, credentials, cog, hdf, cache, fs | |
geotoolz-patcher (geopatcher) | 18.6k | split, process, stitch | spatial, temporal, integrations | |
geotoolz-ops (geoops) | 43.3k | Operators on GeoArray | geom, radiometry, indices, mask, normalize, compositing, restore, segment, measure, spectral, feature, learn, augment, qa, viz, io, plume, matched_filter, einx | |
geotoolz-catalog (geocatalog) | 15.8k | find, index, load files | build, sources, backends, grid, load, matchup, patch, staging, storage | |
geotoolz-products (geoproducts) | 13.0k | EO product readers | goes, himawari, carbonmapper, toy_sensor | |
| xrtoolz | xrtoolz-ops (xrops) | 35.7k | Operators on DataArray, Dataset, DataTree | geom, mask, cf, anomaly, extremes, grad, transforms, wavelets, interpolate, ocn, atm, budgets, metrics, learn, inference, viz; lift |
xrtoolz-products (xrproducts) | 9.1k | archive readers | CMEMS, 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:
| Role | Observations · GeoArray | States · xarray |
|---|---|---|
| compose | pipekit (Core): Operators, graphs, cycles, training | |
| transform | geoops | xrops |
| patch | geopatcher: GeoArray, xarray, rioxarray, dask and xvec fields | |
| carry | geoarray: our carrier, NumPy or JAX | xarray, third-party (coordax is in the GeoStack hero figure but not yet in code); its conventions have no package of their own: the gap |
| read | geoproducts (below geoops once its presets become data) | xrproducts (below xrops) |
| index | geocatalog, with geocatalog.states for analyses, forecasts and ensembles | |
| store | geocloud: 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.
| From | To | What |
|---|---|---|
| geoarray, geoops | functax (Core) | affine grid maths (to_pixel, pixel centres, window shifts) and the bilinear samplers |
| geoarray, geoops, geoproducts | GeoNumerics | radiometry and geometry → radiomax; grid geometry → gridax; areas and constants → geophysix (table above) |
geoops (plume, matched_filter) | plumax, radtrax | plume 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 |
| xrops | GeoNumerics | diagnostics, spectra and metrics → diagnostix; Lagrangian → tracerax (table above) |
geoproducts (presets) | geoops | sensor 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 |
| xtremax | xrops | moves in: extraction and simulations on xarray |
| vardax | xrops | moves in: the xarray halves of extract_patches and the observation masks |
| geoops, xrops | functax (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 |
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.
| From | To | What |
|---|---|---|
| somax | sesmax-core | state, terms, forcing, DA adapters and I/O (moved at somax 9cb19b3) |
| somax | sesmax-ocean | the shallow-water, QG and spherical ocean models; then somax is archived |
geoops (plume, matched_filter) | plumax, radtrax | plume and matched-filter code (#482) |
| plumax | radtrax | spectroscopy copies |
| sesmax-core | integrix (Core) | moves out: stable_step, imex_stepsize_controller; the model terms stay |
| radtrax | argminax (Core) | moves out: optimal-estimation loops |
| plumax | tracerax | moves out: particle state, drift–diffusion stepping, binning and footprints; turbulence closures and inversions stay |
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.
| Topic | radiomax GeoNumerics | radtrax GeoModels |
|---|---|---|
| Geometry | solar and viewing angles, Earth–Sun distance, parallax, geostationary navigation, swath and bowtie geometry | takes radiomax's angles as inputs |
| Radiometry | DN → radiance → top-of-atmosphere reflectance, Planck, brightness temperature | none |
| Air-mass factor | geometric: 1/cos θₛ + 1/cos θᵥ | full: scattering weights, box air-mass factors |
| Spectral response | Planck and brightness temperature with a band's effective coefficients | convolving a modelled high-resolution spectrum with the SRF (the instrument module); the SRF tables stay in georeader |
| SST and LST | split-window formulas with fitted coefficients | physical retrieval through the RTM |
| Atmospheric correction | simple: dark-object subtraction, empirical line | physical (6S-style): aerosol and gas transmittance |
| Surface | kernel BRDF (Ross–Li), NBAR | surface reflectance as part of the forward model |
| Methane | none | absorption cross sections, unit-absorption targets, the matched filter, optimal estimation (its loops go to argminax) |
| SAR and altimetry | calibration, despeckling, tropospheric and ionospheric corrections | none |
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.
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 today | Goes to | Why |
|---|---|---|
| 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 products | functax | expansions and their maths; trainable θ stays possible |
| sphere grids and quadrature, icosphere and meshes, Laplace–Beltrami eigenbasis | functax | CRS-free geometry and quadrature |
| EOF basis; graph-Laplacian eigenpairs | functax | a 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 frequencies | functax | drawing 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 fields | functax | derivatives of functions; derivatives on grids belong to gridax, finitevolX and spectraldiffx |
| spherical-harmonic transform, non-uniform FFT | functax | basis transforms; spectraldiffx uses the same SHT |
geo and _frames: lon/lat ↔ unit vectors, rotations, tangent frames | functax | sphere geometry |
| WGS84 constants and the ellipsoid | geophysix | Core 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 convolution | geonnax | networks over fields; they call functax transforms and bases |
| neural fields: SIREN, MFN, multi-scale SIREN, separable INR, NeuralFieldMLP, NCP | geonnax | stacks of nonlinear layers |
| SNGP and VSSGP cores, conditioning and hypernetworks, mixture and heteroscedastic heads, CRF, ensembles, spectral normalisation | geonnax | network components |
encoders: SphericalHarmonicEncoder, SlepianEncoder, GeoContextEncoder | geonnax | thin Modules over functax features that carry network conventions (feature axis, named specs) |
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.
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.
| Library | Owns | Hands off | Imports from the stack |
|---|
| Library | Today | Proposed | Imports from the stack | Why there |
|---|
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.
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.scripts/dependency_graph.py, and the diagram it generates.geoml becomes core, or merges into the default.