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?
Respostas:
Mais um longo comentário do que qualquer outra coisa:
Veja o código da aproximação do quociente de diferença da SUNDIALS para ter uma idéia melhor do que você deve fazer em uma implementação. Ascher e Petzold é um bom livro para começar, mas SUNDIALS é realmente usado no trabalho de produção e, portanto, foi melhor testado. (Além disso, SUNDIALS está relacionado ao DASPK, no qual Petzold trabalhou.)
Empiricamente, os jacobianos aproximados podem causar falhas de convergência no método de Newton. Não sei se os caracterizaria como "instabilidade"; em alguns casos, simplesmente não é possível atingir as tolerâncias de erro desejadas nos critérios de encerramento. Em outros casos, pode se manifestar como uma instabilidade. Estou quase certo de que haja um resultado mais quantitativo sobre esse fenômeno no livro de métodos numéricos de Higham, ou na discussão de Hairer e Wanner sobre os métodos W.
Depende de onde você acha que o erro pode estar. Se você está extremamente confiante na implementação do Euler para trás, eu não começaria por aí. A experiência me deixou paranóica nas implementações de métodos numéricos; portanto, se fosse eu, começaria a codificar alguns problemas de teste realmente básicos (alguns problemas lineares não rígidos e rígidos, a equação do calor com uma aproximação de diferença finita centralizada, coisas assim) e eu usaria o método de soluções fabricadas para garantir a mim mesmo que sei qual será a solução e com o que devo comparar.
No entanto, você já fez algo disso:
Essa seria a próxima coisa que eu testaria: use um jacobiano analítico. Depois disso, você também pode olhar para os autovalores extremas de sua diferença finita jacobiana com a pequena chance de estar na região instável de Euler retrógrado. Observar os autovalores extremas de seu jacobiano analítico como base de comparação pode fornecer algumas dicas. Supondo que todos verifiquem, o problema provavelmente está na resolução de Newton.
fonte