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).
Freshly constructed maps are normalized to the full input rank of their
enclosing transform: 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 non-singleton axes (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
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 | |
dependency_axes
property
¶
Every input axis this map varies over: its non-singleton axes.
One axis means orthogonal, several mean correlated, and none means the map is degenerate — the shape is the single source of truth for all three.
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 at all — an empty map, or a hand-built all-singleton one.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__ ¶
Value equality, comparing index arrays element-wise.
The generated __eq__ compares them with ==, whose result for two
arrays is an array — so asking whether two maps are equal raised
ValueError: the truth value of an array ... is ambiguous. frozen=True
reads as a promise that a value can be compared and hashed, and this is
what makes good on it.
Source code in src/zarr_indexing/output_map.py
__hash__ ¶
__hash__() -> int
Hashed by the array's contents, so equal maps hash alike.
The generated __hash__ hashed the ndarray itself, which is unhashable;
a map could therefore not go in a set, or key a cache.
Source code in src/zarr_indexing/output_map.py
__init__ ¶
__post_init__ ¶
Own the index array and expose it read-only.
A map is frozen, but the array inside it was not: reaching through a
view's transform to index_array[0] = 9 silently changed what the view
returned, in a package whose whole contract is that a view is a
description of a read and resolving it twice answers alike. Owning the
array also prevents the caller from changing the contents behind the
read-only view, which would invalidate this value object's hash.
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 — the NumPy analogy
is a broadcast (np.broadcast_to(5, (3,))), not an index:
>>> 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 a tagged union — index_array, then input_dimension,
else constant — so loading it dispatches to the right kind here rather
than on any one of them.
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)