Certamente um dos assuntos mais abstratos nas aulas de matemática: polinômios. Afinal, que atire a primeira variável quem nunca ficou confuso em uma divisão de polinômios, é x que não acaba mais. Neste artigo, veremos como a biblioteca SymPy, para a linguagem Python, nos ajuda a lidar com operações envolvendo polinômios.
Criando polinômios manualmente #
Comecemos importando a biblioteca que usaremos durante o artigo:
import sympy
# configuração para outputs melhores no artigo, pode ser ignorado
sympy.init_printing(use_latex='png', scale=1.0, order='grlex',
forecolor='Black', backcolor='White',)
Para começar, vamos utilizar os conhecimentos mais básicos que adquirimos no primeiro artigo da série do SymPy. Vamos criar um símbolo para nossa variável e uma expressão que representará um polinômio:
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)\)
Veja que criamos nossa expressão polinomial representando um polinômio na forma
fatorada. Para obter a forma expandida, ou seja, a forma onde todas as
multiplicações distributivas são feitas, utilizamos o método expand:
expr.expand()
\(\displaystyle x^{3} - 6 x^{2} + 11 x - 6\)
No caso de inicialmente ter um polinômio expandido, pode-se fatorá-lo com o
método factor:
_.factor()
\(\displaystyle \left(x - 3\right) \left(x - 2\right) \left(x - 1\right)\)
A forma fatorada já deixa evidente que as raízes do polinômio são 1, 2 e 3. Mas
podemos obter as raízes programaticamente utilizando o solve do
SymPy:
roots = sympy.solve(expr, x)
roots
\(\displaystyle \left[ 1, \ 2, \ 3\right]\)
Checando igualdades #
Vamos criar duas novas expressões polinomiais:
P = (x - 5) * (x + 5)
Q = x**2 - 25
Veja que P é simplesmente a forma fatorada de Q. Logo, são equivalentes e se
verificarmos a igualdade entre eles o resultado será True, correto?
P == Q
False
Ué…?! Bom, já escrevemos sobre essa questão da igualdade no
SymPy.
A biblioteca analisa igualdade de forma e não equivalência matemática. Logo,
como as formas dos dois polinômios são distintas, a comparação retorna False.
Da mesma maneira, sabemos que matematicamente a diferença entre os polinômios é
zero afinal são equivalentes. Mas o retorno da seguinte comparação também é
False:
P - Q == 0
False
Por que? Porque o SymPy não é proativo nas simplificações, como já vimos em outros artigos. Vejamos a representação da diferença:
P - Q
\(\displaystyle - x^{2} + \left(x - 5\right) \left(x + 5\right) + 25\)
Veja que efetivamente o SymPy armazena a expressão, sem simplificá-la. Se
solicitarmos a simplificação com o método simplify, vemos que efetivamente a
diferença equivale ao valor zero:
sympy.simplify(P - Q) == 0
True
E podemos ver que efetivamente um polinômio é a versão fatorada do outro com as seguintes comparações:
P.simplify() == Q
True
P == Q.factor()
True
Usando o construtor de polinômios #
Até o momento, vimos poucas novidades quando comparado ao artigo sobre expressões com SymPy. Mas vamos mudar isso. Vejamos o tipo do objeto Q:
Q
\(\displaystyle x^{2} - 25\)
type(Q)
sympy.core.add.Add
Veja que, internamente, é um objeto da classe Add do SymPy. Ou seja, para o
SymPy tal objeto é simplesmente a resultante da adição de dois outros objetos
SymPy, o que está correto. Quem está dando o significado de polinômio para o
objeto somos nós. Vamos entender essa última frase.
A grande vantagem de utilizar uma linguagem de alto nível como o Python é que podemos escrever código que seja mais próximo à nossa compreensão utilizando abstrações. Quem cuida de traduzir tais abstrações para a máquina é o interpretador da linguagem. Até o momento utilizamos meras expressões como representação de polinômios, mas o ideal seria efetivamente que o SymPy reconhecesse tais expressões como polinômios e não como adições. E isso é possível e veremos as vantagens dessa abordagem.
Expressões possuem o método as_poly:
Q.as_poly()
\(\displaystyle \operatorname{Poly}{\left( x^{2} - 25, x, domain=\mathbb{Z} \right)}\)
Veja o retorno! Agora o SymPy reconhece como um tipo Poly, reconhece que a
variável é x e que o domínio é dos números inteiros (pode ser alterado, é
apenas o padrão quando não passamos explicitamente o domínio). Vamos associar
esse retorno à uma variável e confirmar o tipo:
Q_poly = Q.as_poly()
type(Q_poly)
sympy.polys.polytools.Poly
E qual a vantagem? Agora podemos solicitar o que desejamos com métodos mais próximos da semântica que utilizamos em matemática. Por exemplo, podemos solicitar os coeficientes do polinômio:
Q_poly.coeffs()
\(\displaystyle \left[ 1, \ -25\right]\)
O retorno é do maior grau em x para o menor. Porém, perceba que o retorno pode
levar a enganos. Afinal, sabemos que há o termo de grau 1 em x, só o
coeficiente dele que é zero. O coeffs retorna apenas coeficientes não nulos.
Para evitar enganos, recomendo sempre utilizar o método all_coeffs:
Q_poly.all_coeffs()
\(\displaystyle \left[ 1, \ 0, \ -25\right]\)
Agora sim, todos os coeficientes.
Outro conceito que sempre aparece no estudo de polinômios: grau do polinômio.
Agora que temos uma abstração mais próxima de nossa linguagem, podemos
efetivamente solicitar o grau com o método degree:
Q_poly.degree()
\(\displaystyle 2\)
Podemos solicitar a lista de fatores do polinômio:
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)\)
Solicitar as raízes:
Q_poly.all_roots()
\(\displaystyle \left[ -5, \ 5\right]\)
Derivadas e integrais #
Podemos, inclusive, fazer operações mais elaboradas. Saindo um pouco de matemática de ensino médio, é muito comum precisarmos derivar ou integrar um polinômio. Agora, tais operações se tornam triviais. Vejamos a primeira derivada de nosso polinômio:
Q_poly.diff()
\(\displaystyle \operatorname{Poly}{\left( 2 x, x, domain=\mathbb{Z} \right)}\)
Podemos obter o valor da derivada em um determinado valor de x utilizando o
método subs já visto em outros artigos:
Q_poly.diff().subs({x: 2})
\(\displaystyle 4\)
Ou, mais simples, com o método eval e passando o valor de x:
Q_poly.diff().eval(2)
\(\displaystyle 4\)
Para derivadas de mais alta ordem, basta passar uma tupla com a variável e a ordem desejada:
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)}\)
Para obter a integral, usamos o método integrate:
Q_poly.integrate()
\(\displaystyle \operatorname{Poly}{\left( \frac{1}{3} x^{3} - 25 x, x, domain=\mathbb{Q} \right)}\)
Da mesma forma, podemos obter o valor da integral em determinado valor de x
com o método subs ou com o método eval:
Q_poly.integrate().subs({x: 1})
\(\displaystyle - \frac{74}{3}\)
Q_poly.integrate().eval(1)
\(\displaystyle - \frac{74}{3}\)
A classe Poly e raízes complexas #
Já vimos que há a classe Poly no SymPy. Podemos passar uma expressão
diretamente para a classe ao invés de usar o método as_poly:
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
Perceba que nosso polinômio A é de terceiro grau. Vejamos todas as suas raízes:
A.all_roots()
\(\displaystyle \left[ -1, \ \frac{1}{2} - \frac{\sqrt{11} i}{2}, \ \frac{1}{2} + \frac{\sqrt{11} i}{2}\right]\)
Temos raízes complexas agora. Vejamos o resultado do atributo is_real de cada
raiz:
for root in A.all_roots():
print(root.is_real)
True
False
False
Faz sentido, a primeira raiz é real, as demais, complexas.
Isso explica a existência de um método all_roots. Afinal, se há um método que
se preocupa em retornar todas as raízes, é porque deve haver algum método que
retorna apenas algumas, né? A depender da aplicação em questão, podemos estar
interessados apenas nas raízes reais. Daí podemos usar o método real_roots:
A.real_roots()
\(\displaystyle \left[ -1\right]\)
Divisão de polinômios #
Vamos verificar dois objetos Poly que criamos no decorrer do artigo:
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)}\)
Para facilitar a compreensão vamos associar uma variável B ao Q_poly:
B = Q_poly
B
\(\displaystyle \operatorname{Poly}{\left( x^{2} - 25, x, domain=\mathbb{Z} \right)}\)
Na chamada do artigo, falamos sobre divisão de polinômios. É chegado o momento, vejamos como fazer tal operação com o SymPy.
É bem simples, chamamos o método div passando o dividendo e o 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)\)
O retorno é uma tupla com dois polinômios, o quociente e o resto. Podemos obter cada um separadamente fazendo unpack da tupla resultante:
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)}\)
Como é uma divisão, esperamos que divisor X quociente + resto = dividendo. Vejamos:
B * quotient + remainder
\(\displaystyle \operatorname{Poly}{\left( x^{3} + 2 x + 3, x, domain=\mathbb{Z} \right)}\)
A == B * quotient + remainder
True
Simples.
Conclusão e mais artigos sobre SymPy #
Neste artigo vimos o básico de como lidar com polinômios no SymPy e espero que tenha sido possível perceber o poder dessa biblioteca.
Até a próxima.