From 4259e11edd44d5dac5a58dc62288b6cf44870ce9 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Fri, 21 Nov 2025 00:46:55 +0800 Subject: [PATCH 01/15] Fix consistency of != with is_zero() and matrix symmetry Ensure consistency between != and is_zero() for variables with domain constraints and fix matrix symmetry issue. --- src/sage/symbolic/expression.pyx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 90fa44941c3..88d7eeaa911 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3374,6 +3374,25 @@ cdef class Expression(Expression_abc): sage: expr = reduce(lambda u, v: 1/u -v, [1/pi] + list(continued_fraction(pi)[:20])) sage: expr.is_zero() False + + Check that ``!=`` is consistent with ``is_zero()`` for variables with domain constraints:: + + sage: x12 = var('x12', domain='real') + sage: bool(x12 != 0) + True + sage: bool(x12 != 0) == (not x12.is_zero()) + True + sage: bool(0 != x12) + True + sage: bool(0 != x12) == (not x12.is_zero()) + True + + This also fixes the matrix symmetry issue:: + + sage: x11, x12, x22 = SR.var('x11, x12, x21', domain='real') + sage: X = matrix(SR, [[x11,x12],[0,x22]]) + sage: X.is_symmetric() + False """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3417,10 +3436,17 @@ cdef class Expression(Expression_abc): if set(vars).intersection(assumption_vars): need_assumptions = True + # Use is_zero() for != 0 when pynac can't decide + # This ensures consistency between x != 0 and not x.is_zero() + if (pynac_result == relational_notimplemented and + self.operator() == operator.ne): + if self.rhs().is_zero(): + return not self.lhs().is_zero() + elif self.lhs().is_zero(): + return not self.rhs().is_zero() + # Use interval fields to try and falsify the relation if not need_assumptions: - if pynac_result == relational_notimplemented and self.operator() == operator.ne: - return not (self.lhs()-self.rhs()).is_zero() res = self.test_relation() if res in (True, False): return res From 062bac7763411146cb78a0b388d0c2b983a5e807 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Fri, 21 Nov 2025 22:20:06 +0800 Subject: [PATCH 02/15] fix it with minimal affect Refactor checks for variable inequality and zero consistency, including domain constraints and assumptions. --- src/sage/symbolic/expression.pyx | 37 +++++++++++--------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 88d7eeaa911..fae548e7c11 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3375,24 +3375,17 @@ cdef class Expression(Expression_abc): sage: expr.is_zero() False - Check that ``!=`` is consistent with ``is_zero()`` for variables with domain constraints:: + Check that :issue:`16031` is fixed:: - sage: x12 = var('x12', domain='real') - sage: bool(x12 != 0) - True - sage: bool(x12 != 0) == (not x12.is_zero()) + sage: y = SR.var("y") + sage: bool(y != 0) True - sage: bool(0 != x12) + sage: y = SR.var("y", domain="real") + sage: bool(y != 0) True - sage: bool(0 != x12) == (not x12.is_zero()) + sage: z = SR.var("z", domain="complex") + sage: bool(z != 0) True - - This also fixes the matrix symmetry issue:: - - sage: x11, x12, x22 = SR.var('x11, x12, x21', domain='real') - sage: X = matrix(SR, [[x11,x12],[0,x22]]) - sage: X.is_symmetric() - False """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3431,22 +3424,18 @@ cdef class Expression(Expression_abc): try: assumption_var_list.append(eqn.variables()) except AttributeError: # if we have a GenericDeclaration - assumption_var_list.append((eqn._var,)) + # Skip domain declarations (real/complex) as they don't + # provide information about whether a variable is zero + if eqn._assumption not in ('real', 'complex'): + assumption_var_list.append((eqn._var,)) assumption_vars = set(sum(assumption_var_list, ())) if set(vars).intersection(assumption_vars): need_assumptions = True - # Use is_zero() for != 0 when pynac can't decide - # This ensures consistency between x != 0 and not x.is_zero() - if (pynac_result == relational_notimplemented and - self.operator() == operator.ne): - if self.rhs().is_zero(): - return not self.lhs().is_zero() - elif self.lhs().is_zero(): - return not self.rhs().is_zero() - # Use interval fields to try and falsify the relation if not need_assumptions: + if pynac_result == relational_notimplemented and self.operator() == operator.ne: + return not (self.lhs()-self.rhs()).is_zero() res = self.test_relation() if res in (True, False): return res From edfb92eb85801aa10647175854cc87e20559e61c Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Fri, 21 Nov 2025 22:29:45 +0800 Subject: [PATCH 03/15] add domain integer --- src/sage/symbolic/expression.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index fae548e7c11..0a4d7c9300d 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3386,6 +3386,9 @@ cdef class Expression(Expression_abc): sage: z = SR.var("z", domain="complex") sage: bool(z != 0) True + sage: z = SR.var("z", domain="integer") + sage: bool(z != 0) + True """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3424,9 +3427,9 @@ cdef class Expression(Expression_abc): try: assumption_var_list.append(eqn.variables()) except AttributeError: # if we have a GenericDeclaration - # Skip domain declarations (real/complex) as they don't + # Skip domain declarations (real/complex/integer) as they don't # provide information about whether a variable is zero - if eqn._assumption not in ('real', 'complex'): + if eqn._assumption not in ('real', 'complex', 'integer'): assumption_var_list.append((eqn._var,)) assumption_vars = set(sum(assumption_var_list, ())) if set(vars).intersection(assumption_vars): From be49822e330d2aaea67302027afdab19eac4d179 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Sat, 22 Nov 2025 00:03:36 +0800 Subject: [PATCH 04/15] Update expression.pyx --- src/sage/symbolic/expression.pyx | 34 ++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 0a4d7c9300d..ce98aa353cb 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3375,19 +3375,28 @@ cdef class Expression(Expression_abc): sage: expr.is_zero() False - Check that :issue:`16031` is fixed:: + Check that domain declarations (real/complex/integer) don't incorrectly + affect inequality testing:: sage: y = SR.var("y") sage: bool(y != 0) True + sage: bool(y != 1) + True sage: y = SR.var("y", domain="real") sage: bool(y != 0) True + sage: bool(y != 1) + True sage: z = SR.var("z", domain="complex") sage: bool(z != 0) True - sage: z = SR.var("z", domain="integer") - sage: bool(z != 0) + sage: bool(z != 1) + True + sage: w = SR.var("w", domain="integer") + sage: bool(w != 0) + True + sage: bool(w != 1) True """ if self.is_relational(): @@ -3423,17 +3432,30 @@ cdef class Expression(Expression_abc): vars = self.variables() if vars: assumption_var_list = [] + has_integer_assumption = False for eqn in assumption_list: try: assumption_var_list.append(eqn.variables()) except AttributeError: # if we have a GenericDeclaration - # Skip domain declarations (real/complex/integer) as they don't - # provide information about whether a variable is zero - if eqn._assumption not in ('real', 'complex', 'integer'): + # Skip pure domain declarations (real/complex) that don't + # provide information about whether a variable is zero. + if eqn._assumption not in ('real', 'complex'): assumption_var_list.append((eqn._var,)) + # Track if we have integer assumptions + if eqn._assumption == 'integer': + has_integer_assumption = True assumption_vars = set(sum(assumption_var_list, ())) if set(vars).intersection(assumption_vars): need_assumptions = True + # Special case: for simple variable inequalities with integer/real/complex + # domain assumptions, don't use Maxima (it incorrectly returns False) + if has_integer_assumption and self.operator() == operator.ne: + # Check if this is a simple case like "x != c" where c is a constant + lhs = self.lhs() + rhs = self.rhs() + if ((lhs.is_symbol() and not rhs.has(lhs)) or + (rhs.is_symbol() and not lhs.has(rhs))): + need_assumptions = False # Use interval fields to try and falsify the relation if not need_assumptions: From fb488993d91d7a540c26b4f2656ed2dce8b655a8 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Sat, 22 Nov 2025 04:31:07 +0800 Subject: [PATCH 05/15] Update src/sage/symbolic/expression.pyx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sage/symbolic/expression.pyx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index ce98aa353cb..1761a922c84 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3447,8 +3447,9 @@ cdef class Expression(Expression_abc): assumption_vars = set(sum(assumption_var_list, ())) if set(vars).intersection(assumption_vars): need_assumptions = True - # Special case: for simple variable inequalities with integer/real/complex - # domain assumptions, don't use Maxima (it incorrectly returns False) + # Special case: for simple variable inequalities with integer assumptions, + # don't use Maxima (it incorrectly returns False). The real/complex cases are + # already handled above by not adding them to assumption_var_list. if has_integer_assumption and self.operator() == operator.ne: # Check if this is a simple case like "x != c" where c is a constant lhs = self.lhs() From 4e7e7fe19215cfe9fd309c36f31148de55542e8d Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Sat, 22 Nov 2025 04:31:16 +0800 Subject: [PATCH 06/15] Update src/sage/symbolic/expression.pyx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sage/symbolic/expression.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 1761a922c84..0a9074dd571 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3392,7 +3392,7 @@ cdef class Expression(Expression_abc): sage: bool(z != 0) True sage: bool(z != 1) - True + True sage: w = SR.var("w", domain="integer") sage: bool(w != 0) True From 2a795121679b603e295c49a3faba77e3f52b011f Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 03:14:01 +0800 Subject: [PATCH 07/15] Clean up assertions and domain checks in expression.pyx Removed assertions and domain declaration checks from the expression.pyx file. --- src/sage/symbolic/expression.pyx | 45 +------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 0a9074dd571..ae3d47aed74 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3286,8 +3286,6 @@ cdef class Expression(Expression_abc): :: - sage: assert(not x == 1) - sage: assert(not x != 1) sage: forget() sage: assume(x>y) sage: assert(not x==y) @@ -3374,30 +3372,6 @@ cdef class Expression(Expression_abc): sage: expr = reduce(lambda u, v: 1/u -v, [1/pi] + list(continued_fraction(pi)[:20])) sage: expr.is_zero() False - - Check that domain declarations (real/complex/integer) don't incorrectly - affect inequality testing:: - - sage: y = SR.var("y") - sage: bool(y != 0) - True - sage: bool(y != 1) - True - sage: y = SR.var("y", domain="real") - sage: bool(y != 0) - True - sage: bool(y != 1) - True - sage: z = SR.var("z", domain="complex") - sage: bool(z != 0) - True - sage: bool(z != 1) - True - sage: w = SR.var("w", domain="integer") - sage: bool(w != 0) - True - sage: bool(w != 1) - True """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3432,31 +3406,14 @@ cdef class Expression(Expression_abc): vars = self.variables() if vars: assumption_var_list = [] - has_integer_assumption = False for eqn in assumption_list: try: assumption_var_list.append(eqn.variables()) except AttributeError: # if we have a GenericDeclaration - # Skip pure domain declarations (real/complex) that don't - # provide information about whether a variable is zero. - if eqn._assumption not in ('real', 'complex'): - assumption_var_list.append((eqn._var,)) - # Track if we have integer assumptions - if eqn._assumption == 'integer': - has_integer_assumption = True + assumption_var_list.append((eqn._var,)) assumption_vars = set(sum(assumption_var_list, ())) if set(vars).intersection(assumption_vars): need_assumptions = True - # Special case: for simple variable inequalities with integer assumptions, - # don't use Maxima (it incorrectly returns False). The real/complex cases are - # already handled above by not adding them to assumption_var_list. - if has_integer_assumption and self.operator() == operator.ne: - # Check if this is a simple case like "x != c" where c is a constant - lhs = self.lhs() - rhs = self.rhs() - if ((lhs.is_symbol() and not rhs.has(lhs)) or - (rhs.is_symbol() and not lhs.has(rhs))): - need_assumptions = False # Use interval fields to try and falsify the relation if not need_assumptions: From 254f6e7757e32758009a59357418eb39b5b01773 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 03:17:04 +0800 Subject: [PATCH 08/15] Implement special case for inequality in check_relation_maxima Added special case handling for inequality checks in relation. --- src/sage/symbolic/relation.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 38e0abd9f18..deeed681e1b 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -420,7 +420,7 @@ def check_relation_maxima(relation): sage: check_relation_maxima(x!=0) True sage: check_relation_maxima(x!=1) - False + True sage: forget() TESTS: @@ -519,6 +519,16 @@ def check_relation_maxima(relation): elif s == 'false': return False # if neither of these, s=='unknown' and we try a few other tricks + # Special case for inequality (!=): if Maxima returns 'unknown', + # try checking equality (==) instead and return the opposite result. + # This preserves semantic consistency with bool(x != y) = not bool(x == y). + if relation.operator() == operator.ne: + # Check equality using the full check_relation_maxima logic + eq_relation = (relation.lhs() == relation.rhs()) + eq_result = check_relation_maxima(eq_relation) + # Return the opposite of the equality result + return not eq_result + if relation.operator() != operator.eq: return False From d83549472dc95fd1a222b29b0b41f0a3fe8b9053 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 03:21:12 +0800 Subject: [PATCH 09/15] add doctests for issue --- src/sage/symbolic/expression.pyx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index ae3d47aed74..8535ad8f9bb 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3372,6 +3372,21 @@ cdef class Expression(Expression_abc): sage: expr = reduce(lambda u, v: 1/u -v, [1/pi] + list(continued_fraction(pi)[:20])) sage: expr.is_zero() False + + Check that :issue:`41125` is fixed:: + + sage: y = SR.var("y") + sage: bool(y != 0) + True + sage: y = SR.var("y", domain="real") + sage: bool(y != 0) + True + sage: z = SR.var("z", domain="complex") + sage: bool(z != 0) + True + sage: z = SR.var("z", domain="integer") + sage: bool(z != 0) + True """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly From 642070d20d689d4cc1aa9b123ff537cdf2c17288 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 16:25:07 +0800 Subject: [PATCH 10/15] Add a function to regard neq as not eq --- src/sage/symbolic/relation.py | 90 +++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index deeed681e1b..cbed3a43d08 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -420,7 +420,7 @@ def check_relation_maxima(relation): sage: check_relation_maxima(x!=0) True sage: check_relation_maxima(x!=1) - True + False sage: forget() TESTS: @@ -488,6 +488,85 @@ def check_relation_maxima(relation): """ m = relation._maxima_() + # Handle some basic cases first + if repr(m) in ['0=0']: + return True + elif repr(m) in ['0#0', '1#1']: + return False + + if relation.operator() == operator.eq: # operator is equality + try: + s = m.parent()._eval_line('is (equal(%s,%s))' % (repr(m.lhs()), + repr(m.rhs()))) + except TypeError: + raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) + + elif relation.operator() == operator.ne: # operator is not equal + try: + s = m.parent()._eval_line('is (notequal(%s,%s))' % (repr(m.lhs()), + repr(m.rhs()))) + except TypeError: + raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) + + else: # operator is < or > or <= or >=, which Maxima handles fine + try: + s = m.parent()._eval_line('is (%s)' % repr(m)) + except TypeError: + raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) + + if s == 'true': + return True + elif s == 'false': + return False # if neither of these, s=='unknown' and we try a few other tricks + + if relation.operator() != operator.eq: + return False + + difference = relation.lhs() - relation.rhs() + if difference.is_trivial_zero(): + return True + + # Try to apply some simplifications to see if left - right == 0. + # + # TODO: If simplify_log() is ever removed from simplify_full(), we + # can replace all of these individual simplifications with a + # single call to simplify_full(). That would work in cases where + # two simplifications are needed consecutively; the current + # approach does not. + # + simp_list = [difference.simplify_factorial(), + difference.simplify_rational(), + difference.simplify_rectform(), + difference.simplify_trig()] + for f in simp_list: + try: + if f().is_trivial_zero(): + return True + break + except Exception: + pass + return False + +def check_relation_maxima_neq_as_not_eq(relation): + """ + A variant of :func:`check_relation_maxima` that treats `x != y` + as `not (x == y)` for consistency with Python's boolean semantics. + + EXAMPLES:: + + sage: from sage.symbolic.relation import check_relation_maxima_neq_as_not_eq + sage: x = var('x') + sage: check_relation_maxima_neq_as_not_eq(x != x) + False + sage: check_relation_maxima_neq_as_not_eq(x == x) + True + sage: check_relation_maxima_neq_as_not_eq(x != 1) + True + sage: check_relation_maxima_neq_as_not_eq(x == 1) + False + """ + m = relation._maxima_() + # Handle some basic cases first if repr(m) in ['0=0']: return True @@ -536,14 +615,6 @@ def check_relation_maxima(relation): if difference.is_trivial_zero(): return True - # Try to apply some simplifications to see if left - right == 0. - # - # TODO: If simplify_log() is ever removed from simplify_full(), we - # can replace all of these individual simplifications with a - # single call to simplify_full(). That would work in cases where - # two simplifications are needed consecutively; the current - # approach does not. - # simp_list = [difference.simplify_factorial(), difference.simplify_rational(), difference.simplify_rectform(), @@ -557,7 +628,6 @@ def check_relation_maxima(relation): pass return False - def string_to_list_of_solutions(s): r""" Used internally by the symbolic solve command to convert the output From b3745b6733c55b347f37383cacb8175db487ee30 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 16:25:56 +0800 Subject: [PATCH 11/15] Replace check_relation_maxima with check_relation_maxima_neq_as_not_eq --- src/sage/symbolic/expression.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 8535ad8f9bb..fa753ce5030 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3447,9 +3447,9 @@ cdef class Expression(Expression_abc): # associated with different semantics, different # precision, etc., that can lead to subtle bugs. Also, a # lot of basic Sage objects can't be put into maxima. - from sage.symbolic.relation import check_relation_maxima + from sage.symbolic.relation import check_relation_maxima_neq_as_not_eq if self.variables(): - return check_relation_maxima(self) + return check_relation_maxima_neq_as_not_eq(self) else: return False From fd915d18ab38ccafd6014d13d54f2a72b8ab9ba8 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Mon, 24 Nov 2025 16:31:46 +0800 Subject: [PATCH 12/15] fix lint --- src/sage/symbolic/relation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index cbed3a43d08..3a374797c32 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -547,6 +547,7 @@ def check_relation_maxima(relation): pass return False + def check_relation_maxima_neq_as_not_eq(relation): """ A variant of :func:`check_relation_maxima` that treats `x != y` @@ -628,6 +629,7 @@ def check_relation_maxima_neq_as_not_eq(relation): pass return False + def string_to_list_of_solutions(s): r""" Used internally by the symbolic solve command to convert the output From 9d91b8dbd2218854f0d2ec3de7599d27002225b2 Mon Sep 17 00:00:00 2001 From: cxzhong Date: Tue, 25 Nov 2025 11:19:16 +0800 Subject: [PATCH 13/15] Refactor the check_relation_maxima --- src/sage/symbolic/expression.pyx | 13 +--- src/sage/symbolic/relation.py | 116 +++++++------------------------ 2 files changed, 29 insertions(+), 100 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index fa753ce5030..2336f471927 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3401,17 +3401,8 @@ cdef class Expression(Expression_abc): return pynac_result == relational_true if pynac_result == relational_true: - if self.operator() == operator.ne: - # this hack is necessary to catch the case where the - # operator is != but is False because of assumptions made - m = self._maxima_() - s = m.parent()._eval_line('is (notequal(%s,%s))' % (repr(m.lhs()),repr(m.rhs()))) - if s == 'false': - return False - else: - return True - else: - return True + #In fact, it will return notimplemented for the unequal cases unknown to be true + return True # If assumptions are involved, falsification is more complicated... need_assumptions = False diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 3a374797c32..a211557827b 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -486,31 +486,30 @@ def check_relation_maxima(relation): sage: assumptions() [k is noninteger] """ - m = relation._maxima_() + from sage.interfaces.maxima_lib import maxima - # Handle some basic cases first - if repr(m) in ['0=0']: - return True - elif repr(m) in ['0#0', '1#1']: - return False + # Use _maxima_init_() to get proper string representation with _SAGE_VAR_ prefixes + # This ensures assumptions are properly recognized by Maxima + lhs_str = relation.lhs()._maxima_init_() + rhs_str = relation.rhs()._maxima_init_() if relation.operator() == operator.eq: # operator is equality try: - s = m.parent()._eval_line('is (equal(%s,%s))' % (repr(m.lhs()), - repr(m.rhs()))) + s = maxima._eval_line('is (equal(%s,%s))' % (lhs_str, rhs_str)) except TypeError: raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) elif relation.operator() == operator.ne: # operator is not equal try: - s = m.parent()._eval_line('is (notequal(%s,%s))' % (repr(m.lhs()), - repr(m.rhs()))) + s = maxima._eval_line('is (notequal(%s,%s))' % (lhs_str, rhs_str)) except TypeError: raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) else: # operator is < or > or <= or >=, which Maxima handles fine try: - s = m.parent()._eval_line('is (%s)' % repr(m)) + # For inequalities, use the full relation string + relation_str = relation._maxima_init_() + s = maxima._eval_line('is (%s)' % relation_str) except TypeError: raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) @@ -526,25 +525,14 @@ def check_relation_maxima(relation): if difference.is_trivial_zero(): return True - # Try to apply some simplifications to see if left - right == 0. - # - # TODO: If simplify_log() is ever removed from simplify_full(), we - # can replace all of these individual simplifications with a - # single call to simplify_full(). That would work in cases where - # two simplifications are needed consecutively; the current - # approach does not. - # - simp_list = [difference.simplify_factorial(), - difference.simplify_rational(), - difference.simplify_rectform(), - difference.simplify_trig()] - for f in simp_list: - try: - if f().is_trivial_zero(): - return True - break - except Exception: - pass + # Try simplify_full() to see if left - right == 0. + # Note: simplify_full() does not include simplify_log(), which is + # unsafe for complex variables, so this is safe to call here. + try: + if difference.simplify_full().is_trivial_zero(): + return True + except Exception: + pass return False @@ -553,6 +541,10 @@ def check_relation_maxima_neq_as_not_eq(relation): A variant of :func:`check_relation_maxima` that treats `x != y` as `not (x == y)` for consistency with Python's boolean semantics. + For inequality relations (!=), this function checks the corresponding + equality and returns its logical negation, ensuring that + ``bool(x != y) == not bool(x == y)``. + EXAMPLES:: sage: from sage.symbolic.relation import check_relation_maxima_neq_as_not_eq @@ -566,68 +558,14 @@ def check_relation_maxima_neq_as_not_eq(relation): sage: check_relation_maxima_neq_as_not_eq(x == 1) False """ - m = relation._maxima_() - - # Handle some basic cases first - if repr(m) in ['0=0']: - return True - elif repr(m) in ['0#0', '1#1']: - return False - - if relation.operator() == operator.eq: # operator is equality - try: - s = m.parent()._eval_line('is (equal(%s,%s))' % (repr(m.lhs()), - repr(m.rhs()))) - except TypeError: - raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) - - elif relation.operator() == operator.ne: # operator is not equal - try: - s = m.parent()._eval_line('is (notequal(%s,%s))' % (repr(m.lhs()), - repr(m.rhs()))) - except TypeError: - raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) - - else: # operator is < or > or <= or >=, which Maxima handles fine - try: - s = m.parent()._eval_line('is (%s)' % repr(m)) - except TypeError: - raise ValueError("unable to evaluate the predicate '%s'" % repr(relation)) - - if s == 'true': - return True - elif s == 'false': - return False # if neither of these, s=='unknown' and we try a few other tricks - - # Special case for inequality (!=): if Maxima returns 'unknown', - # try checking equality (==) instead and return the opposite result. - # This preserves semantic consistency with bool(x != y) = not bool(x == y). + # For inequality (!=), check equality and return the opposite. + # This ensures bool(x != y) == not bool(x == y) for semantic consistency. if relation.operator() == operator.ne: - # Check equality using the full check_relation_maxima logic eq_relation = (relation.lhs() == relation.rhs()) - eq_result = check_relation_maxima(eq_relation) - # Return the opposite of the equality result - return not eq_result + return not check_relation_maxima(eq_relation) - if relation.operator() != operator.eq: - return False - - difference = relation.lhs() - relation.rhs() - if difference.is_trivial_zero(): - return True - - simp_list = [difference.simplify_factorial(), - difference.simplify_rational(), - difference.simplify_rectform(), - difference.simplify_trig()] - for f in simp_list: - try: - if f().is_trivial_zero(): - return True - break - except Exception: - pass - return False + # For all other relations, delegate to check_relation_maxima + return check_relation_maxima(relation) def string_to_list_of_solutions(s): From f037b732b36f88a9436d0398f107776d92dc6d07 Mon Sep 17 00:00:00 2001 From: cxzhong Date: Tue, 25 Nov 2025 11:26:24 +0800 Subject: [PATCH 14/15] Add doctest for negation of equality in Expression class --- src/sage/symbolic/expression.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 2336f471927..3b628664af9 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -3286,6 +3286,7 @@ cdef class Expression(Expression_abc): :: + sage: assert(not x == 1) sage: forget() sage: assume(x>y) sage: assert(not x==y) From cbd99e64d2ac35f41956dfd6cd6b58183b093538 Mon Sep 17 00:00:00 2001 From: Chenxin Zhong Date: Wed, 26 Nov 2025 21:37:25 +0800 Subject: [PATCH 15/15] Handle boolean values in check_relation_maxima function --- src/sage/symbolic/relation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index a211557827b..27959aa157c 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -485,7 +485,18 @@ def check_relation_maxima(relation): [k == 1/2*I*sqrt(3) - 1/2, k == -1/2*I*sqrt(3) - 1/2] sage: assumptions() [k is noninteger] + + Check that boolean values are handled correctly:: + + sage: check_relation_maxima(2 == 2) + True + sage: check_relation_maxima(2 == 3) + False """ + # Handle boolean values directly (e.g., when comparing Python integers) + if isinstance(relation, bool): + return relation + from sage.interfaces.maxima_lib import maxima # Use _maxima_init_() to get proper string representation with _SAGE_VAR_ prefixes