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?
Respuestas:
Un comentario más largo que cualquier otra cosa:
Mire el código para la aproximación del cociente de diferencia de SUNDIALS para tener una mejor idea de lo que debe hacer en una implementación. Ascher and Petzold es un buen libro para comenzar, pero SUNDIALS se usa realmente en el trabajo de producción y, por lo tanto, se ha probado mejor. (Además, SUNDIALS está relacionado con DASPK, en el que trabajó Petzold).
Empíricamente, los jacobianos aproximados pueden causar fallas de convergencia en el método de Newton. No sé si los caracterizaría como "inestabilidad"; en algunos casos, simplemente no es posible lograr las tolerancias de error deseadas en los criterios de terminación. En otros casos, podría manifestarse como una inestabilidad. Estoy casi seguro de que hay un resultado más cuantitativo en este fenómeno en el libro de métodos numéricos de Higham, o en la discusión de Hairer y Wanner sobre los métodos W.
Depende de dónde creas que podría estar el error. Si tienes mucha confianza en tu implementación de Euler hacia atrás, no comenzaría allí. La experiencia me ha vuelto paranoico en mis implementaciones de métodos numéricos, por lo que si fuera yo, comenzaría codificando algunos problemas de prueba realmente básicos (un par de problemas lineales rígidos y no rígidos, la ecuación de calor con una aproximación de diferencia finita centrada, cosas así) y usaría el método de soluciones fabricadas para asegurarme de que sé cuál será la solución y contra qué debería compararme.
Sin embargo, ya has hecho algo de eso:
Eso sería lo siguiente que probaría: usar un jacobiano analítico. Después de eso, también puede observar los valores propios extremos de su diferencia finita jacobiana en caso de que se encuentre en la región inestable de Euler atrasado. Mirar los valores propios extremos de su analítico jacobiano como base para la comparación podría darle una idea. Suponiendo que todos revisen, el problema probablemente esté en la solución de Newton.
fuente