Skip to content

Commit 3be7455

Browse files
committed
Allow to run miniexpr is more cases (specially 1-dim)
1 parent eb9d40a commit 3be7455

File tree

4 files changed

+55
-13
lines changed

4 files changed

+55
-13
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ set(MINIEXPR_BUILD_BENCH OFF CACHE BOOL "Build miniexpr benchmarks" FORCE)
5858

5959
FetchContent_Declare(miniexpr
6060
GIT_REPOSITORY https://github.com/Blosc/miniexpr.git
61-
GIT_TAG 3e0ad9f2800cfb46729da88553a9228845eaa731 # latest SIMD additions
61+
GIT_TAG sleef # latest SIMD additions
62+
# In case you want to use a local copy of miniexpr for development, uncomment the line below
63+
# SOURCE_DIR "/Users/faltet/blosc/miniexpr"
6264
)
6365
FetchContent_MakeAvailable(miniexpr)
6466

src/blosc2/blosc2_ext.pyx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,17 @@ cdef extern from "miniexpr.h":
573573
int me_compile(const char *expression, const me_variable *variables,
574574
int var_count, me_dtype dtype, int *error, me_expr **out)
575575

576+
cdef enum me_compile_status:
577+
ME_COMPILE_SUCCESS
578+
ME_COMPILE_ERR_OOM
579+
ME_COMPILE_ERR_PARSE
580+
ME_COMPILE_ERR_INVALID_ARG
581+
ME_COMPILE_ERR_COMPLEX_UNSUPPORTED
582+
ME_COMPILE_ERR_REDUCTION_INVALID
583+
ME_COMPILE_ERR_VAR_MIXED
584+
ME_COMPILE_ERR_VAR_UNSPECIFIED
585+
ME_COMPILE_ERR_INVALID_ARG_TYPE
586+
576587
int me_eval(const me_expr *expr, const void ** vars_chunk,
577588
int n_vars, void *output_chunk, int chunk_nitems) nogil
578589

@@ -2878,8 +2889,10 @@ cdef class NDArray:
28782889
expression = expression.encode("utf-8") if isinstance(expression, str) else expression
28792890
cdef me_dtype = me_dtype_from_numpy(self.dtype.num)
28802891
cdef me_expr *out_expr
2881-
error = me_compile(expression, variables, n, me_dtype, &error, &out_expr)
2882-
if error != 0:
2892+
cdef int rc = me_compile(expression, variables, n, me_dtype, &error, &out_expr)
2893+
if rc == ME_COMPILE_ERR_INVALID_ARG_TYPE:
2894+
raise TypeError(f"miniexpr does not support operand or output dtype: {expression}")
2895+
if rc != ME_COMPILE_SUCCESS:
28832896
raise NotImplementedError(f"Cannot compile expression: {expression}")
28842897
udata.miniexpr_handle = out_expr
28852898

src/blosc2/lazyexpr.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,9 @@ def fast_eval( # noqa: C901
12381238
ne_args = {}
12391239
dtype = kwargs.pop("dtype", None)
12401240
where: dict | None = kwargs.pop("_where_args", None)
1241+
if where is not None:
1242+
# miniexpr does not support where(); use the regular path.
1243+
use_miniexpr = False
12411244
if isinstance(out, blosc2.NDArray):
12421245
# If 'out' has been passed, and is a NDArray, use it as the base array
12431246
basearr = out
@@ -1290,15 +1293,17 @@ def fast_eval( # noqa: C901
12901293
use_miniexpr = False
12911294

12921295
if use_miniexpr:
1296+
op_dtypes = {op.dtype for op in operands.values() if isinstance(op, blosc2.NDArray)}
1297+
if len(op_dtypes) > 1:
1298+
use_miniexpr = False
1299+
# Avoid padding issues except for 1D arrays (contiguous along the only axis).
1300+
if len(shape) != 1 and builtins.any(s % c != 0 for s, c in zip(shape, chunks, strict=True)):
1301+
use_miniexpr = False
12931302
for op in operands.values():
12941303
# Only NDArray in-memory operands
12951304
if not (isinstance(op, blosc2.NDArray) and op.urlpath is None and out is None):
12961305
use_miniexpr = False
12971306
break
1298-
# Check that partitions are well-behaved (no padding)
1299-
if not blosc2.are_partitions_behaved(op.shape, op.chunks, op.blocks):
1300-
use_miniexpr = False
1301-
break
13021307
# Ensure blocks fit exactly in chunks
13031308
blocks_fit = builtins.all(c % b == 0 for c, b in zip(op.chunks, op.blocks, strict=True))
13041309
if not blocks_fit:
@@ -1310,7 +1315,7 @@ def fast_eval( # noqa: C901
13101315
# All values will be overwritten, so we can use an uninitialized array
13111316
res_eval = blosc2.uninit(shape, dtype, chunks=chunks, blocks=blocks, cparams=cparams, **kwargs)
13121317
try:
1313-
# print("expr->miniexpr:", expression)
1318+
print("expr->miniexpr:", expression)
13141319
res_eval._set_pref_expr(expression, operands)
13151320
# Data to compress is fetched from operands, so it can be uninitialized here
13161321
data = np.empty(res_eval.schunk.chunksize, dtype=np.uint8)
@@ -2001,6 +2006,16 @@ def reduce_slices( # noqa: C901
20012006

20022007
# Only behaved partitions are supported in miniexpr reductions
20032008
if use_miniexpr:
2009+
# Avoid padding issues except for 1D arrays (contiguous along the only axis).
2010+
if len(shape) != 1 and builtins.any(s % c != 0 for s, c in zip(shape, chunks, strict=True)):
2011+
use_miniexpr = False
2012+
if use_miniexpr and isinstance(expression, str):
2013+
has_complex = any(
2014+
isinstance(op, blosc2.NDArray) and blosc2.isdtype(op.dtype, "complex floating")
2015+
for op in operands.values()
2016+
)
2017+
if has_complex and any(tok in expression for tok in ("!=", "==", "<=", ">=", "<", ">")):
2018+
use_miniexpr = False
20042019
for op in operands.values():
20052020
# Check that chunksize is multiple of blocksize and blocks fit exactly in chunks
20062021
blocks_fit = builtins.all(c % b == 0 for c, b in zip(op.chunks, op.blocks, strict=True))

tests/ndarray/test_lazyexpr.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ def test_simple_expression(array_fixture):
180180
expr = a1 + a2 - a3 * a4
181181
nres = ne_evaluate("na1 + na2 - na3 * na4")
182182
res = expr.compute(cparams=blosc2.CParams())
183-
np.testing.assert_allclose(res[:], nres)
183+
if na1.dtype == np.float32:
184+
np.testing.assert_allclose(res[:], nres, rtol=1e-6, atol=1e-6)
185+
else:
186+
np.testing.assert_allclose(res[:], nres)
184187

185188

186189
# Mix Proxy and NDArray operands
@@ -205,10 +208,16 @@ def test_iXXX(array_fixture):
205208
expr **= 2.3 # __ipow__
206209
res = expr.compute()
207210
if not blosc2.IS_WASM:
208-
nres = ne_evaluate("(((((na1 ** 3 + na2 ** 2 + na3 ** 3 - na4 + 3) + 5) - 15) * 2) / 7) ** 2.3")
211+
expr_str = "(((((na1 ** 3 + na2 ** 2 + na3 ** 3 - na4 + 3) + 5) - 15) * 2) / 7) ** 2.3"
209212
else:
210-
nres = ne_evaluate("(((((na1 ** 3 + na2 ** 2 + na3 ** 3 - na4 + 3) + 5) - 15) * 2) / 7)")
211-
np.testing.assert_allclose(res[:], nres)
213+
expr_str = "(((((na1 ** 3 + na2 ** 2 + na3 ** 3 - na4 + 3) + 5) - 15) * 2) / 7)"
214+
if na1.dtype == np.float32:
215+
with np.errstate(invalid="ignore"):
216+
nres = eval(expr_str, {"np": np}, {"na1": na1, "na2": na2, "na3": na3, "na4": na4})
217+
np.testing.assert_allclose(res[:], nres, rtol=1e-5, atol=1e-6)
218+
else:
219+
nres = ne_evaluate(expr_str)
220+
np.testing.assert_allclose(res[:], nres)
212221

213222

214223
def test_complex_evaluate(array_fixture):
@@ -253,7 +262,10 @@ def test_expression_with_constants(array_fixture):
253262
# Test with operands with same chunks and blocks
254263
expr = a1 + 2 - a3 * 3.14
255264
nres = ne_evaluate("na1 + 2 - na3 * 3.14")
256-
np.testing.assert_allclose(expr[:], nres)
265+
if na1.dtype == np.float32:
266+
np.testing.assert_allclose(expr[:], nres, rtol=1e-6)
267+
else:
268+
np.testing.assert_allclose(expr[:], nres)
257269

258270

259271
@pytest.mark.parametrize("compare_expressions", [True, False])

0 commit comments

Comments
 (0)