Certainly, one of the most abstract subjects in math classes: polynomials. After all, who has never been confused in a polynomial division, it is x everywhere. In this article, we will see how the SymPy library, for the Python language, helps us to deal with operations involving polynomials.
Creating polynomials manually #
Let’s start by importing the library we will use throughout the article:
import sympy
# settings for better outputs in the article, can be ignored
sympy.init_printing(
use_latex="mathjax",
scale=1.0,
order="grlex",
forecolor="Black",
backcolor="White",
)
Now, we are going to use the most basic knowledge we acquired in the first article of the SymPy series. Let’s create a symbol for our variable and an expression that will represent a polynomial:
x = sympy.Symbol('x')
expr = (x - 1) * (x - 2) * (x - 3)
expr
\(\displaystyle \left(x - 3\right) \left(x - 2\right) \left(x - 1\right)\)
Note that we created our expression representing a polynomial in factored form.
To obtain the expanded form, that is, the form where all distributive
multiplications are made, we use the expand method:
expr.expand()
\(\displaystyle x^{3} - 6 x^{2} + 11 x - 6\)
If you initially have an expanded polynomial, you can factor it with the
factor method:
_.factor()
\(\displaystyle \left(x - 3\right) \left(x - 2\right) \left(x - 1\right)\)
The factored form already points out that the roots of the polynomial are 1, 2,
and 3. But we can obtain the roots programmatically using SymPy’s
solve:
roots = sympy.solve(expr, x)
roots
\(\displaystyle \left[ 1, \ 2, \ 3\right]\)
Checking equalities #
Moving on, let’s create two new polynomial expressions:
P = (x - 5) * (x + 5)
Q = x**2 - 25
Observe that P is simply the factored form of Q. Therefore, they are
equivalent and if we check the equality between them the result will be True,
right?
P == Q
False
Uh…?! Well, we have already written about this issue of equality in
SymPy.
The library analyzes equality in form and not mathematical equivalence.
Therefore, as the forms of the two polynomials are distinct, the comparison
returns False.
P - Q == 0
False
Why? Because SymPy is not proactive in simplifications, as we have already seen in other articles. Let’s see the representation of the difference:
P - Q
\(\displaystyle - x^{2} + \left(x - 5\right) \left(x + 5\right) + 25\)
See that SymPy effectively evaluates the expression without simplifying it. If
we request the simplification with the simplify method, we see that the
difference effectively equals zero:
sympy.simplify(P - Q) == 0
True
And we can see that one polynomial is the factored version of the other with the following comparisons:
P.simplify() == Q
True
P == Q.factor()
True
Using the polynomial constructor #
Until now, we have seen little news when compared to the article about expressions with SymPy. But let’s change that. Let’s see the type of the object Q:
Q
\(\displaystyle x^{2} - 25\)
type(Q)
sympy.core.add.Add
Note that, internally, it is an object of the Add class of SymPy. That is, for
SymPy, such object is simply the result of adding two other SymPy objects, which
is correct. We are the ones giving meaning to the object as a polynomial.
Let’s understand this last sentence.
The great advantage of using a high-level language like Python is that we can write code that is closer to our understanding using abstractions. The language interpreter is responsible for translating these abstractions into machine code. So far, we have used mere expressions as representations of polynomials, but it would be ideal for SymPy to effectively recognize such expressions as polynomials and not as additions. And this is possible and we will see the advantages of this approach.
Expressions have the as_poly method:
Q.as_poly()
\(\displaystyle \operatorname{Poly}{\left( x^{2} - 25, x, domain=\mathbb{Z} \right)}\)
Behold the return! Now SymPy recognizes it as a Poly type, recognizes that the
variable is x, and that the domain is of integers (it can be changed, it is
just the default when we do not explicitly pass the domain). Let’s associate
this return to a variable and confirm the type:
Q_poly = Q.as_poly()
type(Q_poly)
sympy.polys.polytools.Poly
And what is the advantage? Now we can request what we want with methods closer to the semantics we use in mathematics. For example, we can request the coefficients of the polynomial:
Q_poly.coeffs()
\(\displaystyle \left[ 1, \ -25\right]\)
The return is from the highest degree in x to the lowest. However, note that
the return can be misleading. After all, we know that there is a term of degree
1 in x, only its coefficient is zero. The coeffs returns only non-zero
coefficients. To avoid misunderstandings, I always recommend using the
all_coeffs method:
Q_poly.all_coeffs()
\(\displaystyle \left[ 1, \ 0, \ -25\right]\)
Now, all coefficients.
Another concept that always appears in the study of polynomials: the degree of
the polynomial. Now that we have an abstraction closer to our language, we can
effectively request the degree with the degree method:
Q_poly.degree()
\(\displaystyle 2\)
We can request the list of factors of the polynomial:
Q_poly.factor_list()
\(\displaystyle \left( 1, \ \left[ \left( \operatorname{Poly}{\left( x - 5, x, domain=\mathbb{Z} \right)}, \ 1\right), \ \left( \operatorname{Poly}{\left( x + 5, x, domain=\mathbb{Z} \right)}, \ 1\right)\right]\right)\)
Also, we can request the roots:
Q_poly.all_roots()
\(\displaystyle \left[ -5, \ 5\right]\)
Derivatives and integrals #
In fact, we can perform more elaborate operations. Leaving high school mathematics for a moment, it is very common to need to derive or integrate a polynomial. Now, such operations become trivial. Let’s see the first derivative of our polynomial:
Q_poly.diff()
\(\displaystyle \operatorname{Poly}{\left( 2 x, x, domain=\mathbb{Z} \right)}\)
We can obtain the value of the derivative at a certain value of x using the
subs method already seen in other
articles:
Q_poly.diff().subs({x: 2})
\(\displaystyle 4\)
Or, simpler, with the eval method and passing the value of x:
Q_poly.diff().eval(2)
\(\displaystyle 4\)
For higher-order derivatives, just pass a tuple with the variable and the desired order:
Q_poly.diff((x, 2))
\(\displaystyle \operatorname{Poly}{\left( 2, x, domain=\mathbb{Z} \right)}\)
Q_poly.diff((x, 3))
\(\displaystyle \operatorname{Poly}{\left( 0, x, domain=\mathbb{Z} \right)}\)
With the integrate method, we can obtain the indefinite integral of the
polynomial:
Q_poly.integrate()
\(\displaystyle \operatorname{Poly}{\left( \frac{1}{3} x^{3} - 25 x, x, domain=\mathbb{Q} \right)}\)
Likewise, we can obtain the value of the integral at a certain value of x with
the subs method or with the eval method:
Q_poly.integrate().subs({x: 1})
\(\displaystyle - \frac{74}{3}\)
Q_poly.integrate().eval(1)
\(\displaystyle - \frac{74}{3}\)
The Poly class and complex roots #
We have already seen that there is the Poly class in SymPy. We can pass an
expression directly to the class instead of using the as_poly method:
expr = x**3 + 2*x + 3
A = sympy.Poly(expr)
A
\(\displaystyle \operatorname{Poly}{\left( x^{3} + 2 x + 3, x, domain=\mathbb{Z} \right)}\)
type(A)
sympy.polys.polytools.Poly
Note that our polynomial A is of the third degree. Let’s see all its roots:
A.all_roots()
\(\displaystyle \left[ -1, \ \frac{1}{2} - \frac{\sqrt{11} i}{2}, \ \frac{1}{2} + \frac{\sqrt{11} i}{2}\right]\)
We have complex roots now. Let’s see the result of the is_real attribute of
each root:
for root in A.all_roots():
print(root.is_real)
True
False
False
It makes sense, the first root is real, the others, complex.
This explains the existence of an all_roots method. After all, if there is a
method that is concerned with returning all roots, it is because there must be
some method that returns only some, right? Depending on the application in
question, we may be interested only in real roots. Then we can use the
real_roots method:
A.real_roots()
\(\displaystyle \left[ -1\right]\)
Polynomial division #
Now, let’s check two Poly objects that we created throughout the article:
A
\(\displaystyle \operatorname{Poly}{\left( x^{3} + 2 x + 3, x, domain=\mathbb{Z} \right)}\)
Q_poly
\(\displaystyle \operatorname{Poly}{\left( x^{2} - 25, x, domain=\mathbb{Z} \right)}\)
To facilitate understanding, let’s associate a variable B to Q_poly:
B = Q_poly
B
\(\displaystyle \operatorname{Poly}{\left( x^{2} - 25, x, domain=\mathbb{Z} \right)}\)
In the article call, we talked about polynomial division. The time has come, let’s see how to do such an operation with SymPy.
It is very simple, we call the div method passing the dividend and the
divisor:
sympy.div(A, B)
\(\displaystyle \left( \operatorname{Poly}{\left( x, x, domain=\mathbb{Z} \right)}, \ \operatorname{Poly}{\left( 27 x + 3, x, domain=\mathbb{Z} \right)}\right)\)
The return is a tuple with two polynomials, the quotient and the remainder. We can obtain each one separately by unpacking the resulting tuple:
quotient, remainder = sympy.div(A, B)
quotient
\(\displaystyle \operatorname{Poly}{\left( x, x, domain=\mathbb{Z} \right)}\)
remainder
\(\displaystyle \operatorname{Poly}{\left( 27 x + 3, x, domain=\mathbb{Z} \right)}\)
Since it is a division, we expect that divisor X quotient + remainder = dividend. Let’s see:
B * quotient + remainder
\(\displaystyle \operatorname{Poly}{\left( x^{3} + 2 x + 3, x, domain=\mathbb{Z} \right)}\)
A == B * quotient + remainder
True
Simple as that.
Conclusion and more articles about SymPy #
In this article, we saw the basics of how to deal with polynomials in SymPy and I hope it was possible to realize the power of this library.
See you next time.