通过代数或数值方法求多项式的根¶
使用SymPy以代数方式求解一元多项式的根。例如,求解\(ax^2 + bx + c\)中\(x\)的根得到\(x = \frac{-b\pm\sqrt{b^2 - 4ac}}{2a}\)。
考虑的替代方案¶
代数法求多项式根的示例¶
以下是代数求解多项式根的一个例子:
>>> from sympy import roots
>>> from sympy.abc import x, a, b, c
>>> roots(a*x**2 + b*x + c, x)
{-b/(2*a) - sqrt(-4*a*c + b**2)/(2*a): 1,
-b/(2*a) + sqrt(-4*a*c + b**2)/(2*a): 1}
这个例子再现了二次公式。
寻找多项式根的函数¶
有几种函数可以用来找到多项式的根:
solve()
是一个通用求解函数,可以找到根,尽管效率低于all_roots()
,并且是此列表中唯一不传达根的重数的功能;solve()
还可以处理 非多项式方程 和 非多项式方程组roots()
计算单变量多项式的符号根;对于大多数高次多项式(五次或更高)将会失败nroots()
计算任何多项式的根的数值近似,只要其系数可以进行数值计算,无论系数是理性的还是非理性的。RootOf()
可以精确表示任意大次数的多项式的所有根,只要系数是理性数。RootOf()
可以避免条件不良和返回虚假的复数部分,因为它使用了一种更精确但慢得多的数值算法,基于隔离区间。以下两个函数使用了RootOf()
,因此它们具有相同的性质:real_roots()
可以精确地找到任意大次多项式的所有实根;因为它只寻找实根,所以它可能比寻找所有根的函数更高效。all_roots()
可以精确地找到任意高次多项式的所有根。
factor()
将多项式分解为不可约因子,并可以揭示根位于系数环中
每个都将在此页面上使用。
指导¶
找到多项式的根¶
你可以通过几种代数方法找到多项式的根。使用哪种方法取决于你是否
想要一个代数或数值答案
想要知道每个根的重数(每个根是解的次数)。在下面的
expression
中,表示 \((x+2)^2(x-3)\),根 -2 的重数是二,因为 \(x+2\) 是平方的,而 3 的重数是一,因为 \(x-3\) 没有指数。同样地,对于symbolic
表达式,根 \(-a\) 的重数是二,而根 \(b\) 的重数是一。
>>> from sympy import solve, roots, real_roots, factor, nroots, RootOf, expand
>>> from sympy import Poly
>>> expression = (x+2)**2 * (x-3)
>>> symbolic = (x+a)**2 * (x-b)
无重根的代数解¶
你可以使用 SymPy 的标准 solve()
函数,尽管它不会返回根的重数:
>>> solve(expression, x, dict=True)
[{x: -2}, {x: 3}]
>>> solve(symbolic, x, dict=True)
[{x: -a}, {x: b}]
solve()
将首先尝试使用 roots()
;如果这不起作用,它将尝试使用 all_roots()
。对于三次(三阶多项式)和四次(四阶多项式),这意味着 solve()
将使用根的根式公式,而不是 RootOf()
,即使 RootOf 是可能的。三次和四次公式通常会给出非常复杂的表达式,这些表达式在实践中并不实用。因此,您可能希望将 solve()
参数 cubics
或 quartics
设置为 False
,以返回 RootOf()
结果:
>>> from sympy import solve
>>> from sympy.abc import x
>>> # By default, solve() uses the radical formula, yielding very complex terms
>>> solve(x**4 - x + 1, x)
[-sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3))/2 - sqrt(-2*(1/16 + sqrt(687)*I/144)**(1/3) - 2/sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3)) - 2/(3*(1/16 + sqrt(687)*I/144)**(1/3)))/2,
sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3))/2 - sqrt(-2*(1/16 + sqrt(687)*I/144)**(1/3) + 2/sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3)) - 2/(3*(1/16 + sqrt(687)*I/144)**(1/3)))/2,
sqrt(-2*(1/16 + sqrt(687)*I/144)**(1/3) - 2/sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3)) - 2/(3*(1/16 + sqrt(687)*I/144)**(1/3)))/2 - sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3))/2,
sqrt(-2*(1/16 + sqrt(687)*I/144)**(1/3) + 2/sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3)) - 2/(3*(1/16 + sqrt(687)*I/144)**(1/3)))/2 + sqrt(2/(3*(1/16 + sqrt(687)*I/144)**(1/3)) + 2*(1/16 + sqrt(687)*I/144)**(1/3))/2]
>>> # If you set quartics=False, solve() uses RootOf()
>>> solve(x**4 - x + 1, x, quartics=False)
[CRootOf(x**4 - x + 1, 0),
CRootOf(x**4 - x + 1, 1),
CRootOf(x**4 - x + 1, 2),
CRootOf(x**4 - x + 1, 3)]
用标准数学符号写出从 solve()
得到的第一根,强调了它的复杂性:
此外,五次(五阶)或更高次的多项式没有通用的根式公式,因此它们的 RootOf()
表示可能是最佳选择。
具有根重数的代数解¶
roots
¶
roots()
可以为具有符号系数的多项式的根提供显式表达式(即,如果系数中有符号),如果 factor()
没有揭示它们。然而,它可能对某些多项式失败。以下是 roots()
的示例:
>>> roots(expression, x)
{-2: 2, 3: 1}
>>> roots(symbolic, x)
{-a: 2, b: 1}
它以字典形式返回结果,其中键是根(例如,-2),值是该根的重数(例如,2)。
roots()
函数使用一系列技术(因式分解、分解、根式公式)来尝试找到根的根式表达式(如果可能的话)。当它能够找到根的一些根式表达式时,它会返回这些表达式及其重数。对于大多数高次多项式(五次或更高),此函数将失败,因为它们没有根式解,并且根据阿贝尔-鲁菲尼定理,它们根本没有保证有封闭形式的解。
分解方程¶
另一种方法是使用 factor()
对多项式进行因式分解,这不会直接给出根,但可以给出更简单的表达式:
>>> expression_expanded = expand(expression)
>>> expression_expanded
x**3 + x**2 - 8*x - 12
>>> factor(expression_expanded)
(x - 3)*(x + 2)**2
>>> symbolic_expanded = expand(symbolic)
>>> symbolic_expanded
-a**2*b + a**2*x - 2*a*b*x + 2*a*x**2 - b*x**2 + x**3
>>> factor(symbolic_expanded)
(a + x)**2*(-b + x)
factor()
也可以在一个给定的 多项式环 中分解多项式,这可以揭示根位于系数环中。例如,如果多项式有有理系数,那么 factor()
将揭示任何有理根。如果系数是涉及符号 \(a\) 的多项式且具有有理系数,那么任何作为 \(a\) 的多项式函数且具有有理系数的根都将被揭示。在这个例子中,factor()
揭示了 \(x = a^2\) 和 \(x = -a^3 - a\) 是根:
>>> from sympy import expand, factor
>>> from sympy.abc import x, a
>>> p = expand((x - a**2)*(x + a + a**3))
>>> p
-a**5 + a**3*x - a**3 - a**2*x + a*x + x**2
>>> factor(p)
(-a**2 + x)*(a**3 + a + x)
具有根重数的精确数值解¶
real_roots
¶
如果你的多项式的根是实数,使用 real_roots()
可以确保只返回实数根(不包括复数或虚数根)。
>>> from sympy import real_roots
>>> from sympy.abc import x
>>> cubed = x**3 - 1
>>> # roots() returns real and complex roots
>>> roots(cubed)
{1: 1, -1/2 - sqrt(3)*I/2: 1, -1/2 + sqrt(3)*I/2: 1}
>>> # real_roots() returns only real roots
>>> real_roots(cubed)
[1]
real_roots()
调用 RootOf()
,因此对于所有根均为实数的方程,您可以通过迭代方程的根数来获得相同的结果:
>>> [RootOf(expression, n) for n in range(3)]
[-2, -2, 3]
具有根重数的近似数值解¶
nroots
¶
nroots()
给出了多项式根的近似数值解。这个例子展示了它可能包含数值噪声,例如在应该是实根的情况下包含一个(可忽略的)虚部:
>>> nroots(expression)
[3.0, -2.0 - 4.18482169793536e-14*I, -2.0 + 4.55872552179222e-14*I]
如果你想得到实根的数值近似值,但又想确切知道哪些根是实数,那么最好的方法是使用 real_roots()
和 evalf()
。
>>> [r.n(2) for r in real_roots(expression)]
[-2.0, -2.0, 3.0]
>>> [r.is_real for r in real_roots(expression)]
[True, True, True]
nroots()
类似于 NumPy 的 roots()
函数。通常,这两者之间的区别在于 nroots()
更精确但速度较慢。
nroots()
的一个主要优势是它可以计算任何多项式的根的数值近似,只要这些多项式的系数可以用 evalf()
进行数值评估(即,它们没有自由符号)。相反,根据 阿贝尔-鲁菲尼定理 的解释,对于高阶(五阶或更高)多项式,符号解可能是不可能的。即使存在闭式解,它们也可能有太多的项,以至于在实践中没有用处。因此,即使存在闭式符号解,您也可能希望使用 nroots()
来找到近似的数值解。例如,四阶(四次)多项式的闭式根可能相当复杂:
>>> rq0, rq1, rq2, rq3 = roots(x**4 + 3*x**2 + 2*x + 1)
>>> rq0
sqrt(-4 - 2*(-1/8 + sqrt(237)*I/36)**(1/3) + 4/sqrt(-2 + 7/(6*(-1/8 + sqrt(237)*I/36)**(1/3)) + 2*(-1/8 + sqrt(237)*I/36)**(1/3)) - 7/(6*(-1/8 + sqrt(237)*I/36)**(1/3)))/2 - sqrt(-2 + 7/(6*(-1/8 + sqrt(237)*I/36)**(1/3)) + 2*(-1/8 + sqrt(237)*I/36)**(1/3))/2
因此,您可能更喜欢近似的数值解:
>>> rq0.n()
-0.349745826211722 - 0.438990337475312*I
nroots()
有时可能会对数值上病态的多项式失败,例如 Wilkinson’s polynomial。使用 RootOf()
和 evalf()
如 Numerically Evaluate CRootOf Roots 中所述,可以避免病态条件和返回虚假的复数部分,因为它使用了一种更精确但慢得多的数值算法,基于隔离区间。
复数根¶
对于复杂的根,可以使用类似的函数,例如 solve()
:
>>> from sympy import solve, roots, nroots, real_roots, expand, RootOf, CRootOf, Symbol
>>> from sympy import Poly
>>> from sympy.abc import x
>>> expression_complex = (x**2+4)**2 * (x-3)
>>> solve(expression_complex, x, dict=True)
[{x: 3}, {x: -2*I}, {x: 2*I}]
如果常数是符号性的,您可能需要为 SymPy 指定它们的域,以识别这些解不是实数。例如,指定 \(a\) 为正数会导致虚根:
>>> a = Symbol("a", positive=True)
>>> symbolic_complex = (x**2+a)**2 * (x-3)
>>> solve(symbolic_complex, x, dict=True)
[{x: 3}, {x: -I*sqrt(a)}, {x: I*sqrt(a)}]
roots()
也会找到虚数或复数根:
>>> roots(expression_complex, x)
{3: 1, -2*I: 2, 2*I: 2}
RootOf()
也会返回复数根:
>>> [RootOf(expression_complex, n) for n in range(0,3)]
[3, -2*I, -2*I]
real_roots()
将只返回实根。
>>> real_roots(expression_complex)
[3]
使用 real_roots()
的一个优点是它可能比生成所有根更高效:RootOf()
在处理复杂根时可能会很慢。
如果你将表达式转换为多项式类 Poly
,你可以使用其 all_roots()
方法来找到根:
>>> expression_complex_poly = Poly(expression_complex)
>>> expression_complex_poly.all_roots()
[3, -2*I, -2*I, 2*I, 2*I]
使用解决方案结果¶
从结果中提取解决方案的方式取决于结果的形式。
列表 (all_roots
, real_roots
, nroots
)¶
你可以使用标准的 Python 列表遍历技术,例如循环。在这里,我们将每个根代入表达式以验证结果是否为 \(0\):
>>> expression = (x+2)**2 * (x-3)
>>> my_real_roots = real_roots(expression)
>>> my_real_roots
[-2, -2, 3]
>>> for root in my_real_roots:
... print(f"expression({root}) = {expression.subs(x, root)}")
expression(-2) = 0
expression(-2) = 0
expression(3) = 0
字典列表 (solve
)¶
字典 (roots
)¶
你可以使用标准的 Python 列表遍历技术,例如在字典中循环遍历键和值。这里我们打印每个根的值和重数:
>>> my_roots = roots(expression)
>>> my_roots
{-2: 2, 3: 1}
>>> for root, multiplicity in my_roots.items():
... print(f"Root {root} has multiplicity of {multiplicity}")
Root 3 has multiplicity of 1
Root -2 has multiplicity of 2
表达式 (因子
)¶
你可以使用各种 SymPy 技术来操作代数表达式,例如为 \(x\) 代入符号或数值:
>>> from sympy.abc import y
>>> factored = factor(expression_expanded)
>>> factored
(x - 3)*(x + 2)**2
>>> factored.subs(x, 2*y)
(2*y - 3)*(2*y + 2)**2
>>> factored.subs(x, 7)
324
权衡¶
数学的精确性、根列表的完整性以及速度¶
考虑高次多项式 \(x^5 - x + 1 = 0\)。nroots()
返回所有五个根的数值近似值:
>>> from sympy import roots, solve, real_roots, nroots
>>> from sympy.abc import x
>>> fifth_order = x**5 - x + 1
>>> nroots(fifth_order)
[-1.16730397826142,
-0.181232444469875 - 1.08395410131771*I,
-0.181232444469875 + 1.08395410131771*I,
0.764884433600585 - 0.352471546031726*I,
0.764884433600585 + 0.352471546031726*I]
roots()
有时可能只返回根的子集,或者如果它不能用根式表示任何根,则不返回任何内容。在这种情况下,它不返回任何根(一个空集):
>>> roots(fifth_order, x)
{}
但如果您设置了标志 strict=True
,roots()
将通知您无法返回所有根:
>>> roots(x**5 - x + 1, x, strict=True)
Traceback (most recent call last):
...
sympy.polys.polyerrors.UnsolvableFactorError: Strict mode: some factors cannot be solved in radicals, so a complete
list of solutions cannot be returned. Call roots with strict=False to
get solutions expressible in radicals (if there are any).
获取所有根节点,可能隐含地¶
solve()
将返回所有五个根作为 CRootOf
(ComplexRootOf()
) 类的成员:
>>> fifth_order_solved = solve(fifth_order, x, dict=True)
>>> fifth_order_solved
[{x: CRootOf(x**5 - x + 1, 0)},
{x: CRootOf(x**5 - x + 1, 1)},
{x: CRootOf(x**5 - x + 1, 2)},
{x: CRootOf(x**5 - x + 1, 3)},
{x: CRootOf(x**5 - x + 1, 4)}]
其中 CRootOf
中的第二个参数是根的索引。
数值评估 CRootOf
根¶
然后,您可以使用 evalf()
中的 n
对这些 CRootOf
根进行数值评估:
>>> for root in fifth_order_solved:
... print(root[x].n(10))
-1.167303978
-0.1812324445 - 1.083954101*I
-0.1812324445 + 1.083954101*I
0.7648844336 - 0.352471546*I
0.7648844336 + 0.352471546*I
如果你只对唯一的实根感兴趣,使用 real_roots()
会更快,因为它不会尝试寻找复数根:
>>> real_root = real_roots(fifth_order, x)
>>> real_root
[CRootOf(x**5 - x + 1, 0)]
>>> real_root[0].n(10)
-1.167303978
表示根¶
RootOf()
, real_roots()
, 和 all_roots()
可以精确地找到任意高次多项式的所有根,尽管有 阿贝尔-鲁菲尼定理。这些函数允许根被精确分类并进行符号化操作。
>>> from sympy import init_printing
>>> init_printing()
>>> real_roots(fifth_order)
/ 5 \
[CRootOf\x - x + 1, 0/]
>>> r = r0, r1, r2, r3, r4 = Poly(fifth_order, x).all_roots(); r
/ 5 \ / 5 \ / 5 \ / 5 \ / 5 \
[CRootOf\x - x + 1, 0/, CRootOf\x - x + 1, 1/, CRootOf\x - x + 1, 2/, CRootOf\x - x + 1, 3/, CRootOf\x - x + 1, 4/]
>>> r0
/ 5 \
CRootOf\x - x + 1, 0/
既然根已经被精确地找到,它们的性质就可以在没有数值噪声的情况下确定。例如,我们可以判断根是否为实数。如果我们请求一个根的 conjugate()
(实部相同,虚部符号相反),例如 r1
,并且它恰好等于另一个根 r2
,那么将返回根 r2
:
>>> r0.n()
-1.16730397826142
>>> r0.is_real
True
>>> r1.n()
-0.181232444469875 - 1.08395410131771*I
>>> r2.n()
-0.181232444469875 + 1.08395410131771*I
>>> r1
/ 5 \
CRootOf\x - x + 1, 1/
>>> r1.conjugate()
/ 5 \
CRootOf\x - x + 1, 2/
>>> r1.is_real
False
solve()
也会在可能的情况下给出复数根,但它的效率不如直接使用 all_roots()
。
RootOf()
精确地表示根,这种方式可以进行符号操作,并计算到任意精度。RootOf()
表示法使得精确地:
计算具有精确有理系数的多项式的所有根。
确定每个根的确切重数。
确定根是否为实数。
精确地排列实根和复根。
了解哪些根是彼此的复共轭对。
准确确定哪些根是合理的,哪些是不合理的。
精确表示每一个可能的代数数。
其他数值方法,如 NumPy 的 roots()
、nroots()
和 nsolve()
,即使能做到,也无法稳健地完成这些任务。同样,当使用 evalf()
进行数值评估时,由 solve()
或 roots()
返回的根式表达式也无法稳健地完成这些任务。
并非所有方程都能被求解¶
无闭式解的方程¶
如上所述,高阶多项式(五次或更高次)不太可能有封闭形式的解,因此你可能需要使用例如 RootOf
如上所述 来表示它们,或者使用数值方法如 nroots
如上所述。
报告一个错误¶
如果你在使用这些命令时遇到错误,请在 SymPy 邮件列表 上发布问题。在问题解决之前,你可以使用 Functions to Find the Roots of a Polynomial 中的其他方法,或者尝试 Alternatives to Consider 中的一个。