Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PossibleZeroQ #1100

Merged
merged 1 commit into from
Jan 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 68 additions & 6 deletions mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@
)

from mathics.core.expression import (
Complex,
Expression,
Number,
Integer,
Number,
Rational,
Real,
String,
Symbol,
SymbolFalse,
SymbolNull,
Complex,
String,
SymbolTrue,
SymbolFalse,
from_python,
)
from mathics.core.numbers import min_prec, dps, SpecialValueError

Expand Down Expand Up @@ -1368,8 +1369,8 @@ class Indeterminate(SympyConstant):
class NumberQ(Test):
"""
<dl>
<dt>'NumberQ[$expr$]'
<dd>returns 'True' if $expr$ is an explicit number, and 'False' otherwise.
<dt>'NumberQ[$expr$]'
<dd>returns 'True' if $expr$ is an explicit number, and 'False' otherwise.
</dl>

>> NumberQ[3+I]
Expand All @@ -1384,6 +1385,67 @@ def test(self, expr):
return isinstance(expr, Number)


class PossibleZeroQ(SympyFunction):
"""
<dl>
<dt>'PossibleZeroQ[$expr$]'
<dd>returns 'True' if basic symbolic and numerical methods suggest that expr has value zero, and 'False' otherwise.
</dl>

Test whether a numeric expression is zero:
>> PossibleZeroQ[E^(I Pi/4) - (-1)^(1/4)]
= True

The determination is approximate.

Test whether a symbolic expression is likely to be identically zero:
>> PossibleZeroQ[(x + 1) (x - 1) - x^2 + 1]
= True


>> PossibleZeroQ[(E + Pi)^2 - E^2 - Pi^2 - 2 E Pi]
= True

Show that a numeric expression is nonzero:
>> PossibleZeroQ[E^Pi - Pi^E]
= False

>> PossibleZeroQ[1/x + 1/y - (x + y)/(x y)]
= True

Decide that a numeric expression is zero, based on approximate computations:
>> PossibleZeroQ[2^(2 I) - 2^(-2 I) - 2 I Sin[Log[4]]]
= True

>> PossibleZeroQ[Sqrt[x^2] - x]
= False
"""

sympy_name = "_iszero"

def apply(self, expr, evaluation):
"%(name)s[expr_]"
from sympy.matrices.utilities import _iszero

sympy_expr = expr.to_sympy()
result = _iszero(sympy_expr)
if result is None:
# Can't get exact answer, so try approximate equal
numeric_val = Expression("N", expr).evaluate(evaluation)
if numeric_val and hasattr(numeric_val, "is_approx_zero"):
result = numeric_val.is_approx_zero
elif (
Expression("NumericQ", numeric_val).evaluate(evaluation) == SymbolFalse
):
return (
SymbolTrue
if Expression("Simplify", expr).evaluate(evaluation) == Integer(0)
else SymbolFalse
)

return from_python(result)


class RealNumberQ(Test):
"""
<dl>
Expand Down
14 changes: 8 additions & 6 deletions mathics/builtin/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@
PrecisionValueError,
)
from mathics.core.expression import (
Integer,
Real,
Complex,
Expression,
Integer,
MachineReal,
Number,
Symbol,
PrecisionReal,
Rational,
Real,
Symbol,
SymbolFalse,
SymbolTrue,
from_python,
MachineReal,
PrecisionReal,
)
from mathics.core.convert import from_sympy

Expand Down Expand Up @@ -748,7 +750,7 @@ def test(expr):
else:
return expr.is_numeric()

return Symbol("True") if test(expr) else Symbol("False")
return SymbolTrue if test(expr) else SymbolFalse


class RealValuedNumericQ(Builtin):
Expand Down
14 changes: 13 additions & 1 deletion mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2090,7 +2090,7 @@ def __neg__(self) -> 'Rational':

@property
def is_zero(self) -> bool:
return self.numerator == 0
return self.numerator().is_zero


class Real(Number):
Expand Down Expand Up @@ -2236,6 +2236,12 @@ def __neg__(self) -> 'MachineReal':
def is_zero(self) -> bool:
return self.value == 0.0

@property
def is_approx_zero(self) -> bool:
# FIXME: figure out how to hook int $MachinePrecision and
# what the right definition here would be.
return abs(self.value) <= 10**-14


class PrecisionReal(Real):
'''
Expand Down Expand Up @@ -2428,6 +2434,12 @@ def __neg__(self):
def is_zero(self) -> bool:
return self.real.is_zero and self.imag.is_zero

@property
def is_approx_zero(self) -> bool:
real_zero = self.real.is_approx_zero if hasattr(self.real, "is_approx_zero") else self.real.is_zero
imag_zero = self.imag.is_approx_zero if hasattr(self.imag, "is_approx_zero") else self.imag.is_zero
return real_zero and imag_zero


def encode_mathml(text: str) -> str:
text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
Expand Down