Coverage for /usr/lib/python3/dist-packages/sympy/simplify/fu.py: 7%
971 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
3from sympy.core.add import Add
4from sympy.core.expr import Expr
5from sympy.core.exprtools import Factors, gcd_terms, factor_terms
6from sympy.core.function import expand_mul
7from sympy.core.mul import Mul
8from sympy.core.numbers import pi, I
9from sympy.core.power import Pow
10from sympy.core.singleton import S
11from sympy.core.sorting import ordered
12from sympy.core.symbol import Dummy
13from sympy.core.sympify import sympify
14from sympy.core.traversal import bottom_up
15from sympy.functions.combinatorial.factorials import binomial
16from sympy.functions.elementary.hyperbolic import (
17 cosh, sinh, tanh, coth, sech, csch, HyperbolicFunction)
18from sympy.functions.elementary.trigonometric import (
19 cos, sin, tan, cot, sec, csc, sqrt, TrigonometricFunction)
20from sympy.ntheory.factor_ import perfect_power
21from sympy.polys.polytools import factor
22from sympy.strategies.tree import greedy
23from sympy.strategies.core import identity, debug
25from sympy import SYMPY_DEBUG
28# ================== Fu-like tools ===========================
31def TR0(rv):
32 """Simplification of rational polynomials, trying to simplify
33 the expression, e.g. combine things like 3*x + 2*x, etc....
34 """
35 # although it would be nice to use cancel, it doesn't work
36 # with noncommutatives
37 return rv.normal().factor().expand()
40def TR1(rv):
41 """Replace sec, csc with 1/cos, 1/sin
43 Examples
44 ========
46 >>> from sympy.simplify.fu import TR1, sec, csc
47 >>> from sympy.abc import x
48 >>> TR1(2*csc(x) + sec(x))
49 1/cos(x) + 2/sin(x)
50 """
52 def f(rv):
53 if isinstance(rv, sec):
54 a = rv.args[0]
55 return S.One/cos(a)
56 elif isinstance(rv, csc):
57 a = rv.args[0]
58 return S.One/sin(a)
59 return rv
61 return bottom_up(rv, f)
64def TR2(rv):
65 """Replace tan and cot with sin/cos and cos/sin
67 Examples
68 ========
70 >>> from sympy.simplify.fu import TR2
71 >>> from sympy.abc import x
72 >>> from sympy import tan, cot, sin, cos
73 >>> TR2(tan(x))
74 sin(x)/cos(x)
75 >>> TR2(cot(x))
76 cos(x)/sin(x)
77 >>> TR2(tan(tan(x) - sin(x)/cos(x)))
78 0
80 """
82 def f(rv):
83 if isinstance(rv, tan):
84 a = rv.args[0]
85 return sin(a)/cos(a)
86 elif isinstance(rv, cot):
87 a = rv.args[0]
88 return cos(a)/sin(a)
89 return rv
91 return bottom_up(rv, f)
94def TR2i(rv, half=False):
95 """Converts ratios involving sin and cos as follows::
96 sin(x)/cos(x) -> tan(x)
97 sin(x)/(cos(x) + 1) -> tan(x/2) if half=True
99 Examples
100 ========
102 >>> from sympy.simplify.fu import TR2i
103 >>> from sympy.abc import x, a
104 >>> from sympy import sin, cos
105 >>> TR2i(sin(x)/cos(x))
106 tan(x)
108 Powers of the numerator and denominator are also recognized
110 >>> TR2i(sin(x)**2/(cos(x) + 1)**2, half=True)
111 tan(x/2)**2
113 The transformation does not take place unless assumptions allow
114 (i.e. the base must be positive or the exponent must be an integer
115 for both numerator and denominator)
117 >>> TR2i(sin(x)**a/(cos(x) + 1)**a)
118 sin(x)**a/(cos(x) + 1)**a
120 """
122 def f(rv):
123 if not rv.is_Mul:
124 return rv
126 n, d = rv.as_numer_denom()
127 if n.is_Atom or d.is_Atom:
128 return rv
130 def ok(k, e):
131 # initial filtering of factors
132 return (
133 (e.is_integer or k.is_positive) and (
134 k.func in (sin, cos) or (half and
135 k.is_Add and
136 len(k.args) >= 2 and
137 any(any(isinstance(ai, cos) or ai.is_Pow and ai.base is cos
138 for ai in Mul.make_args(a)) for a in k.args))))
140 n = n.as_powers_dict()
141 ndone = [(k, n.pop(k)) for k in list(n.keys()) if not ok(k, n[k])]
142 if not n:
143 return rv
145 d = d.as_powers_dict()
146 ddone = [(k, d.pop(k)) for k in list(d.keys()) if not ok(k, d[k])]
147 if not d:
148 return rv
150 # factoring if necessary
152 def factorize(d, ddone):
153 newk = []
154 for k in d:
155 if k.is_Add and len(k.args) > 1:
156 knew = factor(k) if half else factor_terms(k)
157 if knew != k:
158 newk.append((k, knew))
159 if newk:
160 for i, (k, knew) in enumerate(newk):
161 del d[k]
162 newk[i] = knew
163 newk = Mul(*newk).as_powers_dict()
164 for k in newk:
165 v = d[k] + newk[k]
166 if ok(k, v):
167 d[k] = v
168 else:
169 ddone.append((k, v))
170 del newk
171 factorize(n, ndone)
172 factorize(d, ddone)
174 # joining
175 t = []
176 for k in n:
177 if isinstance(k, sin):
178 a = cos(k.args[0], evaluate=False)
179 if a in d and d[a] == n[k]:
180 t.append(tan(k.args[0])**n[k])
181 n[k] = d[a] = None
182 elif half:
183 a1 = 1 + a
184 if a1 in d and d[a1] == n[k]:
185 t.append((tan(k.args[0]/2))**n[k])
186 n[k] = d[a1] = None
187 elif isinstance(k, cos):
188 a = sin(k.args[0], evaluate=False)
189 if a in d and d[a] == n[k]:
190 t.append(tan(k.args[0])**-n[k])
191 n[k] = d[a] = None
192 elif half and k.is_Add and k.args[0] is S.One and \
193 isinstance(k.args[1], cos):
194 a = sin(k.args[1].args[0], evaluate=False)
195 if a in d and d[a] == n[k] and (d[a].is_integer or \
196 a.is_positive):
197 t.append(tan(a.args[0]/2)**-n[k])
198 n[k] = d[a] = None
200 if t:
201 rv = Mul(*(t + [b**e for b, e in n.items() if e]))/\
202 Mul(*[b**e for b, e in d.items() if e])
203 rv *= Mul(*[b**e for b, e in ndone])/Mul(*[b**e for b, e in ddone])
205 return rv
207 return bottom_up(rv, f)
210def TR3(rv):
211 """Induced formula: example sin(-a) = -sin(a)
213 Examples
214 ========
216 >>> from sympy.simplify.fu import TR3
217 >>> from sympy.abc import x, y
218 >>> from sympy import pi
219 >>> from sympy import cos
220 >>> TR3(cos(y - x*(y - x)))
221 cos(x*(x - y) + y)
222 >>> cos(pi/2 + x)
223 -sin(x)
224 >>> cos(30*pi/2 + x)
225 -cos(x)
227 """
228 from sympy.simplify.simplify import signsimp
230 # Negative argument (already automatic for funcs like sin(-x) -> -sin(x)
231 # but more complicated expressions can use it, too). Also, trig angles
232 # between pi/4 and pi/2 are not reduced to an angle between 0 and pi/4.
233 # The following are automatically handled:
234 # Argument of type: pi/2 +/- angle
235 # Argument of type: pi +/- angle
236 # Argument of type : 2k*pi +/- angle
238 def f(rv):
239 if not isinstance(rv, TrigonometricFunction):
240 return rv
241 rv = rv.func(signsimp(rv.args[0]))
242 if not isinstance(rv, TrigonometricFunction):
243 return rv
244 if (rv.args[0] - S.Pi/4).is_positive is (S.Pi/2 - rv.args[0]).is_positive is True:
245 fmap = {cos: sin, sin: cos, tan: cot, cot: tan, sec: csc, csc: sec}
246 rv = fmap[type(rv)](S.Pi/2 - rv.args[0])
247 return rv
249 return bottom_up(rv, f)
252def TR4(rv):
253 """Identify values of special angles.
255 a= 0 pi/6 pi/4 pi/3 pi/2
256 ----------------------------------------------------
257 sin(a) 0 1/2 sqrt(2)/2 sqrt(3)/2 1
258 cos(a) 1 sqrt(3)/2 sqrt(2)/2 1/2 0
259 tan(a) 0 sqt(3)/3 1 sqrt(3) --
261 Examples
262 ========
264 >>> from sympy import pi
265 >>> from sympy import cos, sin, tan, cot
266 >>> for s in (0, pi/6, pi/4, pi/3, pi/2):
267 ... print('%s %s %s %s' % (cos(s), sin(s), tan(s), cot(s)))
268 ...
269 1 0 0 zoo
270 sqrt(3)/2 1/2 sqrt(3)/3 sqrt(3)
271 sqrt(2)/2 sqrt(2)/2 1 1
272 1/2 sqrt(3)/2 sqrt(3) sqrt(3)/3
273 0 1 zoo 0
274 """
275 # special values at 0, pi/6, pi/4, pi/3, pi/2 already handled
276 return rv
279def _TR56(rv, f, g, h, max, pow):
280 """Helper for TR5 and TR6 to replace f**2 with h(g**2)
282 Options
283 =======
285 max : controls size of exponent that can appear on f
286 e.g. if max=4 then f**4 will be changed to h(g**2)**2.
287 pow : controls whether the exponent must be a perfect power of 2
288 e.g. if pow=True (and max >= 6) then f**6 will not be changed
289 but f**8 will be changed to h(g**2)**4
291 >>> from sympy.simplify.fu import _TR56 as T
292 >>> from sympy.abc import x
293 >>> from sympy import sin, cos
294 >>> h = lambda x: 1 - x
295 >>> T(sin(x)**3, sin, cos, h, 4, False)
296 (1 - cos(x)**2)*sin(x)
297 >>> T(sin(x)**6, sin, cos, h, 6, False)
298 (1 - cos(x)**2)**3
299 >>> T(sin(x)**6, sin, cos, h, 6, True)
300 sin(x)**6
301 >>> T(sin(x)**8, sin, cos, h, 10, True)
302 (1 - cos(x)**2)**4
303 """
305 def _f(rv):
306 # I'm not sure if this transformation should target all even powers
307 # or only those expressible as powers of 2. Also, should it only
308 # make the changes in powers that appear in sums -- making an isolated
309 # change is not going to allow a simplification as far as I can tell.
310 if not (rv.is_Pow and rv.base.func == f):
311 return rv
312 if not rv.exp.is_real:
313 return rv
315 if (rv.exp < 0) == True:
316 return rv
317 if (rv.exp > max) == True:
318 return rv
319 if rv.exp == 1:
320 return rv
321 if rv.exp == 2:
322 return h(g(rv.base.args[0])**2)
323 else:
324 if rv.exp % 2 == 1:
325 e = rv.exp//2
326 return f(rv.base.args[0])*h(g(rv.base.args[0])**2)**e
327 elif rv.exp == 4:
328 e = 2
329 elif not pow:
330 if rv.exp % 2:
331 return rv
332 e = rv.exp//2
333 else:
334 p = perfect_power(rv.exp)
335 if not p:
336 return rv
337 e = rv.exp//2
338 return h(g(rv.base.args[0])**2)**e
340 return bottom_up(rv, _f)
343def TR5(rv, max=4, pow=False):
344 """Replacement of sin**2 with 1 - cos(x)**2.
346 See _TR56 docstring for advanced use of ``max`` and ``pow``.
348 Examples
349 ========
351 >>> from sympy.simplify.fu import TR5
352 >>> from sympy.abc import x
353 >>> from sympy import sin
354 >>> TR5(sin(x)**2)
355 1 - cos(x)**2
356 >>> TR5(sin(x)**-2) # unchanged
357 sin(x)**(-2)
358 >>> TR5(sin(x)**4)
359 (1 - cos(x)**2)**2
360 """
361 return _TR56(rv, sin, cos, lambda x: 1 - x, max=max, pow=pow)
364def TR6(rv, max=4, pow=False):
365 """Replacement of cos**2 with 1 - sin(x)**2.
367 See _TR56 docstring for advanced use of ``max`` and ``pow``.
369 Examples
370 ========
372 >>> from sympy.simplify.fu import TR6
373 >>> from sympy.abc import x
374 >>> from sympy import cos
375 >>> TR6(cos(x)**2)
376 1 - sin(x)**2
377 >>> TR6(cos(x)**-2) #unchanged
378 cos(x)**(-2)
379 >>> TR6(cos(x)**4)
380 (1 - sin(x)**2)**2
381 """
382 return _TR56(rv, cos, sin, lambda x: 1 - x, max=max, pow=pow)
385def TR7(rv):
386 """Lowering the degree of cos(x)**2.
388 Examples
389 ========
391 >>> from sympy.simplify.fu import TR7
392 >>> from sympy.abc import x
393 >>> from sympy import cos
394 >>> TR7(cos(x)**2)
395 cos(2*x)/2 + 1/2
396 >>> TR7(cos(x)**2 + 1)
397 cos(2*x)/2 + 3/2
399 """
401 def f(rv):
402 if not (rv.is_Pow and rv.base.func == cos and rv.exp == 2):
403 return rv
404 return (1 + cos(2*rv.base.args[0]))/2
406 return bottom_up(rv, f)
409def TR8(rv, first=True):
410 """Converting products of ``cos`` and/or ``sin`` to a sum or
411 difference of ``cos`` and or ``sin`` terms.
413 Examples
414 ========
416 >>> from sympy.simplify.fu import TR8
417 >>> from sympy import cos, sin
418 >>> TR8(cos(2)*cos(3))
419 cos(5)/2 + cos(1)/2
420 >>> TR8(cos(2)*sin(3))
421 sin(5)/2 + sin(1)/2
422 >>> TR8(sin(2)*sin(3))
423 -cos(5)/2 + cos(1)/2
424 """
426 def f(rv):
427 if not (
428 rv.is_Mul or
429 rv.is_Pow and
430 rv.base.func in (cos, sin) and
431 (rv.exp.is_integer or rv.base.is_positive)):
432 return rv
434 if first:
435 n, d = [expand_mul(i) for i in rv.as_numer_denom()]
436 newn = TR8(n, first=False)
437 newd = TR8(d, first=False)
438 if newn != n or newd != d:
439 rv = gcd_terms(newn/newd)
440 if rv.is_Mul and rv.args[0].is_Rational and \
441 len(rv.args) == 2 and rv.args[1].is_Add:
442 rv = Mul(*rv.as_coeff_Mul())
443 return rv
445 args = {cos: [], sin: [], None: []}
446 for a in ordered(Mul.make_args(rv)):
447 if a.func in (cos, sin):
448 args[type(a)].append(a.args[0])
449 elif (a.is_Pow and a.exp.is_Integer and a.exp > 0 and \
450 a.base.func in (cos, sin)):
451 # XXX this is ok but pathological expression could be handled
452 # more efficiently as in TRmorrie
453 args[type(a.base)].extend([a.base.args[0]]*a.exp)
454 else:
455 args[None].append(a)
456 c = args[cos]
457 s = args[sin]
458 if not (c and s or len(c) > 1 or len(s) > 1):
459 return rv
461 args = args[None]
462 n = min(len(c), len(s))
463 for i in range(n):
464 a1 = s.pop()
465 a2 = c.pop()
466 args.append((sin(a1 + a2) + sin(a1 - a2))/2)
467 while len(c) > 1:
468 a1 = c.pop()
469 a2 = c.pop()
470 args.append((cos(a1 + a2) + cos(a1 - a2))/2)
471 if c:
472 args.append(cos(c.pop()))
473 while len(s) > 1:
474 a1 = s.pop()
475 a2 = s.pop()
476 args.append((-cos(a1 + a2) + cos(a1 - a2))/2)
477 if s:
478 args.append(sin(s.pop()))
479 return TR8(expand_mul(Mul(*args)))
481 return bottom_up(rv, f)
484def TR9(rv):
485 """Sum of ``cos`` or ``sin`` terms as a product of ``cos`` or ``sin``.
487 Examples
488 ========
490 >>> from sympy.simplify.fu import TR9
491 >>> from sympy import cos, sin
492 >>> TR9(cos(1) + cos(2))
493 2*cos(1/2)*cos(3/2)
494 >>> TR9(cos(1) + 2*sin(1) + 2*sin(2))
495 cos(1) + 4*sin(3/2)*cos(1/2)
497 If no change is made by TR9, no re-arrangement of the
498 expression will be made. For example, though factoring
499 of common term is attempted, if the factored expression
500 was not changed, the original expression will be returned:
502 >>> TR9(cos(3) + cos(3)*cos(2))
503 cos(3) + cos(2)*cos(3)
505 """
507 def f(rv):
508 if not rv.is_Add:
509 return rv
511 def do(rv, first=True):
512 # cos(a)+/-cos(b) can be combined into a product of cosines and
513 # sin(a)+/-sin(b) can be combined into a product of cosine and
514 # sine.
515 #
516 # If there are more than two args, the pairs which "work" will
517 # have a gcd extractable and the remaining two terms will have
518 # the above structure -- all pairs must be checked to find the
519 # ones that work. args that don't have a common set of symbols
520 # are skipped since this doesn't lead to a simpler formula and
521 # also has the arbitrariness of combining, for example, the x
522 # and y term instead of the y and z term in something like
523 # cos(x) + cos(y) + cos(z).
525 if not rv.is_Add:
526 return rv
528 args = list(ordered(rv.args))
529 if len(args) != 2:
530 hit = False
531 for i in range(len(args)):
532 ai = args[i]
533 if ai is None:
534 continue
535 for j in range(i + 1, len(args)):
536 aj = args[j]
537 if aj is None:
538 continue
539 was = ai + aj
540 new = do(was)
541 if new != was:
542 args[i] = new # update in place
543 args[j] = None
544 hit = True
545 break # go to next i
546 if hit:
547 rv = Add(*[_f for _f in args if _f])
548 if rv.is_Add:
549 rv = do(rv)
551 return rv
553 # two-arg Add
554 split = trig_split(*args)
555 if not split:
556 return rv
557 gcd, n1, n2, a, b, iscos = split
559 # application of rule if possible
560 if iscos:
561 if n1 == n2:
562 return gcd*n1*2*cos((a + b)/2)*cos((a - b)/2)
563 if n1 < 0:
564 a, b = b, a
565 return -2*gcd*sin((a + b)/2)*sin((a - b)/2)
566 else:
567 if n1 == n2:
568 return gcd*n1*2*sin((a + b)/2)*cos((a - b)/2)
569 if n1 < 0:
570 a, b = b, a
571 return 2*gcd*cos((a + b)/2)*sin((a - b)/2)
573 return process_common_addends(rv, do) # DON'T sift by free symbols
575 return bottom_up(rv, f)
578def TR10(rv, first=True):
579 """Separate sums in ``cos`` and ``sin``.
581 Examples
582 ========
584 >>> from sympy.simplify.fu import TR10
585 >>> from sympy.abc import a, b, c
586 >>> from sympy import cos, sin
587 >>> TR10(cos(a + b))
588 -sin(a)*sin(b) + cos(a)*cos(b)
589 >>> TR10(sin(a + b))
590 sin(a)*cos(b) + sin(b)*cos(a)
591 >>> TR10(sin(a + b + c))
592 (-sin(a)*sin(b) + cos(a)*cos(b))*sin(c) + \
593 (sin(a)*cos(b) + sin(b)*cos(a))*cos(c)
594 """
596 def f(rv):
597 if rv.func not in (cos, sin):
598 return rv
600 f = rv.func
601 arg = rv.args[0]
602 if arg.is_Add:
603 if first:
604 args = list(ordered(arg.args))
605 else:
606 args = list(arg.args)
607 a = args.pop()
608 b = Add._from_args(args)
609 if b.is_Add:
610 if f == sin:
611 return sin(a)*TR10(cos(b), first=False) + \
612 cos(a)*TR10(sin(b), first=False)
613 else:
614 return cos(a)*TR10(cos(b), first=False) - \
615 sin(a)*TR10(sin(b), first=False)
616 else:
617 if f == sin:
618 return sin(a)*cos(b) + cos(a)*sin(b)
619 else:
620 return cos(a)*cos(b) - sin(a)*sin(b)
621 return rv
623 return bottom_up(rv, f)
626def TR10i(rv):
627 """Sum of products to function of sum.
629 Examples
630 ========
632 >>> from sympy.simplify.fu import TR10i
633 >>> from sympy import cos, sin, sqrt
634 >>> from sympy.abc import x
636 >>> TR10i(cos(1)*cos(3) + sin(1)*sin(3))
637 cos(2)
638 >>> TR10i(cos(1)*sin(3) + sin(1)*cos(3) + cos(3))
639 cos(3) + sin(4)
640 >>> TR10i(sqrt(2)*cos(x)*x + sqrt(6)*sin(x)*x)
641 2*sqrt(2)*x*sin(x + pi/6)
643 """
644 global _ROOT2, _ROOT3, _invROOT3
645 if _ROOT2 is None:
646 _roots()
648 def f(rv):
649 if not rv.is_Add:
650 return rv
652 def do(rv, first=True):
653 # args which can be expressed as A*(cos(a)*cos(b)+/-sin(a)*sin(b))
654 # or B*(cos(a)*sin(b)+/-cos(b)*sin(a)) can be combined into
655 # A*f(a+/-b) where f is either sin or cos.
656 #
657 # If there are more than two args, the pairs which "work" will have
658 # a gcd extractable and the remaining two terms will have the above
659 # structure -- all pairs must be checked to find the ones that
660 # work.
662 if not rv.is_Add:
663 return rv
665 args = list(ordered(rv.args))
666 if len(args) != 2:
667 hit = False
668 for i in range(len(args)):
669 ai = args[i]
670 if ai is None:
671 continue
672 for j in range(i + 1, len(args)):
673 aj = args[j]
674 if aj is None:
675 continue
676 was = ai + aj
677 new = do(was)
678 if new != was:
679 args[i] = new # update in place
680 args[j] = None
681 hit = True
682 break # go to next i
683 if hit:
684 rv = Add(*[_f for _f in args if _f])
685 if rv.is_Add:
686 rv = do(rv)
688 return rv
690 # two-arg Add
691 split = trig_split(*args, two=True)
692 if not split:
693 return rv
694 gcd, n1, n2, a, b, same = split
696 # identify and get c1 to be cos then apply rule if possible
697 if same: # coscos, sinsin
698 gcd = n1*gcd
699 if n1 == n2:
700 return gcd*cos(a - b)
701 return gcd*cos(a + b)
702 else: #cossin, cossin
703 gcd = n1*gcd
704 if n1 == n2:
705 return gcd*sin(a + b)
706 return gcd*sin(b - a)
708 rv = process_common_addends(
709 rv, do, lambda x: tuple(ordered(x.free_symbols)))
711 # need to check for inducible pairs in ratio of sqrt(3):1 that
712 # appeared in different lists when sorting by coefficient
713 while rv.is_Add:
714 byrad = defaultdict(list)
715 for a in rv.args:
716 hit = 0
717 if a.is_Mul:
718 for ai in a.args:
719 if ai.is_Pow and ai.exp is S.Half and \
720 ai.base.is_Integer:
721 byrad[ai].append(a)
722 hit = 1
723 break
724 if not hit:
725 byrad[S.One].append(a)
727 # no need to check all pairs -- just check for the onees
728 # that have the right ratio
729 args = []
730 for a in byrad:
731 for b in [_ROOT3*a, _invROOT3]:
732 if b in byrad:
733 for i in range(len(byrad[a])):
734 if byrad[a][i] is None:
735 continue
736 for j in range(len(byrad[b])):
737 if byrad[b][j] is None:
738 continue
739 was = Add(byrad[a][i] + byrad[b][j])
740 new = do(was)
741 if new != was:
742 args.append(new)
743 byrad[a][i] = None
744 byrad[b][j] = None
745 break
746 if args:
747 rv = Add(*(args + [Add(*[_f for _f in v if _f])
748 for v in byrad.values()]))
749 else:
750 rv = do(rv) # final pass to resolve any new inducible pairs
751 break
753 return rv
755 return bottom_up(rv, f)
758def TR11(rv, base=None):
759 """Function of double angle to product. The ``base`` argument can be used
760 to indicate what is the un-doubled argument, e.g. if 3*pi/7 is the base
761 then cosine and sine functions with argument 6*pi/7 will be replaced.
763 Examples
764 ========
766 >>> from sympy.simplify.fu import TR11
767 >>> from sympy import cos, sin, pi
768 >>> from sympy.abc import x
769 >>> TR11(sin(2*x))
770 2*sin(x)*cos(x)
771 >>> TR11(cos(2*x))
772 -sin(x)**2 + cos(x)**2
773 >>> TR11(sin(4*x))
774 4*(-sin(x)**2 + cos(x)**2)*sin(x)*cos(x)
775 >>> TR11(sin(4*x/3))
776 4*(-sin(x/3)**2 + cos(x/3)**2)*sin(x/3)*cos(x/3)
778 If the arguments are simply integers, no change is made
779 unless a base is provided:
781 >>> TR11(cos(2))
782 cos(2)
783 >>> TR11(cos(4), 2)
784 -sin(2)**2 + cos(2)**2
786 There is a subtle issue here in that autosimplification will convert
787 some higher angles to lower angles
789 >>> cos(6*pi/7) + cos(3*pi/7)
790 -cos(pi/7) + cos(3*pi/7)
792 The 6*pi/7 angle is now pi/7 but can be targeted with TR11 by supplying
793 the 3*pi/7 base:
795 >>> TR11(_, 3*pi/7)
796 -sin(3*pi/7)**2 + cos(3*pi/7)**2 + cos(3*pi/7)
798 """
800 def f(rv):
801 if rv.func not in (cos, sin):
802 return rv
804 if base:
805 f = rv.func
806 t = f(base*2)
807 co = S.One
808 if t.is_Mul:
809 co, t = t.as_coeff_Mul()
810 if t.func not in (cos, sin):
811 return rv
812 if rv.args[0] == t.args[0]:
813 c = cos(base)
814 s = sin(base)
815 if f is cos:
816 return (c**2 - s**2)/co
817 else:
818 return 2*c*s/co
819 return rv
821 elif not rv.args[0].is_Number:
822 # make a change if the leading coefficient's numerator is
823 # divisible by 2
824 c, m = rv.args[0].as_coeff_Mul(rational=True)
825 if c.p % 2 == 0:
826 arg = c.p//2*m/c.q
827 c = TR11(cos(arg))
828 s = TR11(sin(arg))
829 if rv.func == sin:
830 rv = 2*s*c
831 else:
832 rv = c**2 - s**2
833 return rv
835 return bottom_up(rv, f)
838def _TR11(rv):
839 """
840 Helper for TR11 to find half-arguments for sin in factors of
841 num/den that appear in cos or sin factors in the den/num.
843 Examples
844 ========
846 >>> from sympy.simplify.fu import TR11, _TR11
847 >>> from sympy import cos, sin
848 >>> from sympy.abc import x
849 >>> TR11(sin(x/3)/(cos(x/6)))
850 sin(x/3)/cos(x/6)
851 >>> _TR11(sin(x/3)/(cos(x/6)))
852 2*sin(x/6)
853 >>> TR11(sin(x/6)/(sin(x/3)))
854 sin(x/6)/sin(x/3)
855 >>> _TR11(sin(x/6)/(sin(x/3)))
856 1/(2*cos(x/6))
858 """
859 def f(rv):
860 if not isinstance(rv, Expr):
861 return rv
863 def sincos_args(flat):
864 # find arguments of sin and cos that
865 # appears as bases in args of flat
866 # and have Integer exponents
867 args = defaultdict(set)
868 for fi in Mul.make_args(flat):
869 b, e = fi.as_base_exp()
870 if e.is_Integer and e > 0:
871 if b.func in (cos, sin):
872 args[type(b)].add(b.args[0])
873 return args
874 num_args, den_args = map(sincos_args, rv.as_numer_denom())
875 def handle_match(rv, num_args, den_args):
876 # for arg in sin args of num_args, look for arg/2
877 # in den_args and pass this half-angle to TR11
878 # for handling in rv
879 for narg in num_args[sin]:
880 half = narg/2
881 if half in den_args[cos]:
882 func = cos
883 elif half in den_args[sin]:
884 func = sin
885 else:
886 continue
887 rv = TR11(rv, half)
888 den_args[func].remove(half)
889 return rv
890 # sin in num, sin or cos in den
891 rv = handle_match(rv, num_args, den_args)
892 # sin in den, sin or cos in num
893 rv = handle_match(rv, den_args, num_args)
894 return rv
896 return bottom_up(rv, f)
899def TR12(rv, first=True):
900 """Separate sums in ``tan``.
902 Examples
903 ========
905 >>> from sympy.abc import x, y
906 >>> from sympy import tan
907 >>> from sympy.simplify.fu import TR12
908 >>> TR12(tan(x + y))
909 (tan(x) + tan(y))/(-tan(x)*tan(y) + 1)
910 """
912 def f(rv):
913 if not rv.func == tan:
914 return rv
916 arg = rv.args[0]
917 if arg.is_Add:
918 if first:
919 args = list(ordered(arg.args))
920 else:
921 args = list(arg.args)
922 a = args.pop()
923 b = Add._from_args(args)
924 if b.is_Add:
925 tb = TR12(tan(b), first=False)
926 else:
927 tb = tan(b)
928 return (tan(a) + tb)/(1 - tan(a)*tb)
929 return rv
931 return bottom_up(rv, f)
934def TR12i(rv):
935 """Combine tan arguments as
936 (tan(y) + tan(x))/(tan(x)*tan(y) - 1) -> -tan(x + y).
938 Examples
939 ========
941 >>> from sympy.simplify.fu import TR12i
942 >>> from sympy import tan
943 >>> from sympy.abc import a, b, c
944 >>> ta, tb, tc = [tan(i) for i in (a, b, c)]
945 >>> TR12i((ta + tb)/(-ta*tb + 1))
946 tan(a + b)
947 >>> TR12i((ta + tb)/(ta*tb - 1))
948 -tan(a + b)
949 >>> TR12i((-ta - tb)/(ta*tb - 1))
950 tan(a + b)
951 >>> eq = (ta + tb)/(-ta*tb + 1)**2*(-3*ta - 3*tc)/(2*(ta*tc - 1))
952 >>> TR12i(eq.expand())
953 -3*tan(a + b)*tan(a + c)/(2*(tan(a) + tan(b) - 1))
954 """
955 def f(rv):
956 if not (rv.is_Add or rv.is_Mul or rv.is_Pow):
957 return rv
959 n, d = rv.as_numer_denom()
960 if not d.args or not n.args:
961 return rv
963 dok = {}
965 def ok(di):
966 m = as_f_sign_1(di)
967 if m:
968 g, f, s = m
969 if s is S.NegativeOne and f.is_Mul and len(f.args) == 2 and \
970 all(isinstance(fi, tan) for fi in f.args):
971 return g, f
973 d_args = list(Mul.make_args(d))
974 for i, di in enumerate(d_args):
975 m = ok(di)
976 if m:
977 g, t = m
978 s = Add(*[_.args[0] for _ in t.args])
979 dok[s] = S.One
980 d_args[i] = g
981 continue
982 if di.is_Add:
983 di = factor(di)
984 if di.is_Mul:
985 d_args.extend(di.args)
986 d_args[i] = S.One
987 elif di.is_Pow and (di.exp.is_integer or di.base.is_positive):
988 m = ok(di.base)
989 if m:
990 g, t = m
991 s = Add(*[_.args[0] for _ in t.args])
992 dok[s] = di.exp
993 d_args[i] = g**di.exp
994 else:
995 di = factor(di)
996 if di.is_Mul:
997 d_args.extend(di.args)
998 d_args[i] = S.One
999 if not dok:
1000 return rv
1002 def ok(ni):
1003 if ni.is_Add and len(ni.args) == 2:
1004 a, b = ni.args
1005 if isinstance(a, tan) and isinstance(b, tan):
1006 return a, b
1007 n_args = list(Mul.make_args(factor_terms(n)))
1008 hit = False
1009 for i, ni in enumerate(n_args):
1010 m = ok(ni)
1011 if not m:
1012 m = ok(-ni)
1013 if m:
1014 n_args[i] = S.NegativeOne
1015 else:
1016 if ni.is_Add:
1017 ni = factor(ni)
1018 if ni.is_Mul:
1019 n_args.extend(ni.args)
1020 n_args[i] = S.One
1021 continue
1022 elif ni.is_Pow and (
1023 ni.exp.is_integer or ni.base.is_positive):
1024 m = ok(ni.base)
1025 if m:
1026 n_args[i] = S.One
1027 else:
1028 ni = factor(ni)
1029 if ni.is_Mul:
1030 n_args.extend(ni.args)
1031 n_args[i] = S.One
1032 continue
1033 else:
1034 continue
1035 else:
1036 n_args[i] = S.One
1037 hit = True
1038 s = Add(*[_.args[0] for _ in m])
1039 ed = dok[s]
1040 newed = ed.extract_additively(S.One)
1041 if newed is not None:
1042 if newed:
1043 dok[s] = newed
1044 else:
1045 dok.pop(s)
1046 n_args[i] *= -tan(s)
1048 if hit:
1049 rv = Mul(*n_args)/Mul(*d_args)/Mul(*[(Add(*[
1050 tan(a) for a in i.args]) - 1)**e for i, e in dok.items()])
1052 return rv
1054 return bottom_up(rv, f)
1057def TR13(rv):
1058 """Change products of ``tan`` or ``cot``.
1060 Examples
1061 ========
1063 >>> from sympy.simplify.fu import TR13
1064 >>> from sympy import tan, cot
1065 >>> TR13(tan(3)*tan(2))
1066 -tan(2)/tan(5) - tan(3)/tan(5) + 1
1067 >>> TR13(cot(3)*cot(2))
1068 cot(2)*cot(5) + 1 + cot(3)*cot(5)
1069 """
1071 def f(rv):
1072 if not rv.is_Mul:
1073 return rv
1075 # XXX handle products of powers? or let power-reducing handle it?
1076 args = {tan: [], cot: [], None: []}
1077 for a in ordered(Mul.make_args(rv)):
1078 if a.func in (tan, cot):
1079 args[type(a)].append(a.args[0])
1080 else:
1081 args[None].append(a)
1082 t = args[tan]
1083 c = args[cot]
1084 if len(t) < 2 and len(c) < 2:
1085 return rv
1086 args = args[None]
1087 while len(t) > 1:
1088 t1 = t.pop()
1089 t2 = t.pop()
1090 args.append(1 - (tan(t1)/tan(t1 + t2) + tan(t2)/tan(t1 + t2)))
1091 if t:
1092 args.append(tan(t.pop()))
1093 while len(c) > 1:
1094 t1 = c.pop()
1095 t2 = c.pop()
1096 args.append(1 + cot(t1)*cot(t1 + t2) + cot(t2)*cot(t1 + t2))
1097 if c:
1098 args.append(cot(c.pop()))
1099 return Mul(*args)
1101 return bottom_up(rv, f)
1104def TRmorrie(rv):
1105 """Returns cos(x)*cos(2*x)*...*cos(2**(k-1)*x) -> sin(2**k*x)/(2**k*sin(x))
1107 Examples
1108 ========
1110 >>> from sympy.simplify.fu import TRmorrie, TR8, TR3
1111 >>> from sympy.abc import x
1112 >>> from sympy import Mul, cos, pi
1113 >>> TRmorrie(cos(x)*cos(2*x))
1114 sin(4*x)/(4*sin(x))
1115 >>> TRmorrie(7*Mul(*[cos(x) for x in range(10)]))
1116 7*sin(12)*sin(16)*cos(5)*cos(7)*cos(9)/(64*sin(1)*sin(3))
1118 Sometimes autosimplification will cause a power to be
1119 not recognized. e.g. in the following, cos(4*pi/7) automatically
1120 simplifies to -cos(3*pi/7) so only 2 of the 3 terms are
1121 recognized:
1123 >>> TRmorrie(cos(pi/7)*cos(2*pi/7)*cos(4*pi/7))
1124 -sin(3*pi/7)*cos(3*pi/7)/(4*sin(pi/7))
1126 A touch by TR8 resolves the expression to a Rational
1128 >>> TR8(_)
1129 -1/8
1131 In this case, if eq is unsimplified, the answer is obtained
1132 directly:
1134 >>> eq = cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9)
1135 >>> TRmorrie(eq)
1136 1/16
1138 But if angles are made canonical with TR3 then the answer
1139 is not simplified without further work:
1141 >>> TR3(eq)
1142 sin(pi/18)*cos(pi/9)*cos(2*pi/9)/2
1143 >>> TRmorrie(_)
1144 sin(pi/18)*sin(4*pi/9)/(8*sin(pi/9))
1145 >>> TR8(_)
1146 cos(7*pi/18)/(16*sin(pi/9))
1147 >>> TR3(_)
1148 1/16
1150 The original expression would have resolve to 1/16 directly with TR8,
1151 however:
1153 >>> TR8(eq)
1154 1/16
1156 References
1157 ==========
1159 .. [1] https://en.wikipedia.org/wiki/Morrie%27s_law
1161 """
1163 def f(rv, first=True):
1164 if not rv.is_Mul:
1165 return rv
1166 if first:
1167 n, d = rv.as_numer_denom()
1168 return f(n, 0)/f(d, 0)
1170 args = defaultdict(list)
1171 coss = {}
1172 other = []
1173 for c in rv.args:
1174 b, e = c.as_base_exp()
1175 if e.is_Integer and isinstance(b, cos):
1176 co, a = b.args[0].as_coeff_Mul()
1177 args[a].append(co)
1178 coss[b] = e
1179 else:
1180 other.append(c)
1182 new = []
1183 for a in args:
1184 c = args[a]
1185 c.sort()
1186 while c:
1187 k = 0
1188 cc = ci = c[0]
1189 while cc in c:
1190 k += 1
1191 cc *= 2
1192 if k > 1:
1193 newarg = sin(2**k*ci*a)/2**k/sin(ci*a)
1194 # see how many times this can be taken
1195 take = None
1196 ccs = []
1197 for i in range(k):
1198 cc /= 2
1199 key = cos(a*cc, evaluate=False)
1200 ccs.append(cc)
1201 take = min(coss[key], take or coss[key])
1202 # update exponent counts
1203 for i in range(k):
1204 cc = ccs.pop()
1205 key = cos(a*cc, evaluate=False)
1206 coss[key] -= take
1207 if not coss[key]:
1208 c.remove(cc)
1209 new.append(newarg**take)
1210 else:
1211 b = cos(c.pop(0)*a)
1212 other.append(b**coss[b])
1214 if new:
1215 rv = Mul(*(new + other + [
1216 cos(k*a, evaluate=False) for a in args for k in args[a]]))
1218 return rv
1220 return bottom_up(rv, f)
1223def TR14(rv, first=True):
1224 """Convert factored powers of sin and cos identities into simpler
1225 expressions.
1227 Examples
1228 ========
1230 >>> from sympy.simplify.fu import TR14
1231 >>> from sympy.abc import x, y
1232 >>> from sympy import cos, sin
1233 >>> TR14((cos(x) - 1)*(cos(x) + 1))
1234 -sin(x)**2
1235 >>> TR14((sin(x) - 1)*(sin(x) + 1))
1236 -cos(x)**2
1237 >>> p1 = (cos(x) + 1)*(cos(x) - 1)
1238 >>> p2 = (cos(y) - 1)*2*(cos(y) + 1)
1239 >>> p3 = (3*(cos(y) - 1))*(3*(cos(y) + 1))
1240 >>> TR14(p1*p2*p3*(x - 1))
1241 -18*(x - 1)*sin(x)**2*sin(y)**4
1243 """
1245 def f(rv):
1246 if not rv.is_Mul:
1247 return rv
1249 if first:
1250 # sort them by location in numerator and denominator
1251 # so the code below can just deal with positive exponents
1252 n, d = rv.as_numer_denom()
1253 if d is not S.One:
1254 newn = TR14(n, first=False)
1255 newd = TR14(d, first=False)
1256 if newn != n or newd != d:
1257 rv = newn/newd
1258 return rv
1260 other = []
1261 process = []
1262 for a in rv.args:
1263 if a.is_Pow:
1264 b, e = a.as_base_exp()
1265 if not (e.is_integer or b.is_positive):
1266 other.append(a)
1267 continue
1268 a = b
1269 else:
1270 e = S.One
1271 m = as_f_sign_1(a)
1272 if not m or m[1].func not in (cos, sin):
1273 if e is S.One:
1274 other.append(a)
1275 else:
1276 other.append(a**e)
1277 continue
1278 g, f, si = m
1279 process.append((g, e.is_Number, e, f, si, a))
1281 # sort them to get like terms next to each other
1282 process = list(ordered(process))
1284 # keep track of whether there was any change
1285 nother = len(other)
1287 # access keys
1288 keys = (g, t, e, f, si, a) = list(range(6))
1290 while process:
1291 A = process.pop(0)
1292 if process:
1293 B = process[0]
1295 if A[e].is_Number and B[e].is_Number:
1296 # both exponents are numbers
1297 if A[f] == B[f]:
1298 if A[si] != B[si]:
1299 B = process.pop(0)
1300 take = min(A[e], B[e])
1302 # reinsert any remainder
1303 # the B will likely sort after A so check it first
1304 if B[e] != take:
1305 rem = [B[i] for i in keys]
1306 rem[e] -= take
1307 process.insert(0, rem)
1308 elif A[e] != take:
1309 rem = [A[i] for i in keys]
1310 rem[e] -= take
1311 process.insert(0, rem)
1313 if isinstance(A[f], cos):
1314 t = sin
1315 else:
1316 t = cos
1317 other.append((-A[g]*B[g]*t(A[f].args[0])**2)**take)
1318 continue
1320 elif A[e] == B[e]:
1321 # both exponents are equal symbols
1322 if A[f] == B[f]:
1323 if A[si] != B[si]:
1324 B = process.pop(0)
1325 take = A[e]
1326 if isinstance(A[f], cos):
1327 t = sin
1328 else:
1329 t = cos
1330 other.append((-A[g]*B[g]*t(A[f].args[0])**2)**take)
1331 continue
1333 # either we are done or neither condition above applied
1334 other.append(A[a]**A[e])
1336 if len(other) != nother:
1337 rv = Mul(*other)
1339 return rv
1341 return bottom_up(rv, f)
1344def TR15(rv, max=4, pow=False):
1345 """Convert sin(x)**-2 to 1 + cot(x)**2.
1347 See _TR56 docstring for advanced use of ``max`` and ``pow``.
1349 Examples
1350 ========
1352 >>> from sympy.simplify.fu import TR15
1353 >>> from sympy.abc import x
1354 >>> from sympy import sin
1355 >>> TR15(1 - 1/sin(x)**2)
1356 -cot(x)**2
1358 """
1360 def f(rv):
1361 if not (isinstance(rv, Pow) and isinstance(rv.base, sin)):
1362 return rv
1364 e = rv.exp
1365 if e % 2 == 1:
1366 return TR15(rv.base**(e + 1))/rv.base
1368 ia = 1/rv
1369 a = _TR56(ia, sin, cot, lambda x: 1 + x, max=max, pow=pow)
1370 if a != ia:
1371 rv = a
1372 return rv
1374 return bottom_up(rv, f)
1377def TR16(rv, max=4, pow=False):
1378 """Convert cos(x)**-2 to 1 + tan(x)**2.
1380 See _TR56 docstring for advanced use of ``max`` and ``pow``.
1382 Examples
1383 ========
1385 >>> from sympy.simplify.fu import TR16
1386 >>> from sympy.abc import x
1387 >>> from sympy import cos
1388 >>> TR16(1 - 1/cos(x)**2)
1389 -tan(x)**2
1391 """
1393 def f(rv):
1394 if not (isinstance(rv, Pow) and isinstance(rv.base, cos)):
1395 return rv
1397 e = rv.exp
1398 if e % 2 == 1:
1399 return TR15(rv.base**(e + 1))/rv.base
1401 ia = 1/rv
1402 a = _TR56(ia, cos, tan, lambda x: 1 + x, max=max, pow=pow)
1403 if a != ia:
1404 rv = a
1405 return rv
1407 return bottom_up(rv, f)
1410def TR111(rv):
1411 """Convert f(x)**-i to g(x)**i where either ``i`` is an integer
1412 or the base is positive and f, g are: tan, cot; sin, csc; or cos, sec.
1414 Examples
1415 ========
1417 >>> from sympy.simplify.fu import TR111
1418 >>> from sympy.abc import x
1419 >>> from sympy import tan
1420 >>> TR111(1 - 1/tan(x)**2)
1421 1 - cot(x)**2
1423 """
1425 def f(rv):
1426 if not (
1427 isinstance(rv, Pow) and
1428 (rv.base.is_positive or rv.exp.is_integer and rv.exp.is_negative)):
1429 return rv
1431 if isinstance(rv.base, tan):
1432 return cot(rv.base.args[0])**-rv.exp
1433 elif isinstance(rv.base, sin):
1434 return csc(rv.base.args[0])**-rv.exp
1435 elif isinstance(rv.base, cos):
1436 return sec(rv.base.args[0])**-rv.exp
1437 return rv
1439 return bottom_up(rv, f)
1442def TR22(rv, max=4, pow=False):
1443 """Convert tan(x)**2 to sec(x)**2 - 1 and cot(x)**2 to csc(x)**2 - 1.
1445 See _TR56 docstring for advanced use of ``max`` and ``pow``.
1447 Examples
1448 ========
1450 >>> from sympy.simplify.fu import TR22
1451 >>> from sympy.abc import x
1452 >>> from sympy import tan, cot
1453 >>> TR22(1 + tan(x)**2)
1454 sec(x)**2
1455 >>> TR22(1 + cot(x)**2)
1456 csc(x)**2
1458 """
1460 def f(rv):
1461 if not (isinstance(rv, Pow) and rv.base.func in (cot, tan)):
1462 return rv
1464 rv = _TR56(rv, tan, sec, lambda x: x - 1, max=max, pow=pow)
1465 rv = _TR56(rv, cot, csc, lambda x: x - 1, max=max, pow=pow)
1466 return rv
1468 return bottom_up(rv, f)
1471def TRpower(rv):
1472 """Convert sin(x)**n and cos(x)**n with positive n to sums.
1474 Examples
1475 ========
1477 >>> from sympy.simplify.fu import TRpower
1478 >>> from sympy.abc import x
1479 >>> from sympy import cos, sin
1480 >>> TRpower(sin(x)**6)
1481 -15*cos(2*x)/32 + 3*cos(4*x)/16 - cos(6*x)/32 + 5/16
1482 >>> TRpower(sin(x)**3*cos(2*x)**4)
1483 (3*sin(x)/4 - sin(3*x)/4)*(cos(4*x)/2 + cos(8*x)/8 + 3/8)
1485 References
1486 ==========
1488 .. [1] https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Power-reduction_formulae
1490 """
1492 def f(rv):
1493 if not (isinstance(rv, Pow) and isinstance(rv.base, (sin, cos))):
1494 return rv
1495 b, n = rv.as_base_exp()
1496 x = b.args[0]
1497 if n.is_Integer and n.is_positive:
1498 if n.is_odd and isinstance(b, cos):
1499 rv = 2**(1-n)*Add(*[binomial(n, k)*cos((n - 2*k)*x)
1500 for k in range((n + 1)/2)])
1501 elif n.is_odd and isinstance(b, sin):
1502 rv = 2**(1-n)*S.NegativeOne**((n-1)/2)*Add(*[binomial(n, k)*
1503 S.NegativeOne**k*sin((n - 2*k)*x) for k in range((n + 1)/2)])
1504 elif n.is_even and isinstance(b, cos):
1505 rv = 2**(1-n)*Add(*[binomial(n, k)*cos((n - 2*k)*x)
1506 for k in range(n/2)])
1507 elif n.is_even and isinstance(b, sin):
1508 rv = 2**(1-n)*S.NegativeOne**(n/2)*Add(*[binomial(n, k)*
1509 S.NegativeOne**k*cos((n - 2*k)*x) for k in range(n/2)])
1510 if n.is_even:
1511 rv += 2**(-n)*binomial(n, n/2)
1512 return rv
1514 return bottom_up(rv, f)
1517def L(rv):
1518 """Return count of trigonometric functions in expression.
1520 Examples
1521 ========
1523 >>> from sympy.simplify.fu import L
1524 >>> from sympy.abc import x
1525 >>> from sympy import cos, sin
1526 >>> L(cos(x)+sin(x))
1527 2
1528 """
1529 return S(rv.count(TrigonometricFunction))
1532# ============== end of basic Fu-like tools =====================
1534if SYMPY_DEBUG:
1535 (TR0, TR1, TR2, TR3, TR4, TR5, TR6, TR7, TR8, TR9, TR10, TR11, TR12, TR13,
1536 TR2i, TRmorrie, TR14, TR15, TR16, TR12i, TR111, TR22
1537 )= list(map(debug,
1538 (TR0, TR1, TR2, TR3, TR4, TR5, TR6, TR7, TR8, TR9, TR10, TR11, TR12, TR13,
1539 TR2i, TRmorrie, TR14, TR15, TR16, TR12i, TR111, TR22)))
1542# tuples are chains -- (f, g) -> lambda x: g(f(x))
1543# lists are choices -- [f, g] -> lambda x: min(f(x), g(x), key=objective)
1545CTR1 = [(TR5, TR0), (TR6, TR0), identity]
1547CTR2 = (TR11, [(TR5, TR0), (TR6, TR0), TR0])
1549CTR3 = [(TRmorrie, TR8, TR0), (TRmorrie, TR8, TR10i, TR0), identity]
1551CTR4 = [(TR4, TR10i), identity]
1553RL1 = (TR4, TR3, TR4, TR12, TR4, TR13, TR4, TR0)
1556# XXX it's a little unclear how this one is to be implemented
1557# see Fu paper of reference, page 7. What is the Union symbol referring to?
1558# The diagram shows all these as one chain of transformations, but the
1559# text refers to them being applied independently. Also, a break
1560# if L starts to increase has not been implemented.
1561RL2 = [
1562 (TR4, TR3, TR10, TR4, TR3, TR11),
1563 (TR5, TR7, TR11, TR4),
1564 (CTR3, CTR1, TR9, CTR2, TR4, TR9, TR9, CTR4),
1565 identity,
1566 ]
1569def fu(rv, measure=lambda x: (L(x), x.count_ops())):
1570 """Attempt to simplify expression by using transformation rules given
1571 in the algorithm by Fu et al.
1573 :func:`fu` will try to minimize the objective function ``measure``.
1574 By default this first minimizes the number of trig terms and then minimizes
1575 the number of total operations.
1577 Examples
1578 ========
1580 >>> from sympy.simplify.fu import fu
1581 >>> from sympy import cos, sin, tan, pi, S, sqrt
1582 >>> from sympy.abc import x, y, a, b
1584 >>> fu(sin(50)**2 + cos(50)**2 + sin(pi/6))
1585 3/2
1586 >>> fu(sqrt(6)*cos(x) + sqrt(2)*sin(x))
1587 2*sqrt(2)*sin(x + pi/3)
1589 CTR1 example
1591 >>> eq = sin(x)**4 - cos(y)**2 + sin(y)**2 + 2*cos(x)**2
1592 >>> fu(eq)
1593 cos(x)**4 - 2*cos(y)**2 + 2
1595 CTR2 example
1597 >>> fu(S.Half - cos(2*x)/2)
1598 sin(x)**2
1600 CTR3 example
1602 >>> fu(sin(a)*(cos(b) - sin(b)) + cos(a)*(sin(b) + cos(b)))
1603 sqrt(2)*sin(a + b + pi/4)
1605 CTR4 example
1607 >>> fu(sqrt(3)*cos(x)/2 + sin(x)/2)
1608 sin(x + pi/3)
1610 Example 1
1612 >>> fu(1-sin(2*x)**2/4-sin(y)**2-cos(x)**4)
1613 -cos(x)**2 + cos(y)**2
1615 Example 2
1617 >>> fu(cos(4*pi/9))
1618 sin(pi/18)
1619 >>> fu(cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9))
1620 1/16
1622 Example 3
1624 >>> fu(tan(7*pi/18)+tan(5*pi/18)-sqrt(3)*tan(5*pi/18)*tan(7*pi/18))
1625 -sqrt(3)
1627 Objective function example
1629 >>> fu(sin(x)/cos(x)) # default objective function
1630 tan(x)
1631 >>> fu(sin(x)/cos(x), measure=lambda x: -x.count_ops()) # maximize op count
1632 sin(x)/cos(x)
1634 References
1635 ==========
1637 .. [1] https://www.sciencedirect.com/science/article/pii/S0895717706001609
1638 """
1639 fRL1 = greedy(RL1, measure)
1640 fRL2 = greedy(RL2, measure)
1642 was = rv
1643 rv = sympify(rv)
1644 if not isinstance(rv, Expr):
1645 return rv.func(*[fu(a, measure=measure) for a in rv.args])
1646 rv = TR1(rv)
1647 if rv.has(tan, cot):
1648 rv1 = fRL1(rv)
1649 if (measure(rv1) < measure(rv)):
1650 rv = rv1
1651 if rv.has(tan, cot):
1652 rv = TR2(rv)
1653 if rv.has(sin, cos):
1654 rv1 = fRL2(rv)
1655 rv2 = TR8(TRmorrie(rv1))
1656 rv = min([was, rv, rv1, rv2], key=measure)
1657 return min(TR2i(rv), rv, key=measure)
1660def process_common_addends(rv, do, key2=None, key1=True):
1661 """Apply ``do`` to addends of ``rv`` that (if ``key1=True``) share at least
1662 a common absolute value of their coefficient and the value of ``key2`` when
1663 applied to the argument. If ``key1`` is False ``key2`` must be supplied and
1664 will be the only key applied.
1665 """
1667 # collect by absolute value of coefficient and key2
1668 absc = defaultdict(list)
1669 if key1:
1670 for a in rv.args:
1671 c, a = a.as_coeff_Mul()
1672 if c < 0:
1673 c = -c
1674 a = -a # put the sign on `a`
1675 absc[(c, key2(a) if key2 else 1)].append(a)
1676 elif key2:
1677 for a in rv.args:
1678 absc[(S.One, key2(a))].append(a)
1679 else:
1680 raise ValueError('must have at least one key')
1682 args = []
1683 hit = False
1684 for k in absc:
1685 v = absc[k]
1686 c, _ = k
1687 if len(v) > 1:
1688 e = Add(*v, evaluate=False)
1689 new = do(e)
1690 if new != e:
1691 e = new
1692 hit = True
1693 args.append(c*e)
1694 else:
1695 args.append(c*v[0])
1696 if hit:
1697 rv = Add(*args)
1699 return rv
1702fufuncs = '''
1703 TR0 TR1 TR2 TR3 TR4 TR5 TR6 TR7 TR8 TR9 TR10 TR10i TR11
1704 TR12 TR13 L TR2i TRmorrie TR12i
1705 TR14 TR15 TR16 TR111 TR22'''.split()
1706FU = dict(list(zip(fufuncs, list(map(locals().get, fufuncs)))))
1709def _roots():
1710 global _ROOT2, _ROOT3, _invROOT3
1711 _ROOT2, _ROOT3 = sqrt(2), sqrt(3)
1712 _invROOT3 = 1/_ROOT3
1713_ROOT2 = None
1716def trig_split(a, b, two=False):
1717 """Return the gcd, s1, s2, a1, a2, bool where
1719 If two is False (default) then::
1720 a + b = gcd*(s1*f(a1) + s2*f(a2)) where f = cos if bool else sin
1721 else:
1722 if bool, a + b was +/- cos(a1)*cos(a2) +/- sin(a1)*sin(a2) and equals
1723 n1*gcd*cos(a - b) if n1 == n2 else
1724 n1*gcd*cos(a + b)
1725 else a + b was +/- cos(a1)*sin(a2) +/- sin(a1)*cos(a2) and equals
1726 n1*gcd*sin(a + b) if n1 = n2 else
1727 n1*gcd*sin(b - a)
1729 Examples
1730 ========
1732 >>> from sympy.simplify.fu import trig_split
1733 >>> from sympy.abc import x, y, z
1734 >>> from sympy import cos, sin, sqrt
1736 >>> trig_split(cos(x), cos(y))
1737 (1, 1, 1, x, y, True)
1738 >>> trig_split(2*cos(x), -2*cos(y))
1739 (2, 1, -1, x, y, True)
1740 >>> trig_split(cos(x)*sin(y), cos(y)*sin(y))
1741 (sin(y), 1, 1, x, y, True)
1743 >>> trig_split(cos(x), -sqrt(3)*sin(x), two=True)
1744 (2, 1, -1, x, pi/6, False)
1745 >>> trig_split(cos(x), sin(x), two=True)
1746 (sqrt(2), 1, 1, x, pi/4, False)
1747 >>> trig_split(cos(x), -sin(x), two=True)
1748 (sqrt(2), 1, -1, x, pi/4, False)
1749 >>> trig_split(sqrt(2)*cos(x), -sqrt(6)*sin(x), two=True)
1750 (2*sqrt(2), 1, -1, x, pi/6, False)
1751 >>> trig_split(-sqrt(6)*cos(x), -sqrt(2)*sin(x), two=True)
1752 (-2*sqrt(2), 1, 1, x, pi/3, False)
1753 >>> trig_split(cos(x)/sqrt(6), sin(x)/sqrt(2), two=True)
1754 (sqrt(6)/3, 1, 1, x, pi/6, False)
1755 >>> trig_split(-sqrt(6)*cos(x)*sin(y), -sqrt(2)*sin(x)*sin(y), two=True)
1756 (-2*sqrt(2)*sin(y), 1, 1, x, pi/3, False)
1758 >>> trig_split(cos(x), sin(x))
1759 >>> trig_split(cos(x), sin(z))
1760 >>> trig_split(2*cos(x), -sin(x))
1761 >>> trig_split(cos(x), -sqrt(3)*sin(x))
1762 >>> trig_split(cos(x)*cos(y), sin(x)*sin(z))
1763 >>> trig_split(cos(x)*cos(y), sin(x)*sin(y))
1764 >>> trig_split(-sqrt(6)*cos(x), sqrt(2)*sin(x)*sin(y), two=True)
1765 """
1766 global _ROOT2, _ROOT3, _invROOT3
1767 if _ROOT2 is None:
1768 _roots()
1770 a, b = [Factors(i) for i in (a, b)]
1771 ua, ub = a.normal(b)
1772 gcd = a.gcd(b).as_expr()
1773 n1 = n2 = 1
1774 if S.NegativeOne in ua.factors:
1775 ua = ua.quo(S.NegativeOne)
1776 n1 = -n1
1777 elif S.NegativeOne in ub.factors:
1778 ub = ub.quo(S.NegativeOne)
1779 n2 = -n2
1780 a, b = [i.as_expr() for i in (ua, ub)]
1782 def pow_cos_sin(a, two):
1783 """Return ``a`` as a tuple (r, c, s) such that
1784 ``a = (r or 1)*(c or 1)*(s or 1)``.
1786 Three arguments are returned (radical, c-factor, s-factor) as
1787 long as the conditions set by ``two`` are met; otherwise None is
1788 returned. If ``two`` is True there will be one or two non-None
1789 values in the tuple: c and s or c and r or s and r or s or c with c
1790 being a cosine function (if possible) else a sine, and s being a sine
1791 function (if possible) else oosine. If ``two`` is False then there
1792 will only be a c or s term in the tuple.
1794 ``two`` also require that either two cos and/or sin be present (with
1795 the condition that if the functions are the same the arguments are
1796 different or vice versa) or that a single cosine or a single sine
1797 be present with an optional radical.
1799 If the above conditions dictated by ``two`` are not met then None
1800 is returned.
1801 """
1802 c = s = None
1803 co = S.One
1804 if a.is_Mul:
1805 co, a = a.as_coeff_Mul()
1806 if len(a.args) > 2 or not two:
1807 return None
1808 if a.is_Mul:
1809 args = list(a.args)
1810 else:
1811 args = [a]
1812 a = args.pop(0)
1813 if isinstance(a, cos):
1814 c = a
1815 elif isinstance(a, sin):
1816 s = a
1817 elif a.is_Pow and a.exp is S.Half: # autoeval doesn't allow -1/2
1818 co *= a
1819 else:
1820 return None
1821 if args:
1822 b = args[0]
1823 if isinstance(b, cos):
1824 if c:
1825 s = b
1826 else:
1827 c = b
1828 elif isinstance(b, sin):
1829 if s:
1830 c = b
1831 else:
1832 s = b
1833 elif b.is_Pow and b.exp is S.Half:
1834 co *= b
1835 else:
1836 return None
1837 return co if co is not S.One else None, c, s
1838 elif isinstance(a, cos):
1839 c = a
1840 elif isinstance(a, sin):
1841 s = a
1842 if c is None and s is None:
1843 return
1844 co = co if co is not S.One else None
1845 return co, c, s
1847 # get the parts
1848 m = pow_cos_sin(a, two)
1849 if m is None:
1850 return
1851 coa, ca, sa = m
1852 m = pow_cos_sin(b, two)
1853 if m is None:
1854 return
1855 cob, cb, sb = m
1857 # check them
1858 if (not ca) and cb or ca and isinstance(ca, sin):
1859 coa, ca, sa, cob, cb, sb = cob, cb, sb, coa, ca, sa
1860 n1, n2 = n2, n1
1861 if not two: # need cos(x) and cos(y) or sin(x) and sin(y)
1862 c = ca or sa
1863 s = cb or sb
1864 if not isinstance(c, s.func):
1865 return None
1866 return gcd, n1, n2, c.args[0], s.args[0], isinstance(c, cos)
1867 else:
1868 if not coa and not cob:
1869 if (ca and cb and sa and sb):
1870 if isinstance(ca, sa.func) is not isinstance(cb, sb.func):
1871 return
1872 args = {j.args for j in (ca, sa)}
1873 if not all(i.args in args for i in (cb, sb)):
1874 return
1875 return gcd, n1, n2, ca.args[0], sa.args[0], isinstance(ca, sa.func)
1876 if ca and sa or cb and sb or \
1877 two and (ca is None and sa is None or cb is None and sb is None):
1878 return
1879 c = ca or sa
1880 s = cb or sb
1881 if c.args != s.args:
1882 return
1883 if not coa:
1884 coa = S.One
1885 if not cob:
1886 cob = S.One
1887 if coa is cob:
1888 gcd *= _ROOT2
1889 return gcd, n1, n2, c.args[0], pi/4, False
1890 elif coa/cob == _ROOT3:
1891 gcd *= 2*cob
1892 return gcd, n1, n2, c.args[0], pi/3, False
1893 elif coa/cob == _invROOT3:
1894 gcd *= 2*coa
1895 return gcd, n1, n2, c.args[0], pi/6, False
1898def as_f_sign_1(e):
1899 """If ``e`` is a sum that can be written as ``g*(a + s)`` where
1900 ``s`` is ``+/-1``, return ``g``, ``a``, and ``s`` where ``a`` does
1901 not have a leading negative coefficient.
1903 Examples
1904 ========
1906 >>> from sympy.simplify.fu import as_f_sign_1
1907 >>> from sympy.abc import x
1908 >>> as_f_sign_1(x + 1)
1909 (1, x, 1)
1910 >>> as_f_sign_1(x - 1)
1911 (1, x, -1)
1912 >>> as_f_sign_1(-x + 1)
1913 (-1, x, -1)
1914 >>> as_f_sign_1(-x - 1)
1915 (-1, x, 1)
1916 >>> as_f_sign_1(2*x + 2)
1917 (2, x, 1)
1918 """
1919 if not e.is_Add or len(e.args) != 2:
1920 return
1921 # exact match
1922 a, b = e.args
1923 if a in (S.NegativeOne, S.One):
1924 g = S.One
1925 if b.is_Mul and b.args[0].is_Number and b.args[0] < 0:
1926 a, b = -a, -b
1927 g = -g
1928 return g, b, a
1929 # gcd match
1930 a, b = [Factors(i) for i in e.args]
1931 ua, ub = a.normal(b)
1932 gcd = a.gcd(b).as_expr()
1933 if S.NegativeOne in ua.factors:
1934 ua = ua.quo(S.NegativeOne)
1935 n1 = -1
1936 n2 = 1
1937 elif S.NegativeOne in ub.factors:
1938 ub = ub.quo(S.NegativeOne)
1939 n1 = 1
1940 n2 = -1
1941 else:
1942 n1 = n2 = 1
1943 a, b = [i.as_expr() for i in (ua, ub)]
1944 if a is S.One:
1945 a, b = b, a
1946 n1, n2 = n2, n1
1947 if n1 == -1:
1948 gcd = -gcd
1949 n2 = -n2
1951 if b is S.One:
1952 return gcd, a, n2
1955def _osborne(e, d):
1956 """Replace all hyperbolic functions with trig functions using
1957 the Osborne rule.
1959 Notes
1960 =====
1962 ``d`` is a dummy variable to prevent automatic evaluation
1963 of trigonometric/hyperbolic functions.
1966 References
1967 ==========
1969 .. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
1970 """
1972 def f(rv):
1973 if not isinstance(rv, HyperbolicFunction):
1974 return rv
1975 a = rv.args[0]
1976 a = a*d if not a.is_Add else Add._from_args([i*d for i in a.args])
1977 if isinstance(rv, sinh):
1978 return I*sin(a)
1979 elif isinstance(rv, cosh):
1980 return cos(a)
1981 elif isinstance(rv, tanh):
1982 return I*tan(a)
1983 elif isinstance(rv, coth):
1984 return cot(a)/I
1985 elif isinstance(rv, sech):
1986 return sec(a)
1987 elif isinstance(rv, csch):
1988 return csc(a)/I
1989 else:
1990 raise NotImplementedError('unhandled %s' % rv.func)
1992 return bottom_up(e, f)
1995def _osbornei(e, d):
1996 """Replace all trig functions with hyperbolic functions using
1997 the Osborne rule.
1999 Notes
2000 =====
2002 ``d`` is a dummy variable to prevent automatic evaluation
2003 of trigonometric/hyperbolic functions.
2005 References
2006 ==========
2008 .. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
2009 """
2011 def f(rv):
2012 if not isinstance(rv, TrigonometricFunction):
2013 return rv
2014 const, x = rv.args[0].as_independent(d, as_Add=True)
2015 a = x.xreplace({d: S.One}) + const*I
2016 if isinstance(rv, sin):
2017 return sinh(a)/I
2018 elif isinstance(rv, cos):
2019 return cosh(a)
2020 elif isinstance(rv, tan):
2021 return tanh(a)/I
2022 elif isinstance(rv, cot):
2023 return coth(a)*I
2024 elif isinstance(rv, sec):
2025 return sech(a)
2026 elif isinstance(rv, csc):
2027 return csch(a)*I
2028 else:
2029 raise NotImplementedError('unhandled %s' % rv.func)
2031 return bottom_up(e, f)
2034def hyper_as_trig(rv):
2035 """Return an expression containing hyperbolic functions in terms
2036 of trigonometric functions. Any trigonometric functions initially
2037 present are replaced with Dummy symbols and the function to undo
2038 the masking and the conversion back to hyperbolics is also returned. It
2039 should always be true that::
2041 t, f = hyper_as_trig(expr)
2042 expr == f(t)
2044 Examples
2045 ========
2047 >>> from sympy.simplify.fu import hyper_as_trig, fu
2048 >>> from sympy.abc import x
2049 >>> from sympy import cosh, sinh
2050 >>> eq = sinh(x)**2 + cosh(x)**2
2051 >>> t, f = hyper_as_trig(eq)
2052 >>> f(fu(t))
2053 cosh(2*x)
2055 References
2056 ==========
2058 .. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
2059 """
2060 from sympy.simplify.simplify import signsimp
2061 from sympy.simplify.radsimp import collect
2063 # mask off trig functions
2064 trigs = rv.atoms(TrigonometricFunction)
2065 reps = [(t, Dummy()) for t in trigs]
2066 masked = rv.xreplace(dict(reps))
2068 # get inversion substitutions in place
2069 reps = [(v, k) for k, v in reps]
2071 d = Dummy()
2073 return _osborne(masked, d), lambda x: collect(signsimp(
2074 _osbornei(x, d).xreplace(dict(reps))), S.ImaginaryUnit)
2077def sincos_to_sum(expr):
2078 """Convert products and powers of sin and cos to sums.
2080 Explanation
2081 ===========
2083 Applied power reduction TRpower first, then expands products, and
2084 converts products to sums with TR8.
2086 Examples
2087 ========
2089 >>> from sympy.simplify.fu import sincos_to_sum
2090 >>> from sympy.abc import x
2091 >>> from sympy import cos, sin
2092 >>> sincos_to_sum(16*sin(x)**3*cos(2*x)**2)
2093 7*sin(x) - 5*sin(3*x) + 3*sin(5*x) - sin(7*x)
2094 """
2096 if not expr.has(cos, sin):
2097 return expr
2098 else:
2099 return TR8(expand_mul(TRpower(expr)))