1253 lines
46 KiB
Python
1253 lines
46 KiB
Python
from collections import defaultdict
|
|
from functools import reduce
|
|
|
|
from sympy.core import (sympify, Basic, S, Expr, factor_terms,
|
|
Mul, Add, bottom_up)
|
|
from sympy.core.cache import cacheit
|
|
from sympy.core.function import (count_ops, _mexpand, FunctionClass, expand,
|
|
expand_mul, _coeff_isneg, Derivative)
|
|
from sympy.core.numbers import I, Integer, igcd
|
|
from sympy.core.sorting import _nodes
|
|
from sympy.core.symbol import Dummy, symbols, Wild
|
|
from sympy.external.gmpy import SYMPY_INTS
|
|
from sympy.functions import sin, cos, exp, cosh, tanh, sinh, tan, cot, coth
|
|
from sympy.functions import atan2
|
|
from sympy.functions.elementary.hyperbolic import HyperbolicFunction
|
|
from sympy.functions.elementary.trigonometric import TrigonometricFunction
|
|
from sympy.polys import Poly, factor, cancel, parallel_poly_from_expr
|
|
from sympy.polys.domains import ZZ
|
|
from sympy.polys.polyerrors import PolificationFailed
|
|
from sympy.polys.polytools import groebner
|
|
from sympy.simplify.cse_main import cse
|
|
from sympy.strategies.core import identity
|
|
from sympy.strategies.tree import greedy
|
|
from sympy.utilities.iterables import iterable
|
|
from sympy.utilities.misc import debug
|
|
|
|
def trigsimp_groebner(expr, hints=[], quick=False, order="grlex",
|
|
polynomial=False):
|
|
"""
|
|
Simplify trigonometric expressions using a groebner basis algorithm.
|
|
|
|
Explanation
|
|
===========
|
|
|
|
This routine takes a fraction involving trigonometric or hyperbolic
|
|
expressions, and tries to simplify it. The primary metric is the
|
|
total degree. Some attempts are made to choose the simplest possible
|
|
expression of the minimal degree, but this is non-rigorous, and also
|
|
very slow (see the ``quick=True`` option).
|
|
|
|
If ``polynomial`` is set to True, instead of simplifying numerator and
|
|
denominator together, this function just brings numerator and denominator
|
|
into a canonical form. This is much faster, but has potentially worse
|
|
results. However, if the input is a polynomial, then the result is
|
|
guaranteed to be an equivalent polynomial of minimal degree.
|
|
|
|
The most important option is hints. Its entries can be any of the
|
|
following:
|
|
|
|
- a natural number
|
|
- a function
|
|
- an iterable of the form (func, var1, var2, ...)
|
|
- anything else, interpreted as a generator
|
|
|
|
A number is used to indicate that the search space should be increased.
|
|
A function is used to indicate that said function is likely to occur in a
|
|
simplified expression.
|
|
An iterable is used indicate that func(var1 + var2 + ...) is likely to
|
|
occur in a simplified .
|
|
An additional generator also indicates that it is likely to occur.
|
|
(See examples below).
|
|
|
|
This routine carries out various computationally intensive algorithms.
|
|
The option ``quick=True`` can be used to suppress one particularly slow
|
|
step (at the expense of potentially more complicated results, but never at
|
|
the expense of increased total degree).
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.abc import x, y
|
|
>>> from sympy import sin, tan, cos, sinh, cosh, tanh
|
|
>>> from sympy.simplify.trigsimp import trigsimp_groebner
|
|
|
|
Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens:
|
|
|
|
>>> ex = sin(x)*cos(x)
|
|
>>> trigsimp_groebner(ex)
|
|
sin(x)*cos(x)
|
|
|
|
This is because ``trigsimp_groebner`` only looks for a simplification
|
|
involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try
|
|
``2*x`` by passing ``hints=[2]``:
|
|
|
|
>>> trigsimp_groebner(ex, hints=[2])
|
|
sin(2*x)/2
|
|
>>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2])
|
|
-cos(2*x)
|
|
|
|
Increasing the search space this way can quickly become expensive. A much
|
|
faster way is to give a specific expression that is likely to occur:
|
|
|
|
>>> trigsimp_groebner(ex, hints=[sin(2*x)])
|
|
sin(2*x)/2
|
|
|
|
Hyperbolic expressions are similarly supported:
|
|
|
|
>>> trigsimp_groebner(sinh(2*x)/sinh(x))
|
|
2*cosh(x)
|
|
|
|
Note how no hints had to be passed, since the expression already involved
|
|
``2*x``.
|
|
|
|
The tangent function is also supported. You can either pass ``tan`` in the
|
|
hints, to indicate that tan should be tried whenever cosine or sine are,
|
|
or you can pass a specific generator:
|
|
|
|
>>> trigsimp_groebner(sin(x)/cos(x), hints=[tan])
|
|
tan(x)
|
|
>>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)])
|
|
tanh(x)
|
|
|
|
Finally, you can use the iterable form to suggest that angle sum formulae
|
|
should be tried:
|
|
|
|
>>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y))
|
|
>>> trigsimp_groebner(ex, hints=[(tan, x, y)])
|
|
tan(x + y)
|
|
"""
|
|
# TODO
|
|
# - preprocess by replacing everything by funcs we can handle
|
|
# - optionally use cot instead of tan
|
|
# - more intelligent hinting.
|
|
# For example, if the ideal is small, and we have sin(x), sin(y),
|
|
# add sin(x + y) automatically... ?
|
|
# - algebraic numbers ...
|
|
# - expressions of lowest degree are not distinguished properly
|
|
# e.g. 1 - sin(x)**2
|
|
# - we could try to order the generators intelligently, so as to influence
|
|
# which monomials appear in the quotient basis
|
|
|
|
# THEORY
|
|
# ------
|
|
# Ratsimpmodprime above can be used to "simplify" a rational function
|
|
# modulo a prime ideal. "Simplify" mainly means finding an equivalent
|
|
# expression of lower total degree.
|
|
#
|
|
# We intend to use this to simplify trigonometric functions. To do that,
|
|
# we need to decide (a) which ring to use, and (b) modulo which ideal to
|
|
# simplify. In practice, (a) means settling on a list of "generators"
|
|
# a, b, c, ..., such that the fraction we want to simplify is a rational
|
|
# function in a, b, c, ..., with coefficients in ZZ (integers).
|
|
# (2) means that we have to decide what relations to impose on the
|
|
# generators. There are two practical problems:
|
|
# (1) The ideal has to be *prime* (a technical term).
|
|
# (2) The relations have to be polynomials in the generators.
|
|
#
|
|
# We typically have two kinds of generators:
|
|
# - trigonometric expressions, like sin(x), cos(5*x), etc
|
|
# - "everything else", like gamma(x), pi, etc.
|
|
#
|
|
# Since this function is trigsimp, we will concentrate on what to do with
|
|
# trigonometric expressions. We can also simplify hyperbolic expressions,
|
|
# but the extensions should be clear.
|
|
#
|
|
# One crucial point is that all *other* generators really should behave
|
|
# like indeterminates. In particular if (say) "I" is one of them, then
|
|
# in fact I**2 + 1 = 0 and we may and will compute non-sensical
|
|
# expressions. However, we can work with a dummy and add the relation
|
|
# I**2 + 1 = 0 to our ideal, then substitute back in the end.
|
|
#
|
|
# Now regarding trigonometric generators. We split them into groups,
|
|
# according to the argument of the trigonometric functions. We want to
|
|
# organise this in such a way that most trigonometric identities apply in
|
|
# the same group. For example, given sin(x), cos(2*x) and cos(y), we would
|
|
# group as [sin(x), cos(2*x)] and [cos(y)].
|
|
#
|
|
# Our prime ideal will be built in three steps:
|
|
# (1) For each group, compute a "geometrically prime" ideal of relations.
|
|
# Geometrically prime means that it generates a prime ideal in
|
|
# CC[gens], not just ZZ[gens].
|
|
# (2) Take the union of all the generators of the ideals for all groups.
|
|
# By the geometric primality condition, this is still prime.
|
|
# (3) Add further inter-group relations which preserve primality.
|
|
#
|
|
# Step (1) works as follows. We will isolate common factors in the
|
|
# argument, so that all our generators are of the form sin(n*x), cos(n*x)
|
|
# or tan(n*x), with n an integer. Suppose first there are no tan terms.
|
|
# The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since
|
|
# X**2 + Y**2 - 1 is irreducible over CC.
|
|
# Now, if we have a generator sin(n*x), than we can, using trig identities,
|
|
# express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this
|
|
# relation to the ideal, preserving geometric primality, since the quotient
|
|
# ring is unchanged.
|
|
# Thus we have treated all sin and cos terms.
|
|
# For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0.
|
|
# (This requires of course that we already have relations for cos(n*x) and
|
|
# sin(n*x).) It is not obvious, but it seems that this preserves geometric
|
|
# primality.
|
|
# XXX A real proof would be nice. HELP!
|
|
# Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of
|
|
# CC[S, C, T]:
|
|
# - it suffices to show that the projective closure in CP**3 is
|
|
# irreducible
|
|
# - using the half-angle substitutions, we can express sin(x), tan(x),
|
|
# cos(x) as rational functions in tan(x/2)
|
|
# - from this, we get a rational map from CP**1 to our curve
|
|
# - this is a morphism, hence the curve is prime
|
|
#
|
|
# Step (2) is trivial.
|
|
#
|
|
# Step (3) works by adding selected relations of the form
|
|
# sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is
|
|
# preserved by the same argument as before.
|
|
|
|
def parse_hints(hints):
|
|
"""Split hints into (n, funcs, iterables, gens)."""
|
|
n = 1
|
|
funcs, iterables, gens = [], [], []
|
|
for e in hints:
|
|
if isinstance(e, (SYMPY_INTS, Integer)):
|
|
n = e
|
|
elif isinstance(e, FunctionClass):
|
|
funcs.append(e)
|
|
elif iterable(e):
|
|
iterables.append((e[0], e[1:]))
|
|
# XXX sin(x+2y)?
|
|
# Note: we go through polys so e.g.
|
|
# sin(-x) -> -sin(x) -> sin(x)
|
|
gens.extend(parallel_poly_from_expr(
|
|
[e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens)
|
|
else:
|
|
gens.append(e)
|
|
return n, funcs, iterables, gens
|
|
|
|
def build_ideal(x, terms):
|
|
"""
|
|
Build generators for our ideal. ``Terms`` is an iterable with elements of
|
|
the form (fn, coeff), indicating that we have a generator fn(coeff*x).
|
|
|
|
If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed
|
|
to appear in terms. Similarly for hyperbolic functions. For tan(n*x),
|
|
sin(n*x) and cos(n*x) are guaranteed.
|
|
"""
|
|
I = []
|
|
y = Dummy('y')
|
|
for fn, coeff in terms:
|
|
for c, s, t, rel in (
|
|
[cos, sin, tan, cos(x)**2 + sin(x)**2 - 1],
|
|
[cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1]):
|
|
if coeff == 1 and fn in [c, s]:
|
|
I.append(rel)
|
|
elif fn == t:
|
|
I.append(t(coeff*x)*c(coeff*x) - s(coeff*x))
|
|
elif fn in [c, s]:
|
|
cn = fn(coeff*y).expand(trig=True).subs(y, x)
|
|
I.append(fn(coeff*x) - cn)
|
|
return list(set(I))
|
|
|
|
def analyse_gens(gens, hints):
|
|
"""
|
|
Analyse the generators ``gens``, using the hints ``hints``.
|
|
|
|
The meaning of ``hints`` is described in the main docstring.
|
|
Return a new list of generators, and also the ideal we should
|
|
work with.
|
|
"""
|
|
# First parse the hints
|
|
n, funcs, iterables, extragens = parse_hints(hints)
|
|
debug('n=%s funcs: %s iterables: %s extragens: %s',
|
|
(funcs, iterables, extragens))
|
|
|
|
# We just add the extragens to gens and analyse them as before
|
|
gens = list(gens)
|
|
gens.extend(extragens)
|
|
|
|
# remove duplicates
|
|
funcs = list(set(funcs))
|
|
iterables = list(set(iterables))
|
|
gens = list(set(gens))
|
|
|
|
# all the functions we can do anything with
|
|
allfuncs = {sin, cos, tan, sinh, cosh, tanh}
|
|
# sin(3*x) -> ((3, x), sin)
|
|
trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens
|
|
if g.func in allfuncs]
|
|
# Our list of new generators - start with anything that we cannot
|
|
# work with (i.e. is not a trigonometric term)
|
|
freegens = [g for g in gens if g.func not in allfuncs]
|
|
newgens = []
|
|
trigdict = {}
|
|
for (coeff, var), fn in trigterms:
|
|
trigdict.setdefault(var, []).append((coeff, fn))
|
|
res = [] # the ideal
|
|
|
|
for key, val in trigdict.items():
|
|
# We have now assembeled a dictionary. Its keys are common
|
|
# arguments in trigonometric expressions, and values are lists of
|
|
# pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we
|
|
# need to deal with fn(coeff*x0). We take the rational gcd of the
|
|
# coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol",
|
|
# all other arguments are integral multiples thereof.
|
|
# We will build an ideal which works with sin(x), cos(x).
|
|
# If hint tan is provided, also work with tan(x). Moreover, if
|
|
# n > 1, also work with sin(k*x) for k <= n, and similarly for cos
|
|
# (and tan if the hint is provided). Finally, any generators which
|
|
# the ideal does not work with but we need to accommodate (either
|
|
# because it was in expr or because it was provided as a hint)
|
|
# we also build into the ideal.
|
|
# This selection process is expressed in the list ``terms``.
|
|
# build_ideal then generates the actual relations in our ideal,
|
|
# from this list.
|
|
fns = [x[1] for x in val]
|
|
val = [x[0] for x in val]
|
|
gcd = reduce(igcd, val)
|
|
terms = [(fn, v/gcd) for (fn, v) in zip(fns, val)]
|
|
fs = set(funcs + fns)
|
|
for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]):
|
|
if any(x in fs for x in (c, s, t)):
|
|
fs.add(c)
|
|
fs.add(s)
|
|
for fn in fs:
|
|
for k in range(1, n + 1):
|
|
terms.append((fn, k))
|
|
extra = []
|
|
for fn, v in terms:
|
|
if fn == tan:
|
|
extra.append((sin, v))
|
|
extra.append((cos, v))
|
|
if fn in [sin, cos] and tan in fs:
|
|
extra.append((tan, v))
|
|
if fn == tanh:
|
|
extra.append((sinh, v))
|
|
extra.append((cosh, v))
|
|
if fn in [sinh, cosh] and tanh in fs:
|
|
extra.append((tanh, v))
|
|
terms.extend(extra)
|
|
x = gcd*Mul(*key)
|
|
r = build_ideal(x, terms)
|
|
res.extend(r)
|
|
newgens.extend({fn(v*x) for fn, v in terms})
|
|
|
|
# Add generators for compound expressions from iterables
|
|
for fn, args in iterables:
|
|
if fn == tan:
|
|
# Tan expressions are recovered from sin and cos.
|
|
iterables.extend([(sin, args), (cos, args)])
|
|
elif fn == tanh:
|
|
# Tanh expressions are recovered from sihn and cosh.
|
|
iterables.extend([(sinh, args), (cosh, args)])
|
|
else:
|
|
dummys = symbols('d:%i' % len(args), cls=Dummy)
|
|
expr = fn( Add(*dummys)).expand(trig=True).subs(list(zip(dummys, args)))
|
|
res.append(fn(Add(*args)) - expr)
|
|
|
|
if myI in gens:
|
|
res.append(myI**2 + 1)
|
|
freegens.remove(myI)
|
|
newgens.append(myI)
|
|
|
|
return res, freegens, newgens
|
|
|
|
myI = Dummy('I')
|
|
expr = expr.subs(S.ImaginaryUnit, myI)
|
|
subs = [(myI, S.ImaginaryUnit)]
|
|
|
|
num, denom = cancel(expr).as_numer_denom()
|
|
try:
|
|
(pnum, pdenom), opt = parallel_poly_from_expr([num, denom])
|
|
except PolificationFailed:
|
|
return expr
|
|
debug('initial gens:', opt.gens)
|
|
ideal, freegens, gens = analyse_gens(opt.gens, hints)
|
|
debug('ideal:', ideal)
|
|
debug('new gens:', gens, " -- len", len(gens))
|
|
debug('free gens:', freegens, " -- len", len(gens))
|
|
# NOTE we force the domain to be ZZ to stop polys from injecting generators
|
|
# (which is usually a sign of a bug in the way we build the ideal)
|
|
if not gens:
|
|
return expr
|
|
G = groebner(ideal, order=order, gens=gens, domain=ZZ)
|
|
debug('groebner basis:', list(G), " -- len", len(G))
|
|
|
|
# If our fraction is a polynomial in the free generators, simplify all
|
|
# coefficients separately:
|
|
|
|
from sympy.simplify.ratsimp import ratsimpmodprime
|
|
|
|
if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)):
|
|
num = Poly(num, gens=gens+freegens).eject(*gens)
|
|
res = []
|
|
for monom, coeff in num.terms():
|
|
ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens)
|
|
# We compute the transitive closure of all generators that can
|
|
# be reached from our generators through relations in the ideal.
|
|
changed = True
|
|
while changed:
|
|
changed = False
|
|
for p in ideal:
|
|
p = Poly(p)
|
|
if not ourgens.issuperset(p.gens) and \
|
|
not p.has_only_gens(*set(p.gens).difference(ourgens)):
|
|
changed = True
|
|
ourgens.update(p.exclude().gens)
|
|
# NOTE preserve order!
|
|
realgens = [x for x in gens if x in ourgens]
|
|
# The generators of the ideal have now been (implicitly) split
|
|
# into two groups: those involving ourgens and those that don't.
|
|
# Since we took the transitive closure above, these two groups
|
|
# live in subgrings generated by a *disjoint* set of variables.
|
|
# Any sensible groebner basis algorithm will preserve this disjoint
|
|
# structure (i.e. the elements of the groebner basis can be split
|
|
# similarly), and and the two subsets of the groebner basis then
|
|
# form groebner bases by themselves. (For the smaller generating
|
|
# sets, of course.)
|
|
ourG = [g.as_expr() for g in G.polys if
|
|
g.has_only_gens(*ourgens.intersection(g.gens))]
|
|
res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \
|
|
ratsimpmodprime(coeff/denom, ourG, order=order,
|
|
gens=realgens, quick=quick, domain=ZZ,
|
|
polynomial=polynomial).subs(subs))
|
|
return Add(*res)
|
|
# NOTE The following is simpler and has less assumptions on the
|
|
# groebner basis algorithm. If the above turns out to be broken,
|
|
# use this.
|
|
return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \
|
|
ratsimpmodprime(coeff/denom, list(G), order=order,
|
|
gens=gens, quick=quick, domain=ZZ)
|
|
for monom, coeff in num.terms()])
|
|
else:
|
|
return ratsimpmodprime(
|
|
expr, list(G), order=order, gens=freegens+gens,
|
|
quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)
|
|
|
|
|
|
_trigs = (TrigonometricFunction, HyperbolicFunction)
|
|
|
|
|
|
def _trigsimp_inverse(rv):
|
|
|
|
def check_args(x, y):
|
|
try:
|
|
return x.args[0] == y.args[0]
|
|
except IndexError:
|
|
return False
|
|
|
|
def f(rv):
|
|
# for simple functions
|
|
g = getattr(rv, 'inverse', None)
|
|
if (g is not None and isinstance(rv.args[0], g()) and
|
|
isinstance(g()(1), TrigonometricFunction)):
|
|
return rv.args[0].args[0]
|
|
|
|
# for atan2 simplifications, harder because atan2 has 2 args
|
|
if isinstance(rv, atan2):
|
|
y, x = rv.args
|
|
if _coeff_isneg(y):
|
|
return -f(atan2(-y, x))
|
|
elif _coeff_isneg(x):
|
|
return S.Pi - f(atan2(y, -x))
|
|
|
|
if check_args(x, y):
|
|
if isinstance(y, sin) and isinstance(x, cos):
|
|
return x.args[0]
|
|
if isinstance(y, cos) and isinstance(x, sin):
|
|
return S.Pi / 2 - x.args[0]
|
|
|
|
return rv
|
|
|
|
return bottom_up(rv, f)
|
|
|
|
|
|
def trigsimp(expr, inverse=False, **opts):
|
|
"""Returns a reduced expression by using known trig identities.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
inverse : bool, optional
|
|
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.
|
|
Default : True
|
|
|
|
method : string, optional
|
|
Specifies the method to use. Valid choices are:
|
|
|
|
- ``'matching'``, default
|
|
- ``'groebner'``
|
|
- ``'combined'``
|
|
- ``'fu'``
|
|
- ``'old'``
|
|
|
|
If ``'matching'``, simplify the expression recursively by targeting
|
|
common patterns. If ``'groebner'``, apply an experimental groebner
|
|
basis algorithm. In this case further options are forwarded to
|
|
``trigsimp_groebner``, please refer to
|
|
its docstring. If ``'combined'``, it first runs the groebner basis
|
|
algorithm with small default parameters, then runs the ``'matching'``
|
|
algorithm. If ``'fu'``, run the collection of trigonometric
|
|
transformations described by Fu, et al. (see the
|
|
:py:func:`~sympy.simplify.fu.fu` docstring). If ``'old'``, the original
|
|
SymPy trig simplification function is run.
|
|
opts :
|
|
Optional keyword arguments passed to the method. See each method's
|
|
function docstring for details.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import trigsimp, sin, cos, log
|
|
>>> from sympy.abc import x
|
|
>>> e = 2*sin(x)**2 + 2*cos(x)**2
|
|
>>> trigsimp(e)
|
|
2
|
|
|
|
Simplification occurs wherever trigonometric functions are located.
|
|
|
|
>>> trigsimp(log(e))
|
|
log(2)
|
|
|
|
Using ``method='groebner'`` (or ``method='combined'``) might lead to
|
|
greater simplification.
|
|
|
|
The old trigsimp routine can be accessed as with method ``method='old'``.
|
|
|
|
>>> from sympy import coth, tanh
|
|
>>> t = 3*tanh(x)**7 - 2/coth(x)**7
|
|
>>> trigsimp(t, method='old') == t
|
|
True
|
|
>>> trigsimp(t)
|
|
tanh(x)**7
|
|
|
|
"""
|
|
from sympy.simplify.fu import fu
|
|
|
|
expr = sympify(expr)
|
|
|
|
_eval_trigsimp = getattr(expr, '_eval_trigsimp', None)
|
|
if _eval_trigsimp is not None:
|
|
return _eval_trigsimp(**opts)
|
|
|
|
old = opts.pop('old', False)
|
|
if not old:
|
|
opts.pop('deep', None)
|
|
opts.pop('recursive', None)
|
|
method = opts.pop('method', 'matching')
|
|
else:
|
|
method = 'old'
|
|
|
|
def groebnersimp(ex, **opts):
|
|
def traverse(e):
|
|
if e.is_Atom:
|
|
return e
|
|
args = [traverse(x) for x in e.args]
|
|
if e.is_Function or e.is_Pow:
|
|
args = [trigsimp_groebner(x, **opts) for x in args]
|
|
return e.func(*args)
|
|
new = traverse(ex)
|
|
if not isinstance(new, Expr):
|
|
return new
|
|
return trigsimp_groebner(new, **opts)
|
|
|
|
trigsimpfunc = {
|
|
'fu': (lambda x: fu(x, **opts)),
|
|
'matching': (lambda x: futrig(x)),
|
|
'groebner': (lambda x: groebnersimp(x, **opts)),
|
|
'combined': (lambda x: futrig(groebnersimp(x,
|
|
polynomial=True, hints=[2, tan]))),
|
|
'old': lambda x: trigsimp_old(x, **opts),
|
|
}[method]
|
|
|
|
expr_simplified = trigsimpfunc(expr)
|
|
if inverse:
|
|
expr_simplified = _trigsimp_inverse(expr_simplified)
|
|
|
|
return expr_simplified
|
|
|
|
|
|
def exptrigsimp(expr):
|
|
"""
|
|
Simplifies exponential / trigonometric / hyperbolic functions.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import exptrigsimp, exp, cosh, sinh
|
|
>>> from sympy.abc import z
|
|
|
|
>>> exptrigsimp(exp(z) + exp(-z))
|
|
2*cosh(z)
|
|
>>> exptrigsimp(cosh(z) - sinh(z))
|
|
exp(-z)
|
|
"""
|
|
from sympy.simplify.fu import hyper_as_trig, TR2i
|
|
|
|
def exp_trig(e):
|
|
# select the better of e, and e rewritten in terms of exp or trig
|
|
# functions
|
|
choices = [e]
|
|
if e.has(*_trigs):
|
|
choices.append(e.rewrite(exp))
|
|
choices.append(e.rewrite(cos))
|
|
return min(*choices, key=count_ops)
|
|
newexpr = bottom_up(expr, exp_trig)
|
|
|
|
def f(rv):
|
|
if not rv.is_Mul:
|
|
return rv
|
|
commutative_part, noncommutative_part = rv.args_cnc()
|
|
# Since as_powers_dict loses order information,
|
|
# if there is more than one noncommutative factor,
|
|
# it should only be used to simplify the commutative part.
|
|
if (len(noncommutative_part) > 1):
|
|
return f(Mul(*commutative_part))*Mul(*noncommutative_part)
|
|
rvd = rv.as_powers_dict()
|
|
newd = rvd.copy()
|
|
|
|
def signlog(expr, sign=S.One):
|
|
if expr is S.Exp1:
|
|
return sign, S.One
|
|
elif isinstance(expr, exp) or (expr.is_Pow and expr.base == S.Exp1):
|
|
return sign, expr.exp
|
|
elif sign is S.One:
|
|
return signlog(-expr, sign=-S.One)
|
|
else:
|
|
return None, None
|
|
|
|
ee = rvd[S.Exp1]
|
|
for k in rvd:
|
|
if k.is_Add and len(k.args) == 2:
|
|
# k == c*(1 + sign*E**x)
|
|
c = k.args[0]
|
|
sign, x = signlog(k.args[1]/c)
|
|
if not x:
|
|
continue
|
|
m = rvd[k]
|
|
newd[k] -= m
|
|
if ee == -x*m/2:
|
|
# sinh and cosh
|
|
newd[S.Exp1] -= ee
|
|
ee = 0
|
|
if sign == 1:
|
|
newd[2*c*cosh(x/2)] += m
|
|
else:
|
|
newd[-2*c*sinh(x/2)] += m
|
|
elif newd[1 - sign*S.Exp1**x] == -m:
|
|
# tanh
|
|
del newd[1 - sign*S.Exp1**x]
|
|
if sign == 1:
|
|
newd[-c/tanh(x/2)] += m
|
|
else:
|
|
newd[-c*tanh(x/2)] += m
|
|
else:
|
|
newd[1 + sign*S.Exp1**x] += m
|
|
newd[c] += m
|
|
|
|
return Mul(*[k**newd[k] for k in newd])
|
|
newexpr = bottom_up(newexpr, f)
|
|
|
|
# sin/cos and sinh/cosh ratios to tan and tanh, respectively
|
|
if newexpr.has(HyperbolicFunction):
|
|
e, f = hyper_as_trig(newexpr)
|
|
newexpr = f(TR2i(e))
|
|
if newexpr.has(TrigonometricFunction):
|
|
newexpr = TR2i(newexpr)
|
|
|
|
# can we ever generate an I where there was none previously?
|
|
if not (newexpr.has(I) and not expr.has(I)):
|
|
expr = newexpr
|
|
return expr
|
|
|
|
#-------------------- the old trigsimp routines ---------------------
|
|
|
|
def trigsimp_old(expr, *, first=True, **opts):
|
|
"""
|
|
Reduces expression by using known trig identities.
|
|
|
|
Notes
|
|
=====
|
|
|
|
deep:
|
|
- Apply trigsimp inside all objects with arguments
|
|
|
|
recursive:
|
|
- Use common subexpression elimination (cse()) and apply
|
|
trigsimp recursively (this is quite expensive if the
|
|
expression is large)
|
|
|
|
method:
|
|
- Determine the method to use. Valid choices are 'matching' (default),
|
|
'groebner', 'combined', 'fu' and 'futrig'. If 'matching', simplify the
|
|
expression recursively by pattern matching. If 'groebner', apply an
|
|
experimental groebner basis algorithm. In this case further options
|
|
are forwarded to ``trigsimp_groebner``, please refer to its docstring.
|
|
If 'combined', first run the groebner basis algorithm with small
|
|
default parameters, then run the 'matching' algorithm. 'fu' runs the
|
|
collection of trigonometric transformations described by Fu, et al.
|
|
(see the `fu` docstring) while `futrig` runs a subset of Fu-transforms
|
|
that mimic the behavior of `trigsimp`.
|
|
|
|
compare:
|
|
- show input and output from `trigsimp` and `futrig` when different,
|
|
but returns the `trigsimp` value.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import trigsimp, sin, cos, log, cot
|
|
>>> from sympy.abc import x
|
|
>>> e = 2*sin(x)**2 + 2*cos(x)**2
|
|
>>> trigsimp(e, old=True)
|
|
2
|
|
>>> trigsimp(log(e), old=True)
|
|
log(2*sin(x)**2 + 2*cos(x)**2)
|
|
>>> trigsimp(log(e), deep=True, old=True)
|
|
log(2)
|
|
|
|
Using `method="groebner"` (or `"combined"`) can sometimes lead to a lot
|
|
more simplification:
|
|
|
|
>>> e = (-sin(x) + 1)/cos(x) + cos(x)/(-sin(x) + 1)
|
|
>>> trigsimp(e, old=True)
|
|
(1 - sin(x))/cos(x) + cos(x)/(1 - sin(x))
|
|
>>> trigsimp(e, method="groebner", old=True)
|
|
2/cos(x)
|
|
|
|
>>> trigsimp(1/cot(x)**2, compare=True, old=True)
|
|
futrig: tan(x)**2
|
|
cot(x)**(-2)
|
|
|
|
"""
|
|
old = expr
|
|
if first:
|
|
if not expr.has(*_trigs):
|
|
return expr
|
|
|
|
trigsyms = set().union(*[t.free_symbols for t in expr.atoms(*_trigs)])
|
|
if len(trigsyms) > 1:
|
|
from sympy.simplify.simplify import separatevars
|
|
|
|
d = separatevars(expr)
|
|
if d.is_Mul:
|
|
d = separatevars(d, dict=True) or d
|
|
if isinstance(d, dict):
|
|
expr = 1
|
|
for k, v in d.items():
|
|
# remove hollow factoring
|
|
was = v
|
|
v = expand_mul(v)
|
|
opts['first'] = False
|
|
vnew = trigsimp(v, **opts)
|
|
if vnew == v:
|
|
vnew = was
|
|
expr *= vnew
|
|
old = expr
|
|
else:
|
|
if d.is_Add:
|
|
for s in trigsyms:
|
|
r, e = expr.as_independent(s)
|
|
if r:
|
|
opts['first'] = False
|
|
expr = r + trigsimp(e, **opts)
|
|
if not expr.is_Add:
|
|
break
|
|
old = expr
|
|
|
|
recursive = opts.pop('recursive', False)
|
|
deep = opts.pop('deep', False)
|
|
method = opts.pop('method', 'matching')
|
|
|
|
def groebnersimp(ex, deep, **opts):
|
|
def traverse(e):
|
|
if e.is_Atom:
|
|
return e
|
|
args = [traverse(x) for x in e.args]
|
|
if e.is_Function or e.is_Pow:
|
|
args = [trigsimp_groebner(x, **opts) for x in args]
|
|
return e.func(*args)
|
|
if deep:
|
|
ex = traverse(ex)
|
|
return trigsimp_groebner(ex, **opts)
|
|
|
|
trigsimpfunc = {
|
|
'matching': (lambda x, d: _trigsimp(x, d)),
|
|
'groebner': (lambda x, d: groebnersimp(x, d, **opts)),
|
|
'combined': (lambda x, d: _trigsimp(groebnersimp(x,
|
|
d, polynomial=True, hints=[2, tan]),
|
|
d))
|
|
}[method]
|
|
|
|
if recursive:
|
|
w, g = cse(expr)
|
|
g = trigsimpfunc(g[0], deep)
|
|
|
|
for sub in reversed(w):
|
|
g = g.subs(sub[0], sub[1])
|
|
g = trigsimpfunc(g, deep)
|
|
result = g
|
|
else:
|
|
result = trigsimpfunc(expr, deep)
|
|
|
|
if opts.get('compare', False):
|
|
f = futrig(old)
|
|
if f != result:
|
|
print('\tfutrig:', f)
|
|
|
|
return result
|
|
|
|
|
|
def _dotrig(a, b):
|
|
"""Helper to tell whether ``a`` and ``b`` have the same sorts
|
|
of symbols in them -- no need to test hyperbolic patterns against
|
|
expressions that have no hyperbolics in them."""
|
|
return a.func == b.func and (
|
|
a.has(TrigonometricFunction) and b.has(TrigonometricFunction) or
|
|
a.has(HyperbolicFunction) and b.has(HyperbolicFunction))
|
|
|
|
|
|
_trigpat = None
|
|
def _trigpats():
|
|
global _trigpat
|
|
a, b, c = symbols('a b c', cls=Wild)
|
|
d = Wild('d', commutative=False)
|
|
|
|
# for the simplifications like sinh/cosh -> tanh:
|
|
# DO NOT REORDER THE FIRST 14 since these are assumed to be in this
|
|
# order in _match_div_rewrite.
|
|
matchers_division = (
|
|
(a*sin(b)**c/cos(b)**c, a*tan(b)**c, sin(b), cos(b)),
|
|
(a*tan(b)**c*cos(b)**c, a*sin(b)**c, sin(b), cos(b)),
|
|
(a*cot(b)**c*sin(b)**c, a*cos(b)**c, sin(b), cos(b)),
|
|
(a*tan(b)**c/sin(b)**c, a/cos(b)**c, sin(b), cos(b)),
|
|
(a*cot(b)**c/cos(b)**c, a/sin(b)**c, sin(b), cos(b)),
|
|
(a*cot(b)**c*tan(b)**c, a, sin(b), cos(b)),
|
|
(a*(cos(b) + 1)**c*(cos(b) - 1)**c,
|
|
a*(-sin(b)**2)**c, cos(b) + 1, cos(b) - 1),
|
|
(a*(sin(b) + 1)**c*(sin(b) - 1)**c,
|
|
a*(-cos(b)**2)**c, sin(b) + 1, sin(b) - 1),
|
|
|
|
(a*sinh(b)**c/cosh(b)**c, a*tanh(b)**c, S.One, S.One),
|
|
(a*tanh(b)**c*cosh(b)**c, a*sinh(b)**c, S.One, S.One),
|
|
(a*coth(b)**c*sinh(b)**c, a*cosh(b)**c, S.One, S.One),
|
|
(a*tanh(b)**c/sinh(b)**c, a/cosh(b)**c, S.One, S.One),
|
|
(a*coth(b)**c/cosh(b)**c, a/sinh(b)**c, S.One, S.One),
|
|
(a*coth(b)**c*tanh(b)**c, a, S.One, S.One),
|
|
|
|
(c*(tanh(a) + tanh(b))/(1 + tanh(a)*tanh(b)),
|
|
tanh(a + b)*c, S.One, S.One),
|
|
)
|
|
|
|
matchers_add = (
|
|
(c*sin(a)*cos(b) + c*cos(a)*sin(b) + d, sin(a + b)*c + d),
|
|
(c*cos(a)*cos(b) - c*sin(a)*sin(b) + d, cos(a + b)*c + d),
|
|
(c*sin(a)*cos(b) - c*cos(a)*sin(b) + d, sin(a - b)*c + d),
|
|
(c*cos(a)*cos(b) + c*sin(a)*sin(b) + d, cos(a - b)*c + d),
|
|
(c*sinh(a)*cosh(b) + c*sinh(b)*cosh(a) + d, sinh(a + b)*c + d),
|
|
(c*cosh(a)*cosh(b) + c*sinh(a)*sinh(b) + d, cosh(a + b)*c + d),
|
|
)
|
|
|
|
# for cos(x)**2 + sin(x)**2 -> 1
|
|
matchers_identity = (
|
|
(a*sin(b)**2, a - a*cos(b)**2),
|
|
(a*tan(b)**2, a*(1/cos(b))**2 - a),
|
|
(a*cot(b)**2, a*(1/sin(b))**2 - a),
|
|
(a*sin(b + c), a*(sin(b)*cos(c) + sin(c)*cos(b))),
|
|
(a*cos(b + c), a*(cos(b)*cos(c) - sin(b)*sin(c))),
|
|
(a*tan(b + c), a*((tan(b) + tan(c))/(1 - tan(b)*tan(c)))),
|
|
|
|
(a*sinh(b)**2, a*cosh(b)**2 - a),
|
|
(a*tanh(b)**2, a - a*(1/cosh(b))**2),
|
|
(a*coth(b)**2, a + a*(1/sinh(b))**2),
|
|
(a*sinh(b + c), a*(sinh(b)*cosh(c) + sinh(c)*cosh(b))),
|
|
(a*cosh(b + c), a*(cosh(b)*cosh(c) + sinh(b)*sinh(c))),
|
|
(a*tanh(b + c), a*((tanh(b) + tanh(c))/(1 + tanh(b)*tanh(c)))),
|
|
|
|
)
|
|
|
|
# Reduce any lingering artifacts, such as sin(x)**2 changing
|
|
# to 1-cos(x)**2 when sin(x)**2 was "simpler"
|
|
artifacts = (
|
|
(a - a*cos(b)**2 + c, a*sin(b)**2 + c, cos),
|
|
(a - a*(1/cos(b))**2 + c, -a*tan(b)**2 + c, cos),
|
|
(a - a*(1/sin(b))**2 + c, -a*cot(b)**2 + c, sin),
|
|
|
|
(a - a*cosh(b)**2 + c, -a*sinh(b)**2 + c, cosh),
|
|
(a - a*(1/cosh(b))**2 + c, a*tanh(b)**2 + c, cosh),
|
|
(a + a*(1/sinh(b))**2 + c, a*coth(b)**2 + c, sinh),
|
|
|
|
# same as above but with noncommutative prefactor
|
|
(a*d - a*d*cos(b)**2 + c, a*d*sin(b)**2 + c, cos),
|
|
(a*d - a*d*(1/cos(b))**2 + c, -a*d*tan(b)**2 + c, cos),
|
|
(a*d - a*d*(1/sin(b))**2 + c, -a*d*cot(b)**2 + c, sin),
|
|
|
|
(a*d - a*d*cosh(b)**2 + c, -a*d*sinh(b)**2 + c, cosh),
|
|
(a*d - a*d*(1/cosh(b))**2 + c, a*d*tanh(b)**2 + c, cosh),
|
|
(a*d + a*d*(1/sinh(b))**2 + c, a*d*coth(b)**2 + c, sinh),
|
|
)
|
|
|
|
_trigpat = (a, b, c, d, matchers_division, matchers_add,
|
|
matchers_identity, artifacts)
|
|
return _trigpat
|
|
|
|
|
|
def _replace_mul_fpowxgpow(expr, f, g, rexp, h, rexph):
|
|
"""Helper for _match_div_rewrite.
|
|
|
|
Replace f(b_)**c_*g(b_)**(rexp(c_)) with h(b)**rexph(c) if f(b_)
|
|
and g(b_) are both positive or if c_ is an integer.
|
|
"""
|
|
# assert expr.is_Mul and expr.is_commutative and f != g
|
|
fargs = defaultdict(int)
|
|
gargs = defaultdict(int)
|
|
args = []
|
|
for x in expr.args:
|
|
if x.is_Pow or x.func in (f, g):
|
|
b, e = x.as_base_exp()
|
|
if b.is_positive or e.is_integer:
|
|
if b.func == f:
|
|
fargs[b.args[0]] += e
|
|
continue
|
|
elif b.func == g:
|
|
gargs[b.args[0]] += e
|
|
continue
|
|
args.append(x)
|
|
common = set(fargs) & set(gargs)
|
|
hit = False
|
|
while common:
|
|
key = common.pop()
|
|
fe = fargs.pop(key)
|
|
ge = gargs.pop(key)
|
|
if fe == rexp(ge):
|
|
args.append(h(key)**rexph(fe))
|
|
hit = True
|
|
else:
|
|
fargs[key] = fe
|
|
gargs[key] = ge
|
|
if not hit:
|
|
return expr
|
|
while fargs:
|
|
key, e = fargs.popitem()
|
|
args.append(f(key)**e)
|
|
while gargs:
|
|
key, e = gargs.popitem()
|
|
args.append(g(key)**e)
|
|
return Mul(*args)
|
|
|
|
|
|
_idn = lambda x: x
|
|
_midn = lambda x: -x
|
|
_one = lambda x: S.One
|
|
|
|
def _match_div_rewrite(expr, i):
|
|
"""helper for __trigsimp"""
|
|
if i == 0:
|
|
expr = _replace_mul_fpowxgpow(expr, sin, cos,
|
|
_midn, tan, _idn)
|
|
elif i == 1:
|
|
expr = _replace_mul_fpowxgpow(expr, tan, cos,
|
|
_idn, sin, _idn)
|
|
elif i == 2:
|
|
expr = _replace_mul_fpowxgpow(expr, cot, sin,
|
|
_idn, cos, _idn)
|
|
elif i == 3:
|
|
expr = _replace_mul_fpowxgpow(expr, tan, sin,
|
|
_midn, cos, _midn)
|
|
elif i == 4:
|
|
expr = _replace_mul_fpowxgpow(expr, cot, cos,
|
|
_midn, sin, _midn)
|
|
elif i == 5:
|
|
expr = _replace_mul_fpowxgpow(expr, cot, tan,
|
|
_idn, _one, _idn)
|
|
# i in (6, 7) is skipped
|
|
elif i == 8:
|
|
expr = _replace_mul_fpowxgpow(expr, sinh, cosh,
|
|
_midn, tanh, _idn)
|
|
elif i == 9:
|
|
expr = _replace_mul_fpowxgpow(expr, tanh, cosh,
|
|
_idn, sinh, _idn)
|
|
elif i == 10:
|
|
expr = _replace_mul_fpowxgpow(expr, coth, sinh,
|
|
_idn, cosh, _idn)
|
|
elif i == 11:
|
|
expr = _replace_mul_fpowxgpow(expr, tanh, sinh,
|
|
_midn, cosh, _midn)
|
|
elif i == 12:
|
|
expr = _replace_mul_fpowxgpow(expr, coth, cosh,
|
|
_midn, sinh, _midn)
|
|
elif i == 13:
|
|
expr = _replace_mul_fpowxgpow(expr, coth, tanh,
|
|
_idn, _one, _idn)
|
|
else:
|
|
return None
|
|
return expr
|
|
|
|
|
|
def _trigsimp(expr, deep=False):
|
|
# protect the cache from non-trig patterns; we only allow
|
|
# trig patterns to enter the cache
|
|
if expr.has(*_trigs):
|
|
return __trigsimp(expr, deep)
|
|
return expr
|
|
|
|
|
|
@cacheit
|
|
def __trigsimp(expr, deep=False):
|
|
"""recursive helper for trigsimp"""
|
|
from sympy.simplify.fu import TR10i
|
|
|
|
if _trigpat is None:
|
|
_trigpats()
|
|
a, b, c, d, matchers_division, matchers_add, \
|
|
matchers_identity, artifacts = _trigpat
|
|
|
|
if expr.is_Mul:
|
|
# do some simplifications like sin/cos -> tan:
|
|
if not expr.is_commutative:
|
|
com, nc = expr.args_cnc()
|
|
expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc)
|
|
else:
|
|
for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division):
|
|
if not _dotrig(expr, pattern):
|
|
continue
|
|
|
|
newexpr = _match_div_rewrite(expr, i)
|
|
if newexpr is not None:
|
|
if newexpr != expr:
|
|
expr = newexpr
|
|
break
|
|
else:
|
|
continue
|
|
|
|
# use SymPy matching instead
|
|
res = expr.match(pattern)
|
|
if res and res.get(c, 0):
|
|
if not res[c].is_integer:
|
|
ok = ok1.subs(res)
|
|
if not ok.is_positive:
|
|
continue
|
|
ok = ok2.subs(res)
|
|
if not ok.is_positive:
|
|
continue
|
|
# if "a" contains any of trig or hyperbolic funcs with
|
|
# argument "b" then skip the simplification
|
|
if any(w.args[0] == res[b] for w in res[a].atoms(
|
|
TrigonometricFunction, HyperbolicFunction)):
|
|
continue
|
|
# simplify and finish:
|
|
expr = simp.subs(res)
|
|
break # process below
|
|
|
|
if expr.is_Add:
|
|
args = []
|
|
for term in expr.args:
|
|
if not term.is_commutative:
|
|
com, nc = term.args_cnc()
|
|
nc = Mul._from_args(nc)
|
|
term = Mul._from_args(com)
|
|
else:
|
|
nc = S.One
|
|
term = _trigsimp(term, deep)
|
|
for pattern, result in matchers_identity:
|
|
res = term.match(pattern)
|
|
if res is not None:
|
|
term = result.subs(res)
|
|
break
|
|
args.append(term*nc)
|
|
if args != expr.args:
|
|
expr = Add(*args)
|
|
expr = min(expr, expand(expr), key=count_ops)
|
|
if expr.is_Add:
|
|
for pattern, result in matchers_add:
|
|
if not _dotrig(expr, pattern):
|
|
continue
|
|
expr = TR10i(expr)
|
|
if expr.has(HyperbolicFunction):
|
|
res = expr.match(pattern)
|
|
# if "d" contains any trig or hyperbolic funcs with
|
|
# argument "a" or "b" then skip the simplification;
|
|
# this isn't perfect -- see tests
|
|
if res is None or not (a in res and b in res) or any(
|
|
w.args[0] in (res[a], res[b]) for w in res[d].atoms(
|
|
TrigonometricFunction, HyperbolicFunction)):
|
|
continue
|
|
expr = result.subs(res)
|
|
break
|
|
|
|
# Reduce any lingering artifacts, such as sin(x)**2 changing
|
|
# to 1 - cos(x)**2 when sin(x)**2 was "simpler"
|
|
for pattern, result, ex in artifacts:
|
|
if not _dotrig(expr, pattern):
|
|
continue
|
|
# Substitute a new wild that excludes some function(s)
|
|
# to help influence a better match. This is because
|
|
# sometimes, for example, 'a' would match sec(x)**2
|
|
a_t = Wild('a', exclude=[ex])
|
|
pattern = pattern.subs(a, a_t)
|
|
result = result.subs(a, a_t)
|
|
|
|
m = expr.match(pattern)
|
|
was = None
|
|
while m and was != expr:
|
|
was = expr
|
|
if m[a_t] == 0 or \
|
|
-m[a_t] in m[c].args or m[a_t] + m[c] == 0:
|
|
break
|
|
if d in m and m[a_t]*m[d] + m[c] == 0:
|
|
break
|
|
expr = result.subs(m)
|
|
m = expr.match(pattern)
|
|
m.setdefault(c, S.Zero)
|
|
|
|
elif expr.is_Mul or expr.is_Pow or deep and expr.args:
|
|
expr = expr.func(*[_trigsimp(a, deep) for a in expr.args])
|
|
|
|
try:
|
|
if not expr.has(*_trigs):
|
|
raise TypeError
|
|
e = expr.atoms(exp)
|
|
new = expr.rewrite(exp, deep=deep)
|
|
if new == e:
|
|
raise TypeError
|
|
fnew = factor(new)
|
|
if fnew != new:
|
|
new = sorted([new, factor(new)], key=count_ops)[0]
|
|
# if all exp that were introduced disappeared then accept it
|
|
if not (new.atoms(exp) - e):
|
|
expr = new
|
|
except TypeError:
|
|
pass
|
|
|
|
return expr
|
|
#------------------- end of old trigsimp routines --------------------
|
|
|
|
|
|
def futrig(e, *, hyper=True, **kwargs):
|
|
"""Return simplified ``e`` using Fu-like transformations.
|
|
This is not the "Fu" algorithm. This is called by default
|
|
from ``trigsimp``. By default, hyperbolics subexpressions
|
|
will be simplified, but this can be disabled by setting
|
|
``hyper=False``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import trigsimp, tan, sinh, tanh
|
|
>>> from sympy.simplify.trigsimp import futrig
|
|
>>> from sympy.abc import x
|
|
>>> trigsimp(1/tan(x)**2)
|
|
tan(x)**(-2)
|
|
|
|
>>> futrig(sinh(x)/tanh(x))
|
|
cosh(x)
|
|
|
|
"""
|
|
from sympy.simplify.fu import hyper_as_trig
|
|
|
|
e = sympify(e)
|
|
|
|
if not isinstance(e, Basic):
|
|
return e
|
|
|
|
if not e.args:
|
|
return e
|
|
|
|
old = e
|
|
e = bottom_up(e, _futrig)
|
|
|
|
if hyper and e.has(HyperbolicFunction):
|
|
e, f = hyper_as_trig(e)
|
|
e = f(bottom_up(e, _futrig))
|
|
|
|
if e != old and e.is_Mul and e.args[0].is_Rational:
|
|
# redistribute leading coeff on 2-arg Add
|
|
e = Mul(*e.as_coeff_Mul())
|
|
return e
|
|
|
|
|
|
def _futrig(e):
|
|
"""Helper for futrig."""
|
|
from sympy.simplify.fu import (
|
|
TR1, TR2, TR3, TR2i, TR10, L, TR10i,
|
|
TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, _TR11, TR14, TR22,
|
|
TR12)
|
|
|
|
if not e.has(TrigonometricFunction):
|
|
return e
|
|
|
|
if e.is_Mul:
|
|
coeff, e = e.as_independent(TrigonometricFunction)
|
|
else:
|
|
coeff = None
|
|
|
|
Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add)
|
|
trigs = lambda x: x.has(TrigonometricFunction)
|
|
|
|
tree = [identity,
|
|
(
|
|
TR3, # canonical angles
|
|
TR1, # sec-csc -> cos-sin
|
|
TR12, # expand tan of sum
|
|
lambda x: _eapply(factor, x, trigs),
|
|
TR2, # tan-cot -> sin-cos
|
|
[identity, lambda x: _eapply(_mexpand, x, trigs)],
|
|
TR2i, # sin-cos ratio -> tan
|
|
lambda x: _eapply(lambda i: factor(i.normal()), x, trigs),
|
|
TR14, # factored identities
|
|
TR5, # sin-pow -> cos_pow
|
|
TR10, # sin-cos of sums -> sin-cos prod
|
|
TR11, _TR11, TR6, # reduce double angles and rewrite cos pows
|
|
lambda x: _eapply(factor, x, trigs),
|
|
TR14, # factored powers of identities
|
|
[identity, lambda x: _eapply(_mexpand, x, trigs)],
|
|
TR10i, # sin-cos products > sin-cos of sums
|
|
TRmorrie,
|
|
[identity, TR8], # sin-cos products -> sin-cos of sums
|
|
[identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan
|
|
[
|
|
lambda x: _eapply(expand_mul, TR5(x), trigs),
|
|
lambda x: _eapply(
|
|
expand_mul, TR15(x), trigs)], # pos/neg powers of sin
|
|
[
|
|
lambda x: _eapply(expand_mul, TR6(x), trigs),
|
|
lambda x: _eapply(
|
|
expand_mul, TR16(x), trigs)], # pos/neg powers of cos
|
|
TR111, # tan, sin, cos to neg power -> cot, csc, sec
|
|
[identity, TR2i], # sin-cos ratio to tan
|
|
[identity, lambda x: _eapply(
|
|
expand_mul, TR22(x), trigs)], # tan-cot to sec-csc
|
|
TR1, TR2, TR2i,
|
|
[identity, lambda x: _eapply(
|
|
factor_terms, TR12(x), trigs)], # expand tan of sum
|
|
)]
|
|
e = greedy(tree, objective=Lops)(e)
|
|
|
|
if coeff is not None:
|
|
e = coeff * e
|
|
|
|
return e
|
|
|
|
|
|
def _is_Expr(e):
|
|
"""_eapply helper to tell whether ``e`` and all its args
|
|
are Exprs."""
|
|
if isinstance(e, Derivative):
|
|
return _is_Expr(e.expr)
|
|
if not isinstance(e, Expr):
|
|
return False
|
|
return all(_is_Expr(i) for i in e.args)
|
|
|
|
|
|
def _eapply(func, e, cond=None):
|
|
"""Apply ``func`` to ``e`` if all args are Exprs else only
|
|
apply it to those args that *are* Exprs."""
|
|
if not isinstance(e, Expr):
|
|
return e
|
|
if _is_Expr(e) or not e.args:
|
|
return func(e)
|
|
return e.func(*[
|
|
_eapply(func, ei) if (cond is None or cond(ei)) else ei
|
|
for ei in e.args])
|