Hongguang Fu’s Trigonometric Simplification

Implementation of the trigsimp algorithm by Fu et al.

The idea behind the Fu algorithm is to use a sequence of rules that students learn during their pre-calculus courses. The rules are applied heuristically and it uses a greedy algorithm to apply multiple rules simultaneously and choose the result with the least leaf counts.

There are transform rules in which a single rule is applied to the expression tree. The following are just mnemonic in nature; see the docstrings for examples.

  • TR0() - simplify expression

  • TR1() - sec-csc to cos-sin

  • TR2() - tan-cot to sin-cos ratio

  • TR2i() - sin-cos ratio to tan

  • TR3() - angle canonicalization

  • TR4() - functions at special angles

  • TR5() - powers of sin to powers of cos

  • TR6() - powers of cos to powers of sin

  • TR7() - reduce cos power (increase angle)

  • TR8() - expand products of sin-cos to sums

  • TR9() - contract sums of sin-cos to products

  • TR10() - separate sin-cos arguments

  • TR10i() - collect sin-cos arguments

  • TR11() - reduce double angles

  • TR12() - separate tan arguments

  • TR12i() - collect tan arguments

  • TR13() - expand product of tan-cot

  • TRmorrie() - prod(cos(x*2**i), (i, 0, k - 1)) -> sin(2**k*x)/(2**k*sin(x))

  • TR14() - factored powers of sin or cos to cos or sin power

  • TR15() - negative powers of sin to cot power

  • TR16() - negative powers of cos to tan power

  • TR22() - tan-cot powers to negative powers of sec-csc functions

  • TR111() - negative sin-cos-tan powers to csc-sec-cot

There are 4 combination transforms (CTR1 - CTR4) in which a sequence of transformations are applied and the simplest expression is selected from a few options.

Finally, there are the 2 rule lists (RL1 and RL2), which apply a sequence of transformations and combined transformations, and the fu algorithm itself, which applies rules and rule lists and selects the best expressions. There is also a function L which counts the number of trigonometric functions that appear in the expression.

Other than TR0, re-writing of expressions is not done by the transformations. e.g. TR10i finds pairs of terms in a sum that are in the form like cos(x)*cos(y) + sin(x)*sin(y). Such expression are targeted in a bottom-up traversal of the expression, but no manipulation to make them appear is attempted. For example,

Set-up for examples below:

>>> from sympy.simplify.fu import fu, L, TR9, TR10i, TR11
>>> from sympy import factor, sin, cos, powsimp
>>> from sympy.abc import x, y, z, a
>>> from time import time
>>> eq = cos(x + y)/cos(x)
>>> TR10i(eq.expand(trig=True))
-sin(x)*sin(y)/cos(x) + cos(y)

If the expression is put in “normal” form (with a common denominator) then the transformation is successful:

>>> TR10i(_.normal())
cos(x + y)/cos(x)

TR11’s behavior is similar. It rewrites double angles as smaller angles but doesn’t do any simplification of the result.

>>> TR11(sin(2)**a*cos(1)**(-a), 1)
>>> powsimp(_)

The temptation is to try make these TR rules “smarter” but that should really be done at a higher level; the TR rules should try maintain the “do one thing well” principle. There is one exception, however. In TR10i and TR9 terms are recognized even when they are each multiplied by a common factor:

>>> fu(a*cos(x)*cos(y) + a*sin(x)*sin(y))
a*cos(x - y)

Factoring with factor_terms is used but it is “JIT”-like, being delayed until it is deemed necessary. Furthermore, if the factoring does not help with the simplification, it is not retained, so a*cos(x)*cos(y) + a*sin(x)*sin(z) does not become a factored (but unsimplified in the trigonometric sense) expression:

>>> fu(a*cos(x)*cos(y) + a*sin(x)*sin(z))
a*sin(x)*sin(z) + a*cos(x)*cos(y)

In some cases factoring might be a good idea, but the user is left to make that decision. For example:

>>> expr=((15*sin(2*x) + 19*sin(x + y) + 17*sin(x + z) + 19*cos(x - z) +
... 25)*(20*sin(2*x) + 15*sin(x + y) + sin(y + z) + 14*cos(x - z) +
... 14*cos(y - z))*(9*sin(2*y) + 12*sin(y + z) + 10*cos(x - y) + 2*cos(y -
... z) + 18)).expand(trig=True).expand()

In the expanded state, there are nearly 1000 trig functions:

>>> L(expr)

If the expression were factored first, this would take time but the resulting expression would be transformed very quickly:

>>> def clock(f, n=2):
...    t=time(); f(); return round(time()-t, n)
>>> clock(lambda: factor(expr))  
>>> clock(lambda: TR10i(expr), 3)  

If the unexpanded expression is used, the transformation takes longer but not as long as it took to factor it and then transform it:

>>> clock(lambda: TR10i(expr), 2)  

So neither expansion nor factoring is used in TR10i: if the expression is already factored (or partially factored) then expansion with trig=True would destroy what is already known and take longer; if the expression is expanded, factoring may take longer than simply applying the transformation itself.

Although the algorithms should be canonical, always giving the same result, they may not yield the best result. This, in general, is the nature of simplification where searching all possible transformation paths is very expensive. Here is a simple example. There are 6 terms in the following sum:

>>> expr = (sin(x)**2*cos(y)*cos(z) + sin(x)*sin(y)*cos(x)*cos(z) +
... sin(x)*sin(z)*cos(x)*cos(y) + sin(y)*sin(z)*cos(x)**2 + sin(y)*sin(z) +
... cos(y)*cos(z))
>>> args = expr.args

Serendipitously, fu gives the best result:

>>> fu(expr)
3*cos(y - z)/2 - cos(2*x + y + z)/2

But if different terms were combined, a less-optimal result might be obtained, requiring some additional work to get better simplification, but still less than optimal. The following shows an alternative form of expr that resists optimal simplification once a given step is taken since it leads to a dead end:

>>> TR9(-cos(x)**2*cos(y + z) + 3*cos(y - z)/2 +
...     cos(y + z)/2 + cos(-2*x + y + z)/4 - cos(2*x + y + z)/4)
sin(2*x)*sin(y + z)/2 - cos(x)**2*cos(y + z) + 3*cos(y - z)/2 + cos(y + z)/2

Here is a smaller expression that exhibits the same behavior:

>>> a = sin(x)*sin(z)*cos(x)*cos(y) + sin(x)*sin(y)*cos(x)*cos(z)
>>> TR10i(a)
sin(x)*sin(y + z)*cos(x)
>>> newa = _
>>> TR10i(expr - a)  # this combines two more of the remaining terms
sin(x)**2*cos(y)*cos(z) + sin(y)*sin(z)*cos(x)**2 + cos(y - z)
>>> TR10i(_ + newa) == _ + newa  # but now there is no more simplification

Without getting lucky or trying all possible pairings of arguments, the final result may be less than optimal and impossible to find without better heuristics or brute force trial of all possibilities.



Simplification of rational polynomials, trying to simplify the expression, e.g. combine things like 3*x + 2*x, etc….


Replace sec, csc with 1/cos, 1/sin


>>> from sympy.simplify.fu import TR1, sec, csc
>>> from sympy.abc import x
>>> TR1(2*csc(x) + sec(x))
1/cos(x) + 2/sin(x)

Replace tan and cot with sin/cos and cos/sin


>>> from sympy.simplify.fu import TR2
>>> from sympy.abc import x
>>> from sympy import tan, cot, sin, cos
>>> TR2(tan(x))
>>> TR2(cot(x))
>>> TR2(tan(tan(x) - sin(x)/cos(x)))
sympy.simplify.fu.TR2i(rv, half=False)[source]
Converts ratios involving sin and cos as follows::

sin(x)/cos(x) -> tan(x) sin(x)/(cos(x) + 1) -> tan(x/2) if half=True


>>> from sympy.simplify.fu import TR2i
>>> from sympy.abc import x, a
>>> from sympy import sin, cos
>>> TR2i(sin(x)/cos(x))

Powers of the numerator and denominator are also recognized

>>> TR2i(sin(x)**2/(cos(x) + 1)**2, half=True)

The transformation does not take place unless assumptions allow (i.e. the base must be positive or the exponent must be an integer for both numerator and denominator)

>>> TR2i(sin(x)**a/(cos(x) + 1)**a)
sin(x)**a/(cos(x) + 1)**a

Induced formula: example sin(-a) = -sin(a)


>>> from sympy.simplify.fu import TR3
>>> from sympy.abc import x, y
>>> from sympy import pi
>>> from sympy import cos
>>> TR3(cos(y - x*(y - x)))
cos(x*(x - y) + y)
>>> cos(pi/2 + x)
>>> cos(30*pi/2 + x)

Identify values of special angles.

A= 0 Pi/6 Pi/4 Pi/3 Pi/2

sin(a) 0 1/2 sqrt(2)/2 sqrt(3)/2 1 cos(a) 1 sqrt(3)/2 sqrt(2)/2 1/2 0 tan(a) 0 sqt(3)/3 1 sqrt(3) –


>>> from sympy import pi
>>> from sympy import cos, sin, tan, cot
>>> for s in (0, pi/6, pi/4, pi/3, pi/2):
...    print('%s %s %s %s' % (cos(s), sin(s), tan(s), cot(s)))
1 0 0 zoo
sqrt(3)/2 1/2 sqrt(3)/3 sqrt(3)
sqrt(2)/2 sqrt(2)/2 1 1
1/2 sqrt(3)/2 sqrt(3) sqrt(3)/3
0 1 zoo 0
sympy.simplify.fu.TR5(rv, max=4, pow=False)[source]

Replacement of sin**2 with 1 - cos(x)**2.

See _TR56 docstring for advanced use of max and pow.


>>> from sympy.simplify.fu import TR5
>>> from sympy.abc import x
>>> from sympy import sin
>>> TR5(sin(x)**2)
1 - cos(x)**2
>>> TR5(sin(x)**-2)  # unchanged
>>> TR5(sin(x)**4)
(1 - cos(x)**2)**2
sympy.simplify.fu.TR6(rv, max=4, pow=False)[source]

Replacement of cos**2 with 1 - sin(x)**2.

See _TR56 docstring for advanced use of max and pow.


>>> from sympy.simplify.fu import TR6
>>> from sympy.abc import x
>>> from sympy import cos
>>> TR6(cos(x)**2)
1 - sin(x)**2
>>> TR6(cos(x)**-2)  #unchanged
>>> TR6(cos(x)**4)
(1 - sin(x)**2)**2

Lowering the degree of cos(x)**2.


>>> from sympy.simplify.fu import TR7
>>> from sympy.abc import x
>>> from sympy import cos
>>> TR7(cos(x)**2)
cos(2*x)/2 + 1/2
>>> TR7(cos(x)**2 + 1)
cos(2*x)/2 + 3/2
sympy.simplify.fu.TR8(rv, first=True)[source]

Converting products of cos and/or sin to a sum or difference of cos and or sin terms.


>>> from sympy.simplify.fu import TR8
>>> from sympy import cos, sin
>>> TR8(cos(2)*cos(3))
cos(5)/2 + cos(1)/2
>>> TR8(cos(2)*sin(3))
sin(5)/2 + sin(1)/2
>>> TR8(sin(2)*sin(3))
-cos(5)/2 + cos(1)/2

Sum of cos or sin terms as a product of cos or sin.


>>> from sympy.simplify.fu import TR9
>>> from sympy import cos, sin
>>> TR9(cos(1) + cos(2))
>>> TR9(cos(1) + 2*sin(1) + 2*sin(2))
cos(1) + 4*sin(3/2)*cos(1/2)

If no change is made by TR9, no re-arrangement of the expression will be made. For example, though factoring of common term is attempted, if the factored expression was not changed, the original expression will be returned:

>>> TR9(cos(3) + cos(3)*cos(2))
cos(3) + cos(2)*cos(3)
sympy.simplify.fu.TR10(rv, first=True)[source]

Separate sums in cos and sin.


>>> from sympy.simplify.fu import TR10
>>> from sympy.abc import a, b, c
>>> from sympy import cos, sin
>>> TR10(cos(a + b))
-sin(a)*sin(b) + cos(a)*cos(b)
>>> TR10(sin(a + b))
sin(a)*cos(b) + sin(b)*cos(a)
>>> TR10(sin(a + b + c))
(-sin(a)*sin(b) + cos(a)*cos(b))*sin(c) +     (sin(a)*cos(b) + sin(b)*cos(a))*cos(c)

Sum of products to function of sum.


>>> from sympy.simplify.fu import TR10i
>>> from sympy import cos, sin, sqrt
>>> from sympy.abc import x
>>> TR10i(cos(1)*cos(3) + sin(1)*sin(3))
>>> TR10i(cos(1)*sin(3) + sin(1)*cos(3) + cos(3))
cos(3) + sin(4)
>>> TR10i(sqrt(2)*cos(x)*x + sqrt(6)*sin(x)*x)
2*sqrt(2)*x*sin(x + pi/6)
sympy.simplify.fu.TR11(rv, base=None)[source]

Function of double angle to product. The base argument can be used to indicate what is the un-doubled argument, e.g. if 3*pi/7 is the base then cosine and sine functions with argument 6*pi/7 will be replaced.


>>> from sympy.simplify.fu import TR11
>>> from sympy import cos, sin, pi
>>> from sympy.abc import x
>>> TR11(sin(2*x))
>>> TR11(cos(2*x))
-sin(x)**2 + cos(x)**2
>>> TR11(sin(4*x))
4*(-sin(x)**2 + cos(x)**2)*sin(x)*cos(x)
>>> TR11(sin(4*x/3))
4*(-sin(x/3)**2 + cos(x/3)**2)*sin(x/3)*cos(x/3)

If the arguments are simply integers, no change is made unless a base is provided:

>>> TR11(cos(2))
>>> TR11(cos(4), 2)
-sin(2)**2 + cos(2)**2

There is a subtle issue here in that autosimplification will convert some higher angles to lower angles

>>> cos(6*pi/7) + cos(3*pi/7)
-cos(pi/7) + cos(3*pi/7)

The 6*pi/7 angle is now pi/7 but can be targeted with TR11 by supplying the 3*pi/7 base:

>>> TR11(_, 3*pi/7)
-sin(3*pi/7)**2 + cos(3*pi/7)**2 + cos(3*pi/7)
sympy.simplify.fu.TR12(rv, first=True)[source]

Separate sums in tan.


>>> from sympy.abc import x, y
>>> from sympy import tan
>>> from sympy.simplify.fu import TR12
>>> TR12(tan(x + y))
(tan(x) + tan(y))/(-tan(x)*tan(y) + 1)

Combine tan arguments as (tan(y) + tan(x))/(tan(x)*tan(y) - 1) -> -tan(x + y).


>>> from sympy.simplify.fu import TR12i
>>> from sympy import tan
>>> from sympy.abc import a, b, c
>>> ta, tb, tc = [tan(i) for i in (a, b, c)]
>>> TR12i((ta + tb)/(-ta*tb + 1))
tan(a + b)
>>> TR12i((ta + tb)/(ta*tb - 1))
-tan(a + b)
>>> TR12i((-ta - tb)/(ta*tb - 1))
tan(a + b)
>>> eq = (ta + tb)/(-ta*tb + 1)**2*(-3*ta - 3*tc)/(2*(ta*tc - 1))
>>> TR12i(eq.expand())
-3*tan(a + b)*tan(a + c)/(2*(tan(a) + tan(b) - 1))

Change products of tan or cot.


>>> from sympy.simplify.fu import TR13
>>> from sympy import tan, cot
>>> TR13(tan(3)*tan(2))
-tan(2)/tan(5) - tan(3)/tan(5) + 1
>>> TR13(cot(3)*cot(2))
cot(2)*cot(5) + 1 + cot(3)*cot(5)

Returns cos(x)*cos(2*x)*…*cos(2**(k-1)*x) -> sin(2**k*x)/(2**k*sin(x))


>>> from sympy.simplify.fu import TRmorrie, TR8, TR3
>>> from sympy.abc import x
>>> from sympy import Mul, cos, pi
>>> TRmorrie(cos(x)*cos(2*x))
>>> TRmorrie(7*Mul(*[cos(x) for x in range(10)]))

Sometimes autosimplification will cause a power to be not recognized. e.g. in the following, cos(4*pi/7) automatically simplifies to -cos(3*pi/7) so only 2 of the 3 terms are recognized:

>>> TRmorrie(cos(pi/7)*cos(2*pi/7)*cos(4*pi/7))

A touch by TR8 resolves the expression to a Rational

>>> TR8(_)

In this case, if eq is unsimplified, the answer is obtained directly:

>>> eq = cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9)
>>> TRmorrie(eq)

But if angles are made canonical with TR3 then the answer is not simplified without further work:

>>> TR3(eq)
>>> TRmorrie(_)
>>> TR8(_)
>>> TR3(_)

The original expression would have resolve to 1/16 directly with TR8, however:

>>> TR8(eq)


sympy.simplify.fu.TR14(rv, first=True)[source]

Convert factored powers of sin and cos identities into simpler expressions.


>>> from sympy.simplify.fu import TR14
>>> from sympy.abc import x, y
>>> from sympy import cos, sin
>>> TR14((cos(x) - 1)*(cos(x) + 1))
>>> TR14((sin(x) - 1)*(sin(x) + 1))
>>> p1 = (cos(x) + 1)*(cos(x) - 1)
>>> p2 = (cos(y) - 1)*2*(cos(y) + 1)
>>> p3 = (3*(cos(y) - 1))*(3*(cos(y) + 1))
>>> TR14(p1*p2*p3*(x - 1))
-18*(x - 1)*sin(x)**2*sin(y)**4
sympy.simplify.fu.TR15(rv, max=4, pow=False)[source]

Convert sin(x)**-2 to 1 + cot(x)**2.

See _TR56 docstring for advanced use of max and pow.


>>> from sympy.simplify.fu import TR15
>>> from sympy.abc import x
>>> from sympy import sin
>>> TR15(1 - 1/sin(x)**2)
sympy.simplify.fu.TR16(rv, max=4, pow=False)[source]

Convert cos(x)**-2 to 1 + tan(x)**2.

See _TR56 docstring for advanced use of max and pow.


>>> from sympy.simplify.fu import TR16
>>> from sympy.abc import x
>>> from sympy import cos
>>> TR16(1 - 1/cos(x)**2)

Convert f(x)**-i to g(x)**i where either i is an integer or the base is positive and f, g are: tan, cot; sin, csc; or cos, sec.


>>> from sympy.simplify.fu import TR111
>>> from sympy.abc import x
>>> from sympy import tan
>>> TR111(1 - 1/tan(x)**2)
1 - cot(x)**2
sympy.simplify.fu.TR22(rv, max=4, pow=False)[source]

Convert tan(x)**2 to sec(x)**2 - 1 and cot(x)**2 to csc(x)**2 - 1.

See _TR56 docstring for advanced use of max and pow.


>>> from sympy.simplify.fu import TR22
>>> from sympy.abc import x
>>> from sympy import tan, cot
>>> TR22(1 + tan(x)**2)
>>> TR22(1 + cot(x)**2)

Convert sin(x)**n and cos(x)**n with positive n to sums.


>>> from sympy.simplify.fu import TRpower
>>> from sympy.abc import x
>>> from sympy import cos, sin
>>> TRpower(sin(x)**6)
-15*cos(2*x)/32 + 3*cos(4*x)/16 - cos(6*x)/32 + 5/16
>>> TRpower(sin(x)**3*cos(2*x)**4)
(3*sin(x)/4 - sin(3*x)/4)*(cos(4*x)/2 + cos(8*x)/8 + 3/8)


sympy.simplify.fu.fu(rv, measure=<function <lambda>>)[source]

Attempt to simplify expression by using transformation rules given in the algorithm by Fu et al.

fu() will try to minimize the objective function measure. By default this first minimizes the number of trig terms and then minimizes the number of total operations.


>>> from sympy.simplify.fu import fu
>>> from sympy import cos, sin, tan, pi, S, sqrt
>>> from sympy.abc import x, y, a, b
>>> fu(sin(50)**2 + cos(50)**2 + sin(pi/6))
>>> fu(sqrt(6)*cos(x) + sqrt(2)*sin(x))
2*sqrt(2)*sin(x + pi/3)

CTR1 example

>>> eq = sin(x)**4 - cos(y)**2 + sin(y)**2 + 2*cos(x)**2
>>> fu(eq)
cos(x)**4 - 2*cos(y)**2 + 2

CTR2 example

>>> fu(S.Half - cos(2*x)/2)

CTR3 example

>>> fu(sin(a)*(cos(b) - sin(b)) + cos(a)*(sin(b) + cos(b)))
sqrt(2)*sin(a + b + pi/4)

CTR4 example

>>> fu(sqrt(3)*cos(x)/2 + sin(x)/2)
sin(x + pi/3)

Example 1

>>> fu(1-sin(2*x)**2/4-sin(y)**2-cos(x)**4)
-cos(x)**2 + cos(y)**2

Example 2

>>> fu(cos(4*pi/9))
>>> fu(cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9))

Example 3

>>> fu(tan(7*pi/18)+tan(5*pi/18)-sqrt(3)*tan(5*pi/18)*tan(7*pi/18))

Objective function example

>>> fu(sin(x)/cos(x))  # default objective function
>>> fu(sin(x)/cos(x), measure=lambda x: -x.count_ops()) # maximize op count



This work was started by Dimitar Vlahovski at the Technological School “Electronic systems” (30.11.2011).

Beyond TR13, other rules are not from the original paper, but extended in SymPy.