diff --git a/CHANGES.rst b/CHANGES.rst index 74d8186e20..d65cb88a54 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,9 +26,11 @@ Enhancements * ``Attributes`` accepts a string parameter. * ``ColorNegate`` for colors is supported. * ``D`` and ``Derivative`` improvements. +* ``Expand`` and ``ExpandAll`` now support a second parameter ``patt`` (#1301) +* ``Expand`` and ``ExpandAll`` works with hyperbolic functions (`Sinh`, `Cosh`, `Tanh`, `Coth`) * ``FileNames`` returns a sorted list (#1250). * ``FindRoot`` now receives several optional parameters like ``Method`` and ``MaxIterations``. -* ``FixedPoint`` now supports the ``SameTest`` option. +* ``FixedPoint`` now supports the ``SameTest`` option. * ``Prime`` and ``PrimePi`` now accept a list parameter and have the ``NumericFunction`` attribute. * ``ReplaceRepeated`` and ``FixedPoint`` now supports the ``MaxIteration`` option (#1260). * ``Simplify`` performs a more sophisticated set of simplifications. @@ -36,9 +38,8 @@ Enhancements * ``StringTake`` now accepts form containing a list of strings and specification (#1297). * ``Table`` [*expr*, *n*] is supported. * ``ToString`` accepts an optional *form* parameter. -* ``ToExpression`` handles multi-line string input -* The implementation of Streams was redone. - +* ``ToExpression`` handles multi-line string input. +* The implementation of Streams was redone. Bug fixes @@ -54,7 +55,7 @@ Internal changes ---------------- * doctest accepts the option `-d` to show how long it takes to parse, evaluate and compare each individual test. - + 2.1.0 ----- diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 5f55dc15db..8b08be6675 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -8,6 +8,7 @@ """ from mathics.version import __version__ # noqa used in loading to check consistency. + import sympy import mpmath from functools import lru_cache diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index eff42843c1..a2bcb7b6e1 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -21,6 +21,7 @@ SymbolTrue, ) from mathics.core.convert import from_sympy, sympy_symbol_prefix +from mathics.core.rules import Pattern from mathics.builtin.scoping import dynamic_scoping from mathics.builtin.inference import evaluate_predicate @@ -68,47 +69,102 @@ def _expand(expr): if kwargs["modulus"] is not None and kwargs["modulus"] <= 0: return Integer0 + target_pat = kwargs.get("pattern", None) + if target_pat: + evaluation = kwargs["evaluation"] # A special case for trigonometric functions if "trig" in kwargs and kwargs["trig"]: - if expr.has_form("Sin", 1): + if expr.has_form( + ("Sin", "Cos", "Tan", "Cot", "Sinh", "Cosh", "Tanh", "Coth"), 1 + ): + head = expr.get_head() theta = expr.leaves[0] + if (target_pat is not None) and theta.is_free(target_pat, evaluation): + return expr + if deep: + theta = _expand(theta) if theta.has_form("Plus", 2, None): x, y = theta.leaves[0], Expression("Plus", *theta.leaves[1:]) + if head == Symbol("Sin"): + a = Expression( + "Times", + _expand(Expression("Sin", x)), + _expand(Expression("Cos", y)), + ) - a = Expression( - "Times", - _expand(Expression("Sin", x)), - _expand(Expression("Cos", y)), - ) - - b = Expression( - "Times", - _expand(Expression("Cos", x)), - _expand(Expression("Sin", y)), - ) + b = Expression( + "Times", + _expand(Expression("Cos", x)), + _expand(Expression("Sin", y)), + ) + return _expand(Expression("Plus", a, b)) + elif head == Symbol("Cos"): + a = Expression( + "Times", + _expand(Expression("Cos", x)), + _expand(Expression("Cos", y)), + ) - return Expression("Plus", a, b) + b = Expression( + "Times", + _expand(Expression("Sin", x)), + _expand(Expression("Sin", y)), + ) - elif expr.has_form("Cos", 1): - theta = expr.leaves[0] + return _expand(Expression("Plus", a, -b)) + elif head == Symbol("Sinh"): + a = Expression( + "Times", + _expand(Expression("Sinh", x)), + _expand(Expression("Cosh", y)), + ) - if theta.has_form("Plus", 2, None): - x, y = theta.leaves[0], Expression("Plus", *theta.leaves[1:]) + b = Expression( + "Times", + _expand(Expression("Cosh", x)), + _expand(Expression("Sinh", y)), + ) - a = Expression( - "Times", - _expand(Expression("Cos", x)), - _expand(Expression("Cos", y)), - ) + return _expand(Expression("Plus", a, b)) + elif head == Symbol("Cosh"): + a = Expression( + "Times", + _expand(Expression("Cosh", x)), + _expand(Expression("Cosh", y)), + ) - b = Expression( - "Times", - _expand(Expression("Sin", x)), - _expand(Expression("Sin", y)), - ) + b = Expression( + "Times", + _expand(Expression("Sinh", x)), + _expand(Expression("Sinh", y)), + ) - return Expression("Plus", a, -b) + return _expand(Expression("Plus", a, b)) + elif head == Symbol("Tan"): + a = _expand(Expression("Sin", theta)) + b = Expression( + "Power", _expand(Expression("Cos", theta)), Integer(-1) + ) + return _expand(Expression("Times", a, b)) + elif head == Symbol("Cot"): + a = _expand(Expression("Cos", theta)) + b = Expression( + "Power", _expand(Expression("Sin", theta)), Integer(-1) + ) + return _expand(Expression("Times", a, b)) + elif head == Symbol("Tanh"): + a = _expand(Expression("Sinh", theta)) + b = Expression( + "Power", _expand(Expression("Cosh", theta)), Integer(-1) + ) + return _expand(Expression("Times", a, b)) + elif head == Symbol("Coth"): + a = _expand(Expression("Times", "Cosh", theta)) + b = Expression( + "Power", _expand(Expression("Sinh", theta)), Integer(-1) + ) + return _expand(Expression(a, b)) sub_exprs = [] @@ -128,6 +184,9 @@ def convert_sympy(expr): leaves = expr.get_leaves() if isinstance(expr, Integer): return sympy.Integer(expr.get_int_value()) + if target_pat is not None and not isinstance(expr, Number): + if expr.is_free(target_pat, evaluation): + return store_sub_expr(expr) if expr.has_form("Power", 2): # sympy won't expand `(a + b) / x` to `a / x + b / x` if denom is False # if denom is False we store negative powers to prevent this. @@ -161,7 +220,13 @@ def unconvert_subexprs(expr): if not sub_expr.is_atom(): head = _expand(sub_expr.head) # also expand head leaves = sub_expr.get_leaves() - leaves = [_expand(leaf) for leaf in leaves] + if target_pat: + leaves = [ + leaf if leaf.is_free(target_pat, evaluation) else _expand(leaf) + for leaf in leaves + ] + else: + leaves = [_expand(leaf) for leaf in leaves] sub_exprs[i] = Expression(head, *leaves) else: # thread over Lists etc. @@ -170,7 +235,15 @@ def unconvert_subexprs(expr): for head in threaded_heads: if sub_expr.has_form(head, None): leaves = sub_expr.get_leaves() - leaves = [_expand(leaf) for leaf in leaves] + if target_pat: + leaves = [ + leaf + if leaf.is_free(target_pat, evaluation) + else _expand(leaf) + for leaf in leaves + ] + else: + leaves = [_expand(leaf) for leaf in leaves] sub_exprs[i] = Expression(head, *leaves) break @@ -721,6 +794,8 @@ class Expand(_Expand):
'Expand[$expr$]'
expands out positive integer powers and products of sums in $expr$, as well as trigonometric identities. +
Expand[$expr$, $target$] +
just expands those parts involving $target$. >> Expand[(x + y) ^ 3] @@ -743,11 +818,17 @@ class Expand(_Expand): 'Expand' expands trigonometric identities >> Expand[Sin[x + y], Trig -> True] = Cos[x] Sin[y] + Cos[y] Sin[x] + >> Expand[Tanh[x + y], Trig -> True] + = Cosh[x] Sinh[y] / (Cosh[x] Cosh[y] + Sinh[x] Sinh[y]) + Cosh[y] Sinh[x] / (Cosh[x] Cosh[y] + Sinh[x] Sinh[y]) 'Expand' does not change any other expression. >> Expand[Sin[x (1 + y)]] = Sin[x (1 + y)] + Using the second argument, the expression only + expands those subexpressions containing $pat$: + >> Expand[(x+a)^2+(y+a)^2+(x+y)(x+a), y] + = a ^ 2 + 2 a y + x (a + x) + y (a + x) + y ^ 2 + (a + x) ^ 2 'Expand' also works in Galois fields >> Expand[(1 + a)^12, Modulus -> 3] = 1 + a ^ 3 + a ^ 9 + a ^ 12 @@ -767,11 +848,27 @@ class Expand(_Expand): #> (y^2)^(1/2)/(2x+2y)//Expand = Sqrt[y ^ 2] / (2 x + 2 y) - ## This caused a program crash! #> 2(3+2x)^2/(5+x^2+3x)^3 // Expand = 24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3 """ + def apply_patt(self, expr, target, evaluation, options): + "Expand[expr_, target_, OptionsPattern[Expand]]" + + if target.get_head_name() in ("System`Rule", "System`DelayedRule"): + optname = target.leaves[0].get_name() + options[optname] = target.leaves[1] + target = None + + kwargs = self.convert_options(options, evaluation) + if kwargs is None: + return + + if target: + kwargs["pattern"] = Pattern.create(target) + kwargs["evaluation"] = evaluation + return expand(expr, True, False, **kwargs) + def apply(self, expr, evaluation, options): "Expand[expr_, OptionsPattern[Expand]]" @@ -816,6 +913,8 @@ class ExpandAll(_Expand):
'ExpandAll[$expr$]'
expands out negative integer powers and products of sums in $expr$. +
'ExpandAll[$expr$, $target$]' +
just expands those parts involving $target$.
>> ExpandAll[(a + b) ^ 2 / (c + d)^2] @@ -825,6 +924,12 @@ class ExpandAll(_Expand): >> ExpandAll[(a + Sin[x (1 + y)])^2] = 2 a Sin[x + x y] + a ^ 2 + Sin[x + x y] ^ 2 + >> ExpandAll[Sin[(x+y)^2]] + = Sin[x ^ 2 + 2 x y + y ^ 2] + + >> ExpandAll[Sin[(x+y)^2], Trig->True] + = -Sin[x ^ 2] Sin[2 x y] Sin[y ^ 2] + Cos[x ^ 2] Cos[2 x y] Sin[y ^ 2] + Cos[x ^ 2] Cos[y ^ 2] Sin[2 x y] + Cos[2 x y] Cos[y ^ 2] Sin[x ^ 2] + 'ExpandAll' also expands heads >> ExpandAll[((1 + x)(1 + y))[x]] = (1 + x + y + x y)[x] @@ -832,8 +937,25 @@ class ExpandAll(_Expand): 'ExpandAll' can also work in finite fields >> ExpandAll[(1 + a) ^ 6 / (x + y)^3, Modulus -> 3] = (1 + 2 a ^ 3 + a ^ 6) / (x ^ 3 + y ^ 3) + """ + def apply_patt(self, expr, target, evaluation, options): + "ExpandAll[expr_, target_, OptionsPattern[Expand]]" + if target.get_head_name() in ("System`Rule", "System`DelayedRule"): + optname = target.leaves[0].get_name() + options[optname] = target.leaves[1] + target = None + + kwargs = self.convert_options(options, evaluation) + if kwargs is None: + return + + if target: + kwargs["pattern"] = Pattern.create(target) + kwargs["evaluation"] = evaluation + return expand(expr, numer=True, denom=True, deep=True, **kwargs) + def apply(self, expr, evaluation, options): "ExpandAll[expr_, OptionsPattern[ExpandAll]]" diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 92e7a8e76f..4a4ec4a5a9 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -803,7 +803,9 @@ class Pattern_(PatternObject): def init(self, expr): super(Pattern_, self).init(expr) self.varname = expr.leaves[0].get_name() - if self.varname is None: + if self.varname is None or self.varname == "": + print("------------------->") + print(" expr that raise the error =",expr) self.error("patvar", expr) self.pattern = Pattern.create(expr.leaves[1]) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index a83a1ef268..a9e90680ff 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -2263,7 +2263,6 @@ def __neg__(self) -> "Integer": def is_zero(self) -> bool: return self.value == 0 - Integer0 = Integer(0) Integer1 = Integer(1)