傅洪光的三角函数简化

Fu 等人实现的 trigsimp 算法。

Fu 算法背后的思想是使用学生在预积分课程中学习的一系列规则。这些规则是启发式应用的,并且它使用贪心算法同时应用多个规则并选择叶数最少的结果。

在转换规则中,单个规则应用于表达式树。以下仅仅是助记性质的;请参阅文档字符串以获取示例。

  • TR0() - 简化表达式

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

  • TR2() - 正切-余切到正弦-余弦比

  • TR2i() - 正弦-余弦比到正切

  • TR3() - 角度规范化

  • TR4() - 在特殊角度下的函数

  • TR5() - 正弦的幂到余弦的幂

  • TR6() - 余弦的幂次转换为正弦的幂次

  • TR7() - 减少余弦功率(增加角度)

  • TR8() - 将正弦-余弦乘积展开为和

  • TR9() - 将正弦-余弦的和转换为乘积

  • TR10() - 分离 sin-cos 参数

  • TR10i() - 收集正弦-余弦参数

  • TR11() - 减少双角

  • TR12() - 分离 tan 参数

  • TR12i() - 收集 tan 参数

  • TR13() - 展开 tan-cot 的乘积

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

  • TR14() - 将 sin 或 cos 的幂分解为 cos 或 sin 的幂

  • TR15() - 将正弦的负幂转换为余切的幂

  • TR16() - 余弦的负幂到正切的幂

  • TR22() - 将正切-余切幂转换为正割-余割函数的负幂

  • TR111() - 负的 sin-cos-tan 幂转换为 csc-sec-cot

有4种组合变换(CTR1 - CTR4),在这些变换中,应用一系列变换,并从几个选项中选择最简单的表达式。

最后,有2个规则列表(RL1和RL2),它们应用一系列的变换和组合变换,以及 fu 算法本身,该算法应用规则和规则列表并选择最佳表达式。还有一个函数 L ,它计算表达式中出现的三角函数的数量。

除了 TR0,表达式的重写不是由变换完成的。例如,TR10i 在和式中找到成对的项,这些项的形式类似于 cos(x)*cos(y) + sin(x)*sin(y)。这样的表达式在表达式的自底向上遍历中被针对,但没有尝试进行操作使其出现。例如,

以下示例的设置:

>>> 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)

如果表达式被置于“正常”形式(带有公分母),那么转换是成功的:

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

TR11 的行为类似。它将双角重写为较小的角度,但不会对结果进行任何简化。

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

诱惑在于试图让这些TR规则“更智能”,但这实际上应该在更高层次上完成;TR规则应尽量保持“做好一件事”的原则。然而,有一个例外。在TR10i和TR9中,即使每个术语都乘以一个公因子,它们也能被识别:

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

使用 factor_terms 进行因式分解,但它类似于“JIT”,即延迟到被认为有必要时才进行。此外,如果因式分解无助于简化,则不会保留,因此 a*cos(x)*cos(y) + a*sin(x)*sin(z) 不会变成因式分解(但在三角函数意义上未简化)的表达式:

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

在某些情况下,分解可能是一个好主意,但用户需要自己做出这个决定。例如:

>>> 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()

在展开状态下,存在近1000个三角函数:

>>> L(expr)
932

如果首先对表达式进行因式分解,这会花费时间,但得到的表达式会非常快速地被转换:

>>> 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

如果使用未展开的表达式,转换所需的时间会更长,但不会像先因式分解再转换那样长:

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

因此,在 TR10i 中既不使用展开也不使用因式分解:如果表达式已经是因式分解的(或部分因式分解的),那么使用 trig=True 进行展开会破坏已知的内容并花费更长时间;如果表达式是展开的,因式分解可能比直接应用变换本身花费更长时间。

尽管这些算法应该是规范的,总是给出相同的结果,但它们可能不会产生最佳结果。一般来说,这是简化的本质,因为搜索所有可能的变换路径是非常昂贵的。这里有一个简单的例子。以下和中有6项:

>>> 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

意外地,fu 给出了最好的结果:

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

但如果将不同的术语结合起来,可能会得到一个不太理想的结果,需要一些额外的工作来获得更好的简化,但仍然不如理想。以下展示了一种 expr 的替代形式,一旦采取了某个步骤,它就会阻碍最佳简化,因为它会导致死胡同:

>>> 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

这是一个展示相同行为的简短表达式:

>>> 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

如果不走运或尝试所有可能的参数配对,最终结果可能不尽如人意,且在没有更好的启发式方法或暴力尝试所有可能性之前,无法找到。

规则

sympy.simplify.fu.TR0(rv)[源代码][源代码]

有理多项式的简化,尝试简化表达式,例如合并类似 3*x + 2*x 等项…

sympy.simplify.fu.TR1(rv)[源代码][源代码]

将 sec, csc 替换为 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)
sympy.simplify.fu.TR2(rv)[源代码][源代码]

将 tan 和 cot 替换为 sin/cos 和 cos/sin

示例

>>> 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)[源代码][源代码]
将涉及 sin 和 cos 的比率转换如下:

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

示例

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

分子和分母的幂次也被识别

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

除非假设允许(即,对于分子和分母,底数必须为正数或指数必须为整数),否则转换不会发生。

>>> TR2i(sin(x)**a/(cos(x) + 1)**a)
sin(x)**a/(cos(x) + 1)**a
sympy.simplify.fu.TR3(rv)[源代码][源代码]

诱导公式:示例 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)
-sin(x)
>>> cos(30*pi/2 + x)
-cos(x)
sympy.simplify.fu.TR4(rv)[源代码][源代码]

识别特殊角度的值。

示例

>>> 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)[源代码][源代码]

用 1 - cos(x)**2 替换 sin**2。

参见 _TR56 文档字符串以了解 maxpow 的高级用法。

示例

>>> 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)[源代码][源代码]

用 1 - sin(x)**2 替换 cos**2。

参见 _TR56 文档字符串以了解 maxpow 的高级用法。

示例

>>> 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)[源代码][源代码]

降低 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)[源代码][源代码]

cos 和/或 sin 的乘积转换为 cos 和/或 sin 项的和或差。

示例

>>> 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)[源代码][源代码]

cossin 项的和作为 cossin 的乘积。

示例

>>> 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)

如果 TR9 没有进行任何更改,则不会对表达式进行重新排列。例如,尽管尝试了对公共项进行因式分解,但如果因式分解后的表达式没有变化,将返回原始表达式:

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

cossin 中分开求和。

示例

>>> 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)[源代码][源代码]

积和式到和函数的转换。

示例

>>> 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)[源代码][源代码]

双角到乘积的函数。base 参数可以用来指示未加倍的角度是什么,例如,如果 3*pi/7 是基数,那么参数为 6*pi/7 的余弦和正弦函数将被替换。

示例

>>> 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)

如果参数只是整数,除非提供了基数,否则不会进行任何更改:

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

这里有一个微妙的问题,即自动简化会将一些较大的角度转换为较小的角度。

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

6*pi/7 角现在为 pi/7,但可以通过提供 3*pi/7 基底来使用 TR11 进行目标定位:

>>> 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)[源代码][源代码]

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)
sympy.simplify.fu.TR12i(rv)[源代码][源代码]

将 tan 参数组合为 (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))
sympy.simplify.fu.TR13(rv)[源代码][源代码]

改变 tancot 的乘积。

示例

>>> 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)[源代码][源代码]

返回 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))
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))

有时自动简化会导致无法识别幂。例如,在以下情况下,cos(4*pi/7) 自动简化为 -cos(3*pi/7),因此只有3个项中的2个被识别:

>>> TRmorrie(cos(pi/7)*cos(2*pi/7)*cos(4*pi/7))
-sin(3*pi/7)*cos(3*pi/7)/(4*sin(pi/7))

TR8 的一次触碰将表达式解析为一个有理数

>>> TR8(_)
-1/8

在这种情况下,如果 eq 未简化,答案可以直接获得:

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

但如果使用TR3使角度规范化,那么答案在没有进一步工作的情况下不会被简化:

>>> 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

原始表达式本应直接通过 TR8 解析为 1/16,然而:

>>> TR8(eq)
1/16
sympy.simplify.fu.TR14(rv, first=True)[源代码][源代码]

将分解的正弦和余弦幂次方转换为更简单的表达式。

示例

>>> 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)[源代码][源代码]

将 sin(x)**-2 转换为 1 + cot(x)**2。

参见 _TR56 文档字符串以了解 maxpow 的高级用法。

示例

>>> 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)[源代码][源代码]

将 cos(x)**-2 转换为 1 + tan(x)**2。

参见 _TR56 文档字符串以了解 maxpow 的高级用法。

示例

>>> 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)[源代码][源代码]

将 f(x)**-i 转换为 g(x)**i,其中 i 是整数或底数为正,且 f, g 分别为:tan, cot; sin, csc; 或 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)[源代码][源代码]

将 tan(x)**2 转换为 sec(x)**2 - 1 ,将 cot(x)**2 转换为 csc(x)**2 - 1。

参见 _TR56 文档字符串以了解 maxpow 的高级用法。

示例

>>> 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)[源代码][源代码]

将 sin(x)**n 和 cos(x)**n 转换为正 n 的和。

参考文献

示例

>>> 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>>)[源代码][源代码]

尝试通过使用Fu等人在算法中给出的转换规则来简化表达式。

fu() 将尝试最小化目标函数 measure。默认情况下,首先最小化三角项的数量,然后最小化总操作数。

参考文献

示例

>>> 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 示例

>>> 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 示例

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

CTR3 示例

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

CTR4 示例

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

示例 1

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

示例 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

示例 3

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

目标函数示例

>>> 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)

注释

这项工作由Dimitar Vlahovski在“电子系统”技术学校开始(2011年11月30日)。

除了TR13之外,其他规则并非来自原始论文,而是在SymPy中扩展的。

参考文献