Skip to content

Commit 95b870d

Browse files
authored
Merge branch 'main' into fix_out
2 parents e6fdf86 + 39f5928 commit 95b870d

File tree

7 files changed

+93
-65
lines changed

7 files changed

+93
-65
lines changed

.github/workflows/cibuildwheels.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,27 @@ jobs:
3636
# Linux x86_64 builds
3737
- os: ubuntu-latest
3838
arch: x86_64
39-
cibw_pattern: "cp3{10,11,12,13}-manylinux*"
39+
cibw_pattern: "cp3{10,11,12,13,14}-manylinux*"
4040
artifact_name: "linux-x86_64"
4141

4242
# Linux ARM64 builds (native runners)
4343
- os: ubuntu-24.04-arm
4444
arch: aarch64
45-
cibw_pattern: "cp3{10,11,12,13}-manylinux*"
45+
cibw_pattern: "cp3{10,11,12,13,14}-manylinux*"
4646
artifact_name: "linux-aarch64"
4747
# Don't use native runners for now (looks like wait times are too long)
4848
#runs-on: ["ubuntu-latest", "arm64"]
4949

5050
# Windows builds
5151
- os: windows-latest
5252
arch: x86_64
53-
cibw_pattern: "cp3{10,11,12,13}-win64"
53+
cibw_pattern: "cp3{10,11,12,13,14}-win64"
5454
artifact_name: "windows-x86_64"
5555

5656
# macOS builds (universal2)
5757
- os: macos-latest
5858
arch: x86_64
59-
cibw_pattern: "cp3{10,11,12,13}-macosx*"
59+
cibw_pattern: "cp3{10,11,12,13,14}-macosx*"
6060
artifact_name: "macos-universal2"
6161
steps:
6262
- name: Checkout repo

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ repos:
99
- repo: https://github.com/pre-commit/pre-commit-hooks
1010
rev: v6.0.0
1111
hooks:
12+
- id: check-toml
1213
- id: check-yaml
1314
- id: end-of-file-fixer
1415
- id: mixed-line-ending
15-
- id: requirements-txt-fixer
1616
- id: trailing-whitespace
1717

1818
- repo: https://github.com/astral-sh/ruff-pre-commit

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ classifiers = [
2828
"Programming Language :: Python :: 3.11",
2929
"Programming Language :: Python :: 3.12",
3030
"Programming Language :: Python :: 3.13",
31+
"Programming Language :: Python :: 3.14",
3132
]
3233
requires-python = ">=3.10"
3334
# Follow guidelines from https://scientific-python.org/specs/spec-0000/
@@ -36,7 +37,7 @@ dependencies = [
3637
"ndindex",
3738
"msgpack",
3839
"platformdirs",
39-
"numexpr>=2.13.1; platform_machine != 'wasm32'",
40+
"numexpr>=2.14.0; platform_machine != 'wasm32'",
4041
"py-cpuinfo; platform_machine != 'wasm32'",
4142
"requests",
4243
]

src/blosc2/core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,10 @@ def get_cache_info(cache_level: int) -> tuple:
11731173
if cache_level == 0:
11741174
cache_level = "1d"
11751175

1176-
result = subprocess.run(["lscpu", "--json"], capture_output=True, check=True, text=True)
1176+
try:
1177+
result = subprocess.run(["lscpu", "--json"], capture_output=True, check=True, text=True)
1178+
except (FileNotFoundError, subprocess.CalledProcessError) as err:
1179+
raise ValueError("lscpu not found or error running lscpu") from err
11771180
lscpu_info = json.loads(result.stdout)
11781181
for entry in lscpu_info["lscpu"]:
11791182
if entry["field"] == f"L{cache_level} cache:":

src/blosc2/lazyexpr.py

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ def ne_evaluate(expression, local_dict=None, **kwargs):
171171
relational_ops = ["==", "!=", "<", "<=", ">", ">="]
172172
logical_ops = ["&", "|", "^", "~"]
173173
not_complex_ops = ["maximum", "minimum", "<", "<=", ">", ">="]
174+
funcs_2args = (
175+
"arctan2",
176+
"contains",
177+
"pow",
178+
"power",
179+
"nextafter",
180+
"copysign",
181+
"hypot",
182+
"maximum",
183+
"minimum",
184+
)
174185

175186

176187
def get_expr_globals(expression):
@@ -2239,23 +2250,22 @@ def __init__(self, new_op): # noqa: C901
22392250
dtype_ = check_dtype(op, value1, value2) # perform some checks
22402251
if value2 is None:
22412252
if isinstance(value1, LazyExpr):
2242-
self.expression = f"{op}({value1.expression})"
2253+
self.expression = value1.expression if op is None else f"{op}({value1.expression})"
22432254
self.operands = value1.operands
22442255
else:
22452256
self.operands = {"o0": value1}
22462257
self.expression = "o0" if op is None else f"{op}(o0)"
22472258
return
2248-
elif op in (
2249-
"arctan2",
2250-
"contains",
2251-
"pow",
2252-
"power",
2253-
"nextafter",
2254-
"copysign",
2255-
"hypot",
2256-
"maximum",
2257-
"minimum",
2258-
):
2259+
elif isinstance(value1, LazyExpr) or isinstance(value2, LazyExpr):
2260+
if isinstance(value1, LazyExpr):
2261+
newexpr = value1.update_expr(new_op)
2262+
else:
2263+
newexpr = value2.update_expr(new_op)
2264+
self.expression = newexpr.expression
2265+
self.operands = newexpr.operands
2266+
self._dtype = newexpr.dtype
2267+
return
2268+
elif op in funcs_2args:
22592269
if np.isscalar(value1) and np.isscalar(value2):
22602270
self.expression = f"{op}({value1}, {value2})"
22612271
elif np.isscalar(value2):
@@ -2268,15 +2278,6 @@ def __init__(self, new_op): # noqa: C901
22682278
self.operands = {"o0": value1, "o1": value2}
22692279
self.expression = f"{op}(o0, o1)"
22702280
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
22802281

22812282
self._dtype = dtype_
22822283
if np.isscalar(value1) and np.isscalar(value2):
@@ -2351,51 +2352,64 @@ def update_expr(self, new_op): # noqa: C901
23512352
if not isinstance(value1, LazyExpr) and not isinstance(value2, LazyExpr):
23522353
# We converted some of the operands to NDArray (where() handling above)
23532354
new_operands = {"o0": value1, "o1": value2}
2354-
expression = f"(o0 {op} o1)"
2355+
expression = "op(o0, o1)" if op in funcs_2args else f"(o0 {op} o1)"
23552356
return self._new_expr(expression, new_operands, guess=False, out=None, where=None)
23562357
elif isinstance(value1, LazyExpr) and isinstance(value2, LazyExpr):
23572358
# Expression fusion
23582359
# Fuse operands in expressions and detect duplicates
23592360
new_operands, dup_op = fuse_operands(value1.operands, value2.operands)
23602361
# Take expression 2 and rebase the operands while removing duplicates
23612362
new_expr = fuse_expressions(value2.expression, len(value1.operands), dup_op)
2362-
expression = f"({value1.expression} {op} {new_expr})"
2363-
self.operands = value1.operands
2363+
expression = (
2364+
f"{op}({value1.expression}, {new_expr})"
2365+
if op in funcs_2args
2366+
else f"({value1.expression} {op} {new_expr})"
2367+
)
2368+
def_operands = value1.operands
23642369
elif isinstance(value1, LazyExpr):
2365-
if op == "~":
2366-
expression = f"({op}{value1.expression})"
2367-
elif np.isscalar(value2):
2368-
expression = f"({value1.expression} {op} {value2})"
2370+
if np.isscalar(value2):
2371+
v2 = value2
23692372
elif hasattr(value2, "shape") and value2.shape == ():
2370-
expression = f"({value1.expression} {op} {value2[()]})"
2373+
v2 = value2[()]
23712374
else:
23722375
operand_to_key = {id(v): k for k, v in value1.operands.items()}
23732376
try:
2374-
op_name = operand_to_key[id(value2)]
2377+
v2 = operand_to_key[id(value2)]
23752378
except KeyError:
2376-
op_name = f"o{len(value1.operands)}"
2377-
new_operands = {op_name: value2}
2378-
expression = f"({value1.expression} {op} {op_name})"
2379-
self.operands = value1.operands
2379+
v2 = f"o{len(value1.operands)}"
2380+
new_operands = {v2: value2}
2381+
if op == "~":
2382+
expression = f"({op}{value1.expression})"
2383+
else:
2384+
expression = (
2385+
f"{op}({value1.expression}, {v2})"
2386+
if op in funcs_2args
2387+
else f"({value1.expression} {op} {v2})"
2388+
)
2389+
def_operands = value1.operands
23802390
else:
23812391
if np.isscalar(value1):
2382-
expression = f"({value1} {op} {value2.expression})"
2392+
v1 = value1
23832393
elif hasattr(value1, "shape") and value1.shape == ():
2384-
expression = f"({value1[()]} {op} {value2.expression})"
2394+
v1 = value1[()]
23852395
else:
23862396
operand_to_key = {id(v): k for k, v in value2.operands.items()}
23872397
try:
2388-
op_name = operand_to_key[id(value1)]
2398+
v1 = operand_to_key[id(value1)]
23892399
except KeyError:
2390-
op_name = f"o{len(value2.operands)}"
2391-
new_operands = {op_name: value1}
2392-
if op == "[]": # syntactic sugar for slicing
2393-
expression = f"({op_name}[{value2.expression}])"
2394-
else:
2395-
expression = f"({op_name} {op} {value2.expression})"
2396-
self.operands = value2.operands
2400+
v1 = f"o{len(value2.operands)}"
2401+
new_operands = {v1: value1}
2402+
if op == "[]": # syntactic sugar for slicing
2403+
expression = f"({v1}[{value2.expression}])"
2404+
else:
2405+
expression = (
2406+
f"{op}({v1}, {value2.expression})"
2407+
if op in funcs_2args
2408+
else f"({v1} {op} {value2.expression})"
2409+
)
2410+
def_operands = value2.operands
23972411
# Return a new expression
2398-
operands = self.operands | new_operands
2412+
operands = def_operands | new_operands
23992413
expr = self._new_expr(expression, operands, guess=False, out=None, where=None)
24002414
expr._dtype = dtype_ # override dtype with preserved dtype
24012415
return expr

tests/ndarray/test_elementwise_funcs.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
SHAPES_CHUNKS_HEAVY = [((10, 13, 13), (3, 5, 2))]
3737

3838

39-
def _test_unary_func_impl(np_func, blosc_func, dtype, shape, chunkshape): # noqa: C901
39+
def _test_unary_func_impl(np_func, blosc_func, dtype, shape, chunkshape):
4040
"""Helper function containing the actual test logic for unary functions."""
4141
if np_func.__name__ in ("arccos", "arcsin", "arctanh"):
4242
a_blosc = blosc2.linspace(
@@ -94,18 +94,6 @@ def _test_unary_func_impl(np_func, blosc_func, dtype, shape, chunkshape): # noq
9494
assert True
9595
else:
9696
raise e
97-
except AssertionError as e:
98-
if np_func.__name__ in ("tan", "tanh") and dtype == blosc2.complex128:
99-
warnings.showwarning(
100-
"tan and tanh do not give correct NaN location",
101-
UserWarning,
102-
__file__,
103-
0,
104-
file=sys.stderr,
105-
)
106-
pytest.skip("tan and tanh do not give correct NaN location")
107-
else:
108-
raise e
10997

11098

11199
def _test_binary_func_impl(np_func, blosc_func, dtype, shape, chunkshape): # noqa: C901

tests/ndarray/test_lazyexpr.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,20 @@ def test_chain_expressions():
14401440
res = expr_final.compute()
14411441
np.testing.assert_allclose(res[:], nres)
14421442

1443+
# Test that update_expr does not alter expr1
1444+
expr1 = "a + b"
1445+
expr2 = "sin(a) + tan(c)"
1446+
lexpr1 = blosc2.lazyexpr(expr1)
1447+
lexpr2 = blosc2.lazyexpr(expr2)
1448+
lexpr3 = lexpr1 + lexpr2
1449+
assert lexpr1.expression == lexpr1.expression
1450+
assert lexpr1.operands == lexpr1.operands
1451+
assert lexpr2.expression == lexpr2.expression
1452+
assert lexpr2.operands == lexpr2.operands
1453+
lexpr1 += lexpr2
1454+
assert lexpr1.expression == lexpr3.expression
1455+
assert lexpr1.operands == lexpr3.operands
1456+
14431457

14441458
# Test the chaining of multiple persistent lazy expressions
14451459
def test_chain_persistentexpressions():
@@ -1746,3 +1760,11 @@ def test_lazyexpr_compute_out():
17461760
assert lexpr.compute(out=out) is out
17471761
assert out[0] == np.sin(1)
17481762
assert lexpr.compute() is not out
1763+
1764+
1765+
def test_lazyexpr_2args():
1766+
a = blosc2.ones(10)
1767+
lexpr = blosc2.lazyexpr("sin(a)")
1768+
newexpr = blosc2.hypot(lexpr, 3)
1769+
assert newexpr.expression == "hypot((sin(o0)), 3)"
1770+
assert newexpr.operands["o0"] is a

0 commit comments

Comments
 (0)