Python para Zero de Funções

Neste capítulo, o Python será usado para transformar as ideias dos métodos de Bisseção, Cordas e Newton em algoritmos controlados por intervalo, aproximação e tolerância. Como os comandos básicos já foram apresentados na introdução ao Python, aqui o foco será organizar funções, critérios de parada, registros de iterações e validações numéricas.

Definindo uma função

Nos métodos de zero de funções, a função deve receber um número real e devolver outro número real. Isso permite testar sinais, calcular aproximações e medir resíduos. Por exemplo, para \(f(x)=x^3-10\):

def f(x):
    return x**3 - 10

print(f(2))
print(f(3))

Ao programar os métodos, é útil manter a função separada do algoritmo. Assim, o mesmo código da Bisseção, das Cordas ou de Newton pode ser reaproveitado para outra função, mudando apenas a definição de f.

Funções matemáticas

Quando a expressão usa seno, cosseno, exponencial ou raiz quadrada e será avaliada em um ponto por vez, podemos importar funções da biblioteca math:

from math import cos, sin, exp, sqrt

def f(x):
    return 2*x**3 - 2*x - cos(x**3) - 1
Matemática Python Exemplo
\(\sqrt{x}\) sqrt(x) raiz quadrada
\(\cos(x)\) cos(x) cosseno
\(e^x\) exp(x) exponencial

Validação do intervalo

Antes de aplicar um método fechado, como a Bisseção, é importante validar o intervalo. Se \(f(a)\cdot f(b)<0\), há mudança de sinal entre os extremos. Em Python, podemos transformar esse teste em uma função auxiliar:

def tem_mudanca_sinal(f, a, b):
    return f(a) * f(b) < 0

if tem_mudanca_sinal(f, 2, 3):
    print("Intervalo válido para Bisseção")
else:
    print("Escolha outro intervalo")

Essa validação evita iniciar o algoritmo com uma hipótese incompatível com o método. Ela não garante que exista apenas uma raiz no intervalo, mas confirma a condição usada pelo teorema do valor intermediário.

Valor médio do intervalo

No método da Bisseção, calculamos o ponto médio do intervalo:

\[m = \frac{a+b}{2}\]
m = (a + b) / 2
print(m)

Critérios de parada

Os métodos numéricos repetem aproximações até que algum erro fique menor que a tolerância. Em zero de funções, dois critérios comuns são o tamanho do intervalo e o resíduo \(|f(x)|\):

a = 2
b = 3
m = (a + b) / 2

erro_intervalo = abs(b - a)
erro_funcao = abs(f(m))
tolerancia = 0.001

parar = erro_intervalo < tolerancia or erro_funcao < tolerancia

Também é recomendável limitar o número de iterações. Isso impede que o programa fique preso quando a função, o intervalo ou a tolerância foram escolhidos de forma inadequada.

max_iteracoes = 50
iteracao = 0

while iteracao < max_iteracoes:
    m = (a + b) / 2

    if abs(f(m)) < tolerancia or abs(b - a) < tolerancia:
        break

    if f(a) * f(m) < 0:
        b = m
    else:
        a = m

    iteracao = iteracao + 1

Registrando as iterações

Para montar tabelas de cálculo, podemos guardar os principais valores de cada iteração em uma lista de dicionários. Essa estrutura facilita imprimir, conferir e comparar os resultados:

historico = []

historico.append({
    "iteracao": iteracao,
    "a": a,
    "b": b,
    "m": m,
    "f_m": f(m),
    "erro": abs(b - a)
})

Derivadas com SymPy

No método de Newton, precisamos da derivada da função. A biblioteca sympy permite calcular a derivada simbólica e depois convertê-la em uma função numérica usando lambdify:

import sympy as sp

x = sp.symbols("x")
expressao = x**3 - 10

derivada = sp.diff(expressao, x)
f = sp.lambdify(x, expressao, "math")
df = sp.lambdify(x, derivada, "math")

x_atual = 2
x_proximo = x_atual - f(x_atual) / df(x_atual)
print(x_proximo)

Essa separação entre expressão simbólica, função numérica e derivada numérica ajuda a reduzir erros de transcrição na implementação do método de Newton.

Atividade

Para \(f(x)=x^2-4\), crie uma função auxiliar que valide a mudança de sinal em \([1,3]\), calcule o ponto médio e registre em um dicionário os valores de \(a\), \(b\), \(m\), \(f(m)\) e \(|b-a|\).