Você está na página 1de 11

Raizes de Equações

Renato Vicente
3 de março de 2011

Resumo
Os códigos neste resumo foram extraı́dos e adaptados de [1].

1 Problema
Encontre as soluções reais de f (x) = 0, onde a função f é dada.

Resolver f1 (x) = f2 (x) é uma variante deste problema definindo-se f (x) :=


f1 (x) − f2 (x). Inverter uma função g(x) é outra variante do mesmo prob-
lema. Neste caso queremos, dado y encontrar x tal que y = g(x). Ou seja,
desejamos achar a raiz de y − g(x) = 0.

2 Método Incremental de Busca


O método incremental consiste na varredura em força bruta da abcissa em
busca de uma mudança de sinal. O método é ineficiente para encontrar
raizes mas pode ser utilizado para encontrar intervalos que contenham raizes,
no entanto é necessário tomar cuidado pois a técnica pode confundir uma
singularidade com mudança de sinal com um zero.

#coding: utf-8
## modulo buscaraiz
’’’ x1,x2 = buscaraiz(f,a,b,dx).
Busca a menor raiz de f(x) no intervalo (x1,x2)
no intervalo (a,b) em incrementos dx.
Returna x1 = x2 = None caso nenhuma raiz seja
encontrada.
’’’
def buscaraiz(f,a,b,dx):

1
x1 = a; f1 = f(a)
x2 = a + dx; f2 = f(x2) #intervalo inicial
while f1*f2 > 0.: #procura intervalo com troca de sinal
if x1 >= b: return None,None
x1 = x2; f1 = f2
x2 = x1 + dx; f2 = f(x2) #novo intervalo
else:
return x1,x2 # se houver troca de sinal retorna

Testando de forma interativa:

>>> from pylab import *


>>> f = lambda x: 2*x**2 + 5*x + 2
>>> x=arange(-5.,5.,0.01)
>>> y=f(x)

>>> hold(True)
>>> plot(x,y)
>>> show()
>>> plot(x,y)
>>> grid(True)

>>> from buscaraiz import *


>>> help(buscaraiz)
>>> x1,x2=buscaraiz(f,-4.,0.,1.)
>>> x1,x2 #raiz 1
>>> x1,x2=buscaraiz(f,-1.,2.,1.)
>>> x1,x2 #raiz 2

3 Dicotomia
Localizado um intervalo contendo uma raiz, um dos métodos para aproximar
o valor dessa raiz é a dicotomia (ou bisecção). Na dicotomia os intervalos
com extremos mudando de sinal são sucessivamente reduzidos pela metade.
Em cada passo o valor da raiz é aproximada pela mediana x3 = (x1 + x2 )/2
do intervalo atual x2 − x1 . O procedimento é repetido até que o tamanho do
intevalo atinja um tamanho pré-especificado | x2 −x1 |≤ , que fixa a precisão
no conhecimento da raiz em ±. O número de dicotomias necessárias para
atingirmos a precisão requerida pode ser calculado observando que após n

2
passos a amplitude do intervalo original ∆x é reduzida a ∆x/2n . Dessa
maneira temos:
log(| ∆x | /)
n= (1)
log 2
Na implementação que se segue precisamos também de um módulo para
tratamento de mensagens de erro:
#coding: utf-8
## module erros
’’’ err(string).
Imprime ’string’ e termina um programa.
’’’
import sys
def err(string):
print string
raw_input(’Press return to exit’)
sys.exit()
A implementação a seguir apenas aceita intervalos monotônicos:

#coding: utf-8
## module dicotomia
’’’ raiz = dicomia(f,x1,x2,chave=0,tol=1.0e-9).
Encontra a raiz de f(x) = 0 por dicotomia.
A raiz tem que estar no intervalo (x1,x2).
Escolhendo chave = 1 returna raiz = None se
f(x) aumentar como resultado da dicotomia.
’’’
from math import log,ceil
import erros

def dicotomia(f,x1,x2,chave=0,epsilon=1.0e-9):
# Os valores padr~
ao s~
ao chave desligada e epsilon 1e-09

# Se a raiz está nos extremos do intervalo retorna


f1 = f(x1)
if f1 == 0.: return x1
f2 = f(x2)
if f2 == 0.: return x2

# Se o intevalo n~
ao atende a condiç~
ao suficiente
# para exist^
encia de raiz retorna mensagem de erro

3
if f1*f2 > 0.: erros.err(’Pode n~
ao haver raiz no intervalo’)

# Calcula o número de iteraç~


oes necessárias para
# atingir a precis~ao requerida
n = ceil(log(abs(x2 - x1)/epsilon)/log(2.))

# Faz o número de dicotomias suficientes


for i in range(n):
x3 = 0.5*(x1 + x2); f3 = f(x3)

# Se a funç~
ao n~
ao for monot^
onica no intervalo
# retorna ’None’
if (chave == 1) and (abs(f3) > abs(f1)) \
and (abs(f3) > abs(f2)):
return None

# Se a mediana do intervalo for raiz retorna


if f3 == 0.: return x3

#Verifica se o intervalo está à direita


if f2*f3 < 0.:
x1 = x3; f1 = f3
else: # ou à esquerda
x2 =x3; f2 = f3

return (x1 + x2)/2.

Experimentando no shell interativo:

>>> from pylab import *


>>> f = lambda x:x*x*x -10.*x*x + 5.
>>> x=arange(-5,5,0.01)
>>> y=f(x)
>>> plot(x,y)
>>> grid(True)
>>> show()
>>> from buscaraiz import *
>>> help(buscaraiz)
>>> x1,x2 = buscaraiz(f,-4.,4.,1.)
>>> x1,x2 = buscaraiz(f,0.4.,1.)
>>> from dicotomia import *

4
>>> raiz = dicotomia(f,x1,x2)
>>> raiz

4 Método Iterativo
Se encontrarmos uam função φ tal que uma sequência gerada pelo mapa
xk+1 = φ(xk ) convirja para um x∗ que seja solução de f (x) = 0, teremos um
método iterativo para achar raizes de equações. Uma condição necessária é
que x∗ seja um ponto fixo de φ. Ou seja, que φ(x∗ ) = x∗ . Essa condição,
no entanto, não é suficiente, pois para que haja convergência do método é
necessário adicionalmente que o ponto fixo encontrado seja estável.
Funções φ candidatas naturais têm a forma φ(x) = x + α(x)f (x). A esta-
bilidade do ponto fixo de φ pode ser analisada observando o comportamento
do mapa ao redor do ponto fixo x∗ :
xk+1 = φ(xk ) = φ(x∗ + k ) = x∗ + k+1 (2)
Aqui  indica uma pequena perturbação. Prosseguimos utilizando uma ex-
pansão de Taylor em torno do ponto fixo:
dφ ∗
x∗ + k+1 ≈ x∗ + (x )k (3)
dx
A partir disso notamos que a condição para que uma perturbação seja re-
duzida pelo mapa, restabelecendo o ponto fixo é
dφ ∗
| (x ) |< 1 (4)
dx
Infelizmente não sabemos x∗ , mas temos um critério para escolha da função
α(x). Comecemos pelo caso mais simples com α constante:
−1 < 1 + αf 0 (x∗ ) < 1, (5)
ou seja,
2
0<α<− , (6)
f 0 (x∗ )
Estamos livres para escolher qualquer α nesse intervalo. Em particular,
podemos fazer φ(x∗ ) = 0 e assim terı́amos
1
α=− , (7)
f 0 (x∗ )
Se conhecessemos f 0 (x∗ ) simplesmente usarı́amos o mapa
f (x)
φ(x) = x − . (8)
f 0 (x∗ )

5
5 Método Newton-Raphson
O método Newton-Raphson baseia-se na construção de um mapa como na
seção anterior. Uma forma rápida de introduzirmos o método é imaginando
que temos um chute inicial a para a raiz. Adicionalmente, supomos que o
chute está próximo da raiz que desejamos obter x∗ . Neste caso terı́amos a
seguinte aproximação:

f (x∗ ) ≈ f (a) + (x∗ − a)f 0 (a) (9)

Mas como x∗ é uma raiz temos


f (a)
x∗ ≈ a − (10)
f 0 (a)

Como não estamos necessariamente tão próximos da raiz o que temos é uma
sequência de “chutes” definida pelo mapa:

f (xk )
xk+1 = xk − (11)
f 0 (xk )

Para termos uma idéia da taxa de convergência do método podemos cal-


cular a evolução do erro k := x∗ − xk . A equação (11) pode ser reescrita
como:
f (xk )
k+1 = k − 0 (12)
f (xk )
Escrevendo a série de Taylor na raiz em torno do “chute” xk escrevemos:

2 00
f (xk ) = −k f 0 (xk ) − f (xk ) (13)
2
Substituindo (13) em (12) obtemos:

2k f 00 (xk )
k+1 = − (14)
2f 0 (xk )

Se a razão f 00 (xk )/f 0 (xk ) não oscilar muito em torno da raiz, podemos
dizer que o erro é proporcional ao erro anterior ao quadrado. Se o erro é
pequeno inicialmente ele ficará rapidamente muito menor. A convergência
é quadrática se o erro inicial não for muito grande. O método converge
rapidamente quando o chute inicial é bastante próximo da raiz, mas tem
convergência global ruim. Uma implementação segura combinando Newton-
Raphson e dicotomia segue abaixo:

6
#coding: utf-8
## module newtonRaphson
’’’ raiz,niter = newtonRaphson(f,df,a,b,tol=1.0e-9).
Encontra uma raiz de f(x) = 0 combinando o método Newton-Raphson
e o método de dicotomia. A raiz tem que estar localizada em (a,b).
É necessário fornecer f(x) e sua derivada df(x).
’’’

def newtonRaphson(f,df,a,b,tol=1.0e-9):
import erros

# se a raiz estiver nas extremidades para


fa = f(a)
if fa == 0.: return a
fb = f(b)
if fb == 0.: return b

# se a condiç~
ao suficiente n~
ao for verificada para
if fa*fb > 0.: erros.err(’Pode n~ao haver raiz no intervalo.’)

#inicia iteraç~
oes na mediana do intevalo
x = .5*(a + b)

# no máximo 30 iteraç~
oes
for i in range(30):
fx = f(x)

#se a mediana estiver suficentemente proxima da raiz para


if abs(fx) < tol: return x,i

#raiz à esquerda da mediana


if fa*fx < 0.:
b = x
else: #raiz à direita
a = x; fa = fx

# Passo de Newton-Raphson
dfx = df(x)

# Caso a derivada seja negativa posiciona o chute

7
# fora dos limites iniciais
try: dx = -fx/dfx
except ZeroDivisionError: dx = b - a
x = x + dx

# Se o resultado da aplicaç~
ao de N-R estiver fora
# do intevalo atual use dicotomia
if (b - x)*(x - a) < 0.:
dx = .5*(b-a)
x = a + dx

# Verifique converg^
encia
if abs(dx) < tol*max(abs(b),1.): return x,i

print ’O número de iteraç~


oes excedeu o limite’

No shell iterativo:

>>> from pylab import *


>>> f = lambda x: x**4 - 6.4*x**3 + 6.45*x**2 + 20.538*x -31.752
>>> df = lambda x: 4.*x**3 - 19.2*x**2 +12.9*x +20.538
>>> x=arange(0.,5.,0.01)
>>> y=f(x)
>>> plot(x,y)
>>> show()
>>> plot(x,y)
>>> grid(True)
>>> from newtonRaphson import *
>>> x0=newtonRaphson(f,df,3.,5.)

A outra raiz na função sugerida. A versão protegida de Newton-Raphson, no


entanto não consegue encontá-la. Modifique o código para que seja capaz de
encontrar todas as raizes no intervalo [0, 5].

6 Método de Brent
O método Newton-Raphson requer o uso de derivadas da função cujo zero
queremos encontrar. Uma alternativa interessante na ausência de derivadas
que converge mais rapidamente do que o método de dicotomia é o método de
Brent. Este método consiste da utilização de uma combinação da dicotomia

8
com interpolações quadráticas inversas para gerar candidatos a raiz. O pro-
cedimento inicia-se tomando um intervalo (x1 , x2 ) com a condição suficiente
para existência de raiz f (x1 )f (x2 ) < 0. Elege-se a mediana x3 = (x1 + x2 )/2
do intervalo como candidato inicial a raiz. Conferindo a condição suficiente
escolhe-se um dos subintervalos como candidato à conter uma raiz. Assim se
f (x1 )f (x3 ) < 0 o intervalo escolhido é o da esquerda, caso contrário, o da di-
reita. Utiliza-se um interpolante de Lagrange de três pontos (será visto mais
a frente no curso) para gerar uma aproximação da curva. A raiz da aprox-
imação dentro do intervalo candidato será o novo candidato a raiz tomando
o papel de x3 , o antigo candidato passa a ser um dos extremos do novo in-
tervalo. O procedimento de escolha de subintervalo e interpolação é repetido
até que a correção de um candidato ao próximo seja menor que uma precisão
pré-estabelecida.
A interpolação reversa consiste em construir uma curva quadrática que
passe pelos três pontos (x1 , f (x1 )), (x2 , f (x2 )) e (x3 , f (x3 )). Isso pode con-
struido escrevendo-se:
(f − f2 )(f − f3 ) (f − f1 )(f − f3 ) (f − f1 )(f − f2 )
x(f ) = x1 + x2 + x3
(f1 − f2 )(f1 − f3 ) (f2 − f1 )(f2 − f3 ) (f3 − f1 )(f3 − f1 )
(15)
A raiz dessa curva aproximada estará situada em:
f2 f3 x1 (f2 − f3 ) + f3 f1 x2 (f3 − f1 ) + f1 f2 x3 (f1 − f2 )
x=− (16)
(f1 − f2 )(f2 − f3 )(f3 − f1 )
A correção ao candidato à raiz a cada passo de interpolação será:
x3 (f1 − f2 )(f2 − f3 + f1 ) + f2 x1 (f2 − f3 ) + f1 x2 (f3 − f1 )
∆x = x − x3 = f3
(f2 − f1 )(f2 − f3 )(f3 − f1 )
(17)
Quando essa correção for | ∆x |<  para uma precisão  pré-definida paramos
o algoritmo. Uma implementação simplificada do método de Brent segue:

#coding: utf-8
## module brent
’’’raiz,numiter = brent(f,a,b,tol=1.0e-9).
Encontra a raiz de f(x) = 0 combinando interpolaç~ ao
quadrática com bisecç~
ao (Método de Brent simplificado)
A raiz tem que estar no intervalo especificado (a,b).
É necessário fornecer a funç~
ao f(x).
’’’
import erros

9
def brent(f,a,b,tol=1.0e-9):

#verifica se a raiz está nos extremo do intervalo


x1 = a; x2 = b;
f1 = f(x1)
if f1 == 0.0: return x1
f2 = f(x2)
if f2 == 0.0: return x2

#verifica condiç~
ao suficiente
if f1*f2 > 0.0: erros.err(’Pode n~
ao haver raiz no intervalo’)

#primeiro candidato é a mediana do intervalo


x3 = 0.5*(a + b)

#fará no máximo 30 iteraç~


oes
for i in range(30):

#verifica se o candidato atual já está


#suficientemente próximo do zero
f3 = f(x3)
if abs(f3) < tol: return x3,i

# Escolhe subintervalo candidato


if f1*f3 < 0.0: b = x3 #esquerda
else: a = x3 #direita

# Se amplitude do intevalo candidato for


# pequena, para
if (b - a) < tol*max(abs(b),1.0): return 0.5*(a + b),i

# Faz a interpolaç~
ao quadrática inversa
# para encontrar o novo candidato
denom = (f2 - f1)*(f3 - f1)*(f2 - f3)
numer = x3*(f1 - f2)*(f2 - f3 + f1)\
+ f2*x1*(f2 - f3) + f1*x2*(f3 - f1)

# Se houver divis~
ao por zero
# tira x do intervalo (a,b)
try: dx = f3*numer/denom
except ZeroDivisionError: dx = b - a

10
x = x3 + dx

# Se a iterpolaç~
ao sair do intervalo
# usar dicotomia
if (b - x)*(x - a) < 0.0:
dx = 0.5*(b - a)
x = a + dx

# Escolhe os novos pontos para interpolaç~


ao
if x < x3:# candidato a esquerda do anterior
x2 = x3; f2 = f3
else:# candidato a direita do anterior
x1 = x3; f1 = f3
x3 = x

print ’Número de iteraç~


oes excedeu o limite’

No shell iterativo:
>>> from pylab import *
>>> from math import *
>>> f = lambda x:x*abs(cos(x)) - 1.0
>>> x=arange(0.,4.,0.01)
>>> y=f(x)
>>>plot(x,y)
>>>show()
>>>plot(x,y)
>>>grid(True)
>>>from brent import *
>>>raiz,nint=brent(f,0.,4.)
>>>raiz
>>>nint
Compare o número de iterações necessárias usando o método de Brent
com aquele necessário utilizando dicotomia.

Referências
[1] J. Kiusalaas, Numerical Methods in Engineering with Python, Cambridge
University Press, 2005.

11

Você também pode gostar