Skip to content

Commit dbca827

Browse files
committed
Merge branch 'main' of github.com:Blosc/python-blosc2
2 parents 0917653 + 300bdf1 commit dbca827

File tree

5 files changed

+131
-202
lines changed

5 files changed

+131
-202
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* text=auto
2+
*.md text eol=lf

src/blosc2/lazyexpr.py

Lines changed: 28 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,9 @@
4242
from blosc2.info import InfoReporter
4343
from blosc2.ndarray import (
4444
NUMPY_GE_2_0,
45-
_check_allowed_dtypes,
4645
get_chunks_idx,
4746
get_intersecting_chunks,
48-
local_ufunc_map,
4947
process_key,
50-
ufunc_map,
51-
ufunc_map_1param,
5248
)
5349

5450
from .shape_utils import constructors, elementwise_funcs, infer_shape, linalg_attrs, linalg_funcs, reducers
@@ -116,9 +112,9 @@ def ne_evaluate(expression, local_dict=None, **kwargs):
116112
res = eval(expression, safe_blosc2_globals, local_dict)
117113
if "out" in kwargs:
118114
out = kwargs.pop("out")
119-
out[:] = res[()] if isinstance(res, blosc2.LazyArray) else res
115+
out[:] = res # will handle calc/decomp if res is lazyarray
120116
return out
121-
return res[()] if isinstance(res, blosc2.LazyArray) else res
117+
return res[()] if isinstance(res, blosc2.Operand) else res
122118

123119

124120
# Define empty ndindex tuple for function defaults
@@ -228,7 +224,7 @@ class LazyArrayEnum(Enum):
228224
UDF = 1
229225

230226

231-
class LazyArray(ABC):
227+
class LazyArray(ABC, blosc2.Operand):
232228
@abstractmethod
233229
def indices(self, order: str | list[str] | None = None) -> blosc2.LazyArray:
234230
"""
@@ -408,68 +404,6 @@ def save(self, **kwargs: Any) -> None:
408404
"""
409405
pass
410406

411-
@property
412-
@abstractmethod
413-
def dtype(self) -> np.dtype:
414-
"""
415-
Get the data type of the :ref:`LazyArray`.
416-
417-
Returns
418-
-------
419-
out: np.dtype
420-
The data type of the :ref:`LazyArray`.
421-
"""
422-
pass
423-
424-
@property
425-
@abstractmethod
426-
def shape(self) -> tuple[int]:
427-
"""
428-
Get the shape of the :ref:`LazyArray`.
429-
430-
Returns
431-
-------
432-
out: tuple
433-
The shape of the :ref:`LazyArray`.
434-
"""
435-
pass
436-
437-
@property
438-
@abstractmethod
439-
def ndim(self) -> int:
440-
"""
441-
Get the number of dimensions of the :ref:`LazyArray`.
442-
443-
Returns
444-
-------
445-
out: int
446-
The number of dimensions of the :ref:`LazyArray`.
447-
"""
448-
pass
449-
450-
@property
451-
@abstractmethod
452-
def info(self) -> InfoReporter:
453-
"""
454-
Get information about the :ref:`LazyArray`.
455-
456-
Returns
457-
-------
458-
out: InfoReporter
459-
A printable class with information about the :ref:`LazyArray`.
460-
"""
461-
pass
462-
463-
# Provide minimal __array_interface__ to allow NumPy to work with this object
464-
@property
465-
def __array_interface__(self):
466-
return {
467-
"shape": self.shape,
468-
"typestr": self.dtype.str,
469-
"data": self[()],
470-
"version": 3,
471-
}
472-
473407
# Provide a way to serialize the LazyArray
474408
def to_cframe(self) -> bytes:
475409
"""
@@ -482,11 +416,6 @@ def to_cframe(self) -> bytes:
482416
"""
483417
return self.compute().to_cframe()
484418

485-
def __bool__(self) -> bool:
486-
if math.prod(self.shape) != 1:
487-
raise ValueError(f"The truth value of a LazyArray of shape {self.shape} is ambiguous.")
488-
return self[()].__bool__()
489-
490419

491420
def convert_inputs(inputs):
492421
if not inputs or len(inputs) == 0:
@@ -2328,7 +2257,7 @@ def __init__(self, new_op): # noqa: C901
23282257
"minimum",
23292258
):
23302259
if np.isscalar(value1) and np.isscalar(value2):
2331-
self.expression = f"{op}(o0, o1)"
2260+
self.expression = f"{op}({value1}, {value2})"
23322261
elif np.isscalar(value2):
23332262
self.operands = {"o0": value1}
23342263
self.expression = f"{op}(o0, {value2})"
@@ -2339,6 +2268,15 @@ def __init__(self, new_op): # noqa: C901
23392268
self.operands = {"o0": value1, "o1": value2}
23402269
self.expression = f"{op}(o0, o1)"
23412270
return
2271+
elif isinstance(value1, LazyExpr) or isinstance(value2, LazyExpr):
2272+
if isinstance(value1, LazyExpr):
2273+
newexpr = value1.update_expr(new_op)
2274+
else:
2275+
newexpr = value2.update_expr(new_op)
2276+
self.expression = newexpr.expression
2277+
self.operands = newexpr.operands
2278+
self._dtype = newexpr.dtype
2279+
return
23422280

23432281
self._dtype = dtype_
23442282
if np.isscalar(value1) and np.isscalar(value2):
@@ -2359,16 +2297,6 @@ def __init__(self, new_op): # noqa: C901
23592297
if value1 is value2:
23602298
self.operands = {"o0": value1}
23612299
self.expression = f"(o0 {op} o0)"
2362-
elif isinstance(value1, LazyExpr) or isinstance(value2, LazyExpr):
2363-
if isinstance(value1, LazyExpr):
2364-
self.expression = value1.expression
2365-
self.operands = {"o0": value2}
2366-
else:
2367-
self.expression = value2.expression
2368-
self.operands = {"o0": value1}
2369-
newexpr = self.update_expr(new_op)
2370-
self.expression = newexpr.expression
2371-
self.operands = newexpr.operands
23722300
else:
23732301
# This is the very first time that a LazyExpr is formed from two operands
23742302
# that are not LazyExpr themselves
@@ -2404,6 +2332,7 @@ def update_expr(self, new_op): # noqa: C901
24042332
# One of the two operands are LazyExpr instances
24052333
try:
24062334
value1, op, value2 = new_op
2335+
dtype_ = check_dtype(op, value1, value2) # conserve dtype
24072336
# The new expression and operands
24082337
expression = None
24092338
new_operands = {}
@@ -2430,28 +2359,29 @@ def update_expr(self, new_op): # noqa: C901
24302359
new_operands, dup_op = fuse_operands(value1.operands, value2.operands)
24312360
# Take expression 2 and rebase the operands while removing duplicates
24322361
new_expr = fuse_expressions(value2.expression, len(value1.operands), dup_op)
2433-
expression = f"({self.expression} {op} {new_expr})"
2362+
expression = f"({value1.expression} {op} {new_expr})"
2363+
self.operands = value1.operands
24342364
elif isinstance(value1, LazyExpr):
24352365
if op == "~":
2436-
expression = f"({op}{self.expression})"
2366+
expression = f"({op}{value1.expression})"
24372367
elif np.isscalar(value2):
2438-
expression = f"({self.expression} {op} {value2})"
2368+
expression = f"({value1.expression} {op} {value2})"
24392369
elif hasattr(value2, "shape") and value2.shape == ():
2440-
expression = f"({self.expression} {op} {value2[()]})"
2370+
expression = f"({value1.expression} {op} {value2[()]})"
24412371
else:
24422372
operand_to_key = {id(v): k for k, v in value1.operands.items()}
24432373
try:
24442374
op_name = operand_to_key[id(value2)]
24452375
except KeyError:
2446-
op_name = f"o{len(self.operands)}"
2376+
op_name = f"o{len(value1.operands)}"
24472377
new_operands = {op_name: value2}
2448-
expression = f"({self.expression} {op} {op_name})"
2378+
expression = f"({value1.expression} {op} {op_name})"
24492379
self.operands = value1.operands
24502380
else:
24512381
if np.isscalar(value1):
2452-
expression = f"({value1} {op} {self.expression})"
2382+
expression = f"({value1} {op} {value2.expression})"
24532383
elif hasattr(value1, "shape") and value1.shape == ():
2454-
expression = f"({value1[()]} {op} {self.expression})"
2384+
expression = f"({value1[()]} {op} {value2.expression})"
24552385
else:
24562386
operand_to_key = {id(v): k for k, v in value2.operands.items()}
24572387
try:
@@ -2460,13 +2390,15 @@ def update_expr(self, new_op): # noqa: C901
24602390
op_name = f"o{len(value2.operands)}"
24612391
new_operands = {op_name: value1}
24622392
if op == "[]": # syntactic sugar for slicing
2463-
expression = f"({op_name}[{self.expression}])"
2393+
expression = f"({op_name}[{value2.expression}])"
24642394
else:
2465-
expression = f"({op_name} {op} {self.expression})"
2395+
expression = f"({op_name} {op} {value2.expression})"
24662396
self.operands = value2.operands
24672397
# Return a new expression
24682398
operands = self.operands | new_operands
2469-
return self._new_expr(expression, operands, guess=False, out=None, where=None)
2399+
expr = self._new_expr(expression, operands, guess=False, out=None, where=None)
2400+
expr._dtype = dtype_ # override dtype with preserved dtype
2401+
return expr
24702402
finally:
24712403
blosc2._disable_overloaded_equal = prev_flag
24722404

@@ -2556,107 +2488,6 @@ def blocks(self):
25562488
self._chunks, self._blocks = compute_chunks_blocks(self.shape, None, None, dtype=self.dtype)
25572489
return self._blocks
25582490

2559-
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
2560-
# Handle operations at the array level
2561-
if method != "__call__":
2562-
return NotImplemented
2563-
2564-
if ufunc in local_ufunc_map:
2565-
return local_ufunc_map[ufunc](*inputs)
2566-
2567-
if ufunc in ufunc_map:
2568-
value = inputs[0] if inputs[1] is self else inputs[1]
2569-
_check_allowed_dtypes(value)
2570-
return blosc2.LazyExpr(new_op=(value, ufunc_map[ufunc], self))
2571-
2572-
if ufunc in ufunc_map_1param:
2573-
value = inputs[0]
2574-
_check_allowed_dtypes(value)
2575-
return blosc2.LazyExpr(new_op=(value, ufunc_map_1param[ufunc], None))
2576-
2577-
return NotImplemented
2578-
2579-
def __neg__(self):
2580-
return self.update_expr(new_op=(0, "-", self))
2581-
2582-
def __add__(self, value):
2583-
return self.update_expr(new_op=(self, "+", value))
2584-
2585-
def __iadd__(self, other):
2586-
return self.update_expr(new_op=(self, "+", other))
2587-
2588-
def __radd__(self, value):
2589-
return self.update_expr(new_op=(value, "+", self))
2590-
2591-
def __sub__(self, value):
2592-
return self.update_expr(new_op=(self, "-", value))
2593-
2594-
def __isub__(self, value):
2595-
return self.update_expr(new_op=(self, "-", value))
2596-
2597-
def __rsub__(self, value):
2598-
return self.update_expr(new_op=(value, "-", self))
2599-
2600-
def __mul__(self, value):
2601-
return self.update_expr(new_op=(self, "*", value))
2602-
2603-
def __imul__(self, value):
2604-
return self.update_expr(new_op=(self, "*", value))
2605-
2606-
def __rmul__(self, value):
2607-
return self.update_expr(new_op=(value, "*", self))
2608-
2609-
def __truediv__(self, value):
2610-
return self.update_expr(new_op=(self, "/", value))
2611-
2612-
def __itruediv__(self, value):
2613-
return self.update_expr(new_op=(self, "/", value))
2614-
2615-
def __rtruediv__(self, value):
2616-
return self.update_expr(new_op=(value, "/", self))
2617-
2618-
def __and__(self, value):
2619-
return self.update_expr(new_op=(self, "&", value))
2620-
2621-
def __rand__(self, value):
2622-
return self.update_expr(new_op=(value, "&", self))
2623-
2624-
def __or__(self, value):
2625-
return self.update_expr(new_op=(self, "|", value))
2626-
2627-
def __ror__(self, value):
2628-
return self.update_expr(new_op=(value, "|", self))
2629-
2630-
def __invert__(self):
2631-
return self.update_expr(new_op=(self, "~", None))
2632-
2633-
def __pow__(self, value):
2634-
return self.update_expr(new_op=(self, "**", value))
2635-
2636-
def __rpow__(self, value):
2637-
return self.update_expr(new_op=(value, "**", self))
2638-
2639-
def __ipow__(self, value):
2640-
return self.update_expr(new_op=(self, "**", value))
2641-
2642-
def __lt__(self, value):
2643-
return self.update_expr(new_op=(self, "<", value))
2644-
2645-
def __le__(self, value):
2646-
return self.update_expr(new_op=(self, "<=", value))
2647-
2648-
def __eq__(self, value):
2649-
return self.update_expr(new_op=(self, "==", value))
2650-
2651-
def __ne__(self, value):
2652-
return self.update_expr(new_op=(self, "!=", value))
2653-
2654-
def __gt__(self, value):
2655-
return self.update_expr(new_op=(self, ">", value))
2656-
2657-
def __ge__(self, value):
2658-
return self.update_expr(new_op=(self, ">=", value))
2659-
26602491
def where(self, value1=None, value2=None):
26612492
"""
26622493
Select value1 or value2 values based on the condition of the current expression.

0 commit comments

Comments
 (0)