Skip to content

Commit 75228d3

Browse files
committed
Streamline code a little
1 parent 4ffe594 commit 75228d3

File tree

1 file changed

+35
-54
lines changed

1 file changed

+35
-54
lines changed

src/blosc2/ndarray.py

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)