3030
3131The part view's transform directly addresses its raw wrapped array. The paired
3232projection deliberately retains the chunk-local frame. Parent materialization
33- passes both in `ReadContext`, as does independent `part.result()` execution.
34- Calling `part.view.result()` resolves the general view without its partition
35- record and therefore uses `projection=None`. Use ` part.result()` when scheduling
36- partition reads .
33+ passes both in `ReadContext`. Every view retains the source's partitioning and
34+ plans its own reads, including views obtained from partitions. Calling
35+ ` part.view. result()` therefore supplies the reader with the selected chunk's
36+ projection as well. Output placement is relative to the view being executed .
3737
3838The partitioning is discovered from the wrapped array at construction — first
3939`read_chunk_sizes` (zarr's clipped per-axis sizes, sharding-aware), then
@@ -406,8 +406,8 @@ class Partition:
406406
407407 Yielded by [`LazyArray.parts`][zarr_indexing.lazy_array.LazyArray.parts].
408408 The parts of a view tile it exactly and disjointly: assembling every
409- `part.result()` at its `out_selection` reproduces the whole view's
410- `result()`, with the same reader context on both execution paths . Parts can be
409+ `part.view. result()` at its `out_selection` reproduces the whole view's
410+ `result()`. Each view plans its reads using the retained source grid . Parts can be
411411 resolved concurrently when the source and reader permit it.
412412 Derived parts retain the same reader object; a shared stateful reader owns
413413 synchronization for concurrent calls.
@@ -448,12 +448,12 @@ class Partition:
448448 view
449449 A `LazyArray` covering exactly the cells of the view that live in this
450450 box. Its transform directly addresses its raw wrapped `array`; only the
451- projection's `chunk_transform` is chunk-local. Use `Partition.result()` to read
452- with this partition's projection through the selected reader . Named `view` rather than
451+ projection's `chunk_transform` is chunk-local. The view retains the source
452+ grid and reader and can be read or indexed like any other view . Named `view` rather than
453453 `array` because `LazyArray.array` is the opposite thing — the raw
454454 wrapped source — and the two sat next to each other meaning inverses.
455455 out_selection
456- Where `Partition .result()` belongs in an array of the whole view's shape — a
456+ Where `view .result()` belongs in an array of the whole view's shape — a
457457 NumPy index tuple with one entry per dimension of the view, usable
458458 directly as `out[part.out_selection] = ...`.
459459 is_complete
@@ -472,7 +472,7 @@ class Partition:
472472 >>> view = LazyArray.from_numpy(source).with_parts((2, 2))
473473 >>> out = np.empty(view.shape, dtype=view.dtype)
474474 >>> for part in view.parts():
475- ... out[part.out_selection] = part.result()
475+ ... out[part.out_selection] = part.view. result()
476476 >>> bool((out == source).all())
477477 True
478478 """
@@ -493,34 +493,6 @@ def is_complete(self) -> bool:
493493 """Whether the projection proves it covers the entire selected cell."""
494494 return self .projection .coverage == "full"
495495
496- def result (self ) -> Any :
497- """Materialize this partition with its existing projection.
498-
499- Returns
500- -------
501- numpy.ndarray
502- The selected values in fresh system memory, with this part's view
503- shape and dtype. Assign the result at `out_selection` to assemble
504- the parent view. The reader receives the same global transform and
505- chunk projection as in parent materialization.
506-
507- Notes
508- -----
509- This uses the part's reader and source without replanning. Backend
510- exceptions propagate unchanged. Concurrent calls require a source and
511- reader that support concurrent access.
512- """
513- # Share the view allocator so independent reads preserve masked-array output.
514- out = self .view ._output_buffer (self .view .shape ) # pyright: ignore[reportPrivateUsage]
515- if math .prod (self .view .shape ) != 0 :
516- _invoke_reader (
517- self .view .reader ,
518- self .view .array ,
519- ReadContext (self .view .transform , self .projection ),
520- out ,
521- )
522- return out
523-
524496
525497def _validate_prepared_parts (parts : Sequence [Partition ], out_shape : tuple [int , ...]) -> None :
526498 """Require `parts` to address every output cell exactly once.
@@ -598,7 +570,7 @@ class LazyArray:
598570 [ 8, 10]])
599571 """
600572
601- __slots__ = ("_array" , "_part_owner" , "_parts" , "_reader" , "_transform" , "_window" )
573+ __slots__ = ("_array" , "_part_owner" , "_parts" , "_reader" , "_transform" )
602574
603575 def __init__ (self , array : _WrappedArray ) -> None :
604576 """Wrap `array` without reading it; parameters are documented on the class.
@@ -619,7 +591,6 @@ def __init__(self, array: _WrappedArray) -> None:
619591 )
620592 shape = tuple (int (s ) for s in array .shape )
621593 self ._array = array
622- self ._window : tuple [slice , ...] | None = None
623594 self ._transform = IndexTransform .from_shape (shape )
624595 self ._parts = _discover_parts (array , shape )
625596 self ._reader = basic_reader
@@ -640,7 +611,6 @@ def _derive(
640611 array : _WrappedArray ,
641612 transform : IndexTransform ,
642613 parts : tuple [DimensionGrid , ...] | None ,
643- window : tuple [slice , ...] | None ,
644614 reader : Reader ,
645615 ) -> LazyArray :
646616 """Build a wrapper sharing `array` but carrying a new transform or partitioning."""
@@ -650,17 +620,14 @@ def _derive(
650620 # view's first element is at position 0 whatever it was sliced from.
651621 view ._transform = transform .translate_domain_to ((0 ,) * transform .input_rank )
652622 view ._parts = parts
653- view ._window = window
654623 view ._reader = reader
655624 view ._part_owner = _PartOwner ()
656625 return view
657626
658627 @property
659628 def _base_shape (self ) -> tuple [int , ...]:
660629 """The shape of what this wrapper treats as its base array."""
661- if self ._window is None :
662- return tuple (int (s ) for s in self ._array .shape )
663- return tuple (s .stop - s .start for s in self ._window )
630+ return tuple (int (s ) for s in self ._array .shape )
664631
665632 # -- array-like surface -------------------------------------------------
666633
@@ -674,11 +641,8 @@ def base_shape(self) -> tuple[int, ...]:
674641 """The shape the partitioning is expressed in — not this view's shape.
675642
676643 `with_parts` and `with_parts_per_axis` describe boxes of the array being
677- read, not of the view reading it, so a narrowed view still partitions
678- the extents named here. For a part's own `array`, this is the part's
679- box, which is why the same call means different sizes there. Without
680- somewhere to read it, the frame in force could only be inferred from an
681- error message.
644+ read, not of the view reading it. All derived views, including partition
645+ views, retain the full source shape as their partitioning frame.
682646 """
683647 return self ._base_shape
684648
@@ -720,7 +684,6 @@ def with_reader(self, reader: Reader) -> LazyArray:
720684 self ._array ,
721685 self ._transform ,
722686 self ._parts ,
723- self ._window ,
724687 reader ,
725688 )
726689
@@ -949,9 +912,8 @@ def _part_entries(parts: Sequence[Any], method: str) -> tuple[Any, ...]:
949912 def unpartitioned (self ) -> LazyArray :
950913 """Return the same view, read in one pass.
951914
952- `result()` still allocates its owned output buffer first, then calls the
953- reader once with the whole projected transform. `parts()` still yields a
954- single part covering everything.
915+ The source is treated as a single grid cell. Nonempty reads still pass
916+ its projection to the reader; empty reads make no reader calls.
955917
956918 Returns
957919 -------
@@ -961,7 +923,7 @@ def unpartitioned(self) -> LazyArray:
961923 return self ._with_grids (None )
962924
963925 def _with_grids (self , grids : tuple [DimensionGrid , ...] | None ) -> LazyArray :
964- return LazyArray ._derive (self ._array , self ._transform , grids , self ._window , self . _reader )
926+ return LazyArray ._derive (self ._array , self ._transform , grids , self ._reader )
965927
966928 def parts (self ) -> Iterator [Partition ]:
967929 """Iterate the base partitioning, projected through this view.
@@ -972,7 +934,7 @@ def parts(self) -> Iterator[Partition]:
972934
973935 Yields one [`Partition`][zarr_indexing.lazy_array.Partition] per box the
974936 view actually touches. The parts tile the view exactly and disjointly,
975- and each can be resolved with `Partition .result()`: in another thread,
937+ and each view can be resolved with `part.view .result()`: in another thread,
976938 in another order, or not at all. Those views share this
977939 view's reader, and `LazyArray` does not serialize calls, so a stateful
978940 reader must synchronize its own mutable state.
@@ -993,48 +955,16 @@ def parts(self) -> Iterator[Partition]:
993955 >>> (part.base_coords, part.view.shape, part.is_complete)
994956 ((0, 0), (2, 1), False)
995957 """
996- base_shape = self ._base_shape
997- grids = self ._parts if self ._parts is not None else _whole_array_grids (base_shape )
998- rank = len (base_shape )
999-
1000- if self ._window is None :
1001- plan_transform = self ._transform
1002- else :
1003- plan_transform = self ._transform .translate (tuple (- item .start for item in self ._window ))
1004-
1005- for projection in plan_chunks (plan_transform , grids ):
1006- base_coords = projection .chunk_coords
1007- local = projection .chunk_transform
1008- origin = tuple (grid .chunk_offset (c ) for grid , c in zip (grids , base_coords , strict = True ))
1009- extent = tuple (grid .data_size (c ) for grid , c in zip (grids , base_coords , strict = True ))
1010- if origin == (0 ,) * rank and extent == base_shape :
1011- # The part is the whole base: lowering directly against the
1012- # source beats materializing a block that is the source.
1013- window = self ._window
1014- elif self ._window is None :
1015- window = tuple (slice (o , o + e ) for o , e in zip (origin , extent , strict = True ))
1016- else :
1017- window = tuple (
1018- slice (w .start + o , w .start + o + e )
1019- for w , o , e in zip (self ._window , origin , extent , strict = True )
1020- )
1021- # The global box, computed from the origin directly rather than from
1022- # `window`: a part covering the whole base carries no window (so
1023- # nothing is pre-materialized) but still sits somewhere concrete.
1024- if self ._window is None :
1025- global_origin = origin
1026- else :
1027- global_origin = tuple (
1028- w .start + o for w , o in zip (self ._window , origin , strict = True )
1029- )
958+ grids = self ._parts if self ._parts is not None else _whole_array_grids (self ._base_shape )
959+ for projection in plan_chunks (self ._transform , grids ):
960+ domain = projection .chunk_domain
1030961 yield Partition (
1031962 projection = projection ,
1032- box = tuple (( o , o + e ) for o , e in zip (global_origin , extent , strict = True )),
963+ box = tuple (zip (domain . inclusive_min , domain . exclusive_max , strict = True )),
1033964 view = LazyArray ._derive (
1034965 self ._array ,
1035- local .translate (global_origin ),
1036- None ,
1037- window ,
966+ projection .chunk_transform .translate (domain .inclusive_min ),
967+ self ._parts ,
1038968 self ._reader ,
1039969 ),
1040970 out_selection = _partition_out_selection (projection .cell_transform ),
@@ -1068,7 +998,7 @@ def _select(self, selection: Any, mode: SelectionMode) -> LazyArray:
1068998 composed = transform [literal ]
1069999 else :
10701000 composed = transform .select (literal , mode )
1071- return LazyArray ._derive (self ._array , composed , self ._parts , self ._window , self . _reader )
1001+ return LazyArray ._derive (self ._array , composed , self ._parts , self ._reader )
10721002
10731003 def __getitem__ (self , selection : Any ) -> Any :
10741004 """Read a basic selection eagerly, like `numpy.ndarray.__getitem__`.
@@ -1086,6 +1016,8 @@ def result(self, *, parts: Sequence[Partition] | None = None) -> Any:
10861016 partition is read through the selected reader directly into its
10871017 rectangular destination, or into an owned dense temporary before fancy
10881018 placement. Empty views allocate without reading the source.
1019+ Every reader call includes a chunk projection, even when the source is
1020+ treated as a single grid cell by `unpartitioned()`.
10891021
10901022 Parameters
10911023 ----------
@@ -1128,10 +1060,6 @@ def result(self, *, parts: Sequence[Partition] | None = None) -> Any:
11281060 if size == 0 :
11291061 return out
11301062
1131- if prepared_parts is None and self ._parts is None :
1132- _invoke_reader (self ._reader , self ._array , ReadContext (self ._transform ), out )
1133- return out
1134-
11351063 written = 0
11361064 selected_parts = self .parts () if prepared_parts is None else prepared_parts
11371065 for part in selected_parts :
0 commit comments