Coverage for /usr/lib/python3/dist-packages/sympy/simplify/trigsimp.py: 8%
530 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from collections import defaultdict
2from functools import reduce
4from sympy.core import (sympify, Basic, S, Expr, factor_terms,
5 Mul, Add, bottom_up)
6from sympy.core.cache import cacheit
7from sympy.core.function import (count_ops, _mexpand, FunctionClass, expand,
8 expand_mul, _coeff_isneg, Derivative)
9from sympy.core.numbers import I, Integer, igcd
10from sympy.core.sorting import _nodes
11from sympy.core.symbol import Dummy, symbols, Wild
12from sympy.external.gmpy import SYMPY_INTS
13from sympy.functions import sin, cos, exp, cosh, tanh, sinh, tan, cot, coth
14from sympy.functions import atan2
15from sympy.functions.elementary.hyperbolic import HyperbolicFunction
16from sympy.functions.elementary.trigonometric import TrigonometricFunction
17from sympy.polys import Poly, factor, cancel, parallel_poly_from_expr
18from sympy.polys.domains import ZZ
19from sympy.polys.polyerrors import PolificationFailed
20from sympy.polys.polytools import groebner
21from sympy.simplify.cse_main import cse
22from sympy.strategies.core import identity
23from sympy.strategies.tree import greedy
24from sympy.utilities.iterables import iterable
25from sympy.utilities.misc import debug
27def trigsimp_groebner(expr, hints=[], quick=False, order="grlex",
28 polynomial=False):
29 """
30 Simplify trigonometric expressions using a groebner basis algorithm.
32 Explanation
33 ===========
35 This routine takes a fraction involving trigonometric or hyperbolic
36 expressions, and tries to simplify it. The primary metric is the
37 total degree. Some attempts are made to choose the simplest possible
38 expression of the minimal degree, but this is non-rigorous, and also
39 very slow (see the ``quick=True`` option).
41 If ``polynomial`` is set to True, instead of simplifying numerator and
42 denominator together, this function just brings numerator and denominator
43 into a canonical form. This is much faster, but has potentially worse
44 results. However, if the input is a polynomial, then the result is
45 guaranteed to be an equivalent polynomial of minimal degree.
47 The most important option is hints. Its entries can be any of the
48 following:
50 - a natural number
51 - a function
52 - an iterable of the form (func, var1, var2, ...)
53 - anything else, interpreted as a generator
55 A number is used to indicate that the search space should be increased.
56 A function is used to indicate that said function is likely to occur in a
57 simplified expression.
58 An iterable is used indicate that func(var1 + var2 + ...) is likely to
59 occur in a simplified .
60 An additional generator also indicates that it is likely to occur.
61 (See examples below).
63 This routine carries out various computationally intensive algorithms.
64 The option ``quick=True`` can be used to suppress one particularly slow
65 step (at the expense of potentially more complicated results, but never at
66 the expense of increased total degree).
68 Examples
69 ========
71 >>> from sympy.abc import x, y
72 >>> from sympy import sin, tan, cos, sinh, cosh, tanh
73 >>> from sympy.simplify.trigsimp import trigsimp_groebner
75 Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens:
77 >>> ex = sin(x)*cos(x)
78 >>> trigsimp_groebner(ex)
79 sin(x)*cos(x)
81 This is because ``trigsimp_groebner`` only looks for a simplification
82 involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try
83 ``2*x`` by passing ``hints=[2]``:
85 >>> trigsimp_groebner(ex, hints=[2])
86 sin(2*x)/2
87 >>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2])
88 -cos(2*x)
90 Increasing the search space this way can quickly become expensive. A much
91 faster way is to give a specific expression that is likely to occur:
93 >>> trigsimp_groebner(ex, hints=[sin(2*x)])
94 sin(2*x)/2
96 Hyperbolic expressions are similarly supported:
98 >>> trigsimp_groebner(sinh(2*x)/sinh(x))
99 2*cosh(x)
101 Note how no hints had to be passed, since the expression already involved
102 ``2*x``.
104 The tangent function is also supported. You can either pass ``tan`` in the
105 hints, to indicate that tan should be tried whenever cosine or sine are,
106 or you can pass a specific generator:
108 >>> trigsimp_groebner(sin(x)/cos(x), hints=[tan])
109 tan(x)
110 >>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)])
111 tanh(x)
113 Finally, you can use the iterable form to suggest that angle sum formulae
114 should be tried:
116 >>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y))
117 >>> trigsimp_groebner(ex, hints=[(tan, x, y)])
118 tan(x + y)
119 """
120 # TODO
121 # - preprocess by replacing everything by funcs we can handle
122 # - optionally use cot instead of tan
123 # - more intelligent hinting.
124 # For example, if the ideal is small, and we have sin(x), sin(y),
125 # add sin(x + y) automatically... ?
126 # - algebraic numbers ...
127 # - expressions of lowest degree are not distinguished properly
128 # e.g. 1 - sin(x)**2
129 # - we could try to order the generators intelligently, so as to influence
130 # which monomials appear in the quotient basis
132 # THEORY
133 # ------
134 # Ratsimpmodprime above can be used to "simplify" a rational function
135 # modulo a prime ideal. "Simplify" mainly means finding an equivalent
136 # expression of lower total degree.
137 #
138 # We intend to use this to simplify trigonometric functions. To do that,
139 # we need to decide (a) which ring to use, and (b) modulo which ideal to
140 # simplify. In practice, (a) means settling on a list of "generators"
141 # a, b, c, ..., such that the fraction we want to simplify is a rational
142 # function in a, b, c, ..., with coefficients in ZZ (integers).
143 # (2) means that we have to decide what relations to impose on the
144 # generators. There are two practical problems:
145 # (1) The ideal has to be *prime* (a technical term).
146 # (2) The relations have to be polynomials in the generators.
147 #
148 # We typically have two kinds of generators:
149 # - trigonometric expressions, like sin(x), cos(5*x), etc
150 # - "everything else", like gamma(x), pi, etc.
151 #
152 # Since this function is trigsimp, we will concentrate on what to do with
153 # trigonometric expressions. We can also simplify hyperbolic expressions,
154 # but the extensions should be clear.
155 #
156 # One crucial point is that all *other* generators really should behave
157 # like indeterminates. In particular if (say) "I" is one of them, then
158 # in fact I**2 + 1 = 0 and we may and will compute non-sensical
159 # expressions. However, we can work with a dummy and add the relation
160 # I**2 + 1 = 0 to our ideal, then substitute back in the end.
161 #
162 # Now regarding trigonometric generators. We split them into groups,
163 # according to the argument of the trigonometric functions. We want to
164 # organise this in such a way that most trigonometric identities apply in
165 # the same group. For example, given sin(x), cos(2*x) and cos(y), we would
166 # group as [sin(x), cos(2*x)] and [cos(y)].
167 #
168 # Our prime ideal will be built in three steps:
169 # (1) For each group, compute a "geometrically prime" ideal of relations.
170 # Geometrically prime means that it generates a prime ideal in
171 # CC[gens], not just ZZ[gens].
172 # (2) Take the union of all the generators of the ideals for all groups.
173 # By the geometric primality condition, this is still prime.
174 # (3) Add further inter-group relations which preserve primality.
175 #
176 # Step (1) works as follows. We will isolate common factors in the
177 # argument, so that all our generators are of the form sin(n*x), cos(n*x)
178 # or tan(n*x), with n an integer. Suppose first there are no tan terms.
179 # The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since
180 # X**2 + Y**2 - 1 is irreducible over CC.
181 # Now, if we have a generator sin(n*x), than we can, using trig identities,
182 # express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this
183 # relation to the ideal, preserving geometric primality, since the quotient
184 # ring is unchanged.
185 # Thus we have treated all sin and cos terms.
186 # For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0.
187 # (This requires of course that we already have relations for cos(n*x) and
188 # sin(n*x).) It is not obvious, but it seems that this preserves geometric
189 # primality.
190 # XXX A real proof would be nice. HELP!
191 # Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of
192 # CC[S, C, T]:
193 # - it suffices to show that the projective closure in CP**3 is
194 # irreducible
195 # - using the half-angle substitutions, we can express sin(x), tan(x),
196 # cos(x) as rational functions in tan(x/2)
197 # - from this, we get a rational map from CP**1 to our curve
198 # - this is a morphism, hence the curve is prime
199 #
200 # Step (2) is trivial.
201 #
202 # Step (3) works by adding selected relations of the form
203 # sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is
204 # preserved by the same argument as before.
206 def parse_hints(hints):
207 """Split hints into (n, funcs, iterables, gens)."""
208 n = 1
209 funcs, iterables, gens = [], [], []
210 for e in hints:
211 if isinstance(e, (SYMPY_INTS, Integer)):
212 n = e
213 elif isinstance(e, FunctionClass):
214 funcs.append(e)
215 elif iterable(e):
216 iterables.append((e[0], e[1:]))
217 # XXX sin(x+2y)?
218 # Note: we go through polys so e.g.
219 # sin(-x) -> -sin(x) -> sin(x)
220 gens.extend(parallel_poly_from_expr(
221 [e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens)
222 else:
223 gens.append(e)
224 return n, funcs, iterables, gens
226 def build_ideal(x, terms):
227 """
228 Build generators for our ideal. ``Terms`` is an iterable with elements of
229 the form (fn, coeff), indicating that we have a generator fn(coeff*x).
231 If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed
232 to appear in terms. Similarly for hyperbolic functions. For tan(n*x),
233 sin(n*x) and cos(n*x) are guaranteed.
234 """
235 I = []
236 y = Dummy('y')
237 for fn, coeff in terms:
238 for c, s, t, rel in (
239 [cos, sin, tan, cos(x)**2 + sin(x)**2 - 1],
240 [cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1]):
241 if coeff == 1 and fn in [c, s]:
242 I.append(rel)
243 elif fn == t:
244 I.append(t(coeff*x)*c(coeff*x) - s(coeff*x))
245 elif fn in [c, s]:
246 cn = fn(coeff*y).expand(trig=True).subs(y, x)
247 I.append(fn(coeff*x) - cn)
248 return list(set(I))
250 def analyse_gens(gens, hints):
251 """
252 Analyse the generators ``gens``, using the hints ``hints``.
254 The meaning of ``hints`` is described in the main docstring.
255 Return a new list of generators, and also the ideal we should
256 work with.
257 """
258 # First parse the hints
259 n, funcs, iterables, extragens = parse_hints(hints)
260 debug('n=%s funcs: %s iterables: %s extragens: %s',
261 (funcs, iterables, extragens))
263 # We just add the extragens to gens and analyse them as before
264 gens = list(gens)
265 gens.extend(extragens)
267 # remove duplicates
268 funcs = list(set(funcs))
269 iterables = list(set(iterables))
270 gens = list(set(gens))
272 # all the functions we can do anything with
273 allfuncs = {sin, cos, tan, sinh, cosh, tanh}
274 # sin(3*x) -> ((3, x), sin)
275 trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens
276 if g.func in allfuncs]
277 # Our list of new generators - start with anything that we cannot
278 # work with (i.e. is not a trigonometric term)
279 freegens = [g for g in gens if g.func not in allfuncs]
280 newgens = []
281 trigdict = {}
282 for (coeff, var), fn in trigterms:
283 trigdict.setdefault(var, []).append((coeff, fn))
284 res = [] # the ideal
286 for key, val in trigdict.items():
287 # We have now assembeled a dictionary. Its keys are common
288 # arguments in trigonometric expressions, and values are lists of
289 # pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we
290 # need to deal with fn(coeff*x0). We take the rational gcd of the
291 # coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol",
292 # all other arguments are integral multiples thereof.
293 # We will build an ideal which works with sin(x), cos(x).
294 # If hint tan is provided, also work with tan(x). Moreover, if
295 # n > 1, also work with sin(k*x) for k <= n, and similarly for cos
296 # (and tan if the hint is provided). Finally, any generators which
297 # the ideal does not work with but we need to accommodate (either
298 # because it was in expr or because it was provided as a hint)
299 # we also build into the ideal.
300 # This selection process is expressed in the list ``terms``.
301 # build_ideal then generates the actual relations in our ideal,
302 # from this list.
303 fns = [x[1] for x in val]
304 val = [x[0] for x in val]
305 gcd = reduce(igcd, val)
306 terms = [(fn, v/gcd) for (fn, v) in zip(fns, val)]
307 fs = set(funcs + fns)
308 for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]):
309 if any(x in fs for x in (c, s, t)):
310 fs.add(c)
311 fs.add(s)
312 for fn in fs:
313 for k in range(1, n + 1):
314 terms.append((fn, k))
315 extra = []
316 for fn, v in terms:
317 if fn == tan:
318 extra.append((sin, v))
319 extra.append((cos, v))
320 if fn in [sin, cos] and tan in fs:
321 extra.append((tan, v))
322 if fn == tanh:
323 extra.append((sinh, v))
324 extra.append((cosh, v))
325 if fn in [sinh, cosh] and tanh in fs:
326 extra.append((tanh, v))
327 terms.extend(extra)
328 x = gcd*Mul(*key)
329 r = build_ideal(x, terms)
330 res.extend(r)
331 newgens.extend({fn(v*x) for fn, v in terms})
333 # Add generators for compound expressions from iterables
334 for fn, args in iterables:
335 if fn == tan:
336 # Tan expressions are recovered from sin and cos.
337 iterables.extend([(sin, args), (cos, args)])
338 elif fn == tanh:
339 # Tanh expressions are recovered from sihn and cosh.
340 iterables.extend([(sinh, args), (cosh, args)])
341 else:
342 dummys = symbols('d:%i' % len(args), cls=Dummy)
343 expr = fn( Add(*dummys)).expand(trig=True).subs(list(zip(dummys, args)))
344 res.append(fn(Add(*args)) - expr)
346 if myI in gens:
347 res.append(myI**2 + 1)
348 freegens.remove(myI)
349 newgens.append(myI)
351 return res, freegens, newgens
353 myI = Dummy('I')
354 expr = expr.subs(S.ImaginaryUnit, myI)
355 subs = [(myI, S.ImaginaryUnit)]
357 num, denom = cancel(expr).as_numer_denom()
358 try:
359 (pnum, pdenom), opt = parallel_poly_from_expr([num, denom])
360 except PolificationFailed:
361 return expr
362 debug('initial gens:', opt.gens)
363 ideal, freegens, gens = analyse_gens(opt.gens, hints)
364 debug('ideal:', ideal)
365 debug('new gens:', gens, " -- len", len(gens))
366 debug('free gens:', freegens, " -- len", len(gens))
367 # NOTE we force the domain to be ZZ to stop polys from injecting generators
368 # (which is usually a sign of a bug in the way we build the ideal)
369 if not gens:
370 return expr
371 G = groebner(ideal, order=order, gens=gens, domain=ZZ)
372 debug('groebner basis:', list(G), " -- len", len(G))
374 # If our fraction is a polynomial in the free generators, simplify all
375 # coefficients separately:
377 from sympy.simplify.ratsimp import ratsimpmodprime
379 if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)):
380 num = Poly(num, gens=gens+freegens).eject(*gens)
381 res = []
382 for monom, coeff in num.terms():
383 ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens)
384 # We compute the transitive closure of all generators that can
385 # be reached from our generators through relations in the ideal.
386 changed = True
387 while changed:
388 changed = False
389 for p in ideal:
390 p = Poly(p)
391 if not ourgens.issuperset(p.gens) and \
392 not p.has_only_gens(*set(p.gens).difference(ourgens)):
393 changed = True
394 ourgens.update(p.exclude().gens)
395 # NOTE preserve order!
396 realgens = [x for x in gens if x in ourgens]
397 # The generators of the ideal have now been (implicitly) split
398 # into two groups: those involving ourgens and those that don't.
399 # Since we took the transitive closure above, these two groups
400 # live in subgrings generated by a *disjoint* set of variables.
401 # Any sensible groebner basis algorithm will preserve this disjoint
402 # structure (i.e. the elements of the groebner basis can be split
403 # similarly), and and the two subsets of the groebner basis then
404 # form groebner bases by themselves. (For the smaller generating
405 # sets, of course.)
406 ourG = [g.as_expr() for g in G.polys if
407 g.has_only_gens(*ourgens.intersection(g.gens))]
408 res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \
409 ratsimpmodprime(coeff/denom, ourG, order=order,
410 gens=realgens, quick=quick, domain=ZZ,
411 polynomial=polynomial).subs(subs))
412 return Add(*res)
413 # NOTE The following is simpler and has less assumptions on the
414 # groebner basis algorithm. If the above turns out to be broken,
415 # use this.
416 return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \
417 ratsimpmodprime(coeff/denom, list(G), order=order,
418 gens=gens, quick=quick, domain=ZZ)
419 for monom, coeff in num.terms()])
420 else:
421 return ratsimpmodprime(
422 expr, list(G), order=order, gens=freegens+gens,
423 quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)
426_trigs = (TrigonometricFunction, HyperbolicFunction)
429def _trigsimp_inverse(rv):
431 def check_args(x, y):
432 try:
433 return x.args[0] == y.args[0]
434 except IndexError:
435 return False
437 def f(rv):
438 # for simple functions
439 g = getattr(rv, 'inverse', None)
440 if (g is not None and isinstance(rv.args[0], g()) and
441 isinstance(g()(1), TrigonometricFunction)):
442 return rv.args[0].args[0]
444 # for atan2 simplifications, harder because atan2 has 2 args
445 if isinstance(rv, atan2):
446 y, x = rv.args
447 if _coeff_isneg(y):
448 return -f(atan2(-y, x))
449 elif _coeff_isneg(x):
450 return S.Pi - f(atan2(y, -x))
452 if check_args(x, y):
453 if isinstance(y, sin) and isinstance(x, cos):
454 return x.args[0]
455 if isinstance(y, cos) and isinstance(x, sin):
456 return S.Pi / 2 - x.args[0]
458 return rv
460 return bottom_up(rv, f)
463def trigsimp(expr, inverse=False, **opts):
464 """Returns a reduced expression by using known trig identities.
466 Parameters
467 ==========
469 inverse : bool, optional
470 If ``inverse=True``, it will be assumed that a composition of inverse
471 functions, such as sin and asin, can be cancelled in any order.
472 For example, ``asin(sin(x))`` will yield ``x`` without checking whether
473 x belongs to the set where this relation is true. The default is False.
474 Default : True
476 method : string, optional
477 Specifies the method to use. Valid choices are:
479 - ``'matching'``, default
480 - ``'groebner'``
481 - ``'combined'``
482 - ``'fu'``
483 - ``'old'``
485 If ``'matching'``, simplify the expression recursively by targeting
486 common patterns. If ``'groebner'``, apply an experimental groebner
487 basis algorithm. In this case further options are forwarded to
488 ``trigsimp_groebner``, please refer to
489 its docstring. If ``'combined'``, it first runs the groebner basis
490 algorithm with small default parameters, then runs the ``'matching'``
491 algorithm. If ``'fu'``, run the collection of trigonometric
492 transformations described by Fu, et al. (see the
493 :py:func:`~sympy.simplify.fu.fu` docstring). If ``'old'``, the original
494 SymPy trig simplification function is run.
495 opts :
496 Optional keyword arguments passed to the method. See each method's
497 function docstring for details.
499 Examples
500 ========
502 >>> from sympy import trigsimp, sin, cos, log
503 >>> from sympy.abc import x
504 >>> e = 2*sin(x)**2 + 2*cos(x)**2
505 >>> trigsimp(e)
506 2
508 Simplification occurs wherever trigonometric functions are located.
510 >>> trigsimp(log(e))
511 log(2)
513 Using ``method='groebner'`` (or ``method='combined'``) might lead to
514 greater simplification.
516 The old trigsimp routine can be accessed as with method ``method='old'``.
518 >>> from sympy import coth, tanh
519 >>> t = 3*tanh(x)**7 - 2/coth(x)**7
520 >>> trigsimp(t, method='old') == t
521 True
522 >>> trigsimp(t)
523 tanh(x)**7
525 """
526 from sympy.simplify.fu import fu
528 expr = sympify(expr)
530 _eval_trigsimp = getattr(expr, '_eval_trigsimp', None)
531 if _eval_trigsimp is not None:
532 return _eval_trigsimp(**opts)
534 old = opts.pop('old', False)
535 if not old:
536 opts.pop('deep', None)
537 opts.pop('recursive', None)
538 method = opts.pop('method', 'matching')
539 else:
540 method = 'old'
542 def groebnersimp(ex, **opts):
543 def traverse(e):
544 if e.is_Atom:
545 return e
546 args = [traverse(x) for x in e.args]
547 if e.is_Function or e.is_Pow:
548 args = [trigsimp_groebner(x, **opts) for x in args]
549 return e.func(*args)
550 new = traverse(ex)
551 if not isinstance(new, Expr):
552 return new
553 return trigsimp_groebner(new, **opts)
555 trigsimpfunc = {
556 'fu': (lambda x: fu(x, **opts)),
557 'matching': (lambda x: futrig(x)),
558 'groebner': (lambda x: groebnersimp(x, **opts)),
559 'combined': (lambda x: futrig(groebnersimp(x,
560 polynomial=True, hints=[2, tan]))),
561 'old': lambda x: trigsimp_old(x, **opts),
562 }[method]
564 expr_simplified = trigsimpfunc(expr)
565 if inverse:
566 expr_simplified = _trigsimp_inverse(expr_simplified)
568 return expr_simplified
571def exptrigsimp(expr):
572 """
573 Simplifies exponential / trigonometric / hyperbolic functions.
575 Examples
576 ========
578 >>> from sympy import exptrigsimp, exp, cosh, sinh
579 >>> from sympy.abc import z
581 >>> exptrigsimp(exp(z) + exp(-z))
582 2*cosh(z)
583 >>> exptrigsimp(cosh(z) - sinh(z))
584 exp(-z)
585 """
586 from sympy.simplify.fu import hyper_as_trig, TR2i
588 def exp_trig(e):
589 # select the better of e, and e rewritten in terms of exp or trig
590 # functions
591 choices = [e]
592 if e.has(*_trigs):
593 choices.append(e.rewrite(exp))
594 choices.append(e.rewrite(cos))
595 return min(*choices, key=count_ops)
596 newexpr = bottom_up(expr, exp_trig)
598 def f(rv):
599 if not rv.is_Mul:
600 return rv
601 commutative_part, noncommutative_part = rv.args_cnc()
602 # Since as_powers_dict loses order information,
603 # if there is more than one noncommutative factor,
604 # it should only be used to simplify the commutative part.
605 if (len(noncommutative_part) > 1):
606 return f(Mul(*commutative_part))*Mul(*noncommutative_part)
607 rvd = rv.as_powers_dict()
608 newd = rvd.copy()
610 def signlog(expr, sign=S.One):
611 if expr is S.Exp1:
612 return sign, S.One
613 elif isinstance(expr, exp) or (expr.is_Pow and expr.base == S.Exp1):
614 return sign, expr.exp
615 elif sign is S.One:
616 return signlog(-expr, sign=-S.One)
617 else:
618 return None, None
620 ee = rvd[S.Exp1]
621 for k in rvd:
622 if k.is_Add and len(k.args) == 2:
623 # k == c*(1 + sign*E**x)
624 c = k.args[0]
625 sign, x = signlog(k.args[1]/c)
626 if not x:
627 continue
628 m = rvd[k]
629 newd[k] -= m
630 if ee == -x*m/2:
631 # sinh and cosh
632 newd[S.Exp1] -= ee
633 ee = 0
634 if sign == 1:
635 newd[2*c*cosh(x/2)] += m
636 else:
637 newd[-2*c*sinh(x/2)] += m
638 elif newd[1 - sign*S.Exp1**x] == -m:
639 # tanh
640 del newd[1 - sign*S.Exp1**x]
641 if sign == 1:
642 newd[-c/tanh(x/2)] += m
643 else:
644 newd[-c*tanh(x/2)] += m
645 else:
646 newd[1 + sign*S.Exp1**x] += m
647 newd[c] += m
649 return Mul(*[k**newd[k] for k in newd])
650 newexpr = bottom_up(newexpr, f)
652 # sin/cos and sinh/cosh ratios to tan and tanh, respectively
653 if newexpr.has(HyperbolicFunction):
654 e, f = hyper_as_trig(newexpr)
655 newexpr = f(TR2i(e))
656 if newexpr.has(TrigonometricFunction):
657 newexpr = TR2i(newexpr)
659 # can we ever generate an I where there was none previously?
660 if not (newexpr.has(I) and not expr.has(I)):
661 expr = newexpr
662 return expr
664#-------------------- the old trigsimp routines ---------------------
666def trigsimp_old(expr, *, first=True, **opts):
667 """
668 Reduces expression by using known trig identities.
670 Notes
671 =====
673 deep:
674 - Apply trigsimp inside all objects with arguments
676 recursive:
677 - Use common subexpression elimination (cse()) and apply
678 trigsimp recursively (this is quite expensive if the
679 expression is large)
681 method:
682 - Determine the method to use. Valid choices are 'matching' (default),
683 'groebner', 'combined', 'fu' and 'futrig'. If 'matching', simplify the
684 expression recursively by pattern matching. If 'groebner', apply an
685 experimental groebner basis algorithm. In this case further options
686 are forwarded to ``trigsimp_groebner``, please refer to its docstring.
687 If 'combined', first run the groebner basis algorithm with small
688 default parameters, then run the 'matching' algorithm. 'fu' runs the
689 collection of trigonometric transformations described by Fu, et al.
690 (see the `fu` docstring) while `futrig` runs a subset of Fu-transforms
691 that mimic the behavior of `trigsimp`.
693 compare:
694 - show input and output from `trigsimp` and `futrig` when different,
695 but returns the `trigsimp` value.
697 Examples
698 ========
700 >>> from sympy import trigsimp, sin, cos, log, cot
701 >>> from sympy.abc import x
702 >>> e = 2*sin(x)**2 + 2*cos(x)**2
703 >>> trigsimp(e, old=True)
704 2
705 >>> trigsimp(log(e), old=True)
706 log(2*sin(x)**2 + 2*cos(x)**2)
707 >>> trigsimp(log(e), deep=True, old=True)
708 log(2)
710 Using `method="groebner"` (or `"combined"`) can sometimes lead to a lot
711 more simplification:
713 >>> e = (-sin(x) + 1)/cos(x) + cos(x)/(-sin(x) + 1)
714 >>> trigsimp(e, old=True)
715 (1 - sin(x))/cos(x) + cos(x)/(1 - sin(x))
716 >>> trigsimp(e, method="groebner", old=True)
717 2/cos(x)
719 >>> trigsimp(1/cot(x)**2, compare=True, old=True)
720 futrig: tan(x)**2
721 cot(x)**(-2)
723 """
724 old = expr
725 if first:
726 if not expr.has(*_trigs):
727 return expr
729 trigsyms = set().union(*[t.free_symbols for t in expr.atoms(*_trigs)])
730 if len(trigsyms) > 1:
731 from sympy.simplify.simplify import separatevars
733 d = separatevars(expr)
734 if d.is_Mul:
735 d = separatevars(d, dict=True) or d
736 if isinstance(d, dict):
737 expr = 1
738 for k, v in d.items():
739 # remove hollow factoring
740 was = v
741 v = expand_mul(v)
742 opts['first'] = False
743 vnew = trigsimp(v, **opts)
744 if vnew == v:
745 vnew = was
746 expr *= vnew
747 old = expr
748 else:
749 if d.is_Add:
750 for s in trigsyms:
751 r, e = expr.as_independent(s)
752 if r:
753 opts['first'] = False
754 expr = r + trigsimp(e, **opts)
755 if not expr.is_Add:
756 break
757 old = expr
759 recursive = opts.pop('recursive', False)
760 deep = opts.pop('deep', False)
761 method = opts.pop('method', 'matching')
763 def groebnersimp(ex, deep, **opts):
764 def traverse(e):
765 if e.is_Atom:
766 return e
767 args = [traverse(x) for x in e.args]
768 if e.is_Function or e.is_Pow:
769 args = [trigsimp_groebner(x, **opts) for x in args]
770 return e.func(*args)
771 if deep:
772 ex = traverse(ex)
773 return trigsimp_groebner(ex, **opts)
775 trigsimpfunc = {
776 'matching': (lambda x, d: _trigsimp(x, d)),
777 'groebner': (lambda x, d: groebnersimp(x, d, **opts)),
778 'combined': (lambda x, d: _trigsimp(groebnersimp(x,
779 d, polynomial=True, hints=[2, tan]),
780 d))
781 }[method]
783 if recursive:
784 w, g = cse(expr)
785 g = trigsimpfunc(g[0], deep)
787 for sub in reversed(w):
788 g = g.subs(sub[0], sub[1])
789 g = trigsimpfunc(g, deep)
790 result = g
791 else:
792 result = trigsimpfunc(expr, deep)
794 if opts.get('compare', False):
795 f = futrig(old)
796 if f != result:
797 print('\tfutrig:', f)
799 return result
802def _dotrig(a, b):
803 """Helper to tell whether ``a`` and ``b`` have the same sorts
804 of symbols in them -- no need to test hyperbolic patterns against
805 expressions that have no hyperbolics in them."""
806 return a.func == b.func and (
807 a.has(TrigonometricFunction) and b.has(TrigonometricFunction) or
808 a.has(HyperbolicFunction) and b.has(HyperbolicFunction))
811_trigpat = None
812def _trigpats():
813 global _trigpat
814 a, b, c = symbols('a b c', cls=Wild)
815 d = Wild('d', commutative=False)
817 # for the simplifications like sinh/cosh -> tanh:
818 # DO NOT REORDER THE FIRST 14 since these are assumed to be in this
819 # order in _match_div_rewrite.
820 matchers_division = (
821 (a*sin(b)**c/cos(b)**c, a*tan(b)**c, sin(b), cos(b)),
822 (a*tan(b)**c*cos(b)**c, a*sin(b)**c, sin(b), cos(b)),
823 (a*cot(b)**c*sin(b)**c, a*cos(b)**c, sin(b), cos(b)),
824 (a*tan(b)**c/sin(b)**c, a/cos(b)**c, sin(b), cos(b)),
825 (a*cot(b)**c/cos(b)**c, a/sin(b)**c, sin(b), cos(b)),
826 (a*cot(b)**c*tan(b)**c, a, sin(b), cos(b)),
827 (a*(cos(b) + 1)**c*(cos(b) - 1)**c,
828 a*(-sin(b)**2)**c, cos(b) + 1, cos(b) - 1),
829 (a*(sin(b) + 1)**c*(sin(b) - 1)**c,
830 a*(-cos(b)**2)**c, sin(b) + 1, sin(b) - 1),
832 (a*sinh(b)**c/cosh(b)**c, a*tanh(b)**c, S.One, S.One),
833 (a*tanh(b)**c*cosh(b)**c, a*sinh(b)**c, S.One, S.One),
834 (a*coth(b)**c*sinh(b)**c, a*cosh(b)**c, S.One, S.One),
835 (a*tanh(b)**c/sinh(b)**c, a/cosh(b)**c, S.One, S.One),
836 (a*coth(b)**c/cosh(b)**c, a/sinh(b)**c, S.One, S.One),
837 (a*coth(b)**c*tanh(b)**c, a, S.One, S.One),
839 (c*(tanh(a) + tanh(b))/(1 + tanh(a)*tanh(b)),
840 tanh(a + b)*c, S.One, S.One),
841 )
843 matchers_add = (
844 (c*sin(a)*cos(b) + c*cos(a)*sin(b) + d, sin(a + b)*c + d),
845 (c*cos(a)*cos(b) - c*sin(a)*sin(b) + d, cos(a + b)*c + d),
846 (c*sin(a)*cos(b) - c*cos(a)*sin(b) + d, sin(a - b)*c + d),
847 (c*cos(a)*cos(b) + c*sin(a)*sin(b) + d, cos(a - b)*c + d),
848 (c*sinh(a)*cosh(b) + c*sinh(b)*cosh(a) + d, sinh(a + b)*c + d),
849 (c*cosh(a)*cosh(b) + c*sinh(a)*sinh(b) + d, cosh(a + b)*c + d),
850 )
852 # for cos(x)**2 + sin(x)**2 -> 1
853 matchers_identity = (
854 (a*sin(b)**2, a - a*cos(b)**2),
855 (a*tan(b)**2, a*(1/cos(b))**2 - a),
856 (a*cot(b)**2, a*(1/sin(b))**2 - a),
857 (a*sin(b + c), a*(sin(b)*cos(c) + sin(c)*cos(b))),
858 (a*cos(b + c), a*(cos(b)*cos(c) - sin(b)*sin(c))),
859 (a*tan(b + c), a*((tan(b) + tan(c))/(1 - tan(b)*tan(c)))),
861 (a*sinh(b)**2, a*cosh(b)**2 - a),
862 (a*tanh(b)**2, a - a*(1/cosh(b))**2),
863 (a*coth(b)**2, a + a*(1/sinh(b))**2),
864 (a*sinh(b + c), a*(sinh(b)*cosh(c) + sinh(c)*cosh(b))),
865 (a*cosh(b + c), a*(cosh(b)*cosh(c) + sinh(b)*sinh(c))),
866 (a*tanh(b + c), a*((tanh(b) + tanh(c))/(1 + tanh(b)*tanh(c)))),
868 )
870 # Reduce any lingering artifacts, such as sin(x)**2 changing
871 # to 1-cos(x)**2 when sin(x)**2 was "simpler"
872 artifacts = (
873 (a - a*cos(b)**2 + c, a*sin(b)**2 + c, cos),
874 (a - a*(1/cos(b))**2 + c, -a*tan(b)**2 + c, cos),
875 (a - a*(1/sin(b))**2 + c, -a*cot(b)**2 + c, sin),
877 (a - a*cosh(b)**2 + c, -a*sinh(b)**2 + c, cosh),
878 (a - a*(1/cosh(b))**2 + c, a*tanh(b)**2 + c, cosh),
879 (a + a*(1/sinh(b))**2 + c, a*coth(b)**2 + c, sinh),
881 # same as above but with noncommutative prefactor
882 (a*d - a*d*cos(b)**2 + c, a*d*sin(b)**2 + c, cos),
883 (a*d - a*d*(1/cos(b))**2 + c, -a*d*tan(b)**2 + c, cos),
884 (a*d - a*d*(1/sin(b))**2 + c, -a*d*cot(b)**2 + c, sin),
886 (a*d - a*d*cosh(b)**2 + c, -a*d*sinh(b)**2 + c, cosh),
887 (a*d - a*d*(1/cosh(b))**2 + c, a*d*tanh(b)**2 + c, cosh),
888 (a*d + a*d*(1/sinh(b))**2 + c, a*d*coth(b)**2 + c, sinh),
889 )
891 _trigpat = (a, b, c, d, matchers_division, matchers_add,
892 matchers_identity, artifacts)
893 return _trigpat
896def _replace_mul_fpowxgpow(expr, f, g, rexp, h, rexph):
897 """Helper for _match_div_rewrite.
899 Replace f(b_)**c_*g(b_)**(rexp(c_)) with h(b)**rexph(c) if f(b_)
900 and g(b_) are both positive or if c_ is an integer.
901 """
902 # assert expr.is_Mul and expr.is_commutative and f != g
903 fargs = defaultdict(int)
904 gargs = defaultdict(int)
905 args = []
906 for x in expr.args:
907 if x.is_Pow or x.func in (f, g):
908 b, e = x.as_base_exp()
909 if b.is_positive or e.is_integer:
910 if b.func == f:
911 fargs[b.args[0]] += e
912 continue
913 elif b.func == g:
914 gargs[b.args[0]] += e
915 continue
916 args.append(x)
917 common = set(fargs) & set(gargs)
918 hit = False
919 while common:
920 key = common.pop()
921 fe = fargs.pop(key)
922 ge = gargs.pop(key)
923 if fe == rexp(ge):
924 args.append(h(key)**rexph(fe))
925 hit = True
926 else:
927 fargs[key] = fe
928 gargs[key] = ge
929 if not hit:
930 return expr
931 while fargs:
932 key, e = fargs.popitem()
933 args.append(f(key)**e)
934 while gargs:
935 key, e = gargs.popitem()
936 args.append(g(key)**e)
937 return Mul(*args)
940_idn = lambda x: x
941_midn = lambda x: -x
942_one = lambda x: S.One
944def _match_div_rewrite(expr, i):
945 """helper for __trigsimp"""
946 if i == 0:
947 expr = _replace_mul_fpowxgpow(expr, sin, cos,
948 _midn, tan, _idn)
949 elif i == 1:
950 expr = _replace_mul_fpowxgpow(expr, tan, cos,
951 _idn, sin, _idn)
952 elif i == 2:
953 expr = _replace_mul_fpowxgpow(expr, cot, sin,
954 _idn, cos, _idn)
955 elif i == 3:
956 expr = _replace_mul_fpowxgpow(expr, tan, sin,
957 _midn, cos, _midn)
958 elif i == 4:
959 expr = _replace_mul_fpowxgpow(expr, cot, cos,
960 _midn, sin, _midn)
961 elif i == 5:
962 expr = _replace_mul_fpowxgpow(expr, cot, tan,
963 _idn, _one, _idn)
964 # i in (6, 7) is skipped
965 elif i == 8:
966 expr = _replace_mul_fpowxgpow(expr, sinh, cosh,
967 _midn, tanh, _idn)
968 elif i == 9:
969 expr = _replace_mul_fpowxgpow(expr, tanh, cosh,
970 _idn, sinh, _idn)
971 elif i == 10:
972 expr = _replace_mul_fpowxgpow(expr, coth, sinh,
973 _idn, cosh, _idn)
974 elif i == 11:
975 expr = _replace_mul_fpowxgpow(expr, tanh, sinh,
976 _midn, cosh, _midn)
977 elif i == 12:
978 expr = _replace_mul_fpowxgpow(expr, coth, cosh,
979 _midn, sinh, _midn)
980 elif i == 13:
981 expr = _replace_mul_fpowxgpow(expr, coth, tanh,
982 _idn, _one, _idn)
983 else:
984 return None
985 return expr
988def _trigsimp(expr, deep=False):
989 # protect the cache from non-trig patterns; we only allow
990 # trig patterns to enter the cache
991 if expr.has(*_trigs):
992 return __trigsimp(expr, deep)
993 return expr
996@cacheit
997def __trigsimp(expr, deep=False):
998 """recursive helper for trigsimp"""
999 from sympy.simplify.fu import TR10i
1001 if _trigpat is None:
1002 _trigpats()
1003 a, b, c, d, matchers_division, matchers_add, \
1004 matchers_identity, artifacts = _trigpat
1006 if expr.is_Mul:
1007 # do some simplifications like sin/cos -> tan:
1008 if not expr.is_commutative:
1009 com, nc = expr.args_cnc()
1010 expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc)
1011 else:
1012 for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division):
1013 if not _dotrig(expr, pattern):
1014 continue
1016 newexpr = _match_div_rewrite(expr, i)
1017 if newexpr is not None:
1018 if newexpr != expr:
1019 expr = newexpr
1020 break
1021 else:
1022 continue
1024 # use SymPy matching instead
1025 res = expr.match(pattern)
1026 if res and res.get(c, 0):
1027 if not res[c].is_integer:
1028 ok = ok1.subs(res)
1029 if not ok.is_positive:
1030 continue
1031 ok = ok2.subs(res)
1032 if not ok.is_positive:
1033 continue
1034 # if "a" contains any of trig or hyperbolic funcs with
1035 # argument "b" then skip the simplification
1036 if any(w.args[0] == res[b] for w in res[a].atoms(
1037 TrigonometricFunction, HyperbolicFunction)):
1038 continue
1039 # simplify and finish:
1040 expr = simp.subs(res)
1041 break # process below
1043 if expr.is_Add:
1044 args = []
1045 for term in expr.args:
1046 if not term.is_commutative:
1047 com, nc = term.args_cnc()
1048 nc = Mul._from_args(nc)
1049 term = Mul._from_args(com)
1050 else:
1051 nc = S.One
1052 term = _trigsimp(term, deep)
1053 for pattern, result in matchers_identity:
1054 res = term.match(pattern)
1055 if res is not None:
1056 term = result.subs(res)
1057 break
1058 args.append(term*nc)
1059 if args != expr.args:
1060 expr = Add(*args)
1061 expr = min(expr, expand(expr), key=count_ops)
1062 if expr.is_Add:
1063 for pattern, result in matchers_add:
1064 if not _dotrig(expr, pattern):
1065 continue
1066 expr = TR10i(expr)
1067 if expr.has(HyperbolicFunction):
1068 res = expr.match(pattern)
1069 # if "d" contains any trig or hyperbolic funcs with
1070 # argument "a" or "b" then skip the simplification;
1071 # this isn't perfect -- see tests
1072 if res is None or not (a in res and b in res) or any(
1073 w.args[0] in (res[a], res[b]) for w in res[d].atoms(
1074 TrigonometricFunction, HyperbolicFunction)):
1075 continue
1076 expr = result.subs(res)
1077 break
1079 # Reduce any lingering artifacts, such as sin(x)**2 changing
1080 # to 1 - cos(x)**2 when sin(x)**2 was "simpler"
1081 for pattern, result, ex in artifacts:
1082 if not _dotrig(expr, pattern):
1083 continue
1084 # Substitute a new wild that excludes some function(s)
1085 # to help influence a better match. This is because
1086 # sometimes, for example, 'a' would match sec(x)**2
1087 a_t = Wild('a', exclude=[ex])
1088 pattern = pattern.subs(a, a_t)
1089 result = result.subs(a, a_t)
1091 m = expr.match(pattern)
1092 was = None
1093 while m and was != expr:
1094 was = expr
1095 if m[a_t] == 0 or \
1096 -m[a_t] in m[c].args or m[a_t] + m[c] == 0:
1097 break
1098 if d in m and m[a_t]*m[d] + m[c] == 0:
1099 break
1100 expr = result.subs(m)
1101 m = expr.match(pattern)
1102 m.setdefault(c, S.Zero)
1104 elif expr.is_Mul or expr.is_Pow or deep and expr.args:
1105 expr = expr.func(*[_trigsimp(a, deep) for a in expr.args])
1107 try:
1108 if not expr.has(*_trigs):
1109 raise TypeError
1110 e = expr.atoms(exp)
1111 new = expr.rewrite(exp, deep=deep)
1112 if new == e:
1113 raise TypeError
1114 fnew = factor(new)
1115 if fnew != new:
1116 new = sorted([new, factor(new)], key=count_ops)[0]
1117 # if all exp that were introduced disappeared then accept it
1118 if not (new.atoms(exp) - e):
1119 expr = new
1120 except TypeError:
1121 pass
1123 return expr
1124#------------------- end of old trigsimp routines --------------------
1127def futrig(e, *, hyper=True, **kwargs):
1128 """Return simplified ``e`` using Fu-like transformations.
1129 This is not the "Fu" algorithm. This is called by default
1130 from ``trigsimp``. By default, hyperbolics subexpressions
1131 will be simplified, but this can be disabled by setting
1132 ``hyper=False``.
1134 Examples
1135 ========
1137 >>> from sympy import trigsimp, tan, sinh, tanh
1138 >>> from sympy.simplify.trigsimp import futrig
1139 >>> from sympy.abc import x
1140 >>> trigsimp(1/tan(x)**2)
1141 tan(x)**(-2)
1143 >>> futrig(sinh(x)/tanh(x))
1144 cosh(x)
1146 """
1147 from sympy.simplify.fu import hyper_as_trig
1149 e = sympify(e)
1151 if not isinstance(e, Basic):
1152 return e
1154 if not e.args:
1155 return e
1157 old = e
1158 e = bottom_up(e, _futrig)
1160 if hyper and e.has(HyperbolicFunction):
1161 e, f = hyper_as_trig(e)
1162 e = f(bottom_up(e, _futrig))
1164 if e != old and e.is_Mul and e.args[0].is_Rational:
1165 # redistribute leading coeff on 2-arg Add
1166 e = Mul(*e.as_coeff_Mul())
1167 return e
1170def _futrig(e):
1171 """Helper for futrig."""
1172 from sympy.simplify.fu import (
1173 TR1, TR2, TR3, TR2i, TR10, L, TR10i,
1174 TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, _TR11, TR14, TR22,
1175 TR12)
1177 if not e.has(TrigonometricFunction):
1178 return e
1180 if e.is_Mul:
1181 coeff, e = e.as_independent(TrigonometricFunction)
1182 else:
1183 coeff = None
1185 Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add)
1186 trigs = lambda x: x.has(TrigonometricFunction)
1188 tree = [identity,
1189 (
1190 TR3, # canonical angles
1191 TR1, # sec-csc -> cos-sin
1192 TR12, # expand tan of sum
1193 lambda x: _eapply(factor, x, trigs),
1194 TR2, # tan-cot -> sin-cos
1195 [identity, lambda x: _eapply(_mexpand, x, trigs)],
1196 TR2i, # sin-cos ratio -> tan
1197 lambda x: _eapply(lambda i: factor(i.normal()), x, trigs),
1198 TR14, # factored identities
1199 TR5, # sin-pow -> cos_pow
1200 TR10, # sin-cos of sums -> sin-cos prod
1201 TR11, _TR11, TR6, # reduce double angles and rewrite cos pows
1202 lambda x: _eapply(factor, x, trigs),
1203 TR14, # factored powers of identities
1204 [identity, lambda x: _eapply(_mexpand, x, trigs)],
1205 TR10i, # sin-cos products > sin-cos of sums
1206 TRmorrie,
1207 [identity, TR8], # sin-cos products -> sin-cos of sums
1208 [identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan
1209 [
1210 lambda x: _eapply(expand_mul, TR5(x), trigs),
1211 lambda x: _eapply(
1212 expand_mul, TR15(x), trigs)], # pos/neg powers of sin
1213 [
1214 lambda x: _eapply(expand_mul, TR6(x), trigs),
1215 lambda x: _eapply(
1216 expand_mul, TR16(x), trigs)], # pos/neg powers of cos
1217 TR111, # tan, sin, cos to neg power -> cot, csc, sec
1218 [identity, TR2i], # sin-cos ratio to tan
1219 [identity, lambda x: _eapply(
1220 expand_mul, TR22(x), trigs)], # tan-cot to sec-csc
1221 TR1, TR2, TR2i,
1222 [identity, lambda x: _eapply(
1223 factor_terms, TR12(x), trigs)], # expand tan of sum
1224 )]
1225 e = greedy(tree, objective=Lops)(e)
1227 if coeff is not None:
1228 e = coeff * e
1230 return e
1233def _is_Expr(e):
1234 """_eapply helper to tell whether ``e`` and all its args
1235 are Exprs."""
1236 if isinstance(e, Derivative):
1237 return _is_Expr(e.expr)
1238 if not isinstance(e, Expr):
1239 return False
1240 return all(_is_Expr(i) for i in e.args)
1243def _eapply(func, e, cond=None):
1244 """Apply ``func`` to ``e`` if all args are Exprs else only
1245 apply it to those args that *are* Exprs."""
1246 if not isinstance(e, Expr):
1247 return e
1248 if _is_Expr(e) or not e.args:
1249 return func(e)
1250 return e.func(*[
1251 _eapply(func, ei) if (cond is None or cond(ei)) else ei
1252 for ei in e.args])