Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ 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.
* ``Simplify`` accepts a second parameter that temporarily overwrites ``$Assumptions``.
* ``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
Expand All @@ -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
-----
Expand Down
1 change: 1 addition & 0 deletions mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
184 changes: 153 additions & 31 deletions mathics/builtin/numbers/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = []

Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -721,6 +794,8 @@ class Expand(_Expand):
<dt>'Expand[$expr$]'
<dd>expands out positive integer powers and products of sums in $expr$,
as well as trigonometric identities.
<dt>Expand[$expr$, $target$]
<dd>just expands those parts involving $target$.
</dl>

>> Expand[(x + y) ^ 3]
Expand All @@ -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
Expand All @@ -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]]"

Expand Down Expand Up @@ -816,6 +913,8 @@ class ExpandAll(_Expand):
<dl>
<dt>'ExpandAll[$expr$]'
<dd>expands out negative integer powers and products of sums in $expr$.
<dt>'ExpandAll[$expr$, $target$]'
<dd>just expands those parts involving $target$.
</dl>

>> ExpandAll[(a + b) ^ 2 / (c + d)^2]
Expand All @@ -825,15 +924,38 @@ 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]

'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]]"

Expand Down
4 changes: 3 additions & 1 deletion mathics/builtin/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
1 change: 0 additions & 1 deletion mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2263,7 +2263,6 @@ def __neg__(self) -> "Integer":
def is_zero(self) -> bool:
return self.value == 0


Integer0 = Integer(0)
Integer1 = Integer(1)

Expand Down