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.
Tópicos
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
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()
No caso de inicialmente ter um polinômio expandido, pode-se fatorá-lo com o método factor
:
_.factor()
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
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
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
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()
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()
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()
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()
Podemos solicitar a lista de fatores do polinômio:
Q_poly.factor_list()
Solicitar as raízes:
Q_poly.all_roots()
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()
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})
Ou, mais simples, com o método eval
e passando o valor de x:
Q_poly.diff().eval(2)
Para derivadas de mais alta ordem, basta passar uma tupla com a variável e a ordem desejada:
Q_poly.diff((x, 2))
Q_poly.diff((x, 3))
Para obter a integral, usamos o método integrate
:
Q_poly.integrate()
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})
Q_poly.integrate().eval(1)
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
type(A)
sympy.polys.polytools.Poly
Perceba que nosso polinômio A é de terceiro grau. Vejamos todas as suas raízes:
A.all_roots()
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()
Divisão de polinômios
Vamos verificar dois objetos Poly
que criamos no decorrer do artigo:
A
Q_poly
Para facilitar a compreensão vamos associar uma variável B
ao Q_poly
:
B = Q_poly
B
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)
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
remainder
Como é uma divisão, esperamos que divisor X quociente + resto = dividendo. Vejamos:
B * quotient + remainder
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.
Caso queira saber quando novos artigos são disponibilizados, siga o Ciência Programada nas redes sociais. A lista completa de artigos sobre SymPy pode ser vista na tag SymPy.
Até a próxima.