Skip to content

Commit dc2e279

Browse files
d-v-bclaude
andcommitted
fix(zarr-indexing): adapt the execution prototype to the trimmed planner
The planner now rejects diagonals with ValueError and has no shared projection walk, so execute_transform factors the plan up front and lets that rejection surface at construction instead of on first iteration. _axis_plan reuses chunk_resolution's data-extent helper rather than carrying a copy. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent ee70f57 commit dc2e279

3 files changed

Lines changed: 17 additions & 28 deletions

File tree

packages/zarr-indexing/src/zarr_indexing/_axis_plan.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, NamedTuple
66

77
from zarr_indexing._affine import checked_affine
8+
from zarr_indexing.chunk_resolution import _data_size # pyright: ignore[reportPrivateUsage]
89

910
if TYPE_CHECKING:
1011
from collections.abc import Iterator
@@ -21,11 +22,6 @@ class AxisRun(NamedTuple):
2122
position: int
2223

2324

24-
def data_size(grid: DimensionGridLike, chunk: int) -> int:
25-
method = getattr(grid, "data_size", None)
26-
return grid.chunk_size(chunk) if method is None else int(method(chunk))
27-
28-
2925
def axis_runs(start: int, stride: int, nitems: int, grid: DimensionGridLike) -> Iterator[AxisRun]:
3026
"""Intersect an affine request with chunks, in request traversal order.
3127
@@ -43,7 +39,7 @@ def axis_runs(start: int, stride: int, nitems: int, grid: DimensionGridLike) ->
4339
chunk = grid.index_to_chunk(coordinate)
4440
offset = grid.chunk_offset(chunk)
4541
local = coordinate - offset
46-
extent = data_size(grid, chunk)
42+
extent = _data_size(grid, chunk)
4743
if stride == 0:
4844
count = nitems
4945
else:

packages/zarr-indexing/src/zarr_indexing/_execution.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@
1818
from zarr_indexing._axis_plan import axis_runs
1919
from zarr_indexing._selector import as_scalar_index
2020
from zarr_indexing.boundary import split_scalar_axes
21-
from zarr_indexing.chunk_resolution import (
22-
ChunkPlan,
23-
IndexedSet,
24-
_shared_input_axis, # pyright: ignore[reportPrivateUsage]
25-
plan_chunks,
26-
)
21+
from zarr_indexing.chunk_resolution import ChunkPlan, IndexedSet, plan_chunks
2722
from zarr_indexing.errors import BoundsCheckError
2823
from zarr_indexing.grid import DimensionGridLike, RegularDimensionGridLike
2924
from zarr_indexing.output_map import ArrayMap, ConstantMap, DimensionMap
@@ -375,20 +370,18 @@ def execute_transform(
375370
return _with_policy(sorted_plan, access, "snapshot", conflicts)
376371
_validate_storage_bounds(transform, grids)
377372
plan = plan_chunks(transform, grids)
378-
# Prepare factored array grouping once. Affine diagonals intentionally use
379-
# ChunkPlan's shared projection path rather than per-output-axis tables.
373+
# Factor the plan once, up front: a transform the planner cannot factor
374+
# (a diagonal) is rejected here rather than on first iteration.
375+
partition = plan.partition()
380376
if (
381377
any(isinstance(m, ArrayMap) for m in transform.output)
382-
and _shared_input_axis(transform) is None
378+
and not partition.sets
379+
and all(bool((joint.chunk_start >= 0).all()) for joint in partition.joint_sets)
383380
):
384-
partition = plan.partition()
385-
if not partition.sets and all(
386-
bool((joint.chunk_start >= 0).all()) for joint in partition.joint_sets
387-
):
388-
# Column arithmetic is checked once by JointSet.local; nonnegative
389-
# chunk origins make its final local subtraction safe in intp.
390-
work = _ComponentWork(plan, tuple(joint.local for joint in partition.joint_sets))
391-
return _with_policy(ExecutionPlan(domain.shape, work), access, "snapshot", conflicts)
381+
# Column arithmetic is checked once by JointSet.local; nonnegative
382+
# chunk origins make its final local subtraction safe in intp.
383+
work = _ComponentWork(plan, tuple(joint.local for joint in partition.joint_sets))
384+
return _with_policy(ExecutionPlan(domain.shape, work), access, "snapshot", conflicts)
392385
return _with_policy(ExecutionPlan(domain.shape, plan), access, "snapshot", conflicts)
393386

394387

packages/zarr-indexing/tests/test_execution.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,17 @@ def test_scalar_coordinate_consumer_preserves_value_shape(consumer: Any) -> None
234234

235235

236236
@pytest.mark.parametrize("access", ["read", "write"])
237-
def test_diagonal_with_independent_gather_is_preparable(access: Any) -> None:
237+
def test_diagonal_is_rejected_before_iteration(access: Any) -> None:
238238
from zarr_indexing import ArrayMap
239239

240240
transform = IndexTransform(
241241
IndexDomain.from_shape((3, 2)),
242242
(DimensionMap(0), DimensionMap(0), ArrayMap(np.array([[1, 0]]))),
243243
)
244-
plan = execute_transform(
245-
transform, dimension_grids_from_chunks((2, 2, 2), (3, 3, 2)), access=access
246-
)
247-
assert len(list(plan)) == 2
244+
with pytest.raises(ValueError, match="diagonal"):
245+
execute_transform(
246+
transform, dimension_grids_from_chunks((2, 2, 2), (3, 3, 2)), access=access
247+
)
248248

249249

250250
def test_last_write_lowering_removes_duplicate_destinations() -> None:

0 commit comments

Comments
 (0)