Eu implementei um solucionador de Euler para trás no python 3 (usando numpy). Para minha própria conveniência e como exercício, também escrevi uma pequena função que calcula uma aproximação de diferença finita do gradiente para que nem sempre seja necessário determinar analiticamente o jacobiano (se é que isso é possível!).
Usando as descrições fornecidas em Ascher e Petzold 1998 , escrevi esta função que determina o gradiente em um determinado ponto x:
def jacobian(f,x,d=4):
'''computes the gradient (Jacobian) at a point for a multivariate function.
f: function for which the gradient is to be computed
x: position vector of the point for which the gradient is to be computed
d: parameter to determine perturbation value eps, where eps = 10^(-d).
See Ascher und Petzold 1998 p.54'''
x = x.astype(np.float64,copy=False)
n = np.size(x)
t = 1 # Placeholder for the time step
jac = np.zeros([n,n])
eps = 10**(-d)
for j in np.arange(0,n):
yhat = x.copy()
ytilde = x.copy()
yhat[j] = yhat[j]+eps
ytilde[j] = ytilde[j]-eps
jac[:,j] = 1/(2*eps)*(f(t,yhat)-f(t,ytilde))
return jac
Testei essa função usando uma função multivariada para o pêndulo e comparando o jacobiano simbólico ao gradiente determinado numericamente para uma série de pontos. Fiquei satisfeito com os resultados do teste, o erro foi em torno de 1e-10. Quando resolvi o ODE para o pêndulo usando o jacobiano aproximado, ele também funcionou muito bem; Não consegui detectar nenhuma diferença entre os dois.
Então tentei testá-lo com o seguinte PDE (equação de Fisher em 1D):
usando uma discretização por diferenças finitas.
Agora, o método de Newton explode no primeiro intervalo de tempo:
/home/sfbosch/Fisher-Equation.py:40: RuntimeWarning: overflow encountered in multiply
du = (k/(h**2))*np.dot(K,u) + lmbda*(u*(C-u))
./newton.py:31: RuntimeWarning: invalid value encountered in subtract
jac[:,j] = 1/(2*eps)*(f(t,yhut)-f(t,yschlange))
Traceback (most recent call last):
File "/home/sfbosch/Fisher-Equation.py", line 104, in <module>
fisher1d(ts,dt,h,L,k,C,lmbda)
File "/home/sfbosch/Fisher-Equation.py", line 64, in fisher1d
t,xl = euler.implizit(fisherode,ts,u0,dt)
File "./euler.py", line 47, in implizit
yi = nt.newton(g,y,maxiter,tol,Jg)
File "./newton.py", line 54, in newton
dx = la.solve(A,b)
File "/usr/lib64/python3.3/site-packages/scipy/linalg/basic.py", line 73, in solve
a1, b1 = map(np.asarray_chkfinite,(a,b))
File "/usr/lib64/python3.3/site-packages/numpy/lib/function_base.py", line 613, in asarray_chkfinite
"array must not contain infs or NaNs")
ValueError: array must not contain infs or NaNs
Isso ocorre para uma variedade de valores eps, mas estranhamente, apenas quando o tamanho da etapa espacial do PDE e o tamanho da etapa do tempo são definidos para que a condição de Courant – Friedrichs – Lewy não seja atendida. Caso contrário, funciona. (Esse é o comportamento que você esperaria se resolvesse com o Euler avançado!)
Para completar, aqui está a função do método Newton:
def newton(f,x0,maxiter=160,tol=1e-4,jac=jacobian):
'''Newton's Method.
f: function to be evaluated
x0: initial value for the iteration
maxiter: maximum number of iterations (default 160)
tol: error tolerance (default 1e-4)
jac: the gradient function (Jacobian) where jac(fun,x)'''
x = x0
err = tol + 1
k = 0
t = 1 # Placeholder for the time step
while err > tol and k < maxiter:
A = jac(f,x)
b = -f(t,x)
dx = la.solve(A,b)
x = x + dx
k = k + 1
err = np.linalg.norm(dx)
if k >= maxiter:
print("Maxiter reached. Result may be inaccurate.")
print("k = %d" % k)
return x
(A função la.solve é scipy.linalg.solve.)
Estou confiante de que minha implementação Euler para trás está em ordem, porque a testei usando uma função para o Jacobiano e obtenho resultados estáveis.
Eu posso ver no depurador que newton () gerencia 35 iterações antes que o erro ocorra. Esse número permanece o mesmo para todos os eps que tentei.
Uma observação adicional: quando computo o gradiente com o FDA e uma função usando a condição inicial como entrada e comparo os dois enquanto varia o tamanho do epsilon, o erro aumenta à medida que o epsilon diminui. Eu esperaria que fosse grande a princípio, depois diminuísse e depois aumentasse novamente à medida que o epsilon diminui. Portanto, um erro na minha implementação do jacobiano é uma suposição razoável, mas, se for, é tão sutil que não consigo vê-lo. EDIT: Modifiquei jacobian () para usar as diferenças avançadas em vez das centrais, e agora observo o desenvolvimento esperado do erro. No entanto, newton () ainda não converge. Observando dx na iteração de Newton, vejo que ela apenas cresce, não há sequer uma flutuação: quase dobra (fator 1.9) a cada passo, com o fator aumentando progressivamente.
Ascher e Petzold mencionam que as aproximações das diferenças para os jacobianos nem sempre funcionam bem. Pode um jacobiano aproximado com diferenças finitas causar instabilidade no método de Newton? Ou a causa está em outro lugar? De que outra forma eu poderia abordar esse problema?