Skip to content

Commit d66afc4

Browse files
committed
Fixes to setitem for 0 chunks
1 parent fbd3b06 commit d66afc4

4 files changed

Lines changed: 33 additions & 23 deletions

File tree

src/blosc2/lazyexpr.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,8 +1201,7 @@ def fast_eval( # noqa: C901
12011201

12021202
chunk_operands = {}
12031203
# Check which chunks intersect with _slice
1204-
chunk_size = ndindex.ChunkSize(chunks)
1205-
all_chunks = chunk_size.as_subchunks((), shape) # if _slice is (), returns all chunks
1204+
all_chunks = get_intersecting_chunks((), shape, chunks) # if _slice is (), returns all chunks
12061205
for nchunk, chunk_slice in enumerate(all_chunks):
12071206
cslice = chunk_slice.raw
12081207
offset = tuple(s.start for s in cslice) # offset for the udf
@@ -1404,9 +1403,10 @@ def slices_eval( # noqa: C901
14041403

14051404
# Iterate over the operands and get the chunks
14061405
chunk_operands = {}
1407-
# Check which chunks intersect with _slice
1408-
chunk_size = ndindex.ChunkSize(chunks)
1409-
intersecting_chunks = chunk_size.as_subchunks(_slice, shape) # if _slice is (), returns all chunks
1406+
# Check which chunks intersect with _slice (handles zero chunks internally)
1407+
intersecting_chunks = get_intersecting_chunks(
1408+
_slice, shape, chunks
1409+
) # if _slice is (), returns all chunks
14101410

14111411
for nchunk, chunk_slice in enumerate(intersecting_chunks):
14121412
# get intersection of chunk and target
@@ -1790,7 +1790,7 @@ def reduce_slices( # noqa: C901
17901790
same_shape = all(operand.shape == o.shape for o in operands.values() if hasattr(o, "shape"))
17911791
same_chunks = all(operand.chunks == o.chunks for o in operands.values() if hasattr(o, "chunks"))
17921792
same_blocks = all(operand.blocks == o.blocks for o in operands.values() if hasattr(o, "blocks"))
1793-
fast_path = same_shape and same_chunks and same_blocks
1793+
fast_path = same_shape and same_chunks and same_blocks and (0 not in chunks)
17941794
aligned, iter_disk = False, False
17951795
if fast_path:
17961796
# Check that all operands are NDArray for fast path
@@ -1824,8 +1824,8 @@ def reduce_slices( # noqa: C901
18241824
# Iterate over the operands and get the chunks
18251825
chunk_operands = {}
18261826
# Check which chunks intersect with _slice
1827-
chunk_size = ndindex.ChunkSize(chunks)
1828-
intersecting_chunks = chunk_size.as_subchunks(_slice, shape) # if _slice is (), returns all chunks
1827+
# if chunks has 0 we loop once but fast path is false as gives error (schunk has no chunks)
1828+
intersecting_chunks = get_intersecting_chunks(_slice, shape, chunks)
18291829
out_init = False
18301830

18311831
for nchunk, chunk_slice in enumerate(intersecting_chunks):
@@ -2882,7 +2882,7 @@ def sort(self, order: str | list[str] | None = None) -> blosc2.LazyArray:
28822882
lazy_expr._order = order
28832883
return lazy_expr
28842884

2885-
def compute(self, item=(), **kwargs) -> blosc2.NDArray: # noqa : C901
2885+
def compute(self, item=(), **kwargs) -> blosc2.NDArray:
28862886
# When NumPy ufuncs are called, the user may add an `out` parameter to kwargs
28872887
if "out" in kwargs:
28882888
kwargs["_output"] = kwargs.pop("out")
@@ -2900,14 +2900,7 @@ def compute(self, item=(), **kwargs) -> blosc2.NDArray: # noqa : C901
29002900
if hasattr(self, "_order"):
29012901
kwargs["_order"] = self._order
29022902
# handle empty arrays
2903-
if 0 in self.shape:
2904-
result = (
2905-
np.empty(self.shape, dtype=self.dtype)
2906-
if "_getitem" in kwargs
2907-
else blosc2.empty(self.shape, dtype=self.dtype)
2908-
)
2909-
else:
2910-
result = self._compute_expr(item, kwargs)
2903+
result = self._compute_expr(item, kwargs)
29112904
if "_order" in kwargs and "_indices" not in kwargs:
29122905
# We still need to apply the index in result
29132906
x = self._where_args["_where_x"]
@@ -3618,6 +3611,16 @@ def evaluate(
36183611
return lexpr[()]
36193612

36203613

3614+
def get_intersecting_chunks(_slice, shape, chunks):
3615+
if 0 not in chunks:
3616+
chunk_size = ndindex.ChunkSize(chunks)
3617+
return chunk_size.as_subchunks(_slice, shape) # if _slice is (), returns all chunks
3618+
else:
3619+
return (
3620+
ndindex.ndindex(...).expand(shape),
3621+
) # chunk is whole array so just return full tuple to do loop once
3622+
3623+
36213624
if __name__ == "__main__":
36223625
from time import time
36233626

src/blosc2/ndarray.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ def __rpow__(self, value: int | float | NDArray | NDField | blosc2.C2Array, /) -
965965

966966
def __bool__(self) -> bool:
967967
if math.prod(self.shape) != 1:
968-
raise ValueError("The truth value of an array of shape {self.shape} is ambiguous.")
968+
raise ValueError(f"The truth value of an array of shape {self.shape} is ambiguous.")
969969
return bool(self[()])
970970

971971
@is_documented_by(sum)
@@ -1476,8 +1476,6 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
14761476
_slice = _slice.raw
14771477
# now all indices are slices or arrays of integers (or booleans)
14781478
# # moreover, all arrays are consecutive (otherwise an error is raised)
1479-
# if builtins.any(k.step < 0 for k in _slice if isinstance(k, slice)):
1480-
# raise ValueError("Fancy indexing not supported for slices with negative steps.")
14811479

14821480
if np.all([isinstance(s, (slice, np.ndarray)) for s in _slice]) and np.all(
14831481
[s.dtype is not bool for s in _slice if isinstance(s, np.ndarray)]
@@ -1600,7 +1598,7 @@ def _get_set_findex_default(self, _slice, out_shape=None, updater=None):
16001598
out = self # default return for no intersecting chunks
16011599
if 0 in self.shape:
16021600
return out
1603-
chunk_size = ndindex.ChunkSize(self.chunks)
1601+
chunk_size = ndindex.ChunkSize(self.chunks) # only works with nonzero chunks
16041602
# repeated indices are grouped together
16051603
intersecting_chunks = chunk_size.as_subchunks(
16061604
_slice, self.shape

src/blosc2/schunk.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,8 @@ def update_data(self, nchunk: int, data: object, copy: bool) -> int:
866866
Number of chunks after update: 4
867867
"""
868868
blosc2_ext.check_access_mode(self.urlpath, self.mode)
869-
return super().update_data(nchunk, data, copy)
869+
nchunks = super().nchunks
870+
return super().update_data(nchunk, data, copy) if nchunks > 0 else nchunks
870871

871872
def get_slice(self, start: int = 0, stop: int | None = None, out: object = None) -> str | bytes | None:
872873
"""Get a slice from :paramref:`start` to :paramref:`stop`.

tests/ndarray/test_reductions.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,12 +452,20 @@ def test_save_constructor_reduce2(shape, disk, compute):
452452
def test_reduction_index():
453453
shape = (20, 20)
454454
a = blosc2.linspace(0, 20, num=np.prod(shape), shape=shape)
455-
arr = blosc2.lazyexpr("sum(a,axis=0)", {"a": a})
455+
arr = blosc2.lazyexpr("sum(a, axis=0)", {"a": a})
456456
newarr = arr.compute()
457457
assert arr[:10].shape == (10,)
458458
assert arr[0].shape == ()
459459
assert arr.shape == newarr.shape
460460

461+
a = blosc2.ones(shape=(0, 0))
462+
arr = blosc2.lazyexpr("sum(a, axis=(0, 1, 2))", {"a": a})
463+
with pytest.raises(np.exceptions.AxisError):
464+
newarr = arr.compute()
465+
arr = blosc2.lazyexpr("sum(a, axis=(0, 0))", {"a": a})
466+
with pytest.raises(ValueError):
467+
newarr = arr.compute()
468+
461469

462470
@pytest.mark.parametrize("idx", [0, 1, (0,), slice(1, 2), (slice(0, 1),), slice(0, 4), (0, 2)])
463471
def test_reduction_index2(idx):

0 commit comments

Comments
 (0)