zarr_indexing.json
zarr_indexing.json ¶
The canonical ndsel wire vocabulary, and the rules for lowering it.
This is the engine layer. Where messages.py is pure JSON→JSON and imposes
no array constraints, this module holds the JSON shapes a canonical ndsel body
takes (spec section 4.3, as produced by zarr_indexing.messages.normalize_ndsel)
together with the lowering rules that turn one into the numpy-backed engine
representation.
The conversions themselves are methods on the types, since each type owns
its one serialization: IndexTransform.to_json / from_json,
IndexDomain.to_json / from_json, and to_json on each output map kind,
with output_index_map_from_json in zarr_indexing.output_map dispatching the
wire's tagged union back to the right kind. This module is what they share.
Three engine constraints live here and only here:
- Finite bounds. An
IndexDomainaddresses a finite array, so a canonical body carrying a"-inf"/"+inf"bound cannot be lowered;from_jsonraises. - Implicit bounds lower by value. The
[n]-bracket implicit/explicit flag is a message-layer concern; the engine keeps only the integer value. - Integer
index_arraycontent. The message layer carriesindex_arrayverbatim (the spec defers its shape and type), so lowering is where a float, boolean or string array is rejected — as anNdselError, rather than truncating[0.9, 1.9]to cells 0 and 1 or leaking a raw NumPy error.
The index_array wire format (and the degenerate-collapse it documents)¶
ndsel and TensorStore both reject an output map that carries both
input_dimension and index_array; input_dimension belongs to affine
(single_input_dimension) maps. The in-memory ArrayMap matches: what a map
depends on is read from its full-rank array's shape (its non-singleton axes),
so there is nothing to reconstruct on load. On serialize (to_json):
- An all-singleton
index_array(size 1) selects a single coordinate regardless of input, so it is collapsed to aconstantmap{offset: offset + stride*value}. The size-1 input dimension stays in the domain, unconsumed — a valid transform. The selection layer already builds such maps asConstantMap(output_map.array_map_or_constant); this covers hand-built transforms. - An empty
index_array(size 0) collapses the same way, to{offset: 0}. It names no cell, and it can only be empty because an input dimension is — the full-rank invariant makes every axis either 1 or the domain's extent — so nothing is ever read through it and the emptiness is carried by the domain, which is emitted separately. TensorStore does the same:t[ts.d[0][[]]]isout[0] = 0, emitted as{}. Emitting the array instead would produce a document neither implementation could load, becausendarray.tolist()renders every empty array as[]once the leading axis is the zero-length one, and nested lists cannot spell the shape back —[[]]is(1, 0)and nothing spells(0, 1). - Non-degenerate
index_arraymaps are emitted with their array and bounds only.
IndexDomainJSON ¶
Bases: TypedDict
Canonical JSON representation of an IndexDomain.
Examples:
>>> doc: IndexDomainJSON = {
... "input_inclusive_min": [0],
... "input_exclusive_max": [4],
... "input_labels": ["x"],
... }
>>> from zarr_indexing import IndexDomain
>>> IndexDomain.from_json(doc).shape
(4,)
Source code in src/zarr_indexing/json.py
IndexTransformJSON ¶
Bases: TypedDict
Canonical JSON representation of an IndexTransform (spec section 4.3).
Examples:
>>> doc: IndexTransformJSON = {
... "input_rank": 1,
... "input_inclusive_min": [0],
... "input_exclusive_max": [2],
... "input_labels": [""],
... "output": [{"offset": 1, "stride": 2, "input_dimension": 0}],
... }
>>> from zarr_indexing import IndexTransform
>>> IndexTransform.from_json(doc).domain.shape
(2,)
Source code in src/zarr_indexing/json.py
input_exclusive_max
instance-attribute
¶
Per-dimension exclusive upper bounds; "+inf" is legal on the wire but cannot be lowered.
input_inclusive_min
instance-attribute
¶
Per-dimension lower bounds; "-inf" is legal on the wire but cannot be lowered.
input_labels
instance-attribute
¶
Per-dimension names; the empty string marks an unlabeled dimension.
input_rank
instance-attribute
¶
The number of input dimensions; the bounds and labels lists match it in length.
output
instance-attribute
¶
output: Required[list[OutputIndexMapJSON]]
One output map per output dimension.
OutputIndexMapJSON ¶
Bases: TypedDict
Canonical JSON representation of a single output index map.
Exactly one of three forms (distinguished by which fields are present):
{"offset": 5}— constant{"offset": 0, "stride": 1, "input_dimension": 0}— single_input_dimension{"offset": 0, "stride": 1, "index_array": [...], "index_array_bounds": ["-inf", "+inf"]}— index_array
Examples:
>>> from zarr_indexing import output_index_map_from_json
>>> constant: OutputIndexMapJSON = {"offset": 5}
>>> output_index_map_from_json(constant)
ConstantMap(offset=5)
>>> affine: OutputIndexMapJSON = {"offset": 0, "stride": 2, "input_dimension": 1}
>>> output_index_map_from_json(affine)
DimensionMap(input_dimension=1, offset=0, stride=2)
Source code in src/zarr_indexing/json.py
index_array
instance-attribute
¶
index_array: NestedIntList
Nested lists of output coordinates, one nesting level per input dimension.
index_array_bounds
instance-attribute
¶
index_array_bounds: list[IndexValueJSON]
Bounds the index_array values are promised to lie in; ["-inf", "+inf"] if unconstrained.