zarr_indexing.output_map
zarr_indexing.output_map ¶
Output index maps — three ordered mappings to integer coordinates.
An output index map describes how input cells address one dimension of the output space. Its coordinates form an ordered, duplicate-preserving sequence aligned with the input domain, never a mathematical set. Three representations cover the cases that arise in practice:
ConstantMap(offset=5)— every request cell maps to coordinate5DimensionMap(input_dimension=0, offset=3, stride=2)over input[0, 5)— the ordered arithmetic progression[3, 5, 7, 9, 11]ArrayMap(index_array=[5, 1, 1])— the explicit sequence[5, 1, 1], preserving both order and the repeated coordinate
Every output map participates in two operations defined on IndexTransform,
which provides the input-domain context these maps lack:
- intersect — retain mapped cells whose coordinates lie within a range
(e.g., a chunk), without changing their order or multiplicity.
Restricting
[3, 5, 5, 9]to[4, 8)produces[5, 5]. - translate — shift every coordinate by a constant (e.g., make chunk-local).
Translating
[5, 5, 7]by-4produces[1, 1, 3].
These two operations are the foundation of chunk resolution: for each chunk, intersect the map with the chunk's range, then translate to chunk-local coordinates.
The three types exist because they trade off generality for efficiency:
ConstantMap: O(1) storage, O(1) intersectionDimensionMap: O(1) storage, O(1) intersection (analytical)ArrayMap: O(n) storage, O(n) intersection (must scan the array)
Collapsing everything to ArrayMap would be correct but wasteful — a
billion-element slice would materialize a billion coordinates just to group
them by chunk, when DimensionMap does it with three integers.
ArrayMap
dataclass
¶
An explicit ordered, duplicate-preserving coordinate mapping.
Maps each input position i to offset + stride * index_array[i].
Index-array order and repeated entries are semantic and remain present in
the result. Arises from fancy indexing (e.g., arr[[5, 1, 1]] or boolean
masks).
A map used in a transform must have its full input rank:
index_array has the enclosing domain's rank, sized
fully on the axes it varies over and singleton (size 1) elsewhere. The
shape is the single source of truth for what the map depends on — its
dependency axes are exactly its axes of size greater than one (see
_array_map_dependency_axes) — and it distinguishes the two
flavors of multi-array fancy indexing:
- orthogonal (
oindex): each array varies along a single, distinct axis (all others singleton); the result is their outer product. - vectorized (
vindex): the arrays are correlated and share the same non-singleton (broadcast) axes; the result is a pointwise scatter.
A map holding exactly one coordinate carries no shape to read a dependency
from, and none is needed: it is the ConstantMap it equals, and the
selection layer builds that instead (see array_map_or_constant). A
hand-built all-singleton ArrayMap is still a valid value; resolution
classifies it with the correlated maps and reads it pointwise.
Examples:
The fancy selection arr[[5, 1, 1]] reads coordinate 5, then 1, then 1
— order and the duplicate preserved, exactly as NumPy fancy indexing:
>>> m = ArrayMap(index_array=np.array([5, 1, 1]))
>>> [m.offset + m.stride * c for c in m.index_array.tolist()]
[5, 1, 1]
>>> np.arange(10)[[5, 1, 1]].tolist()
[5, 1, 1]
Source code in src/zarr_indexing/output_map.py
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 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
dependency_axes
property
¶
Structural dependency axes: axes of size greater than one.
Axes of size greater than one are reported, regardless of coordinate values or a zero-size axis elsewhere. Whether the whole transform is orthogonal also depends on how other maps use these axes; a single map's shape does not establish independence.
Examples:
dependent_axis
property
¶
dependent_axis: int | None
Return the single input axis an orthogonal ArrayMap varies over.
This is the array's one non-singleton axis, read from the shape — the
single source of truth for what a map depends on. The selection layer
collapses a single-coordinate map to a ConstantMap
(array_map_or_constant), so a non-empty map built by this package always
has at least one dependency axis.
Returns:
-
int or None–The axis the map varies over, or
Nonewhen it varies over no input axis of size greater than one, such as an all-singleton map.Noneis a valid result, not an error; such maps resolve through the pointwise (general) path.
Raises:
-
ValueError–If the map varies over more than one axis, which makes it correlated rather than orthogonal.
Examples:
An oindex selection on axis 1 of a rank-2 transform stores its
coordinates full-sized on axis 1 and singleton on axis 0, so the
dependency axis is read straight off the shape:
index_array
instance-attribute
¶
Explicit coordinates at the enclosing transform's full input rank; order and duplicates are semantic. Its non-singleton axes are the map's dependency axes.
offset
class-attribute
instance-attribute
¶
offset: int = 0
Constant term of the affine adjustment: the output coordinate is offset + stride * index_array[i].
stride
class-attribute
instance-attribute
¶
stride: int = 1
Multiplier applied to each index_array value before offset is added.
__eq__ ¶
Compare offset, stride, array shape, and index values.
Return a scalar boolean for another ArrayMap and NotImplemented for other types.
Source code in src/zarr_indexing/output_map.py
__hash__ ¶
__hash__() -> int
Hash the offset, stride, array shape, and index bytes.
The immutable coordinate snapshot keeps the hash stable, and equal maps have equal hashes.
Source code in src/zarr_indexing/output_map.py
__init__ ¶
__post_init__ ¶
Own an immutable snapshot of the integer index coordinates.
The snapshot is backed by immutable bytes, so callers cannot modify it or re-enable its WRITEABLE flag. Changes to the supplied array do not change the map's coordinates or hash. This freezes the coordinate mapping, not the source values read through it.
Source code in src/zarr_indexing/output_map.py
__reduce__ ¶
Reconstruct through __init__, preserving the ownership invariant.
to_json ¶
to_json() -> OutputIndexMapJSON
Convert to the canonical wire form, collapsing a degenerate map.
A map holding exactly one coordinate, or none at all, is emitted as a
constant map — see the module note on the wire format in
zarr_indexing.json. Both are degenerate: the
first selects one coordinate whatever the input, and the second names
no cell and can only be empty because an input dimension is, so the
emptiness travels in the domain instead.
Examples:
>>> ArrayMap(np.array([[4], [1], [1]])).to_json()["index_array"]
[[4], [1], [1]]
>>> ArrayMap(np.array([7])).to_json() # degenerate: one coordinate
{'offset': 7}
Source code in src/zarr_indexing/output_map.py
ConstantMap
dataclass
¶
A constant output-coordinate mapping.
Every input cell maps to offset. Arises from integer indexing (e.g.,
arr[5] fixes one dimension to coordinate 5).
Examples:
Every input cell maps to the same output coordinate, like broadcasting
coordinate 5 with np.broadcast_to(5, (3,)). Repeated fancy indices can
also describe these coordinates, using an explicit list:
>>> from zarr_indexing.domain import IndexDomain
>>> from zarr_indexing.transform import IndexTransform
>>> domain = IndexDomain.from_shape((3,))
>>> t = IndexTransform(domain=domain, output=(ConstantMap(offset=5),))
>>> t.apply((0,)), t.apply((1,)), t.apply((2,))
((5,), (5,), (5,))
Source code in src/zarr_indexing/output_map.py
offset
class-attribute
instance-attribute
¶
offset: int = 0
The fixed output coordinate every input cell maps to.
to_json ¶
to_json() -> OutputIndexMapJSON
Convert to the canonical wire form: the bare constant map.
Examples:
DimensionMap
dataclass
¶
An ordered affine mapping to output coordinates.
Maps each input coordinate i to offset + stride * i, where the input
range comes from the enclosing IndexTransform's domain. Arises from slice
indexing (e.g., arr[2:10:3] gives offset=2, stride=3).
Examples:
The slice arr[2:11:3] reads coordinates 2, 5, 8 — the rule
offset + stride * i with offset=2, stride=3:
>>> m = DimensionMap(input_dimension=0, offset=2, stride=3)
>>> [m.offset + m.stride * i for i in range(3)]
[2, 5, 8]
>>> np.arange(11)[2:11:3].tolist()
[2, 5, 8]
Source code in src/zarr_indexing/output_map.py
input_dimension
instance-attribute
¶
input_dimension: int
The input (domain) dimension whose coordinate this map reads.
offset
class-attribute
instance-attribute
¶
offset: int = 0
The output coordinate that input coordinate 0 maps to.
stride
class-attribute
instance-attribute
¶
stride: int = 1
The output-coordinate step per unit input step; negative walks backward, zero repeats offset.
to_json ¶
to_json() -> OutputIndexMapJSON
Convert to the canonical wire form: the single_input_dimension map.
Examples:
>>> DimensionMap(input_dimension=1, offset=0, stride=2).to_json()
{'offset': 0, 'stride': 2, 'input_dimension': 1}
Source code in src/zarr_indexing/output_map.py
array_map_or_constant ¶
array_map_or_constant(
index_array: NDArray[integer[Any]],
offset: int = 0,
stride: int = 1,
) -> ArrayMap | ConstantMap
An ArrayMap, collapsed to the ConstantMap it equals when it can be.
An index array holding exactly one coordinate maps every input cell to the
same place; representing it as a lookup table would leave a map whose shape
names no dependency axis, the one form the shape-derived classifier cannot
read. The selection and composition layers build their array maps through
this helper so that a non-empty ArrayMap always varies over at least one
axis. An empty array stays an ArrayMap: it maps no cell at all, and the
emptiness lives in the domain that accompanies it.
Source code in src/zarr_indexing/output_map.py
output_index_map_from_json ¶
output_index_map_from_json(
data: OutputIndexMapJSON,
) -> OutputIndexMap
Construct the output map a canonical wire form names.
The wire form is structurally discriminated: the presence of index_array
selects an array map, input_dimension selects a dimension map, and
neither selects a constant map.
Examples:
>>> output_index_map_from_json({"offset": 5})
ConstantMap(offset=5)
>>> output_index_map_from_json({"offset": 0, "stride": 2, "input_dimension": 1})
DimensionMap(input_dimension=1, offset=0, stride=2)