@@ -142,6 +142,66 @@ def offset_by(
142142 out_idx [:, :2 ] += np .expand_dims (delta , axis = 1 )
143143 return type (self )(np_index = out_idx , value = self .value )
144144
145+ def intersect (
146+ self ,
147+ np_index : NpIndex , # shape=[{rank}, 3], dtype=int
148+ ) -> _GenericFragment [A ] | None :
149+ """Intersects this fragment with the given NpIndex.
150+
151+ The result is in this fragment's coordinate space. For example,
152+ intersecting a fragment with its own index gives an identical fragment.
153+
154+ Args:
155+ np_index: The NpIndex to intersect with.
156+
157+ Returns:
158+ A new fragment representing the intersection, or None if there is no
159+ overlap.
160+ """
161+ if (self .step != 1 ).any () or (np_index [:, 2 ] != 1 ).any ():
162+ raise NotImplementedError ('index steps other than 1 are not supported.' )
163+
164+ out_np_index = np_index .copy ()
165+ start = out_np_index [:, 0 ] = np .maximum (out_np_index [:, 0 ], self .start )
166+ stop = out_np_index [:, 1 ] = np .minimum (out_np_index [:, 1 ], self .stop )
167+ if not (start < stop ).all ():
168+ return None
169+ return type (self )(
170+ np_index = out_np_index , value = self .slice_of_value (out_np_index )
171+ )
172+
173+ def slice (
174+ self ,
175+ np_index : NpIndex , # shape=[{rank}, 3], dtype=int
176+ ) -> _GenericFragment [A ] | None : # Use typing.Self once 3.11 is minimum.
177+ """Slices this fragment by the given NpIndex.
178+
179+ The result is in the slice's coordinate space. For example, slicing a
180+ fragment by its own index gives a fragment whose start is zero.
181+
182+ Args:
183+ np_index: The NpIndex to slice by.
184+
185+ Returns:
186+ A new fragment representing the slice, or None if there is no overlap.
187+ """
188+ intersection = self .intersect (np_index )
189+ return intersection .offset_by (- np_index [:, 0 ]) if intersection else None
190+
191+ def slice_of_value (self , np_index : NpIndex ) -> A :
192+ """Takes a slice of the value of this fragment.
193+
194+ It is required that `np_index` has already been clamped to the fragment's
195+ bounds; otherwise a ValueError will result.
196+
197+ Args:
198+ np_index: The NpIndex to slice by.
199+
200+ Returns:
201+ A slice of the fragment's value.
202+ """
203+ raise NotImplementedError ()
204+
145205
146206@dataclasses .dataclass (frozen = True , init = False , eq = False , repr = False )
147207class AbstractFragment (_GenericFragment [type (None )]):
@@ -178,21 +238,9 @@ def offset_by(
178238 out_idx [:, :2 ] += np .expand_dims (delta , axis = 1 )
179239 return type (self )(np_index = out_idx )
180240
181- def slice (
182- self ,
183- np_index : NpIndex , # shape=[{rank}, 3], dtype=int
184- ) -> AbstractFragment | None : # Use typing.Self once 3.11 is minimum.
185- """Slices this fragment to find the part that overlaps the given NpIndex."""
186- if (self .step != 1 ).any () or (np_index [:, 2 ] != 1 ).any ():
187- raise NotImplementedError ('Coming ... soon?' )
188-
189- slice_shape = np_index [:, 1 ] - np_index [:, 0 ]
190- out = self .offset_by (- np_index [:, 0 ])
191- start = out .start [:] = np .maximum (out .start , 0 )
192- stop = out .stop [:] = np .minimum (out .stop , slice_shape )
193- if not (start < stop ).all ():
194- return None
195- return out
241+ def slice_of_value (self , np_index : NpIndex ) -> None :
242+ del np_index
243+ return None
196244
197245
198246@dataclasses .dataclass (frozen = True , init = False )
@@ -230,39 +278,14 @@ def __array__(self) -> np.ndarray:
230278 def nbytes (self ) -> int :
231279 return self .value .nbytes
232280
233- def slice (
234- self ,
235- np_index : NpIndex , # shape=[{rank}, 3], dtype=int
236- ) -> _ConcreteFragment | None : # Use typing.Self once 3.11 is minimum.
237- """Slices this fragment to find the part that overlaps the given NpIndex."""
238- if (self .step != 1 ).any () or (np_index [:, 2 ] != 1 ).any ():
239- raise NotImplementedError ('Coming ... soon?' )
240-
241- slice_shape = np_index [:, 1 ] - np_index [:, 0 ]
242- out = self .offset_by (- np_index [:, 0 ])
243- start = out .start [:] = np .maximum (out .start , 0 )
244- stop = out .stop [:] = np .minimum (out .stop , slice_shape )
245- if not (start < stop ).all ():
246- return None
247- return type (self )(
248- np_index = out .np_index , value = self .slice_of_value (np_index )
249- )
250-
251- def slice_of_value (
252- self ,
253- new_np_idx : NpIndex ,
254- ) -> A :
255- """Returns a slice of `value`."""
256- start = self .start
257- stop = self .stop
281+ def slice_of_value (self , np_index : NpIndex ) -> Aconcrete :
258282 # This is just a convenient way to construct the required tuple of slices.
259- f = AbstractFragment (
260- np_index = np .stack ([
261- np .maximum (start , new_np_idx [:, 0 ]),
262- np .minimum (stop , new_np_idx [:, 1 ]),
263- new_np_idx [:, 2 ],
264- ], axis = 1 )
265- ).offset_by (- start )
283+ f = AbstractFragment (np_index = np_index ).offset_by (- self .start )
284+ if (f .start < 0 ).any () or (f .stop > self .value .shape ).any ():
285+ raise ValueError (
286+ f'Attempt to slice fragment value of shape { self .shape } with'
287+ f' out-of-bounds index { f } '
288+ )
266289 return self .value [f .index or ...]
267290
268291
@@ -353,7 +376,7 @@ def __array__(self) -> np.ndarray:
353376 def slice (
354377 self ,
355378 index : NpIndex | Index , # shape=[{rank}, 3], dtype=int
356- ) -> '_GenericFragments[F] ' : # Use typing.Self once 3.11 is minimum .
379+ ) -> '_GenericFragments[_GenericFragment[A]] ' : # Use typing.Self once >= 3.11.
357380 """Returns a slice of this object."""
358381 if not isinstance (index , np .ndarray ):
359382 index = np_utils .resolve_slice (index , self .shape )
0 commit comments