通过代数或数值方法求多项式的根

使用SymPy以代数方式求解一元多项式的根。例如,求解\(ax^2 + bx + c\)\(x\)的根得到\(x = \frac{-b\pm\sqrt{b^2 - 4ac}}{2a}\)

考虑的替代方案

  • 如果你需要一个数值(而非代数)解,你可以使用以下任一方法:

  • 如果你需要代数地解多项式方程组,使用 solve()

代数法求多项式根的示例

以下是代数求解多项式根的一个例子:

>>> 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() 将多项式分解为不可约因子,并可以揭示根位于系数环中

每个都将在此页面上使用。

指导

参见 Use Exact ValuesUse Exact Values

找到多项式的根

你可以通过几种代数方法找到多项式的根。使用哪种方法取决于你是否

  • 想要一个代数或数值答案

  • 想要知道每个根的重数(每个根是解的次数)。在下面的 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() 参数 cubicsquartics 设置为 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() 得到的第一根,强调了它的复杂性:

\[- \frac{\sqrt{\frac{2}{3 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}}} + 2 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}}}}{2} - \frac{\sqrt{- 2 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}} - \frac{2}{\sqrt{\frac{2}{3 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}}} + 2 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}}}} - \frac{2}{3 \sqrt[3]{\frac{1}{16} + \frac{\sqrt{687} i}{144}}}}}{2}\]

此外,五次(五阶)或更高次的多项式没有通用的根式公式,因此它们的 RootOf() 表示可能是最佳选择。

更多关于使用 solve() 的信息,请参阅 代数求解方程

具有根重数的代数解

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)

参见 Use the Solution Result

字典 (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=Trueroots() 将通知您无法返回所有根:

>>> 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 中的一个。