Resolvendo equações com SymPy

sympy equacoes

Grande parte do estudo de matemática em níveis mais fundamentais é dedicada a resoluções de equações e sistemas de equações. Neste artigo veremos como utilizar o SymPy para essas tarefas de forma rápida e intuitiva.

Vamos começar importando o SymPy e criando alguns símbolos. Já falamos sobre criação de símbolos no primeiro artigo dessa série sobre SymPy então, se tiver dúvidas, dê uma olhada lá.

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',)

x, y, a, b, c = sympy.symbols('x y a b c')

Usando expressões e solve

Vamos agora criar uma expressão na variável x:

expr = x**2 + 2*x - 8

Como podemos ver, a expressão é de grau 2 em x:

expr

Assim, se igualarmos a expressão a zero, teremos uma equação quadrática, a famosa equação de 2° grau, e poderíamos resolver tal equação. Ou seja, achar os valores de x para os quais a equação tem valor zero, as raízes da equação. Para isso, usamos o método solve com a assinatura solve(expr, var), sendo var a variável para a qual se quer resolver a equação:

sympy.solve(expr, x)

Assim, vemos que as raízes da equação x2+2x-8=0 são -4 e 2.

Agora o grande poder do SymPy é ser um CAS simbólico. Logo, podemos ter as expressões gerais para raízes de uma equação de segundo grau escrita na forma geral ax2+bx+c=0:

sympy.solve( a*x**2 + b*x + c, x)

Ora, nada mais é que a famosa fórmula de Bhaskara para resolução de equações de segundo grau. A fórmula geralmente apresentada nos livros contém o símbolo ± para indicar que são duas raízes. Acima temos de forma mais explícita as duas raízes.

Vimos anteriormente que a solução é apresentada na forma de uma lista com cada raiz. Vamos, então, ver cada solução separadamente acessando cada item da lista da forma usual em Python:

solutions = sympy.solve( a*x**2 + b*x + c, x)
solutions[0]
solutions[1]

Por listas serem iteráveis, podemos fazer um loop substituindo a, b e c pelos valores utilizados na primeira expressão desse artigo e verificar que as raízes são, realmente, -4 e 2:

roots = []
for solution in solutions:
    roots.append(solution.subs({'a': 1, 'b': 2, 'c': -8}))
    
roots

Usando igualdades e solve

A abordagem anterior é perfeitamente correta e é muito usada em scripts e tutoriais que você encontra na internet. No entanto, há outra forma de abordar o problema que utiliza uma abstração de igualdade que existe no SymPy e que, particularmente, acho mais interessante por ser mais semântica, mais próxima do que efetivamente queremos representar matematicamente.

O SymPy possui a classe Equality que tem por intuito expressar a igualdade entre dois objetos. Há a abreviação Eq que torna mais fácil de escrever códigos. Assim, a mesma expressão anterior, que traduzimos implicitamente como equação apenas quando fizemos solve(expr, x), poderia ser mais explicitamente ser reconhecida diretamente como equação com:

equation = sympy.Eq(x**2 + 2*x - 8, 0)
equation

Veja que agora efetivamente escrevemos como uma equação, com um lado esquerdo e um direito. O direito, no caso, sendo o número zero. Certamente essa forma é mais facilmente compreendida.

E, claro, podemos passar essa equação para o solve e obter as mesmas raízes:

sympy.solve(equation, x)

Não necessariamente o lado direito precisa ser zero. A mesma equação poderia ser representada por:

sympy.Eq(x**2 + 2*x, 8)

Que, obviamente, resultaria nas mesmas raízes ao ser passada para solve:

sympy.solve(sympy.Eq(x**2 + 2*x, 8), x)

Sistemas de equações

Já vimos que podemos resolver uma equação, mas e um sistema? Podemos facilmente. A primeira forma é passando um iterável de expressões para solve e, também, as variáveis do sistema. Suponha um sistema formado pelas equações x + y = 3 e 3x – 2y = 0. Poderíamos escrever:

sympy.solve((x + y - 3, 3*x - 2*y), 
            (x, y))

Poderíamos, também, ser mais explícitos criando cada equação com Eq e passando para solve:

eq1 = sympy.Eq(x + y, 3)
eq2 = sympy.Eq(3*x - 2*y, 0)

sympy.solve((eq1, eq2), 
            (x, y))

Cuidado com floats!

Observe que o resultado do sistema saiu na forma de um dicionário. E podemos efetivamente substituir em qualquer equação para ver o resultado:

eq1.subs({x: 6/5, y: 9/5})

O que significa o resultado True? Significa que a igualdade da equação e satisfeita. Veja a representação de eq1:

eq1

Assim, o True significa que, quando os valores passados substituem x e y na equação, a igualdade é satisfeita, é verdadeira.

Bom, seria de se esperar que a mesma substituição em eq2 retornaria True, certo?

eq2.subs({x: 6/5, y: 9/5})

Ué…?!

Cuidado com floats! No primeiro artigo sobre SymPy já abordamos um pouco disso e vimos como podemos representar frações no SymPy. Veja, quando se escreve 6/5 em Python o que temos é:

6/5

Ou seja, um float. E operações com floats envolvem aproximações, como vimos no artigo já citado. Assim, compensa transformar em representações de frações do SymPy:

eq2.subs({x: sympy.S('6/5'), y: sympy.S('9/5')})

Agora sim, temos a igualdade como válida.

Completando quadrados

Completar quadrados é uma técnica para converter um polinômio na forma ax2+bx+c para a forma a(x-h)2+k para alguns valores de h e k. É utilizada em diversos contextos de ensino, sendo o mais conhecido na derivação da fórmula quadrática de Bhaskara.

Na animação a seguir, vemos que h=-b ⁄ (2a) e k= c-(b2 ⁄ 4a):

gif_quadrado

Precisamos, então, criar dois novos símbolos:

h, k = sympy.symbols('h k')

Podemos, apenas como recurso didático, expressar a igualdade pelo SymPy:

square = sympy.Eq(a * x**2 + b*x + c, a * (x - h)**2 + k)
square

Vamos, agora, pegar uma equação quadrática qualquer e tentar achar os valores de h e k. Por exemplo: x2-4x+7 = (x-h)2+k

square_example = sympy.Eq(x**2 - 4*x + 7, (x - h)**2 + k)
sympy.solve(square_example, (h, k))

Conhecendo mais o objeto Equality

Vamos aproveitar e conhecer melhor o objeto Equality com esse exemplo. Até o momento, o utilizamos como uma forma de representar uma equação matemática, mas já vimos que o retorno pode ser também um booleano, True ou False. Vamos entender.

Primeiro, vamos substituir os valores encontrados para h e para k em nosso objeto:

square_example.subs({h: 2, k: 3})

É possível obter cada lado da igualdade com os atributos rhs (lado direito, right hand side) e lhs (lado esquerdo, left hand side):

square_example.subs({h: 2, k: 3}).rhs
square_example.subs({h: 2, k: 3}).lhs

Podemos expandir o quadrado do lado direito:

square_example.subs({h: 2, k: 3}).rhs.expand()

Bom, isso mostra que efetivamente os dois lados são iguais, certo? Logo, a seguinte comparação de igualdade deve ser verdadeira:

square_example.subs({h: 2, k: 3}).lhs == square_example.subs({h: 2, k: 3}).rhs
False

Só que não… por que? Conforme a documentação do SymPy, a biblioteca analisa igualdade de forma e não equivalência matemática. Para detalhes, veja esta parte da documentação do SymPy e esse FAQ no repositório do SymPy.

Pelo que foi visto, se a comparação for entre o lado esquerdo e a expansão do lado direito, o resultado deve ser True:

square_example.subs({h: 2, k: 3}).lhs == square_example.subs({h: 2, k: 3}).rhs.expand()
True

Agora sim. E tudo poderia ser resumido na linha seguinte, que instrui o SymPy a fazer as expansões possíveis e verificar a igualdade:

square_example.subs({h: 2, k: 3}).expand()

Conclusão e mais artigos sobre SymPy

A tentação de fazer toda e qualquer equação que apareça na sua frente com o SymPy agora deve ser grande. Sistemas então, nem se fala! Mas veja como é importante ter um bom fundamento matemático para entender o que se pode fazer com a biblioteca. Não saia matando aula de matemática (ou até mate, mas depois estude pelos livros 😉 ).

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:

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