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.

GeoStack overview

A JAX-native research stack for geophysical modeling, inference and data assimilation

Abstract

There is no Keras for geophysics. GeoStack is the connective tissue that is missing: one operator-graph abstraction that runs from structured linear algebra up to end-to-end pipelines, with content-addressed data and models throughout and machine learning pluggable at every step. This page is the map. It ties together the fifteen scoping reports, the GeoModels pages and the benchmarks gallery (Where to read next), and it ends with the open questions and a plan.

Keywords:geophysicsdata assimilationJAXoperator graphsremote sensing

1The decision

GeoStack is one operator algebra across three stacks: GeoML (numerics and inference), GeoModels (forward models) and GeoStack proper (data, pipelines, evaluation). None merges into another: the JAX libraries stay pipeline-free and reach pipekit only through thin adapters/pipekit.py modules.

The work splits into six workstreams. Each makes one decision, and the letters tag every proposed (X) call on the examples page and every gate in Table 6.

End state. GeoML owns linear algebra, kernels and inference and never imports pipekit outside an adapter. GeoModels owns physics and exposes it only through adapters. pipekit owns composition, cycles, training and evaluation. geocatalog owns the one index, for observations and, through its state profile, for model states; geotoolz and xrtoolz own the carrier-specific operators. Orchestration and serving stay external.

2The elevator pitch

A vertically integrated, JAX-native research software stack for geophysical modeling, inference and data assimilation: from structured linear algebra primitives, through composable operator graphs, to indexed multi-tier data products, with ML pluggable at every level.

Great JAX numerics and great xarray pipelines exist today; what is missing is the operator-graph abstraction that connects them, with content-addressed data and models throughout.

3The shape of the problem

Five gaps motivate everything below. The first four are old; the fifth has only become visible recently.

1 · Scattered numerics

The same linear algebra, kernel methods and filtering routines are reimplemented in every project. There are no shared primitives.

2 · ML tooling ignores classical numerics

Keras and PyTorch layers assume supervised learning on tensors. They do not express linear operators, PDE solvers, state estimation or uncertainty propagation.

3 · Monolithic domain software

Ocean models, retrieval algorithms and DA systems are dense, tightly coupled and hard to recombine.

4 · No connective tissue

Even with good numerics and good ML, there is no Keras-like pipeline to compose them for real geophysical work.

5 · No indexed model-state tier

Observations are catalogable (STAC, GeoParquet). Forecasts, analyses and reanalyses are scattered files with bespoke conventions: the L3–L5 half of the lifecycle has nothing like the catalogs that organise L0–L2.

The stack addresses all five.

4Three organising axes

Three axes structure the framework (Table 1). They are orthogonal: every operation sits somewhere on all three.

Table 1:The three axes and what each one orders.

AxisOrdersFigure
1 · Modeling cyclethe research loop: model, observe, compare, refineFigure 1
2 · Data lifecycledata maturity, from raw telemetry (L0) to derived products (L5)Figure 2, Figure 3
3 · Six-step cyclethe journey from a simple model to amortized inferenceFigure 6

4.1The modeling cycle (axis 1)

The real-world system feeds two tracks. Modeling runs conceptual model, equations and code, predictions. Observing runs sensor, measurements, observations. Both meet at compare. A random misfit goes to estimate (state by data assimilation, parameters by calibration), whose inner loop updates the predictions; a systematic misfit goes to refine the model, whose outer loop returns to the conceptual model and, dashed, to the sensor.

Figure 1:The modeling cycle runs on two parallel tracks and two loops.

The modeling track turns a conceptual model of the real-world system into equations and code that make predictions. The observing track measures the same system with sensors and turns measurements into observations. The tracks meet where predictions are compared with observations, and the misfit decides which loop runs. A random misfit, one the error model explains, runs the fast inner loop: state estimation (data assimilation) updates the state xx, parameter estimation (calibration) updates the parameters θ\theta, and the same model runs again. A systematic misfit runs the slow outer loop: the model’s structure changes, or the observing system does (more or better data, better processing).

4.2The L0–L5 data lifecycle (axis 2)

Six maturity levels, L0 to L5, across the top, each step named on its arrow. Below, telemetry becomes one stream of radiances, which retrieval fans out into ocean, atmosphere, land and ice lanes that run through analysis and reanalysis to dashed derived products.

Figure 2:The data lifecycle, science axis.

One stream of radiances fans out into ocean, atmosphere, land and ice at retrieval, and ends in derived products such as extremes (L5, dashed: examples still to be defined). Figure 3 maps the same levels to the tools that handle them.

Table 2:The six levels of the data lifecycle.

LevelNameWhat it holdsCarrier
L0Unstructured observationsraw instrument output: telemetry, counts, packetsGeoTensor
L1Structured observationscalibrated, georeferenced radiancesGeoTensor
L2Retrieved observationsper-pixel geophysical variables, with gaps where retrieval failsGeoTensor
L3Analysisa regular, gap-filled grid: the “world right now”xarray
L4Reanalysis and forecastdynamical model output: data-constrained, fused or projectedxarray
L5Derived productsextremes, indicators and impacts computed from L4xarray

Table 2 names the six levels.

The science axis runs left to right. Until retrieval it is one stream: the same L1 radiances feed every geoscience. Retrieval is where it fans out into ocean, atmosphere, land and ice variables, and each domain then carries its own analysis and reanalysis or forecast. L5 holds the products derived from those states, such as extremes (marine heatwaves, droughts, storms) or impact indicators. Which products belong in L5 is still open.

The same six levels across the top. Below, a compute row names what performs the step into each level (radtrax at L2, GeoML at L3, GeoModels with data assimilation at L4, xtremax at L5), then rows for compose (pipekit, all levels), transform (geoops, xrtoolz), patch (geopatcher), carry (GeoTensor, xarray), read (georeader, xrreader), index (geocatalog, geocatalog.states) and store (geocloud, all levels), split by a dashed line where the carrier switches from GeoTensor to xarray.

Figure 3:The data lifecycle, engineering axis.

The top row names what performs each step; the rows below map every level to the tool that composes, transforms, patches, carries, reads, indexes and stores it, switching carrier from GeoTensor to xarray between L2 and L3.

The engineering axis runs top to bottom, from computing a level down to storing it, and maps every level to the GeoStack tool that handles it.

The rows below split where the carrier changes:

ML enters at every transition.

4.3Two streams converge

The lifecycle is not one chain. A top-down satellite stream and a bottom-up in-situ stream converge between L2 and L3 (Figure 4).

The satellite chain L0 to L5 on the left; in-situ networks for ocean, atmosphere, land and ice at the bottom right. L2 and in-situ meet at a matchup node that feeds L3 and yields matchup pairs for validation and bias correction; in-situ also enters L4 directly through assimilation.

Figure 4:Satellite and in-situ observations converge through matchup.

In-situ networks in every domain join the satellite chain through spatiotemporal matchup between L2 and L3, and enter L4 directly through assimilation. They have no L0 or L1 equivalent. Matchup also yields the pairs that validate and bias-correct the satellite stream: the misfits that drive the loops of Figure 1.

The matchup pattern is canonical and deserves its own operator family (stage 2 runs it; where it lives is an open question).

4.4Three layers of transformation

The cycle and the lifecycle are joined by a third lens: what we do to the data on its way into a model (Figure 5).

Scientific data (observations as GeoTensors, states as xarray) becomes ML-ready tensors with provenance through harmonisation and patching (geopatcher), which geonnax encoders compress into latent embeddings; a dashed decode arrow maps latents back to fields. Each layer lists what it is, its array shape and the packages that own it.

Figure 5:Scientific data, ML-ready data, embeddings, and who owns each.

The first step is data engineering (GeoStack), the second representation learning (GeoML). The middle layer is the handoff that makes training reproducible: cached, CF-compliant tensors with content-addressed splits. Decoders run the chain backwards, which is what emulators and generative models do.

4.5The six-step cycle (axis 3)

The research-to-production journey (Figure 6). Every domain library follows this cycle implicitly; the framework makes it explicit and composable.

Five steps in a row, from simple model to amortized inference, each with its defining equation. Step 1 simulates (θ, y) pairs that train steps 3 and 5; step 2 validates steps 3 to 5. A shared improve step under steps 2 to 5 lists each step's ground truth, the step before it. An axis below shows the cost per query falling from hours to milliseconds.

Figure 6:From a simple model to amortized inference, with one shared improve step.

Step 2, the classical method, is the oracle that validates steps 3–5; it is the inner loop of Figure 1. Step 1 also simulates the (θ, y) pairs that train the emulator and the amortized map. Step 6 is shared: any of steps 2–5 can be improved, using the previous step as ground truth, and changing the structure is the outer loop. Moving right, the cost of each query falls from hours to milliseconds because the training cost is paid once, up front.

In symbols: let θ be the quantity we want (an emission rate, a model state), f the physical forward model, H the observation operator, y the observations and ε their error with covariance R. Step 1 is the generative story:

y=H(f(θ))+ε,   ε∼N(0,R)y = H\left(f(\theta)\right) + \varepsilon, ~~~ \varepsilon \sim \mathcal{N}(0, R)

Step 2 inverts it with classical, model-based inference. Bayes’ rule with a prior p(θ) defines the target:

p(θ∣y)∝p(y∣θ) p(θ)p(\theta \mid y) \propto p(y \mid \theta) ~ p(\theta)

Computing (2) is exact only in tractable cases, such as a linear-Gaussian model solved with the Kalman filter or a GP. The usual step-2 methods (a finite-ensemble EnKF, 3D- or 4D-Var) approximate it, slowly. Step 2 is the oracle because it is the most trusted reference available, not because it is exact.

Step 3 trains an emulator f^ϕ\hat f_\phi with weights φ on step-1 simulations by (3), and step 4 reruns step 2 with f^ϕ\hat f_\phi in place of f:

ϕ⋆=arg⁡min⁡ϕEθ∼p(θ)∥f(θ)−f^ϕ(θ)∥2\phi^\star = \arg\min_\phi \mathbb{E}_{\theta \sim p(\theta)} \left\| f(\theta) - \hat f_\phi(\theta) \right\|^2

Step 5 skips the inversion: it trains a conditional density qψq_\psi with weights ψ that maps observations straight to an approximate posterior, on pairs (θ, y) drawn from the generative model (1), by (4):

ψ⋆=arg⁡min⁡ψE(θ,y)∼p(θ,y)[−log⁡qψ(θ∣y)],   qψ⋆(θ∣y)≈p(θ∣y)\psi^\star = \arg\min_\psi \mathbb{E}_{(\theta, y) \sim p(\theta, y)} \left[ -\log q_\psi(\theta \mid y) \right], ~~~ q_{\psi^\star}(\theta \mid y) \approx p(\theta \mid y)

Table 3 says where each step lives in the stack. Two structural commitments fall out of the cycle:

Step 2 is the oracle

Classical methods (filterax, vardax, pyrox) are not legacy code waiting to be replaced by neural emulators. They become reference generators that validate steps 3–5. The classical path stays alive, promoted to oracle.

Benchmarks formalise understanding

Writing a benchmark contract (carriers, references, metrics, splits, baselines, expected failure modes) is formalising what you understand about the problem. Every transition between steps is a benchmarkable contract.

Table 3:Where each step lives in the stack.

StepLives in
1 · Simple modelGeoModels (somax, plumax, radtrax)
2 · Model-based inferencepipekit-cycle.DACycle with classical analysis steps
3 · Emulatorpipekit-train.SimulationDataset, registered in pipekit-experiment.ModelRegistry
4 · Emulator-based inferencepipekit-cycle.DACycle with NeuralForward
5 · Amortized inferencepipekit-train with gauss_flows or pyrox
6 · Improvepipekit-evaluate.Benchmark reruns, compared row by row, with lineage in pipekit-experiment

4.6How the axes relate

The axes form a cube of operations. At any point you are somewhere on the modeling cycle (research stage), somewhere on the lifecycle (data maturity) and somewhere on the six-step cycle (production maturity). Figure 7 crosses the lifecycle transitions with the steps.

A grid with the five lifecycle steps as rows, from read and calibrate (detector counts to radiance) to derive (heatwave return levels), each with a running example, and steps 1 to 5 as columns headed by their equations. Each cell names a method and what it does on the example; the step-2 column is highlighted as the oracle. Arrows above show step 3 scored against step 1, and steps 4 and 5 against step 2.

Figure 7:Every lifecycle step crossed with every six-step stage is a concrete method.

Each row follows one running example from a simple model to an amortized map: radiance from detector counts, methane from shortwave infrared, sea level from altimeter tracks, the ocean state from sea-level maps, and heatwave return levels.

Every cell is a benchmarkable artifact, scored against the earlier step that computes the same thing: an emulator against the simulator it replaces, and emulator-based and amortized inference against the step-2 oracle’s posterior. pipekit-evaluate’s benchmark ladder ships this rule (Report 17). The whole lifecycle is a nested set of six-step cycles, each gated by benchmarks. That is what makes the framework improvable in practice: improving a cell re-runs its downstream column, lineage is content-addressed, and nothing is opaque.

5The big picture stack

Two zoom levels: the layers first, then the packages inside GeoStack.

5.1Four layers

Four owned layers between external foundations at the bottom and external orchestration and serving at the top. GeoStack (data) and GeoNumerics (numerics) sit side by side at the bottom, GeoML (learning and inference) on both, GeoModels (physics) on top; imports point down. georeader is marked third-party and read-only.

Figure 8:The stack in four layers.

Each layer has one job: GeoStack moves data in and out, GeoNumerics does structured numerics, GeoML learns and infers, GeoModels simulates physics. Imports point down, and GeoStack and GeoNumerics never import each other (Figure 10 and tests/test_dependency_graph.py check it). Dashed rows are external; georeader belongs to GeoStack but is spaceml-org’s, read here and never written.

5.2GeoStack, zoomed in

Most current work happens in the GeoStack layer (Figure 9).

The GeoStack layer. The pipekit core (pipekit, pipekit-array, pipekit-jax) at the bottom; above it two carrier columns, GeoTensor for observations and xarray for states, in rows by job: read (georeader, geoproducts; xrreader), index (geocatalog; the proposed geocatalog.states), transform (geoops; xrtoolz, xrcore, xrgrad, xreinx, xrsklearn), patch (geopatcher, either carrier) and store (geocloud, either carrier). The carrier-neutral ML loops (pipekit-cycle, -train, -experiment, -evaluate) on top.

Figure 9:One pipekit core, two carriers, the same jobs on both.

Row by row, the GeoTensor side and the xarray side do the same job on a different carrier; patching and storage serve both. The ML loops on top never see which carrier they run on.

5.3The dependency graph

Figure 10 shows which repositories import which.

The 18 repositories in four layer bands: GeoModels on top, GeoML below, GeoNumerics and GeoStack side by side at the bottom. Edges point down from each repository to those it depends on or imports; dashed edges are optional extras; the geotoolz and pipekit cycle is drawn in red. georeader is marked third-party.

Figure 10:How the repositories import each other.

Drawn from the generated graph on the dependency-graph page, which tests/test_dependency_graph.py keeps current; that page has the package-level detail and the table of known violations. Solid edges are dependencies or imports, dashed edges optional extras, and red edges a cycle.

5.4Ownership rules

A package imports only from layers below it, so there are no circular dependencies; geotoolz and xrtoolz re-export the common framework symbols, so user code usually imports a single package. The three stacks of the decision each have their rules: GeoStack’s (pipekit and its packages, geocatalog, geopatcher, geotoolz and xrtoolz) are Report 16’s, and GeoModels’ (somax, plumax, radtrax, on top of the GeoNumerics layer) are on the package boundaries page. GeoML’s rules are stated only here: the JAX libraries touch pipekit only through an optional adapter module, never in their core.

  1. gaussx owns structured linear algebra and Gaussian primitives. It imports no GeoStack package.

  2. geonnax owns basis functions, neural layers and coordinate transforms. somax and the other models compose them and never reimplement them.

  3. kernellib owns covariance kernels and tapers. It imports no inference library.

  4. optax_bayes owns the Bayesian learning rule as optax transforms. It imports no model library.

  5. spectraldiffx and finitevolx own discretisations: spectral and finite-volume operators, no physics.

  6. pyrox owns GP and Bayesian NN building blocks for NumPyro. Its pipekit surface (KrigingOperator) lives only in pyrox.adapters.pipekit.

  7. filterax owns ensemble Kalman methods and vardax owns 3D/4D-Var. Each exposes an AnalysisStep only through adapters/pipekit.py.

  8. gauss_flows owns Gaussianization and normalizing flows; xtremax owns extreme-value and point-process models. Neither imports pipekit outside an adapter.

6Walkthrough: methane attribution

The canonical example: a complete pipeline from satellite scenes to source emission estimates, with ML at several stages.

Five science steps down the left, grouped into stages 1 to 4: catalog the scenes, retrieve methane (L1 to L2), match with in-situ, grid and gap-fill (L2 to L3), assimilate hourly (L3 to L4). Each arrow names the data that flows, with its level, carrier and shape; each step names its packages and where ML enters. EMIT scenes, TCCON columns and CAMS and aircraft references come in from the right. Along the bottom, stage 5's engineering steps: index outputs, evaluate, register and serve; evaluation feeds the misfit back to assimilation.

Figure 11:Eight steps from cataloguing scenes to serving estimates.

Stages 1–4 are the science, stage 5 the engineering around it. Each arrow names what flows to the next step; the dashed arrow is the inner loop of Figure 1, the misfit that updates the state and parameters.

The data: EMIT imaging-spectrometer scenes (L1 radiances) over an area of interest, TCCON column measurements as in-situ truth, and CAMS reanalysis and aircraft campaigns as evaluation references. The dimensions used below: B spectral bands, H × W pixels per scene, M observations, N grid cells, Ne=40N_e = 40 ensemble members and T = 24 hourly steps.

The eight steps of Figure 11 group into five stages. Each stage states the problem it solves before the code does; stages 3 and 4 have closed-form special cases that double as test oracles (Table 6).

6.1Stage 1 · Retrieve methane (steps 1–2: geocatalog → georeader → geotoolz)

TL;DR. Find every EMIT scene over the area, mask clouds, and turn each pixel’s spectrum into a methane enhancement.

Problem. For pixel i, xi∈RBx_i \in \mathbb{R}^B is its radiance spectrum, μ the scene-background mean, Σ∈RB×B\Sigma \in \mathbb{R}^{B \times B} the background covariance and t the methane target signature (unit absorption times μ). The matched filter’s enhancement estimate α^i\hat\alpha_i is the least-squares amplitude of t in the whitened residual:

α^i=(xi−μ)⊤Σ−1tt⊤Σ−1t\hat\alpha_i = \frac{(x_i - \mu)^\top \Sigma^{-1} t}{t^\top \Sigma^{-1} t}

The matched filter (5) is the fast classical enhancement estimate; its derivation is in radtrax · 1. The full-physics retrieval, optimal estimation on a radiative-transfer model, is step 2 of the six-step cycle for the L1→L2 cell (Figure 7). A learned retrieval takes the same slot in the pipeline, and optimal estimation stays as its oracle.

6.2Stage 2 · Match with in-situ (step 3: geocatalog matchup)

TL;DR. Pair each TCCON measurement with the one satellite retrieval nearest to it in space and time; measurements with nothing nearby are dropped.

Problem. In-situ observation j sits at location sjs_j and time tjt_j; satellite retrieval k at sks_k, tkt_k; d is great-circle distance. The candidates for j are the retrievals inside both tolerances, (6):

Wj={k:∣tj−tk∣≤3 h  and  d(sj,sk)≤25 km}W_j = \left\{ k : |t_j - t_k| \le 3~\mathrm{h} ~~\text{and}~~ d(s_j, s_k) \le 25~\mathrm{km} \right\}

If WjW_j is empty, j has no matchup. Otherwise j keeps the single candidate with the smallest scaled distance (7), so no TCCON record is counted twice:

k⋆(j)=arg⁡min⁡k∈Wj(d(sj,sk)25 km)2+(∣tj−tk∣3 h)2k^\star(j) = \arg\min_{k \in W_j} \left(\frac{d(s_j, s_k)}{25~\mathrm{km}}\right)^2 + \left(\frac{|t_j - t_k|}{3~\mathrm{h}}\right)^2

Ties go to the earlier retrieval, then the smaller catalog index, so the selection is deterministic.

The pairs serve two jobs, so TCCON is split once, by station, before anything is fitted. Pairs from the training sites are the (label, feature) data a learned retrieval trains on; pairs from the held-out sites validate stage 1 and are the only TCCON references used in stage 5. No site appears in both, so held-out scores never include a label the model saw. How much of the pairs’ disagreement is representativeness error is an open question.

6.3Stage 3 · Grid and gap-fill (step 4: pyrox through pipekit)

TL;DR. Turn scattered retrievals into a regular map with an uncertainty for every grid cell.

Problem. y∈RMy \in \mathbb{R}^M are the L2 retrievals, σ² their noise variance and k a Matérn-5/2 kernel with a 50 km length scale. KffK_{ff} is k between the M observation locations, K∗fK_{*f} between the N grid cells and the observations, and K∗∗K_{**} between grid cells. The GP posterior mean m and variance v on the grid are:

m=K∗f(Kff+σ2IM)−1ym = K_{*f} \left(K_{ff} + \sigma^2 I_M\right)^{-1} y
v=diag⁡(K∗∗−K∗f(Kff+σ2IM)−1Kf∗)v = \operatorname{diag}\left(K_{**} - K_{*f} \left(K_{ff} + \sigma^2 I_M\right)^{-1} K_{f*}\right)

Kriging is the oracle for the L2→L3 cell; DINEOF and neural gap-fillers are judged against it. Stage 4 assimilates this map: m is its observation and diag(v)\mathrm{diag}(v) its observation-error covariance.

6.4Stage 4 · Assimilate hourly (step 5: pipekit-cycle → plumax → filterax)

TL;DR. Step a transport model forward each hour and pull it towards the observations.

Problem. xt∈RNx_t \in \mathbb{R}^N is the methane state at hour t, 𝓜 one plumax transport step of 3,600 s, H the column observation operator, R the observation-error covariance, here R=diag(v)R = \mathrm{diag}(v) from the stage-3 kriging, and PbP^b the background covariance estimated from the NeN_e members. Each cycle forecasts every member i=1,…,Nei = 1, \dots, N_e by (10):

xtb,(i)=M(xt−1a,(i))x_t^{b,(i)} = \mathcal{M}\left(x_{t-1}^{a,(i)}\right)

then updates each member against its own perturbed copy of the observations, ε(i)∼N(0,R)\varepsilon^{(i)} \sim \mathcal{N}(0, R) drawn independently per member (the stochastic EnKF):

K=PbH⊤(HPbH⊤+R)−1,   xta,(i)=xtb,(i)+K(yt+ε(i)−Hxtb,(i))K = P^b H^\top \left(H P^b H^\top + R\right)^{-1}, ~~~ x_t^{a,(i)} = x_t^{b,(i)} + K \left(y_t + \varepsilon^{(i)} - H x_t^{b,(i)}\right)

The perturbations matter: updating every member with the same unperturbed yty_t shrinks the anomalies by (I−KH)(I - KH) and leaves the analysis ensemble too narrow, which would bias the CRPS in stage 5. PbP^b is inflated by 1.05 and localised with a Gaspari–Cohn taper; localisation trades exactness for removing spurious long-range correlations, and the perturbations make the estimate random. They are drawn from one key seeded from the run’s config, so a rerun reproduces them (gate G1). A square-root (ETKF) variant replaces the perturbations with a deterministic anomaly transform. With a linear 𝓜 and H, no inflation and no localisation, its analysis mean equals the Kalman update applied to the ensemble’s own sample mean and covariance, which is gate D2.

6.5Stage 5 · Index, evaluate, register (steps 6–8: geocatalog.states → pipekit-evaluate → pipekit-experiment)

TL;DR. Store every analysis, score it against three independent references, and attach the score to the model that made it.

Problem. For a forecast distribution F, a reference value y, and independent draws X, X′ from F, the continuous ranked probability score is below; lower is better, and with one member it reduces to absolute error. Mass closure checks that total methane is conserved to within 1 %.

CRPS(F,y)=EF∣X−y∣−12EF∣X−X′∣\mathrm{CRPS}(F, y) = \mathbb{E}_F |X - y| - \frac{1}{2} \mathbb{E}_F |X - X'|

The same pipeline, written as YAML, becomes the regulatory artifact (Report 9, use case 9). The same operators run in a notebook (case 1), an ETL job (case 2), a FastAPI endpoint (case 5) or a Fused UDF (the Fused integration design).

7Per-layer briefs

A compact summary per package, by layer (Figure 8). Where a report or a GeoModels page covers a package, the brief is a link to it.

External
GeoStack
GeoNumerics
GeoML
GeoModels

The JAX ecosystem: JAX, Equinox, lineax, optax, diffrax, NumPyro and matfree.

The xarray ecosystem: DataArray, Dataset and DataTree, plus rioxarray, xvec, cf-xarray, regionmask, xskillscore, dask and intake.

8Key code patterns

Each pattern has runnable-looking code on the overview examples page.

9Open questions

None of these block work; all deserve thought before committing to a decision.

Raised by the design review
  • What InferenceArtifact still needs for a cold rerun. The shipped InferenceArtifact already saves and loads a pipeline’s YAML, its pinned model hashes, the lockfile and metadata. A rerun that rebuilds the pipeline and pulls its frozen inputs is proposed. Open: whether that rerun resolves inputs from the pinned catalog URIs and SHA-256 digests alone, without cloud credentials, and how a Benchmark (not yet serialisable) is frozen next to it.

Resolved elsewhere
  • Serialising a Graph. Emitting a built pipeline as YAML that loads back is proposal A of Report 9; until it lands, gate A1 covers only YAML-safe operators.

  • A per-step observation error. An obs_err_source is proposed in Report 10, workstream B.

  • Two evaluation APIs. Report 15, workstream D builds the contract and the tracks on the shipped Benchmark cube (from_contract, several references per run), with no separate MultiTrackEvaluation.

  • Where xrreader stops and georeader starts. A mission reader lives with its output carrier (Report 16, workstream E).

  • Which stack somax and spectraldiffx belong to. Rule 5 of the package boundaries puts somax in GeoModels and spectraldiffx and finitevolx in GeoNumerics. methanex is still unplaced.

Sequencing
  • When does pipekit-jax’s operator layer land? pipekit-jax 0.0.2 has shipped, as a model wrapper and a diffrax forward model (Report 5); the operator layer is still proposed. The six-step cycle gives it a concrete driver: differentiable step-3 emulators make step 4 (gradient-based inverse problems) tractable at scale. Open until such a project appears.

  • Which in-situ catalog backend comes first? The scoping proposal behind Report 6 leaned to Argo (via argopy), but ICOADS, SOCAT and the World Ocean Database matter for other domains. Realistically one per quarter, driven by projects.

Design
  • The Slice Protocol refactor. GeoSlice is the only slice type today. Adding ProfileSlice and TrajectorySlice is the right structural move, but it is two weeks of work rippling across geocatalog, geotoolz, xrtoolz and pipekit-train. Better done before more code commits to GeoSlice.

  • Where matchup lives. geocatalog.queries.matchup() is the framework function, with carrier-specific helpers in xrtoolz and geotoolz. Does matchup need its own catalog type? Probably; not yet scoped.

  • Backend choice in pipekit-train. Equinox + Optax and the Bayesian backends ship; Lightning and Keras 3 are scaffolds (Report 11). The intended split: Lightning for PyTorch models, Equinox + Optax for JAX-traceable pipelines, Keras 3 for multi-backend. Worth stating per use case.

Operational
  • Monorepo or polyrepo? Monorepo for the framework and infrastructure (uv workspaces); the JAX-native algorithm and domain libraries stay in their own repositories and ship adapter modules.

  • One documentation site or many? One site for the monorepo packages, separate sites for the algorithm libraries, and one research journal that cuts across everything.

  • CI per package or per monorepo? Per-package CI inside the monorepo, plus one weekly integration matrix pinning every package to HEAD. Releases by git tag (geocatalog-v0.2.0).

Scientific
  • Matchup UQ. MatchupPair.representativeness_uncertainty is easy to declare and hard to compute; v0.1 ships a placeholder.

  • Cascading uncertainty. A five-stage emission chain accumulates uncertainty across stages. Predicting that honestly is open research.

  • Probabilistic event detection. “A plume happened” is binary; “a 73 % chance a plume happened” is much harder, and current event metrics assume the binary case.

Sustainability
  • Solo maintenance. About twenty packages is a lot even with monorepo discipline. Scope discipline is the operating constraint: each phase delivers standalone value, and no layer needs the full stack to exist.

  • Community adoption. If oceanbench adopts pipekit-evaluate’s benchmark contracts, the framework is validated; if not, it stays personal infrastructure. When to start reaching out is open.

10A plan

Each phase produces packages that are useful on their own (Table 4).

Table 4:Phases at a glance.

PhaseScopeStatusTime
1GeoNumerics: gaussx (v0.0.6), spectraldiffx, finitevolxgaussx done; the rest draftsongoing
2GeoML: geonnax, kernellib, optax_bayes, pyrox, filterax, vardax, gauss_flows, xtremaxin progresseach at its own pace
3GeoModels: somax, plumax, radtraxsome doneproject-driven
4Pipeline frameworkcurrent focus~3–4 months
5Evaluation, benchmarks and cycle disciplinenext~2 months
6Deferred items, each with a triggerwhen projects demand—
7Polish and communityopen-ended—

10.1Phases 4 and 5, month by month

Month 1
Month 2
Month 3
Month 4
Month 5
Month 6
  • pipekit 0.0.2: framework core, fourteen groups and state primitives (shipped; Report 2)

  • pipekit-array 0.0.2: Array API operators (shipped, phase A of four; Report 3)

  • the pipekit-ecosystem monorepo with uv workspaces

Gate: A1 config round-trip (Table 6).

10.2Deferred until a project needs it

Table 5 lists what waits for a project, and what pulls it in.

Table 5:Phase 6: what is deferred, and what triggers it.

ItemTrigger
pipekit-jax’s operator layer (operators as PyTrees; 0.0.2 ships only a model wrapper and a diffrax forward model, Report 5)the first project needing differentiable retrievals at scale or JIT-compiled inference
Deployment adapters (pipekit.deploy.fused, .litserve, .modal)the first deployment beyond hand-shimming
Slice Protocol refactor (ProfileSlice, TrajectorySlice)the first project needing first-class in-situ catalogs (likely the BGC benchmark)
More in-situ backends (Argo, ICOADS, SOCAT, …)one per quarter, project-driven

Phase 7 covers the documentation site, a research journal, the first external benchmark adoption (OceanBench, WeatherBench-Ocean?) and capstone tutorials per domain.

10.3Gates

A month is done when its gates pass, not when its packages exist (Figure 12). Every pass condition is a number or a closed form, and several reuse the walkthrough’s closed-form stages as oracles (Table 6).

Phases 4 and 5 as six monthly boxes from top to bottom, each with its status (shipped, in progress or planned) and followed by a diamond naming the gates, with their pass conditions, that close it. Phases 1 to 3, one per lower layer (GeoNumerics, GeoML, GeoModels), run alongside and feed the DA adapters in month 3 and the models trained in month 4; phases 6 and 7 follow the last gate.

Figure 12:The framework ships first; every month closes on its gates.

Phases 1–3, one per lower layer, run alongside and meet the chain twice: the DA adapters land in month 3 and the GeoML models are trained in month 4. Phases 6 and 7 wait for projects to pull them.

Table 6:Gates: what must pass before each month closes.

GateWorkstreamPasses when
A1 Config round-tripAFor every shipped operator not marked forbid_in_yaml, loads(dumps(op)) has the same config hash and returns the same output on a fixed input; once the topology serialiser lands, the same holds for a Graph (as Report 9’s A1)
B1 Backend parityBThe InMemory and DuckDB geocatalog backends return the identical slice set for the same bbox and time query
B2 State rowsBA cycle’s ensemble history, written as one file per run and member and indexed with build_state_catalog, loads back with load_states with no run dropped and equal to the history to 0 (Report 13, gates A2 and B1)
C1 Patch identityCAn identity model through patch → stitch with Hann-window overlap-add reproduces the input to 1e-6 in float64, away from scene edges
D1 Kriging oracleDKrigingOperator’s mean and variance match (8) and (9) to 1e-6 in float64
D2 Kalman oracleDWith linear 𝓜 and H, no inflation and no localisation, the square-root ensemble analysis mean matches the Kalman update computed from the same ensemble’s sample mean and covariance, to 1e-6 in float64
E1 Registry round-tripELoading a stored operator returns one with the same content hash and bitwise-equal outputs
F1 Metric identitiesFCRPS (12) with one member equals absolute error exactly; RMSE of a field against itself is 0
G1 End to endallStages 1–5 of the walkthrough run from one YAML file. A cold rerun of the frozen inference artifact reproduces the analysis history’s hash, and rerunning the frozen benchmark (its task, estimators, scorers and pinned TCCON references) on that history reproduces the report’s hash

10.4Risks

11Where to read next

11.1How the pages are written

Each page checks a design, a scoping report or a source note, against the code pinned in references/, and the code takes precedence. A Differs box marks each place where the code, or a check the page runs, disagrees with the design. Gaps that matter become workstreams with proposals; the rest go to open questions or upcoming work. Every number on a page comes from a notebook that asserts it. Pseudocode follows one convention: a shapes header names the dimensions, the equation sits in a comment above the line that computes it, shapes go on the right, and every call into the stack carries a status marker (markers).

11.2The pages

Start with the capability index and the overview examples; then read by question.

Across the stack

pipekit

geotoolz and xrtoolz

Benchmarking

GeoModels

12In summary

GeoStack is one operator algebra from structured linear algebra to indexed multi-tier data products, organised by three orthogonal axes and split into the three stacks of the decision. Classical methods stay as oracles, benchmarks are content-addressed contracts, and differentiability lives only in the JAX packages. The plan is a default sequence that projects will reshape: pick a page from Where to read next and come back with questions, since no part of the stack requires the rest.

13Glossary

carrier
The data structure an operator acts on: NumPy or JAX arrays, a GeoTensor, or an xarray object. pipekit operators are carrier-agnostic.
GeoTensor
georeader’s raster carrier: an array with its geotransform and CRS. The carrier for L0–L2.
GeoSlice
geocatalog’s wire format for one spatiotemporal slice of an observation catalog.
state profile
geocatalog’s reserved columns for model states (run time, ensemble member, producer hash and parent state) and the loader and queries in geocatalog.states that use them. It replaces the planned statecatalog package and its StateSlice.
matchup
Pairing observations from different sources that coincide in space and time; the operation where the satellite and in-situ streams converge.
oracle
The classical method (step 2) used as the trusted reference to validate emulators and amortized predictors.