Classes para derivadas
Relembrando a classe para a derivação numérica
class Derivada:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
def __call__(self, x):
f, h = self.f, self.h ## formando formas curtas
return (f(x+h) - f(x))/h
def f(x):
return exp(-x)*cos(tanh(x))
from math import exp, cos, tanh
dfdx = Derivada(f)
print dfdx(2.0)
Existem diversas fórmulas para a derivação numérica
Como podemos escrever um módulo que oferece todas essas fórmulas?
É fácil:
class Frente1:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h) - f(x))/h
class Tras1:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
def __call__(self, x):
f, h = self.f, self.h
return (f(x) - f(x-h))/h
class Central2:
## mesmo contrutor
## coloque as fórmulas relevantes em __call__
Qual é o problema com esse tipo de código?
Todos os construtores são iguais então muito código é duplicado.
- Ideia geral: colocar o código em comum em uma superclasse;
- Herdar o construtor da superclasse fazendo subclasses para as diferentes fórmulas de derivação utilizando o
__call__
Classe hierárquica para derivação numérica
Superclasse
class derivada:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
Subclasse para a equação de derivação de primeira ordem para frente
class frente1(derivada):
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h) - f(x))/h
Subclasse para a equação de derivação de quarta ordem central
class central4(derivada):
def __call__(self, x):
f, h = self.f, self.h
return (4./3)*(f(x+h) - f(x-h)) /(2*h) - \
(1./3)*(f(x+2*h) - f(x-2*h))/(4*h)
Uso das classes de derivação
Exemplo: sendo $ f(x) = sen(x) $, calcule $ f'(x) $ para $ x = \pi $, usando a derivação central de ordem 4.
>>> from derivada import *
>>> from math import sin
>>> meucos = central4(sin)
>>> # calcule sin'(pi):
>>> meucos(pi)
-1.000000082740371
central4(sin)
chama o construtor herdado na superclasse, enquanto meucos(pi)
chama __call__
na subclasse central4
Exercício 2: avaliando o fluxo do programa
Um programa flexível para a derivação numérica
Suponha que nós queremos derivar equações da linha de comando:
Terminal> python df.py 'exp(sin(x))' Central 2 3.1
-1.04155573055
Terminal> python df.py 'f(x)' difftype difforder x
f'(x)
Usando o eval
e a classe hierárquica derivada
este programa principal pode ser feito em poucas linhas (muitas linhas em C# e Java):
import sys
from derivada import *
from math import *
from scitools.StringFunction import StringFunction
f = StringFunction(sys.argv[1])
derivada_tipo = sys.argv[2]
derivada_ordem = sys.argv[3]
nome_classe = derivada_tipo + derivada_ordem
df = eval(nome_classe + '(f)')
x = float(sys.argv[4])
print df(x)
Investigando erros numéricos por aproximações
- Podemos investigar, empiricamente, a precisão de nossa família de 6 equações de derivação numérica;
- Função de teste: $ f(x) = exp(-10x) $;
- Queremos o resultado na seguinte forma:
h Frente1 Central2 Central4
6.25E-02 -2.56418286E+00 6.63876231E-01 -5.32825724E-02
3.12E-02 -1.41170013E+00 1.63556996E-01 -3.21608292E-03
1.56E-02 -7.42100948E-01 4.07398036E-02 -1.99260429E-04
7.81E-03 -3.80648092E-01 1.01756309E-02 -1.24266603E-05
3.91E-03 -1.92794011E-01 2.54332554E-03 -7.76243120E-07
1.95E-03 -9.70235594E-02 6.35795004E-04 -4.85085874E-08
Observações:
- Reduzindo à metade $h$ de linha a linha o erro se reduz por um fator de 2, 4 e 16, isto é, os erros se reduzem em $h$, $h^2$ e $h^4$;
central4
tem uma precisão muito superior se comparada comfrente1
.
Implementação
class derivada2(derivada):
def __init__(self, f, h=1E-5, dfdx_exact=None):
derivada.__init__(self, f, h)
self.exact = dfdx_exact
def error(self, x):
if self.exact is not None:
df_numerical = self(x)
df_exact = self.exact(x)
return df_exact - df_numerical
class frente1(derivada2):
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h) - f(x))/h
class central2(derivada2):
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h) - f(x-h))/(2*h)
class central4(derivada2):
def __call__(self, x):
f, h = self.f, self.h
return (4./3)*(f(x+h) - f(x-h)) /(2*h) - (1./3)*(f(x+2*h) - f(x-2*h))/(4*h)
Uso
from derivada2 import *
from math import exp
def f1(x):
return exp(-10*x)
def df1dx(x):
return -10*exp(-10*x)
def tabela(f, x, h_values, metodos, dfdx=None):
# imprimir o cabeçalho (h e os nomes das classes para os métodos)
print ’ h ’,
for metodo in metodos:
print ’%-15s’ % method.__name__,
print # nova linha
# imprimir a tabela
for h in h_values:
print ’%10.2E’ % h,
for metodo in metodos:
if dfdx is not None:
# escrever o erro
d = metodo(f, h, dfdx)
output = d.error(x)
else:
# escrever o valor
d = metodo(f, h)
output = d(x)
print ’%15.8E’ % output,
print # nova linha
tabela(f1, 0, [2**(-k) for k in range(10)], [frente1, central2, central4], df1dx)