Skip to content

Gather, scatter & indexing

XTensor methods that select, split, or scatter along named dimensions — coordinate-aware sel/isel/interp, PyTorch's gather/scatter family, and slicing/splitting.

Bases: ExtendedTensor

A tensor with named dimensions and, optionally, per-dimension coordinate labels -- an xarray-like DataArray over a live torch.Tensor.

  • Dimensions are named through names (self-managed in _axis_names, independent of PyTorch's experimental builtin named-tensor feature, so the class works even where that API has been removed).
  • Coordinates label the positions along a named dimension. They live in coords -- a mapping dim name -> labels -- keyed by dimension name, so they follow their dimension through reshaping/reordering with no positional bookkeeping. A labelled dimension must be named.
  • Axis descriptors may enrich a name with extra fields -- any custom key you like (type is the OME-NGFF convention shown in examples; orientation is the one field with built-in behaviour) -- passed as a dict in place of a bare name ({"name": "x", "type": "space"}). names stays the ergonomic view (bare names); axes returns the full descriptors. The extra fields live in _axis_meta, keyed by dimension name, so they follow the dimension like coordinates do.

Select by label with sel, by integer position with isel, or reach a single label by attribute (x.red).

Methods:

sel

sel(*indexers_positional: Mapping, mode: Optional[str] = None, tolerance: Any = None, method: Optional[str] = None, **indexers_kwargs: Any) -> Self

Select by coordinate label (or numeric value) along named dims.

x.sel(channel="red") selects the position whose label is "red". A list of labels selects several positions; a single label drops the dimension (like integer indexing). For structured coordinates, a str matches a label's "name", and a dict queries the labels' fields (x.sel(channel={"type": "signal"})), keeping the axis and selecting every match.

On a numeric coordinate, the selector is a value (x.sel(t="2s")). mode chooses which tick an inexact value snaps to:

  • "round" (default) — the nearest tick by value;
  • "floor" / "ceil" — the largest tick <= / smallest tick >= the value (value space, robust to a descending coordinate);
  • "prev" / "next" — the neighbouring tick at the lower / higher index (tick order; needs a monotonic coordinate).

tolerance (a value in the position unit) caps the allowed gap. A bare .sel(t=v) is exact (tolerance=0); passing a mode implies an unbounded snap unless a tolerance is given.

A slice(lo, hi) on a numeric coordinate is a value range, unit-aware, resolving to a contiguous integer slice — half-open like ordinary Python indexing (lo <= value < hi), not xarray's inclusive-both-ends convention (see the "Differences from xarray" guide). Bounds are compared numerically regardless of order or of the coordinate's own direction: t=slice(1, 5) and t=slice(5, 1) select the same range. A one-sided range keeps the bound in the slot it was given (slice(1, None) -> value >= 1; slice(None, 5) -> value < 5); an out-of-range or empty result is a well-formed empty axis, not an error. slice.step is not supported (mode/tolerance don't apply to a range either).

A joint query over several dims sharing one coordinate (e.g. a lat/lon pair that together locate a point on a 2-D grid) picks all of those dims' positions in one shot: pass a value for every coordinate name that spans the same dims (x.sel(lat=52.1, lon=4.3)) — no dedicated syntax, ordinary keyword arguments that happen to share dims are recognised as one joint system. Only a square query is supported (exactly one coordinate value per spanned dim); an under- or over-determined query raises rather than guessing. Only mode="round" (the default) applies to a joint query — floor/ceil/prev/next have no well-defined meaning across several coupled dims at once. tolerance still applies, per queried coordinate name (a bare query is exact by default), checked against the chosen position's own value.

On an irregular grid coordinate (an explicit multi-dim array with no regular spacing, e.g. an irregular satellite-swath lat/lon), a joint query works the same way — one value per coordinate name spanning the same dims — but resolves to the single nearest grid point across the queried coordinates' raw magnitudes. Mixing coordinates with very different units (degrees and metres, say) weighs the nearer one more heavily, same as any unnormalised distance always does. Only a single point is supported per call, not a vectorized query over many points at once. tolerance/mode/method behave the same as the joint case above (only the default "nearest" mode applies; a gap over tolerance raises).

Pass indexers as an explicit mapping (x.sel({"mode": "red"})) instead of keyword arguments when a dim's name collides with one of sel's own keyword parameters (mode, tolerance, method) — xarray's own escape hatch for exactly this, since a keyword argument matching one of those names is always bound to the parameter, never reaching the indexers. Passing both raises.

isel

isel(**indexers: Any) -> Self

Select by integer position along named dimensions.

x.isel(row=0, col=slice(1, 3)) indexes row at position 0 and col at positions 1..2, leaving the other axes untouched.

interp

interp(*indexers_positional: Mapping, method: Any = 'linear', bound: Any = None, extrapolate: Any = None, name: Optional[str] = None, **indexers_kwargs: Any) -> Self

Interpolate onto new coordinate values along named dims.

Where sel picks existing positions, interp computes values at arbitrary positions of a numeric coordinate, the xarray way:

x.interp(t=2.5)                   # one point -> drops the axis
x.interp(t=[0.0, 0.5, 1.0])       # several  -> keeps the axis
x.interp(t="2.5s")                # unitful (backend converts)
x.interp(t=q, method="cubic")     # a query tensor (grads flow)

method is the interpolation order -- "nearest" (built in) or a higher order ("linear" (default), "quadratic", "cubic", or an int), which needs the optional fiery.interpol backend (pip install fiery-xtensor[interp]). An out-of-range query follows bound (default: the interp_bound option -- "replicate" clamps to the edge) and extrapolate (default: the interp_extrapolate option); both can be set with set_options.

A scalar query drops the axis (like sel); a list/tensor keeps it, its coordinate becoming the queried positions. A regular (evenly-spaced) coordinate supports every method; an irregular (explicit values) one only supports "nearest"/"linear", both exact, since the map between value space and index space is locally linear between two bracketing ticks. A higher order needs a true non-uniform spline in value space, which isn't currently supported for an irregular coordinate.

A joint query over several dims that share one coordinate (e.g. a lat/lon pair spanning several dims at once) resolves to a fractional position -- never rounded -- across all of them at once, then interpolates in that many dimensions together (falling back to a built-in nearest gather for method="nearest", no extra backend needed). A query with every name given as a scalar is a single point: all the spanned dims drop, like the 1-D scalar case above. Any name given as a list/tensor makes it "many": every name's query broadcasts to a common length N, and the spanned dims collapse into one new axis of N sampled points -- not an outer-product grid, since the dims are coupled and you can't vary one queried name without moving through every spanned dim at once (mirroring xarray's own vectorized/pointwise-indexing convention for a value-based query on a multi-dim coordinate). The new axis is named name if given, else the shared name of any query that is itself a named 1-D XTensor -- x.interp(lat=XTensor([...], names=("pts",)), lon=[...]) needs no name= at all, mirroring how xarray derives the result's new dimension from the indexer arrays' own shared dim name -- else unnamed (matching xstack's convention for a brand-new axis with nothing to infer from). When a name is resolved, the axis carries every queried name's own sampled values as a riding coordinate -- an unnamed axis can't be keyed, so it has none. Only one such joint group is supported per call; call interp again for a second group.

Pass indexers as an explicit mapping (x.interp({"method": 5.0})) instead of keyword arguments when a dim's name collides with one of interp's own keyword parameters (method, bound, extrapolate, name) -- xarray's own escape hatch for exactly this, since a keyword argument matching one of those names is always bound to the parameter, never reaching the indexers. Passing both raises.

gather

gather(*args, **kwargs) -> tx.Any

Name-aware torch.gather: behaves like torch.gather, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.gather for the full numerical behaviour.

index_add

index_add(*args, **kwargs) -> tx.Any

Name-aware torch.index_add: behaves like torch.index_add, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_add for the full numerical behaviour.

index_copy

index_copy(*args, **kwargs) -> tx.Any

Name-aware torch.index_copy: behaves like torch.index_copy, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_copy for the full numerical behaviour.

index_fill

index_fill(*args, **kwargs) -> tx.Any

Name-aware torch.index_fill: behaves like torch.index_fill, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_fill for the full numerical behaviour.

index_select

index_select(*args, **kwargs) -> tx.Any

Name-aware torch.index_select: behaves like torch.index_select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_select for the full numerical behaviour.

masked_select

masked_select(*args, **kwargs) -> tx.Any

Name-aware torch.masked_select: behaves like torch.masked_select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.masked_select for the full numerical behaviour.

nonzero

nonzero(*args, **kwargs) -> tx.Any

Name-aware torch.nonzero: behaves like torch.nonzero, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.nonzero for the full numerical behaviour.

scatter

scatter(*args, **kwargs) -> tx.Any

Name-aware torch.scatter: behaves like torch.scatter, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.scatter for the full numerical behaviour.

scatter_add

scatter_add(*args, **kwargs) -> tx.Any

Name-aware torch.scatter_add: behaves like torch.scatter_add, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.scatter_add for the full numerical behaviour.

take_along_dim

take_along_dim(*args, **kwargs) -> tx.Any

Name-aware torch.take_along_dim: behaves like torch.take_along_dim, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.take_along_dim for the full numerical behaviour.

where

where(*args, **kwargs) -> tx.Any

Name-aware torch.where: behaves like torch.where, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.where for the full numerical behaviour.

chunk

chunk(*args, **kwargs) -> tuple

Name-aware torch.chunk: behaves like torch.chunk, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.chunk for the full numerical behaviour.

flip

flip(*args, **kwargs) -> XTensor

Name-aware torch.flip: behaves like torch.flip, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.flip for the full numerical behaviour.

narrow

narrow(*args, **kwargs) -> tx.Any

Name-aware torch.narrow: behaves like torch.narrow, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.narrow for the full numerical behaviour.

roll

roll(*args, **kwargs) -> XTensor

Name-aware torch.roll: behaves like torch.roll, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.roll for the full numerical behaviour.

select

select(*args, **kwargs) -> tx.Any

Name-aware torch.select: behaves like torch.select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.select for the full numerical behaviour.

split

split(*args, **kwargs) -> tuple[torch.Tensor, ...]

Name-aware torch.split: behaves like torch.split, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.split for the full numerical behaviour.

unbind

unbind(*args, **kwargs) -> tuple

Name-aware torch.unbind: behaves like torch.unbind, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.unbind for the full numerical behaviour.