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):