zarr_indexing.lazy_array
LazyArray.lazy[...] is metadata-only: every derived view keeps the same
reader and composes its transform without reading data. result() allocates
owned system memory, then calls that reader once for each projected part.
Rectangular parts write directly into their final slices; advanced placement
may first use an owned dense temporary. LazyArray(source) assumes only basic
indexing, while LazyArray.from_numpy(array) explicitly selects NumPy's
optimized reader.
The built-in readers lower through NumPy system memory and support sources whose basic reads can be converted there. They do not implicitly transfer device arrays; a device source needs an explicit custom reader that transfers into the supplied system-memory output. Derived views and parts share their reader and part views may be materialized concurrently, so stateful readers must synchronize their own mutable state.
Every public Partition.view.transform directly maps that view's zero-origin
coordinates into its raw Partition.view.array, including for non-first
partitions. Partition.projection.chunk_transform intentionally stays local to
the selected chunk. During materialization the reader receives both frames in
one ReadContext: the public global transform in context.transform and the
same local plan in context.projection.
zarr_indexing.lazy_array ¶
LazyArray — TensorStore-style lazy indexing over array-like sources.
LazyArray wraps a source with shape, dtype, and basic integer/slice
__getitem__, whose reads can be lowered through NumPy system memory. It adds
a .lazy accessor whose indexing operations build up an
IndexTransform instead of reading data:
view = LazyArray(source).lazy[10:50, ::2].lazy.oindex[[3, 1, 1], :]
view.shape # known without touching the data
values = view.result()
Nothing is read until result() (or __array__, or an eager __getitem__).
Every .lazy operation is metadata-only. Composition does not accumulate
layers: a view of a view is still a single transform and retains its reader.
Parts
A LazyArray carries a partitioning of the array it wraps: a grid of boxes
that a read is broken into. parts() walks those boxes as they fall through the
view, yielding a Partition per box. Its
paired projection describes the chunk-local read and where its cells land in the
request; view carries that partition's transform. result() allocates one
fresh output buffer, then reads each partition once through the selected reader
into the final buffer or an owned temporary for fancy placement.
The part view's transform directly addresses its raw wrapped array. The paired
projection deliberately retains the chunk-local frame; both travel together in
the ReadContext passed to the reader.
The partitioning is discovered from the wrapped array at construction — first
read_chunk_sizes (zarr's clipped per-axis sizes, sharding-aware), then
chunks, read as per-axis sizes if its entries are sequences and as a uniform
box shape if they are integers. Those attribute names belong to the wrapped
array; this API refers only to parts. An array that advertises neither gets a
single whole-array part, and resolving it reads the whole view through its
selected reader in one pass.
with_parts replaces the partitioning without touching the data or the view:
view.with_parts((64, 64)) # uniform boxes, tail clipped
view.with_parts_per_axis(((3, 3, 1),)) # explicit per-axis sizes
view.unpartitioned() # one whole-array part; resolve in one shot
Repartitioning changes how the read is divided, not what result() returns.
Parts that do not align with the source's own boxes are permitted and can be
useful (to bound peak memory, or to batch small reads); they cost extra I/O but
do not affect correctness.
Readers
Every wrapper carries a reader that owns the backend-specific request. The
transform answers which values? and is independent of the backend; the
reader answers how does this backend obtain them? and must preserve the
complete transform exactly. Readers do not define indexing semantics,
partitioning, scheduling, or result ownership. The conservative
LazyArray(source) uses basic_reader, which needs only basic slicing.
LazyArray.from_numpy(array) explicitly opts into numpy_reader for direct
NumPy indexing. with_reader() replaces the reader without reading or changing
the view metadata. The reader object is shared by all derived views and their
parts. Consumers may materialize part views concurrently; LazyArray does not
serialize calls, so a stateful reader must synchronize its own mutable state.
Both built-in readers lower through NumPy system memory. They do not implicitly transfer device arrays. A device source requires an explicit custom reader that performs any needed transfer into the supplied system-memory output buffer.
Boxes and queries
A selection is either rectangular — an interval and a stride per dimension,
which is what basic indexing composes to at any depth — or a query, an
explicit list of coordinates, which is what oindex, vindex, and masks
produce and which subsequent basic indexing cannot undo. is_box reports the
category and bounding_box() reports the storage region touched: the exact
interval per dimension for a box, a hull for a query. A box is only dense in
that interval when every entry of strides() is 1. The distinction is
structural rather than an optimization; the design
notes describe why it matters to consumers of a selection.
The positional dialect
Selections on LazyArray are positional, NumPy-style: index 0 is the first
element of the current view, -1 is the last, boolean masks must match the
view's shape, and every index is bounds-checked against the view.
This differs deliberately from zarr.Array.lazy[...], which exposes the
literal TensorStore dialect: a zarr view keeps the coordinate system of the
array it came from, so after v = arr.lazy[10:50] the first element of v is
v[10] and a negative index is out of bounds rather than counted from the end.
That dialect suits zarr, where a view's coordinates stay comparable with the
parent array's. LazyArray is a duck array and has to behave like the array it
wraps to be usable as a NumPy drop-in or as a dask source, so it re-zeroes its
coordinates on every view and uses positions. zarr_indexing.boundary performs
the translation between the two.
Two more NumPy rules the dialect keeps, in every mode:
- A scalar integer drops its axis. Any non-boolean object implementing Python's
SupportsIndexprotocol is accepted as one, including in slice bounds and steps; an__int__method alone is deliberately not enough. A scalar is a basic index wherever it appears, applied before any advanced index rather than broadcast against one. Solazy.oindex[0]has the shape ofx[0],lazy.oindex[0, [1, 2], :]meansx[0][numpy.ix_([1, 2], ...)], andlazy.oindex[0, 1, 2]andlazy.vindex[0, 1, 2]are both zero-rank. Use a length-1 list to keep an axis. - Advanced indices are placed as NumPy places them. For a
vindexselection that leaves some axes unindexed, the gathered dimensions sit where the coordinate arrays sat when those arrays are adjacent, and lead when a slice separates them — solazy.vindex[..., i, j]has shape(x.shape[0], *broadcast), matchingx[..., i, j].
Materializing on fallback
LazyArray implements __array__ but deliberately implements neither
__array_ufunc__ nor __array_function__. A NumPy function given a view
therefore materializes the whole thing through
__array__ and works on the resulting array: numpy.sum(view),
numpy.add(view, 1) and numpy.stack([view, view]) all do, and so does
numpy.ones(view.shape) + view, where the ndarray on the left dispatches.
Python's arithmetic operators do not: view + 1 raises TypeError, because
the wrapper defines no arithmetic dunders and an int has nothing to dispatch
to. Both facts follow from the same intent — laziness here applies to indexing,
not to building a deferred compute graph — and a LazyArray is not a drop-in
for arithmetic on a large array either way. Use .lazy[...] to narrow the view
first, or pass the wrapper to dask.array.from_array so that dask owns the
compute graph.
Ownership
result() always allocates fresh system memory before reading through the
selected reader. A numpy.ma source keeps its mask by receiving a masked
output buffer; other source-specific array types do not survive materializing.
ArrayLike ¶
Bases: Protocol
The surface LazyArray needs from the array it wraps.
Source code in src/zarr_indexing/lazy_array.py
LazyArray ¶
A lazily-indexable view over a system-memory/basic-indexing source.
Wrapping neither copies nor reads the wrapped array at construction time.
Indexing through .lazy composes an IndexTransform and returns another
LazyArray; result() materializes.
Selections use the positional NumPy dialect and reads are broken up
along a partitioning discovered from the wrapped array. Every derived
view retains its reader; that reader receives the complete projected
transform once per part. See the module docstring, which also covers how the
dialect differs from zarr.Array.lazy and why every non-indexing NumPy
operation materializes the view.
This wrapper describes reads. It defines no __setitem__, so
assigning into a view raises TypeError. Writing belongs to the
consumer: plan the selection with
plan_chunks and own the
read-modify-write, since chunk atomicity and concurrent-writer policy are
the backend's to decide, not an indexing plan's.
Parameters:
-
array(_WrappedArray) –The array to wrap. It must expose
shape,dtype, and__getitem__with basic (integer/slice) indexing;__setitem__is not required, so a read-only source wraps as well as a writable one. Its partitioning, if it advertises one, is discovered here; usewith_partsto choose a different one. This conservative constructor selectsbasic_reader; usefrom_numpyfor a NumPy array orwith_readerto select another backend adapter.
Examples:
>>> import numpy as np
>>> source = np.arange(12).reshape(3, 4)
>>> view = LazyArray.from_numpy(source).with_parts((2, 2)).lazy[1:, ::2]
>>> view.shape
(2, 2)
>>> view.result()
array([[ 4, 6],
[ 8, 10]])
Source code in src/zarr_indexing/lazy_array.py
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 | |
__slots__
class-attribute
instance-attribute
¶
base_shape
property
¶
The shape the partitioning is expressed in — not this view's shape.
with_parts and with_parts_per_axis describe boxes of the array being
read, not of the view reading it, so a narrowed view still partitions
the extents named here. For a part's own array, this is the part's
box, which is why the same call means different sizes there. Without
somewhere to read it, the frame in force could only be inferred from an
error message.
is_box
property
¶
is_box: bool
Whether this view selects a rectangular region rather than a point list.
True exactly when the composed transform's output maps are all
ConstantMap or DimensionMap — no ArrayMap. Such a selection is
affine and monotone along every axis, so it is described completely by
an interval and a stride per dimension:
bounding_box
together with
strides. Basic indexing,
at any depth of composition, stays a box; one oindex, vindex, or
mask anywhere in the chain makes the selection a query permanently.
A box is dense — every cell of its bounding box selected — only when
every stride is 1. A strided box covers its hull sparsely:
lazy[10:50, ::4] selects 40x20 cells out of a 40x77 hull, so a
consumer that reads the whole hull and discards the rest transfers 3.85x
the data it needs. Check strides before treating a box as a single
slab read.
The distinction lets a consumer decide between a slab read and a gather; see the design notes for why it is a category rather than an optimization.
Examples:
lazy
property
¶
Lazy indexing: lazy[...], lazy.oindex[...], lazy.vindex[...].
Each returns a new LazyArray view; no data is read.
shape
property
¶
The shape of this view — the transform's input domain, not the source's.
transform
property
¶
transform: IndexTransform
The composed transform from this view's coordinates to storage.
__array__ ¶
Materialize the view as a NumPy array.
The result never shares memory with the wrapped array, whatever copy
asks for: result() already allocates, so copy=True gets an array the
caller owns and copy=None gets the same one rather than a second
allocation. copy=False is refused, because materializing means reading
— the values do not exist as a NumPy array until this call makes them.
Source code in src/zarr_indexing/lazy_array.py
__dask_tokenize__ ¶
__dask_tokenize__() -> Any
A deterministic token: the wrapped array and the view.
Two wrappers produce equal tokens when they wrap the same data and
address the same cells. The view contributes a digest of its canonical
ndsel body, so transforms that differ only in representation produce
the same token, and a fancy selection with a large index array does not
embed that array's JSON in the token. See _wrapped_token for the
determinism scope of the wrapped array's contribution; dask is imported
lazily and is never a requirement of this package.
The partitioning and reader are deliberately absent. Both decide how the data is read — in which boxes, and through which request strategy — and neither changes the values that come back, so two wrappers differing only in those describe the same data. A token identifies data, so they token alike and a consumer that caches on tokens reuses one result for both.
Source code in src/zarr_indexing/lazy_array.py
__getitem__ ¶
Read a basic selection eagerly, like numpy.ndarray.__getitem__.
Reads here are eager, not lazy, so that a LazyArray works as a duck
array for consumers (dask's from_array, numpy.asarray) that expect
indexing to produce data. Use .lazy[...] for the lazy form.
Source code in src/zarr_indexing/lazy_array.py
__init__ ¶
Wrap array without reading it; parameters are documented on the class.
The only validation here is the numpy.matrix rejection (TypeError).
Source code in src/zarr_indexing/lazy_array.py
__iter__ ¶
Iterate eagerly over the first axis, like a NumPy array.
The rank check happens in __iter__ itself rather than in the
generator, so iter(view) on a zero-rank view raises immediately as
NumPy's does, instead of waiting for the first next.
Source code in src/zarr_indexing/lazy_array.py
__len__ ¶
__len__() -> int
The length of the first axis, as for a NumPy array; TypeError on a 0-d view.
bounding_box ¶
The storage region this view touches, one interval per storage dimension.
Defined for any selection, box or not, as the hull: the smallest
[inclusive_min, exclusive_max) interval per dimension of the array
this view reads from that contains every coordinate the selection
reaches.
The hull is dense — every cell in it selected — only for a box whose
every stride is 1. A strided box selects a sublattice of its hull (pair
this with strides to
describe it fully), and a query's hull is a superset that can be
arbitrarily loose: oindex[[0, 999]] has a 1000-wide hull over two
rows.
Returns:
-
tuple of (int, int), or None–One interval per storage dimension, or
Nonewhen the view is empty (size == 0) and so touches no coordinate at all, leaving no interval to report.
Notes
The coordinates directly address the raw array this view exposes.
Consequently, partition views report source-global hulls;
Partition.box separately gives
the whole global partition cell rather than only the selected hull.
Examples:
>>> import numpy as np
>>> array = LazyArray.from_numpy(np.arange(12).reshape(3, 4))
>>> array.lazy[1:, ::2].bounding_box()
((1, 3), (0, 3))
>>> array.lazy.oindex[[2, 0], :].bounding_box()
((0, 3), (0, 4))
>>> array.lazy[1:1].bounding_box() is None
True
Source code in src/zarr_indexing/lazy_array.py
from_numpy
classmethod
¶
Wrap a NumPy array with its explicitly selected optimized reader.
Source code in src/zarr_indexing/lazy_array.py
parts ¶
Iterate the base partitioning, projected through this view.
Single-use: this is a generator, so it is consumed by the first walk and
a second for over the same object yields nothing. Call parts() again
for a fresh walk, or keep a list of it if you need to revisit.
Yields one Partition per box the
view actually touches. The parts tile the view exactly and disjointly,
and each carries a LazyArray that can be resolved on its own: in
another thread, in another order, or not at all. Those views share this
view's reader, and LazyArray does not serialize calls, so a stateful
reader must synchronize its own mutable state.
A wrapper with no partitioning (see with_parts) yields a single part
covering the whole array.
Yields:
-
Partition–One per touched box, in the resolver's own order.
Examples:
>>> import numpy as np
>>> view = LazyArray.from_numpy(np.arange(12).reshape(3, 4)).with_parts((2, 2))
>>> part = next(view.lazy[:, 1:].parts())
>>> (part.base_coords, part.view.shape, part.is_complete)
((0, 0), (2, 1), False)
Source code in src/zarr_indexing/lazy_array.py
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 | |
result ¶
Materialize this view.
Every result starts as a fresh system-memory buffer. Each touched partition is read through the selected reader directly into its rectangular destination, or into an owned dense temporary before fancy placement. Empty views allocate without reading the source.
Parameters:
-
parts(Sequence[Partition] | None, default:None) –A reusable sequence previously returned by this exact view's
parts()method. Supplying it reuses that partition plan instead of constructing another one. The parts must tile the view exactly.
Returns:
-
ndarray–An array of shape
self.shape, identical whatever partitioning is in force, always in fresh system memory. A view with a zero-rank domain returns a zero-dimensional array, not a scalar.
Raises:
-
ValueError–If supplied parts were prepared by another view, or do not tile this view exactly. The output buffer is uninitialized where nothing was written, so a bad plan is reported rather than returned.
-
AssertionError–If this library's own partition walk fails to cover the view — a bug in zarr-indexing, never a consequence of the caller's input.
Source code in src/zarr_indexing/lazy_array.py
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 | |
strides ¶
The step between selected coordinates, one per storage dimension.
Together with bounding_box(), this fully describes a box selection:
bounding_box() gives the interval per dimension, strides() gives the
step per dimension. A stride of 1 means every cell of the hull along
that dimension is selected; k means every k-th. Dimensions fixed by
an integer index report 1 — they span a single coordinate.
Returns:
-
tuple of int, or None–One positive stride per storage dimension, or
Nonewhenis_boxis false: a query's coordinates are a lookup table and have no step. An empty box still reports its strides even thoughbounding_boxreturnsNone, because the step is a property of the selection's shape, not of the (empty) region it touches.
Notes
Magnitudes only. A reversing view (lazy[::-1]) selects the same set of
coordinates as the equivalent forward view, so it reports the same
bounding box and the same strides. The traversal direction is recorded
in the transform, not in this description of the region touched. A
consumer that needs the order reads the transform, or reverses the block
it gets back.
Examples:
>>> import numpy as np
>>> array = LazyArray.from_numpy(np.arange(24).reshape(4, 6))
>>> (array.lazy[1:, ::2].bounding_box(), array.lazy[1:, ::2].strides())
(((1, 4), (0, 5)), (1, 2))
>>> array.lazy[2, ::3].strides()
(1, 3)
>>> array.lazy.oindex[[2, 0], :].strides() is None
True
Source code in src/zarr_indexing/lazy_array.py
unpartitioned ¶
unpartitioned() -> LazyArray
Return the same view, read in one pass.
result() still allocates its owned output buffer first, then calls the
reader once with the whole projected transform. parts() still yields a
single part covering everything.
Returns:
-
LazyArray–The same view with no partitioning.
Source code in src/zarr_indexing/lazy_array.py
with_parts ¶
Return the same view, read in uniform boxes of shape parts.
One integer per dimension of base_shape, with the trailing box in each
dimension clipped to the extent. The transform, the wrapped array, and
therefore result() are all unchanged; only the boxes the read is
broken into differ. Nothing is copied and nothing is read.
For per-axis sizes see
with_parts_per_axis,
and to read in one pass see
unpartitioned.
The three were one parameter whose meaning was decided by inspecting the
type of what it was given, which left no way to ask for one of them and
be told when you had spelled it wrong.
Parameters:
Returns:
-
LazyArray–The same view with a new partitioning.
Raises:
-
ValueError–If
partshas the wrong length or contains a non-positive extent. Uniform part sizes must remain positive even for a zero-length axis; usewith_parts_per_axisfor the accepted explicit zero-axis spellings.
Examples:
>>> import numpy as np
>>> view = LazyArray.from_numpy(np.arange(12).reshape(3, 4))
>>> [part.base_coords for part in view.with_parts((2, 3)).parts()]
[(0, 0), (0, 1), (1, 0), (1, 1)]
Source code in src/zarr_indexing/lazy_array.py
with_parts_per_axis ¶
Return the same view, read in boxes of explicitly listed sizes.
The dask convention: one sequence of box extents per dimension of
base_shape, each summing to that dimension's extent. Use it when the
boxes are not uniform — a partitioning discovered from a store, or one
whose last box differs by more than clipping.
Parameters:
Returns:
-
LazyArray–The same view with a new partitioning.
Raises:
-
ValueError–If
sizeshas the wrong length, contains a negative extent, uses a zero extent on a nonempty axis, or declares sizes that do not sum tobase_shape. On a zero-length axis,(),(0,), and repeated zeros all describe no chunks.
Examples:
>>> import numpy as np
>>> view = LazyArray.from_numpy(np.arange(12).reshape(3, 4))
>>> [part.box for part in view.with_parts_per_axis(((1, 2), (4,))).parts()]
[((0, 1), (0, 4)), ((1, 3), (0, 4))]
Source code in src/zarr_indexing/lazy_array.py
with_reader ¶
Return the same metadata view resolved through reader.
Source code in src/zarr_indexing/lazy_array.py
Partition
dataclass
¶
One box of a LazyArray's partitioning, as it falls through the view.
Yielded by LazyArray.parts.
The parts of a view tile it exactly and disjointly: assembling every
view.result() at its out_selection reproduces the whole view's
result(), and each part can be resolved independently and concurrently.
Derived parts retain the same reader object; a shared stateful reader owns
synchronization for concurrent calls.
A consumer that needs the plan before materialization can prepare it once and reuse the same immutable parts for both scheduling and assembly:
parts = tuple(view.parts())
schedule(part.base_coords for part in parts)
values = view.result(parts=parts)
Prepared parts are owned by the exact view that created them and must tile it completely. Passing parts from another view, even an equivalent one, is rejected without reading; omitting a part is likewise rejected rather than returning a partly initialized result.
Attributes:
-
projection(ChunkProjection) –The source-independent description of this part. Its paired
chunk_transformandcell_transformshare one compact synthetic domain, mapping each selected cell to chunk-local storage and request coordinates respectively. This is the authoritative placement model;base_coordsandis_completeare conveniences derived from it. -
base_coords(tuple[int, ...]) –Which box of the base partitioning this is, one coordinate per dimension of the wrapped array.
-
box(tuple[tuple[int, int], ...]) –The box itself, in the global storage coordinates of the wrapped array: one
[inclusive_min, exclusive_max)interval per dimension. It describes the whole partition cell, whileview.bounding_box()is the global hull of only the selected values in that cell. For a nested or repartitioned view this box may be narrower thanprojection.chunk_domain. -
view(LazyArray) –A
LazyArraycovering exactly the cells of the view that live in this box. Its transform directly addresses its raw wrappedarray; only the projection'schunk_transformis chunk-local. Resolving the view reads the box once through its selected reader. Namedviewrather thanarraybecauseLazyArray.arrayis the opposite thing — the raw wrapped source — and the two sat next to each other meaning inverses. -
out_selection(tuple[Any, ...]) –Where
view.result()belongs in an array of the whole view's shape — a NumPy index tuple with one entry per dimension of the view, usable directly asout[part.out_selection] = .... -
is_complete(bool) –Whether the view covers the whole box. Useful to a writer deciding between a blind overwrite and a read-modify-write. Fancy projections report
Falsebecause their coverage is deliberatelyunknownuntil duplicate-aware proof is added.
Examples:
Assembling every part's result at its out_selection reproduces the view:
>>> import numpy as np
>>> source = np.arange(12).reshape(3, 4)
>>> view = LazyArray.from_numpy(source).with_parts((2, 2))
>>> out = np.empty(view.shape, dtype=view.dtype)
>>> for part in view.parts():
... out[part.out_selection] = part.view.result()
>>> bool((out == source).all())
True
Source code in src/zarr_indexing/lazy_array.py
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | |