@@ -1460,7 +1460,7 @@ def T(self):
14601460 raise ValueError ("This property only works for 2-dimensional arrays." )
14611461 return permute_dims (self )
14621462
1463- def get_fselection_numpy (self , key : list | np .ndarray ) -> np .ndarray : # noqa: C901
1463+ def get_fselection_numpy (self , key : list | np .ndarray ) -> np .ndarray :
14641464 """
14651465 Select a slice from the array using a fancy index.
14661466 Closely matches NumPy fancy indexing behaviour, except in
@@ -1477,7 +1477,7 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
14771477 out: np.ndarray
14781478
14791479 """
1480- # TODO: Make this faster and running out of memory - avoid broadcasting keys
1480+ # TODO: Make this faster and avoid running out of memory - avoid broadcasting keys
14811481 ## Can't do this because ndindex doesn't support all the same indexing cases as Numpy
14821482 # if math.prod(self.shape) * self.dtype.itemsize < blosc2.MAX_FAST_PATH_SIZE:
14831483 # return self[:][key] # load into memory for smallish arrays
@@ -1496,77 +1496,58 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
14961496 chunks = np .array (chunks )
14971497 # |------|
14981498 # ------| arrs |------
1499- arrs = []
1500- begin = None
1501- end = None
1502- flat_shape = ()
1503-
1504- if len (_slice ) == 1 : # 1D fast path possible if no slices
1505- arr = _slice [0 ].reshape (1 , - 1 )
1506- flat_shape += (arr .shape [- 1 ],)
1507- begin = 0
1508- else :
1509- for num , i in enumerate (_slice ):
1510- if isinstance (i , np .ndarray ): # collecting arrays
1511- if end is not None :
1512- raise ValueError ("Cannot use slices between arrays of integers in index" )
1513- arrs .append (i .reshape (- 1 )) # flatten does copy
1514- if begin is None :
1515- begin = num
1516- else : # slice
1517- if arrs : # flush arrays
1518- arr = np .stack (arrs )
1519- arrs = []
1520- end = num
1521- flat_shape += (arr .shape [- 1 ],)
1522- flat_shape += ((i .stop - i .start + (i .step - 1 )) // i .step ,)
1523-
1524- # flush at the end if arrays remain
1525- if arrs :
1526- arr = np .stack (arrs ) # uses quite a bit of memory seemingly
1527- flat_shape += (arr .shape [- 1 ],)
1528-
1499+ arridxs = [i for i , s in enumerate (_slice ) if isinstance (s , np .ndarray )]
1500+ begin , end = arridxs [0 ], arridxs [- 1 ] + 1
1501+ flat_shape = tuple ((i .stop - i .start + (i .step - 1 )) // i .step for i in _slice [:begin ])
1502+ idx_dim = np .prod (_slice [begin ].shape )
1503+ arr = np .empty ((idx_dim , end - begin ), dtype = _slice [begin ].dtype )
1504+ for i , s in enumerate (_slice [begin :end ]):
1505+ arr [:, i ] = s .reshape (- 1 ) # have to do a copy
1506+ flat_shape += (idx_dim ,)
1507+ flat_shape += tuple ((i .stop - i .start + (i .step - 1 )) // i .step for i in _slice [end :])
15291508 # out_shape could have new dims if indexing arrays are not all 1D
15301509 # (we have just flattened them so need to handle accordingly)
15311510 prior_tuple = _slice [:begin ]
1532- post_tuple = _slice [end :] if end is not None else ()
1511+ post_tuple = _slice [end :]
15331512 divider = chunks [begin :end ]
1534- chunked_arr = arr .T // divider
1535- if len (arr ) == 1 : # 1D chunks, can avoid loading whole chunks
1536- idx_order = np .argsort (arr .squeeze (axis = 0 ), axis = - 1 ) # sort by real index
1537- chunk_nitems = np .bincount (
1538- chunked_arr .reshape (- 1 ), minlength = self .schunk .nchunks
1539- ) # only works for 1D but faster (no copy or sort)
1540- unique_chunks = np .nonzero (chunk_nitems )
1513+ chunked_arr = arr // divider
1514+ if arr .shape [- 1 ] == 1 : # 1D chunks, can avoid loading whole chunks
1515+ idx_order = np .argsort (arr .squeeze (axis = 1 ), axis = - 1 ) # sort by real index
1516+ chunk_nitems = np .bincount (chunked_arr .reshape (- 1 ), minlength = self .schunk .nchunks )
1517+ unique_chunks = np .nonzero (chunk_nitems )[0 ][:, None ] # add dummy axis
15411518 chunk_nitems = chunk_nitems [unique_chunks ]
15421519 else :
1543- # does a copy and sorts - can this be avoided?
1544- unique_chunks , chunk_nitems = np .unique (chunked_arr , axis = 0 , return_counts = True )
1545- idx_order = np .lexsort (
1546- tuple (a for a in reversed (chunked_arr .T ))
1520+ chunked_arr = np .ascontiguousarray (
1521+ chunked_arr
1522+ ) # ensure C-order memory to allow structured dtype view
1523+ # use np.unique but avoid sort and copy
1524+ _ , row_ids , idx_inv , chunk_nitems = np .unique (
1525+ chunked_arr .view ([("" , chunked_arr .dtype )] * chunked_arr .shape [1 ]),
1526+ return_counts = True ,
1527+ return_index = True ,
1528+ return_inverse = True ,
1529+ )
1530+ unique_chunks = chunked_arr [row_ids ]
1531+ idx_order = np .argsort (
1532+ idx_inv .squeeze (- 1 )
15471533 ) # sort by chunks (can't sort by index since larger index could belong to lower chunk)
15481534 # e.g. chunks of (100, 10) means (50, 15) has chunk idx (0,1) but (60,5) has (0, 0)
1549- del chunked_arr # no longer need this
1550- sorted_idxs = arr [:, idx_order ]
1535+ sorted_idxs = arr [idx_order ]
15511536 out = np .empty (flat_shape , dtype = self .dtype )
15521537 shape = np .array (shape )
15531538
15541539 chunk_nitems_cumsum = np .cumsum (chunk_nitems )
15551540 cprior_slices = [
15561541 slice_to_chunktuple (s , c ) for s , c in zip (prior_tuple , chunks [:begin ], strict = True )
15571542 ]
1558- cpost_slices = (
1559- [slice_to_chunktuple (s , c ) for s , c in zip (post_tuple , chunks [end :], strict = True )]
1560- if end is not None
1561- else []
1562- )
1543+ cpost_slices = [slice_to_chunktuple (s , c ) for s , c in zip (post_tuple , chunks [end :], strict = True )]
15631544 for chunk_i , chunk_idx in enumerate (unique_chunks ):
15641545 start = 0 if chunk_i == 0 else chunk_nitems_cumsum [chunk_i - 1 ]
15651546 stop = chunk_nitems_cumsum [chunk_i ]
1566- selection = sorted_idxs [:, start :stop ]. T
1567- out_mid_selection = (idx_order [start :stop ]. T ,)
1547+ selection = sorted_idxs [start :stop ]
1548+ out_mid_selection = (idx_order [start :stop ],)
15681549 if (
1569- len ( arr ) == 1
1550+ arr . shape [ - 1 ] == 1
15701551 ): # can avoid loading in whole chunk if 1D for array indexed chunks, a bit faster
15711552 chunk_begin = selection [0 ]
15721553 chunk_end = selection [- 1 ] + 1
0 commit comments