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 expressionTR1()
- sec-csc to cos-sinTR2()
- tan-cot to sin-cos ratioTR2i()
- sin-cos ratio to tanTR3()
- angle canonicalizationTR4()
- functions at special anglesTR5()
- powers of sin to powers of cosTR6()
- powers of cos to powers of sinTR7()
- reduce cos power (increase angle)TR8()
- expand products of sin-cos to sumsTR9()
- contract sums of sin-cos to productsTR10()
- separate sin-cos argumentsTR10i()
- collect sin-cos argumentsTR11()
- reduce double anglesTR12()
- separate tan argumentsTR12i()
- collect tan argumentsTR13()
- expand product of tan-cotTRmorrie()
- 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 powerTR15()
- negative powers of sin to cot powerTR16()
- negative powers of cos to tan powerTR22()
- tan-cot powers to negative powers of sec-csc functionsTR111()
- 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)
(2*sin(1)*cos(1))**a/cos(1)**a
>>> powsimp(_)
(2*sin(1))**a
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)
932
If the expression where 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))
0.86
>>> clock(lambda: TR10i(expr), 3)
0.016
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)
0.28
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
True
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.
Rules#
- sympy.simplify.fu.TR0(rv)[source]#
Simplification of rational polynomials, trying to simplify the expression, e.g. combine things like 3*x + 2*x, etc….
- sympy.simplify.fu.TR1(rv)[source]#
Replace sec, csc with 1/cos, 1/sin
Examples
>>> 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)
- sympy.simplify.fu.TR2(rv)[source]#
Replace tan and cot with sin/cos and cos/sin
Examples
>>> from sympy.simplify.fu import TR2 >>> from sympy.abc import x >>> from sympy import tan, cot, sin, cos >>> TR2(tan(x)) sin(x)/cos(x) >>> TR2(cot(x)) cos(x)/sin(x) >>> TR2(tan(tan(x) - sin(x)/cos(x))) 0
- 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
Examples
>>> from sympy.simplify.fu import TR2i >>> from sympy.abc import x, a >>> from sympy import sin, cos >>> TR2i(sin(x)/cos(x)) tan(x)
Powers of the numerator and denominator are also recognized
>>> TR2i(sin(x)**2/(cos(x) + 1)**2, half=True) tan(x/2)**2
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
- sympy.simplify.fu.TR3(rv)[source]#
Induced formula: example sin(-a) = -sin(a)
Examples
>>> 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) -sin(x) >>> cos(30*pi/2 + x) -cos(x)
- sympy.simplify.fu.TR4(rv)[source]#
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) –
Examples
>>> 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
andpow
.Examples
>>> 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 sin(x)**(-2) >>> 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
andpow
.Examples
>>> 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 cos(x)**(-2) >>> TR6(cos(x)**4) (1 - sin(x)**2)**2
- sympy.simplify.fu.TR7(rv)[source]#
Lowering the degree of cos(x)**2.
Examples
>>> 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/orsin
to a sum or difference ofcos
and orsin
terms.Examples
>>> 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
- sympy.simplify.fu.TR9(rv)[source]#
Sum of
cos
orsin
terms as a product ofcos
orsin
.Examples
>>> from sympy.simplify.fu import TR9 >>> from sympy import cos, sin >>> TR9(cos(1) + cos(2)) 2*cos(1/2)*cos(3/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
andsin
.Examples
>>> 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)
- sympy.simplify.fu.TR10i(rv)[source]#
Sum of products to function of sum.
Examples
>>> 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)) cos(2) >>> 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.Examples
>>> from sympy.simplify.fu import TR11 >>> from sympy import cos, sin, pi >>> from sympy.abc import x >>> TR11(sin(2*x)) 2*sin(x)*cos(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)) 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
.Examples
>>> 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)
- sympy.simplify.fu.TR12i(rv)[source]#
Combine tan arguments as (tan(y) + tan(x))/(tan(x)*tan(y) - 1) -> -tan(x + y).
Examples
>>> 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))
- sympy.simplify.fu.TR13(rv)[source]#
Change products of
tan
orcot
.Examples
>>> 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)
- sympy.simplify.fu.TRmorrie(rv)[source]#
Returns cos(x)*cos(2*x)*…*cos(2**(k-1)*x) -> sin(2**k*x)/(2**k*sin(x))
Examples
>>> 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)) sin(4*x)/(4*sin(x)) >>> TRmorrie(7*Mul(*[cos(x) for x in range(10)])) 7*sin(12)*sin(16)*cos(5)*cos(7)*cos(9)/(64*sin(1)*sin(3))
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)) -sin(3*pi/7)*cos(3*pi/7)/(4*sin(pi/7))
A touch by TR8 resolves the expression to a Rational
>>> TR8(_) -1/8
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) 1/16
But if angles are made canonical with TR3 then the answer is not simplified without further work:
>>> TR3(eq) sin(pi/18)*cos(pi/9)*cos(2*pi/9)/2 >>> TRmorrie(_) sin(pi/18)*sin(4*pi/9)/(8*sin(pi/9)) >>> TR8(_) cos(7*pi/18)/(16*sin(pi/9)) >>> TR3(_) 1/16
The original expression would have resolve to 1/16 directly with TR8, however:
>>> TR8(eq) 1/16
References
- sympy.simplify.fu.TR14(rv, first=True)[source]#
Convert factored powers of sin and cos identities into simpler expressions.
Examples
>>> from sympy.simplify.fu import TR14 >>> from sympy.abc import x, y >>> from sympy import cos, sin >>> TR14((cos(x) - 1)*(cos(x) + 1)) -sin(x)**2 >>> TR14((sin(x) - 1)*(sin(x) + 1)) -cos(x)**2 >>> 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
andpow
.Examples
>>> from sympy.simplify.fu import TR15 >>> from sympy.abc import x >>> from sympy import sin >>> TR15(1 - 1/sin(x)**2) -cot(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
andpow
.Examples
>>> from sympy.simplify.fu import TR16 >>> from sympy.abc import x >>> from sympy import cos >>> TR16(1 - 1/cos(x)**2) -tan(x)**2
- sympy.simplify.fu.TR111(rv)[source]#
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.Examples
>>> 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
andpow
.Examples
>>> from sympy.simplify.fu import TR22 >>> from sympy.abc import x >>> from sympy import tan, cot >>> TR22(1 + tan(x)**2) sec(x)**2 >>> TR22(1 + cot(x)**2) csc(x)**2
- sympy.simplify.fu.TRpower(rv)[source]#
Convert sin(x)**n and cos(x)**n with positive n to sums.
Examples
>>> 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)
References
- 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 functionmeasure
. By default this first minimizes the number of trig terms and then minimizes the number of total operations.Examples
>>> 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)) 3/2 >>> 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) sin(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)) sin(pi/18) >>> fu(cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9)) 1/16
Example 3
>>> fu(tan(7*pi/18)+tan(5*pi/18)-sqrt(3)*tan(5*pi/18)*tan(7*pi/18)) -sqrt(3)
Objective function example
>>> fu(sin(x)/cos(x)) # default objective function tan(x) >>> fu(sin(x)/cos(x), measure=lambda x: -x.count_ops()) # maximize op count sin(x)/cos(x)
References
Notes#
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.