Polinômios com SymPy – Cálculos com Python

sympy polinômios

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.

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.

Compartilhe:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima