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.

Sister libraries on pipekit

Report 3 · Arrays, GeoTensor and xarray, checked against what geotoolz, xrtoolz and pipekit-array ship

Abstract

pipekit is carrier-agnostic, so the real work happens in libraries that pick a carrier. This report proposed three: pipekit-array for any Array API array, geotoolz for GeoTensor, and xrtoolz for xarray. All three now exist. They are not layered the way the proposal drew them. geotoolz and xrtoolz are large and depend on pipekit directly. pipekit-array has shipped its first phase, four names, and nothing uses it yet. Each part below records what was proposed, what shipped, and the gap. The gaps become open questions and follow-ups at the end.

Keywords:pipekit-arraygeotoolzxrtoolzArray APIGeoTensorxarray

The layers above pipekit

The proposal drew a strict stack: geotoolz and xrtoolz on top, delegating their array maths down to pipekit-array, which sits on pipekit core. What shipped has the same three carriers and different wiring (Figure 1). geotoolz sits directly on pipekit core. xrtoolz sits on a small package of its own, xrcore, which subclasses pipekit’s Operator. pipekit-array also sits on pipekit core, but neither domain library imports it. A fourth sibling, pipekit-jax, has shipped too. Table 1 sets each proposed layer against what it became. The stack-wide layering and its ownership rules are in Report 16.

Dependency diagram. The geotoolz family depends directly on pipekit core. The xrtoolz family depends on xrcore, which depends on pipekit core. pipekit-array and pipekit-jax depend on pipekit core; pipekit-jax also needs pipekit-cycle. Dashed arrows from geotoolz and xrtoolz to pipekit-array mark planned links that do not exist.

Figure 1:The shipped dependencies. The dashed links to pipekit-array were planned and are not wired.

Table 1:The proposed layers, and what each became.

LayerProposedShippedSource lines
Arraypipekit-array: 12 Array API operators, about 400 linespipekit-array 0.0.2: phase A of four, 4 names281
Geogeotoolz: GeoTensor operators, with framework code reduced to a shimgeotoolz 0.10.2 plus four packages: geocatalog, geocloud, geopatcher, geoproducts35,044 (+ 49,425 in the four)
xarrayxr_toolz: xarray operators, with framework code reduced to a shimxrtoolz 0.0.5 plus five packages: xrcore, xreinx, xrgrad, xrreader, xrsklearn31,729 (+ 16,588 in the five)
JAXpipekit-jax: deferred until a project needs itpipekit-jax 0.0.2: model wrapping, not tracing (Section ??)493

The proposal’s sizes were estimates. The shipped line counts include docstrings and exclude tests.

Part 1 · pipekit-array: arrays through the Array API

What the Array API gives us

The Python Array API standard specifies a common array interface. A conforming array implements __array_namespace__(), which returns a module-like namespace with mean, sum, where, reshape and the rest. One function then works on any conforming backend:

1
2
3
4
5
from pipekit_array import array_namespace

def channel_mean(x):                          # x: (..., C)
    xp = array_namespace(x)                   # numpy, jax.numpy, cupy, …
    return xp.mean(x, axis=-1, keepdims=True)  # x̄ = (1/C) ∑ᶜ xᶜ  →  (..., 1)

The duck-array idea, formalised.

The proposal’s conformance table was out of date in three places. It is corrected below from the NumPy and array-api-compat documentation (Table 2):

Table 2:Array API support by backend, corrected.

LibraryHow it conformsThe proposal said
NumPy ≥ 2.0the main numpy namespace (standard 2022.12, working toward 2023.12)“via numpy.array_api”: that experimental module was removed in NumPy 2.0
JAXjax.numpy itself, from JAX 0.4.32 (jax.experimental.array_api before)“since JAX 0.4.20”
CuPythrough array-api-compatfull
PyTorchthrough array-api-compat; tensors do not expose __array_namespace__ themselves“via torch.array_api”: there is no such module
Daskthrough array-api-compat; lazypartial
sparsenative supportpartial

pipekit-array’s dispatch handles that wrinkle correctly. array_namespace tries array-api-compat first, which is shipped as the optional [compat] extra and needed for PyTorch, then falls back to x.__array_namespace__(). Either way it fails with the same message when no input conforms or the inputs mix backends.

What ships

pipekit-array is planned in four phases (its docs/design/boundaries.md, §4). Phase A has shipped:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
from pipekit import Signature
from pipekit_array import ConcatenateAlong, MeanScalar, StackAlong, array_namespace

x = np.arange(24.0).reshape(2, 3, 4)          # (band, y, x)
array_namespace(x).__name__                   # → 'numpy'
MeanScalar()(x)                               # → 11.5            full mean, scalar
MeanScalar(axis=(-2, -1))(x)                  # → [ 5.5 17.5]     per band, (band,)
StackAlong(axis=0)([x[0], x[1]]).shape        # → (2, 3, 4)
ConcatenateAlong(axis=0)([x, x]).shape        # → (4, 3, 4)

pipe = StackAlong(axis=0) | MeanScalar(axis=(-2, -1))
pipe([x[0], x[1]])                            # → [ 5.5 17.5]

sig = Signature(dims=(("band", 2), ("y", 3), ("x", 4)), dtype="float64")
MeanScalar(axis=(-2, -1)).compute_output_signature(sig)
# → Signature((band=2) float64)               the reduced dims are dropped

pipekit-array 0.0.2, from a real run on NumPy.

MeanScalar is the first operator on pipekit’s Signature that does real shape inference: it drops the reduced dimensions, which is the payoff Report 2 said core leaves to the sister libraries. xrtoolz’s operators (in geo, interpolate, metrics and xreinx) infer shapes too, but on xrcore’s separate Signature (Section ??). Table 3 lists the whole planned catalogue by phase; only phase A ships.

Table 3:The proposed pipekit-array catalogue, by phase, at 0.0.2.

OperatorPurposePhaseStatus
array_namespaceArray API dispatchAships
MeanScalarreduce with xp.mean; the proposal’s field= argument shipped as axis= and keepdims=Aships
StackAlong(axis)stack a list of arrays along a new axisAships
ConcatenateAlong(axis)concatenate along an existing axisAships
Subsample(stride)stride-decimate the last two axesBplanned
ApplyToBands(inner, axis=0)split, apply, stack over an axisBplanned
BatchedMap(op, batch_size)split along axis 0, apply, concatenateBplanned
Histogram(bins)capture distributions per tap siteBplanned
Diff(reference, atol)raise on drift from a stored referenceCplanned
AssertValueRange(min, max, on_fail)raise or warn when out of rangeCplanned
AssertNoNaN()raise on any NaNCplanned
AssertValidFraction(min_valid)raise when too few values are validCplanned
ModelOp(model, method, batch_size)framework-agnostic inferenceDplanned

The trade-offs

These held up as written, and they are why pipekit-array is worth its own package.

What works:

What doesn’t:

Dependency surface

Shipped as proposed: dependencies = ["pipekit"], with one extra per backend (numpy, jax, torch, cupy, dask), plus a compat extra the proposal did not have (array-api-compat, needed for PyTorch). With no backend installed, array_namespace raises a TypeError naming the extras to install, rather than an ImportError.

Migrating the NumPy operators

The proposal moved the NumPy-flavoured operators out of geotoolz and xrtoolz into pipekit-array, then re-exported them with carrier-specific defaults:

1
2
3
4
5
# geotoolz/qc.py (proposed)
from pipekit_array.qc import AssertValueRange as _AssertValueRange

class AssertValueRange(_AssertValueRange):
    """Read GeoTensor's fill_value as the default bounds when none are given."""

The proposed pattern: one implementation, re-exported with GeoTensor defaults.

None of this has happened, because the pipekit-array side (phase C) does not exist yet. Neither does the geotoolz side: geotoolz has no value-range, NaN or valid-fraction assertions at all. Its assert_* functions in _src/contract.py are test-contract helpers, not operators.

Part 2 · geotoolz: GeoTensor operators

Scope

The proposal made geotoolz a thinner library focused on remote sensing over georeader.GeoTensor, with its old framework code (geotoolz.core) reduced to a shim that re-exports pipekit. The shipped geotoolz has no core at all: it depends on pipekit (pipekit>=0.0.2) and its top-level __init__ re-exports 16 pipekit names directly (Table 4). gz.Operator is pipekit.Operator.

Table 4:The pipekit names geotoolz re-exports at 0.10.2, all the same objects.

Re-exportedNot re-exported
Operator, Carrier, Sequential, Graph, Input, Node, Identity, Const, Lambda, Sink, Tap, Snapshot, ShapeTrace, Branch, Switch, FanoutCache, Memoize, Try, Coalesce, Retry, Profile, Histogram, Quarantine, the Assert* classes, the parallel maps, pipe, compose, juxt, complement

geotoolz defines no class with the same name as a pipekit public name. It deliberately keeps augment.Compose, io.SinkOperator and io.SourceOperator out of its top level, so they cannot be confused with pipekit’s compose and Sink. Its operators accept either a GeoTensor or a plain np.ndarray and return the same kind, so the carrier is wider than the proposal assumed.

Layout

Table 5 maps geotoolz’s subpackages onto the proposed layout.

Table 5:geotoolz’s subpackages at 0.10.2, against the proposed layout.

SubpackageWhat it holdsIn the proposal
ioReadBounds, WriteCOG (the [cloud] extra)as io
geomBowtieCorrection, GeostationaryParallaxCorrect, Tile, Stitch, SlidingWindow, co-registrationas geom
radiometryRadianceToReflectance, BTFromRadiance, DOS1, SimpleAtmosphericCorrectionas radiometry
indicesNDVI, NDWI, EVI, …, with generic band-name resolution (DEFAULT_BAND_KEYS)as indices
spectralspectral operatorsas spectral
matched_filterMatchedFilter, ColumnEnhancement and 31 moreas matched_filter
qaQA-bit decoding and cloud masks: DecodeBitmask, MaskClouds, S2SCL, MODISStateQA, mask_from_qa_bits, mask_from_sclthe proposal’s cloud; its qa meant assertions
maskApplyMask, PolygonMask, BBoxMask, CountryMask, LandMaskas mask
vizTrueColor, FalseColor, StretchToUint8, ApplyColormap, ApplyDiscreteColormapas viz
compositingMedianComposite, MaxNDVIComposite, CloudFreeComposite, BAPComposite, MinCloud, …as compositing
augmentRandomFlip, RandomRotate90, …as augment
normalizestateful scalers with fit (StandardScaler, RobustScaler, MinMaxScaler), Normalize, PerBandStatsas normalize
restoredespeckling (Lee, RefinedLee, Frost) and gap-filling (IDW, Laplacian, biharmonic, nearest)as restore
plumePlumeMask, PlumeFootprint, IMEEstimate, CrossSectionalFlux, WindAdvectionCone, SBMP, …as plume
learnModelOp, SklearnOpthe proposal’s pipekit_array.inference
einx, feature, measure, segmenteinx notation over carriers; edges, blobs, Hough, HOG; region properties and contours; segmentationnew
patch_opsbridge to geopatcher: GridSampler, ApplyToChips, MergePatches, StratifiedSample, BalancedSampler, TriangularWindowthe proposal’s patch

Three proposed subpackages moved into packages of their own, and two did not ship:

What geotoolz owns, and what has not shipped

Table 6 lists the named symbols that did not ship under their proposed name.

Table 6:The proposal’s named geotoolz symbols that did not ship under that name.

ProposedAt 0.10.2
TOAToBOAnot shipped; the nearest is SimpleAtmosphericCorrection
DarkObjectSubtractionshipped as DOS1
ACE, LinearUnmixingnot shipped
MaskFromQABits, MaskFromSCLfunctions mask_from_qa_bits, mask_from_scl; operators DecodeBitmask, S2SCL
ColormapApplyColormap, ApplyDiscreteColormap
AssertCRSEquals, AssertResolutionWithin, AssertSchemanot shipped
ExtractPatches, StitchPatchesnot shipped; geopatcher’s GridSampler / MergePatches, and geom.Tile / Stitch
GeoCatalog, CatalogPipelineGeoCatalog is a Protocol in geocatalog; CatalogPipeline not shipped
super-resolution (in restore)not shipped
point-source attribution (in plume)no dedicated operator
CH4 / NH3 / N2O matched filteringmatched_filter is gas-agnostic: ColumnEnhancement(gas="CH4", sensor="EMIT") records the gas as metadata

The ownership argument itself held: geotoolz keeps everything that needs GeoTensor’s crs, transform and bounds, plus the sensor knowledge and the remote-sensing domain operators. Two of the proposal’s reasons for keeping things there did not turn into code: geo-specific QC (AssertCRSEquals, AssertResolutionWithin) and per-sensor calibration tables in geotoolz.

Sensor readers

The proposal put eight sensor modules in geotoolz.readers, each bundling a reader, sensor-specific operators and zero-argument presets. There is no geotoolz.readers. Readers and presets live in geoproducts, downstream of geotoolz (its [operators] extra pulls geotoolz in), and two of the eight have shipped (Table 7):

Table 7:The eight planned sensor modules at geotoolz 0.10.2.

PlannedStatusWhere
GOES-R ABIships: Reader, L2Reader, Calibration, zero-arg presetsgeoproducts.goes
Himawari AHIships: the same shapegeoproducts.himawari
MODIS L1B + L2no reader; qa.MODISStateQA and geom.BowtieCorrection existgeotoolz
VIIRS SDR + EDRno reader—
MSG SEVIRIno reader—
MTG-FCIno reader—
TROPOMI L2no reader in geotoolz; xrreader has open_tropomi_ch4_l2xrtoolz-reader
Sentinel-3 OLCI / SLSTRno reader—

The GOES and Himawari presets are NDVI, TrueColor, NaturalColor, DayCloudPhase, FireTemperature, MaskClouds, ParallaxCorrect, a synthetic or hybrid green band, and a Recipe. geoproducts also has a toy sensor for tests and a Carbon Mapper client. georeader keeps its own readers for Sentinel-2 SAFE, EMIT, EnMAP, PRISMA, PROBA-V, SPOT-VGT and Earth Engine.

ModelOp and SklearnOp

Both live in geotoolz.learn, not in pipekit-array.

Both set forbid_in_yaml = True, because both hold a model object.

Part 3 · xrtoolz: xarray operators

Scope

The proposal made xr_toolz a focused xarray library, with its framework code reduced to a shim. It shipped as xrtoolz, and the framework layer became a small package of its own, xrcore (xrtoolz-core, 418 lines). xrcore’s Operator subclasses pipekit’s and adds one dispatch mode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pipekit as pk
import xarray as xr
import xrtoolz

class Double(xrtoolz.Operator):
    def __init__(self, var: str = "u") -> None:
        self.var = var

    def _apply(self, ds: xr.Dataset) -> xr.Dataset:
        return (2 * ds[self.var]).rename(f"{self.var}2").to_dataset()

xrtoolz.Operator is pk.Operator            # → False: a subclass,
issubclass(xrtoolz.Operator, pk.Operator)  # → True   so it composes with pipekit
xrtoolz.Sequential is pk.Sequential        # → True   re-exported unchanged

ds = xr.Dataset({"u": ("x", [1.0, 2.0]), "v": ("x", [3.0, 4.0])})
tree = xr.DataTree.from_dict({"/a": ds, "/b": ds * 10})
Double("u")(tree)["b"].to_dataset()["u2"].values   # → [20. 40.]  every node

xrcore’s Operator, from a real run.

A Node argument still builds a graph and plain data still runs _apply. A DataTree argument runs _apply_tree, which by default maps _apply over every node with xr.map_over_datasets. That is the xr.DataTree support the proposal listed, built in at the base class.

xrtoolz re-exports Graph, Input, Node, Sequential and Tap from pipekit, all the same objects. It defines no Tap, Sequential, Graph, Identity, Fanout or Lambda of its own.

Layout

The proposal had sixteen subpackages. xrtoolz folds six of them into one geo subpackage, and moves data access out to xrreader (Table 8; the named symbols are in Table 9):

Table 8:xrtoolz’s subpackages at 0.0.5, against the proposed layout.

SubpackageWhat it holdsThe proposal’s modules
geovalidation, CRS, subsetting, masks, climatology, regions, wavelets, extremes, vector/rastervalidation, crs, subset, masks, detrend
interpolateregridding, gap-filling, smoothing, resampling, binninginterpolate
transformsFourier, wavelet and DCT transforms; encoders; decompositionstransforms
metricsRMSE, PSDScore, R2Score, …metrics
ocnOkuboWeiss, RelativeVorticity, KineticEnergy, …kinematics, ocn
atmatmospheric quantities, including atm.gas.ch4atm
budgetsbudget calculationsnew
inferenceModelOp, SklearnModelOp, JaxModelOpinference
vizplot_* functions; matplotlib *Panel operators in viz.validationviz
combinatorsAugment, ApplyToEachcore
rs, iceempty: a docstring onlyrs
einx, calcdeprecated aliases for xreinx and xrgrad—
(moved to xrreader)CMEMSSource, CDSSource, AemetSource, CATALOGdata

Table 9:The proposal’s named xrtoolz symbols, at 0.0.5.

ProposedAt 0.0.5
ValidateCoords, RenameCoords, SubsetBBox, SubsetTimeship, in geo
AddLandMask, AddOceanMask, AddCountryMaskship, in geo, backed by regionmask
CalculateClimatology, RemoveClimatologyship, in geo
RMSE, PSDScoreship, in metrics
OkuboWeiss, RelativeVorticity, KineticEnergyship, in ocn
SortCoordsnot shipped
AssignCRS, Reproject, GetCRSfunctions assign_crs, reproject, get_crs, plus a ReprojectMatch operator (rioxarray)
SubsetWherethe function subset_where
ComputeAnomalythe function calculate_anomaly
Regrid, GapFill, Smooth, ResampleRegridConservative, RegridLike, Coarsen, Refine; the FillNaN* family; GaussianSmooth, MovingAverage, LowpassFilter; ResampleTime
one-hot and cyclical encoderscyclical ships (CyclicalEncode, EncodeTimeCyclical); one-hot does not
PSD and wavelet decompositionsPowerSpectrum, CWT; the functions pca, eof, ica, nmf, kmeans
NashSutcliffenot shipped; the nearest is R2Score

The xarray combinators

The proposal kept two combinators in xrtoolz because they merge with xarray, and both shipped:

1
2
3
4
5
6
7
from xrtoolz import ApplyToEach, Augment

sorted(Augment(Double("u"))(ds).data_vars)    # → ['u', 'u2', 'v']

each = ApplyToEach(Double(), kwarg="var", values=["u", "v"])
sorted(each(ds).data_vars)                    # → ['u', 'u2', 'v', 'v2']
# Double(var="u") runs, merges; then Double(var="v") runs, merges

Augment and ApplyToEach at xrtoolz 0.0.5, from a real run.

The proposal asked for a docstring note that this merge differs from pipekit’s framework combinators. The note exists on pipekit’s side (combine.py, which still calls the library xr_toolz), not in xrtoolz. pipekit’s Tap replaced xrtoolz’s, as planned.

Inference

xrtoolz.inference ships ModelOp, SklearnModelOp and JaxModelOp, all xrcore operators. The proposal’s sample_dim argument became feature_dim: every dimension except feature_dim is treated as a sample dimension, and output_name and output_dim name the result. JaxModelOp imports JAX lazily and has a jit flag. xrsklearn separately ships SklearnOp and XarrayEstimator.

The five sibling packages

None of these were in the proposal.

The array maths was meant to delegate to pipekit-array (the reduction inside RMSE, for example). No xrtoolz package imports pipekit-array. RMSE reduces through NumPy kernels in metrics/_src/_pixel_kernels.py.

Part 4 · Where each symbol lives

The proposal flagged this as the most common confusion. Table 10 shows where each name lives at the pinned revisions.

Table 10:Where each symbol lives at pipekit 0.0.2, geotoolz 0.10.2 and xrtoolz 0.0.5.

SymbolDefined inRe-exported fromThe proposal said
Operatorpipekit._base.operatorpipekit, geotoolz; subclassed by xrcore.Operator (re-exported by xrtoolz)re-exported from both cores
Sequential, Graph, Input, Nodepipekit._basepipekit, geotoolz, xrtoolzas shipped
Fanout, Identity, Const, Lambda, Sinkpipekitpipekit, geotoolzboth libraries
Tappipekit.observegeotoolz, xrtoolzas shipped
Branch, Switchpipekit.controlgeotoolzboth libraries
Try, Coalesce, Retrypipekit.controlpipekit onlyboth libraries
Snapshot, ShapeTracepipekit.observegeotoolzboth libraries
Profile, Cache, Quarantine, AssertShape, AssertDType, the parallel maps, pipe, compose, juxt, complementpipekitpipekit onlyboth libraries
Signaturepipekit.signature; a separate one in xrcorepipekit; xrtoolz (xrcore’s)one, in pipekit.signature
Histogrampipekit.observepipekit onlypipekit_array.observe
BatchedMappipekit.parallelpipekit onlypipekit_array.parallel
ApplyToBands, Subsample, Diff, AssertValueRange, AssertNoNaN, AssertValidFractionnot shipped—pipekit_array
ModelOpgeotoolz.learn; a separate one in xrtoolz.inferenceeach librarypipekit_array.inference
Augment, ApplyToEachxrtoolz.combinatorsxrtoolzas shipped
GeoCataloggeocatalog (a Protocol)geocataloggeotoolz.catalog
CatalogPipelinenot shipped—geotoolz.catalog
patch extraction and stitchinggeopatchergeotoolz.patch_opsgeotoolz.patch
sensor readers and presetsgeoproducts.goes, geoproducts.himawarigeoproductsgeotoolz.readers
reprojection, CRSxrtoolz.geo (functions, plus ReprojectMatch)xrtoolz.geoxr_toolz.crs
climatology, anomaliesxrtoolz.geoxrtoolz.geoxr_toolz.detrend

Part 5 · Other libraries that could fit on top

The proposal asked, for each candidate, whether it deserves a library of its own.

Part 6 · How to decide on a new sister library

The proposal’s three questions, applied to the shipped code:

  1. Is there a clearly typed carrier? If not, the operators belong in an existing library or in core.

  2. Are there at least eight operators that would consume it natively? If not, contribute them to the closest existing library.

  3. Does the carrier already have a wide-enough standard? The Array API does for arrays, and xarray.Dataset is its own de facto standard. If the carrier has no coherent standard, you would be inventing one alongside the operators.

Table 11 applies them to each candidate.

Table 11:The criteria, with the shipped evidence.

LibraryTyped carrier≥ 8 native operatorsA standardVerdict
pipekit-arrayany Array API array3 operators and the dispatcher; 12 plannedthe Array APIpasses on plan; not yet on code
geotoolzGeoTensor (and plain np.ndarray)hundreds: 272 top-level namesGeoTensor in this stackpasses
xrtoolzxr.DataArray, Dataset, DataTreehundreds, across a dozen subpackagesxarraypasses
JAX-traceableJAX PyTreesnone yetJAXdeferred until a project drives it
numba, dask, pandasno new carrier, orchestration, no shared standard——do not pass

Two points in the shipped code bear on question 2. The domain libraries grew packages of their own (geopatcher, geocatalog, xrreader, xrgrad), each with a clear job and its own dependencies, and that turned out to be how they stay focused. pipekit-array exists ahead of its operators. That is fine for a library planned in phases, but until phase C lands, the code does not justify the package.

Summary

Table 12 sets each layer’s proposed status against the pinned revisions.

Table 12:The ecosystem as proposed, and at the pinned revisions.

LayerLibraryWhat it ownsProposed statusAt the pinned revisions
Corepipekitthe framework: operators, composition, observers, control, QC, parallelismnew0.0.2, 52 names (Report 2)
Arraypipekit-arrayArray API operatorsnew0.0.2, phase A: 4 names; no dependents
Geogeotoolz (+ 4)GeoTensor operators, patches, catalogs, cloud I/O, sensor productsexisting, refactored0.10.2; the framework moved to pipekit; readers in geoproducts (2 of 8 sensors)
xarrayxrtoolz (+ 5)xarray operators, readers, calculus, einx, sklearnexisting, refactored0.0.5; the framework moved to pipekit, plus xrcore
JAXpipekit-jaxthe proposal: JAX-traceable operatorsdeferred0.0.2, but model wrapping and diffrax, not tracing
Out of scopepipekit-dask, pipekit-numba, pipekit-pandas—not plannednot built

The proposal’s takeaway was two new packages (pipekit, pipekit-array) and a refactor of two existing ones. The refactor happened: neither domain library carries framework code any more. The ecosystem grew beyond the proposal: pipekit’s workspace has seven packages, geotoolz’s five and xrtoolz’s six. The piece that has not landed is the middle layer the proposal was built around, a shared array library the domain libraries delegate to.

Open questions

Decisions the shipped code raises:

  1. Is pipekit-array still the middle layer? Both domain libraries grew their own array code instead. Either pipekit-array’s phases B to D land and geotoolz and xrtoolz migrate onto them, or pipekit-array becomes an optional toolkit beside them and the layering in this report is retired.

  2. What do pipekit-array’s BatchedMap and Histogram become? Core already ships both names. Options: give the array versions distinct names (BatchedArrayMap, say), make them subclasses that add axis-0 splitting, or drop them and extend the core classes.

  3. One Signature or two? Resolved: one, pipekit’s (Report 16 §A).

  4. One JaxModelOp name or two? Resolved: one model-operator family (Report 16 §B).

  5. Where do the numeric QC asserts go? AssertValueRange, AssertNoNaN and AssertValidFraction were promised to pipekit-array, and pipekit’s qc.py still says they live there. They exist nowhere. Should they wait for phase C, or land in geotoolz and xrtoolz first?

  6. Does geotoolz own geo QC? AssertCRSEquals, AssertResolutionWithin and AssertSchema were part of the case for geotoolz and did not ship.

  7. Which sensors come next in geoproducts? Resolved for TROPOMI: the reader stays on the xarray side (Report 16 §E).

Upcoming work

Table 13 lists the follow-ups whose shape is clear.

Table 13:Follow-ups from checking the proposal against the pinned revisions.

Follow-upWhereWhy
Make require_operator, callable_name and nested_config public, or stop importing thempipekit _base/operator.py; geotoolz, geopatchergeotoolz depends on private API
Index each library’s public subpackages in the capability indexgeostack scripts/capabilities.py (UPSTREAM)only top-level modules are indexed: the xrtoolz subpackages (geo, metrics, ocn, …) are missing, geocloud has no rows, and the clash check misses JaxModelOp and Reduce
Ship pipekit-array phases B to D, resolving the BatchedMap / Histogram names firstpipekit-array9 of the 13 planned names are missing
Fix pipekit’s qc.py docstring that says the numeric asserts live in pipekit-arraypipekit qc.pythey don’t exist yet
Rename xr_toolz to xrtoolz in pipekit’s combine.py docstring, and add the merge-semantics note to xrtoolz’s Augment / ApplyToEachpipekit, xrtoolzstale name; a note planned on the xrtoolz side
Build CatalogPipeline on ProcessMap / ThreadMapgeocatalogReports 2 and 3 both assume it; use cases 2 and 12 need it
Fill or remove the empty xrtoolz.rs and xrtoolz.ice subpackagesxrtoolzthey ship as docstrings only
Add SortCoords, a one-hot encoder and a Nash–Sutcliffe score, or drop them from the planxrtoolznamed in the proposal, not shipped

References

Citations

To be added: the Python Array API standard, array-api-compat, NumPy’s Array API compatibility notes, xarray, and georeader, as entries in references.bib. Until then, the Array API standard, array-api-compat and NumPy’s compatibility notes are linked inline.