diff --git a/CHANGES.rst b/CHANGES.rst index 97f1ea3f4..c23414213 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,18 @@ CHANGES ======= +Internals +========= + +* now `Expression.is_numeric()` accepts an `Evaluation` object as a parameter, + to use the definitions. +* To numerify expressions, the function `_numeric_evaluation_with_prec` was introduced in the module `mathics.builtin.numeric` to avoid the idiom + `Expression("N", expr, prec).evaluate(evaluation)`. The idea is to avoid when it is possible to call the Pattern matching routines to obtain the numeric value of an expression. +* `Positive`, `Negative`, `NonPositive` and `NonNegative` do not check their arguments using the Pattern matching mechanism, but inside the Python code. +* special cases for `quick_pattern_test` were reduced as much as possible. +* A bug comming from a failure in the order in which `mathics.core.definitions` stores the rules was fixed. + + 4.0.1 ----- diff --git a/mathics/benchmark.py b/mathics/benchmark.py index 02c635b72..c114409bf 100644 --- a/mathics/benchmark.py +++ b/mathics/benchmark.py @@ -26,6 +26,41 @@ # Mathics expressions to benchmark BENCHMARKS = { + "NumericQ": [ + "NumericQ[Sqrt[2]]", + "NumericQ[Sqrt[-2]]", + "NumericQ[Sqrt[2.]]", + "NumericQ[Sqrt[-2.]]", + "NumericQ[q]", + 'NumericQ["q"]', + ], + # This function checks for numericQ in ints argument before calling + "Positive": [ + "Positive[Sqrt[2]]", + "Positive[Sqrt[-2]]", + "Positive[Sqrt[2.]]", + "Positive[Sqrt[-2.]]", + "Positive[q]", + 'Positive["q"]', + ], + # This function uses the WL rules definition, like in master + "NonNegative": [ + "NonNegative[Sqrt[2]]", + "NonNegative[Sqrt[-2]]", + "NonNegative[Sqrt[2.]]", + "NonNegative[Sqrt[-2.]]", + "NonNegative[q]", + 'NonNegative["q"]', + ], + # This function does the check inside the method. + "Negative": [ + "Negative[Sqrt[2]]", + "Negative[Sqrt[-2]]", + "Negative[Sqrt[2.]]", + "Negative[Sqrt[-2.]]", + "Negative[q]", + 'Negative["q"]', + ], "Arithmetic": ["1 + 2", "5 * 3"], "Plot": [ "Plot[0, {x, -3, 3}]", diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index e972c3535..19dd9c0b4 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -47,6 +47,7 @@ from mathics.core.convert import from_sympy, SympyExpression, sympy_symbol_prefix from mathics.builtin.scoping import dynamic_scoping from mathics.builtin.inference import get_assumptions_list, evaluate_predicate +from mathics.builtin.numeric import _numeric_evaluation_with_prec @lru_cache(maxsize=1024) @@ -133,7 +134,7 @@ def apply(self, z, evaluation): prec = min_prec(*args) d = dps(prec) args = [ - Expression(SymbolN, arg, Integer(d)).evaluate(evaluation) + _numeric_evaluation_with_prec(arg, evaluation, Integer(d)) for arg in args ] with mpmath.workprec(prec): diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 77a3087e6..7d6bcce56 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -527,11 +527,13 @@ def __init__(self, *args, **kwargs): class Test(Builtin): def apply(self, expr, evaluation) -> Symbol: "%(name)s[expr_]" - - if self.test(expr): + tst = self.test(expr) + if tst: return SymbolTrue - else: + elif tst is False: return SymbolFalse + else: + return class SympyFunction(SympyObject): diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 95fe1aa0a..219f3af0d 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -198,7 +198,7 @@ class _InequalityOperator(BinaryOperator): def numerify_args(items, evaluation): items_sequence = items.get_sequence() all_numeric = all( - item.is_numeric() and item.get_precision() is None + item.is_numeric(evaluation) and item.get_precision() is None for item in items_sequence ) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 55b4ee8cf..459493c4a 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -630,7 +630,7 @@ def apply_any(self, args, evaluation, options): timezone = Real(-time.timezone / 3600.0) else: timezone = options["System`TimeZone"].evaluate(evaluation) - if not timezone.is_numeric(): + if not timezone.is_numeric(evaluation): evaluation.message("DateObject", "notz", timezone) # TODO: if tz != timezone, shift the datetime list. @@ -1123,7 +1123,7 @@ def apply_2(self, expr, t, evaluation): def apply_3(self, expr, t, failexpr, evaluation): "TimeConstrained[expr_, t_, failexpr_]" t = t.evaluate(evaluation) - if not t.is_numeric(): + if not t.is_numeric(evaluation): evaluation.message("TimeConstrained", "timc", t) return try: diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 018fbd0c9..a2e45b1dc 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -156,7 +156,7 @@ def logical_expand_assumptions(assumptions_list, evaluation): evaluation.message("Assumption", "faas") changed = True continue - if assumption.is_numeric(): + if assumption.is_numeric(evaluation): evaluation.message("Assumption", "baas") changed = True continue @@ -306,7 +306,7 @@ def get_assumption_rules_dispatch(evaluation): if pat.has_form("Equal", 2): if value: lhs, rhs = pat._leaves - if lhs.is_numeric(): + if lhs.is_numeric(evaluation): assumption_rules.append(Rule(rhs, lhs)) else: assumption_rules.append(Rule(lhs, rhs)) diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index e6f0568d7..9a6c6d4bc 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -367,7 +367,7 @@ class QuotientRemainder(Builtin): def apply(self, m, n, evaluation): "QuotientRemainder[m_, n_]" - if m.is_numeric() and n.is_numeric(): + if m.is_numeric(evaluation) and n.is_numeric(): py_m = m.to_python() py_n = n.to_python() if py_n == 0: diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 9359b28a8..a468ce2a8 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -2704,7 +2704,7 @@ def convert_vectors(p): raise _IllegalDataPoint yield v - if dist_p[0].is_numeric(): + if dist_p[0].is_numeric(evaluation): numeric_p = [[x] for x in convert_scalars(dist_p)] else: numeric_p = list(convert_vectors(dist_p)) diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index 8528bde5a..2d10af5f9 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -49,7 +49,7 @@ def apply(self, l, evaluation): return self.rect(l) except _NotRectangularException: evaluation.message("Median", "rectn", Expression("Median", l)) - elif all(leaf.is_numeric() for leaf in l.leaves): + elif all(leaf.is_numeric(evaluation) for leaf in l.leaves): v = l.get_mutable_leaves() # copy needed for introselect n = len(v) if n % 2 == 0: # even number of elements? diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 7fb84c32c..bac364fd6 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1476,7 +1476,7 @@ def coeff_power_internal(self, expr, var_exprs, filt, evaluation, form="expr"): def key_powers(lst): key = Expression("Plus", *lst) key = key.evaluate(evaluation) - if key.is_numeric(): + if key.is_numeric(evaluation): return key.to_python() return 0 diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index f180eb0da..d987a1c62 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -18,7 +18,6 @@ SymbolTrue, SymbolFalse, SymbolList, - SymbolN, SymbolRule, SymbolUndefined, from_python, @@ -27,6 +26,7 @@ from mathics.core.rules import Pattern from mathics.core.numbers import dps from mathics.builtin.scoping import dynamic_scoping +from mathics.builtin.numeric import _numeric_evaluation_with_prec from mathics import Symbol import sympy @@ -1280,9 +1280,8 @@ def sub(evaluation): # TODO: use Precision goal... if x1 == x0: break - x0 = Expression(SymbolN, x1).evaluate( - evaluation - ) # N required due to bug in sympy arithmetic + x0 = _numeric_evaluation_with_prec(x1, evaluation) + # N required due to bug in sympy arithmetic count += 1 else: evaluation.message("FindRoot", "maxiter") @@ -1377,7 +1376,7 @@ class FindRoot(Builtin): def apply(self, f, x, x0, evaluation, options): "FindRoot[f_, {x_, x0_}, OptionsPattern[]]" # First, determine x0 and x - x0 = Expression(SymbolN, x0).evaluate(evaluation) + x0 = _numeric_evaluation_with_prec(x0, evaluation) if not isinstance(x0, Number): evaluation.message("FindRoot", "snum", x0) return @@ -1545,7 +1544,7 @@ def apply_makeboxes(self, x, x0, data, nmin, nmax, den, form, evaluation): expansion = [] for i, leaf in enumerate(data.leaves): - if leaf.is_numeric() and leaf.is_zero: + if leaf.is_numeric(evaluation) and leaf.is_zero: continue if powers[i].is_zero: expansion.append(leaf) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 982b3b6a2..a30235e2d 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -16,11 +16,12 @@ Rational, Symbol, from_python, - SymbolN, ) from mathics.core.convert import from_sympy, SympyPrime import mpmath +from mathics.builtin.numeric import _numeric_evaluation_with_prec + class ContinuedFraction(SympyFunction): """ @@ -412,13 +413,13 @@ def apply(self, n, b, evaluation): return expr if n_sympy.is_constant(): - temp_n = Expression(SymbolN, n).evaluate(evaluation) + temp_n = _numeric_evaluation_with_prec(n, evaluation) py_n = temp_n.to_python() else: return expr if b_sympy.is_constant(): - temp_b = Expression(SymbolN, b).evaluate(evaluation) + temp_b = _numeric_evaluation_with_prec(b, evaluation) py_b = temp_b.to_python() else: return expr @@ -443,7 +444,7 @@ def apply_2(self, n, evaluation): return expr # Handle Input with special cases such as PI and E if n_sympy.is_constant(): - temp_n = Expression(SymbolN, n).evaluate(evaluation) + temp_n = _numeric_evaluation_with_prec(n, evaluation) py_n = temp_n.to_python() else: return expr diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index d2dd03bd3..e45e1dc28 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -251,7 +251,7 @@ class _RandomBase(Builtin): def _size_to_python(self, domain, size, evaluation): is_proper_spec = size.get_head_name() == "System`List" and all( - n.is_numeric() for n in size.leaves + n.is_numeric(evaluation) for n in size.leaves ) py_size = size.to_python() if is_proper_spec else None @@ -589,7 +589,7 @@ def _weights_to_python(self, weights, evaluation): # we need to normalize weights as numpy.rand.randchoice expects this and as we can limit # accuracy problems with very large or very small weights by normalizing with sympy is_proper_spec = weights.get_head_name() == "System`List" and all( - w.is_numeric() for w in weights.leaves + w.is_numeric(evaluation) for w in weights.leaves ) if ( @@ -599,7 +599,7 @@ def _weights_to_python(self, weights, evaluation): "Divide", weights, Expression("Total", weights) ).evaluate(evaluation) if norm_weights is None or not all( - w.is_numeric() for w in norm_weights.leaves + w.is_numeric(evaluation) for w in norm_weights.leaves ): return evaluation.message(self.get_name(), "wghtv", weights), None weights = norm_weights diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index c651551f5..980a5d480 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -42,6 +42,7 @@ SymbolFalse, SymbolTrue, SymbolList, + SymbolMachinePrecision, SymbolN, from_python, ) @@ -61,6 +62,10 @@ def log_n_b(py_n, py_b) -> int: return int(mpmath.ceil(mpmath.log(py_n, py_b))) if py_n != 0 and py_n != 1 else 1 +def _numeric_evaluation_with_prec(expression, evaluation, prec=SymbolMachinePrecision): + return Expression("N", expression, prec).evaluate(evaluation) + + def _scipy_interface(integrator, options_map, mandatory=None, adapt_func=None): """ This function provides a proxy for scipy.integrate @@ -1126,7 +1131,7 @@ def apply_with_func_domain(self, func, domain, evaluation, options): (np.arctanh, lambda u: 1.0 / (1.0 - u ** 2)) ) else: - if not b.is_numeric(): + if not b.is_numeric(evaluation): evaluation.message("nlim", coords[i], b) return z = a.leaves[0].value @@ -1136,7 +1141,7 @@ def apply_with_func_domain(self, func, domain, evaluation, options): (lambda u: b - z + z / u, lambda u: -z * u ** (-2.0)) ) elif b.get_head_name() == "System`DirectedInfinity": - if not a.is_numeric(): + if not a.is_numeric(evaluation): evaluation.message("nlim", coords[i], a) return a = a.value @@ -1145,14 +1150,14 @@ def apply_with_func_domain(self, func, domain, evaluation, options): coordtransform.append( (lambda u: a - z + z / u, lambda u: z * u ** (-2.0)) ) - elif a.is_numeric() and b.is_numeric(): + elif a.is_numeric(evaluation) and b.is_numeric(evaluation): a = Expression(SymbolN, a).evaluate(evaluation).value b = Expression(SymbolN, b).evaluate(evaluation).value subdomain2.append([a, b]) coordtransform.append(None) else: for x in (a, b): - if not x.is_numeric(): + if not x.is_numeric(evaluation): evaluation.message("nlim", coords[i], x) return @@ -1228,17 +1233,7 @@ class NumericQ(Builtin): def apply(self, expr, evaluation): "NumericQ[expr_]" - - def test(expr): - if isinstance(expr, Expression): - attr = evaluation.definitions.get_attributes(expr.head.get_name()) - return "System`NumericFunction" in attr and all( - test(leaf) for leaf in expr.leaves - ) - else: - return expr.is_numeric() - - return SymbolTrue if test(expr) else SymbolFalse + return SymbolTrue if expr.is_numeric(evaluation) else SymbolFalse class Precision(Builtin): @@ -1648,7 +1643,7 @@ def apply(self, n, evaluation): if isinstance(n, Symbol) and n.name.startswith("System`"): return evaluation.message("RealDigits", "ndig", n) - if n.is_numeric(): + if n.is_numeric(evaluation): return self.apply_with_base(n, from_python(10), evaluation) def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 958ae6edc..eff83952a 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -358,7 +358,7 @@ def apply_list(self, expr, rules, evaluation, options): return rules maxit = self.get_option(options, "MaxIterations", evaluation) - if maxit.is_numeric(): + if maxit.is_numeric(evaluation): maxit = maxit.get_int_value() else: maxit = -1 diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index e0bf6c2f7..e7c2937ca 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -433,7 +433,7 @@ def apply(self, f, expr, n, evaluation, options): if count is None: count = self.get_option(options, "MaxIterations", evaluation) - if count.is_numeric(): + if count.is_numeric(evaluation): count = count.get_int_value() else: count = None diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 477e25981..0667e82d3 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -100,7 +100,7 @@ def is_relation(eqn): left.get_head_name() == func.get_head_name() and len(left.leaves) == 1 # noqa and isinstance(l.leaves[0].to_python(), int) - and r.is_numeric() + and r.is_numeric(evaluation) ): r_sympy = r.to_sympy() diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index 88ce36083..c1a11be79 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -83,7 +83,7 @@ def list_to_sparse(self, array, evaluation): for i, leaf in enumerate(array.leaves): if leaf.has_form("SparseArray", None) or leaf.has_form("List", None): return - if leaf.is_numeric() and leaf.is_zero: + if leaf.is_numeric(evaluation) and leaf.is_zero: continue leaves.append( Expression("Rule", Expression("List", Integer(i + 1)), leaf) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 224db67a5..14bdaa171 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -735,6 +735,8 @@ def get_tag_position(pattern, name) -> typing.Optional[str]: head_name = pattern.get_head_name() if head_name == name: return "down" + elif head_name == "System`N" and len(pattern.leaves) == 2: + return "n" elif head_name == "System`Condition" and len(pattern.leaves) > 0: return get_tag_position(pattern.leaves[0], name) elif pattern.get_lookup_name() == name: @@ -802,8 +804,6 @@ def __init__( self.downvalues = downvalues self.subvalues = subvalues self.upvalues = upvalues - for rule in rules: - self.add_rule(rule) self.formatvalues = dict((name, list) for name, list in formatvalues.items()) self.messages = messages self.attributes = set(attributes) @@ -813,6 +813,8 @@ def __init__( self.nvalues = nvalues self.defaultvalues = defaultvalues self.builtin = builtin + for rule in rules: + self.add_rule(rule) def get_values_list(self, pos): assert pos.isalpha() diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 296ea8560..20e2719ae 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -348,7 +348,7 @@ def is_atom(self) -> bool: def is_true(self) -> bool: return False - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: # used by NumericQ and expression ordering return False @@ -1837,24 +1837,29 @@ def thread(self, evaluation, head=None) -> typing.Tuple[bool, "Expression"]: leaves = [Expression(self._head, *item) for item in items] return True, Expression(head, *leaves) - def is_numeric(self) -> bool: - return ( - self._head.get_name() - in system_symbols( - "Sqrt", - "Times", - "Plus", - "Subtract", - "Minus", - "Power", - "Abs", - "Divide", - "Sin", + def is_numeric(self, evaluation=None) -> bool: + if evaluation: + if not "System`NumericFunction" in evaluation.definitions.get_attributes( + self._head.get_name() + ): + return False + return all(leaf.is_numeric(evaluation) for leaf in self._leaves) + else: + return ( + self._head.get_name() + in system_symbols( + "Sqrt", + "Times", + "Plus", + "Subtract", + "Minus", + "Power", + "Abs", + "Divide", + "Sin", + ) + and all(leaf.is_numeric() for leaf in self._leaves) ) - and all(leaf.is_numeric() for leaf in self._leaves) - ) - # TODO: complete list of numeric functions, or access NumericFunction - # attribute def numerify(self, evaluation) -> "Expression": _prec = None @@ -2088,7 +2093,7 @@ def evaluate(self, evaluation): def is_true(self) -> bool: return self == SymbolTrue - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: return self.name in system_symbols( "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" ) @@ -2113,6 +2118,7 @@ def __getnewargs__(self): SymbolFalse = Symbol("False") SymbolInfinity = Symbol("Infinity") SymbolList = Symbol("List") +SymbolMachinePrecision = Symbol("MachinePrecision") SymbolMakeBoxes = Symbol("MakeBoxes") SymbolN = Symbol("N") SymbolNull = Symbol("Null") @@ -2120,6 +2126,8 @@ def __getnewargs__(self): SymbolSequence = Symbol("Sequence") SymbolTrue = Symbol("True") SymbolUndefined = Symbol("Undefined") +SymbolLess = Symbol("Less") +SymbolGreater = Symbol("Greater") @lru_cache(maxsize=1024) @@ -2145,7 +2153,7 @@ class Number(Atom): def __str__(self) -> str: return str(self.value) - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: return True