SymPy 无法求解三角方程组

SES*_*SES 3 python sympy

我试图让 SymPy 解决方程组,但它给了我一个错误说:

NotImplementedError: could not solve 3*sin(3*t0/2)*tan(t0) + 2*cos(3*t0/2) - 4
Run Code Online (Sandbox Code Playgroud)

有没有另一种方法可以解决方程组:

sin(x)+(y-x)cos(x)           = 0

-1.5(y-x)sin(1.5x)+cos(1.5x) = 2
Run Code Online (Sandbox Code Playgroud)

我用了 :

from sympy import *
solve([sin(x)+(y-x)cos(x), -1.5(y-x)sin(1.5x)+cos(1.5x)-2], x, y)
Run Code Online (Sandbox Code Playgroud)

小智 5

SymPy 可以用这个方程做得更好,但最终它等效于某个 10 次多项式,其根只能抽象地表示。我将描述可以采取的步骤并展示 SymPy 可以走多远。这是一个半手动的求解过程,应该更加自动化。

首先,不要在等式中放入 1.5 或其他浮点数。相反,引入一个系数a = Rational(3, 2)并使用它:

eq = [sin(x) + (y-x)*cos(x), -a*(y-x)*sin(a*x) + cos(a*x) - 2]
Run Code Online (Sandbox Code Playgroud)

变量 y 可以使用第一个方程来消除:y=x-tan(x),这对我们来说很容易看到,但 SymPy 有时会错过这个机会。让我们帮助它:

eq1 = eq[1].subs(y, x-tan(x))   #   3*sin(3*x/2)*tan(x)/2 + cos(3*x/2) - 2
Run Code Online (Sandbox Code Playgroud)

由于不同参数的三角函数的这种混合,solve并且solveset(另一种 SymPy 求解器)放弃了方程。我们中的一些人记得在学生时代,三角函数可以表示为半参数的正切的有理函数,所以让我们这样做:用 重写方程tan。

eq2 = eq1.rewrite(tan)   #   (-tan(3*x/4)**2 + 1)/(tan(3*x/4)**2 + 1) - 2 + 3*tan(3*x/4)*tan(x)/(tan(3*x/4)**2 + 1)
Run Code Online (Sandbox Code Playgroud)

如前所述,这使争论减半。在三角函数中有像 x/4 这样的分数是不好的。引入一个新符号 ,var('u')并使 u = x/4:

eq3 = eq2.subs(x, 4*u)   #   (-tan(3*u)**2 + 1)/(tan(3*u)**2 + 1) - 2 + 3*tan(3*u)*tan(4*u)/(tan(3*u)**2 + 1)
Run Code Online (Sandbox Code Playgroud)

现在我们可以用 展开所有这些切线tan(u),使用expand_trig。等式变得更长:

eq4 = expand_trig(eq3)  #  (1 - (-tan(u)**3 + 3*tan(u))**2/(-3*tan(u)**2 + 1)**2)/(1 + (-tan(u)**3 + 3*tan(u))**2/(-3*tan(u)**2 + 1)**2) - 2 + 3*(-4*tan(u)**3 + 4*tan(u))*(-tan(u)**3 + 3*tan(u))/((1 + (-tan(u)**3 + 3*tan(u))**2/(-3*tan(u)**2 + 1)**2)*(-3*tan(u)**2 + 1)*(tan(u)**4 - 6*tan(u)**2 + 1))
Run Code Online (Sandbox Code Playgroud)

但它也更简单,因为tan(u)可以将其视为另一个未知数,例如v。

eq5 = eq4.subs(tan(u), v)  #  (1 - (-v**3 + 3*v)**2/(-3*v**2 + 1)**2)/(1 + (-v**3 + 3*v)**2/(-3*v**2 + 1)**2) - 2 + 3*(-4*v**3 + 4*v)*(-v**3 + 3*v)/((1 + (-v**3 + 3*v)**2/(-3*v**2 + 1)**2)*(-3*v**2 + 1)*(v**4 - 6*v**2 + 1))
Run Code Online (Sandbox Code Playgroud)

太好了,现在我们有了一个有理函数。可以用solveset(eq5, x). 默认情况下solveset 给出所有复杂的解决方案,我们只需要其中的实根,所以让我们将域指定为实数:

vsol = list(solveset(eq5, v, domain=S.Reals))
Run Code Online (Sandbox Code Playgroud)

这些没有代数公式,所以它们被抽象地记录下来,但这些是我们可以使用的实际数字:

[CRootOf(3*v**10 + 9*v**8 - 78*v**6 + 22*v**4 - 21*v**2 + 1, 0),
 CRootOf(3*v**10 + 9*v**8 - 78*v**6 + 22*v**4 - 21*v**2 + 1, 1),
 CRootOf(3*v**10 + 9*v**8 - 78*v**6 + 22*v**4 - 21*v**2 + 1, 2),
 CRootOf(3*v**10 + 9*v**8 - 78*v**6 + 22*v**4 - 21*v**2 + 1, 3)]
Run Code Online (Sandbox Code Playgroud)

例如,我们现在可以回到 x 和 y,并评估解决方案:

xsol = [4*atan(v) for v in vsol] 
ysol = [x - tan(x) for x in xsol]
numsol = [(N(x), N(y)) for x, y in zip(xsol, ysol)]
Run Code Online (Sandbox Code Playgroud)

数值是

[(-4.35962510714700, -1.64344290066272),
 (-0.877886785847899, 0.326585146723377),
 (0.877886785847899, -0.326585146723377),
 (4.35962510714700, 1.64344290066272)]
Run Code Online (Sandbox Code Playgroud)

当然还有无穷多,因为切线是周期性的。最后,让我们检查这些实际工作:

residuals = [[e.subs({x: xv, y: yv}) for e in eq] for xv, yv in numsol]
Run Code Online (Sandbox Code Playgroud)

这些是一堆 1e-15 或更少阶的数字,所以是的,这些方程在机器精度范围内成立。

与我们从 SciPy 或其他数值求解器获得的纯数值解不同,这些解可以以任何精度进行评估,而无需重复该过程。例如,第一个 x 解的 50 位数字:

xsol[0].evalf(50)  #   -4.3596251071470021258397061103704574594477338857831
Run Code Online (Sandbox Code Playgroud)