Skip to content

Commit 2c0e66f

Browse files
mmaterarocky
authored andcommitted
fixing and improving Expand and ExpandAll
1 parent 68f24bd commit 2c0e66f

4 files changed

Lines changed: 161 additions & 37 deletions

File tree

CHANGES.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ Enhancements
2626
* ``Attributes`` accepts a string parameter.
2727
* ``ColorNegate`` for colors is supported.
2828
* ``D`` and ``Derivative`` improvements.
29+
* ``Expand`` and ``ExpandAll`` now support a second parameter ``patt`` (#1301)
30+
* ``Expand`` and ``ExpandAll`` works with hyperbolic functions (`Sinh`, `Cosh`, `Tanh`, `Coth`)
2931
* ``FileNames`` returns a sorted list (#1250).
3032
* ``FindRoot`` now receives several optional parameters like ``Method`` and ``MaxIterations``.
31-
* ``FixedPoint`` now supports the ``SameTest`` option.
33+
* ``FixedPoint`` now supports the ``SameTest`` option.
3234
* ``Prime`` and ``PrimePi`` now accept a list parameter and have the ``NumericFunction`` attribute.
3335
* ``ReplaceRepeated`` and ``FixedPoint`` now supports the ``MaxIteration`` option (#1260).
3436
* ``Simplify`` performs a more sophisticated set of simplifications.
3537
* ``Simplify`` accepts a second parameter that temporarily overwrites ``$Assumptions``.
3638
* ``StringTake`` now accepts form containing a list of strings and specification (#1297).
3739
* ``Table`` [*expr*, *n*] is supported.
3840
* ``ToString`` accepts an optional *form* parameter.
39-
* ``ToExpression`` handles multi-line string input
40-
* The implementation of Streams was redone.
41-
41+
* ``ToExpression`` handles multi-line string input.
42+
* The implementation of Streams was redone.
4243

4344

4445
Bug fixes
@@ -54,7 +55,7 @@ Internal changes
5455
----------------
5556

5657
* doctest accepts the option `-d` to show how long it takes to parse, evaluate and compare each individual test.
57-
58+
5859

5960
2.1.0
6061
-----

mathics/builtin/arithmetic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
from mathics.version import __version__ # noqa used in loading to check consistency.
11+
1112
import sympy
1213
import mpmath
1314
from functools import lru_cache

mathics/builtin/numbers/algebra.py

Lines changed: 154 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
SymbolTrue,
2222
)
2323
from mathics.core.convert import from_sympy, sympy_symbol_prefix
24+
from mathics.core.rules import Pattern
2425
from mathics.builtin.scoping import dynamic_scoping
2526
from mathics.builtin.inference import evaluate_predicate
2627

@@ -68,47 +69,102 @@ def _expand(expr):
6869
if kwargs["modulus"] is not None and kwargs["modulus"] <= 0:
6970
return Integer0
7071

72+
target_pat = kwargs.get("pattern", None)
73+
if target_pat:
74+
evaluation = kwargs["evaluation"]
7175
# A special case for trigonometric functions
7276
if "trig" in kwargs and kwargs["trig"]:
73-
if expr.has_form("Sin", 1):
77+
if expr.has_form(
78+
("Sin", "Cos", "Tan", "Cot", "Sinh", "Cosh", "Tanh", "Coth"), 1
79+
):
80+
head = expr.get_head()
7481
theta = expr.leaves[0]
82+
if (target_pat is not None) and theta.is_free(target_pat, evaluation):
83+
return expr
84+
if deep:
85+
theta = _expand(theta)
7586

7687
if theta.has_form("Plus", 2, None):
7788
x, y = theta.leaves[0], Expression("Plus", *theta.leaves[1:])
89+
if head == Symbol("Sin"):
90+
a = Expression(
91+
"Times",
92+
_expand(Expression("Sin", x)),
93+
_expand(Expression("Cos", y)),
94+
)
7895

79-
a = Expression(
80-
"Times",
81-
_expand(Expression("Sin", x)),
82-
_expand(Expression("Cos", y)),
83-
)
84-
85-
b = Expression(
86-
"Times",
87-
_expand(Expression("Cos", x)),
88-
_expand(Expression("Sin", y)),
89-
)
96+
b = Expression(
97+
"Times",
98+
_expand(Expression("Cos", x)),
99+
_expand(Expression("Sin", y)),
100+
)
101+
return _expand(Expression("Plus", a, b))
102+
elif head == Symbol("Cos"):
103+
a = Expression(
104+
"Times",
105+
_expand(Expression("Cos", x)),
106+
_expand(Expression("Cos", y)),
107+
)
90108

91-
return Expression("Plus", a, b)
109+
b = Expression(
110+
"Times",
111+
_expand(Expression("Sin", x)),
112+
_expand(Expression("Sin", y)),
113+
)
92114

93-
elif expr.has_form("Cos", 1):
94-
theta = expr.leaves[0]
115+
return _expand(Expression("Plus", a, -b))
116+
elif head == Symbol("Sinh"):
117+
a = Expression(
118+
"Times",
119+
_expand(Expression("Sinh", x)),
120+
_expand(Expression("Cosh", y)),
121+
)
95122

96-
if theta.has_form("Plus", 2, None):
97-
x, y = theta.leaves[0], Expression("Plus", *theta.leaves[1:])
123+
b = Expression(
124+
"Times",
125+
_expand(Expression("Cosh", x)),
126+
_expand(Expression("Sinh", y)),
127+
)
98128

99-
a = Expression(
100-
"Times",
101-
_expand(Expression("Cos", x)),
102-
_expand(Expression("Cos", y)),
103-
)
129+
return _expand(Expression("Plus", a, b))
130+
elif head == Symbol("Cosh"):
131+
a = Expression(
132+
"Times",
133+
_expand(Expression("Cosh", x)),
134+
_expand(Expression("Cosh", y)),
135+
)
104136

105-
b = Expression(
106-
"Times",
107-
_expand(Expression("Sin", x)),
108-
_expand(Expression("Sin", y)),
109-
)
137+
b = Expression(
138+
"Times",
139+
_expand(Expression("Sinh", x)),
140+
_expand(Expression("Sinh", y)),
141+
)
110142

111-
return Expression("Plus", a, -b)
143+
return _expand(Expression("Plus", a, b))
144+
elif head == Symbol("Tan"):
145+
a = _expand(Expression("Sin", theta))
146+
b = Expression(
147+
"Power", _expand(Expression("Cos", theta)), Integer(-1)
148+
)
149+
return _expand(Expression("Times", a, b))
150+
elif head == Symbol("Cot"):
151+
a = _expand(Expression("Cos", theta))
152+
b = Expression(
153+
"Power", _expand(Expression("Sin", theta)), Integer(-1)
154+
)
155+
return _expand(Expression("Times", a, b))
156+
elif head == Symbol("Tanh"):
157+
a = _expand(Expression("Sinh", theta))
158+
b = Expression(
159+
"Power", _expand(Expression("Cosh", theta)), Integer(-1)
160+
)
161+
return _expand(Expression("Times", a, b))
162+
elif head == Symbol("Coth"):
163+
a = _expand(Expression("Times", "Cosh", theta))
164+
b = Expression(
165+
"Power", _expand(Expression("Sinh", theta)), Integer(-1)
166+
)
167+
return _expand(Expression(a, b))
112168

113169
sub_exprs = []
114170

@@ -128,6 +184,9 @@ def convert_sympy(expr):
128184
leaves = expr.get_leaves()
129185
if isinstance(expr, Integer):
130186
return sympy.Integer(expr.get_int_value())
187+
if target_pat is not None and not isinstance(expr, Number):
188+
if expr.is_free(target_pat, evaluation):
189+
return store_sub_expr(expr)
131190
if expr.has_form("Power", 2):
132191
# sympy won't expand `(a + b) / x` to `a / x + b / x` if denom is False
133192
# if denom is False we store negative powers to prevent this.
@@ -161,7 +220,13 @@ def unconvert_subexprs(expr):
161220
if not sub_expr.is_atom():
162221
head = _expand(sub_expr.head) # also expand head
163222
leaves = sub_expr.get_leaves()
164-
leaves = [_expand(leaf) for leaf in leaves]
223+
if target_pat:
224+
leaves = [
225+
leaf if leaf.is_free(target_pat, evaluation) else _expand(leaf)
226+
for leaf in leaves
227+
]
228+
else:
229+
leaves = [_expand(leaf) for leaf in leaves]
165230
sub_exprs[i] = Expression(head, *leaves)
166231
else:
167232
# thread over Lists etc.
@@ -170,7 +235,15 @@ def unconvert_subexprs(expr):
170235
for head in threaded_heads:
171236
if sub_expr.has_form(head, None):
172237
leaves = sub_expr.get_leaves()
173-
leaves = [_expand(leaf) for leaf in leaves]
238+
if target_pat:
239+
leaves = [
240+
leaf
241+
if leaf.is_free(target_pat, evaluation)
242+
else _expand(leaf)
243+
for leaf in leaves
244+
]
245+
else:
246+
leaves = [_expand(leaf) for leaf in leaves]
174247
sub_exprs[i] = Expression(head, *leaves)
175248
break
176249

@@ -721,6 +794,8 @@ class Expand(_Expand):
721794
<dt>'Expand[$expr$]'
722795
<dd>expands out positive integer powers and products of sums in $expr$,
723796
as well as trigonometric identities.
797+
<dt>Expand[$expr$, $target$]
798+
<dd>just expands those parts involving $target$.
724799
</dl>
725800
726801
>> Expand[(x + y) ^ 3]
@@ -743,11 +818,17 @@ class Expand(_Expand):
743818
'Expand' expands trigonometric identities
744819
>> Expand[Sin[x + y], Trig -> True]
745820
= Cos[x] Sin[y] + Cos[y] Sin[x]
821+
>> Expand[Tanh[x + y], Trig -> True]
822+
= Cosh[x] Sinh[y] / (Cosh[x] Cosh[y] + Sinh[x] Sinh[y]) + Cosh[y] Sinh[x] / (Cosh[x] Cosh[y] + Sinh[x] Sinh[y])
746823
747824
'Expand' does not change any other expression.
748825
>> Expand[Sin[x (1 + y)]]
749826
= Sin[x (1 + y)]
750827
828+
Using the second argument, the expression only
829+
expands those subexpressions containing $pat$:
830+
>> Expand[(x+a)^2+(y+a)^2+(x+y)(x+a), y]
831+
= a ^ 2 + 2 a y + x (a + x) + y (a + x) + y ^ 2 + (a + x) ^ 2
751832
'Expand' also works in Galois fields
752833
>> Expand[(1 + a)^12, Modulus -> 3]
753834
= 1 + a ^ 3 + a ^ 9 + a ^ 12
@@ -767,11 +848,28 @@ class Expand(_Expand):
767848
#> (y^2)^(1/2)/(2x+2y)//Expand
768849
= Sqrt[y ^ 2] / (2 x + 2 y)
769850
770-
## This caused a program crash!
851+
771852
#> 2(3+2x)^2/(5+x^2+3x)^3 // Expand
772853
= 24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3
773854
"""
774855

856+
def apply_patt(self, expr, target, evaluation, options):
857+
"Expand[expr_, target_, OptionsPattern[Expand]]"
858+
859+
if target.get_head_name() in ("System`Rule", "System`DelayedRule"):
860+
optname = target.leaves[0].get_name()
861+
options[optname] = target.leaves[1]
862+
target = None
863+
864+
kwargs = self.convert_options(options, evaluation)
865+
if kwargs is None:
866+
return
867+
868+
if target:
869+
kwargs["pattern"] = Pattern.create(target)
870+
kwargs["evaluation"] = evaluation
871+
return expand(expr, True, False, **kwargs)
872+
775873
def apply(self, expr, evaluation, options):
776874
"Expand[expr_, OptionsPattern[Expand]]"
777875

@@ -816,6 +914,8 @@ class ExpandAll(_Expand):
816914
<dl>
817915
<dt>'ExpandAll[$expr$]'
818916
<dd>expands out negative integer powers and products of sums in $expr$.
917+
<dt>'ExpandAll[$expr$, $target$]'
918+
<dd>just expands those parts involving $target$.
819919
</dl>
820920
821921
>> ExpandAll[(a + b) ^ 2 / (c + d)^2]
@@ -825,15 +925,38 @@ class ExpandAll(_Expand):
825925
>> ExpandAll[(a + Sin[x (1 + y)])^2]
826926
= 2 a Sin[x + x y] + a ^ 2 + Sin[x + x y] ^ 2
827927
928+
>> ExpandAll[Sin[(x+y)^2]]
929+
= Sin[x ^ 2 + 2 x y + y ^ 2]
930+
931+
>> ExpandAll[Sin[(x+y)^2], Trig->True]
932+
= -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]
933+
828934
'ExpandAll' also expands heads
829935
>> ExpandAll[((1 + x)(1 + y))[x]]
830936
= (1 + x + y + x y)[x]
831937
832938
'ExpandAll' can also work in finite fields
833939
>> ExpandAll[(1 + a) ^ 6 / (x + y)^3, Modulus -> 3]
834940
= (1 + 2 a ^ 3 + a ^ 6) / (x ^ 3 + y ^ 3)
941+
835942
"""
836943

944+
def apply_patt(self, expr, target, evaluation, options):
945+
"ExpandAll[expr_, target_, OptionsPattern[Expand]]"
946+
if target.get_head_name() in ("System`Rule", "System`DelayedRule"):
947+
optname = target.leaves[0].get_name()
948+
options[optname] = target.leaves[1]
949+
target = None
950+
951+
kwargs = self.convert_options(options, evaluation)
952+
if kwargs is None:
953+
return
954+
955+
if target:
956+
kwargs["pattern"] = Pattern.create(target)
957+
kwargs["evaluation"] = evaluation
958+
return expand(expr, numer=True, denom=True, deep=True, **kwargs)
959+
837960
def apply(self, expr, evaluation, options):
838961
"ExpandAll[expr_, OptionsPattern[ExpandAll]]"
839962

mathics/core/expression.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2263,7 +2263,6 @@ def __neg__(self) -> "Integer":
22632263
def is_zero(self) -> bool:
22642264
return self.value == 0
22652265

2266-
22672266
Integer0 = Integer(0)
22682267
Integer1 = Integer(1)
22692268

0 commit comments

Comments
 (0)