Introdução ao SymPy – Cálculos com Python

sympy introducao

Muitos consideram matemática, física e afins matérias difíceis, geralmente pela abordagem tradicional vista nas escolas. Mas a verdade é que, mostradas de uma forma mais adequada, são áreas sensacionais e que, se dominadas, nos ajudam a dar outra visão de mundo. E computadores podem ajudar em etapas mais complexas e tediosas das manipulações aritméticas. Nesse artigo, começaremos a explorar a biblioteca SymPy, aproveitando para fazer uma boa revisão de conceitos matemáticos fundamentais.

Um sistema algébrico computacional (em inglês computer algebra system (CAS)) pode ser utilizado para cálculos com expressões matemáticas complicadas, resolução de equações, simulações, dentre outras aplicações.

Há sistemas gratuitos como o SymPy e o Octave, e comerciais como Maple, MATLAB e Mathematica. Em essência, todos oferecem as mesmas funcionalidades, de forma que a escolha recai em decidir aspectos técnicos e de custo X benefício, especialmente no que diz respeito a integrações com outras ferramentas. Nesse sentido, o SymPy tem uma grande vantagem, já que a linguagem Python é largamente utilizada em diversos contextos de forma que facilmente se pode expandir as funcionalidades. Inclusive, no site do projeto SymPy, estes são os pontos destacados:

  • gratuito, sob a licença BSD
  • baseado em Python
  • leve, tendo como única dependência a biblioteca mpmath
  • é uma biblioteca, podendo ser usada em outras aplicações e extendida facilmente

E, o principal motivo para a escolha do SymPy, você pode conferir suas respostas para as listas de cálculo da faculdade ou daquelas provas de professores sem criatividade, com um monte de conta e sem nenhuma aplicação real ou contextualização 😉 Só não diga que eu escrevi isso.

nazareth_calculo

E se você é professor e a carapuça serviu, calma e respira. É óbvio que o aluno precisa aprender cálculo, até porque se não souber o mínimo não vai nem ao menos saber usar o pacote, computadores não fazem milagres, devolvem exatamente o que foi solicitado. A crítica é sobre aplicar esses conhecimentos em casos reais. Crie projetos onde as contas são aplicadas, crie resolvedores de problemas e não calculadoras humanas.

O SymPy é um sistema algébrico computacional simbólico. Isto significa que números e operações são representados simbolicamente permitindo obter resultados exatos. Vamos entender isso melhor.

Considere o número \sqrt{2}. No SymPy, tal número é representado pelo objeto Pow(2, 1/2), enquanto que em CAS numéricos, como o Octave, é representado como uma aproximação 1.41421356237310 (um float). Mas e daí? Bom, dependendo da aplicação, não há problemas em haver essa aproximação, mas ela pode levar a problemas. Vamos ver um exemplo.

Se usarmos o método sqrt do pacote math do Python, também teremos a raiz armazenada como um float:

import math

math.sqrt(2)
1.4142135623730951

Ora, sabemos que se elevar \sqrt{2} ao quadrado, devemos ter o número 2, correto? Vejamos:

math.sqrt(2)**2
2.0000000000000004

Viu o que aconteceu? Há um “4” perdido ali que, de forma bem simplificada, surge do fato de a representação ser uma aproximação.

E qual a consequência disso? Poderíamos listar algumas, mas vou focar em uma que pega muitos iniciantes de surpresa. É muito comum o uso de comparações com == em condicionais ou em testes de funções. Essas comparações correm sério risco de falhar quando envolvem floats:

math.sqrt(2)**2 == 2
False

O que fazer nesses casos? Bom, se o contexto exigir continuar utilizando aproximações numéricas e floats, uma forma simples, mas nem sempre confiável, é comparar com uma tolerância aceitável no contexto. Por exemplo, digamos que um erro de 1 em 10000 é aceitável em um dado contexto. Assim, o teste poderia ser escrito como:

math.sqrt(2)**2 - 2 < 1E-4
True

Em algum momento vou fazer artigos abordando melhor os cuidados a se tomar quando trabalhar com floats, mas no momento fica essa recomendação de leitura: aritmética com floats da própria documentação do Python.

Vejamos como se comporta o SymPy nesse exemplo simples.

Primeiro, a instalação pode ser feita com um simples pip install sympy. Caso utilize o Anaconda, que já escrevemos sobre aqui, o SymPy já está instalado. A importação é simples:

import sympy

Vamos ver como é a representação da raiz quadrada de 2:

sympy.sqrt(2)
\displaystyle \sqrt{2}

Observe que apareceu o símbolo \sqrt{2} e não uma aproximação na forma de float. Vamos fazer a operação de elevar ao quadrado:

sympy.sqrt(2)**2
\displaystyle 2

Obtivemos o resultado exato 2. Inclusive podemos verificar que é realmente o número 2 com a comparação:

sympy.sqrt(2)**2 == 2
True

E, sendo a expressão tratada simbolicamente, pode ser simplificada simbolicamente. Lembra que \sqrt{8} = 2\sqrt{2}? Então:

sympy.sqrt(8)
\displaystyle 2 \sqrt{2}

O SymPy cuidou dessa simplificação para você. E, mais, simbolicamente fica explícita essa relação, enquanto que se você fizer \sqrt{8} na sua calculadora ou usando um pacote numérico dificilmente perceberá que o resultado corresponde a 2\sqrt{2}:

math.sqrt(8)
2.8284271247461903

Vamos agora ver o básico de matemática com SymPy.

Matemática básica com SymPy

Vamos entender como o SymPy representa objetos matemáticos e operações simples. Mas antes, vamos nos lembrar que em Python há dois tipos numéricos principais: inteiros e floats.

3  # int
3
3.0  # float
3.0

Floats são representações aproximadas de números reais, até a 16ª casa decimal. Quando fazemos uma divisão, mesmo que de inteiros, o resultado é um float:

1/1
1.0
1/7
0.14285714285714285

A chamada divisão inteira, representada por duas barras, fornece a parte inteira da divisão:

1//7
0

Enquanto que o sinal %, conhecido como módulo, fornece o resto da divisão:

1 % 7
1

Números racionais

Agora, sabemos que 1/7 pertence ao conjunto dos números racionais e sua representação decimal é infinitamente longa. Para uma representação exata, precisamos do método sympify do SymPy:

sympy.sympify('1/7')
\displaystyle \frac{1}{7}

Que pode ser chamado abreviadamente de S:

sympy.S('1/7')
\displaystyle \frac{1}{7}

Veja como foi passada a fração para o método, como uma string. Vamos ver o tipo do objeto gerado:

type(sympy.S('1/7'))
sympy.core.numbers.Rational

Logo, a mesma fração poderia ser criada como:

sympy.Rational(1, 7)
\displaystyle \frac{1}{7}

Observe que na forma acima passados o numerador e o denominador na forma de inteiros. Podemos fazer operações entre racionais:

sympy.Rational(1, 2) + sympy.S('1/3')
\displaystyle \frac{5}{6}

Um objeto SymPy dividido por um inteiro resulta em um objeto SymPy:

sympy.S('1')/7
\displaystyle \frac{1}{7}

Obtendo aproximações numéricas

Durante a resolução de problemas, é melhor deixar para obter a representação numérica apenas ao final. Para obter a aproximação numérica de um objeto SymPy como um float, usamos o método evalf(). Vamos ver um exemplo com o número \pi:

sympy.pi
\displaystyle \pi
type(sympy.pi)
sympy.core.numbers.Pi

Observe que é um tipo específico do SymPy, que possui alguns números em sua biblioteca. Veremos alguns no decorrer do tempo. Vejamos a representação como float:

sympy.pi.evalf()
\displaystyle 3.14159265358979
type(sympy.pi.evalf())
sympy.core.numbers.Float

Observe acima que o SymPy possui uma classe Float própria (o mesmo vale para inteiros e outros tipos de números), não utilizando o float padrão do Python. Assim, cuidado ao misturar tipos padrão do Python e tipos SymPy. Na dúvida, aplique o método sympify já mostrado para garantir que tudo está como objetos SymPy.

Uma forma resumida de chamar o método evalf é simplesmente usar o método n:

sympy.pi.n()
\displaystyle 3.14159265358979

É possível solicitar mais casas decimais. Por exemplo, se desejarmos 50 casas decimais para o número \pi:

sympy.pi.n(50)
\displaystyle 3.1415926535897932384626433832795028841971693993751

Isso é possível pois o SymPy utiliza a biblioteca mpmath por baixo, que permite cálculos com precisão aritmética arbitrária.

Também existe uma função global N para obter uma representação numérica de objetos SymPy:

sympy.N(sympy.pi)
\displaystyle 3.14159265358979

Como é matemática simbólica, podemos utilizar, por exemplo, a noção de infinito, que possui o curioso símbolo oo:

sympy.oo
\displaystyle \infty
sympy.oo > 100
\displaystyle \text{True}

Símbolos

Como estamos falando de álgebra simbólica, nada melhor que vermos como criar símbolos com SymPy. De Python, sabemos que, se utilizarmos algum símbolo não definido previamente, um NameError será levantado:

x + 2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_342/917526016.py in <module>
----> 1 x + 2

NameError: name 'x' is not defined

A partir do momento que definirmos x, ele pode ser utilizado. E, em Python, não há necessidade de declarar o tipo de x:

x = 2
x + 2
4

A questão é que gostaríamos que x fosse reconhecido como um símbolo. Pense no x das aulas de matemática do ensino fundamental, das famosas questões de “encontre o x”:

encontre_x

Hehe…

Ou seja, queremos x com o sentido clássico de uma variável matemática. Podemos fazer da seguinte forma:

x = sympy.Symbol('x')
x + 2
\displaystyle x + 2

Veja que agora aparece como uma expressão matemática. Podemos criar vários símbolos ao mesmo tempo, repare que agora o método é symbols:

y, z = sympy.symbols('y z')
x + y + z - 5
\displaystyle x + y + z - 5

O SymPy permite criar símbolos que sigam um padrão. Por exemplo, no estudo de polinômios é muito comum representar todos os coeficientes com a letra a mudando apenas o número subscrito. Isso pode ser criado com uma representação muito similar à representação de slices de sequências em Python, na qual o último número é excluso:

a1, a2, a3, a4 = sympy.symbols('a1:5')
a4 * x**4 + a3 * x**3 + a2 * x**2 + a1 * x + 2
\displaystyle a_{1} x + a_{2} x^{2} + a_{3} x^{3} + a_{4} x^{4} + 2

Veja que por padrão o SymPy apresenta o polinômio do menor grau em x para o maior, deixando o termo de x^0 para o final. É possível mudar a ordem alterando a instrução de ordem monomial do método init_printing, que configura como serão apresentadas as expressões do SymPy. Vejamos:

sympy.init_printing(order='grlex')
a4 * x**4 + a3 * x**3 + a2 * x**2 + a1 * x + 2

grlex vem de graded lexicographic order, partindo do maior grau para o menor, o que é mais usual em textos matemáticos e afins. Para voltar ao padrão do SymPy, usamos a opção lex:

sympy.init_printing(order='lex')
a4 * x**4 + a3 * x**3 + a2 * x**2 + a1 * x + 2

De acordo com a documentação do SymPy, algumas letras são reservadas e deve se evitar utilizá-las como símbolos: I, E, N, S, O, Q e C. N e S já vimos anteriormente. I e E representam o número imaginário i \equiv \sqrt{-1} e a base do logaritmo natural, respectivamente:

sympy.I
sympy.E

O O é utilizado para notação de big O:

sympy.O
sympy.series.order.Order
sympy.O(x**2) * x

O C e o Q têm usos mais específicos, que fogem ao escopo desse material introdutório, mas que podem ser vistas na documentação já citada.

Expressões

Expressões podem ser criadas combinando símbolos com operações matemáticas e outras funções:

expr = 2 * x + 3 * x - sympy.sin(x) - 3 * x + 42
expr

Observe que a expressão já foi apresentada simplificada pois se trata de um caso simples de adição. Em casos mais elaborados, o SymPy não é proativo na simplificação, tendo em vista que pode ser de interesse do usuário obter a expressão crua, sem manipulações. Mas é possível solicitar a simplificação.

Simplificando expressões e obtendo coeficientes

Há um método específico para simplificação:

(x + x * y) / x
sympy.simplify(_)

O underscore _ pega o resultado da célula anterior e passa para o simplify.

Voltando para nossa expressão expr, é possível verificar os coeficientes dos termos em x com o método coeff, supondo a expressão como um polinômio em x:

expr.coeff(x)

Como a expressão é de ordem 1 em x, não há, por exemplo, coeficiente para um termo de ordem 2 ou, melhor dizendo, tal coeficiente é zero:

expr.coeff(x, 2)

Se quisermos a parcela 42 na expressão, basta solicitar o coeficiente do termo x^0:

expr.coeff(x, 0)

É possível também verificar os coeficientes relativos a outros termos, basta tratá-los como se fossem a variável do polinômio:

expr.coeff(sympy.sin(x))

Fatoração e expansão (distributiva)

Há métodos para diversas operações matemáticas usuais em expressões. Por exemplo, a operação de fatoração:

sympy.factor(x**2 - 2*x - 8)

E a contrária, a expansão ou multiplicação distributiva:

sympy.expand( (x - 4)*(x + 2) )

Também é possível fazer expansões de expressões trigonométricas. Por exemplo:

sympy.cos(x + y)
sympy.expand(_, trig=True)

O método collect

Quando símbolos são utilizados como coeficientes, o SymPy não os junta. Mas podemos usar o método collect que coleta aditivamente termos com relação a uma potência. Para entender, vamos criar dois símbolos, a e b, e criar uma expressão com os mesmos:

a, b = sympy.symbols('a b')
expr = x**2 + x*b + a*x + a*b
expr

Vamos, então, passar a expressão para o método collect, dizendo para agrupar os coeficientes do termo x^1:

sympy.collect( expr, x )

Observe que a expressão foi escrita de forma a deixar explícito que o coeficiente do termo x é (a + b). Isso pode ser útil em alguns contextos, especialmente para apresentação.

No entanto, mesmo sem escrever explicitamente, o SymPy já reconhece internamente o coeficiente como (a + b), como se pode ver com o método coeff:

expr.coeff(x, 1)

Substituindo valores em uma expressão

Algo de muito interesse no contexto de expressões matemáticas é poder substituir valores para verificar qual o valor da expressão após tal substituição. Vamos criar uma nova expressão:

expr = sympy.sin(x) + sympy.cos(y)
expr

Com o método subs, podemos passar um dicionário com os valores desejados para cada símbolo:

expr.subs({x: 1, y:2})

Observe que o retorno foi ainda simbólico, mas podemos ver uma aproximação em float usando o método n visto anteriormente:

expr.subs({x: 1, y:2}).n()

Caso o resultado da substituição seja exato, o retorno será apresentado diretamente. Por exemplo, sabemos que \sin(\pi) = 0 e \cos(0) = 1. De forma que a soma daria 1. Vejamos:

expr.subs({x: sympy.pi, y:0})

O mesmo para outros ângulos notáveis, que possuem representações simbólicas. Por exemplo:

expr.subs({x: sympy.pi/2, y:sympy.pi/4})

Caso não tenha entendido, relembre os ângulos notáveis com a imagem seguinte do círculo unitário:

unit_circle_angles

No caso de se solicitar uma substituição com valores já aproximados, teremos o seguinte tipo de resultado:

expr.subs({x: sympy.pi/2.5, y:sympy.pi/4.3})

Conclusão e mais artigos sobre SymPy

Neste artigo vimos o básico de SymPy e espero que tenha sido possível perceber o poder dessa biblioteca. Esse é apenas o primeiro de uma série de artigos sobre a 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 e, caso queira ver a série na ordem, veja a lista abaixo, que será atualizada a cada novo artigo:

Até a próxima.

Compartilhe:

2 comentários em “Introdução ao SymPy – Cálculos com Python”

  1. Pedro Paulo

    Parabéns pelo conteúdo. Excelente e didático!
    Já coloquei a página nos meus favoritos e também como página de inicialização do meu navegador.
    Continuem com este trabalho incrível!

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