From 3a85f674bd9020e98764baef9573e50f13badcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Tue, 29 Jul 2025 11:04:57 +0200 Subject: [PATCH 1/5] Make Basic.has behave more like SymPy, use new visitor --- symengine/__init__.py | 2 +- symengine/lib/symengine.pxd | 1 + symengine/lib/symengine_wrapper.in.pyx | 12 ++++++++++-- symengine/tests/test_expr.py | 11 +++++++++++ symengine/tests/test_functions.py | 1 + symengine/tests/test_number.py | 6 +++++- symengine_version.txt | 2 +- 7 files changed, 30 insertions(+), 5 deletions(-) diff --git a/symengine/__init__.py b/symengine/__init__.py index e9545baf6..ced750579 100644 --- a/symengine/__init__.py +++ b/symengine/__init__.py @@ -19,7 +19,7 @@ Max, Min, DenseMatrix, Matrix, ImmutableMatrix, ImmutableDenseMatrix, MutableDenseMatrix, MatrixBase, Basic, DictBasic, symarray, series, diff, zeros, - eye, diag, ones, Derivative, Subs, expand, has_symbol, + eye, diag, ones, Derivative, Subs, expand, has_basic, has_symbol, UndefFunction, Function, UnevaluatedExpr, latex, have_numpy, true, false, Equality, Unequality, GreaterThan, LessThan, StrictGreaterThan, StrictLessThan, Eq, Ne, Ge, Le, diff --git a/symengine/lib/symengine.pxd b/symengine/lib/symengine.pxd index 65b3456aa..e6b963d35 100644 --- a/symengine/lib/symengine.pxd +++ b/symengine/lib/symengine.pxd @@ -769,6 +769,7 @@ cdef extern from "" namespace "SymEngine": unsigned next_prime() nogil cdef extern from "" namespace "SymEngine": + bool has_basic(const Basic &b, const Basic &x) nogil except + bool has_symbol(const Basic &b, const Basic &x) nogil except + rcp_const_basic coeff(const Basic &b, const Basic &x, const Basic &n) nogil except + set_basic free_symbols(const Basic &b) nogil except + diff --git a/symengine/lib/symengine_wrapper.in.pyx b/symengine/lib/symengine_wrapper.in.pyx index 413eb1843..d5c68b237 100644 --- a/symengine/lib/symengine_wrapper.in.pyx +++ b/symengine/lib/symengine_wrapper.in.pyx @@ -1187,8 +1187,11 @@ cdef class Basic(object): cdef Basic _n = sympify(n) return c2py(symengine.coeff(deref(self.thisptr), deref(_x.thisptr), deref(_n.thisptr))) - def has(self, *symbols): - return any([has_symbol(self, symbol) for symbol in symbols]) + def has(self, *args): + for arg in args: + if has_basic(self, arg): + return True + return False def args_as_sage(Basic self): cdef symengine.vec_basic Y = deref(self.thisptr).get_args() @@ -4945,6 +4948,11 @@ def powermod_list(a, b, m): s.append(c2py((v[i]))) return s +def has_basic(obj, looking_for=None): + cdef Basic b = _sympify(obj) + cdef Basic s = _sympify(looking_for) + return symengine.has_basic(deref(b.thisptr), deref(s.thisptr)) + def has_symbol(obj, symbol=None): cdef Basic b = _sympify(obj) cdef Basic s = _sympify(symbol) diff --git a/symengine/tests/test_expr.py b/symengine/tests/test_expr.py index 8cbf4ab7b..896b75107 100644 --- a/symengine/tests/test_expr.py +++ b/symengine/tests/test_expr.py @@ -26,3 +26,14 @@ def test_as_powers_dict(): assert (x*(1/Integer(2))**y).as_powers_dict() == {x: Integer(1), Integer(2): -y} assert (2**y).as_powers_dict() == {2: y} assert (2**-y).as_powers_dict() == {2: -y} + + +def test_Basic__has(): + x = Symbol('x') + y = Symbol('y') + xp3 = (x+3) + ym4 = (y-4) + e = xp3**ym4 + assert e.has(xp3) + assert e.has(ym4) + assert not e.has(y-5) diff --git a/symengine/tests/test_functions.py b/symengine/tests/test_functions.py index 207add989..7c4573dfd 100644 --- a/symengine/tests/test_functions.py +++ b/symengine/tests/test_functions.py @@ -86,6 +86,7 @@ def test_derivative(): fxy = Function("f")(x, y) assert (1+fxy).has(fxy) + assert (1+fxy).has(1) g = Derivative(Function("f")(x, y), x, 2, y, 1) assert g == fxy.diff(x, x, y) assert g == fxy.diff(y, 1, x, 2) diff --git a/symengine/tests/test_number.py b/symengine/tests/test_number.py index f61189a42..51165dcd4 100644 --- a/symengine/tests/test_number.py +++ b/symengine/tests/test_number.py @@ -1,6 +1,6 @@ from symengine.test_utilities import raises -from symengine import Integer, I, S, Symbol, pi, Rational +from symengine import Integer, I, S, Symbol, pi, Rational, has_basic from symengine.lib.symengine_wrapper import (perfect_power, is_square, integer_nthroot) @@ -100,6 +100,10 @@ def test_is_conditions(): assert pi.is_Atom +def test_has_basic(): + assert has_basic(3 + pi, pi) + + def test_perfect_power(): assert perfect_power(1) == True assert perfect_power(7) == False diff --git a/symengine_version.txt b/symengine_version.txt index e49372bea..f7b50225f 100644 --- a/symengine_version.txt +++ b/symengine_version.txt @@ -1 +1 @@ -c9510fb4b5c30b84adb993573a51f2a9a38a4cfe +10d3591214e3f5906106f35ed19b6428b3fd86a3 From e58bb984758b71be5fcea40e7503477504ca76c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Tue, 29 Jul 2025 11:27:02 +0200 Subject: [PATCH 2/5] More tests of .has(...) --- symengine/tests/test_expr.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/symengine/tests/test_expr.py b/symengine/tests/test_expr.py index 896b75107..a638fc431 100644 --- a/symengine/tests/test_expr.py +++ b/symengine/tests/test_expr.py @@ -37,3 +37,8 @@ def test_Basic__has(): assert e.has(xp3) assert e.has(ym4) assert not e.has(y-5) + + assert (x + oo).has(oo) + assert (x - oo).has(-oo) + assert not (x + oo).has(-oo) + #assert not (x - oo).has(oo) <-- not sure we want to test explicitly for "x + NegativeInfinity" From 6f30caa656b32f3d482db0c49338c4870a6903ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Tue, 29 Jul 2025 13:19:09 +0200 Subject: [PATCH 3/5] do not export has_basic --- symengine/__init__.py | 2 +- symengine/tests/test_number.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/symengine/__init__.py b/symengine/__init__.py index ced750579..e9545baf6 100644 --- a/symengine/__init__.py +++ b/symengine/__init__.py @@ -19,7 +19,7 @@ Max, Min, DenseMatrix, Matrix, ImmutableMatrix, ImmutableDenseMatrix, MutableDenseMatrix, MatrixBase, Basic, DictBasic, symarray, series, diff, zeros, - eye, diag, ones, Derivative, Subs, expand, has_basic, has_symbol, + eye, diag, ones, Derivative, Subs, expand, has_symbol, UndefFunction, Function, UnevaluatedExpr, latex, have_numpy, true, false, Equality, Unequality, GreaterThan, LessThan, StrictGreaterThan, StrictLessThan, Eq, Ne, Ge, Le, diff --git a/symengine/tests/test_number.py b/symengine/tests/test_number.py index 51165dcd4..f61189a42 100644 --- a/symengine/tests/test_number.py +++ b/symengine/tests/test_number.py @@ -1,6 +1,6 @@ from symengine.test_utilities import raises -from symengine import Integer, I, S, Symbol, pi, Rational, has_basic +from symengine import Integer, I, S, Symbol, pi, Rational from symengine.lib.symengine_wrapper import (perfect_power, is_square, integer_nthroot) @@ -100,10 +100,6 @@ def test_is_conditions(): assert pi.is_Atom -def test_has_basic(): - assert has_basic(3 + pi, pi) - - def test_perfect_power(): assert perfect_power(1) == True assert perfect_power(7) == False From e2e48991a9be50aae74bda8b12eab2e3a9256757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Mon, 4 Aug 2025 17:18:35 +0200 Subject: [PATCH 4/5] bump upstream symengine commit --- symengine_version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symengine_version.txt b/symengine_version.txt index f7b50225f..573952658 100644 --- a/symengine_version.txt +++ b/symengine_version.txt @@ -1 +1 @@ -10d3591214e3f5906106f35ed19b6428b3fd86a3 +8a3bc848cd8246a4c15eee9bddc15de0d1157812 From a9389cdf1eebb51b6ae20cc6f2dcfb5c544b5ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Mon, 4 Aug 2025 17:32:57 +0200 Subject: [PATCH 5/5] update tests of HasBasicVisitor --- symengine/tests/test_expr.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/symengine/tests/test_expr.py b/symengine/tests/test_expr.py index a638fc431..706073513 100644 --- a/symengine/tests/test_expr.py +++ b/symengine/tests/test_expr.py @@ -1,4 +1,4 @@ -from symengine import Symbol, Integer, oo +from symengine import Symbol, Integer, oo, sin from symengine.test_utilities import raises @@ -31,13 +31,12 @@ def test_as_powers_dict(): def test_Basic__has(): x = Symbol('x') y = Symbol('y') - xp3 = (x+3) - ym4 = (y-4) - e = xp3**ym4 - assert e.has(xp3) - assert e.has(ym4) - assert not e.has(y-5) - + xpowy = x**y + e = sin(xpowy) + assert e.has(x) + assert e.has(y) + assert e.has(xpowy) + raises(Exception, lambda: e.has(x+1)) # subtree matching of associative operators not yet supported assert (x + oo).has(oo) assert (x - oo).has(-oo) assert not (x + oo).has(-oo)