Ir para o conteúdo principal

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

·2490 palavras·12 minutos·
Programação Sympy Python Matemática
Autor
Francisco Bustamante
Um químico trabalhando com Ciência de Dados e Programação em Python.
Tabela de conteúdos
SymPy - Este artigo faz parte de uma série de artigos.
Parte 1: Esse Artigo

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.

Introdução e instalação do SymPy
#

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
Nazareth confusa com cálculo

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”:

econtre_x
Clássico ’encontre o 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

\(\displaystyle a_{4} x^{4} + a_{3} x^{3} + a_{2} x^{2} + a_{1} 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

\(\displaystyle a_{1} x + a_{2} x^{2} + a_{3} x^{3} + a_{4} x^{4} + 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

\(\displaystyle i\)

sympy.E

\(\displaystyle e\)

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

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

\(\displaystyle O\left(x^{3}\right)\)

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

\(\displaystyle 2 x - \sin{\left(x \right)} + 42\)

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

\(\displaystyle \frac{x y + x}{x}\)

sympy.simplify(_)

\(\displaystyle y + 1\)

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)

\(\displaystyle 2\)

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)

\(\displaystyle 0\)

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

expr.coeff(x, 0)

\(\displaystyle 42\)

É 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))

\(\displaystyle -1\)

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)

\(\displaystyle \left(x - 4\right) \left(x + 2\right)\)

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

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

\(\displaystyle x^{2} - 2 x - 8\)

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

sympy.cos(x + y)

\(\displaystyle \cos{\left(x + y \right)}\)

sympy.expand(_, trig=True)

\(\displaystyle - \sin{\left(x \right)} \sin{\left(y \right)} + \cos{\left(x \right)} \cos{\left(y \right)}\)

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

\(\displaystyle a b + a x + b x + x^{2}\)

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 )

\(\displaystyle a b + x^{2} + x \left(a + b\right)\)

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)

\(\displaystyle a + b\)

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

\(\displaystyle \sin{\left(x \right)} + \cos{\left(y \right)}\)

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

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

\(\displaystyle \cos{\left(2 \right)} + \sin{\left(1 \right)}\)

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()

\(\displaystyle 0.425324148260754\)

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})

\(\displaystyle 1\)

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})

\(\displaystyle \frac{\sqrt{2}}{2} + 1\)

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

angulos_notaveis
Ângulos notáveis no círculo unitário

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})

\(\displaystyle \cos{\left(0.232558139534884 \pi \right)} + \sin{\left(0.4 \pi \right)}\)

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.

Até a próxima.

SymPy - Este artigo faz parte de uma série de artigos.
Parte 1: Esse Artigo

Relacionados

yield from - O que é? Entendendo geradores em Python
·1058 palavras·5 minutos
Programação Python Geradores Yield Drops
Você já viu o termo “yield from” em algum código Python e ficou imaginando o que era? Nesse artigo vamos nos aprofundar ainda mais em geradores e entender, com exemplos, o que significa o “yield from” e como podemos utilizá-lo para deixar nossos códigos ainda mais eficientes.
Sequências infinitas em Python - Fibonacci como você nunca viu
·1038 palavras·5 minutos
Programação Python Itertools Islice Drops
Você sabia que é possível criar uma sequência infinita sem ter problemas de memória e ainda consumir essa sequência da forma que você quiser? Hoje veremos um método específico do módulo itertools, o islice, que é excelente para nosso propósito. Aprenderemos a utilizá-lo com o exemplo da série de Fibonacci.
Funções any e all em Python
·2245 palavras·11 minutos
Programação Python Any All Drops
Neste artigo, vamos ver como funcionam as funções all e any, presentes numa instalação padrão da linguagem Python. E, mais, vamos ver a utilidade de cada uma dessas funções e cuidados ao utilizá-las com base em uma análise de complexidade.