2151 lines
71 KiB
Python
2151 lines
71 KiB
Python
|
from collections import defaultdict
|
||
|
|
||
|
from sympy.concrete.products import Product
|
||
|
from sympy.concrete.summations import Sum
|
||
|
from sympy.core import (Basic, S, Add, Mul, Pow, Symbol, sympify,
|
||
|
expand_func, Function, Dummy, Expr, factor_terms,
|
||
|
expand_power_exp, Eq)
|
||
|
from sympy.core.exprtools import factor_nc
|
||
|
from sympy.core.parameters import global_parameters
|
||
|
from sympy.core.function import (expand_log, count_ops, _mexpand,
|
||
|
nfloat, expand_mul, expand)
|
||
|
from sympy.core.numbers import Float, I, pi, Rational
|
||
|
from sympy.core.relational import Relational
|
||
|
from sympy.core.rules import Transform
|
||
|
from sympy.core.sorting import ordered
|
||
|
from sympy.core.sympify import _sympify
|
||
|
from sympy.core.traversal import bottom_up as _bottom_up, walk as _walk
|
||
|
from sympy.functions import gamma, exp, sqrt, log, exp_polar, re
|
||
|
from sympy.functions.combinatorial.factorials import CombinatorialFunction
|
||
|
from sympy.functions.elementary.complexes import unpolarify, Abs, sign
|
||
|
from sympy.functions.elementary.exponential import ExpBase
|
||
|
from sympy.functions.elementary.hyperbolic import HyperbolicFunction
|
||
|
from sympy.functions.elementary.integers import ceiling
|
||
|
from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold,
|
||
|
piecewise_simplify)
|
||
|
from sympy.functions.elementary.trigonometric import TrigonometricFunction
|
||
|
from sympy.functions.special.bessel import (BesselBase, besselj, besseli,
|
||
|
besselk, bessely, jn)
|
||
|
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||
|
from sympy.integrals.integrals import Integral
|
||
|
from sympy.matrices.expressions import (MatrixExpr, MatAdd, MatMul,
|
||
|
MatPow, MatrixSymbol)
|
||
|
from sympy.polys import together, cancel, factor
|
||
|
from sympy.polys.numberfields.minpoly import _is_sum_surds, _minimal_polynomial_sq
|
||
|
from sympy.simplify.combsimp import combsimp
|
||
|
from sympy.simplify.cse_opts import sub_pre, sub_post
|
||
|
from sympy.simplify.hyperexpand import hyperexpand
|
||
|
from sympy.simplify.powsimp import powsimp
|
||
|
from sympy.simplify.radsimp import radsimp, fraction, collect_abs
|
||
|
from sympy.simplify.sqrtdenest import sqrtdenest
|
||
|
from sympy.simplify.trigsimp import trigsimp, exptrigsimp
|
||
|
from sympy.utilities.decorator import deprecated
|
||
|
from sympy.utilities.iterables import has_variety, sift, subsets, iterable
|
||
|
from sympy.utilities.misc import as_int
|
||
|
|
||
|
import mpmath
|
||
|
|
||
|
|
||
|
def separatevars(expr, symbols=[], dict=False, force=False):
|
||
|
"""
|
||
|
Separates variables in an expression, if possible. By
|
||
|
default, it separates with respect to all symbols in an
|
||
|
expression and collects constant coefficients that are
|
||
|
independent of symbols.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
If ``dict=True`` then the separated terms will be returned
|
||
|
in a dictionary keyed to their corresponding symbols.
|
||
|
By default, all symbols in the expression will appear as
|
||
|
keys; if symbols are provided, then all those symbols will
|
||
|
be used as keys, and any terms in the expression containing
|
||
|
other symbols or non-symbols will be returned keyed to the
|
||
|
string 'coeff'. (Passing None for symbols will return the
|
||
|
expression in a dictionary keyed to 'coeff'.)
|
||
|
|
||
|
If ``force=True``, then bases of powers will be separated regardless
|
||
|
of assumptions on the symbols involved.
|
||
|
|
||
|
Notes
|
||
|
=====
|
||
|
|
||
|
The order of the factors is determined by Mul, so that the
|
||
|
separated expressions may not necessarily be grouped together.
|
||
|
|
||
|
Although factoring is necessary to separate variables in some
|
||
|
expressions, it is not necessary in all cases, so one should not
|
||
|
count on the returned factors being factored.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.abc import x, y, z, alpha
|
||
|
>>> from sympy import separatevars, sin
|
||
|
>>> separatevars((x*y)**y)
|
||
|
(x*y)**y
|
||
|
>>> separatevars((x*y)**y, force=True)
|
||
|
x**y*y**y
|
||
|
|
||
|
>>> e = 2*x**2*z*sin(y)+2*z*x**2
|
||
|
>>> separatevars(e)
|
||
|
2*x**2*z*(sin(y) + 1)
|
||
|
>>> separatevars(e, symbols=(x, y), dict=True)
|
||
|
{'coeff': 2*z, x: x**2, y: sin(y) + 1}
|
||
|
>>> separatevars(e, [x, y, alpha], dict=True)
|
||
|
{'coeff': 2*z, alpha: 1, x: x**2, y: sin(y) + 1}
|
||
|
|
||
|
If the expression is not really separable, or is only partially
|
||
|
separable, separatevars will do the best it can to separate it
|
||
|
by using factoring.
|
||
|
|
||
|
>>> separatevars(x + x*y - 3*x**2)
|
||
|
-x*(3*x - y - 1)
|
||
|
|
||
|
If the expression is not separable then expr is returned unchanged
|
||
|
or (if dict=True) then None is returned.
|
||
|
|
||
|
>>> eq = 2*x + y*sin(x)
|
||
|
>>> separatevars(eq) == eq
|
||
|
True
|
||
|
>>> separatevars(2*x + y*sin(x), symbols=(x, y), dict=True) is None
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
expr = sympify(expr)
|
||
|
if dict:
|
||
|
return _separatevars_dict(_separatevars(expr, force), symbols)
|
||
|
else:
|
||
|
return _separatevars(expr, force)
|
||
|
|
||
|
|
||
|
def _separatevars(expr, force):
|
||
|
if isinstance(expr, Abs):
|
||
|
arg = expr.args[0]
|
||
|
if arg.is_Mul and not arg.is_number:
|
||
|
s = separatevars(arg, dict=True, force=force)
|
||
|
if s is not None:
|
||
|
return Mul(*map(expr.func, s.values()))
|
||
|
else:
|
||
|
return expr
|
||
|
|
||
|
if len(expr.free_symbols) < 2:
|
||
|
return expr
|
||
|
|
||
|
# don't destroy a Mul since much of the work may already be done
|
||
|
if expr.is_Mul:
|
||
|
args = list(expr.args)
|
||
|
changed = False
|
||
|
for i, a in enumerate(args):
|
||
|
args[i] = separatevars(a, force)
|
||
|
changed = changed or args[i] != a
|
||
|
if changed:
|
||
|
expr = expr.func(*args)
|
||
|
return expr
|
||
|
|
||
|
# get a Pow ready for expansion
|
||
|
if expr.is_Pow and expr.base != S.Exp1:
|
||
|
expr = Pow(separatevars(expr.base, force=force), expr.exp)
|
||
|
|
||
|
# First try other expansion methods
|
||
|
expr = expr.expand(mul=False, multinomial=False, force=force)
|
||
|
|
||
|
_expr, reps = posify(expr) if force else (expr, {})
|
||
|
expr = factor(_expr).subs(reps)
|
||
|
|
||
|
if not expr.is_Add:
|
||
|
return expr
|
||
|
|
||
|
# Find any common coefficients to pull out
|
||
|
args = list(expr.args)
|
||
|
commonc = args[0].args_cnc(cset=True, warn=False)[0]
|
||
|
for i in args[1:]:
|
||
|
commonc &= i.args_cnc(cset=True, warn=False)[0]
|
||
|
commonc = Mul(*commonc)
|
||
|
commonc = commonc.as_coeff_Mul()[1] # ignore constants
|
||
|
commonc_set = commonc.args_cnc(cset=True, warn=False)[0]
|
||
|
|
||
|
# remove them
|
||
|
for i, a in enumerate(args):
|
||
|
c, nc = a.args_cnc(cset=True, warn=False)
|
||
|
c = c - commonc_set
|
||
|
args[i] = Mul(*c)*Mul(*nc)
|
||
|
nonsepar = Add(*args)
|
||
|
|
||
|
if len(nonsepar.free_symbols) > 1:
|
||
|
_expr = nonsepar
|
||
|
_expr, reps = posify(_expr) if force else (_expr, {})
|
||
|
_expr = (factor(_expr)).subs(reps)
|
||
|
|
||
|
if not _expr.is_Add:
|
||
|
nonsepar = _expr
|
||
|
|
||
|
return commonc*nonsepar
|
||
|
|
||
|
|
||
|
def _separatevars_dict(expr, symbols):
|
||
|
if symbols:
|
||
|
if not all(t.is_Atom for t in symbols):
|
||
|
raise ValueError("symbols must be Atoms.")
|
||
|
symbols = list(symbols)
|
||
|
elif symbols is None:
|
||
|
return {'coeff': expr}
|
||
|
else:
|
||
|
symbols = list(expr.free_symbols)
|
||
|
if not symbols:
|
||
|
return None
|
||
|
|
||
|
ret = {i: [] for i in symbols + ['coeff']}
|
||
|
|
||
|
for i in Mul.make_args(expr):
|
||
|
expsym = i.free_symbols
|
||
|
intersection = set(symbols).intersection(expsym)
|
||
|
if len(intersection) > 1:
|
||
|
return None
|
||
|
if len(intersection) == 0:
|
||
|
# There are no symbols, so it is part of the coefficient
|
||
|
ret['coeff'].append(i)
|
||
|
else:
|
||
|
ret[intersection.pop()].append(i)
|
||
|
|
||
|
# rebuild
|
||
|
for k, v in ret.items():
|
||
|
ret[k] = Mul(*v)
|
||
|
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def posify(eq):
|
||
|
"""Return ``eq`` (with generic symbols made positive) and a
|
||
|
dictionary containing the mapping between the old and new
|
||
|
symbols.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Any symbol that has positive=None will be replaced with a positive dummy
|
||
|
symbol having the same name. This replacement will allow more symbolic
|
||
|
processing of expressions, especially those involving powers and
|
||
|
logarithms.
|
||
|
|
||
|
A dictionary that can be sent to subs to restore ``eq`` to its original
|
||
|
symbols is also returned.
|
||
|
|
||
|
>>> from sympy import posify, Symbol, log, solve
|
||
|
>>> from sympy.abc import x
|
||
|
>>> posify(x + Symbol('p', positive=True) + Symbol('n', negative=True))
|
||
|
(_x + n + p, {_x: x})
|
||
|
|
||
|
>>> eq = 1/x
|
||
|
>>> log(eq).expand()
|
||
|
log(1/x)
|
||
|
>>> log(posify(eq)[0]).expand()
|
||
|
-log(_x)
|
||
|
>>> p, rep = posify(eq)
|
||
|
>>> log(p).expand().subs(rep)
|
||
|
-log(x)
|
||
|
|
||
|
It is possible to apply the same transformations to an iterable
|
||
|
of expressions:
|
||
|
|
||
|
>>> eq = x**2 - 4
|
||
|
>>> solve(eq, x)
|
||
|
[-2, 2]
|
||
|
>>> eq_x, reps = posify([eq, x]); eq_x
|
||
|
[_x**2 - 4, _x]
|
||
|
>>> solve(*eq_x)
|
||
|
[2]
|
||
|
"""
|
||
|
eq = sympify(eq)
|
||
|
if iterable(eq):
|
||
|
f = type(eq)
|
||
|
eq = list(eq)
|
||
|
syms = set()
|
||
|
for e in eq:
|
||
|
syms = syms.union(e.atoms(Symbol))
|
||
|
reps = {}
|
||
|
for s in syms:
|
||
|
reps.update({v: k for k, v in posify(s)[1].items()})
|
||
|
for i, e in enumerate(eq):
|
||
|
eq[i] = e.subs(reps)
|
||
|
return f(eq), {r: s for s, r in reps.items()}
|
||
|
|
||
|
reps = {s: Dummy(s.name, positive=True, **s.assumptions0)
|
||
|
for s in eq.free_symbols if s.is_positive is None}
|
||
|
eq = eq.subs(reps)
|
||
|
return eq, {r: s for s, r in reps.items()}
|
||
|
|
||
|
|
||
|
def hypersimp(f, k):
|
||
|
"""Given combinatorial term f(k) simplify its consecutive term ratio
|
||
|
i.e. f(k+1)/f(k). The input term can be composed of functions and
|
||
|
integer sequences which have equivalent representation in terms
|
||
|
of gamma special function.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
The algorithm performs three basic steps:
|
||
|
|
||
|
1. Rewrite all functions in terms of gamma, if possible.
|
||
|
|
||
|
2. Rewrite all occurrences of gamma in terms of products
|
||
|
of gamma and rising factorial with integer, absolute
|
||
|
constant exponent.
|
||
|
|
||
|
3. Perform simplification of nested fractions, powers
|
||
|
and if the resulting expression is a quotient of
|
||
|
polynomials, reduce their total degree.
|
||
|
|
||
|
If f(k) is hypergeometric then as result we arrive with a
|
||
|
quotient of polynomials of minimal degree. Otherwise None
|
||
|
is returned.
|
||
|
|
||
|
For more information on the implemented algorithm refer to:
|
||
|
|
||
|
1. W. Koepf, Algorithms for m-fold Hypergeometric Summation,
|
||
|
Journal of Symbolic Computation (1995) 20, 399-417
|
||
|
"""
|
||
|
f = sympify(f)
|
||
|
|
||
|
g = f.subs(k, k + 1) / f
|
||
|
|
||
|
g = g.rewrite(gamma)
|
||
|
if g.has(Piecewise):
|
||
|
g = piecewise_fold(g)
|
||
|
g = g.args[-1][0]
|
||
|
g = expand_func(g)
|
||
|
g = powsimp(g, deep=True, combine='exp')
|
||
|
|
||
|
if g.is_rational_function(k):
|
||
|
return simplify(g, ratio=S.Infinity)
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
|
||
|
def hypersimilar(f, g, k):
|
||
|
"""
|
||
|
Returns True if ``f`` and ``g`` are hyper-similar.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Similarity in hypergeometric sense means that a quotient of
|
||
|
f(k) and g(k) is a rational function in ``k``. This procedure
|
||
|
is useful in solving recurrence relations.
|
||
|
|
||
|
For more information see hypersimp().
|
||
|
|
||
|
"""
|
||
|
f, g = list(map(sympify, (f, g)))
|
||
|
|
||
|
h = (f/g).rewrite(gamma)
|
||
|
h = h.expand(func=True, basic=False)
|
||
|
|
||
|
return h.is_rational_function(k)
|
||
|
|
||
|
|
||
|
def signsimp(expr, evaluate=None):
|
||
|
"""Make all Add sub-expressions canonical wrt sign.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
If an Add subexpression, ``a``, can have a sign extracted,
|
||
|
as determined by could_extract_minus_sign, it is replaced
|
||
|
with Mul(-1, a, evaluate=False). This allows signs to be
|
||
|
extracted from powers and products.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import signsimp, exp, symbols
|
||
|
>>> from sympy.abc import x, y
|
||
|
>>> i = symbols('i', odd=True)
|
||
|
>>> n = -1 + 1/x
|
||
|
>>> n/x/(-n)**2 - 1/n/x
|
||
|
(-1 + 1/x)/(x*(1 - 1/x)**2) - 1/(x*(-1 + 1/x))
|
||
|
>>> signsimp(_)
|
||
|
0
|
||
|
>>> x*n + x*-n
|
||
|
x*(-1 + 1/x) + x*(1 - 1/x)
|
||
|
>>> signsimp(_)
|
||
|
0
|
||
|
|
||
|
Since powers automatically handle leading signs
|
||
|
|
||
|
>>> (-2)**i
|
||
|
-2**i
|
||
|
|
||
|
signsimp can be used to put the base of a power with an integer
|
||
|
exponent into canonical form:
|
||
|
|
||
|
>>> n**i
|
||
|
(-1 + 1/x)**i
|
||
|
|
||
|
By default, signsimp does not leave behind any hollow simplification:
|
||
|
if making an Add canonical wrt sign didn't change the expression, the
|
||
|
original Add is restored. If this is not desired then the keyword
|
||
|
``evaluate`` can be set to False:
|
||
|
|
||
|
>>> e = exp(y - x)
|
||
|
>>> signsimp(e) == e
|
||
|
True
|
||
|
>>> signsimp(e, evaluate=False)
|
||
|
exp(-(x - y))
|
||
|
|
||
|
"""
|
||
|
if evaluate is None:
|
||
|
evaluate = global_parameters.evaluate
|
||
|
expr = sympify(expr)
|
||
|
if not isinstance(expr, (Expr, Relational)) or expr.is_Atom:
|
||
|
return expr
|
||
|
# get rid of an pre-existing unevaluation regarding sign
|
||
|
e = expr.replace(lambda x: x.is_Mul and -(-x) != x, lambda x: -(-x))
|
||
|
e = sub_post(sub_pre(e))
|
||
|
if not isinstance(e, (Expr, Relational)) or e.is_Atom:
|
||
|
return e
|
||
|
if e.is_Add:
|
||
|
rv = e.func(*[signsimp(a) for a in e.args])
|
||
|
if not evaluate and isinstance(rv, Add
|
||
|
) and rv.could_extract_minus_sign():
|
||
|
return Mul(S.NegativeOne, -rv, evaluate=False)
|
||
|
return rv
|
||
|
if evaluate:
|
||
|
e = e.replace(lambda x: x.is_Mul and -(-x) != x, lambda x: -(-x))
|
||
|
return e
|
||
|
|
||
|
|
||
|
def simplify(expr, ratio=1.7, measure=count_ops, rational=False, inverse=False, doit=True, **kwargs):
|
||
|
"""Simplifies the given expression.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Simplification is not a well defined term and the exact strategies
|
||
|
this function tries can change in the future versions of SymPy. If
|
||
|
your algorithm relies on "simplification" (whatever it is), try to
|
||
|
determine what you need exactly - is it powsimp()?, radsimp()?,
|
||
|
together()?, logcombine()?, or something else? And use this particular
|
||
|
function directly, because those are well defined and thus your algorithm
|
||
|
will be robust.
|
||
|
|
||
|
Nonetheless, especially for interactive use, or when you do not know
|
||
|
anything about the structure of the expression, simplify() tries to apply
|
||
|
intelligent heuristics to make the input expression "simpler". For
|
||
|
example:
|
||
|
|
||
|
>>> from sympy import simplify, cos, sin
|
||
|
>>> from sympy.abc import x, y
|
||
|
>>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2)
|
||
|
>>> a
|
||
|
(x**2 + x)/(x*sin(y)**2 + x*cos(y)**2)
|
||
|
>>> simplify(a)
|
||
|
x + 1
|
||
|
|
||
|
Note that we could have obtained the same result by using specific
|
||
|
simplification functions:
|
||
|
|
||
|
>>> from sympy import trigsimp, cancel
|
||
|
>>> trigsimp(a)
|
||
|
(x**2 + x)/x
|
||
|
>>> cancel(_)
|
||
|
x + 1
|
||
|
|
||
|
In some cases, applying :func:`simplify` may actually result in some more
|
||
|
complicated expression. The default ``ratio=1.7`` prevents more extreme
|
||
|
cases: if (result length)/(input length) > ratio, then input is returned
|
||
|
unmodified. The ``measure`` parameter lets you specify the function used
|
||
|
to determine how complex an expression is. The function should take a
|
||
|
single argument as an expression and return a number such that if
|
||
|
expression ``a`` is more complex than expression ``b``, then
|
||
|
``measure(a) > measure(b)``. The default measure function is
|
||
|
:func:`~.count_ops`, which returns the total number of operations in the
|
||
|
expression.
|
||
|
|
||
|
For example, if ``ratio=1``, ``simplify`` output cannot be longer
|
||
|
than input.
|
||
|
|
||
|
::
|
||
|
|
||
|
>>> from sympy import sqrt, simplify, count_ops, oo
|
||
|
>>> root = 1/(sqrt(2)+3)
|
||
|
|
||
|
Since ``simplify(root)`` would result in a slightly longer expression,
|
||
|
root is returned unchanged instead::
|
||
|
|
||
|
>>> simplify(root, ratio=1) == root
|
||
|
True
|
||
|
|
||
|
If ``ratio=oo``, simplify will be applied anyway::
|
||
|
|
||
|
>>> count_ops(simplify(root, ratio=oo)) > count_ops(root)
|
||
|
True
|
||
|
|
||
|
Note that the shortest expression is not necessary the simplest, so
|
||
|
setting ``ratio`` to 1 may not be a good idea.
|
||
|
Heuristically, the default value ``ratio=1.7`` seems like a reasonable
|
||
|
choice.
|
||
|
|
||
|
You can easily define your own measure function based on what you feel
|
||
|
should represent the "size" or "complexity" of the input expression. Note
|
||
|
that some choices, such as ``lambda expr: len(str(expr))`` may appear to be
|
||
|
good metrics, but have other problems (in this case, the measure function
|
||
|
may slow down simplify too much for very large expressions). If you do not
|
||
|
know what a good metric would be, the default, ``count_ops``, is a good
|
||
|
one.
|
||
|
|
||
|
For example:
|
||
|
|
||
|
>>> from sympy import symbols, log
|
||
|
>>> a, b = symbols('a b', positive=True)
|
||
|
>>> g = log(a) + log(b) + log(a)*log(1/b)
|
||
|
>>> h = simplify(g)
|
||
|
>>> h
|
||
|
log(a*b**(1 - log(a)))
|
||
|
>>> count_ops(g)
|
||
|
8
|
||
|
>>> count_ops(h)
|
||
|
5
|
||
|
|
||
|
So you can see that ``h`` is simpler than ``g`` using the count_ops metric.
|
||
|
However, we may not like how ``simplify`` (in this case, using
|
||
|
``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way
|
||
|
to reduce this would be to give more weight to powers as operations in
|
||
|
``count_ops``. We can do this by using the ``visual=True`` option:
|
||
|
|
||
|
>>> print(count_ops(g, visual=True))
|
||
|
2*ADD + DIV + 4*LOG + MUL
|
||
|
>>> print(count_ops(h, visual=True))
|
||
|
2*LOG + MUL + POW + SUB
|
||
|
|
||
|
>>> from sympy import Symbol, S
|
||
|
>>> def my_measure(expr):
|
||
|
... POW = Symbol('POW')
|
||
|
... # Discourage powers by giving POW a weight of 10
|
||
|
... count = count_ops(expr, visual=True).subs(POW, 10)
|
||
|
... # Every other operation gets a weight of 1 (the default)
|
||
|
... count = count.replace(Symbol, type(S.One))
|
||
|
... return count
|
||
|
>>> my_measure(g)
|
||
|
8
|
||
|
>>> my_measure(h)
|
||
|
14
|
||
|
>>> 15./8 > 1.7 # 1.7 is the default ratio
|
||
|
True
|
||
|
>>> simplify(g, measure=my_measure)
|
||
|
-log(a)*log(b) + log(a) + log(b)
|
||
|
|
||
|
Note that because ``simplify()`` internally tries many different
|
||
|
simplification strategies and then compares them using the measure
|
||
|
function, we get a completely different result that is still different
|
||
|
from the input expression by doing this.
|
||
|
|
||
|
If ``rational=True``, Floats will be recast as Rationals before simplification.
|
||
|
If ``rational=None``, Floats will be recast as Rationals but the result will
|
||
|
be recast as Floats. If rational=False(default) then nothing will be done
|
||
|
to the Floats.
|
||
|
|
||
|
If ``inverse=True``, it will be assumed that a composition of inverse
|
||
|
functions, such as sin and asin, can be cancelled in any order.
|
||
|
For example, ``asin(sin(x))`` will yield ``x`` without checking whether
|
||
|
x belongs to the set where this relation is true. The default is
|
||
|
False.
|
||
|
|
||
|
Note that ``simplify()`` automatically calls ``doit()`` on the final
|
||
|
expression. You can avoid this behavior by passing ``doit=False`` as
|
||
|
an argument.
|
||
|
|
||
|
Also, it should be noted that simplifying a boolean expression is not
|
||
|
well defined. If the expression prefers automatic evaluation (such as
|
||
|
:obj:`~.Eq()` or :obj:`~.Or()`), simplification will return ``True`` or
|
||
|
``False`` if truth value can be determined. If the expression is not
|
||
|
evaluated by default (such as :obj:`~.Predicate()`), simplification will
|
||
|
not reduce it and you should use :func:`~.refine()` or :func:`~.ask()`
|
||
|
function. This inconsistency will be resolved in future version.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.assumptions.refine.refine : Simplification using assumptions.
|
||
|
sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
|
||
|
"""
|
||
|
|
||
|
def shorter(*choices):
|
||
|
"""
|
||
|
Return the choice that has the fewest ops. In case of a tie,
|
||
|
the expression listed first is selected.
|
||
|
"""
|
||
|
if not has_variety(choices):
|
||
|
return choices[0]
|
||
|
return min(choices, key=measure)
|
||
|
|
||
|
def done(e):
|
||
|
rv = e.doit() if doit else e
|
||
|
return shorter(rv, collect_abs(rv))
|
||
|
|
||
|
expr = sympify(expr, rational=rational)
|
||
|
kwargs = {
|
||
|
"ratio": kwargs.get('ratio', ratio),
|
||
|
"measure": kwargs.get('measure', measure),
|
||
|
"rational": kwargs.get('rational', rational),
|
||
|
"inverse": kwargs.get('inverse', inverse),
|
||
|
"doit": kwargs.get('doit', doit)}
|
||
|
# no routine for Expr needs to check for is_zero
|
||
|
if isinstance(expr, Expr) and expr.is_zero:
|
||
|
return S.Zero if not expr.is_Number else expr
|
||
|
|
||
|
_eval_simplify = getattr(expr, '_eval_simplify', None)
|
||
|
if _eval_simplify is not None:
|
||
|
return _eval_simplify(**kwargs)
|
||
|
|
||
|
original_expr = expr = collect_abs(signsimp(expr))
|
||
|
|
||
|
if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack
|
||
|
return expr
|
||
|
|
||
|
if inverse and expr.has(Function):
|
||
|
expr = inversecombine(expr)
|
||
|
if not expr.args: # simplified to atomic
|
||
|
return expr
|
||
|
|
||
|
# do deep simplification
|
||
|
handled = Add, Mul, Pow, ExpBase
|
||
|
expr = expr.replace(
|
||
|
# here, checking for x.args is not enough because Basic has
|
||
|
# args but Basic does not always play well with replace, e.g.
|
||
|
# when simultaneous is True found expressions will be masked
|
||
|
# off with a Dummy but not all Basic objects in an expression
|
||
|
# can be replaced with a Dummy
|
||
|
lambda x: isinstance(x, Expr) and x.args and not isinstance(
|
||
|
x, handled),
|
||
|
lambda x: x.func(*[simplify(i, **kwargs) for i in x.args]),
|
||
|
simultaneous=False)
|
||
|
if not isinstance(expr, handled):
|
||
|
return done(expr)
|
||
|
|
||
|
if not expr.is_commutative:
|
||
|
expr = nc_simplify(expr)
|
||
|
|
||
|
# TODO: Apply different strategies, considering expression pattern:
|
||
|
# is it a purely rational function? Is there any trigonometric function?...
|
||
|
# See also https://github.com/sympy/sympy/pull/185.
|
||
|
|
||
|
|
||
|
# rationalize Floats
|
||
|
floats = False
|
||
|
if rational is not False and expr.has(Float):
|
||
|
floats = True
|
||
|
expr = nsimplify(expr, rational=True)
|
||
|
|
||
|
expr = _bottom_up(expr, lambda w: getattr(w, 'normal', lambda: w)())
|
||
|
expr = Mul(*powsimp(expr).as_content_primitive())
|
||
|
_e = cancel(expr)
|
||
|
expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829
|
||
|
expr2 = shorter(together(expr, deep=True), together(expr1, deep=True))
|
||
|
|
||
|
if ratio is S.Infinity:
|
||
|
expr = expr2
|
||
|
else:
|
||
|
expr = shorter(expr2, expr1, expr)
|
||
|
if not isinstance(expr, Basic): # XXX: temporary hack
|
||
|
return expr
|
||
|
|
||
|
expr = factor_terms(expr, sign=False)
|
||
|
|
||
|
# must come before `Piecewise` since this introduces more `Piecewise` terms
|
||
|
if expr.has(sign):
|
||
|
expr = expr.rewrite(Abs)
|
||
|
|
||
|
# Deal with Piecewise separately to avoid recursive growth of expressions
|
||
|
if expr.has(Piecewise):
|
||
|
# Fold into a single Piecewise
|
||
|
expr = piecewise_fold(expr)
|
||
|
# Apply doit, if doit=True
|
||
|
expr = done(expr)
|
||
|
# Still a Piecewise?
|
||
|
if expr.has(Piecewise):
|
||
|
# Fold into a single Piecewise, in case doit lead to some
|
||
|
# expressions being Piecewise
|
||
|
expr = piecewise_fold(expr)
|
||
|
# kroneckersimp also affects Piecewise
|
||
|
if expr.has(KroneckerDelta):
|
||
|
expr = kroneckersimp(expr)
|
||
|
# Still a Piecewise?
|
||
|
if expr.has(Piecewise):
|
||
|
# Do not apply doit on the segments as it has already
|
||
|
# been done above, but simplify
|
||
|
expr = piecewise_simplify(expr, deep=True, doit=False)
|
||
|
# Still a Piecewise?
|
||
|
if expr.has(Piecewise):
|
||
|
# Try factor common terms
|
||
|
expr = shorter(expr, factor_terms(expr))
|
||
|
# As all expressions have been simplified above with the
|
||
|
# complete simplify, nothing more needs to be done here
|
||
|
return expr
|
||
|
|
||
|
# hyperexpand automatically only works on hypergeometric terms
|
||
|
# Do this after the Piecewise part to avoid recursive expansion
|
||
|
expr = hyperexpand(expr)
|
||
|
|
||
|
if expr.has(KroneckerDelta):
|
||
|
expr = kroneckersimp(expr)
|
||
|
|
||
|
if expr.has(BesselBase):
|
||
|
expr = besselsimp(expr)
|
||
|
|
||
|
if expr.has(TrigonometricFunction, HyperbolicFunction):
|
||
|
expr = trigsimp(expr, deep=True)
|
||
|
|
||
|
if expr.has(log):
|
||
|
expr = shorter(expand_log(expr, deep=True), logcombine(expr))
|
||
|
|
||
|
if expr.has(CombinatorialFunction, gamma):
|
||
|
# expression with gamma functions or non-integer arguments is
|
||
|
# automatically passed to gammasimp
|
||
|
expr = combsimp(expr)
|
||
|
|
||
|
if expr.has(Sum):
|
||
|
expr = sum_simplify(expr, **kwargs)
|
||
|
|
||
|
if expr.has(Integral):
|
||
|
expr = expr.xreplace({
|
||
|
i: factor_terms(i) for i in expr.atoms(Integral)})
|
||
|
|
||
|
if expr.has(Product):
|
||
|
expr = product_simplify(expr, **kwargs)
|
||
|
|
||
|
from sympy.physics.units import Quantity
|
||
|
|
||
|
if expr.has(Quantity):
|
||
|
from sympy.physics.units.util import quantity_simplify
|
||
|
expr = quantity_simplify(expr)
|
||
|
|
||
|
short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr)
|
||
|
short = shorter(short, cancel(short))
|
||
|
short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short)))
|
||
|
if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase, exp):
|
||
|
short = exptrigsimp(short)
|
||
|
|
||
|
# get rid of hollow 2-arg Mul factorization
|
||
|
hollow_mul = Transform(
|
||
|
lambda x: Mul(*x.args),
|
||
|
lambda x:
|
||
|
x.is_Mul and
|
||
|
len(x.args) == 2 and
|
||
|
x.args[0].is_Number and
|
||
|
x.args[1].is_Add and
|
||
|
x.is_commutative)
|
||
|
expr = short.xreplace(hollow_mul)
|
||
|
|
||
|
numer, denom = expr.as_numer_denom()
|
||
|
if denom.is_Add:
|
||
|
n, d = fraction(radsimp(1/denom, symbolic=False, max_terms=1))
|
||
|
if n is not S.One:
|
||
|
expr = (numer*n).expand()/d
|
||
|
|
||
|
if expr.could_extract_minus_sign():
|
||
|
n, d = fraction(expr)
|
||
|
if d != 0:
|
||
|
expr = signsimp(-n/(-d))
|
||
|
|
||
|
if measure(expr) > ratio*measure(original_expr):
|
||
|
expr = original_expr
|
||
|
|
||
|
# restore floats
|
||
|
if floats and rational is None:
|
||
|
expr = nfloat(expr, exponent=False)
|
||
|
|
||
|
return done(expr)
|
||
|
|
||
|
|
||
|
def sum_simplify(s, **kwargs):
|
||
|
"""Main function for Sum simplification"""
|
||
|
if not isinstance(s, Add):
|
||
|
s = s.xreplace({a: sum_simplify(a, **kwargs)
|
||
|
for a in s.atoms(Add) if a.has(Sum)})
|
||
|
s = expand(s)
|
||
|
if not isinstance(s, Add):
|
||
|
return s
|
||
|
|
||
|
terms = s.args
|
||
|
s_t = [] # Sum Terms
|
||
|
o_t = [] # Other Terms
|
||
|
|
||
|
for term in terms:
|
||
|
sum_terms, other = sift(Mul.make_args(term),
|
||
|
lambda i: isinstance(i, Sum), binary=True)
|
||
|
if not sum_terms:
|
||
|
o_t.append(term)
|
||
|
continue
|
||
|
other = [Mul(*other)]
|
||
|
s_t.append(Mul(*(other + [s._eval_simplify(**kwargs) for s in sum_terms])))
|
||
|
|
||
|
result = Add(sum_combine(s_t), *o_t)
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def sum_combine(s_t):
|
||
|
"""Helper function for Sum simplification
|
||
|
|
||
|
Attempts to simplify a list of sums, by combining limits / sum function's
|
||
|
returns the simplified sum
|
||
|
"""
|
||
|
used = [False] * len(s_t)
|
||
|
|
||
|
for method in range(2):
|
||
|
for i, s_term1 in enumerate(s_t):
|
||
|
if not used[i]:
|
||
|
for j, s_term2 in enumerate(s_t):
|
||
|
if not used[j] and i != j:
|
||
|
temp = sum_add(s_term1, s_term2, method)
|
||
|
if isinstance(temp, (Sum, Mul)):
|
||
|
s_t[i] = temp
|
||
|
s_term1 = s_t[i]
|
||
|
used[j] = True
|
||
|
|
||
|
result = S.Zero
|
||
|
for i, s_term in enumerate(s_t):
|
||
|
if not used[i]:
|
||
|
result = Add(result, s_term)
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def factor_sum(self, limits=None, radical=False, clear=False, fraction=False, sign=True):
|
||
|
"""Return Sum with constant factors extracted.
|
||
|
|
||
|
If ``limits`` is specified then ``self`` is the summand; the other
|
||
|
keywords are passed to ``factor_terms``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Sum
|
||
|
>>> from sympy.abc import x, y
|
||
|
>>> from sympy.simplify.simplify import factor_sum
|
||
|
>>> s = Sum(x*y, (x, 1, 3))
|
||
|
>>> factor_sum(s)
|
||
|
y*Sum(x, (x, 1, 3))
|
||
|
>>> factor_sum(s.function, s.limits)
|
||
|
y*Sum(x, (x, 1, 3))
|
||
|
"""
|
||
|
# XXX deprecate in favor of direct call to factor_terms
|
||
|
kwargs = {"radical": radical, "clear": clear,
|
||
|
"fraction": fraction, "sign": sign}
|
||
|
expr = Sum(self, *limits) if limits else self
|
||
|
return factor_terms(expr, **kwargs)
|
||
|
|
||
|
|
||
|
def sum_add(self, other, method=0):
|
||
|
"""Helper function for Sum simplification"""
|
||
|
#we know this is something in terms of a constant * a sum
|
||
|
#so we temporarily put the constants inside for simplification
|
||
|
#then simplify the result
|
||
|
def __refactor(val):
|
||
|
args = Mul.make_args(val)
|
||
|
sumv = next(x for x in args if isinstance(x, Sum))
|
||
|
constant = Mul(*[x for x in args if x != sumv])
|
||
|
return Sum(constant * sumv.function, *sumv.limits)
|
||
|
|
||
|
if isinstance(self, Mul):
|
||
|
rself = __refactor(self)
|
||
|
else:
|
||
|
rself = self
|
||
|
|
||
|
if isinstance(other, Mul):
|
||
|
rother = __refactor(other)
|
||
|
else:
|
||
|
rother = other
|
||
|
|
||
|
if type(rself) is type(rother):
|
||
|
if method == 0:
|
||
|
if rself.limits == rother.limits:
|
||
|
return factor_sum(Sum(rself.function + rother.function, *rself.limits))
|
||
|
elif method == 1:
|
||
|
if simplify(rself.function - rother.function) == 0:
|
||
|
if len(rself.limits) == len(rother.limits) == 1:
|
||
|
i = rself.limits[0][0]
|
||
|
x1 = rself.limits[0][1]
|
||
|
y1 = rself.limits[0][2]
|
||
|
j = rother.limits[0][0]
|
||
|
x2 = rother.limits[0][1]
|
||
|
y2 = rother.limits[0][2]
|
||
|
|
||
|
if i == j:
|
||
|
if x2 == y1 + 1:
|
||
|
return factor_sum(Sum(rself.function, (i, x1, y2)))
|
||
|
elif x1 == y2 + 1:
|
||
|
return factor_sum(Sum(rself.function, (i, x2, y1)))
|
||
|
|
||
|
return Add(self, other)
|
||
|
|
||
|
|
||
|
def product_simplify(s, **kwargs):
|
||
|
"""Main function for Product simplification"""
|
||
|
terms = Mul.make_args(s)
|
||
|
p_t = [] # Product Terms
|
||
|
o_t = [] # Other Terms
|
||
|
|
||
|
deep = kwargs.get('deep', True)
|
||
|
for term in terms:
|
||
|
if isinstance(term, Product):
|
||
|
if deep:
|
||
|
p_t.append(Product(term.function.simplify(**kwargs),
|
||
|
*term.limits))
|
||
|
else:
|
||
|
p_t.append(term)
|
||
|
else:
|
||
|
o_t.append(term)
|
||
|
|
||
|
used = [False] * len(p_t)
|
||
|
|
||
|
for method in range(2):
|
||
|
for i, p_term1 in enumerate(p_t):
|
||
|
if not used[i]:
|
||
|
for j, p_term2 in enumerate(p_t):
|
||
|
if not used[j] and i != j:
|
||
|
tmp_prod = product_mul(p_term1, p_term2, method)
|
||
|
if isinstance(tmp_prod, Product):
|
||
|
p_t[i] = tmp_prod
|
||
|
used[j] = True
|
||
|
|
||
|
result = Mul(*o_t)
|
||
|
|
||
|
for i, p_term in enumerate(p_t):
|
||
|
if not used[i]:
|
||
|
result = Mul(result, p_term)
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def product_mul(self, other, method=0):
|
||
|
"""Helper function for Product simplification"""
|
||
|
if type(self) is type(other):
|
||
|
if method == 0:
|
||
|
if self.limits == other.limits:
|
||
|
return Product(self.function * other.function, *self.limits)
|
||
|
elif method == 1:
|
||
|
if simplify(self.function - other.function) == 0:
|
||
|
if len(self.limits) == len(other.limits) == 1:
|
||
|
i = self.limits[0][0]
|
||
|
x1 = self.limits[0][1]
|
||
|
y1 = self.limits[0][2]
|
||
|
j = other.limits[0][0]
|
||
|
x2 = other.limits[0][1]
|
||
|
y2 = other.limits[0][2]
|
||
|
|
||
|
if i == j:
|
||
|
if x2 == y1 + 1:
|
||
|
return Product(self.function, (i, x1, y2))
|
||
|
elif x1 == y2 + 1:
|
||
|
return Product(self.function, (i, x2, y1))
|
||
|
|
||
|
return Mul(self, other)
|
||
|
|
||
|
|
||
|
def _nthroot_solve(p, n, prec):
|
||
|
"""
|
||
|
helper function for ``nthroot``
|
||
|
It denests ``p**Rational(1, n)`` using its minimal polynomial
|
||
|
"""
|
||
|
from sympy.solvers import solve
|
||
|
while n % 2 == 0:
|
||
|
p = sqrtdenest(sqrt(p))
|
||
|
n = n // 2
|
||
|
if n == 1:
|
||
|
return p
|
||
|
pn = p**Rational(1, n)
|
||
|
x = Symbol('x')
|
||
|
f = _minimal_polynomial_sq(p, n, x)
|
||
|
if f is None:
|
||
|
return None
|
||
|
sols = solve(f, x)
|
||
|
for sol in sols:
|
||
|
if abs(sol - pn).n() < 1./10**prec:
|
||
|
sol = sqrtdenest(sol)
|
||
|
if _mexpand(sol**n) == p:
|
||
|
return sol
|
||
|
|
||
|
|
||
|
def logcombine(expr, force=False):
|
||
|
"""
|
||
|
Takes logarithms and combines them using the following rules:
|
||
|
|
||
|
- log(x) + log(y) == log(x*y) if both are positive
|
||
|
- a*log(x) == log(x**a) if x is positive and a is real
|
||
|
|
||
|
If ``force`` is ``True`` then the assumptions above will be assumed to hold if
|
||
|
there is no assumption already in place on a quantity. For example, if
|
||
|
``a`` is imaginary or the argument negative, force will not perform a
|
||
|
combination but if ``a`` is a symbol with no assumptions the change will
|
||
|
take place.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Symbol, symbols, log, logcombine, I
|
||
|
>>> from sympy.abc import a, x, y, z
|
||
|
>>> logcombine(a*log(x) + log(y) - log(z))
|
||
|
a*log(x) + log(y) - log(z)
|
||
|
>>> logcombine(a*log(x) + log(y) - log(z), force=True)
|
||
|
log(x**a*y/z)
|
||
|
>>> x,y,z = symbols('x,y,z', positive=True)
|
||
|
>>> a = Symbol('a', real=True)
|
||
|
>>> logcombine(a*log(x) + log(y) - log(z))
|
||
|
log(x**a*y/z)
|
||
|
|
||
|
The transformation is limited to factors and/or terms that
|
||
|
contain logs, so the result depends on the initial state of
|
||
|
expansion:
|
||
|
|
||
|
>>> eq = (2 + 3*I)*log(x)
|
||
|
>>> logcombine(eq, force=True) == eq
|
||
|
True
|
||
|
>>> logcombine(eq.expand(), force=True)
|
||
|
log(x**2) + I*log(x**3)
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
posify: replace all symbols with symbols having positive assumptions
|
||
|
sympy.core.function.expand_log: expand the logarithms of products
|
||
|
and powers; the opposite of logcombine
|
||
|
|
||
|
"""
|
||
|
|
||
|
def f(rv):
|
||
|
if not (rv.is_Add or rv.is_Mul):
|
||
|
return rv
|
||
|
|
||
|
def gooda(a):
|
||
|
# bool to tell whether the leading ``a`` in ``a*log(x)``
|
||
|
# could appear as log(x**a)
|
||
|
return (a is not S.NegativeOne and # -1 *could* go, but we disallow
|
||
|
(a.is_extended_real or force and a.is_extended_real is not False))
|
||
|
|
||
|
def goodlog(l):
|
||
|
# bool to tell whether log ``l``'s argument can combine with others
|
||
|
a = l.args[0]
|
||
|
return a.is_positive or force and a.is_nonpositive is not False
|
||
|
|
||
|
other = []
|
||
|
logs = []
|
||
|
log1 = defaultdict(list)
|
||
|
for a in Add.make_args(rv):
|
||
|
if isinstance(a, log) and goodlog(a):
|
||
|
log1[()].append(([], a))
|
||
|
elif not a.is_Mul:
|
||
|
other.append(a)
|
||
|
else:
|
||
|
ot = []
|
||
|
co = []
|
||
|
lo = []
|
||
|
for ai in a.args:
|
||
|
if ai.is_Rational and ai < 0:
|
||
|
ot.append(S.NegativeOne)
|
||
|
co.append(-ai)
|
||
|
elif isinstance(ai, log) and goodlog(ai):
|
||
|
lo.append(ai)
|
||
|
elif gooda(ai):
|
||
|
co.append(ai)
|
||
|
else:
|
||
|
ot.append(ai)
|
||
|
if len(lo) > 1:
|
||
|
logs.append((ot, co, lo))
|
||
|
elif lo:
|
||
|
log1[tuple(ot)].append((co, lo[0]))
|
||
|
else:
|
||
|
other.append(a)
|
||
|
|
||
|
# if there is only one log in other, put it with the
|
||
|
# good logs
|
||
|
if len(other) == 1 and isinstance(other[0], log):
|
||
|
log1[()].append(([], other.pop()))
|
||
|
# if there is only one log at each coefficient and none have
|
||
|
# an exponent to place inside the log then there is nothing to do
|
||
|
if not logs and all(len(log1[k]) == 1 and log1[k][0] == [] for k in log1):
|
||
|
return rv
|
||
|
|
||
|
# collapse multi-logs as far as possible in a canonical way
|
||
|
# TODO: see if x*log(a)+x*log(a)*log(b) -> x*log(a)*(1+log(b))?
|
||
|
# -- in this case, it's unambiguous, but if it were were a log(c) in
|
||
|
# each term then it's arbitrary whether they are grouped by log(a) or
|
||
|
# by log(c). So for now, just leave this alone; it's probably better to
|
||
|
# let the user decide
|
||
|
for o, e, l in logs:
|
||
|
l = list(ordered(l))
|
||
|
e = log(l.pop(0).args[0]**Mul(*e))
|
||
|
while l:
|
||
|
li = l.pop(0)
|
||
|
e = log(li.args[0]**e)
|
||
|
c, l = Mul(*o), e
|
||
|
if isinstance(l, log): # it should be, but check to be sure
|
||
|
log1[(c,)].append(([], l))
|
||
|
else:
|
||
|
other.append(c*l)
|
||
|
|
||
|
# logs that have the same coefficient can multiply
|
||
|
for k in list(log1.keys()):
|
||
|
log1[Mul(*k)] = log(logcombine(Mul(*[
|
||
|
l.args[0]**Mul(*c) for c, l in log1.pop(k)]),
|
||
|
force=force), evaluate=False)
|
||
|
|
||
|
# logs that have oppositely signed coefficients can divide
|
||
|
for k in ordered(list(log1.keys())):
|
||
|
if k not in log1: # already popped as -k
|
||
|
continue
|
||
|
if -k in log1:
|
||
|
# figure out which has the minus sign; the one with
|
||
|
# more op counts should be the one
|
||
|
num, den = k, -k
|
||
|
if num.count_ops() > den.count_ops():
|
||
|
num, den = den, num
|
||
|
other.append(
|
||
|
num*log(log1.pop(num).args[0]/log1.pop(den).args[0],
|
||
|
evaluate=False))
|
||
|
else:
|
||
|
other.append(k*log1.pop(k))
|
||
|
|
||
|
return Add(*other)
|
||
|
|
||
|
return _bottom_up(expr, f)
|
||
|
|
||
|
|
||
|
def inversecombine(expr):
|
||
|
"""Simplify the composition of a function and its inverse.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
No attention is paid to whether the inverse is a left inverse or a
|
||
|
right inverse; thus, the result will in general not be equivalent
|
||
|
to the original expression.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.simplify.simplify import inversecombine
|
||
|
>>> from sympy import asin, sin, log, exp
|
||
|
>>> from sympy.abc import x
|
||
|
>>> inversecombine(asin(sin(x)))
|
||
|
x
|
||
|
>>> inversecombine(2*log(exp(3*x)))
|
||
|
6*x
|
||
|
"""
|
||
|
|
||
|
def f(rv):
|
||
|
if isinstance(rv, log):
|
||
|
if isinstance(rv.args[0], exp) or (rv.args[0].is_Pow and rv.args[0].base == S.Exp1):
|
||
|
rv = rv.args[0].exp
|
||
|
elif rv.is_Function and hasattr(rv, "inverse"):
|
||
|
if (len(rv.args) == 1 and len(rv.args[0].args) == 1 and
|
||
|
isinstance(rv.args[0], rv.inverse(argindex=1))):
|
||
|
rv = rv.args[0].args[0]
|
||
|
if rv.is_Pow and rv.base == S.Exp1:
|
||
|
if isinstance(rv.exp, log):
|
||
|
rv = rv.exp.args[0]
|
||
|
return rv
|
||
|
|
||
|
return _bottom_up(expr, f)
|
||
|
|
||
|
|
||
|
def kroneckersimp(expr):
|
||
|
"""
|
||
|
Simplify expressions with KroneckerDelta.
|
||
|
|
||
|
The only simplification currently attempted is to identify multiplicative cancellation:
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, kroneckersimp
|
||
|
>>> from sympy.abc import i
|
||
|
>>> kroneckersimp(1 + KroneckerDelta(0, i) * KroneckerDelta(1, i))
|
||
|
1
|
||
|
"""
|
||
|
def args_cancel(args1, args2):
|
||
|
for i1 in range(2):
|
||
|
for i2 in range(2):
|
||
|
a1 = args1[i1]
|
||
|
a2 = args2[i2]
|
||
|
a3 = args1[(i1 + 1) % 2]
|
||
|
a4 = args2[(i2 + 1) % 2]
|
||
|
if Eq(a1, a2) is S.true and Eq(a3, a4) is S.false:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def cancel_kronecker_mul(m):
|
||
|
args = m.args
|
||
|
deltas = [a for a in args if isinstance(a, KroneckerDelta)]
|
||
|
for delta1, delta2 in subsets(deltas, 2):
|
||
|
args1 = delta1.args
|
||
|
args2 = delta2.args
|
||
|
if args_cancel(args1, args2):
|
||
|
return S.Zero * m # In case of oo etc
|
||
|
return m
|
||
|
|
||
|
if not expr.has(KroneckerDelta):
|
||
|
return expr
|
||
|
|
||
|
if expr.has(Piecewise):
|
||
|
expr = expr.rewrite(KroneckerDelta)
|
||
|
|
||
|
newexpr = expr
|
||
|
expr = None
|
||
|
|
||
|
while newexpr != expr:
|
||
|
expr = newexpr
|
||
|
newexpr = expr.replace(lambda e: isinstance(e, Mul), cancel_kronecker_mul)
|
||
|
|
||
|
return expr
|
||
|
|
||
|
|
||
|
def besselsimp(expr):
|
||
|
"""
|
||
|
Simplify bessel-type functions.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
This routine tries to simplify bessel-type functions. Currently it only
|
||
|
works on the Bessel J and I functions, however. It works by looking at all
|
||
|
such functions in turn, and eliminating factors of "I" and "-1" (actually
|
||
|
their polar equivalents) in front of the argument. Then, functions of
|
||
|
half-integer order are rewritten using strigonometric functions and
|
||
|
functions of integer order (> 1) are rewritten using functions
|
||
|
of low order. Finally, if the expression was changed, compute
|
||
|
factorization of the result with factor().
|
||
|
|
||
|
>>> from sympy import besselj, besseli, besselsimp, polar_lift, I, S
|
||
|
>>> from sympy.abc import z, nu
|
||
|
>>> besselsimp(besselj(nu, z*polar_lift(-1)))
|
||
|
exp(I*pi*nu)*besselj(nu, z)
|
||
|
>>> besselsimp(besseli(nu, z*polar_lift(-I)))
|
||
|
exp(-I*pi*nu/2)*besselj(nu, z)
|
||
|
>>> besselsimp(besseli(S(-1)/2, z))
|
||
|
sqrt(2)*cosh(z)/(sqrt(pi)*sqrt(z))
|
||
|
>>> besselsimp(z*besseli(0, z) + z*(besseli(2, z))/2 + besseli(1, z))
|
||
|
3*z*besseli(0, z)/2
|
||
|
"""
|
||
|
# TODO
|
||
|
# - better algorithm?
|
||
|
# - simplify (cos(pi*b)*besselj(b,z) - besselj(-b,z))/sin(pi*b) ...
|
||
|
# - use contiguity relations?
|
||
|
|
||
|
def replacer(fro, to, factors):
|
||
|
factors = set(factors)
|
||
|
|
||
|
def repl(nu, z):
|
||
|
if factors.intersection(Mul.make_args(z)):
|
||
|
return to(nu, z)
|
||
|
return fro(nu, z)
|
||
|
return repl
|
||
|
|
||
|
def torewrite(fro, to):
|
||
|
def tofunc(nu, z):
|
||
|
return fro(nu, z).rewrite(to)
|
||
|
return tofunc
|
||
|
|
||
|
def tominus(fro):
|
||
|
def tofunc(nu, z):
|
||
|
return exp(I*pi*nu)*fro(nu, exp_polar(-I*pi)*z)
|
||
|
return tofunc
|
||
|
|
||
|
orig_expr = expr
|
||
|
|
||
|
ifactors = [I, exp_polar(I*pi/2), exp_polar(-I*pi/2)]
|
||
|
expr = expr.replace(
|
||
|
besselj, replacer(besselj,
|
||
|
torewrite(besselj, besseli), ifactors))
|
||
|
expr = expr.replace(
|
||
|
besseli, replacer(besseli,
|
||
|
torewrite(besseli, besselj), ifactors))
|
||
|
|
||
|
minusfactors = [-1, exp_polar(I*pi)]
|
||
|
expr = expr.replace(
|
||
|
besselj, replacer(besselj, tominus(besselj), minusfactors))
|
||
|
expr = expr.replace(
|
||
|
besseli, replacer(besseli, tominus(besseli), minusfactors))
|
||
|
|
||
|
z0 = Dummy('z')
|
||
|
|
||
|
def expander(fro):
|
||
|
def repl(nu, z):
|
||
|
if (nu % 1) == S.Half:
|
||
|
return simplify(trigsimp(unpolarify(
|
||
|
fro(nu, z0).rewrite(besselj).rewrite(jn).expand(
|
||
|
func=True)).subs(z0, z)))
|
||
|
elif nu.is_Integer and nu > 1:
|
||
|
return fro(nu, z).expand(func=True)
|
||
|
return fro(nu, z)
|
||
|
return repl
|
||
|
|
||
|
expr = expr.replace(besselj, expander(besselj))
|
||
|
expr = expr.replace(bessely, expander(bessely))
|
||
|
expr = expr.replace(besseli, expander(besseli))
|
||
|
expr = expr.replace(besselk, expander(besselk))
|
||
|
|
||
|
def _bessel_simp_recursion(expr):
|
||
|
|
||
|
def _use_recursion(bessel, expr):
|
||
|
while True:
|
||
|
bessels = expr.find(lambda x: isinstance(x, bessel))
|
||
|
try:
|
||
|
for ba in sorted(bessels, key=lambda x: re(x.args[0])):
|
||
|
a, x = ba.args
|
||
|
bap1 = bessel(a+1, x)
|
||
|
bap2 = bessel(a+2, x)
|
||
|
if expr.has(bap1) and expr.has(bap2):
|
||
|
expr = expr.subs(ba, 2*(a+1)/x*bap1 - bap2)
|
||
|
break
|
||
|
else:
|
||
|
return expr
|
||
|
except (ValueError, TypeError):
|
||
|
return expr
|
||
|
if expr.has(besselj):
|
||
|
expr = _use_recursion(besselj, expr)
|
||
|
if expr.has(bessely):
|
||
|
expr = _use_recursion(bessely, expr)
|
||
|
return expr
|
||
|
|
||
|
expr = _bessel_simp_recursion(expr)
|
||
|
if expr != orig_expr:
|
||
|
expr = expr.factor()
|
||
|
|
||
|
return expr
|
||
|
|
||
|
|
||
|
def nthroot(expr, n, max_len=4, prec=15):
|
||
|
"""
|
||
|
Compute a real nth-root of a sum of surds.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
expr : sum of surds
|
||
|
n : integer
|
||
|
max_len : maximum number of surds passed as constants to ``nsimplify``
|
||
|
|
||
|
Algorithm
|
||
|
=========
|
||
|
|
||
|
First ``nsimplify`` is used to get a candidate root; if it is not a
|
||
|
root the minimal polynomial is computed; the answer is one of its
|
||
|
roots.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.simplify.simplify import nthroot
|
||
|
>>> from sympy import sqrt
|
||
|
>>> nthroot(90 + 34*sqrt(7), 3)
|
||
|
sqrt(7) + 3
|
||
|
|
||
|
"""
|
||
|
expr = sympify(expr)
|
||
|
n = sympify(n)
|
||
|
p = expr**Rational(1, n)
|
||
|
if not n.is_integer:
|
||
|
return p
|
||
|
if not _is_sum_surds(expr):
|
||
|
return p
|
||
|
surds = []
|
||
|
coeff_muls = [x.as_coeff_Mul() for x in expr.args]
|
||
|
for x, y in coeff_muls:
|
||
|
if not x.is_rational:
|
||
|
return p
|
||
|
if y is S.One:
|
||
|
continue
|
||
|
if not (y.is_Pow and y.exp == S.Half and y.base.is_integer):
|
||
|
return p
|
||
|
surds.append(y)
|
||
|
surds.sort()
|
||
|
surds = surds[:max_len]
|
||
|
if expr < 0 and n % 2 == 1:
|
||
|
p = (-expr)**Rational(1, n)
|
||
|
a = nsimplify(p, constants=surds)
|
||
|
res = a if _mexpand(a**n) == _mexpand(-expr) else p
|
||
|
return -res
|
||
|
a = nsimplify(p, constants=surds)
|
||
|
if _mexpand(a) is not _mexpand(p) and _mexpand(a**n) == _mexpand(expr):
|
||
|
return _mexpand(a)
|
||
|
expr = _nthroot_solve(expr, n, prec)
|
||
|
if expr is None:
|
||
|
return p
|
||
|
return expr
|
||
|
|
||
|
|
||
|
def nsimplify(expr, constants=(), tolerance=None, full=False, rational=None,
|
||
|
rational_conversion='base10'):
|
||
|
"""
|
||
|
Find a simple representation for a number or, if there are free symbols or
|
||
|
if ``rational=True``, then replace Floats with their Rational equivalents. If
|
||
|
no change is made and rational is not False then Floats will at least be
|
||
|
converted to Rationals.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
For numerical expressions, a simple formula that numerically matches the
|
||
|
given numerical expression is sought (and the input should be possible
|
||
|
to evalf to a precision of at least 30 digits).
|
||
|
|
||
|
Optionally, a list of (rationally independent) constants to
|
||
|
include in the formula may be given.
|
||
|
|
||
|
A lower tolerance may be set to find less exact matches. If no tolerance
|
||
|
is given then the least precise value will set the tolerance (e.g. Floats
|
||
|
default to 15 digits of precision, so would be tolerance=10**-15).
|
||
|
|
||
|
With ``full=True``, a more extensive search is performed
|
||
|
(this is useful to find simpler numbers when the tolerance
|
||
|
is set low).
|
||
|
|
||
|
When converting to rational, if rational_conversion='base10' (the default), then
|
||
|
convert floats to rationals using their base-10 (string) representation.
|
||
|
When rational_conversion='exact' it uses the exact, base-2 representation.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import nsimplify, sqrt, GoldenRatio, exp, I, pi
|
||
|
>>> nsimplify(4/(1+sqrt(5)), [GoldenRatio])
|
||
|
-2 + 2*GoldenRatio
|
||
|
>>> nsimplify((1/(exp(3*pi*I/5)+1)))
|
||
|
1/2 - I*sqrt(sqrt(5)/10 + 1/4)
|
||
|
>>> nsimplify(I**I, [pi])
|
||
|
exp(-pi/2)
|
||
|
>>> nsimplify(pi, tolerance=0.01)
|
||
|
22/7
|
||
|
|
||
|
>>> nsimplify(0.333333333333333, rational=True, rational_conversion='exact')
|
||
|
6004799503160655/18014398509481984
|
||
|
>>> nsimplify(0.333333333333333, rational=True)
|
||
|
1/3
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.core.function.nfloat
|
||
|
|
||
|
"""
|
||
|
try:
|
||
|
return sympify(as_int(expr))
|
||
|
except (TypeError, ValueError):
|
||
|
pass
|
||
|
expr = sympify(expr).xreplace({
|
||
|
Float('inf'): S.Infinity,
|
||
|
Float('-inf'): S.NegativeInfinity,
|
||
|
})
|
||
|
if expr is S.Infinity or expr is S.NegativeInfinity:
|
||
|
return expr
|
||
|
if rational or expr.free_symbols:
|
||
|
return _real_to_rational(expr, tolerance, rational_conversion)
|
||
|
|
||
|
# SymPy's default tolerance for Rationals is 15; other numbers may have
|
||
|
# lower tolerances set, so use them to pick the largest tolerance if None
|
||
|
# was given
|
||
|
if tolerance is None:
|
||
|
tolerance = 10**-min([15] +
|
||
|
[mpmath.libmp.libmpf.prec_to_dps(n._prec)
|
||
|
for n in expr.atoms(Float)])
|
||
|
# XXX should prec be set independent of tolerance or should it be computed
|
||
|
# from tolerance?
|
||
|
prec = 30
|
||
|
bprec = int(prec*3.33)
|
||
|
|
||
|
constants_dict = {}
|
||
|
for constant in constants:
|
||
|
constant = sympify(constant)
|
||
|
v = constant.evalf(prec)
|
||
|
if not v.is_Float:
|
||
|
raise ValueError("constants must be real-valued")
|
||
|
constants_dict[str(constant)] = v._to_mpmath(bprec)
|
||
|
|
||
|
exprval = expr.evalf(prec, chop=True)
|
||
|
re, im = exprval.as_real_imag()
|
||
|
|
||
|
# safety check to make sure that this evaluated to a number
|
||
|
if not (re.is_Number and im.is_Number):
|
||
|
return expr
|
||
|
|
||
|
def nsimplify_real(x):
|
||
|
orig = mpmath.mp.dps
|
||
|
xv = x._to_mpmath(bprec)
|
||
|
try:
|
||
|
# We'll be happy with low precision if a simple fraction
|
||
|
if not (tolerance or full):
|
||
|
mpmath.mp.dps = 15
|
||
|
rat = mpmath.pslq([xv, 1])
|
||
|
if rat is not None:
|
||
|
return Rational(-int(rat[1]), int(rat[0]))
|
||
|
mpmath.mp.dps = prec
|
||
|
newexpr = mpmath.identify(xv, constants=constants_dict,
|
||
|
tol=tolerance, full=full)
|
||
|
if not newexpr:
|
||
|
raise ValueError
|
||
|
if full:
|
||
|
newexpr = newexpr[0]
|
||
|
expr = sympify(newexpr)
|
||
|
if x and not expr: # don't let x become 0
|
||
|
raise ValueError
|
||
|
if expr.is_finite is False and xv not in [mpmath.inf, mpmath.ninf]:
|
||
|
raise ValueError
|
||
|
return expr
|
||
|
finally:
|
||
|
# even though there are returns above, this is executed
|
||
|
# before leaving
|
||
|
mpmath.mp.dps = orig
|
||
|
try:
|
||
|
if re:
|
||
|
re = nsimplify_real(re)
|
||
|
if im:
|
||
|
im = nsimplify_real(im)
|
||
|
except ValueError:
|
||
|
if rational is None:
|
||
|
return _real_to_rational(expr, rational_conversion=rational_conversion)
|
||
|
return expr
|
||
|
|
||
|
rv = re + im*S.ImaginaryUnit
|
||
|
# if there was a change or rational is explicitly not wanted
|
||
|
# return the value, else return the Rational representation
|
||
|
if rv != expr or rational is False:
|
||
|
return rv
|
||
|
return _real_to_rational(expr, rational_conversion=rational_conversion)
|
||
|
|
||
|
|
||
|
def _real_to_rational(expr, tolerance=None, rational_conversion='base10'):
|
||
|
"""
|
||
|
Replace all reals in expr with rationals.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.simplify.simplify import _real_to_rational
|
||
|
>>> from sympy.abc import x
|
||
|
|
||
|
>>> _real_to_rational(.76 + .1*x**.5)
|
||
|
sqrt(x)/10 + 19/25
|
||
|
|
||
|
If rational_conversion='base10', this uses the base-10 string. If
|
||
|
rational_conversion='exact', the exact, base-2 representation is used.
|
||
|
|
||
|
>>> _real_to_rational(0.333333333333333, rational_conversion='exact')
|
||
|
6004799503160655/18014398509481984
|
||
|
>>> _real_to_rational(0.333333333333333)
|
||
|
1/3
|
||
|
|
||
|
"""
|
||
|
expr = _sympify(expr)
|
||
|
inf = Float('inf')
|
||
|
p = expr
|
||
|
reps = {}
|
||
|
reduce_num = None
|
||
|
if tolerance is not None and tolerance < 1:
|
||
|
reduce_num = ceiling(1/tolerance)
|
||
|
for fl in p.atoms(Float):
|
||
|
key = fl
|
||
|
if reduce_num is not None:
|
||
|
r = Rational(fl).limit_denominator(reduce_num)
|
||
|
elif (tolerance is not None and tolerance >= 1 and
|
||
|
fl.is_Integer is False):
|
||
|
r = Rational(tolerance*round(fl/tolerance)
|
||
|
).limit_denominator(int(tolerance))
|
||
|
else:
|
||
|
if rational_conversion == 'exact':
|
||
|
r = Rational(fl)
|
||
|
reps[key] = r
|
||
|
continue
|
||
|
elif rational_conversion != 'base10':
|
||
|
raise ValueError("rational_conversion must be 'base10' or 'exact'")
|
||
|
|
||
|
r = nsimplify(fl, rational=False)
|
||
|
# e.g. log(3).n() -> log(3) instead of a Rational
|
||
|
if fl and not r:
|
||
|
r = Rational(fl)
|
||
|
elif not r.is_Rational:
|
||
|
if fl in (inf, -inf):
|
||
|
r = S.ComplexInfinity
|
||
|
elif fl < 0:
|
||
|
fl = -fl
|
||
|
d = Pow(10, int(mpmath.log(fl)/mpmath.log(10)))
|
||
|
r = -Rational(str(fl/d))*d
|
||
|
elif fl > 0:
|
||
|
d = Pow(10, int(mpmath.log(fl)/mpmath.log(10)))
|
||
|
r = Rational(str(fl/d))*d
|
||
|
else:
|
||
|
r = S.Zero
|
||
|
reps[key] = r
|
||
|
return p.subs(reps, simultaneous=True)
|
||
|
|
||
|
|
||
|
def clear_coefficients(expr, rhs=S.Zero):
|
||
|
"""Return `p, r` where `p` is the expression obtained when Rational
|
||
|
additive and multiplicative coefficients of `expr` have been stripped
|
||
|
away in a naive fashion (i.e. without simplification). The operations
|
||
|
needed to remove the coefficients will be applied to `rhs` and returned
|
||
|
as `r`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.simplify.simplify import clear_coefficients
|
||
|
>>> from sympy.abc import x, y
|
||
|
>>> from sympy import Dummy
|
||
|
>>> expr = 4*y*(6*x + 3)
|
||
|
>>> clear_coefficients(expr - 2)
|
||
|
(y*(2*x + 1), 1/6)
|
||
|
|
||
|
When solving 2 or more expressions like `expr = a`,
|
||
|
`expr = b`, etc..., it is advantageous to provide a Dummy symbol
|
||
|
for `rhs` and simply replace it with `a`, `b`, etc... in `r`.
|
||
|
|
||
|
>>> rhs = Dummy('rhs')
|
||
|
>>> clear_coefficients(expr, rhs)
|
||
|
(y*(2*x + 1), _rhs/12)
|
||
|
>>> _[1].subs(rhs, 2)
|
||
|
1/6
|
||
|
"""
|
||
|
was = None
|
||
|
free = expr.free_symbols
|
||
|
if expr.is_Rational:
|
||
|
return (S.Zero, rhs - expr)
|
||
|
while expr and was != expr:
|
||
|
was = expr
|
||
|
m, expr = (
|
||
|
expr.as_content_primitive()
|
||
|
if free else
|
||
|
factor_terms(expr).as_coeff_Mul(rational=True))
|
||
|
rhs /= m
|
||
|
c, expr = expr.as_coeff_Add(rational=True)
|
||
|
rhs -= c
|
||
|
expr = signsimp(expr, evaluate = False)
|
||
|
if expr.could_extract_minus_sign():
|
||
|
expr = -expr
|
||
|
rhs = -rhs
|
||
|
return expr, rhs
|
||
|
|
||
|
def nc_simplify(expr, deep=True):
|
||
|
'''
|
||
|
Simplify a non-commutative expression composed of multiplication
|
||
|
and raising to a power by grouping repeated subterms into one power.
|
||
|
Priority is given to simplifications that give the fewest number
|
||
|
of arguments in the end (for example, in a*b*a*b*c*a*b*c simplifying
|
||
|
to (a*b)**2*c*a*b*c gives 5 arguments while a*b*(a*b*c)**2 has 3).
|
||
|
If ``expr`` is a sum of such terms, the sum of the simplified terms
|
||
|
is returned.
|
||
|
|
||
|
Keyword argument ``deep`` controls whether or not subexpressions
|
||
|
nested deeper inside the main expression are simplified. See examples
|
||
|
below. Setting `deep` to `False` can save time on nested expressions
|
||
|
that do not need simplifying on all levels.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import symbols
|
||
|
>>> from sympy.simplify.simplify import nc_simplify
|
||
|
>>> a, b, c = symbols("a b c", commutative=False)
|
||
|
>>> nc_simplify(a*b*a*b*c*a*b*c)
|
||
|
a*b*(a*b*c)**2
|
||
|
>>> expr = a**2*b*a**4*b*a**4
|
||
|
>>> nc_simplify(expr)
|
||
|
a**2*(b*a**4)**2
|
||
|
>>> nc_simplify(a*b*a*b*c**2*(a*b)**2*c**2)
|
||
|
((a*b)**2*c**2)**2
|
||
|
>>> nc_simplify(a*b*a*b + 2*a*c*a**2*c*a**2*c*a)
|
||
|
(a*b)**2 + 2*(a*c*a)**3
|
||
|
>>> nc_simplify(b**-1*a**-1*(a*b)**2)
|
||
|
a*b
|
||
|
>>> nc_simplify(a**-1*b**-1*c*a)
|
||
|
(b*a)**(-1)*c*a
|
||
|
>>> expr = (a*b*a*b)**2*a*c*a*c
|
||
|
>>> nc_simplify(expr)
|
||
|
(a*b)**4*(a*c)**2
|
||
|
>>> nc_simplify(expr, deep=False)
|
||
|
(a*b*a*b)**2*(a*c)**2
|
||
|
|
||
|
'''
|
||
|
if isinstance(expr, MatrixExpr):
|
||
|
expr = expr.doit(inv_expand=False)
|
||
|
_Add, _Mul, _Pow, _Symbol = MatAdd, MatMul, MatPow, MatrixSymbol
|
||
|
else:
|
||
|
_Add, _Mul, _Pow, _Symbol = Add, Mul, Pow, Symbol
|
||
|
|
||
|
# =========== Auxiliary functions ========================
|
||
|
def _overlaps(args):
|
||
|
# Calculate a list of lists m such that m[i][j] contains the lengths
|
||
|
# of all possible overlaps between args[:i+1] and args[i+1+j:].
|
||
|
# An overlap is a suffix of the prefix that matches a prefix
|
||
|
# of the suffix.
|
||
|
# For example, let expr=c*a*b*a*b*a*b*a*b. Then m[3][0] contains
|
||
|
# the lengths of overlaps of c*a*b*a*b with a*b*a*b. The overlaps
|
||
|
# are a*b*a*b, a*b and the empty word so that m[3][0]=[4,2,0].
|
||
|
# All overlaps rather than only the longest one are recorded
|
||
|
# because this information helps calculate other overlap lengths.
|
||
|
m = [[([1, 0] if a == args[0] else [0]) for a in args[1:]]]
|
||
|
for i in range(1, len(args)):
|
||
|
overlaps = []
|
||
|
j = 0
|
||
|
for j in range(len(args) - i - 1):
|
||
|
overlap = []
|
||
|
for v in m[i-1][j+1]:
|
||
|
if j + i + 1 + v < len(args) and args[i] == args[j+i+1+v]:
|
||
|
overlap.append(v + 1)
|
||
|
overlap += [0]
|
||
|
overlaps.append(overlap)
|
||
|
m.append(overlaps)
|
||
|
return m
|
||
|
|
||
|
def _reduce_inverses(_args):
|
||
|
# replace consecutive negative powers by an inverse
|
||
|
# of a product of positive powers, e.g. a**-1*b**-1*c
|
||
|
# will simplify to (a*b)**-1*c;
|
||
|
# return that new args list and the number of negative
|
||
|
# powers in it (inv_tot)
|
||
|
inv_tot = 0 # total number of inverses
|
||
|
inverses = []
|
||
|
args = []
|
||
|
for arg in _args:
|
||
|
if isinstance(arg, _Pow) and arg.args[1].is_extended_negative:
|
||
|
inverses = [arg**-1] + inverses
|
||
|
inv_tot += 1
|
||
|
else:
|
||
|
if len(inverses) == 1:
|
||
|
args.append(inverses[0]**-1)
|
||
|
elif len(inverses) > 1:
|
||
|
args.append(_Pow(_Mul(*inverses), -1))
|
||
|
inv_tot -= len(inverses) - 1
|
||
|
inverses = []
|
||
|
args.append(arg)
|
||
|
if inverses:
|
||
|
args.append(_Pow(_Mul(*inverses), -1))
|
||
|
inv_tot -= len(inverses) - 1
|
||
|
return inv_tot, tuple(args)
|
||
|
|
||
|
def get_score(s):
|
||
|
# compute the number of arguments of s
|
||
|
# (including in nested expressions) overall
|
||
|
# but ignore exponents
|
||
|
if isinstance(s, _Pow):
|
||
|
return get_score(s.args[0])
|
||
|
elif isinstance(s, (_Add, _Mul)):
|
||
|
return sum([get_score(a) for a in s.args])
|
||
|
return 1
|
||
|
|
||
|
def compare(s, alt_s):
|
||
|
# compare two possible simplifications and return a
|
||
|
# "better" one
|
||
|
if s != alt_s and get_score(alt_s) < get_score(s):
|
||
|
return alt_s
|
||
|
return s
|
||
|
# ========================================================
|
||
|
|
||
|
if not isinstance(expr, (_Add, _Mul, _Pow)) or expr.is_commutative:
|
||
|
return expr
|
||
|
args = expr.args[:]
|
||
|
if isinstance(expr, _Pow):
|
||
|
if deep:
|
||
|
return _Pow(nc_simplify(args[0]), args[1]).doit()
|
||
|
else:
|
||
|
return expr
|
||
|
elif isinstance(expr, _Add):
|
||
|
return _Add(*[nc_simplify(a, deep=deep) for a in args]).doit()
|
||
|
else:
|
||
|
# get the non-commutative part
|
||
|
c_args, args = expr.args_cnc()
|
||
|
com_coeff = Mul(*c_args)
|
||
|
if com_coeff != 1:
|
||
|
return com_coeff*nc_simplify(expr/com_coeff, deep=deep)
|
||
|
|
||
|
inv_tot, args = _reduce_inverses(args)
|
||
|
# if most arguments are negative, work with the inverse
|
||
|
# of the expression, e.g. a**-1*b*a**-1*c**-1 will become
|
||
|
# (c*a*b**-1*a)**-1 at the end so can work with c*a*b**-1*a
|
||
|
invert = False
|
||
|
if inv_tot > len(args)/2:
|
||
|
invert = True
|
||
|
args = [a**-1 for a in args[::-1]]
|
||
|
|
||
|
if deep:
|
||
|
args = tuple(nc_simplify(a) for a in args)
|
||
|
|
||
|
m = _overlaps(args)
|
||
|
|
||
|
# simps will be {subterm: end} where `end` is the ending
|
||
|
# index of a sequence of repetitions of subterm;
|
||
|
# this is for not wasting time with subterms that are part
|
||
|
# of longer, already considered sequences
|
||
|
simps = {}
|
||
|
|
||
|
post = 1
|
||
|
pre = 1
|
||
|
|
||
|
# the simplification coefficient is the number of
|
||
|
# arguments by which contracting a given sequence
|
||
|
# would reduce the word; e.g. in a*b*a*b*c*a*b*c,
|
||
|
# contracting a*b*a*b to (a*b)**2 removes 3 arguments
|
||
|
# while a*b*c*a*b*c to (a*b*c)**2 removes 6. It's
|
||
|
# better to contract the latter so simplification
|
||
|
# with a maximum simplification coefficient will be chosen
|
||
|
max_simp_coeff = 0
|
||
|
simp = None # information about future simplification
|
||
|
|
||
|
for i in range(1, len(args)):
|
||
|
simp_coeff = 0
|
||
|
l = 0 # length of a subterm
|
||
|
p = 0 # the power of a subterm
|
||
|
if i < len(args) - 1:
|
||
|
rep = m[i][0]
|
||
|
start = i # starting index of the repeated sequence
|
||
|
end = i+1 # ending index of the repeated sequence
|
||
|
if i == len(args)-1 or rep == [0]:
|
||
|
# no subterm is repeated at this stage, at least as
|
||
|
# far as the arguments are concerned - there may be
|
||
|
# a repetition if powers are taken into account
|
||
|
if (isinstance(args[i], _Pow) and
|
||
|
not isinstance(args[i].args[0], _Symbol)):
|
||
|
subterm = args[i].args[0].args
|
||
|
l = len(subterm)
|
||
|
if args[i-l:i] == subterm:
|
||
|
# e.g. a*b in a*b*(a*b)**2 is not repeated
|
||
|
# in args (= [a, b, (a*b)**2]) but it
|
||
|
# can be matched here
|
||
|
p += 1
|
||
|
start -= l
|
||
|
if args[i+1:i+1+l] == subterm:
|
||
|
# e.g. a*b in (a*b)**2*a*b
|
||
|
p += 1
|
||
|
end += l
|
||
|
if p:
|
||
|
p += args[i].args[1]
|
||
|
else:
|
||
|
continue
|
||
|
else:
|
||
|
l = rep[0] # length of the longest repeated subterm at this point
|
||
|
start -= l - 1
|
||
|
subterm = args[start:end]
|
||
|
p = 2
|
||
|
end += l
|
||
|
|
||
|
if subterm in simps and simps[subterm] >= start:
|
||
|
# the subterm is part of a sequence that
|
||
|
# has already been considered
|
||
|
continue
|
||
|
|
||
|
# count how many times it's repeated
|
||
|
while end < len(args):
|
||
|
if l in m[end-1][0]:
|
||
|
p += 1
|
||
|
end += l
|
||
|
elif isinstance(args[end], _Pow) and args[end].args[0].args == subterm:
|
||
|
# for cases like a*b*a*b*(a*b)**2*a*b
|
||
|
p += args[end].args[1]
|
||
|
end += 1
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
# see if another match can be made, e.g.
|
||
|
# for b*a**2 in b*a**2*b*a**3 or a*b in
|
||
|
# a**2*b*a*b
|
||
|
|
||
|
pre_exp = 0
|
||
|
pre_arg = 1
|
||
|
if start - l >= 0 and args[start-l+1:start] == subterm[1:]:
|
||
|
if isinstance(subterm[0], _Pow):
|
||
|
pre_arg = subterm[0].args[0]
|
||
|
exp = subterm[0].args[1]
|
||
|
else:
|
||
|
pre_arg = subterm[0]
|
||
|
exp = 1
|
||
|
if isinstance(args[start-l], _Pow) and args[start-l].args[0] == pre_arg:
|
||
|
pre_exp = args[start-l].args[1] - exp
|
||
|
start -= l
|
||
|
p += 1
|
||
|
elif args[start-l] == pre_arg:
|
||
|
pre_exp = 1 - exp
|
||
|
start -= l
|
||
|
p += 1
|
||
|
|
||
|
post_exp = 0
|
||
|
post_arg = 1
|
||
|
if end + l - 1 < len(args) and args[end:end+l-1] == subterm[:-1]:
|
||
|
if isinstance(subterm[-1], _Pow):
|
||
|
post_arg = subterm[-1].args[0]
|
||
|
exp = subterm[-1].args[1]
|
||
|
else:
|
||
|
post_arg = subterm[-1]
|
||
|
exp = 1
|
||
|
if isinstance(args[end+l-1], _Pow) and args[end+l-1].args[0] == post_arg:
|
||
|
post_exp = args[end+l-1].args[1] - exp
|
||
|
end += l
|
||
|
p += 1
|
||
|
elif args[end+l-1] == post_arg:
|
||
|
post_exp = 1 - exp
|
||
|
end += l
|
||
|
p += 1
|
||
|
|
||
|
# Consider a*b*a**2*b*a**2*b*a:
|
||
|
# b*a**2 is explicitly repeated, but note
|
||
|
# that in this case a*b*a is also repeated
|
||
|
# so there are two possible simplifications:
|
||
|
# a*(b*a**2)**3*a**-1 or (a*b*a)**3
|
||
|
# The latter is obviously simpler.
|
||
|
# But in a*b*a**2*b**2*a**2 the simplifications are
|
||
|
# a*(b*a**2)**2 and (a*b*a)**3*a in which case
|
||
|
# it's better to stick with the shorter subterm
|
||
|
if post_exp and exp % 2 == 0 and start > 0:
|
||
|
exp = exp/2
|
||
|
_pre_exp = 1
|
||
|
_post_exp = 1
|
||
|
if isinstance(args[start-1], _Pow) and args[start-1].args[0] == post_arg:
|
||
|
_post_exp = post_exp + exp
|
||
|
_pre_exp = args[start-1].args[1] - exp
|
||
|
elif args[start-1] == post_arg:
|
||
|
_post_exp = post_exp + exp
|
||
|
_pre_exp = 1 - exp
|
||
|
if _pre_exp == 0 or _post_exp == 0:
|
||
|
if not pre_exp:
|
||
|
start -= 1
|
||
|
post_exp = _post_exp
|
||
|
pre_exp = _pre_exp
|
||
|
pre_arg = post_arg
|
||
|
subterm = (post_arg**exp,) + subterm[:-1] + (post_arg**exp,)
|
||
|
|
||
|
simp_coeff += end-start
|
||
|
|
||
|
if post_exp:
|
||
|
simp_coeff -= 1
|
||
|
if pre_exp:
|
||
|
simp_coeff -= 1
|
||
|
|
||
|
simps[subterm] = end
|
||
|
|
||
|
if simp_coeff > max_simp_coeff:
|
||
|
max_simp_coeff = simp_coeff
|
||
|
simp = (start, _Mul(*subterm), p, end, l)
|
||
|
pre = pre_arg**pre_exp
|
||
|
post = post_arg**post_exp
|
||
|
|
||
|
if simp:
|
||
|
subterm = _Pow(nc_simplify(simp[1], deep=deep), simp[2])
|
||
|
pre = nc_simplify(_Mul(*args[:simp[0]])*pre, deep=deep)
|
||
|
post = post*nc_simplify(_Mul(*args[simp[3]:]), deep=deep)
|
||
|
simp = pre*subterm*post
|
||
|
if pre != 1 or post != 1:
|
||
|
# new simplifications may be possible but no need
|
||
|
# to recurse over arguments
|
||
|
simp = nc_simplify(simp, deep=False)
|
||
|
else:
|
||
|
simp = _Mul(*args)
|
||
|
|
||
|
if invert:
|
||
|
simp = _Pow(simp, -1)
|
||
|
|
||
|
# see if factor_nc(expr) is simplified better
|
||
|
if not isinstance(expr, MatrixExpr):
|
||
|
f_expr = factor_nc(expr)
|
||
|
if f_expr != expr:
|
||
|
alt_simp = nc_simplify(f_expr, deep=deep)
|
||
|
simp = compare(simp, alt_simp)
|
||
|
else:
|
||
|
simp = simp.doit(inv_expand=False)
|
||
|
return simp
|
||
|
|
||
|
|
||
|
def dotprodsimp(expr, withsimp=False):
|
||
|
"""Simplification for a sum of products targeted at the kind of blowup that
|
||
|
occurs during summation of products. Intended to reduce expression blowup
|
||
|
during matrix multiplication or other similar operations. Only works with
|
||
|
algebraic expressions and does not recurse into non.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
withsimp : bool, optional
|
||
|
Specifies whether a flag should be returned along with the expression
|
||
|
to indicate roughly whether simplification was successful. It is used
|
||
|
in ``MatrixArithmetic._eval_pow_by_recursion`` to avoid attempting to
|
||
|
simplify an expression repetitively which does not simplify.
|
||
|
"""
|
||
|
|
||
|
def count_ops_alg(expr):
|
||
|
"""Optimized count algebraic operations with no recursion into
|
||
|
non-algebraic args that ``core.function.count_ops`` does. Also returns
|
||
|
whether rational functions may be present according to negative
|
||
|
exponents of powers or non-number fractions.
|
||
|
|
||
|
Returns
|
||
|
=======
|
||
|
|
||
|
ops, ratfunc : int, bool
|
||
|
``ops`` is the number of algebraic operations starting at the top
|
||
|
level expression (not recursing into non-alg children). ``ratfunc``
|
||
|
specifies whether the expression MAY contain rational functions
|
||
|
which ``cancel`` MIGHT optimize.
|
||
|
"""
|
||
|
|
||
|
ops = 0
|
||
|
args = [expr]
|
||
|
ratfunc = False
|
||
|
|
||
|
while args:
|
||
|
a = args.pop()
|
||
|
|
||
|
if not isinstance(a, Basic):
|
||
|
continue
|
||
|
|
||
|
if a.is_Rational:
|
||
|
if a is not S.One: # -1/3 = NEG + DIV
|
||
|
ops += bool (a.p < 0) + bool (a.q != 1)
|
||
|
|
||
|
elif a.is_Mul:
|
||
|
if a.could_extract_minus_sign():
|
||
|
ops += 1
|
||
|
if a.args[0] is S.NegativeOne:
|
||
|
a = a.as_two_terms()[1]
|
||
|
else:
|
||
|
a = -a
|
||
|
|
||
|
n, d = fraction(a)
|
||
|
|
||
|
if n.is_Integer:
|
||
|
ops += 1 + bool (n < 0)
|
||
|
args.append(d) # won't be -Mul but could be Add
|
||
|
|
||
|
elif d is not S.One:
|
||
|
if not d.is_Integer:
|
||
|
args.append(d)
|
||
|
ratfunc=True
|
||
|
|
||
|
ops += 1
|
||
|
args.append(n) # could be -Mul
|
||
|
|
||
|
else:
|
||
|
ops += len(a.args) - 1
|
||
|
args.extend(a.args)
|
||
|
|
||
|
elif a.is_Add:
|
||
|
laargs = len(a.args)
|
||
|
negs = 0
|
||
|
|
||
|
for ai in a.args:
|
||
|
if ai.could_extract_minus_sign():
|
||
|
negs += 1
|
||
|
ai = -ai
|
||
|
args.append(ai)
|
||
|
|
||
|
ops += laargs - (negs != laargs) # -x - y = NEG + SUB
|
||
|
|
||
|
elif a.is_Pow:
|
||
|
ops += 1
|
||
|
args.append(a.base)
|
||
|
|
||
|
if not ratfunc:
|
||
|
ratfunc = a.exp.is_negative is not False
|
||
|
|
||
|
return ops, ratfunc
|
||
|
|
||
|
def nonalg_subs_dummies(expr, dummies):
|
||
|
"""Substitute dummy variables for non-algebraic expressions to avoid
|
||
|
evaluation of non-algebraic terms that ``polys.polytools.cancel`` does.
|
||
|
"""
|
||
|
|
||
|
if not expr.args:
|
||
|
return expr
|
||
|
|
||
|
if expr.is_Add or expr.is_Mul or expr.is_Pow:
|
||
|
args = None
|
||
|
|
||
|
for i, a in enumerate(expr.args):
|
||
|
c = nonalg_subs_dummies(a, dummies)
|
||
|
|
||
|
if c is a:
|
||
|
continue
|
||
|
|
||
|
if args is None:
|
||
|
args = list(expr.args)
|
||
|
|
||
|
args[i] = c
|
||
|
|
||
|
if args is None:
|
||
|
return expr
|
||
|
|
||
|
return expr.func(*args)
|
||
|
|
||
|
return dummies.setdefault(expr, Dummy())
|
||
|
|
||
|
simplified = False # doesn't really mean simplified, rather "can simplify again"
|
||
|
|
||
|
if isinstance(expr, Basic) and (expr.is_Add or expr.is_Mul or expr.is_Pow):
|
||
|
expr2 = expr.expand(deep=True, modulus=None, power_base=False,
|
||
|
power_exp=False, mul=True, log=False, multinomial=True, basic=False)
|
||
|
|
||
|
if expr2 != expr:
|
||
|
expr = expr2
|
||
|
simplified = True
|
||
|
|
||
|
exprops, ratfunc = count_ops_alg(expr)
|
||
|
|
||
|
if exprops >= 6: # empirically tested cutoff for expensive simplification
|
||
|
if ratfunc:
|
||
|
dummies = {}
|
||
|
expr2 = nonalg_subs_dummies(expr, dummies)
|
||
|
|
||
|
if expr2 is expr or count_ops_alg(expr2)[0] >= 6: # check again after substitution
|
||
|
expr3 = cancel(expr2)
|
||
|
|
||
|
if expr3 != expr2:
|
||
|
expr = expr3.subs([(d, e) for e, d in dummies.items()])
|
||
|
simplified = True
|
||
|
|
||
|
# very special case: x/(x-1) - 1/(x-1) -> 1
|
||
|
elif (exprops == 5 and expr.is_Add and expr.args [0].is_Mul and
|
||
|
expr.args [1].is_Mul and expr.args [0].args [-1].is_Pow and
|
||
|
expr.args [1].args [-1].is_Pow and
|
||
|
expr.args [0].args [-1].exp is S.NegativeOne and
|
||
|
expr.args [1].args [-1].exp is S.NegativeOne):
|
||
|
|
||
|
expr2 = together (expr)
|
||
|
expr2ops = count_ops_alg(expr2)[0]
|
||
|
|
||
|
if expr2ops < exprops:
|
||
|
expr = expr2
|
||
|
simplified = True
|
||
|
|
||
|
else:
|
||
|
simplified = True
|
||
|
|
||
|
return (expr, simplified) if withsimp else expr
|
||
|
|
||
|
|
||
|
bottom_up = deprecated(
|
||
|
"""
|
||
|
Using bottom_up from the sympy.simplify.simplify submodule is
|
||
|
deprecated.
|
||
|
|
||
|
Instead, use bottom_up from the top-level sympy namespace, like
|
||
|
|
||
|
sympy.bottom_up
|
||
|
""",
|
||
|
deprecated_since_version="1.10",
|
||
|
active_deprecations_target="deprecated-traversal-functions-moved",
|
||
|
)(_bottom_up)
|
||
|
|
||
|
|
||
|
# XXX: This function really should either be private API or exported in the
|
||
|
# top-level sympy/__init__.py
|
||
|
walk = deprecated(
|
||
|
"""
|
||
|
Using walk from the sympy.simplify.simplify submodule is
|
||
|
deprecated.
|
||
|
|
||
|
Instead, use walk from sympy.core.traversal.walk
|
||
|
""",
|
||
|
deprecated_since_version="1.10",
|
||
|
active_deprecations_target="deprecated-traversal-functions-moved",
|
||
|
)(_walk)
|