Find the Roots of a Polynomial Algebraically or Numerically¶
Use SymPy to find the roots of a univariate polynomial algebraically. For example, finding the roots of \(ax^2 + bx + c\) for \(x\) yields \(x = \frac{-b\pm\sqrt{b^2 - 4ac}}{2a}\).
Alternatives to Consider¶
Example of Finding the Roots of a Polynomial Algebraically¶
Here is an example of finding the roots of a polynomial algebraically:
>>> 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}
This example reproduces the quadratic formula.
Functions to Find the Roots of a Polynomial¶
There are several functions that you can use to find the roots of a polynomial:
solve()
is a general solving function which can find roots, though is less efficient thanall_roots()
and is the only function in this list that does not convey the multiplicity of roots;solve()
also works on non-polynomial equations and systems of non-polynomial equationsroots()
computes the symbolic roots of a univariate polynomial; will fail for most high-degree polynomials (five or greater)nroots()
computes numerical approximations of the roots of any polynomial whose coefficients can be numerically evaluated, whether the coefficients are rational or irrationalRootOf()
can represent all the roots exactly of a polynomial of arbitrarily large degree, as long as the coefficients are rational numbers.RootOf()
can avoid both ill-conditioning and returning spurious complex parts because it uses a more exact, but much slower, numerical algorithm based on isolating intervals. The following two functions useRootOf()
so they have the same properties:real_roots()
can find all the real roots exactly of a polynomial of arbitrarily large degree; because it finds only the real roots, it can be more efficient than functions that find all roots.all_roots()
can find all the roots exactly of a polynomial of arbitrarily large degree
factor()
factors a polynomial into irreducibles and can reveal that roots lie in the coefficient ring
Each will be used on this page.
Guidance¶
Refer to Include the Variable to be Solved for in the Function Call and Use Exact Values.
Find the Roots of a Polynomial¶
You can find the roots of a polynomial algebraically in several ways. The one to use depends on whether you
want an algebraic or numeric answer
want the multiplicity of each root (how many times each root is a solution). In the
expression
below representing \((x+2)^2(x-3)\), the root -2 has a multiplicity of two because \(x+2\) is squared, whereas 3 has a multiplicity of one because \(x-3\) has no exponent. Similarly, for thesymbolic
expression, the root \(-a\) has a multiplicity of two and the root \(b\) has a multiplicity of one.
>>> 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)
Algebraic Solution Without Root Multiplicities¶
You can use SymPy’s standard solve()
function, though it will not return
the multiplicity of roots:
>>> solve(expression, x, dict=True)
[{x: -2}, {x: 3}]
>>> solve(symbolic, x, dict=True)
[{x: -a}, {x: b}]
solve()
will first try using roots()
; if that doesn’t work, it
will try using all_roots()
. For cubics
(third-degree polynomials) and quartics (fourth-degree polynomials), that means
that solve()
will use radical formulae from roots rather than
RootOf()
even if RootOf is possible. The cubic
and quartic formulae often give very complex expressions that are not useful in
practice. As a result, you may want to set the solve()
parameter
cubics
or quartics
to False
to return
RootOf()
results:
>>> 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)]
Writing the first root from solve()
in standard mathematical notation
emphasizes how complex it is:
Further, there is no general radical formula for quintics (fifth degree) or
higher polynomials, so their RootOf()
representations may be the best option.
Refer to Solve an Equation Algebraically for more about using
solve()
.
Algebraic Solution With Root Multiplicities¶
roots
¶
roots()
can give explicit expressions for the roots of polynomials that
have symbolic coefficients (that is, if there are symbols in the coefficients)
if factor()
does not reveal them. However, it may fail for some
polynomials. Here are examples of roots()
:
>>> roots(expression, x)
{-2: 2, 3: 1}
>>> roots(symbolic, x)
{-a: 2, b: 1}
It returns results as a dictionary, where the key is the root (for example, -2) and the value is the multiplicity of that root (for example, 2).
roots()
function uses a combination of techniques (factorization,
decomposition, radical formulae) to find expressions in radicals if possible for
the roots. When it can find some radical expressions for the roots, it returns
them along with their multiplicity. This function will fail for most high-degree
polynomials (five or greater) because they do not have radical solutions, and
there is no guarantee that they have closed-form solutions at all, as explained
by the Abel-Ruffini
theorem.
Factor the Equation¶
A different approach is to factor a polynomial using factor()
, which
does not give the roots directly but can give you simpler expressions:
>>> 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()
can also factorize a polynomial in a given polynomial
ring which can reveal roots lie in the coefficient ring. For
example, if the polynomial has rational coefficients, then factor()
will
reveal any rational roots. If the coefficients are polynomials involving, for
example, symbol \(a\) with rational coefficients then any roots that are
polynomial functions of \(a\) with rational coefficients will be revealed. In this
example, factor()
reveals that \(x = a^2\) and \(x = -a^3 - a\) are roots:
>>> 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)
Exact Numeric Solution With Root Multiplicities¶
real_roots
¶
If the roots to your polynomial are real, using real_roots()
ensures
that only real (not complex or imaginary) roots will be returned.
>>> 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()
calls RootOf()
, so for
equations whose roots are all real, you can get the same results by iterating
over the number of roots of your equation:
>>> [RootOf(expression, n) for n in range(3)]
[-2, -2, 3]
Approximate Numeric Solution With Root Multiplicities¶
nroots
¶
nroots()
gives an approximate numerical approximation to the roots of a
polynomial. This example demonstrates that it can include numerical noise, for
example a (negligible) imaginary component in what should be a real root:
>>> nroots(expression)
[3.0, -2.0 - 4.18482169793536e-14*I, -2.0 + 4.55872552179222e-14*I]
If you want numeric approximations of the real roots, but you want to know
exactly which roots are real, then the best method is real_roots()
with
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()
is analogous to NumPy’s roots()
function.
Usually the difference between these two is that nroots()
is more
accurate but slower.
A major advantage of nroots()
is that it can compute numerical
approximations of the roots of any polynomial whose coefficients can be
numerically evaluated with evalf()
(that is, they do not have
free symbols). Contrarily, symbolic solutions may not be possible for
higher-order (fifth or greater) polynomials as explained by the Abel-Ruffini
theorem. Even if
closed-form solutions are available, they may have so many terms that they are
not useful in practice. You may therefore want to use nroots()
to find
approximate numeric solutions even if closed-form symbolic solutions are
available. For example, the closed-form roots of a fourth-order (quartic)
polynomial may be rather complicated:
>>> 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
so you may prefer an approximate numerical solution:
>>> rq0.n()
-0.349745826211722 - 0.438990337475312*I
nroots()
can fail sometimes for polynomials that are numerically ill
conditioned, for example Wilkinson’s
polynomial. Using
RootOf()
and evalf()
as
described in Numerically Evaluate CRootOf Roots can avoid both
ill-conditioning and returning spurious complex parts because it uses a more
exact, but much slower, numerical algorithm based on isolating intervals.
Complex Roots¶
For complex roots, similar functions can be used, for example 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}]
If the constants are symbolic, you may need to specify their domain for SymPy to recognize that the solutions are not real. For example, specifying that \(a\) is positive leads to imaginary roots:
>>> 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()
will also find imaginary or complex roots:
>>> roots(expression_complex, x)
{3: 1, -2*I: 2, 2*I: 2}
RootOf()
will also return complex roots:
>>> [RootOf(expression_complex, n) for n in range(0,3)]
[3, -2*I, -2*I]
real_roots()
will return only the real roots.
>>> real_roots(expression_complex)
[3]
An advantage of real_roots()
is that it can be more efficient than
generating all the roots: RootOf()
can be slow
for complex roots.
If you make the expression into a polynomial class Poly
, you can use
its all_roots()
method to find the roots:
>>> expression_complex_poly = Poly(expression_complex)
>>> expression_complex_poly.all_roots()
[3, -2*I, -2*I, 2*I, 2*I]
Use the Solution Result¶
The way to extract solutions from the result depends on the form of the result.
List (all_roots
, real_roots
, nroots
)¶
You can use standard Python list traversal techniques such as looping. Here, we substitute each root into the expression to verify that the result is \(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
List of dictionaries (solve
)¶
Refer to Use the Solution Result.
Dictionary (roots
)¶
You can use standard Python list traversal techniques such as looping through the keys and values in a dictionary. Here we print the value and multiplicity of each root:
>>> 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
Expression (factor
)¶
You can manipulate an algebraic expression using various SymPy techniques, for example substituting in a symbolic or numeric value for \(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
Tradeoffs¶
Mathematical Exactness, Completeness of List of Roots, and Speed¶
Consider the high-order polynomial \(x^5 - x + 1 = 0\). nroots()
returns
numerical approximations to all five roots:
>>> 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()
can sometimes return only a subset of the roots or nothing if it
can’t express any roots in radicals. In this case, it returns no roots (an empty
set):
>>> roots(fifth_order, x)
{}
But if you set the flag strict=True
, roots()
will inform you that all
roots cannot be returned:
>>> 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).
Get All Roots, Perhaps Implicitly¶
solve()
will return all five roots as CRootOf
(ComplexRootOf()
) class members:
>>> 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)}]
where the second argument in each CRootOf
is the index of the root.
Numerically Evaluate CRootOf
Roots¶
You can then numerically evaluate those CRootOf
roots using n
from
evalf()
:
>>> 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
If you are only interested in the sole real root, it is faster to use
real_roots()
because it will not attempt to find the complex roots:
>>> real_root = real_roots(fifth_order, x)
>>> real_root
[CRootOf(x**5 - x + 1, 0)]
>>> real_root[0].n(10)
-1.167303978
Representing Roots¶
RootOf()
, real_roots()
, and
all_roots()
can find all the roots exactly of
a polynomial of arbitrarily large degree despite the Abel-Ruffini
theorem. Those
functions allow the roots to be categorized precisely and manipulated
symbolically.
>>> 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/
Now that the roots have been found exactly, their properties can be determined
free of numerical noise. For example, we can tell whether roots are real or not.
If we request the conjugate()
(same real part and
imaginary part with opposite sign) of a root, for example r1
, and that is
exactly equal to another root r2
, that root r2
will be returned:
>>> 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()
will also give the complex roots where possible but it is less
efficient than using all_roots()
directly.
RootOf()
exactly represents the root in a way
that can be manipulated symbolically, and computed to arbitrary precision. The
RootOf()
representation makes it possible to
precisely:
Compute all roots of a polynomial with exact rational coefficients.
Decide exactly the multiplicity of every root.
Determine exactly whether roots are real or not.
Order the real and complex roots precisely.
Know which roots are complex conjugate pairs of each other.
Determine precisely which roots are rational vs irrational.
Represent every possible algebraic number exactly.
The other numerical methods such NumPy’s roots()
,
nroots()
, and nsolve()
cannot do any of these things robustly,
if at all. Similarly, when numerically evaluated using
evalf()
, the radical expressions returned by solve()
or roots()
cannot do these things robustly.
Not All Equations Can Be Solved¶
Equations With No Closed-Form Solution¶
As mentioned above, higher-order polynomials (fifth or greater) are unlikely to
have closed-form solutions, so you may have to represent them using, for
example, RootOf
as described above, or use a numerical
method such as nroots
as described above.
Report a Bug¶
If you encounter a bug with these commands, please post the problem on the SymPy mailing list. Until the issue is resolved, you can use another of the Functions to Find the Roots of a Polynomial or try one of the Alternatives to Consider.