Skip to content

Commit 3507e80

Browse files
committed
Now passes tests
1 parent 1fc4732 commit 3507e80

File tree

4 files changed

+73
-32
lines changed

4 files changed

+73
-32
lines changed

bench/ndarray/fancy_index.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ def genarray(r, ndims=2, verbose=True):
3737
shape = (d,) * ndims
3838
chunks = (d // 4,) * ndims
3939
blocks = (max(d // 10, 1),) * ndims
40+
urlpath = f'linspace{r}{ndims}D.b2nd'
4041
t = time.time()
4142
arr = blosc2.linspace(0, 1000, num=np.prod(shape), shape=shape, dtype=np.float64,
42-
urlpath=f'linspace{r}{ndims}D.b2nd', mode='w')
43+
urlpath=urlpath, mode='w')
4344
t = time.time() - t
4445
arrsize = np.prod(arr.shape) * arr.dtype.itemsize / 2 ** 30
4546
if verbose:
@@ -136,16 +137,17 @@ def timer(arr):
136137
error_kw=dict(lw=2, capthick=2, ecolor='k'))
137138
labs+=label
138139

139-
filename = f"results{labs}{NDIMS}D" + "sparse" if SPARSE else f"results{labs}{NDIMS}D"
140+
filename = f"{labs}{NDIMS}D" + "sparse" if SPARSE else f"{labs}{NDIMS}D"
141+
filename+=blosc2.__version__.replace('.','_')
140142

141143
with open(f"{filename}.pkl", 'wb') as f:
142-
pickle.dump(result_tuple, f)
144+
pickle.dump({'times':result_tuple, 'sizes':genuine_sizes}, f)
143145

144146
plt.xlabel('Array size (GB)')
145147
plt.legend()
146148
plt.xticks(x-width, np.round(genuine_sizes, 2))
147149
plt.ylabel("Time (s)")
148-
plt.title(f"Fancy indexing performance comparison, {NDIMS}D" +f"{" sparse" if SPARSE else ""}")
150+
plt.title(f"Fancy indexing {blosc2.__version__}, {NDIMS}D" +f"{" sparse" if SPARSE else ""}")
149151
plt.gca().set_yscale('log')
150152
plt.savefig(f'plots/fancyIdx{filename}.png', format="png")
151153
plt.show()

bench/ndarray/fancy_index1D.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727

2828
NUMPY = True
2929
BLOSC = True
30-
ZARR = True
31-
HDF5 = True
32-
SPARSE = True
30+
ZARR = False
31+
HDF5 = False
32+
SPARSE = False
3333

3434
if HDF5:
3535
SPARSE = True # HDF5 takes too long for non-sparse indexing
@@ -50,7 +50,7 @@ def genarray(r, verbose=True):
5050
return arr, arrsize
5151

5252

53-
target_sizes = np.float64(np.array([.2, .5, 1, 2, 5, 10]))
53+
target_sizes = np.float64(np.array([.1, .2, .5, 1, 2]))
5454
rng = np.random.default_rng()
5555
blosctimes = []
5656
nptimes = []
@@ -61,12 +61,12 @@ def genarray(r, verbose=True):
6161
arr, arrsize = genarray(d)
6262
genuine_sizes += [arrsize]
6363
idx = rng.integers(low=0, high=arr.shape[0], size=(1000,)) if SPARSE else rng.integers(low=0, high=arr.shape[0], size=(arr.shape[0]//4,))
64-
sorted_idx = np.unique(np.sort(idx))
64+
sorted_idx = np.sort(np.unique(idx))
6565
## Test fancy indexing for different use cases
6666
def timer(arr):
6767
time_list = []
6868
if not (HDF5 or ZARR):
69-
b = arr[[[sorted_idx], [idx]]]
69+
b = arr[[[idx[::-1]], [idx]]]
7070
time_list += [time.time() - t]
7171
t = time.time()
7272
t = time.time()
@@ -114,15 +114,16 @@ def timer(arr):
114114
error_kw=dict(lw=2, capthick=2, ecolor='k'))
115115
labs+=label
116116

117-
filename = f"results{labs}1Dsparse" if SPARSE else f"results{labs}1D"
117+
filename = f"{labs}1Dsparse" if SPARSE else f"{labs}1D"
118+
filename+=blosc2.__version__.replace('.','_')
118119
with open(filename+".pkl", 'wb') as f:
119120
pickle.dump({'times':result_tuple, 'sizes':genuine_sizes}, f)
120121

121122
plt.xlabel('Array size (GB)')
122123
plt.legend()
123124
plt.xticks(x-width, np.round(genuine_sizes, 2))
124125
plt.ylabel("Time (s)")
125-
plt.title(f"Fancy indexing performance comparison, 1D {' sparse' if SPARSE else ''}")
126+
plt.title(f"Fancy indexing {blosc2.__version__}, 1D {' sparse' if SPARSE else ''}")
126127
plt.gca().set_yscale('log')
127128
plt.savefig(f'plots/{filename}.png', format="png")
128129
plt.show()

src/blosc2/ndarray.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,6 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
14881488
_slice = ndindex.ndindex(key).expand(shape)
14891489
out_shape = _slice.newshape(shape)
14901490
_slice = _slice.raw
1491-
shape = np.array(shape)
14921491

14931492
if np.all([isinstance(s, (slice, np.ndarray)) for s in _slice]) and np.all(
14941493
[s.dtype is not bool for s in _slice if isinstance(s, np.ndarray)]
@@ -1518,20 +1517,24 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
15181517
end = num
15191518
f_ = 2
15201519
flat_shape += (arr.shape[-1],)
1521-
flat_shape += ((i.stop - i.start) // i.step,)
1520+
# k in [1,step], stop = start + n*step + k
1521+
# stop - start + step - 1 = (n+1)*step + k-1 = (n+1)*step + [0, step - 1]
1522+
flat_shape += ((i.stop - i.start + (i.step - 1)) // i.step,)
15221523
if not isinstance(arr, np.ndarray): # might have missed last part of loop
15231524
arr = np.stack(arr)
15241525
flat_shape += (arr.shape[-1],)
15251526
# out_shape could have new dims if indexing arrays are not all 1D
15261527
# (we have just flattened them so need to handle accordingly)
1528+
divider = chunks[begin:end]
1529+
chunked_arr = arr.T // divider
1530+
unique_chunks, chunk_nitems = np.unique(chunked_arr, axis=0, return_counts=True)
15271531
idx_order = np.lexsort(
1528-
tuple(a for a in reversed(arr))
1532+
tuple(a for a in reversed(chunked_arr.T))
15291533
) # sort by column with priority to first column
15301534
sorted_idxs = arr[:, idx_order]
15311535
out = np.empty(flat_shape, dtype=self.dtype)
1536+
shape = np.array(shape)
15321537

1533-
divider = chunks[begin:end]
1534-
unique_chunks, chunk_nitems = np.unique(sorted_idxs.T // divider, axis=0, return_counts=True)
15351538
chunk_nitems_cumsum = np.cumsum(chunk_nitems)
15361539
prior_tuple = _slice[:begin]
15371540
post_tuple = _slice[end:] if end is not None else ()
@@ -1547,13 +1550,23 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
15471550
start = 0 if chunk_i == 0 else chunk_nitems_cumsum[chunk_i - 1]
15481551
stop = chunk_nitems_cumsum[chunk_i]
15491552
selection = sorted_idxs[:, start:stop].T
1550-
out_selection = idx_order[start:stop].T
1553+
mid_out_selection = idx_order[start:stop].T
1554+
chunk_begin = chunk_idx * chunks[begin:end]
1555+
chunk_end = np.minimum((chunk_idx + 1) * chunks[begin:end], shape[begin:end])
15511556
# loop over chunks coming from slices before and after array indices
15521557
for cprior_tuple in product(*cprior_slices):
15531558
prior_selection = selector(cprior_tuple, prior_tuple, chunks[:begin])
15541559
# selection relative to coordinates of out (necessarily step = 1)
1560+
# stop = start + step * n + k => n = (stop - start - 1) // step
1561+
# hence, out_stop = out_start + n + 1
1562+
# ps.start = pt.start + out_start * step
15551563
out_prior_selection = tuple(
1556-
slice(ps.start - pt.start, ps.stop - pt.start, 1)
1564+
slice(
1565+
(ps.start - pt.start + pt.step - 1) // pt.step,
1566+
(ps.start - pt.start + pt.step - 1) // pt.step
1567+
+ (ps.stop - ps.start + ps.step - 1) // ps.step,
1568+
1,
1569+
)
15571570
for ps, pt in zip(prior_selection, prior_tuple, strict=True)
15581571
)
15591572
for cpost_tuple in product(*cpost_slices):
@@ -1562,11 +1575,14 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
15621575
)
15631576
# selection relative to coordinates of out (necessarily step = 1)
15641577
out_post_selection = tuple(
1565-
slice(ps.start - pt.start, ps.stop - pt.start, 1)
1578+
slice(
1579+
(ps.start - pt.start + pt.step - 1) // pt.step,
1580+
(ps.start - pt.start + pt.step - 1) // pt.step
1581+
+ (ps.stop - ps.start + ps.step - 1) // ps.step,
1582+
1,
1583+
)
15661584
for ps, pt in zip(post_selection, post_tuple, strict=True)
15671585
)
1568-
chunk_begin = chunk_idx * chunks[begin:end]
1569-
chunk_end = np.minimum((chunk_idx + 1) * chunks[begin:end], shape[begin:end])
15701586
locbegin = np.hstack(
15711587
(
15721588
[s.start for s in prior_selection],
@@ -1581,7 +1597,7 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray: # noqa: C
15811597
casting="unsafe",
15821598
dtype="int64",
15831599
)
1584-
out_selection = out_prior_selection + (out_selection,) + out_post_selection
1600+
out_selection = out_prior_selection + (mid_out_selection,) + out_post_selection
15851601
to_be_loaded = np.empty(locend - locbegin, dtype=self.dtype)
15861602
# basically load whole chunk, except for slice part at beginning and end
15871603
super().get_slice_numpy(
@@ -4615,7 +4631,21 @@ def slice_to_chunktuple(s, n):
46154631

46164632

46174633
def selector(ctuple, _tuple, chunks):
4618-
return tuple(
4619-
slice(builtins.max(s.start, i * csize), builtins.min(csize * (i + 1), s.stop), s.step)
4620-
for i, s, csize in zip(ctuple, _tuple, chunks, strict=True)
4621-
)
4634+
# we assume that at least one element of chunk intersects with the slice
4635+
# (as a consequence of only looping over intersecting chunks)
4636+
result = ()
4637+
for i, s, csize in zip(ctuple, _tuple, chunks, strict=True):
4638+
# we need to advance to first element within chunk that intersects with slice, not
4639+
# necessarily the first element of chunk
4640+
# i * csize = s.start + n*step + k, already added n+1 elements, k in [1, step]
4641+
np1 = (i * csize - s.start + s.step - 1) // s.step # gives (n + 1)
4642+
# can have n = -1 if s.start > i * csize, but never < -1 since have to intersect with chunk
4643+
result += (
4644+
slice(
4645+
builtins.max(s.start, s.start + np1 * s.step), # start+(n+1)*step gives i*csize if k=step
4646+
builtins.min(csize * (i + 1), s.stop),
4647+
s.step,
4648+
),
4649+
)
4650+
4651+
return result

tests/ndarray/test_ndarray.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,32 @@ def test_oindex():
292292
np.testing.assert_allclose(arr[:], nparr)
293293

294294

295-
def test_findex():
295+
@pytest.mark.parametrize("c", [None, 10])
296+
def test_findex(c):
296297
# Test 1d fast path
297298
ndim = 1
299+
chunks = (c,) * ndim if c is not None else None
298300
dtype = np.dtype("float")
299-
d = 1 + int(blosc2.MAX_FAST_PATH_SIZE / dtype.itemsize) # just over fast path size
301+
d = 1 + int(blosc2.MAX_FAST_PATH_SIZE / dtype.itemsize) if c is None else 100 # just over fast path size
300302
shape = (d,) * ndim
301-
arr = blosc2.linspace(0, 100, num=np.prod(shape), shape=shape, dtype=dtype)
303+
arr = blosc2.linspace(0, 100, num=np.prod(shape), shape=shape, dtype=dtype, chunks=chunks)
302304
rng = np.random.default_rng()
303305
idx = rng.integers(low=0, high=d, size=(d // 4,))
304306
nparr = arr[:]
305307
b = arr[idx]
306308
n = nparr[idx]
307309
np.testing.assert_allclose(b, n)
310+
b = arr[[[idx[::-1]], [idx]]]
311+
n = nparr[[[idx[::-1]], [idx]]]
312+
np.testing.assert_allclose(b, n)
308313

309314
ndim = 3
310-
d = 1 + int((blosc2.MAX_FAST_PATH_SIZE / 8) ** (1 / ndim)) # just over fast path size
315+
d = (
316+
1 + int((blosc2.MAX_FAST_PATH_SIZE / 8) ** (1 / ndim)) if c is None else d
317+
) # just over fast path size
311318
shape = (d,) * ndim
312-
arr = blosc2.linspace(0, 100, num=np.prod(shape), shape=shape, dtype=dtype)
319+
chunks = (c,) * ndim if c is not None else None
320+
arr = blosc2.linspace(0, 100, num=np.prod(shape), shape=shape, dtype=dtype, chunks=chunks)
313321
rng = np.random.default_rng()
314322
idx = rng.integers(low=0, high=d, size=(100,))
315323

0 commit comments

Comments
 (0)