diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py
index 06a4337b0..8eaccab79 100644
--- a/mathics/builtin/base.py
+++ b/mathics/builtin/base.py
@@ -799,10 +799,14 @@ def get_operator_display(self) -> Optional[str]:
class Predefined(Builtin):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.symbol = Symbol(self.get_name())
+
def get_functions(self, prefix="eval", is_pymodule=False) -> List[Callable]:
functions = list(super().get_functions(prefix))
if prefix == "eval" and hasattr(self, "evaluate"):
- functions.append((Symbol(self.get_name()), self.evaluate))
+ functions.append((self.symbol, self.evaluate))
return functions
diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py
index 787826869..73779ff6a 100644
--- a/mathics/builtin/numbers/constants.py
+++ b/mathics/builtin/numbers/constants.py
@@ -9,25 +9,19 @@
# This tells documentation how to sort this module
sort_order = "mathics.builtin.mathematical-constants"
-
import math
+from typing import Optional
import mpmath
import numpy
import sympy
from mathics.builtin.base import Builtin, Predefined, SympyObject
-from mathics.core.atoms import MachineReal, PrecisionReal
+from mathics.core.atoms import NUMERICAL_CONSTANTS, MachineReal, PrecisionReal
from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
-from mathics.core.number import (
- MACHINE_DIGITS,
- MAX_MACHINE_NUMBER,
- MIN_MACHINE_NUMBER,
- PrecisionValueError,
- get_precision,
- prec,
-)
+from mathics.core.number import MACHINE_DIGITS, PrecisionValueError, get_precision, prec
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolIndeterminate
@@ -89,28 +83,33 @@ def eval_N(self, precision, evaluation):
def is_constant(self) -> bool:
return True
- def get_constant(self, precision, evaluation):
+ def get_constant(
+ self,
+ precision: Optional[BaseElement] = None,
+ evaluation: Optional[Evaluation] = None,
+ ):
# first, determine the precision
d = None
- if precision:
- try:
- d = get_precision(precision, evaluation)
- except PrecisionValueError:
- pass
+ preference = None
+ if evaluation:
+ if precision:
+ try:
+ d = get_precision(precision, evaluation)
+ except PrecisionValueError:
+ pass
+
+ preflist = evaluation._preferred_n_method.copy()
+ while preflist:
+ pref_method = preflist.pop()
+ if pref_method in ("numpy", "mpmath", "sympy"):
+ preference = pref_method
+ break
if d is None:
d = MACHINE_DIGITS
# If preference not especified, determine it
# from the precision.
- preference = None
- preflist = evaluation._preferred_n_method.copy()
- while preflist:
- pref_method = preflist.pop()
- if pref_method in ("numpy", "mpmath", "sympy"):
- preference = pref_method
- break
-
if preference is None:
if d <= MACHINE_DIGITS:
preference = "numpy"
@@ -131,10 +130,16 @@ def get_constant(self, precision, evaluation):
preference = "mpmath"
else:
preference = ""
+
if preference == "numpy":
- value = numpy_constant(self.numpy_name)
if d == MACHINE_DIGITS:
- return MachineReal(value)
+ try:
+ return NUMERICAL_CONSTANTS[self.symbol]
+ except KeyError:
+ value = MachineReal(numpy_constant(self.numpy_name))
+ NUMERICAL_CONSTANTS[self.symbol] = value
+ return value
+ value = numpy_constant(self.numpy_name)
if preference == "sympy":
value = sympy_constant(self.sympy_name, d + 2)
if preference == "mpmath":
@@ -177,13 +182,16 @@ class _NumpyConstant(_Constant_Common):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.numpy_name is None:
- self.numpy_name = strip_context(self.get_name()).lower()
+ self.numpy_name = strip_context(self.symbol.name).lower()
self.mathics_to_numpy[self.__class__.__name__] = self.numpy_name
+ try:
+ value_float = numpy_constant(self.numpy_name)
+ except AttributeError:
+ value_float = self.to_numpy(self.symbol)
+ NUMERICAL_CONSTANTS[self.symbol] = MachineReal(value_float)
def to_numpy(self, args):
- if self.numpy_name is None or len(args) != 0:
- return None
- return self.get_constant()
+ return NUMERICAL_CONSTANTS[self.symbol]
class _SympyConstant(_Constant_Common, SympyObject):
@@ -608,7 +616,7 @@ class MaxMachineNumber(Predefined):
summary_text = "largest normalized positive machine number"
def evaluate(self, evaluation: Evaluation) -> MachineReal:
- return MachineReal(MAX_MACHINE_NUMBER)
+ return NUMERICAL_CONSTANTS[self.symbol]
class MinMachineNumber(Predefined):
@@ -635,10 +643,10 @@ class MinMachineNumber(Predefined):
summary_text = "smallest normalized positive machine number"
def evaluate(self, evaluation: Evaluation) -> MachineReal:
- return MachineReal(MIN_MACHINE_NUMBER)
+ return NUMERICAL_CONSTANTS[self.symbol]
-class Pi(_MPMathConstant, _SympyConstant):
+class Pi(_MPMathConstant, _NumpyConstant, _SympyConstant):
"""
:Pi, \u03c0: https://en.wikipedia.org/wiki/Pi (
@@ -740,3 +748,10 @@ class Underflow(Builtin):
"Underflow[] * x_Real": "0.",
}
summary_text = "underflow in numeric evaluation"
+
+
+# Constants that are not numpy constants,
+for cls in (Catalan, Degree, Glaisher, GoldenRatio, Khinchin):
+ instance = cls(expression=False)
+ val = instance.get_constant()
+ NUMERICAL_CONSTANTS[instance.symbol] = MachineReal(val.value)
diff --git a/mathics/builtin/testing_expressions/numerical_properties.py b/mathics/builtin/testing_expressions/numerical_properties.py
index 8de45e8b1..5f5822f9e 100644
--- a/mathics/builtin/testing_expressions/numerical_properties.py
+++ b/mathics/builtin/testing_expressions/numerical_properties.py
@@ -13,6 +13,7 @@
from mathics.core.expression import Expression
from mathics.core.symbols import BooleanType, SymbolFalse, SymbolTrue
from mathics.core.systemsymbols import SymbolExpandAll, SymbolSimplify
+from mathics.eval.arithmetic import test_zero_arithmetic_expr
from mathics.eval.nevaluator import eval_N
@@ -460,6 +461,10 @@ def eval(self, expr, evaluation):
"%(name)s[expr_]"
from sympy.matrices.utilities import _iszero
+ # This handles most of the arithmetic cases
+ if test_zero_arithmetic_expr(expr):
+ return SymbolTrue
+
sympy_expr = expr.to_sympy()
result = _iszero(sympy_expr)
if result is None:
diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py
index f2b5c91a1..eadc66ad0 100644
--- a/mathics/core/atoms.py
+++ b/mathics/core/atoms.py
@@ -13,6 +13,8 @@
from mathics.core.number import (
FP_MANTISA_BINARY_DIGITS,
MACHINE_PRECISION_VALUE,
+ MAX_MACHINE_NUMBER,
+ MIN_MACHINE_NUMBER,
dps,
min_prec,
prec,
@@ -946,6 +948,14 @@ def is_zero(self) -> bool:
MATHICS3_COMPLEX_I = Complex(Integer0, Integer1)
MATHICS3_COMPLEX_I_NEG = Complex(Integer0, IntegerM1)
+# Numerical constants
+# These constants are populated by the `Predefined`
+# classes. See `mathics.builtin.numbers.constants`
+NUMERICAL_CONSTANTS = {
+ Symbol("System`$MaxMachineNumber"): MachineReal(MAX_MACHINE_NUMBER),
+ Symbol("System`$MinMachineNumber"): MachineReal(MIN_MACHINE_NUMBER),
+}
+
class String(Atom, BoxElementMixin):
value: str
diff --git a/mathics/core/number.py b/mathics/core/number.py
index f30de3b92..01673f0ea 100644
--- a/mathics/core/number.py
+++ b/mathics/core/number.py
@@ -68,7 +68,9 @@ def _get_float_inf(value, evaluation) -> Optional[float]:
return value.round_to_float(evaluation)
-def get_precision(value, evaluation, show_messages=True) -> Optional[float]:
+def get_precision(
+ value: BaseElement, evaluation, show_messages: bool = True
+) -> Optional[float]:
"""
Returns the ``float`` in the interval [``$MinPrecision``, ``$MaxPrecision``] closest to ``value``.
If ``value`` does not belongs to that interval, and ``show_messages`` is True, a Message warning is shown.
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index 52659e775..f463e5f91 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -24,6 +24,7 @@
# This list is sorted in alphabetic order.
SymbolAborted = Symbol("System`$Aborted")
+SymbolAbs = Symbol("System`Abs")
SymbolAccuracy = Symbol("System`Accuracy")
SymbolAll = Symbol("System`All")
SymbolAlternatives = Symbol("System`Alternatives")
@@ -84,6 +85,7 @@
SymbolEquivalent = Symbol("System`Equivalent")
SymbolEulerGamma = Symbol("System`EulerGamma")
SymbolExactNumberQ = Symbol("System`ExactNumberQ")
+SymbolExp = Symbol("System`Exp")
SymbolExpandAll = Symbol("System`ExpandAll")
SymbolExport = Symbol("System`Export")
SymbolExportString = Symbol("System`ExportString")
@@ -109,6 +111,7 @@
SymbolHoldForm = Symbol("System`HoldForm")
SymbolHoldPattern = Symbol("System`HoldPattern")
SymbolHue = Symbol("System`Hue")
+SymbolI = Symbol("System`I")
SymbolIf = Symbol("System`If")
SymbolIm = Symbol("System`Im")
SymbolImage = Symbol("System`Image")
@@ -126,6 +129,7 @@
SymbolLess = Symbol("System`Less")
SymbolLessEqual = Symbol("System`LessEqual")
SymbolKey = Symbol("System`Key")
+SymbolKhinchin = Symbol("System`Khinchin")
SymbolLetterCharacter = Symbol("System`LetterCharacter")
SymbolLine = Symbol("System`Line")
SymbolLog = Symbol("System`Log")
@@ -179,6 +183,7 @@
SymbolPi = Symbol("System`Pi")
SymbolPiecewise = Symbol("System`Piecewise")
SymbolPlot = Symbol("System`Plot")
+SymbolPlus = Symbol("System`Plus")
SymbolPoint = Symbol("System`Point")
SymbolPower = Symbol("System`Power")
SymbolPolygon = Symbol("System`Polygon")
@@ -242,6 +247,7 @@
SymbolTan = Symbol("System`Tan")
SymbolTanh = Symbol("System`Tanh")
SymbolTeXForm = Symbol("System`TeXForm")
+SymbolTimes = Symbol("System`Times")
SymbolThrow = Symbol("System`Throw")
SymbolThreshold = Symbol("System`Threshold")
SymbolToString = Symbol("System`ToString")
diff --git a/mathics/eval/arithmetic.py b/mathics/eval/arithmetic.py
index 0b9564067..00976ac7b 100644
--- a/mathics/eval/arithmetic.py
+++ b/mathics/eval/arithmetic.py
@@ -14,12 +14,14 @@
import sympy
from mathics.core.atoms import (
+ NUMERICAL_CONSTANTS,
Complex,
Integer,
Integer0,
Integer1,
Integer2,
IntegerM1,
+ MachineReal,
Number,
Rational,
RationalOneHalf,
@@ -29,11 +31,24 @@
from mathics.core.convert.sympy import from_sympy
from mathics.core.element import BaseElement, ElementsProperties
from mathics.core.expression import Expression
-from mathics.core.number import FP_MANTISA_BINARY_DIGITS, SpecialValueError, min_prec
-from mathics.core.symbols import Symbol, SymbolPlus, SymbolPower, SymbolTimes
-from mathics.core.systemsymbols import SymbolComplexInfinity, SymbolIndeterminate
+from mathics.core.number import (
+ FP_MANTISA_BINARY_DIGITS,
+ MAX_MACHINE_NUMBER,
+ MIN_MACHINE_NUMBER,
+ SpecialValueError,
+ min_prec,
+)
+from mathics.core.rules import Rule
+from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolPower, SymbolTimes
+from mathics.core.systemsymbols import (
+ SymbolComplexInfinity,
+ SymbolI,
+ SymbolIndeterminate,
+ SymbolLog,
+)
RationalMOneHalf = Rational(-1, 2)
+RealOne = Real(1.0)
# This cache might not be used that much.
@@ -374,3 +389,222 @@ def segregate_numbers_from_sorted_list(
if not isinstance(element, Number):
return list(elements[:pos]), list(elements[pos:])
return list(elements), []
+
+
+def test_arithmetic_expr(expr: BaseElement, only_real: bool = True) -> bool:
+ """
+ Check if an expression `expr` is an arithmetic expression
+ composed only by numbers and arithmetic operations.
+ If only_real is set to True, then `I` is not considered a number.
+ """
+ if isinstance(expr, (Integer, Rational, Real)):
+ return True
+ if expr in NUMERICAL_CONSTANTS:
+ return True
+ if isinstance(expr, Complex) or expr is SymbolI:
+ return not only_real
+ if isinstance(expr, Symbol):
+ return False
+
+ head, elements = expr.head, expr.elements
+
+ if head in (SymbolPlus, SymbolTimes):
+ return all(test_arithmetic_expr(term, only_real) for term in elements)
+ if expr.has_form("Exp", 1):
+ return test_arithmetic_expr(elements[0], only_real)
+ if head is SymbolLog:
+ if len(elements) > 2:
+ return False
+ if len(elements) == 2:
+ base = elements[0]
+ if not test_positive_arithmetic_expr(base):
+ return False
+ return test_arithmetic_expr(elements[-1], only_real)
+ if expr.has_form("Power", 2):
+ base, exponent = elements
+ if only_real:
+ if isinstance(exponent, Integer):
+ return test_arithmetic_expr(base)
+ return all(test_arithmetic_expr(item, only_real) for item in elements)
+ return False
+
+
+def test_negative_arithmetic_expr(expr: BaseElement) -> bool:
+ """
+ Check if the expression is an arithmetic expression
+ representing a negative value.
+ """
+ if isinstance(expr, (Integer, Rational, Real)):
+ return expr.value < 0
+
+ expr = eval_multiply_numbers(IntegerM1, expr)
+ return test_positive_arithmetic_expr(expr)
+
+
+def test_nonnegative_arithmetic_expr(expr: BaseElement) -> bool:
+ """
+ Check if the expression is an arithmetic expression
+ representing a nonnegative number
+ """
+ if not test_arithmetic_expr(expr):
+ return False
+
+ if test_zero_arithmetic_expr(expr) or test_positive_arithmetic_expr(expr):
+ return True
+
+
+def test_nonpositive_arithetic_expr(expr: BaseElement) -> bool:
+ """
+ Check if the expression is an arithmetic expression
+ representing a nonnegative number
+ """
+ if not test_arithmetic_expr(expr):
+ return False
+
+ if test_zero_arithmetic_expr(expr) or test_negative_arithmetic_expr(expr):
+ return True
+ return False
+
+
+def test_positive_arithmetic_expr(expr: BaseElement) -> bool:
+ """
+ Check if the expression is an arithmetic expression
+ representing a positive value.
+ """
+ if isinstance(expr, (Integer, Rational, Real)):
+ return expr.value > 0
+ if expr in NUMERICAL_CONSTANTS:
+ return True
+ if isinstance(expr, Atom):
+ return False
+
+ head, elements = expr.get_head(), expr.elements
+
+ if head is SymbolPlus:
+ positive_nonpositive_terms = {True: [], False: []}
+ for term in elements:
+ positive_nonpositive_terms[test_positive_arithmetic_expr(term)].append(term)
+
+ if len(positive_nonpositive_terms[False]) == 0:
+ return True
+ if len(positive_nonpositive_terms[True]) == 0:
+ return False
+
+ pos, neg = (
+ eval_add_numbers(*items) for items in positive_nonpositive_terms.values()
+ )
+ if neg.is_zero:
+ return True
+ if not test_arithmetic_expr(neg):
+ return False
+
+ total = eval_add_numbers(pos, neg)
+ # Check positivity of the evaluated expression
+ if isinstance(total, (Integer, Rational, Real)):
+ return total.value > 0
+ if isinstance(total, Complex):
+ return False
+ if total.sameQ(expr):
+ return False
+ return test_positive_arithmetic_expr(total)
+
+ if head is SymbolTimes:
+ nonpositive_factors = tuple(
+ (item for item in elements if not test_positive_arithmetic_expr(item))
+ )
+ if len(nonpositive_factors) == 0:
+ return True
+ evaluated_expr = eval_multiply_numbers(*nonpositive_factors)
+ if evaluated_expr.sameQ(expr):
+ return False
+ return test_positive_arithmetic_expr(evaluated_expr)
+ if expr.has_form("Power", 2):
+ base, exponent = elements
+ if isinstance(exponent, Integer) and exponent.value % 2 == 0:
+ return test_arithmetic_expr(base)
+ return test_arithmetic_expr(exponent) and test_positive_arithmetic_expr(base)
+ if expr.has_form("Exp", 1):
+ return test_arithmetic_expr(expr.elements[0], only_real=True)
+ if expr.has_form("Sqrt", 1):
+ return test_positive_arithmetic_expr(expr.elements[0])
+ if head is SymbolLog:
+ if len(elements) > 2:
+ return False
+ if len(elements) == 2:
+ if not test_positive_arithmetic_expr(elements[0]):
+ return False
+ arg = elements[-1]
+ return test_positive_arithmetic_expr(eval_add_numbers(arg, IntegerM1))
+ if expr.has_form("Abs", 1):
+ arg = elements[0]
+ return test_arithmetic_expr(
+ arg, only_real=False
+ ) and not test_zero_arithmetic_expr(arg)
+ if head.has_form("DirectedInfinity", 1):
+ return test_positive_arithmetic_expr(elements[0])
+
+ return False
+
+
+def test_zero_arithmetic_expr(expr: BaseElement, numeric: bool = False) -> bool:
+ """
+ return True if expr evaluates to a number compatible
+ with 0
+ """
+
+ def is_numeric_zero(z: Number):
+ if isinstance(z, Complex):
+ if abs(z.real.value) + abs(z.imag.value) < 2.0e-10:
+ return True
+ if isinstance(z, Number):
+ if abs(z.value) < 1e-10:
+ return True
+ return False
+
+ if expr.is_zero:
+ return True
+ if numeric:
+ if is_numeric_zero(expr):
+ return True
+ expr = to_inexact_value(expr)
+ if expr.has_form("Times", None):
+ if any(
+ test_zero_arithmetic_expr(element, numeric=numeric)
+ for element in expr.elements
+ ) and not any(
+ element.has_form("DirectedInfinity", None) for element in expr.elements
+ ):
+ return True
+ if expr.has_form("Power", 2):
+ base, exp = expr.elements
+ if test_zero_arithmetic_expr(base, numeric):
+ return test_nonnegative_arithmetic_expr(exp)
+ if base.has_form("DirectedInfinity", None):
+ return test_positive_arithmetic_expr(exp)
+ if expr.has_form("Plus", None):
+ result = eval_add_numbers(*expr.elements)
+ if numeric:
+ if isinstance(result, complex):
+ if abs(result.real.value) + abs(result.imag.value) < 2.0e-10:
+ return True
+ if isinstance(result, Number):
+ if abs(result.value) < 1e-10:
+ return True
+ return result.is_zero
+ return False
+
+
+def to_inexact_value(expr: BaseElement) -> BaseElement:
+ """
+ Converts an expression into an inexact expression.
+ Replaces numerical constants by their numerical approximation,
+ and then multiplies the expression by Real(1.)
+ """
+ if expr.is_inexact():
+ return expr
+
+ if isinstance(expr, Expression):
+ for const, value in NUMERICAL_CONSTANTS.items():
+ expr, success = expr.do_apply_rule(Rule(const, value))
+
+ return eval_multiply_numbers(RealOne, expr)
diff --git a/mathics/eval/parts.py b/mathics/eval/parts.py
index 3ecf356d1..61a1adf33 100644
--- a/mathics/eval/parts.py
+++ b/mathics/eval/parts.py
@@ -6,7 +6,7 @@
from typing import List
-from mathics.core.atoms import Integer, Integer1
+from mathics.core.atoms import Integer
from mathics.core.convert.expression import make_expression
from mathics.core.element import BaseElement, BoxElementMixin
from mathics.core.exceptions import (
@@ -20,11 +20,7 @@
from mathics.core.list import ListExpression
from mathics.core.subexpression import SubExpression
from mathics.core.symbols import Atom, Symbol, SymbolList
-from mathics.core.systemsymbols import (
- SymbolDirectedInfinity,
- SymbolInfinity,
- SymbolNothing,
-)
+from mathics.core.systemsymbols import SymbolInfinity, SymbolNothing
from mathics.eval.patterns import Matcher
diff --git a/mathics/session.py b/mathics/session.py
index 410d9c2b1..043106b34 100644
--- a/mathics/session.py
+++ b/mathics/session.py
@@ -100,3 +100,9 @@ def format_result(self, str_expression=None, timeout=None, form=None):
if form is None:
form = self.form
return res.do_format(self.evaluation, form)
+
+ def parse(self, str_expression):
+ """
+ Just parse the expression
+ """
+ return parse(self.definitions, MathicsSingleLineFeeder(str_expression))
diff --git a/test/builtin/arithmetic/test_abs.py b/test/builtin/arithmetic/test_abs.py
index c523bf1d3..11b3da92b 100644
--- a/test/builtin/arithmetic/test_abs.py
+++ b/test/builtin/arithmetic/test_abs.py
@@ -42,8 +42,7 @@ def test_abs(str_expr, str_expected, msg):
("Sign[2+3 I]", "(2 + 3 I)/(13^(1/2))", None),
("Sign[2.+3 I]", "0.5547 + 0.83205 I", None),
("Sign[4^(2 Pi)]", "1", None),
- # FIXME: add rules to handle this kind of case
- # ("Sign[I^(2 Pi)]", "I^(2 Pi)", None),
+ ("Sign[I^(2 Pi)]", "I^(2 Pi)", None),
# ("Sign[4^(2 Pi I)]", "1", None),
],
)
diff --git a/test/builtin/arithmetic/test_lowlevel_properties.py b/test/builtin/arithmetic/test_lowlevel_properties.py
new file mode 100644
index 000000000..10cebfd06
--- /dev/null
+++ b/test/builtin/arithmetic/test_lowlevel_properties.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.eval.arithmetic low level positivity tests
+"""
+from test.helper import session
+
+import pytest
+
+from mathics.eval.arithmetic import (
+ test_arithmetic_expr as check_arithmetic,
+ test_positive_arithmetic_expr as check_positive,
+ test_zero_arithmetic_expr as check_zero,
+)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected", "msg"),
+ [
+ ("I", False, None),
+ ("0", False, None),
+ ("1", True, None),
+ ("Pi", True, None),
+ ("a", False, None),
+ ("-Pi", False, None),
+ ("(-1)^2", True, None),
+ ("(-1)^3", False, None),
+ ("Sqrt[2]", True, None),
+ ("Sqrt[-2]", False, None),
+ ("(-2)^(1/2)", False, None),
+ ("(2)^(1/2)", True, None),
+ ("Exp[a]", False, None),
+ ("Exp[2.3]", True, None),
+ ("Log[1/2]", False, None),
+ ("Exp[I]", False, None),
+ ("Log[3]", True, None),
+ ("Log[I]", False, None),
+ ("Abs[a]", False, None),
+ ("Abs[0]", False, None),
+ ("Abs[1+3 I]", True, None),
+ ("Sin[Pi]", False, None),
+ ],
+)
+def test_positivity(str_expr, expected, msg):
+ expr = session.parse(str_expr)
+ if msg:
+ assert check_positive(expr) == expected, msg
+ else:
+ assert check_positive(expr) == expected
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected", "msg"),
+ [
+ ("I", False, None),
+ ("0", True, None),
+ ("1", False, None),
+ ("Pi", False, None),
+ ("a", False, None),
+ ("a-a", True, None),
+ ("3-3.", True, None),
+ ("2-Sqrt[4]", True, None),
+ ("-Pi", False, None),
+ ("(-1)^2", False, None),
+ ("(-1)^3", False, None),
+ ("Sqrt[2]", False, None),
+ ("Sqrt[-2]", False, None),
+ ("(-2)^(1/2)", False, None),
+ ("(2)^(1/2)", False, None),
+ ("Exp[a]", False, None),
+ ("Exp[2.3]", False, None),
+ ("Log[1/2]", False, None),
+ ("Exp[I]", False, None),
+ ("Log[3]", False, None),
+ ("Log[I]", False, None),
+ ("Abs[a]", False, None),
+ ("Abs[0]", False, None),
+ ("Abs[1+3 I]", False, None),
+ # ("Sin[Pi]", False, None),
+ ],
+)
+def test_zero(str_expr, expected, msg):
+ expr = session.parse(str_expr)
+ if msg:
+ assert check_zero(expr) == expected, msg
+ else:
+ assert check_zero(expr) == expected