diff --git a/CHANGES.rst b/CHANGES.rst index 628d809e7..7e8baf9a0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,11 +4,6 @@ CHANGES ======= -API ---- - -* ``eval_sign`` and ``eval_abs`` are now in ``mathics.eval.arithmetic``. - Compatibility ------------- @@ -16,6 +11,12 @@ Compatibility +Internals +--- + +* ``eval_abs`` and ``eval_sign`` extracted from ``Abs`` and ``Sign`` and added to ``mathics.eval.arithmetic``. + + Bugs ---- diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index fb8300916..6f8ecd74f 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -7,15 +7,11 @@ Basic arithmetic functions, including complex number arithmetic. """ -import sympy - -# This tells documentation how to sort this module -sort_order = "mathics.builtin.mathematical-functions" - - from functools import lru_cache +from typing import Optional import mpmath +import sympy from mathics.builtin.base import ( Builtin, @@ -48,6 +44,7 @@ from mathics.core.convert.expression import to_expression from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix from mathics.core.element import ElementsProperties +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import dps, min_prec @@ -69,10 +66,13 @@ SymbolTable, SymbolUndefined, ) -from mathics.eval.arithmetic import eval_abs, eval_mpmath_function, eval_sign +from mathics.eval.arithmetic import eval_Abs, eval_mpmath_function, eval_Sign from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify +# This tells documentation how to sort this module +sort_order = "mathics.builtin.mathematical-functions" + ExpressionComplexInfinity = Expression(SymbolDirectedInfinity) @@ -98,7 +98,7 @@ def get_mpmath_function(self, args): return None return getattr(mpmath, self.mpmath_name) - def eval(self, z, evaluation): + def eval(self, z, evaluation: Evaluation): "%(name)s[z__]" args = numerify(z, evaluation).get_sequence() @@ -171,34 +171,35 @@ def create_infix(items, operator, prec, grouping): class Abs(_MPMathFunction): """ - - :Absolute value: - https://en.wikipedia.org/wiki/Absolute_value ( - :SymPy: - https://docs.sympy.org/latest/modules/functions/elementary.html#sympy.functions.elementary.complexes.Abs, - :WMA: https://reference.wolfram.com/language/ref/Abs) - -
-
'Abs[$x$]' -
returns the absolute value of $x$. -
- - >> Abs[-3] - = 3 - - >> Plot[Abs[x], {x, -4, 4}] - = -Graphics- - - 'Abs' returns the magnitude of complex numbers: - >> Abs[3 + I] - = Sqrt[10] - >> Abs[3.0 + I] - = 3.16228 - - All of the below evaluate to Infinity: - - >> Abs[Infinity] == Abs[I Infinity] == Abs[ComplexInfinity] - = True + + :Absolute value: + https://en.wikipedia.org/wiki/Absolute_value ( + :SymPy: + https://docs.sympy.org/latest/modules/functions/ + elementary.html#sympy.functions.elementary.complexes.Abs, + :WMA: https://reference.wolfram.com/language/ref/Abs) + +
+
'Abs[$x$]' +
returns the absolute value of $x$. +
+ + >> Abs[-3] + = 3 + + >> Plot[Abs[x], {x, -4, 4}] + = -Graphics- + + 'Abs' returns the magnitude of complex numbers: + >> Abs[3 + I] + = Sqrt[10] + >> Abs[3.0 + I] + = 3.16228 + + All of the below evaluate to Infinity: + + >> Abs[Infinity] == Abs[I Infinity] == Abs[ComplexInfinity] + = True """ mpmath_name = "fabs" # mpmath actually uses python abs(x) / x.__abs__() @@ -208,9 +209,9 @@ class Abs(_MPMathFunction): summary_text = "absolute value of a number" sympy_name = "Abs" - def eval(self, x, evaluation): - "%(name)s[x_]" - result = eval_abs(x) + def eval(self, x, evaluation: Evaluation): + "Abs[x_]" + result = eval_Abs(x) if result is not None: return result return super(Abs, self).eval(x, evaluation) @@ -271,7 +272,7 @@ class Arg(_MPMathFunction): sympy_name = "arg" def eval(self, z, evaluation, options={}): - "%(name)s[z_, OptionsPattern[%(name)s]]" + "Arg[z_, OptionsPattern[Arg]]" if Expression(SymbolPossibleZeroQ, z).evaluate(evaluation) is SymbolTrue: return Integer0 preference = self.get_option(options, "Method", evaluation).get_string_value() @@ -309,7 +310,7 @@ class Assuming(Builtin): summary_text = "set assumptions during the evaluation" attributes = A_HOLD_REST | A_PROTECTED - def eval_assuming(self, assumptions, expr, evaluation): + def eval_assuming(self, assumptions, expr, evaluation: Evaluation): "Assuming[assumptions_, expr_]" assumptions = assumptions.evaluate(evaluation) if assumptions is SymbolTrue: @@ -365,11 +366,11 @@ class Boole(Builtin): = Boole[a == 7] """ - summary_text = "translate 'True' to 1, and 'False' to 0" attributes = A_LISTABLE | A_PROTECTED + summary_text = "translate 'True' to 1, and 'False' to 0" - def eval(self, expr, evaluation): - "%(name)s[expr_]" + def eval(self, expr, evaluation: Evaluation): + "Boole[expr_]" if expr is SymbolTrue: return Integer1 elif expr is SymbolFalse: @@ -439,8 +440,8 @@ class Complex_(Builtin): summary_text = "head for complex numbers" name = "Complex" - def eval(self, r, i, evaluation): - "%(name)s[r_?NumberQ, i_?NumberQ]" + def eval(self, r, i, evaluation: Evaluation): + "Complex[r_?NumberQ, i_?NumberQ]" if isinstance(r, Complex) or isinstance(i, Complex): sym_form = r.to_sympy() + sympy.I * i.to_sympy() @@ -451,11 +452,13 @@ def eval(self, r, i, evaluation): class ConditionalExpression(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/ConditionalExpression.html + :WMA link:https://reference.wolfram.com/ +language/ref/ConditionalExpression.html
'ConditionalExpression[$expr$, $cond$]' -
returns $expr$ if $cond$ evaluates to $True$, $Undefined$ if $cond$ evaluates to $False$. +
returns $expr$ if $cond$ evaluates to $True$, $Undefined$ if $cond$ \ + evaluates to $False$.
>> ConditionalExpression[x^2, True] @@ -492,7 +495,7 @@ class ConditionalExpression(Builtin): "expr1_ ^ ConditionalExpression[expr2_, cond_]": "ConditionalExpression[expr1^expr2, cond]", } - def eval_generic(self, expr, cond, evaluation): + def eval_generic(self, expr, cond, evaluation: Evaluation): "ConditionalExpression[expr_, cond_]" # What we need here is a way to evaluate # cond as a predicate, using assumptions. @@ -638,20 +641,21 @@ class DirectedInfinity(SympyFunction): formats = { "DirectedInfinity[1]": "HoldForm[Infinity]", - "DirectedInfinity[-1]": "PrecedenceForm[-HoldForm[Infinity], 390]", + "DirectedInfinity[-1]": "HoldForm[-Infinity]", "DirectedInfinity[]": "HoldForm[ComplexInfinity]", - "DirectedInfinity[z_]": "PrecedenceForm[z HoldForm[Infinity], 390]", + "DirectedInfinity[DirectedInfinity[z_]]": "DirectedInfinity[z]", + "DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]", } - def eval_directed_infinity(self, direction, evaluation): + def eval_directed_infinity(self, direction, evaluation: Evaluation): """DirectedInfinity[direction_]""" if direction in (Integer1, IntegerM1): return None if direction.is_zero: return ExpressionComplexInfinity - normalized_direction = eval_sign(direction) - # TODO: improve eval_sign, to avoid the need of the + normalized_direction = eval_Sign(direction) + # TODO: improve eval_Sign, to avoid the need of the # following block: # ############################################ if normalized_direction is None: @@ -712,7 +716,7 @@ class I_(Predefined): summary_text = "imaginary unit" python_equivalent = 1j - def evaluate(self, evaluation): + def evaluate(self, evaluation: Evaluation): return Complex(Integer0, Integer1) @@ -740,17 +744,17 @@ class Im(SympyFunction): summary_text = "imaginary part" attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED - def eval_complex(self, number, evaluation): + def eval_complex(self, number, evaluation: Evaluation): "Im[number_Complex]" if isinstance(number, Complex): return number.imag - def eval_number(self, number, evaluation): + def eval_number(self, number, evaluation: Evaluation): "Im[number_?NumberQ]" return Integer0 - def eval(self, number, evaluation): + def eval(self, number, evaluation: Evaluation): "Im[number_]" return from_sympy(sympy.im(number.to_sympy().expand(complex=True))) @@ -818,7 +822,7 @@ class Piecewise(SympyFunction): attributes = A_HOLD_ALL | A_PROTECTED - def eval(self, items, evaluation): + def eval(self, items, evaluation: Evaluation): "%(name)s[items__]" result = self.to_sympy( Expression(SymbolPiecewise, *items.get_sequence()), evaluation=evaluation @@ -977,8 +981,8 @@ class Rational_(Builtin): summary_text = "head for rational numbers" name = "Rational" - def eval(self, n: Integer, m: Integer, evaluation): - "%(name)s[n_Integer, m_Integer]" + def eval(self, n: Integer, m: Integer, evaluation: Evaluation): + "Rational[n_Integer, m_Integer]" if m.value == 1: return n @@ -1011,17 +1015,17 @@ class Re(SympyFunction): attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED sympy_name = "re" - def eval_complex(self, number, evaluation): + def eval_complex(self, number, evaluation: Evaluation): "Re[number_Complex]" if isinstance(number, Complex): return number.real - def eval_number(self, number, evaluation): + def eval_number(self, number, evaluation: Evaluation): "Re[number_?NumberQ]" return number - def eval(self, number, evaluation): + def eval(self, number, evaluation: Evaluation): "Re[number_]" return from_sympy(sympy.re(number.to_sympy().expand(complex=True))) @@ -1174,9 +1178,9 @@ class Sign(SympyFunction): "Sign[Power[a_, b_]]": "Power[Sign[a], b]", } - def eval(self, x, evaluation): + def eval(self, x, evaluation: Evaluation): "%(name)s[x_]" - result = eval_sign(x) + result = eval_Sign(x) if result is not None: return result # return None @@ -1187,7 +1191,7 @@ def eval(self, x, evaluation): # Unhandled cases. Use sympy return super(Sign, self).eval(x, evaluation) - def eval_error(self, x, seqs, evaluation): + def eval_error(self, x, seqs, evaluation: Evaluation): "Sign[x_, seqs__]" evaluation.message("Sign", "argx", Integer(len(seqs.get_sequence()) + 1)) @@ -1207,7 +1211,8 @@ class Sum(IterationFunction, SympyFunction):
$i$ ranges from $imin$ to $imax$ in steps of $di$.
'Sum[$expr$, {$i$, $imin$, $imax$}, {$j$, $jmin$, $jmax$}, ...]' -
evaluates $expr$ as a multiple sum, with {$i$, ...}, {$j$, ...}, ... being in outermost-to-innermost order. +
evaluates $expr$ as a multiple sum, with {$i$, ...}, {$j$, ...}, ... being \ + in outermost-to-innermost order. @@ -1293,7 +1298,7 @@ class Sum(IterationFunction, SympyFunction): def get_result(self, items): return Expression(SymbolPlus, *items) - def to_sympy(self, expr, **kwargs) -> SympyExpression: + def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]: """ Perform summation via sympy.summation """ diff --git a/mathics/eval/arithmetic.py b/mathics/eval/arithmetic.py index d15b17ee8..881eebf7d 100644 --- a/mathics/eval/arithmetic.py +++ b/mathics/eval/arithmetic.py @@ -57,7 +57,7 @@ def call_mpmath( return Symbol(exc.name) -def eval_abs(expr: BaseElement) -> Optional[BaseElement]: +def eval_Abs(expr: BaseElement) -> Optional[BaseElement]: """ if expr is a number, return the absolute value. """ @@ -72,7 +72,7 @@ def eval_abs(expr: BaseElement) -> Optional[BaseElement]: return None -def eval_sign(expr: BaseElement) -> Optional[BaseElement]: +def eval_Sign(expr: BaseElement) -> Optional[BaseElement]: """ if expr is a number, return its sign. """ diff --git a/test/builtin/arithmetic/test_basic.py b/test/builtin/arithmetic/test_basic.py index fd52f8fe4..d99b0b9dc 100644 --- a/test/builtin/arithmetic/test_basic.py +++ b/test/builtin/arithmetic/test_basic.py @@ -109,6 +109,26 @@ def test_exponential(str_expr, str_expected): "ComplexInfinity", "Goes to the previous case because of the rule in Power", ), + ], +) +def test_multiply(str_expr, str_expected, msg): + check_evaluation( + str_expr, + str_expected, + failure_message=msg, + hold_expected=True, + to_string_expr=True, + ) + + +@pytest.mark.skip("DirectedInfinity Precedence needs going over") +@pytest.mark.parametrize( + ( + "str_expr", + "str_expected", + "msg", + ), + [ ( "a b DirectedInfinity[1. + 2. I]", "a b ((0.447214 + 0.894427 I) Infinity)", @@ -137,7 +157,7 @@ def test_exponential(str_expr, str_expected): # ("a b DirectedInfinity[-3]", "a b (-Infinity)", ""), ], ) -def test_multiply(str_expr, str_expected, msg): +def test_directed_infinity_precedence(str_expr, str_expected, msg): check_evaluation( str_expr, str_expected,