pipekit-array¶
Array-API operators on top of pipekit-ops.
One operator runs on NumPy, JAX, CuPy, PyTorch or dask because it dispatches on array_namespace(x).
The design doc has the architecture, the ADRs and the v0.1 operator catalogue.
What ships today: namespace dispatch, MeanScalar, StackAlong and ConcatenateAlong, the named carrier DimArray, all the dim-level functions with their operator twins (ApplyAlong included), padded batches and BatchAlong, sample tables and Estimator, the einx bridge, KernelOp, the xarray adapter and the test kit.
What is still to build from the v0.1 catalogue: the numeric QC family (AssertClose, AssertValueRange, AssertNoNaN, AssertValidFraction), the array-aware Histogram and the trained-model wrapper ModelOp.
array_namespace¶
pipekit_array._namespace
¶
Array API namespace dispatch shim.
The single point of backend dispatch for every pipekit-array
operator. array_namespace(*xs) returns the Array API
namespace (numpy, jax.numpy, torch via array-api-compat, …)
shared by all input arrays.
Tries the optional array-api-compat library first — without it,
PyTorch tensors don't expose __array_namespace__ natively and torch
backends won't dispatch. Falls back to native
x.__array_namespace__() lookup if the compat shim isn't installed.
See docs/design/architecture.md §4 and ADR A1 for the rationale.
array_namespace(*xs)
¶
Return the Array API namespace shared by all input arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*xs
|
Any
|
One or more array-like objects. All must share the same Array API backend. |
()
|
Returns:
| Type | Description |
|---|---|
Any
|
The module-like namespace object (e.g. |
Any
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If no input implements the Array API, or if the
inputs span more than one backend. The error message is
consistent whether or not |
Source code in packages/pipekit-array/src/pipekit_array/_namespace.py
Reductions¶
pipekit_array.reductions
¶
Reductions — MeanScalar.
Wraps xp.mean as a pipekit Operator. See
docs/design/api/operators.md for the spec.
MeanScalar
¶
Bases: Operator
Reduce an array to a scalar (or per-axis means) via xp.mean.
Defaults to a full reduction (axis=None) yielding a scalar of
rank 0. Output dtype matches the input's floating dtype per the
Array API mean contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | tuple[int, ...] | None
|
Axis or tuple of axes to reduce over. |
None
|
keepdims
|
bool
|
If True, retains the reduced axes with size 1. |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
From |
Example::
out = MeanScalar()(image) # scalar
out = MeanScalar(axis=(-2, -1))(image) # per-band mean
out = MeanScalar(axis=0, keepdims=True)(image) # shape (1, H, W)
Source code in packages/pipekit-array/src/pipekit_array/reductions.py
Combinators¶
pipekit_array.combinators
¶
Combinators — StackAlong, ConcatenateAlong.
Thin wrappers over xp.stack and xp.concat (the Array API
standardised names) so the operations compose into a pipeline. See
docs/design/api/operators.md for the spec.
The planned ApplyToBands and array-shaped BatchedMap shipped as
pipekit_array.ApplyAlong and pipekit_array.BatchAlong (ADRs A15, A19).
ConcatenateAlong
¶
Bases: Operator
Concatenate a list-of-arrays along an existing axis via xp.concat.
xp.concat is the Array API standardised name; numpy 2.0+
exposes it as concat alongside the legacy concatenate alias.
All input arrays must share shape (except along axis), dtype,
and backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Existing axis to concatenate along, for raw arrays. Default 0. |
None
|
dim
|
str | None
|
For named carriers instead: the dim to join along
( |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
From |
Example::
out = ConcatenateAlong(axis=0)([arr_a, arr_b]) # shape (2*N, H, W)
Source code in packages/pipekit-array/src/pipekit_array/combinators.py
StackAlong
¶
Bases: Operator
Stack a list-of-arrays along a new axis via xp.stack.
All input arrays must share the same shape, dtype, and backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Position of the new axis in the output, for raw arrays. Default 0. |
None
|
dim
|
str | None
|
For named carriers instead: the new dim's name
( |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
From |
Example::
out = StackAlong(axis=0)([arr_a, arr_b, arr_c]) # shape (3, H, W)
Source code in packages/pipekit-array/src/pipekit_array/combinators.py
Named arrays¶
pipekit_array.dimarray
¶
DimArray — a raw array with named dims, and its reference adapter.
What a geo carrier becomes when a kernel consumes one of its spatial dims
(per-column statistics consume row and keep col), and the plain
named carrier for tests and for libraries with none of their own
(PK-P7, decided 23 Oct 2026).
- It holds a raw array (NumPy, JAX or dask), one name per axis, and the static labels of any dim (band names, level values).
- It is a JAX pytree whose only leaf is the data: dims and labels are
static, so a new set of values never recompiles. Under a bare
jax.vmap(in_axes=0) the mapped dim is the first one: it is dropped inside and comes back as"batch"(vmapped_dims); to map a named dim, usepipekit_array.map_over. - It does not hold a georeference, coordinates beyond labels, or
attrs, and does not do alignment, arithmetic or selection by value;
DimArray.to_xarraycrosses to xarray when an analysis needs those.
DimArrayAdapter is the reference pipekit_ops.NamedArrayAdapter: the test
kit (pipekit_array.testing) compares every other adapter against it.
DimArray
dataclass
¶
A raw array with one name per axis and static labels on some dims.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
A NumPy, JAX or dask array. |
required |
dims
|
tuple[str, ...]
|
One unique name per axis of |
required |
labels
|
Mapping[str, tuple[Any, ...]]
|
Static labels per dim, e.g. |
dict()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
The dims do not match the data's rank, repeat a name, or a label tuple's length differs from its dim's size. |
Example
import numpy as np mu = DimArray(np.zeros((3, 2)), ("col", "band"), {"band": ["B11", "B12"]}) mu.dims, mu.shape, mu.labels["band"] (('col', 'band'), (3, 2), ('B11', 'B12'))
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
dtype
property
¶
The data's dtype.
shape
property
¶
The data's shape.
__array__(dtype=None, copy=None)
¶
The data as a NumPy array, so np.asarray(d) works (dims are dropped).
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
to_xarray()
¶
An xr.DataArray with the same data and dims; labels become coordinates.
Raises:
| Type | Description |
|---|---|
ImportError
|
xarray is not installed ( |
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
DimArrayAdapter
¶
The reference pipekit_ops.NamedArrayAdapter, for DimArray.
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
check_compatible(*xs)
¶
The arrays back, if every shared dim has one size and one set of labels.
Raises:
| Type | Description |
|---|---|
ValueError
|
A dim has two sizes, or two different label sets. |
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
data(x)
¶
dims(x)
¶
isel(x, indexers)
¶
Select by position; integers drop their dim, labels follow the data.
Raises:
| Type | Description |
|---|---|
KeyError
|
An indexer names a dim |
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
labels(x, dim)
¶
relabel(x, labels)
¶
x with the labels of the named dims set; None removes them.
The optional pipekit_ops.LabelledAdapter method (ADR A22).
Raises:
| Type | Description |
|---|---|
ValueError
|
A dim is not in |
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
rewrap(x, data, dims)
¶
A DimArray around new data; a kept dim keeps its labels if its size did.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
signature(x)
¶
Dims with their sizes, and the dtype's name.
is_mask(index)
¶
A boolean array, or a non-empty list / tuple of bools.
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
vmapped_dims(dims, shape)
¶
Static dims matched to leaves jax.vmap added or removed an axis from.
vmap maps leaves but not static dims: inside, the data has lost
its leading axis; outside, the results gained one. With the default
in_axes=0 / out_axes=0 that axis is the first, so drop the
first dims, or prepend "batch" ("batch_1", … if taken). A
shape that already matches, or no shape (a tracing placeholder),
leaves dims alone. Map over any other axis with map_over.
Source code in packages/pipekit-array/src/pipekit_array/dimarray.py
Dim-level functions¶
pipekit_array.dims
¶
Dim-level functions: selection, reduction, stacking and mapping by dim name.
Each is written once, over the carrier's registered
pipekit_ops.NamedArrayAdapter, and behaves the same on every registered
carrier: a DimArray, an xr.DataArray ([xarray]), a geoarray
carrier. The rule for joining this list (GA-Q3): a function never reads
coordinate values or geometry. resample, groupby and rolling
stay xarray's; reprojection and sampling at points stay in geoarray.
isel,sel— by position, by exact label; an integer or a single label drops its dim.transpose— reorder dims by name.reduce,argreduce— over named dims;reduce(..., where=valid)leaves masked entries out (a padded batch, nodata).take_along— gather along a dim with an index carrier (argreducethentake_alongis a max-NDVI composite).map_over— a function on each item along a dim.stack,concat— a new dim, or along an existing one.stack_items— items that may each carry their own geometry (patches, per-frame grids) along a new leading dim;stackwhere that is the same thing.combine— an element-wise function of carriers with the same dims.
Fast paths. An adapter may provide a method of the same name (the
xarray adapter's reduce stays lazy on dask; geoarray's map_over
is one jax.vmap over data and transform rows). It is called with the
same arguments instead of the generic path; the test kit
(pipekit_array.testing) proves the two agree.
The Operator forms live in pipekit_array.dim_operators.
adapter_of(x, what)
¶
The adapter of a named carrier; a TypeError for an unnamed value.
Source code in packages/pipekit-array/src/pipekit_array/dims.py
argreduce(x, fn, dim)
¶
The index of a reduction along one dim ("argmax", "argmin").
Example
import numpy as np from pipekit_array import DimArray ndvi = DimArray(np.random.default_rng(0).random((12, 4)), ("time", "x")) argreduce(ndvi, "argmax", "time").dims ('x',)
Source code in packages/pipekit-array/src/pipekit_array/dims.py
check_index_labels(adapter, x, index_adapter, index, dims)
¶
take_along's check: on each shared dim the index's labels are x's.
Raises:
| Type | Description |
|---|---|
ValueError
|
Labels differ in value or order (the index is positional). |
Source code in packages/pipekit-array/src/pipekit_array/dims.py
combine(fn, *xs)
¶
fn of the data of carriers with the same dims; never aligns.
Inputs are put in the first one's dim order by name before fn sees
them, and the result has those dims.
Example
import numpy as np from pipekit_array import DimArray nir = DimArray(np.full((2, 3), 0.4), ("y", "x")) red = DimArray(np.full((2, 3), 0.1), ("y", "x")) nd = combine(lambda a, b: (a - b) / (a + b), nir, red) nd.dims, bool(np.allclose(nd.data, 0.6)) (('y', 'x'), True)
Source code in packages/pipekit-array/src/pipekit_array/dims.py
concat(xs, dim)
¶
Join carriers along an existing dim; the other dims must agree.
The generic path drops dim's labels (the joined dim has a new size);
an adapter's concat fast path may join them.
Source code in packages/pipekit-array/src/pipekit_array/dims.py
isel(x, /, **indexers)
¶
Select by position on named dims; an integer drops its dim.
x is positional-only, so a dim may be called x.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
**indexers
|
Any
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.zeros((12, 2, 4)), ("time", "band", "x")) isel(cube, time=0).dims ('band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dims.py
map_over(x, fn, dim)
¶
fn on each item along dim; the results stacked along dim, first.
Each item is isel(x, dim=i); fn returns a named carrier, and every
result must have the same dims.
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((3, 2, 4)), ("time", "band", "x")) map_over(cube, lambda item: reduce(item, "sum", "band"), "time").dims ('time', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dims.py
namespace_of(data)
¶
The array namespace of raw data, dask included (which has none of its own).
Source code in packages/pipekit-array/src/pipekit_array/dims.py
reduce(x, fn, dim, *, where=None)
¶
Reduce over one or more named dims.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
fn
|
Reduction
|
An array-namespace name ( |
required |
dim
|
str | Sequence[str]
|
A dim name, or several. |
required |
where
|
Any | None
|
Optional bool mask of the entries to keep, passed to |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A plain |
TypeError
|
|
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((12, 2, 4)), ("time", "band", "x")) reduce(cube, "mean", "time").dims ('band', 'x') batch = DimArray(np.array([[1.0], [3.0], [9.0]]), ("sample", "x")) reduce(batch, "mean", "sample", where=np.array([True, True, False])).data array([2.])
Source code in packages/pipekit-array/src/pipekit_array/dims.py
require_one_backend(datas, what)
¶
Raise BackendError when raw arrays sit on more than one backend.
Inputs never move between backends silently: a NumPy array next to a JAX one would otherwise be converted depending on input order.
Source code in packages/pipekit-array/src/pipekit_array/dims.py
sel(x, /, **labels)
¶
Select by exact label on named dims: no ranges, no nearest.
x is positional-only, so a dim may be called x.
A single label drops its dim; a list of labels keeps it.
Raises:
| Type | Description |
|---|---|
KeyError
|
A dim has no labels, or a label is not among them. |
Example
import numpy as np from pipekit_array import DimArray scene = DimArray(np.zeros((2, 4)), ("band", "x"), {"band": ("B4", "B8")}) sel(scene, band="B8").dims ('x',)
Source code in packages/pipekit-array/src/pipekit_array/dims.py
stack(xs, dim)
¶
A new leading dim dim from carriers with the same dims; never aligns.
Source code in packages/pipekit-array/src/pipekit_array/dims.py
stack_items(xs, dim)
¶
Items along a new leading dim dim, each keeping its own geometry.
The inverse of the split map_over makes: where stack requires one
shared grid (geoarray's stack never aligns two), stack_items lets
each item carry its own per-item leaves (a patch's transform row, a
frame's grid) and makes dim a batch dim. A carrier family with no
per-item geometry (DimArray, xr.DataArray) has nothing to keep
apart, so this is stack; geoarray's adapter provides the
stack_items fast path.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Example
import numpy as np from pipekit_array import DimArray items = [DimArray(np.ones((2, 3)), ("y", "x")) for _ in range(4)] stack_items(items, "sample").dims ('sample', 'y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dims.py
take_along(x, index, dim)
¶
Gather one entry of dim per position, with an index carrier.
index has x's dims without dim (an argreduce result, in any
dim order); the result has those dims too.
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.arange(24.0).reshape(3, 2, 4), ("time", "band", "x")) best = DimArray(np.array([0, 2, 1, 0]), ("x",)) take_along(cube, best, "time").dims ('band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dims.py
transpose(x, order)
¶
Reorder dims by name; order names every dim once.
Source code in packages/pipekit-array/src/pipekit_array/dims.py
pipekit_array.dim_shapes
¶
Dim-level functions that change the dims: rename, add, drop, broadcast, merge, split.
Phase 1 of the dim-functions plan (docs/design/api/dim_functions.md,
ADRs A11 to A14 and A22). Like the functions of pipekit_array.dims, each
runs on any carrier with a registered pipekit_ops.NamedArrayAdapter,
works on positions only (labels follow the data, their values are never
read), and calls an adapter method of the same name, when there is one,
instead of the generic path.
rename— new dim names; data, order and labels unchanged.expand_dims,squeeze— add or drop length-1 dims.broadcast_like— add another carrier's missing dims, in front.merge_dims,split_dims—y xintopixeland back; labels become tuples and are split back.
Labels on new or renamed dims (A22). rewrap keeps labels by dim
name, so it cannot label a dim whose name or labels are new. rename,
broadcast_like, merge_dims and split_dims compute those labels and
set them with the adapter's optional relabel
(pipekit_ops.LabelledAdapter). An adapter without it runs them when no
labels would be set, and raises TypeError when some would: labels are
never dropped silently.
The Operator forms live in pipekit_array.dim_operators.
broadcast_like(x, other, /, *, exclude=())
¶
Add other's missing dims to x, in front, in other's order.
y_{a…, d…} = x_{d…}, for every index a of the added dims.
Shared dims must agree exactly in size and labels (xarray outer-joins
instead). x's own dims keep their order, so the result's order is
xarray's only when the shared dims are in other's order. The added
dims carry other's labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
other
|
Any
|
A carrier of the same family; only its dims, sizes and labels are read. |
required |
exclude
|
Sequence[str]
|
Dims of |
()
|
Raises:
| Type | Description |
|---|---|
TypeError
|
The adapter has no |
ValueError
|
A shared dim differs in size or labels. |
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.zeros((12, 2, 4)), ("time", "band", "x")) median = DimArray(np.ones((2, 4)), ("band", "x")) broadcast_like(median, cube).shape (12, 2, 4)
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
expand_dims(x, dim, /, *, axis=0)
¶
Insert length-1 dims, consecutively, at position axis of the result.
y_{…, 0, …} = x_{…}: the new dims have size 1 and no labels.
A negative axis counts from the end, so axis=-1 appends.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
dim
|
str | Sequence[str]
|
A new dim name, or several. |
required |
axis
|
int
|
Where the first new dim goes in the result. |
0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A new name is already a dim, a name repeats, or
|
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> scene = DimArray(np.zeros((2, 4)), ("band", "x"))
>>> expand_dims(scene, "member").dims
('member', 'band', 'x')
>>> expand_dims(scene, ("member", "run"), axis=-1).shape
(2, 4, 1, 1)
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
merge_dims(x, /, **dims)
¶
Merge the listed dims into one, placed last, in C order (xarray's stack).
y_{…, p} = x_{…, i, j}, with p = i·X + j for pixel=("y", "x").
The merged dim's labels are tuples of the source labels, with
positions for an unlabelled source; it has none when no source has
labels. split_dims undoes it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
**dims
|
Sequence[str]
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A source is not a dim of |
ValueError
|
A merge lists no source, a source is merged twice, or the result repeats a dim name. |
TypeError
|
A merge's sources are one string, or the adapter has no
|
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> bands = {"band": ("B4", "B8")}
>>> cube = DimArray(np.zeros((2, 3, 4)), ("band", "y", "x"), bands)
>>> pixels = merge_dims(cube, pixel=("y", "x"))
>>> pixels.dims, pixels.shape
(('band', 'pixel'), (2, 12))
>>> merge_dims(cube, pb=("band", "y")).labels["pb"][:2]
(('B4', 0), ('B4', 1))
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
merged_labels(sources, sizes)
¶
The labels of a merged dim: tuples of the source labels, in C order.
An unlabelled source contributes its positions; when no source has labels, neither has the merged dim.
Example
merged_labels([("B1", "B2"), None], [2, 3])[:4] (('B1', 0), ('B1', 1), ('B1', 2), ('B2', 0)) merged_labels([None, None], [2, 3]) is None True
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
rename(x, /, **names)
¶
New dim names; data, order and labels unchanged.
y = x, with each dim d called names[d].
x is positional-only, so a dim may be called x.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
**names
|
str
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
An old name is not a dim of |
ValueError
|
The new names repeat a dim. |
TypeError
|
The adapter has neither a |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> scene = DimArray(np.zeros((2, 4)), ("band", "x"), {"band": ("B4", "B8")})
>>> channels = rename(scene, band="channel")
>>> channels.dims, channels.labels["channel"]
(('channel', 'x'), ('B4', 'B8'))
>>> rename(channels, channel="band").labels == scene.labels
True
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
set_labels(adapter, out, labels, what)
¶
out with the labels of the named dims set through relabel.
Called after rewrap with every new or renamed dim, None included:
rewrap may have matched a stale label to a reused name.
Raises:
| Type | Description |
|---|---|
TypeError
|
The adapter has no |
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
split_dims(x, /, **dims)
¶
Split one dim into several, in place, in C order (xarray's unstack).
y_{…, i, j} = x_{…, p}, with p = i·X + j for pixel={"y": Y, "x": X}.
The sizes are given, not read from the labels, so they are static
under jit (A14). Tuple labels from merge_dims are split back.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
**dims
|
Mapping[str, int]
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A dim to split is not a dim of |
ValueError
|
The sizes do not multiply to the dim's length, a split lists no part, or the result repeats a dim name. |
TypeError
|
The adapter has no |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> pixels = DimArray(np.zeros((2, 12)), ("band", "pixel"))
>>> split_dims(pixels, pixel={"y": 3, "x": 4}).dims
('band', 'y', 'x')
>>> cube = DimArray(np.zeros((3, 4)), ("y", "x"), {"x": tuple("abcd")})
>>> back = split_dims(merge_dims(cube, p=("y", "x")), p={"y": 3, "x": 4})
>>> back.labels
{'x': ('a', 'b', 'c', 'd')}
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
split_labels(labels, sizes)
¶
Each part's labels, recovered from a merged dim's tuple labels.
The inverse of merged_labels. A part whose labels are exactly its
positions 0 … n-1 comes back unlabelled, as it was merged. Labels
that are not tuples of one entry per part, or not a C-order grid,
give no labels.
Example
split_labels((("B1", 0), ("B1", 1), ("B2", 0), ("B2", 1)), [2, 2]) (('B1', 'B2'), None)
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
squeeze(x, dim)
¶
Drop named length-1 dims.
y_{…} = x_{…, 0, …}.
dim is required: squeezing every length-1 dim would also drop a
batch of one (A12).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
dim
|
str | Sequence[str]
|
A dim name, or several; each must have size 1. |
required |
Raises:
| Type | Description |
|---|---|
KeyError
|
A name is not a dim of |
ValueError
|
A named dim's size is not 1. |
Example
import numpy as np from pipekit_array import DimArray squeeze(DimArray(np.zeros((1, 2, 4)), ("time", "band", "x")), "time").dims ('band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_shapes.py
pipekit_array.dim_shifts
¶
Dim-level functions that move data by position: pad, roll, shift, diff, …
Phase 2 of the dim-functions plan (docs/design/api/dim_functions.md,
ADRs A11, A12 and A16). Like the functions of pipekit_array.dims, each
runs on any carrier with a registered pipekit_ops.NamedArrayAdapter and
calls an adapter method of the same name, when there is one, instead of
the generic path.
pad— widen named dims, with a constant or from the data's edges.roll,shift— move data along named dims, cyclically or with a fill.diff— the n-th forward difference along one dim.cumreduce— a cumulative function along one dim ("cumulative_sum").where— keep entries where a mask is True, aligned by dim name.
Index-only. None reads a label. Labels stay on their positions
(xarray's roll_coords=False), and a dim whose size changes loses its
labels, which rewrap does by itself.
Dtype preserved. A fill of NaN needs a floating dtype: pad,
shift and where raise TypeError on integer or bool data unless a
fill value is given, where xarray would promote to float (A12).
The Operator forms live in pipekit_array.dim_operators.
cumreduce(x, fn, dim)
¶
A cumulative function along dim; labels and length unchanged.
c_t = ∑_{τ ≤ t} x_τ for "cumulative_sum".
No implicit skipna (xarray's cumsum skips NaN by default): ask
for "nancumsum".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
fn
|
Reduction
|
An array-namespace name ( |
required |
dim
|
str
|
The dim to accumulate along. |
required |
Raises:
| Type | Description |
|---|---|
KeyError
|
|
ValueError
|
The namespace has no |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> change = DimArray(np.array([1.0, 2.0, 3.0]), ("time",))
>>> cumreduce(change, "cumulative_sum", "time").data
array([1., 3., 6.])
>>> gaps = DimArray(np.array([1.0, np.nan, 3.0]), ("time",))
>>> cumreduce(gaps, "nancumsum", "time").data
array([1., 1., 4.])
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
diff(x, dim, /, n=1, *, label='upper')
¶
The n-th forward difference along dim (xarray's diff).
Δy_t = y_{t+1} - y_t, applied n times: N entries become N - n.
The result's labels on dim are the minuend's ("upper", positions
n … N - 1) or the subtrahend's ("lower", positions 0 … N - n - 1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
dim
|
str
|
The dim to difference along. |
required |
n
|
int
|
The order, n ≥ 0; static under |
1
|
label
|
str
|
|
'upper'
|
Raises:
| Type | Description |
|---|---|
KeyError
|
|
ValueError
|
|
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> series = DimArray(np.array([1.0, 4.0, 9.0, 16.0]), ("time",))
>>> diff(series, "time").data
array([3., 5., 7.])
>>> labelled = DimArray(series.data, ("time",), {"time": tuple("abcd")})
>>> second = diff(labelled, "time", 2, label="lower")
>>> second.data, second.labels["time"]
(array([2., 2.]), ('a', 'b'))
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
pad(x, /, *, mode='constant', constant_values=None, **pad_width)
¶
Widen named dims at either end (xarray's pad, four modes).
y_{…, t, …} = x_{…, t - b, …} for b ≤ t < b + N; the new entries follow
mode.
A padded dim loses its labels (xarray pads its coordinate with NaN).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
mode
|
str
|
|
'constant'
|
constant_values
|
float | None
|
The fill for |
None
|
**pad_width
|
int | tuple[int, int]
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A padded dim is not a dim of |
ValueError
|
|
TypeError
|
|
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> series = DimArray(np.arange(3.0), ("time",))
>>> pad(series, time=(2, 0)).data
array([nan, nan, 0., 1., 2.])
>>> pad(series, mode="wrap", time=1).data
array([2., 0., 1., 2., 0.])
>>> counts = DimArray(np.arange(3), ("time",))
>>> pad(counts, time=1)
Traceback (most recent call last):
...
TypeError: pad: int64 data has no NaN to fill with; pass constant_values=
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
roll(x, /, **shifts)
¶
Shift data cyclically along named dims; labels stay (xarray's roll).
y_{…, t, …} = x_{…, (t - k) mod N, …}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
**shifts
|
int
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A rolled dim is not a dim of |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> series = DimArray(np.arange(4.0), ("time",), {"time": tuple("abcd")})
>>> rolled = roll(series, time=1)
>>> rolled.data, rolled.labels["time"]
(array([3., 0., 1., 2.]), ('a', 'b', 'c', 'd'))
>>> bool(np.array_equal(roll(rolled, time=-1).data, series.data))
True
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
shift(x, /, *, fill_value=None, **shifts)
¶
Shift data along named dims, filling the vacated entries (xarray's shift).
y_{…, t, …} = x_{…, t - k, …} if 0 ≤ t - k < N, else the fill.
Labels stay on their positions. A shift of at least the dim's length fills it entirely.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
fill_value
|
float | None
|
The value of the vacated entries; None is NaN. |
None
|
**shifts
|
int
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A shifted dim is not a dim of |
TypeError
|
Integer or bool data without |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> series = DimArray(np.arange(4.0), ("time",))
>>> shift(series, time=1).data
array([nan, 0., 1., 2.])
>>> shift(DimArray(np.arange(4), ("time",)), time=-2, fill_value=-1).data
array([ 2, 3, -1, -1])
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
where(x, cond, /, other=None)
¶
Keep x where cond is True, other elsewhere (xarray's where).
y = x where v, else the fill; True keeps (A16).
cond aligns by dim name, as reduce(where=) does: a named mask
whose dims are among x's, so a time y x cloud mask applies to a
time band y x cube; or a plain array covering x's leading dims.
It is never right-broadcast.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
cond
|
Any
|
A bool mask v, named or plain. |
required |
other
|
float | None
|
The fill where |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A named |
TypeError
|
Integer or bool data without |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> cube = DimArray(np.ones((2, 3, 2)), ("time", "band", "x"))
>>> valid = DimArray(np.array([[True, False], [True, True]]), ("time", "x"))
>>> clear = where(cube, valid)
>>> clear.dims, clear.data[0, :, 1]
(('time', 'band', 'x'), array([nan, nan, nan]))
>>> where(cube, valid, 0.0).data[0, 0]
array([1., 0.])
Source code in packages/pipekit-array/src/pipekit_array/dim_shifts.py
pipekit_array.dim_windows
¶
Dim-level functions that cut a dim into windows: sliding and non-overlapping.
Phase 3 of the dim-functions plan (docs/design/api/dim_functions.md,
ADR A15). Like the functions of pipekit_array.dims, each runs on any
carrier with a registered pipekit_ops.NamedArrayAdapter, works on
positions only (labels follow the data, their values are never read),
and calls an adapter method of the same name, when there is one, instead
of the generic path.
window— sliding windows along one dim, as a new last dim; full windows only, each labelled by its last entry.coarsen— non-overlapping blocks reduced by a function:split_dimsthenreduce; the coarsened dim loses its labels.
The Operator forms live in pipekit_array.dim_operators; the
operators that apply another operator along a dim (ApplyAlong) or in
chunks of a dim (pipekit_array.batching.BatchAlong) live next to
MapOver and batch.
coarsen(x, fn, /, *, boundary='exact', **windows)
¶
Reduce non-overlapping blocks of k entries along each named dim.
z_{…, i', …} = fn_{0 ≤ r < k} x_{…, k·i' + r, …}, i' < ⌊N/k⌋.
A composition: split_dims each dim into (dim, block) then
reduce over the blocks, so coarsen(x, "mean", time=3) is
reduce(split_dims(x, time={"time": 4, "window": 3}), "mean",
"window"). A coarsened dim loses its labels: xarray's coord_func
reads coordinate values, which this layer never does (A15).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
fn
|
Reduction
|
An array-namespace name ( |
required |
boundary
|
str
|
|
'exact'
|
**windows
|
int
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A dim is not a dim of |
ValueError
|
No dim is given, k is not positive, |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> ndvi = DimArray(np.arange(24.0).reshape(2, 12), ("y", "x"))
>>> coarse = coarsen(ndvi, "mean", x=4)
>>> coarse.dims, coarse.data.tolist()
(('y', 'x'), [[1.5, 5.5, 9.5], [13.5, 17.5, 21.5]])
>>> coarsen(ndvi, "max", boundary="trim", x=5, y=2).data.tolist()
[[16.0, 21.0]]
>>> coarsen(ndvi, "mean", x=5)
Traceback (most recent call last):
...
ValueError: coarsen: 5 does not divide 'x' of size 12; ...
Source code in packages/pipekit-array/src/pipekit_array/dim_windows.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
window(x, /, *, window_dim='window', stride=1, **sizes)
¶
Sliding windows along one dim; the window becomes a new last dim.
w_{…, n, …, r} = x_{…, n·s + r, …}, 0 ≤ r < S, 0 ≤ n < ⌊(N - S)/s⌋ + 1.
Full windows only: there is no NaN fill, so float32 stays float32 and
integers work (A15). Window n carries the label of its last entry,
position n·s + S - 1, as xarray's
rolling(...).construct(window_dim).isel(dim=slice(S - 1, None, s))
does. For xarray's length-N result, pad the dim by S - 1 first.
Algorithm
- Count the full windows, n = ⌊(N - S)/s⌋ + 1.
- Take the S strided slices of the data, x_{r::s}, each n long.
- Stack them on a new last axis and rewrap on the slice of end positions, so the windowed dim keeps the labels of those.
The generic path copies the data S times; the xarray adapter's fast path is a view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
window_dim
|
str
|
The new last dim, indexing the entries r of a window. |
'window'
|
stride
|
int
|
The step s ≥ 1 between window starts. |
1
|
**sizes
|
int
|
|
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
The dim is not a dim of |
ValueError
|
|
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> ndvi = DimArray(np.arange(12.0), ("time",), {"time": tuple(range(12))})
>>> windows = window(ndvi, time=3, stride=2)
>>> windows.dims, windows.shape
(('time', 'window'), (5, 3))
>>> windows.labels["time"]
(2, 4, 6, 8, 10)
>>> window(DimArray(np.arange(4), ("time",)), time=2).data.tolist()
[[0, 1], [1, 2], [2, 3]]
Source code in packages/pipekit-array/src/pipekit_array/dim_windows.py
pipekit_array.dim_algebra
¶
Dim-level statistics and algebra: quantiles over dims, and contraction.
Phase 4 of the dim-functions plan (docs/design/api/dim_functions.md,
ADRs A17, A18 and A22). Like the functions of pipekit_array.dims, each
runs on any carrier with a registered pipekit_ops.NamedArrayAdapter,
works on positions only, and calls an adapter method of the same name,
when there is one, instead of the generic path.
quantile— quantiles over named dims; a sequenceqadds a leadingquantiledim labelled withq.dot— the sum of a product over named dims, asxr.dot; a shared dim not summed is a batch dim and appears once.
Both set the labels of their new dims through the adapter's optional
relabel (A22), as pipekit_array.dim_shapes does.
The Operator forms live in pipekit_array.dim_operators.
check_method(method, what)
¶
Raise ValueError unless method is one of METHODS.
Source code in packages/pipekit-array/src/pipekit_array/dim_algebra.py
dot(*xs, dim)
¶
The sum of the product of carriers over named dims (xr.dot).
s_{o…} = ∑{d ∈ dim} ∏ₖ x⁽ᵏ⁾, where o… are the dims not in dim.
The output dims are every input dim not in dim, in order of first
appearance. A dim shared by several inputs and not in dim is a
batch dim: it appears once, so dot is one named-index einsum,
never tensordot (A17). Shared dims must agree exactly in size and
labels; dims from inputs after the first carry those inputs' labels.
Algorithm
- Give each distinct dim a letter, in order of first appearance.
- Write one subscript per input from its dims, and the output's
from the dims not in
dim. xp.einsum; for a namespace without it, broadcast every input to the union of the dims, multiply, andxp.sumoverdim(exact, but holds the full product).rewrapon the first input, thenrelabelthe dims taken from later inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*xs
|
Any
|
Named carriers of one family and one backend. |
()
|
dim
|
str | Sequence[str]
|
A dim name, or several, to sum over; required (A17): xarray would sum every shared dim, so a dim shared by accident would vanish. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
No carrier is given, a shared dim differs in size or labels, or there are more dims than letters. |
KeyError
|
A name in |
BackendError
|
The inputs sit on more than one backend. |
TypeError
|
The adapter has no |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> x = DimArray(np.ones((12, 13)), ("time", "band"))
>>> w = DimArray(np.ones((12, 13, 3)), ("time", "band", "component"))
>>> s = dot(x, w, dim="band")
>>> s.dims, bool(np.allclose(s.data, 13.0))
(('time', 'component'), True)
>>> pixels = DimArray(np.ones((2, 13, 6)), ("time", "band", "pixel"))
>>> labelled = {"component": ("pc1", "pc2", "pc3")}
>>> weights = DimArray(np.ones((13, 3)), ("band", "component"), labelled)
>>> scores = dot(pixels, weights, dim="band")
>>> scores.dims, scores.labels["component"]
(('time', 'pixel', 'component'), ('pc1', 'pc2', 'pc3'))
Source code in packages/pipekit-array/src/pipekit_array/dim_algebra.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |
probabilities(q, what)
¶
q as one float, or a tuple of floats; each must lie in [0, 1].
Raises:
| Type | Description |
|---|---|
ValueError
|
A probability is outside [0, 1]. |
Source code in packages/pipekit-array/src/pipekit_array/dim_algebra.py
quantile(x, q, dim, /, *, method='linear', skipna=False)
¶
Quantiles over named dims (xarray's quantile).
Q_{k…} = quantile q_k of {x_{…, d…} : d over dim}.
A sequence q adds a leading dim quantile, labelled with q;
a float drops it. A float input keeps its dtype. NaN propagates unless
skipna (no implicit skipna, A12).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
q
|
float | Sequence[float]
|
A probability in [0, 1], or a sequence of them; static under
|
required |
dim
|
str | Sequence[str]
|
A dim name, or several, to reduce over. |
required |
method
|
str
|
One of |
'linear'
|
skipna
|
bool
|
Ignore NaN ( |
False
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A name is not a dim of |
ValueError
|
|
TypeError
|
The adapter has no |
Examples:
>>> import numpy as np
>>> from pipekit_array import DimArray
>>> cube = DimArray(np.arange(24.0).reshape(4, 2, 3), ("time", "band", "x"))
>>> spread = quantile(cube, [0.1, 0.9], "time")
>>> spread.dims, spread.labels["quantile"]
(('quantile', 'band', 'x'), (0.1, 0.9))
>>> quantile(cube, 0.5, ("time", "x"), method="lower").dims
('band',)
Source code in packages/pipekit-array/src/pipekit_array/dim_algebra.py
pipekit_array.dim_operators
¶
The dim-level functions as Operators, for pipelines and config.
Each wraps one function of pipekit_array.dims,
pipekit_array.dim_shapes or pipekit_array.dim_shifts and computes its
output Signature without running. Operators that take only names and
numbers (ISel, Sel, Transpose, Reduce, ArgReduce, TakeAlong,
Rename, ExpandDims, Squeeze, BroadcastLike, MergeDims,
SplitDims, Pad, Roll, Shift, Diff, CumReduce, Where)
round-trip through dumps / loads; those that hold a user function
(MapOver, Combine) set forbid_in_yaml, like pipekit_ops.Lambda.
Reduce and ArgReduce take the reduction by its array-namespace name
("mean", "argmax") so they serialise; the function forms in
pipekit_array.dims also take a callable.
Phase 3 (pipekit_array.dim_windows): Window and Coarsen (by the
reduction's name) round-trip; ApplyAlong holds an Operator, not a
function, so it round-trips whenever that operator does.
Phase 4 (pipekit_array.dim_algebra): Quantile and Dot round-trip;
q is stored as a float or an ordered list.
ApplyAlong
¶
Bases: Operator
inner on each item along dim; the results stacked along dim, first.
map_over with an Operator instead of a function, so it serialises
whenever inner does: inner is stored as a nested config, and
ApplyAlong itself does not set forbid_in_yaml (a flagged
inner is found by the serialiser's walk). Use MapOver for a
plain function. Replaces the planned ApplyToBands (A15).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inner
|
Operator
|
The operator applied to each item, |
required |
dim
|
str
|
The dim to map over. |
required |
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((3, 2, 4)), ("time", "band", "x")) ApplyAlong(Reduce("sum", dim="band"), dim="time")(cube).dims ('time', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
ArgReduce
¶
Bases: Operator
The index of a reduction along one dim ("argmax", "argmin").
The output signature's dtype is None: the integer width depends on
the backend.
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
BroadcastLike
¶
Bases: Operator
Add other's missing dims to x, in front: op(x, other).
compute_output_signature takes the pair (x's, other's): from
x's alone the added dims are unknown, so it returns None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exclude
|
Sequence[str]
|
Dims of |
()
|
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Coarsen
¶
Bases: Operator
Reduce non-overlapping blocks along named dims by a named reduction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
str
|
The reduction's name in the array namespace: |
required |
boundary
|
str
|
|
'exact'
|
**windows
|
int
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Coarsen("mean", y=2, x=2)(DimArray(np.ones((4, 6)), ("y", "x"))).shape (2, 3)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Combine
¶
Bases: Operator
An element-wise function of carriers with the same dims: op(a, b, …).
Holds a user function, so its config is a debug repr only.
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
CumReduce
¶
Bases: Operator
A cumulative function along dim, by its array-namespace name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
str
|
|
required |
dim
|
str
|
The dim to accumulate along. |
required |
Example
import numpy as np from pipekit_array import DimArray CumReduce("cumulative_sum", "time")(DimArray(np.ones(3), ("time",))).data array([1., 2., 3.])
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Diff
¶
Bases: Operator
The n-th forward difference along dim: N entries become N - n.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str
|
The dim to difference along. |
required |
n
|
int
|
The order, n ≥ 0. |
1
|
label
|
str
|
The labels kept on |
'upper'
|
Example
import numpy as np from pipekit_array import DimArray Diff("time")(DimArray(np.zeros((12, 4)), ("time", "x"))).shape (11, 4)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Dot
¶
Bases: Operator
The sum of the product of carriers over named dims: op(*xs).
compute_output_signature takes the tuple of the inputs' signatures:
from one signature the other inputs' dims are unknown, so it returns
None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str | Sequence[str]
|
A dim name, or several, to sum over. |
required |
Example
import numpy as np from pipekit_array import DimArray x = DimArray(np.ones((12, 13)), ("time", "band")) w = DimArray(np.ones((13, 3)), ("band", "component")) Dot(dim="band")(x, w).dims ('time', 'component')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
ExpandDims
¶
Bases: Operator
Insert length-1 dims at position axis of the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str | Sequence[str]
|
A new dim name, or several. |
required |
axis
|
int
|
Where the first new dim goes; negative counts from the end. |
0
|
Example
import numpy as np from pipekit_array import DimArray ExpandDims("member")(DimArray(np.zeros((2, 4)), ("band", "x"))).dims ('member', 'band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
ISel
¶
Bases: Operator
Select by position along one dim; an integer drops the dim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str
|
The dim to index. |
required |
index
|
int | slice
|
An int, or a slice (stored in config as start/stop/step). |
required |
Example
import numpy as np from pipekit_array import DimArray ISel(dim="time", index=0)(DimArray(np.zeros((12, 4)), ("time", "x"))).dims ('x',)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
MapOver
¶
Bases: Operator
fn on each item along dim; the results stacked along dim, first.
Holds a user function, so its config is a debug repr only.
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
MergeDims
¶
Bases: Operator
Merge the listed dims into one, placed last, in C order.
Its config lists the merges in order (JSON objects sort their keys).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**dims
|
Sequence[str]
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.zeros((2, 3, 4)), ("band", "y", "x")) MergeDims(pixel=("y", "x"))(cube).shape (2, 12)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Pad
¶
Bases: Operator
Widen named dims at either end; a padded dim loses its labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
str
|
|
'constant'
|
constant_values
|
float | None
|
The fill for |
None
|
**pad_width
|
int | Sequence[int]
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Pad(time=(2, 0))(DimArray(np.zeros((12, 4)), ("time", "x"))).shape (14, 4)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Quantile
¶
Bases: Operator
Quantiles over named dims; a sequence q adds a leading quantile dim.
q is stored in config as a float or an ordered list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
float | Sequence[float]
|
A probability in [0, 1], or a sequence of them. |
required |
dim
|
str | Sequence[str]
|
A dim name, or several, to reduce over. |
required |
method
|
str
|
One of |
'linear'
|
skipna
|
bool
|
Ignore NaN. |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((12, 2, 4)), ("time", "band", "x")) Quantile([0.1, 0.9], dim="time")(cube).dims ('quantile', 'band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Reduce
¶
Bases: Operator
Reduce over named dims with an array-namespace reduction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
str
|
The reduction's name in the array namespace: |
required |
dim
|
str | Sequence[str]
|
A dim name, or several. |
required |
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((12, 2, 4)), ("time", "band", "x")) Reduce("median", dim="time")(cube).dims ('band', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Rename
¶
Bases: Operator
New dim names; data, order and labels unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**names
|
str
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Rename(band="channel")(DimArray(np.zeros((2, 4)), ("band", "x"))).dims ('channel', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Roll
¶
Bases: Operator
Shift data cyclically along named dims; labels stay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**shifts
|
int
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Roll(time=1)(DimArray(np.arange(3.0), ("time",))).data array([2., 0., 1.])
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Sel
¶
Bases: Operator
Select by exact label along one dim; one label drops the dim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str
|
The dim to select on; it must have labels. |
required |
label
|
Any
|
One label, or a list of labels (the dim is kept). A tuple is one label when the dim has tuple-valued labels, else several. |
required |
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Shift
¶
Bases: Operator
Shift data along named dims, filling the vacated entries; labels stay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fill_value
|
float | None
|
The value of the vacated entries; None is NaN. |
None
|
**shifts
|
int
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Shift(time=1)(DimArray(np.arange(3.0), ("time",))).data array([nan, 0., 1.])
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
SplitDims
¶
Bases: Operator
Split one dim into several, in place, in C order.
Its config lists each split's parts in order (JSON objects sort their keys).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**dims
|
Mapping[str, int]
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray pixels = DimArray(np.zeros((2, 12)), ("band", "pixel")) SplitDims(pixel={"y": 3, "x": 4})(pixels).dims ('band', 'y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Squeeze
¶
Bases: Operator
Drop named length-1 dims.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
str | Sequence[str]
|
A dim name, or several; each must have size 1. |
required |
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
TakeAlong
¶
Bases: Operator
Gather one entry of dim per position: op(x, index).
index is a named carrier with x's dims without dim, such as
an ArgReduce result.
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Transpose
¶
Bases: Operator
Reorder dims by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
Sequence[str]
|
Every dim, once, in the new order. |
required |
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Where
¶
Bases: Operator
Keep x where cond is True, other elsewhere: op(x, cond).
compute_output_signature takes x's signature, or the pair
(x's, cond's): the output has x's dims and dtype either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
float | None
|
The fill where |
None
|
Example
import numpy as np from pipekit_array import DimArray valid = DimArray(np.array([True, False]), ("x",)) Where()(DimArray(np.ones(2), ("x",)), valid).data array([ 1., nan])
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Window
¶
Bases: Operator
Sliding windows along one dim, as a new last dim; full windows only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window_dim
|
str
|
The new last dim. |
'window'
|
stride
|
int
|
The step between window starts. |
1
|
**sizes
|
int
|
|
{}
|
Example
import numpy as np from pipekit_array import DimArray Window(stride=2, time=3)(DimArray(np.zeros((12, 4)), ("time", "x"))).shape (5, 4, 3)
Source code in packages/pipekit-array/src/pipekit_array/dim_operators.py
Sample tables¶
to_rows(x, sample_dims) turns any named carrier into a (n_samples, n_features) table, with rows over the sample dims and columns over the rest.
from_rows(rows, layout) turns a table back into a carrier like the input: with the original features, with a new feature dim (feature_dim="component") or, for one value per sample, with the sample dims only.
Both run on the data's own backend (geostack OP-O3).
pipekit_array.rows
¶
Sample tables by dim name: to_rows and from_rows.
A per-sample model (scikit-learn, a pixel classifier) wants a 2-D table:
one row per sample, one column per feature. to_rows turns any named
carrier into one, choosing the samples by dim name, and from_rows turns
a table of rows back into a carrier like the input, on the data's own
backend (NumPy, JAX under jit, dask):
x (time, band, y, x) --to_rows(sample_dims=("time", "y", "x"))-->
rows (time·y·x, band), layout
rows (n, k) --from_rows(..., feature_dim="component")-->
(time, component, y, x)
Samples are the C-order product of the sample dims, taken in the carrier's dim order; features likewise over every other dim. Like the rest of the dim-level functions, these never read coordinate values (GA-Q3): labels and geometry come back through the carrier's adapter, which keeps those of the dims that survive with their size.
RowLayout
dataclass
¶
How to_rows flattened a carrier, so from_rows can rebuild one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dims
|
tuple[str, ...]
|
The carrier's dims, in its order. |
required |
sizes
|
tuple[int, ...]
|
The size of each dim, in the same order. |
required |
sample_dims
|
tuple[str, ...]
|
The dims that became rows, in the carrier's order. |
required |
feature_dims
|
tuple[str, ...]
|
The dims that became columns, in the carrier's order. |
required |
template
|
Any
|
The carrier to rewrap like (left out of equality and repr). |
None
|
Source code in packages/pipekit-array/src/pipekit_array/rows.py
from_rows(rows, layout, *, feature_dim=None)
¶
A carrier like layout.template from a table of rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
Any
|
|
required |
layout
|
RowLayout
|
From |
required |
feature_dim
|
str | None
|
The name of the new feature dim, for a table whose columns are not the original features. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A carrier with |
Any
|
feature dim (if any) where the first feature dim was, rewrapped by |
Any
|
the template's adapter. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The rows do not match the layout, or a new feature dim
is needed and |
Example
import numpy as np from pipekit_array import DimArray scene = DimArray(np.arange(24.0).reshape(2, 3, 4), ("band", "y", "x")) rows, layout = to_rows(scene, sample_dims=("y", "x")) from_rows(rows[:, :1] * 0, layout, feature_dim="score").dims ('score', 'y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/rows.py
to_rows(x, sample_dims)
¶
x as a (n_samples, n_features) table, and how to undo it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier. |
required |
sample_dims
|
Sequence[str]
|
The dims whose entries are samples (rows); every other
dim of |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The table, on |
RowLayout
|
With no feature dims the table has one column. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
No sample dims, a repeated one, or one |
Example
import numpy as np from pipekit_array import DimArray scene = DimArray(np.arange(24.0).reshape(2, 3, 4), ("band", "y", "x")) rows, layout = to_rows(scene, sample_dims=("y", "x")) rows.shape, layout.feature_dims ((12, 2), ('band',))
Source code in packages/pipekit-array/src/pipekit_array/rows.py
scikit-learn estimators¶
Estimator fits and applies a scikit-learn-style estimator on any named carrier, through to_rows and from_rows ([sklearn], imported lazily; geostack OP-O3).
pipekit_array.estimator
¶
Estimator: one scikit-learn wrapper for every named carrier (geostack OP-O3).
A scikit-learn-style estimator learns from a (n_samples, n_features)
table. Estimator chooses the samples by dim name, flattens the carrier
with pipekit_array.to_rows, fits or applies the estimator, and rebuilds a
carrier like the input with pipekit_array.from_rows:
cube (time, band, y, x) ── Estimator(PCA(3), sample_dims=("time", "y", "x"),
feature_dim="component") ──>
(time, component, y, x)
It satisfies pipekit_ops.FittableTransformer: fit returns the operator
with estimator_ and layout_ set, and transform (and
predict, predict_proba, inverse_transform) only read them. Fitting
and applying run on the host: scikit-learn computes on NumPy, so a dask
input is computed and a JAX input converted, eagerly; inside a JAX trace
fit raises.
NaN policies. nan_policy decides what a non-finite entry costs:
"propagate"(default): the rows go to the estimator as they are;"raise": any non-finite entry raises, naming the policy;"mask_samples": rows with a non-finite entry are left out of fitting and applying, and come back asNaNin the output;"mask_features": columns non-finite in every fitted row are dropped at fit time and at transform time, and come back asNaNfrominverse_transform;"mask":"mask_features"then"mask_samples"on the columns kept, for a land-masked field that also has gaps.
The scikit-learn import is lazy: install the [sklearn] extra.
Estimator
¶
Bases: Operator
A scikit-learn-style estimator fitted and applied on a named carrier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
estimator
|
Any
|
Any object with scikit-learn's |
required |
sample_dims
|
tuple[str, ...] | list[str]
|
The dims whose entries are samples; every other dim is a feature. |
required |
feature_dim
|
str | None
|
The name of the output dim when the estimator's output
columns are not the input's features (PCA components, class
probabilities). Left |
None
|
nan_policy
|
NanPolicy
|
|
'propagate'
|
method
|
str
|
What calling the operator runs once fitted:
|
'transform'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
An unknown |
Example
import numpy as np from sklearn.decomposition import PCA from pipekit_array import DimArray rng = np.random.default_rng(0) cube = DimArray(rng.normal(size=(5, 4, 3)), ("band", "y", "x")) pca = Estimator(PCA(2), sample_dims=("y", "x"), feature_dim="component") pca.fit(cube).transform(cube).dims ('component', 'y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/estimator.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
fit(x, y=None)
¶
Learn the estimator from x (and y, for a supervised one).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A named carrier with the sample dims. |
required |
y
|
Any
|
Targets: a carrier whose dims are the sample dims (plus at
most one target dim), or |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
This operator, fitted. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
Source code in packages/pipekit-array/src/pipekit_array/estimator.py
inverse_transform(z)
¶
Map transform's output back to the fitted features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
z
|
Any
|
A carrier with the sample dims and the output dim ( |
required |
Returns:
| Type | Description |
|---|---|
Any
|
A carrier with the fitted feature dims and sizes in place of the |
Any
|
output dim; dropped features ( |
Source code in packages/pipekit-array/src/pipekit_array/estimator.py
predict(x)
¶
predict_proba(x)
¶
Class probabilities per sample, along feature_dim ("class").
einx¶
pipekit_array.einx writes tensor algebra as einx patterns whose axis names are the carrier's dim names: rearrange, reduce, einsum, repeat and the Einx operator.
It runs on NumPy or JAX and rewraps through the first input's adapter, so the dims that survive keep their labels, coordinates and grid.
A dask input raises BackendError naming .compute(), because einx has no dask backend (geostack OP-O2).
pipekit_array.einx
¶
A named-dims einx bridge on any carrier (geostack OP-O2).
einx <https://github.com/fferflo/einx>_ writes tensor algebra as patterns
("band y x -> y x band"). Here the pattern's axis names are the
carrier's dim names: each input is matched to its slot by name, einx runs
on the data's own backend (NumPy, JAX under jit), and the result is
rewrapped through the first input's adapter with the output slot's dims, so
it keeps the labels, coordinates and (for geoarray) the grid of the dims
that survive:
rearrange("band y x -> y x band", scene) # transpose by name
reduce("time band y x -> band y x", cube, op="mean") # over time
einsum("time band, band k -> time k", cube, w) # contract band
repeat("y x -> member y x", field, member=8) # broadcast
einsum, reduce and repeat match input slots to dims by name, in
any order. rearrange also splits and merges dims ("(a b)"), so its
input slot is read positionally: it lists the input's dims in order, a
group standing for one dim that is split. A merged output group
(a b) becomes one dim named a_b.
dask is not an einx backend: a dask-backed input raises
pipekit_ops.BackendError naming .compute() rather than computing it
silently. einx is imported lazily: install the [einx] extra.
Einx
¶
Bases: Operator
One einx operation as an operator: Einx("reduce", pattern, op="mean").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation
|
str
|
|
required |
pattern
|
str
|
The pattern, with dim names as axis names. |
required |
op
|
str | None
|
The reduction, for |
None
|
sizes
|
dict[str, int] | None
|
Axis sizes forwarded to einx. |
None
|
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((2, 3, 4)), ("time", "y", "x")) Einx("reduce", "time y x -> y x", op="mean")(cube).dims ('y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/einx.py
PatternError
¶
einsum(pattern, *xs, **sizes)
¶
Contract the dims named on the input slots and left off the output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str
|
|
required |
*xs
|
Any
|
One named carrier per input slot; carriers of one family are checked to combine first (geoarray: the same grid). |
()
|
**sizes
|
int
|
Forwarded to einx. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A carrier with the output slot's dims, rewrapped by the first |
Any
|
input's adapter. |
Example
import numpy as np from pipekit_array import DimArray a = DimArray(np.ones((2, 3)), ("time", "band")) w = DimArray(np.ones((3, 4)), ("band", "k")) einsum("time band, band k -> time k", a, w).shape (2, 4)
Source code in packages/pipekit-array/src/pipekit_array/einx.py
rearrange(pattern, x, **sizes)
¶
Transpose, split or merge dims by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str
|
One input slot and one output slot. The input slot lists
|
required |
x
|
Any
|
A named carrier. |
required |
**sizes
|
int
|
Sizes of split dims einx cannot infer ( |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A carrier with the output slot's dims (a merged |
Any
|
|
Raises:
| Type | Description |
|---|---|
PatternError
|
The input slot does not describe |
Example
import numpy as np from pipekit_array import DimArray scene = DimArray(np.zeros((3, 4, 5)), ("band", "y", "x")) rearrange("band y x -> y x band", scene).dims ('y', 'x', 'band')
Source code in packages/pipekit-array/src/pipekit_array/einx.py
reduce(pattern, x, *, op, **sizes)
¶
Reduce over the dims on the input slot that the output slot leaves out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str
|
|
required |
x
|
Any
|
A named carrier. |
required |
op
|
str | Callable[..., Any]
|
|
required |
**sizes
|
int
|
Forwarded to einx. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A carrier with the output slot's dims. |
Example
import numpy as np from pipekit_array import DimArray cube = DimArray(np.ones((2, 3, 4)), ("time", "y", "x")) reduce("time y x -> y x", cube, op="sum").data[0, 0] np.float64(2.0)
Source code in packages/pipekit-array/src/pipekit_array/einx.py
repeat(pattern, x, **sizes)
¶
Broadcast x along new dims (sizes in sizes).
Example
import numpy as np from pipekit_array import DimArray field = DimArray(np.zeros((4, 5)), ("y", "x")) repeat("y x -> member y x", field, member=8).shape (8, 4, 5)
Source code in packages/pipekit-array/src/pipekit_array/einx.py
Batches¶
batch groups carriers into fixed-size Padded batches (value, valid) along a new dim.
A short last batch is padded with copies of its last item (pad="last"), yielded short ("none") or dropped ("drop").
stack_items is the stacking it uses; leave the padding out of a statistic with reduce(..., where=padded.valid) (ADR A10).
BatchAlong(inner, size, dim, pad) is the operator form for one carrier: it cuts dim into chunks, runs inner on each and joins the results (ADR A19).
pipekit_array.batching
¶
Fixed-size batches of named carriers, with a mask of the real entries.
batch groups a stream of carriers into Padded batches of one size, so a
jitted step compiles once for every batch, the short last one included:
items ──batch(size=4, dim="sample", pad="last")──▶ Padded
i₀ i₁ i₂ i₃ i₄ i₅ value (sample, *item_dims)
valid (sample,) bool
batch 1: i₀ i₁ i₂ i₃ valid = T T T T
batch 2: i₄ i₅ i₅ i₅ valid = T T F F ← copies of the last real item
Padding copies the last real item: its values are finite and in
distribution, so a masked loss and its gradient stay finite and a
normalisation over the batch sees real statistics. Reduce with
reduce(out, "mean", "sample", where=padded.valid) to leave the padding
out.
BatchAlong is the operator form for one carrier: it cuts a dim into
chunks, runs an operator on each and joins the results, with the same
pad policies (A19).
BatchAlong
¶
Bases: Operator
inner on chunks of size along dim of one carrier, joined back.
y = concat([inner(x_{0:m}), inner(x_{m:2m}), …], dim), m = size.
Not pipekit_ops.BatchedMap, which takes an iterable and calls its
operator on lists of items: BatchAlong splits one named carrier
along a dim (A19, which supersedes A5). The use case is a GPU-bound
model on a cube that does not fit in device memory at once.
inner must keep dim, one output entry per input entry (a
per-item operator), for the "last" padding to be trimmed and for
dim's labels to come back; the result then equals inner(x).
Algorithm
- Cut
diminto chunks ofsize; the last may be short. - Apply
pad(ADR A10) to a short last chunk:"none"keeps it short;"last"pads it tosizewith copies of its last item, so a jittedinnercompiles once, and trims the padding from its output;"drop"drops it. - Join the outputs along
dimand setdim's labels to the input's (an adapter's genericconcatdrops them).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inner
|
Operator
|
The operator applied to each chunk. |
required |
size
|
int
|
Entries per chunk; must be positive. |
required |
dim
|
str
|
The dim to cut. |
required |
pad
|
PadPolicy
|
|
'none'
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
Example
import numpy as np from pipekit_array import DimArray, Reduce cube = DimArray(np.ones((10, 2, 4)), ("sample", "band", "x")) per_chunk = BatchAlong(Reduce("sum", "band"), size=4, dim="sample") per_chunk(cube).dims, per_chunk(cube).shape (('sample', 'x'), (10, 4)) BatchAlong(Reduce("sum", "band"), 4, "sample", pad="drop")(cube).shape (8, 4)
Source code in packages/pipekit-array/src/pipekit_array/batching.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
Padded
¶
Bases: NamedTuple
A batch: the items as one carrier and a mask of the real ones.
A NamedTuple, so JAX treats it as a pytree: a jitted step taking a
Padded does not retrace when the mask changes. A full batch is a
Padded whose valid is all True.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
Any
|
The items stacked along the batch dim, first
( |
valid |
Any
|
|
Example
import numpy as np from pipekit_array import DimArray, reduce items = [DimArray(np.full(3, float(i)), ("x",)) for i in range(3)] padded = next(batch(items, size=4, dim="sample")) padded.valid.tolist() [True, True, True, False] reduce(padded.value, "mean", "sample", where=padded.valid).data array([1., 1., 1.])
Source code in packages/pipekit-array/src/pipekit_array/batching.py
batch(items, *, size, dim, pad='last')
¶
Group named carriers into Padded batches of size along dim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
Iterable[Any]
|
Named carriers with the same dims (each registered with pipekit's adapter registry), consumed lazily. |
required |
size
|
int
|
Items per batch; must be positive. |
required |
dim
|
str
|
The new leading batch dim ( |
required |
pad
|
PadPolicy
|
What to do with a short last batch:
|
'last'
|
Yields:
| Type | Description |
|---|---|
Padded
|
One |
Padded
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Example
import numpy as np from pipekit_array import DimArray items = [DimArray(np.ones((2, 2)), ("y", "x")) for _ in range(5)] def sizes(pad): ... return [len(b.valid) for b in batch(items, size=2, dim="s", pad=pad)] sizes("last"), sizes("none"), sizes("drop") ([2, 2, 2], [2, 2, 1], [2, 2])
Source code in packages/pipekit-array/src/pipekit_array/batching.py
Kernels¶
pipekit_array.kernel
¶
KernelSpec and KernelOp — one L1 kernel, written once, on every carrier.
A shapes-only kernel declares the axes it consumes and produces with a
generalized-ufunc signature ("(c)->()", "(r,c),(c),(c,d)->(r)"),
the convention NumPy (np.vectorize), JAX (jnp.vectorize) and
xarray (apply_ufunc core dims) already share. KernelOp wraps the
kernel once and runs it:
raw arrays → the kernel, called as is
named carriers → core dims moved last by name, the kernel on the
data, the result rewrapped by the adapter
(NumPy eagerly, JAX traced under jit)
an adapter with → that adapter's own path (the xarray adapter:
``apply_kernel`` one ``apply_ufunc``, lazy on dask)
Kernel conventions:
- Consumed axes last. Core dims arrive as the trailing axes.
- Broadcast over leading axes, or set
vectorize=Trueand pay fornp.vectorize/jnp.vectorize. - Non-carrier inputs are keyword parameters (a background mean, a
threshold), passed whole and never mapped. Geometry derived from the
carrier (
geometry=) is passed the same way; geometry that varies per pixel has the carrier's dims and is a positional input instead. - Write against the array namespace (
xp.linalg.solve) to run on every backend. A kernel that needs a JAX-only library (lineax) declaresbackends=("jax",)(spike finding F3).
Rules from the spike (geotoolz#516):
- Outputs keep the first input's dim order (F2): a gufunc puts core dims last, so each output is transposed back, with dims new to the output last.
geometry=needs one geometry per call (F4): on a carrier whose adapter reportsbatch_dims(per-item transforms),KernelOpraises and namesmap_over.- An output-only core dim declares its size (F5):
KernelSpec.sizesmaps it to an input core dim, so a lazy backend can plan the output.
Carriers of different kinds combine by dim name and size (a geo carrier
"band row col" with a DimArray "col band"); grid checks
(check_compatible) run between carriers of the same kind.
KernelOp
¶
Bases: Operator
An L1 kernel wrapped once, run on raw arrays and on every named carrier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel
|
Callable[..., Any]
|
The L1 function: arrays (core dims last) and keyword params in, arrays out. |
required |
spec
|
KernelSpec | str
|
A |
required |
core
|
dict[str, str]
|
Each core letter of the spec → the carrier dim it stands for,
e.g. |
required |
geometry
|
dict[str, Callable[[Any], Any]] | None
|
Kernel keyword → a function of the first carrier returning
arrays ( |
None
|
params
|
Mapping[str, Any] | None
|
Keyword parameters passed to every call, as a mapping: the
way to pass one named |
None
|
**kwargs
|
Any
|
More keyword parameters passed to every call. |
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
TypeError
|
|
KernelOp holds a user function, so its config is a debug repr only
(forbid_in_yaml, like pipekit_ops.Lambda); a named operator built on
one by composition (geoops.NDVI) is what serialises.
Example
import numpy as np from pipekit_array import DimArray def nd(a, b, eps): ... return (a - b) / (a + b + eps) op = KernelOp(nd, "(),()->()", core={}, eps=1e-10) nir = DimArray(np.full((2, 3), 0.4), ("y", "x")) red = DimArray(np.full((2, 3), 0.1), ("y", "x")) op(nir, red).dims ('y', 'x')
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | |
compute_output_signature(input_signature)
¶
The output signature(s) for a one-input kernel; None otherwise.
A two-output kernel gives a tuple of signatures. With several inputs
the other inputs' dims are unknown here, so the answer is None.
The dtype is the one spec.dtypes declares ("same": the
input's), else unknown: a kernel may return a mask or an index.
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
KernelSpec
dataclass
¶
A generalized-ufunc signature over abstract core-dim letters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signature
|
str
|
E.g. |
required |
backends
|
tuple[str, ...]
|
The backends the kernel is written for, from |
('numpy', 'jax', 'dask', 'xarray')
|
vectorize
|
bool
|
The kernel handles only its core dims, and is mapped over
the rest with |
False
|
sizes
|
tuple[tuple[str, str], ...]
|
Output-only core letters and the input letter whose size each
has, e.g. |
()
|
dtypes
|
tuple[str, ...]
|
Each output's dtype, for output signatures and lazy
backends: a dtype name ( |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
The signature does not parse, |
Example
spec = KernelSpec("(r,c)->(c),(c,d)", sizes=(("d", "c"),)) spec.inputs, spec.outputs ((('r', 'c'),), (('c',), ('c', 'd')))
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
derive_geometry(geometry, first)
¶
The kernel's derived-geometry keywords, computed from the first carrier.
Raises:
| Type | Description |
|---|---|
ValueError
|
The carrier's adapter reports |
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
kernel_output_dims(spec, core, input_dims)
¶
The dims of each output, from the inputs' dims, without running.
Loop dims are every input's dims minus its core dims, in order of first appearance. Output k has the loop dims plus its core dims, in the first input's order, with dims new to the output last (F2).
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
kernel_output_signature(spec, core, input_signature)
¶
KernelOp.compute_output_signature for any operator running spec.
Shared with pipekit_jax.JaxKernelOp, so a converted operator reports
the same dims, sizes, dtypes and output count.
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
run_kernel(kernel, spec, core, carriers, kwargs)
¶
Run kernel on raw arrays or named carriers; the dispatch behind KernelOp.
Raises:
| Type | Description |
|---|---|
BackendError
|
Carriers on more than one backend, or a backend the spec does not list. |
TypeError
|
A raw array next to named carriers. |
ValueError
|
A core dim is missing from its input, or a dim has two sizes. |
Source code in packages/pipekit-array/src/pipekit_array/kernel.py
xarray¶
pipekit_array.xarray
¶
The xr.DataArray adapter (pipekit-array[xarray]).
Registered lazily: importing pipekit-array never imports xarray; the
adapter loads the first time a DataArray reaches pipekit_ops.get_adapter
(pipekit_ops.register_lazy_adapter). This module is the only place
pipekit-array imports xarray.
- Exact coordinates.
XarrayAdapter.check_compatiblealigns withjoin="exact": two cubes whose x differ by 1e-7 raise instead of inner-joining to an empty result. - Lazy on dask.
KernelOpruns as onexr.apply_ufunccall,dask="parallelized", one task per chunk; the dim-level fast paths (reduce,argreduce,take_along,stack,concat,combine,transpose,rename,window) are native xarray calls, so nothing computes until.compute(). DataArrayonly (PK-P5):DatasetandDataTreedispatch stays in xrops.
from_xarray turns a DataArray into a DimArray, keeping the labels of
dims that have a 1-D index coordinate.
XarrayAdapter
¶
The pipekit_ops.NamedArrayAdapter for xr.DataArray, with fast paths.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | |
apply_kernel(kernel, spec, core, kwargs, *xs)
¶
A KernelOp call as one xr.apply_ufunc: lazy, exact, in input order.
Core dims move last; kwargs (params and derived geometry) are
passed whole; output-only core dims take their sizes from
spec.sizes; outputs come back in the first input's dim order (F2).
Raises:
| Type | Description |
|---|---|
BackendError
|
The spec does not list |
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
argreduce(x, fn, dim)
¶
x.reduce with NumPy's argmax / argmin (or fn).
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
check_compatible(*xs)
¶
The inputs back if their shared coordinates are exactly equal.
Raises:
| Type | Description |
|---|---|
ValueError
|
Coordinates differ; xarray would otherwise inner-join. |
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
combine(fn, *xs)
¶
apply_ufunc(fn, …, join="exact"), lazy on dask.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
concat(xs, dim)
¶
Along an existing dim, joining its coordinate; others must be identical.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
data(x)
¶
dims(x)
¶
isel(x, indexers)
¶
labels(x, dim)
¶
The values of dim's 1-D index coordinate, or None.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
reduce(x, fn, dims, *, where=None)
¶
x.reduce with the array-namespace function, lazy on dask.
A name resolves to NumPy's function ("nanmean" included), so NaN
handling matches the generic path: DataArray's own mean would
skip NaN by default. where arrives aligned to x's full shape
(pipekit_array.reduce aligns it) and is passed to the function.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
relabel(x, labels)
¶
assign_coords for a tuple, drop_vars for None (ADR A22).
Tuple-valued labels (pipekit_array.merge_dims) become a 1-D object
coordinate, one tuple per entry.
Raises:
| Type | Description |
|---|---|
ValueError
|
A dim is not in |
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
rename(x, names)
¶
x.rename, which also renames the dim in non-index coordinates.
The generic path (rewrap then relabel) would drop a
coordinate such as quality(time, x) on a renamed dim (A22).
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
rewrap(x, data, dims)
¶
A DataArray around new data, keeping coords and attrs of surviving dims.
A coordinate is kept when all its dims survive with their sizes; a
scalar one named after a new dim (left by isel) is dropped, since
xarray refuses a scalar variable named after a dim.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
signature(x)
¶
Dims with their sizes, and the dtype's name.
stack(xs, dim)
¶
A new leading dim; coordinates must be identical (never aligned).
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
take_along(x, index, dim)
¶
One entry of dim per position, lazily on dask, in x's dim order.
An apply_ufunc over take_along_axis, not a vectorized
isel: dask cannot index with a lazy indexer. Attributes are kept,
auxiliary coordinates on dim (quality(time, x)) are gathered
with the same index, and dim's own coordinate is dropped.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
transpose(x, order)
¶
window(x, dim, size, *, window_dim, stride)
¶
rolling(...).construct(window_dim) from the first full window: a view.
The fill is zero, not NaN, so integers keep their dtype; the
isel drops every filled window.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
from_xarray(da)
¶
A DimArray with the same data and dims; 1-D index coordinates become labels.
Source code in packages/pipekit-array/src/pipekit_array/xarray.py
register()
¶
Register XarrayAdapter for xr.DataArray (the lazy loader calls this).
Test kit¶
The test kit ships in the wheel, for carrier libraries' own test suites (gates PK-K1 and PK-K4).
pipekit's own tests run PK-K2, PK-K3 and, under -m slow, PK-K5.
pipekit_array.testing
¶
Contract checks for named-array adapters and KernelOp (gates PK-K1, PK-K4).
A carrier library proves its adapter in its own test suite with one call, instead of copying tests::
from pipekit_array.testing import check_adapter, check_kernel_op
def test_adapter_contract():
check_adapter(GeoArrayAdapter(), make_geoarray)
def test_ndvi_on_every_carrier():
check_kernel_op(ndvi, [(nir_np, red_np), (nir_jax, red_jax), (nir_da, red_da)])
check_adapter(adapter, make_carrier)— every dim-level function (and the adapter's fast paths, which are what the functions call) on the carrier equals the generic path on aDimArraywith the same data, dims and labels; the protocol methods behave. The functions that make new or renamed dims (pipekit_array.dim_shapes) must also give the same labels; those on the band labels run when the adapter hasrelabel(pipekit_ops.LabelledAdapter). So must the index-only transforms (pipekit_array.dim_shifts) and the windows (pipekit_array.dim_windows). So mustquantileanddot(pipekit_array.dim_algebra), withinrtol.check_kernel_op(op, calls)— the sameKernelOpon each carrier family gives equal data, equal dims, and the signatureop.compute_output_signaturepredicts (PK-K1, PK-K4).
Both raise AssertionError naming the function and the carrier type.
They need NumPy, and nothing from pytest.
check_adapter(adapter, make_carrier, *, rtol=1e-12, seed=0)
¶
Assert that adapter and its fast paths agree with the reference adapter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adapter
|
NamedArrayAdapter
|
The adapter under test; it must be the one registered for
the carriers |
required |
make_carrier
|
MakeCarrier
|
|
required |
rtol
|
float
|
Tolerance for reductions; selections compare exactly. |
1e-12
|
seed
|
int
|
For the random data. |
0
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
On the first mismatch, naming the function and the carrier type. |
Source code in packages/pipekit-array/src/pipekit_array/testing.py
check_kernel_op(op, calls, *, rtol=1e-06)
¶
Assert that one KernelOp agrees across carrier families (PK-K1, PK-K4).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
KernelOp
|
The operator under test. |
required |
calls
|
Sequence[Any]
|
The inputs of each call, one entry per carrier family: a carrier for a one-input kernel, or a tuple of inputs. The first entry is the reference. |
required |
rtol
|
float
|
Relative tolerance on the outputs' values. |
1e-06
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
Outputs differ in dims or values, or a one-input
kernel's output dims differ from |
Source code in packages/pipekit-array/src/pipekit_array/testing.py
values_of(x)
¶
A named carrier's data on the host, computing it if lazy.