# 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 - If you need a numeric (rather than algebraic) solution, you can use either - NumPy's {external:func}`~numpy.roots` - SciPy's {external:func}`~scipy.optimize.root` - If you need to solve systems of polynomial equations algebraically, use {func}`~.solve` ## Example of Finding the Roots of a Polynomial Algebraically Here is an example of finding the roots of a polynomial algebraically: ```py >>> 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](https://en.wikipedia.org/wiki/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: - {func}`~.solve` is a general solving function which can find roots, though is less efficient than {meth}`~sympy.polys.polytools.Poly.all_roots` and is the only function in this list that does not convey the multiplicity of roots; {func}`~.solve` also works on [non-polynomial equations](solve-equation-algebraically.md) and [systems of non-polynomial equations](solve-system-of-equations-algebraically.md) - {func}`~.roots` computes the symbolic roots of a univariate polynomial; will fail for most high-degree polynomials (five or greater) - {func}`~.nroots` computes numerical approximations of the roots of any polynomial whose coefficients can be numerically evaluated, whether the coefficients are rational or irrational - {func}`~sympy.polys.rootoftools.RootOf` can represent all the roots exactly of a polynomial of arbitrarily large degree, as long as the coefficients are rational numbers. {func}`~sympy.polys.rootoftools.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 use {func}`~sympy.polys.rootoftools.RootOf` so they have the same properties: - {func}`~.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. - {meth}`~sympy.polys.polytools.Poly.all_roots` can find all the roots exactly of a polynomial of arbitrarily large degree - {func}`~.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 [](solving-guidance.md#include-the-variable-to-be-solved-for-in-the-function-call) and [](solving-guidance.md#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 the `symbolic` expression, the root $-a$ has a multiplicity of two and the root $b$ has a multiplicity of one. ```py >>> 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 {func}`~.solve` function, though it will not return the multiplicity of roots: ```py >>> solve(expression, x, dict=True) [{x: -2}, {x: 3}] >>> solve(symbolic, x, dict=True) [{x: -a}, {x: b}] ``` {func}`~.solve` will first try using {func}`~.roots`; if that doesn't work, it will try using {meth}`~sympy.polys.polytools.Poly.all_roots`. For cubics (third-degree polynomials) and quartics (fourth-degree polynomials), that means that {func}`~.solve` will use radical formulae from roots rather than {func}`~sympy.polys.rootoftools.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 {func}`~.solve` parameter `cubics` or `quartics` to `False` to return {func}`~sympy.polys.rootoftools.RootOf` results: ```py >>> 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 {func}`~.solve` in standard mathematical notation emphasizes how complex it is: $$- \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}$$ Further, there is no general radical formula for quintics (fifth degree) or higher polynomials, so their {func}`~sympy.polys.rootoftools.RootOf` representations may be the best option. Refer to [](solve-equation-algebraically.md) for more about using {func}`~.solve`. ### Algebraic Solution With Root Multiplicities #### `roots` {func}`~.roots` can give explicit expressions for the roots of polynomials that have symbolic coefficients (that is, if there are symbols in the coefficients) if {func}`~.factor` does not reveal them. However, it may fail for some polynomials. Here are examples of {func}`~.roots`: ```py >>> 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). {func}`~.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](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem). #### Factor the Equation A different approach is to factor a polynomial using {func}`~.factor`, which does not give the roots directly but can give you simpler expressions: ```py >>> 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) ``` {func}`~.factor` can also factorize a polynomial in a given [polynomial ring](polys-ring) which can reveal roots lie in the coefficient ring. For example, if the polynomial has rational coefficients, then {func}`~.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, {func}`~.factor` reveals that $x = a^2$ and $x = -a^3 - a$ are roots: ```py >>> 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 {func}`~.real_roots` ensures that only real (not complex or imaginary) roots will be returned. ```py >>> 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] ``` {func}`~.real_roots` calls {func}`~sympy.polys.rootoftools.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: ```py >>> [RootOf(expression, n) for n in range(3)] [-2, -2, 3] ``` ### Approximate Numeric Solution With Root Multiplicities #### `nroots` {func}`~.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: ```py >>> 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 {func}`~.real_roots` with {func}`~sympy.core.evalf`: ```py >>> [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] ``` {func}`~.nroots` is analogous to NumPy's {external:func}`~numpy.roots` function. Usually the difference between these two is that {func}`~.nroots` is more accurate but slower. A major advantage of {func}`~.nroots` is that it can compute numerical approximations of the roots of any polynomial whose coefficients can be numerically evaluated with {func}`~sympy.core.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](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_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 {func}`~.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: ```py >>> 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: ```py >>> rq0.n() -0.349745826211722 - 0.438990337475312*I ``` {func}`~.nroots` can fail sometimes for polynomials that are numerically ill conditioned, for example [Wilkinson's polynomial](https://en.wikipedia.org/wiki/Wilkinson%27s_polynomial). Using {func}`~sympy.polys.rootoftools.RootOf` and {func}`~sympy.core.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 {func}`~.solve`: ```py >>> 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: ```py >>> 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)}] ``` {func}`~.roots` will also find imaginary or complex roots: ```py >>> roots(expression_complex, x) {3: 1, -2*I: 2, 2*I: 2} ``` {func}`~sympy.polys.rootoftools.RootOf` will also return complex roots: ```py >>> [RootOf(expression_complex, n) for n in range(0,3)] [3, -2*I, -2*I] ``` {func}`~.real_roots` will return only the real roots. ```py >>> real_roots(expression_complex) [3] ``` An advantage of {func}`~.real_roots` is that it can be more efficient than generating all the roots: {func}`~sympy.polys.rootoftools.RootOf` can be slow for complex roots. If you make the expression into a polynomial class {class}`~.Poly`, you can use its {meth}`~sympy.polys.polytools.Poly.all_roots` method to find the roots: ```py >>> 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$: ```py >>> 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 [](solve-equation-algebraically.md#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: ```py >>> 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$: ```py >>> 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$. {func}`~.nroots` returns numerical approximations to all five roots: ```py >>> 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] ``` {func}`~.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): ```py >>> roots(fifth_order, x) {} ``` But if you set the flag `strict=True`, {func}`~.roots` will inform you that all roots cannot be returned: ```py >>> 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 {func}`~.solve` will return all five roots as `CRootOf` ({func}`~sympy.polys.rootoftools.ComplexRootOf`) class members: ```py >>> 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 {func}`~sympy.core.evalf`: ```py >>> 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 {func}`~.real_roots` because it will not attempt to find the complex roots: ```py >>> real_root = real_roots(fifth_order, x) >>> real_root [CRootOf(x**5 - x + 1, 0)] >>> real_root[0].n(10) -1.167303978 ``` ### Representing Roots {func}`~sympy.polys.rootoftools.RootOf`, {func}`~.real_roots`, and {meth}`~sympy.polys.polytools.Poly.all_roots` can find all the roots exactly of a polynomial of arbitrarily large degree despite the [Abel-Ruffini theorem](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem). Those functions allow the roots to be categorized precisely and manipulated symbolically. ```py >>> 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 {meth}`~sympy.core.expr.Expr.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: ```py >>> 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 ``` {func}`~.solve` will also give the complex roots where possible but it is less efficient than using {meth}`~sympy.polys.polytools.Poly.all_roots` directly. {func}`~sympy.polys.rootoftools.RootOf` exactly represents the root in a way that can be manipulated symbolically, and computed to arbitrary precision. The {func}`~sympy.polys.rootoftools.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 {external:func}`~numpy.roots`, {func}`~.nroots`, and {func}`~.nsolve` cannot do any of these things robustly, if at all. Similarly, when numerically evaluated using {func}`~sympy.core.evalf`, the radical expressions returned by {func}`~.solve` or {func}`~.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](#representing-roots), or use a numerical method such as [`nroots` as described above](#nroots). ## Report a Bug If you encounter a bug with these commands, please post the problem on the [SymPy mailing list](https://groups.google.com/g/sympy). 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).