@@ -1511,9 +1511,9 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
15111511 out_shape = _slice .newshape (shape )
15121512 _slice = _slice .raw
15131513 # now all indices are slices or arrays of integers (or booleans)
1514- # moreover, all arrays are consecutive (otherwise an error is raised)
1515- if builtins .any (k .step < 0 for k in _slice if isinstance (k , slice )):
1516- raise ValueError ("Fancy indexing not supported for slices with negative steps." )
1514+ # # moreover, all arrays are consecutive (otherwise an error is raised)
1515+ # if builtins.any(k.step < 0 for k in _slice if isinstance(k, slice)):
1516+ # raise ValueError("Fancy indexing not supported for slices with negative steps.")
15171517
15181518 if np .all ([isinstance (s , (slice , np .ndarray )) for s in _slice ]) and np .all (
15191519 [s .dtype is not bool for s in _slice if isinstance (s , np .ndarray )]
@@ -1523,7 +1523,21 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
15231523 # ------| arrs |------
15241524 arridxs = [i for i , s in enumerate (_slice ) if isinstance (s , np .ndarray )]
15251525 begin , end = arridxs [0 ], arridxs [- 1 ] + 1
1526- flat_shape = tuple ((i .stop - i .start + (i .step - 1 )) // i .step for i in _slice [:begin ])
1526+
1527+ start , stop , step , _ = get_ndarray_start_stop (begin , _slice [:begin ], self .shape [:begin ])
1528+ prior_tuple = tuple (
1529+ slice (s , st , stp ) for s , st , stp in zip (start , stop , step , strict = True )
1530+ ) # convert to start and stop +ve
1531+ start , stop , step , _ = get_ndarray_start_stop (
1532+ len (self .shape [end :]), _slice [end :], self .shape [end :]
1533+ )
1534+ post_tuple = tuple (
1535+ slice (s , st , stp ) for s , st , stp in zip (start , stop , step , strict = True )
1536+ ) # convert to start and stop +ve
1537+
1538+ flat_shape = tuple (
1539+ (i .stop - i .start - i .step // builtins .abs (i .step )) // i .step + 1 for i in prior_tuple
1540+ )
15271541 idx_dim = np .prod (_slice [begin ].shape )
15281542
15291543 # TODO: find a nicer way to do the copy maybe
@@ -1532,11 +1546,11 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
15321546 arr [:, i ] = s .reshape (- 1 ) # have to do a copy
15331547
15341548 flat_shape += (idx_dim ,)
1535- flat_shape += tuple ((i .stop - i .start + (i .step - 1 )) // i .step for i in _slice [end :])
1549+ flat_shape += tuple (
1550+ (i .stop - i .start - i .step // builtins .abs (i .step )) // i .step + 1 for i in post_tuple
1551+ )
15361552 # out_shape could have new dims if indexing arrays are not all 1D
15371553 # (we have just flattened them so need to handle accordingly)
1538- prior_tuple = _slice [:begin ]
1539- post_tuple = _slice [end :]
15401554 divider = chunks [begin :end ]
15411555 chunked_arr = arr // divider
15421556 if arr .shape [- 1 ] == 1 : # 1D chunks, can avoid loading whole chunks
@@ -1592,7 +1606,7 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
15921606 )
15931607 for cpost_tuple in product (* cpost_slices ):
15941608 out_post_selection , post_selection , loc_post_selection = _get_selection (
1595- cpost_tuple , post_tuple , chunks [end :] if end is not None else []
1609+ cpost_tuple , post_tuple , chunks [end :]
15961610 )
15971611 locbegin , locend = _get_local_slice (
15981612 prior_selection , post_selection , (chunk_begin , chunk_end )
@@ -1806,13 +1820,10 @@ def __setitem__( # noqa : C901
18061820 raise ValueError ("Cannot mix non-unit steps and None indexing for __setitem__." )
18071821 chunks = self .chunks
18081822 shape = self .shape
1809- pos_key = tuple (
1810- slice (s , st , stp ) if stp > 0 else slice (st + 1 , s + 1 , - stp )
1811- for s , st , stp in zip (start , stop , step , strict = True )
1812- ) # get positive steps
18131823 _slice = tuple (slice (s , st , stp ) for s , st , stp in zip (start , stop , step , strict = True ))
1814- # this will work only for positive steps
1815- intersecting_chunks = [slice_to_chunktuple (s , c ) for s , c in zip (pos_key , chunks , strict = True )]
1824+ intersecting_chunks = [
1825+ slice_to_chunktuple (s , c ) for s , c in zip (_slice , chunks , strict = True )
1826+ ] # internally handles negative steps
18161827 intersecting_chunks = [
18171828 (0 ,) if i == () else i for i in intersecting_chunks
18181829 ] # special case of dims with 0 length
@@ -1826,12 +1837,13 @@ def updater(sel_idx):
18261837 return value [sel_idx ]
18271838
18281839 for c in product (* intersecting_chunks ):
1829- sel_idx , _ , sub_idx = _get_selection (c , _slice , chunks , load_full = True )
1840+ sel_idx , glob_selection , sub_idx = _get_selection (c , _slice , chunks )
18301841 sel_idx = tuple (s for s , m in zip (sel_idx , mask , strict = True ) if not m )
18311842 sub_idx = tuple (s if not m else k .start for s , m , k in zip (sub_idx , mask , key_ , strict = True ))
1832- locstart , locstop = (
1833- tuple (c_ * cs for c_ , cs in zip (c , chunks , strict = True )),
1834- tuple ((c_ + 1 ) * cs for c_ , cs in zip (c , chunks , strict = True )),
1843+ locstart , locstop = _get_local_slice (
1844+ glob_selection ,
1845+ (),
1846+ ((), ()), # switches start and stop for negative steps
18351847 )
18361848 chunk = np .empty (
18371849 tuple (sp - st for st , sp in zip (locstart , locstop , strict = True )), dtype = self .dtype
@@ -1870,6 +1882,8 @@ def __len__(self) -> int:
18701882 """Returns the length of the first dimension of the array.
18711883 This is equivalent to ``self.shape[0]``.
18721884 """
1885+ if self .shape == ():
1886+ raise TypeError ("len() of unsized object" )
18731887 return self .shape [0 ]
18741888
18751889 def get_chunk (self , nchunk : int ) -> bytes :
@@ -4761,13 +4775,18 @@ def slice_to_chunktuple(s, n):
47614775 out: tuple
47624776 """
47634777 start , stop , step = s .start , s .stop , s .step
4778+ if step < 0 :
4779+ temp = stop
4780+ stop = start + 1
4781+ start = temp + 1
4782+ step = - step # get positive steps
47644783 if step > n :
47654784 return tuple ((start + k * step ) // n for k in range (ceiling (stop - start , step )))
47664785 else :
47674786 return tuple (range (start // n , ceiling (stop , n )))
47684787
47694788
4770- def _get_selection (ctuple , ptuple , chunks , load_full = False ):
4789+ def _get_selection (ctuple , ptuple , chunks ):
47714790 # we assume that at least one element of chunk intersects with the slice
47724791 # (as a consequence of only looping over intersecting chunks)
47734792 # ptuple is global slice, ctuple is chunk coords (in units of chunks)
@@ -4803,7 +4822,7 @@ def _get_selection(ctuple, ptuple, chunks, load_full=False):
48034822 # selection relative to coordinates of out (necessarily out_step = 1 as we work through out chunk-by-chunk of self)
48044823 # when added n + 1 elements
48054824 # ps.start = pt.start + step * (n+1) => n = (ps.start - pt.start - sign) // step
4806- # hence, out_start = n + 1 or shape(out) - 1 - (n + 1) if step < 0
4825+ # hence, out_start = n + 1
48074826 # ps.stop = pt.start + step * (out_stop - 1) + k, k in [step, -1] or [1, step]
48084827 # => out_stop = (ps.stop - pt.start - sign) // step + 1
48094828 out_pselection = ()
@@ -4823,39 +4842,34 @@ def _get_selection(ctuple, ptuple, chunks, load_full=False):
48234842 )
48244843 i += 1
48254844
4826- if load_full :
4827-
4828- def my_checker_f (x ): # handle case -1 to get full chunk
4829- return x if x >= 0 else None
4830-
4831- loc_selection = tuple (
4832- slice (s .start - i * csize , my_checker_f (s .stop - i * csize ), s .step )
4833- for i , s , csize in zip (
4834- ctuple , pselection , chunks , strict = True
4835- ) # if s.step < 0 then we match with out coords no problem
4836- ) # local coords of full chunk
4837- else :
4838- loc_selection = tuple (
4839- slice (0 , s .stop - s .start , s .step ) if s .step > 0 else slice (s .start - s .stop , None , s .step )
4840- for s in pselection
4841- ) # local coords of loaded part of chunk
4845+ loc_selection = tuple ( # is s.stop is None, get whole chunk so s.start - 0
4846+ slice (0 , s .stop - s .start , s .step )
4847+ if s .step > 0
4848+ else slice (s .start if s .stop == - 1 else s .start - s .stop , None , s .step )
4849+ for s in pselection
4850+ ) # local coords of loaded part of chunk
48424851
48434852 return out_pselection , pselection , loc_selection
48444853
48454854
48464855def _get_local_slice (prior_selection , post_selection , chunk_bounds ):
48474856 chunk_begin , chunk_end = chunk_bounds
4857+ # +1 for negative steps as have to include start (exclude stop)
48484858 locbegin = np .hstack (
48494859 (
4850- [s .start for s in prior_selection ],
4860+ [s .start if s . step > 0 else s . stop + 1 for s in prior_selection ],
48514861 chunk_begin ,
4852- [s .start for s in post_selection ],
4862+ [s .start if s . step > 0 else s . stop + 1 for s in post_selection ],
48534863 ),
48544864 casting = "unsafe" ,
48554865 dtype = "int64" ,
48564866 )
48574867 locend = np .hstack (
4858- ([s .stop for s in prior_selection ], chunk_end , [s .stop for s in post_selection ]),
4868+ (
4869+ [s .stop if s .step > 0 else s .start + 1 for s in prior_selection ],
4870+ chunk_end ,
4871+ [s .stop if s .step > 0 else s .start + 1 for s in post_selection ],
4872+ ),
48594873 casting = "unsafe" ,
48604874 dtype = "int64" ,
48614875 )
0 commit comments