He implementado un solucionador de Euler hacia atrás en Python 3 (usando numpy). Para mi propia conveniencia y como ejercicio, también escribí una pequeña función que calcula una aproximación de diferencia finita del gradiente para que no siempre tenga que determinar el jacobiano analíticamente (¡si es posible!).
Usando las descripciones proporcionadas en Ascher y Petzold 1998 , escribí esta función que determina el gradiente en un punto dado 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
Probé esta función tomando una función multivariada para el péndulo y comparando el jacobiano simbólico con el gradiente determinado numéricamente para un rango de puntos. Estaba satisfecho con los resultados de la prueba, el error fue de alrededor de 1e-10. Cuando resolví el ODE para el péndulo usando el jacobiano aproximado, también funcionó muy bien; No pude detectar ninguna diferencia entre los dos.
Luego intenté probarlo con la siguiente PDE (ecuación de Fisher en 1D):
utilizando una discreta diferenciación finita.
Ahora el método de Newton explota en el primer paso:
/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
Esto sucede para una variedad de valores de eps, pero extrañamente, solo cuando el tamaño de paso espacial PDE y el tamaño de paso de tiempo se establecen de modo que no se cumpla la condición de Courant-Friedrichs-Lewy. De lo contrario, funciona. (¡Este es el comportamiento que esperarías si resolvieras con Euler adelante!)
Para completar, aquí está la función para el 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
(La función la.solve es scipy.linalg.solve).
Estoy seguro de que mi implementación anterior de Euler está en orden, porque la he probado usando una función para el jacobiano y obtengo resultados estables.
Puedo ver en el depurador que newton () gestiona 35 iteraciones antes de que ocurra el error. Este número sigue siendo el mismo para cada eps que he probado.
Una observación adicional: cuando calculo el gradiente con la FDA y una función usando la condición inicial como entrada y comparo los dos al variar el tamaño de épsilon, el error crece a medida que épsilon se reduce. Al principio, esperaría que fuera grande, luego más pequeño y luego más grande a medida que el épsilon se encoge. Entonces, un error en mi implementación del Jacobian es una suposición razonable, pero si es así, es tan sutil que no puedo verlo. EDITAR: Modifiqué jacobian () para usar adelante en lugar de las diferencias centrales, y ahora observo el desarrollo esperado del error. Sin embargo, newton () todavía no puede converger. Al observar dx en la iteración de Newton, veo que solo crece, ni siquiera hay una fluctuación: casi se duplica (factor 1.9) con cada paso, con el factor cada vez más grande.
Ascher y Petzold mencionan que las aproximaciones de diferencia para el jacobiano no siempre funcionan bien. ¿Puede un jacobiano aproximado con diferencias finitas causar inestabilidad en el método de Newton? ¿O es la causa en otro lugar? ¿De qué otra forma podría abordar este problema?