diff --git a/CHANGES.rst b/CHANGES.rst index 28e87acb8..7e8baf9a0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,26 @@ CHANGES ======= + +Compatibility +------------- + +* ``*Plot`` does not show messages during the evaluation. + + + +Internals +--- + +* ``eval_abs`` and ``eval_sign`` extracted from ``Abs`` and ``Sign`` and added to ``mathics.eval.arithmetic``. + + +Bugs +---- + +* Improved support for ``DirectedInfinity`` and ``Indeterminate``. + + 6.0.1 ----- diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 136294ef0..6f8ecd74f 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -7,13 +7,8 @@ Basic arithmetic functions, including complex number arithmetic. """ -from mathics.eval.numerify import numerify - -# 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 @@ -49,17 +44,16 @@ 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 from mathics.core.symbols import ( Atom, Symbol, - SymbolAbs, SymbolFalse, SymbolList, SymbolPlus, - SymbolPower, SymbolTimes, SymbolTrue, ) @@ -72,8 +66,14 @@ SymbolTable, SymbolUndefined, ) -from mathics.eval.arithmetic import eval_mpmath_function +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) class _MPMathFunction(SympyFunction): @@ -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,6 +209,13 @@ class Abs(_MPMathFunction): summary_text = "absolute value of a number" sympy_name = "Abs" + 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) + class Arg(_MPMathFunction): """ @@ -264,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() @@ -302,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: @@ -358,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: @@ -432,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() @@ -444,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] @@ -485,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. @@ -590,8 +600,7 @@ class DirectedInfinity(SympyFunction): = Indeterminate >> DirectedInfinity[0] - : Indeterminate expression 0 Infinity encountered. - = Indeterminate + = ComplexInfinity #> DirectedInfinity[1+I]+DirectedInfinity[2+I] = (2 / 5 + I / 5) Sqrt[5] Infinity + (1 / 2 + I / 2) Sqrt[2] Infinity @@ -602,15 +611,12 @@ class DirectedInfinity(SympyFunction): summary_text = "infinite quantity with a defined direction in the complex plane" rules = { - "DirectedInfinity[Indeterminate]": "Indeterminate", "DirectedInfinity[args___] ^ -1": "0", - "0 * DirectedInfinity[args___]": "Message[Infinity::indet, Unevaluated[0 DirectedInfinity[args]]]; Indeterminate", - # "DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]", - # "DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]", - # "DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]", - # Rules already implemented in Times.eval - # "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]", - # "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]", + # Special arguments: + "DirectedInfinity[DirectedInfinity[args___]]": "DirectedInfinity[args]", + "DirectedInfinity[Indeterminate]": "Indeterminate", + "DirectedInfinity[Alternatives[0, 0.]]": "DirectedInfinity[]", + # Plus "DirectedInfinity[a_] + DirectedInfinity[b_] /; b == -a": ( "Message[Infinity::indet," " Unevaluated[DirectedInfinity[a] + DirectedInfinity[b]]];" @@ -622,17 +628,15 @@ class DirectedInfinity(SympyFunction): "Indeterminate" ), "DirectedInfinity[args___] + _?NumberQ": "DirectedInfinity[args]", - "DirectedInfinity[0]": ( + # Times. See if can be reinstalled in eval_Times + "Alternatives[0, 0.] DirectedInfinity[z___]": ( "Message[Infinity::indet," - " Unevaluated[DirectedInfinity[0]]];" + " Unevaluated[0 DirectedInfinity[z]]];" "Indeterminate" ), - "DirectedInfinity[0.]": ( - "Message[Infinity::indet," - " Unevaluated[DirectedInfinity[0.]]];" - "Indeterminate" - ), - "DirectedInfinity[DirectedInfinity[x___]]": "DirectedInfinity[x]", + "a_?NumericQ * DirectedInfinity[b_]": "DirectedInfinity[a * b]", + "a_ DirectedInfinity[]": "DirectedInfinity[]", + "DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a * b]", } formats = { @@ -643,18 +647,41 @@ class DirectedInfinity(SympyFunction): "DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]", } - def eval(self, z, evaluation): - """DirectedInfinity[z_]""" - if z in (Integer1, IntegerM1): + def eval_directed_infinity(self, direction, evaluation: Evaluation): + """DirectedInfinity[direction_]""" + if direction in (Integer1, IntegerM1): return None - if isinstance(z, Number) or isinstance(eval_N(z, evaluation), Number): - direction = (z / Expression(SymbolAbs, z)).evaluate(evaluation) - return Expression( - SymbolDirectedInfinity, - direction, - elements_properties=ElementsProperties(True, True, True), - ) - return None + if direction.is_zero: + return ExpressionComplexInfinity + + normalized_direction = eval_Sign(direction) + # TODO: improve eval_Sign, to avoid the need of the + # following block: + # ############################################ + if normalized_direction is None: + ndir = eval_N(direction, evaluation) + if isinstance(ndir, (Integer, Rational, Real)): + if abs(ndir.value) == 1.0: + normalized_direction = direction + else: + normalized_direction = direction / Abs(direction) + elif isinstance(ndir, Complex): + re, im = ndir.value + if abs(re.value**2 + im.value**2 - 1.0) < 1.0e-9: + normalized_direction = direction + else: + normalized_direction = direction / Abs(direction) + else: + return None + # ############################################## + + if normalized_direction is None: + return None + return Expression( + SymbolDirectedInfinity, + normalized_direction.evaluate(evaluation), + elements_properties=ElementsProperties(True, False, False), + ) def to_sympy(self, expr, **kwargs): if len(expr.elements) == 1: @@ -689,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) @@ -717,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 - 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))) @@ -795,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 @@ -954,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 @@ -988,19 +1015,18 @@ 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 - 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))) @@ -1148,22 +1174,24 @@ class Sign(SympyFunction): "argx": "Sign called with `1` arguments; 1 argument is expected.", } - def eval(self, x, evaluation): + rules = { + "Sign[Power[a_, b_]]": "Power[Sign[a], b]", + } + + def eval(self, x, evaluation: Evaluation): "%(name)s[x_]" - # Sympy and mpmath do not give the desired form of complex number - if isinstance(x, Complex): - return Expression( - SymbolTimes, - x, - Expression(SymbolPower, Expression(SymbolAbs, x), IntegerM1), - ) + result = eval_Sign(x) + if result is not None: + return result + # return None sympy_x = x.to_sympy() if sympy_x is None: return None - return super().eval(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)) @@ -1183,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. @@ -1269,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 1be0c9540..881eebf7d 100644 --- a/mathics/eval/arithmetic.py +++ b/mathics/eval/arithmetic.py @@ -3,7 +3,18 @@ import mpmath import sympy -from mathics.core.atoms import Integer0, Integer1, Integer2, IntegerM1, Number +from mathics.core.atoms import ( + Complex, + Integer, + Integer0, + Integer1, + Integer2, + IntegerM1, + Number, + Rational, + RationalOneHalf, + Real, +) from mathics.core.convert.mpmath import from_mpmath from mathics.core.convert.sympy import from_sympy from mathics.core.element import BaseElement @@ -14,7 +25,6 @@ SymbolComplexInfinity, SymbolDirectedInfinity, SymbolIndeterminate, - SymbolInfinity, ) @@ -47,6 +57,42 @@ def call_mpmath( return Symbol(exc.name) +def eval_Abs(expr: BaseElement) -> Optional[BaseElement]: + """ + if expr is a number, return the absolute value. + """ + if isinstance(expr, (Integer, Rational, Real)): + if expr.value >= 0: + return expr + return eval_Times(IntegerM1, expr) + if isinstance(expr, Complex): + re, im = expr.real, expr.imag + sqabs = eval_Plus(eval_Times(re, re), eval_Times(im, im)) + return Expression(SymbolPower, sqabs, RationalOneHalf) + return None + + +def eval_Sign(expr: BaseElement) -> Optional[BaseElement]: + """ + if expr is a number, return its sign. + """ + if isinstance(expr, (Integer, Rational, Real)): + if expr.value > 0: + return Integer1 + elif expr.value == 0: + return Integer0 + else: + return IntegerM1 + + if isinstance(expr, Complex): + re, im = expr.real, expr.imag + sqabs = eval_Plus(eval_Times(re, re), eval_Times(im, im)) + return eval_Times( + expr, Expression(SymbolPower, sqabs, eval_Times(IntegerM1, RationalOneHalf)) + ) + return None + + def eval_mpmath_function( mpmath_function: Callable, *args: Tuple[Number], prec: Optional[int] = None ) -> Optional[Number]: @@ -171,7 +217,9 @@ def append_last(): def eval_Times(*items): elements = [] numbers = [] + # This variable tracks DirectInfinity[] factors. infinity_factor = False + # These quantities only have sense if there are numeric terms. # Also, prec is only needed if is_machine_precision is not True. prec = min_prec(*items) @@ -181,7 +229,12 @@ def eval_Times(*items): for item in items: if isinstance(item, Number): numbers.append(item) - elif elements and item == elements[-1]: + continue + if item.get_head() is SymbolDirectedInfinity: + infinity_factor = True + if item is SymbolIndeterminate: + return item + if elements and item == elements[-1]: elements[-1] = Expression(SymbolPower, elements[-1], Integer2) elif ( elements @@ -214,16 +267,6 @@ def eval_Times(*items): item, Expression(SymbolPlus, Integer1, elements[-1].elements[1]), ) - elif item.get_head().sameQ(SymbolDirectedInfinity): - infinity_factor = True - if len(item.elements) > 0: - direction = item.elements[0] - if isinstance(direction, Number): - numbers.append(direction) - else: - elements.append(direction) - elif item.sameQ(SymbolInfinity) or item.sameQ(SymbolComplexInfinity): - infinity_factor = True else: elements.append(item) @@ -247,9 +290,9 @@ def eval_Times(*items): if number.sameQ(Integer1): number = None elif number.is_zero: - if infinity_factor: - return SymbolIndeterminate - return number + if not infinity_factor: + return number + # else, they are handled using the DirectedInfinity upvalues rules. elif number.sameQ(IntegerM1) and elements and elements[0].has_form("Plus", None): elements[0] = Expression( elements[0].get_head(), @@ -264,15 +307,14 @@ def eval_Times(*items): elements.insert(0, number) if not elements: - if infinity_factor: - return SymbolComplexInfinity + # if infinity_factor: + # return SymbolComplexInfinity return Integer1 if len(elements) == 1: ret = elements[0] else: ret = Expression(SymbolTimes, *elements) - if infinity_factor: - return Expression(SymbolDirectedInfinity, ret) - else: - return ret + # if infinity_factor: + # return Expression(SymbolDirectedInfinity, ret) + return ret diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 32467626f..691616bbc 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -12,24 +12,22 @@ from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping -from mathics.core.atoms import Integer, Integer0, Real, String +from mathics.core.atoms import Integer, Integer0, Real from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import SymbolN, SymbolPower, SymbolTrue +from mathics.core.symbols import SymbolN, SymbolTrue from mathics.core.systemsymbols import ( SymbolGraphics, SymbolHue, SymbolLine, SymbolLog10, SymbolLogPlot, - SymbolMessageName, SymbolPoint, SymbolPolygon, - SymbolQuiet, ) RealPoint6 = Real(0.6) @@ -102,15 +100,10 @@ def quiet_f(*args): return quiet_f expr: Optional[Type[BaseElement]] = Expression(SymbolN, expr).evaluate(evaluation) - quiet_expr = Expression( - SymbolQuiet, - expr, - ListExpression(Expression(SymbolMessageName, SymbolPower, String("infy"))), - ) def quiet_f(*args): vars = {arg_name: Real(arg) for arg_name, arg in zip(arg_names, args)} - value = dynamic_scoping(quiet_expr.evaluate, vars, evaluation) + value = dynamic_scoping(expr.evaluate, vars, evaluation) if list_is_expected: if value.has_form("List", None): value = [extract_pyreal(item) for item in value.elements] @@ -391,6 +384,7 @@ def get_points_range(points): # like the Hue. graphics = [] + prev_quiet_all, evaluation.quiet_all = evaluation.quiet_all, True for index, f in enumerate(functions): points = [] xvalues = [] # x value for each point in points @@ -561,6 +555,8 @@ def find_excl(excl): mesh_points = [to_mathics_list(xx, yy) for xx, yy in points] graphics.append(Expression(SymbolPoint, ListExpression(*mesh_points))) + # Restore the quiet_all state + evaluation.quiet_all = prev_quiet_all return Expression( SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) ) diff --git a/test/builtin/arithmetic/test_basic.py b/test/builtin/arithmetic/test_basic.py index cc771eb00..d99b0b9dc 100644 --- a/test/builtin/arithmetic/test_basic.py +++ b/test/builtin/arithmetic/test_basic.py @@ -109,29 +109,62 @@ 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", - "", + "a b ((0.447214 + 0.894427 I) Infinity)", + "symbols times floating point complex directed infinity", + ), + ("a b DirectedInfinity[I]", "a b (I Infinity)", ""), + ( + "a b (-1 + 2 I) Infinity", + "a b ((-1 / 5 + 2 I / 5) Sqrt[5] Infinity)", + "symbols times algebraic exact factor times infinity", + ), + ( + "a b (-1 + 2 Pi I) Infinity", + "a b (Infinity (-1 + 2 I Pi) / Sqrt[1 + 4 Pi ^ 2])", + "complex irrational exact", ), - ("a b DirectedInfinity[I]", "a b I Infinity", ""), - ("a b (-1 + 2 I) Infinity", "a b (-1 / 5 + 2 I / 5) Sqrt[5] Infinity", ""), - ("a b (-1 + 2 Pi I) Infinity", "a b (-1 + 2 I Pi) Infinity", ""), ( "a b DirectedInfinity[(1 + 2 I)/ Sqrt[5]]", - "a b (1 / 5 + 2 I / 5) Sqrt[5] Infinity", - "", + "a b ((1 / 5 + 2 I / 5) Sqrt[5] Infinity)", + "symbols times algebraic complex directed infinity", ), - ("a b DirectedInfinity[q]", "a b q Infinity", ""), + ("a b DirectedInfinity[q]", "a b (q Infinity)", ""), # Failing tests # Problem with formatting. Parenthezise are missing... # ("a b DirectedInfinity[-I]", "a b (-I Infinity)", ""), # ("a b DirectedInfinity[-3]", "a b (-Infinity)", ""), ], ) -@pytest.mark.xfail -def test_multiply(str_expr, str_expected, msg): - check_evaluation(str_expr, str_expected, failure_message=msg, hold_expected=True) +def test_directed_infinity_precedence(str_expr, str_expected, msg): + check_evaluation( + str_expr, + str_expected, + failure_message=msg, + hold_expected=True, + to_string_expr=True, + ) @pytest.mark.parametrize( @@ -164,7 +197,7 @@ def test_multiply(str_expr, str_expected, msg): ("I^(2/3)", "(-1) ^ (1 / 3)", None), # In WMA, the next test would return ``-(-I)^(2/3)`` # which is less compact and elegant... - ("(-I)^(2/3)", "(-1) ^ (-1 / 3)", None), + # ("(-I)^(2/3)", "(-1) ^ (-1 / 3)", None), ("(2+3I)^3", "-46 + 9 I", None), ("(1.+3. I)^.6", "1.46069 + 1.35921 I", None), ("3^(1+2 I)", "3 ^ (1 + 2 I)", None), @@ -175,21 +208,20 @@ def test_multiply(str_expr, str_expected, msg): # sympy, which produces the result ("(3/Pi)^(-I)", "(3 / Pi) ^ (-I)", None), # Association rules - ('(a^"w")^2', 'a^(2 "w")', "Integer power of a power with string exponent"), + # ('(a^"w")^2', 'a^(2 "w")', "Integer power of a power with string exponent"), ('(a^2)^"w"', '(a ^ 2) ^ "w"', None), ('(a^2)^"w"', '(a ^ 2) ^ "w"', None), ("(a^2)^(1/2)", "Sqrt[a ^ 2]", None), ("(a^(1/2))^2", "a", None), ("(a^(1/2))^2", "a", None), ("(a^(3/2))^3.", "(a ^ (3 / 2)) ^ 3.", None), - ("(a^(1/2))^3.", "a ^ 1.5", None), - ("(a^(.3))^3.", "a ^ 0.9", None), + # ("(a^(1/2))^3.", "a ^ 1.5", "Power associativity rational, real"), + # ("(a^(.3))^3.", "a ^ 0.9", "Power associativity for real powers"), ("(a^(1.3))^3.", "(a ^ 1.3) ^ 3.", None), # Exponentials involving expressions ("(a^(p-2 q))^3", "a ^ (3 p - 6 q)", None), ("(a^(p-2 q))^3.", "(a ^ (p - 2 q)) ^ 3.", None), ], ) -@pytest.mark.xfail def test_power(str_expr, str_expected, msg): check_evaluation(str_expr, str_expected, failure_message=msg)