Ich habe einen Rückwärts-Euler-Löser in Python 3 implementiert (mit Numpy). Zu meiner eigenen Bequemlichkeit und als Übung habe ich auch eine kleine Funktion geschrieben, die eine endliche Differenzapproximation des Gradienten berechnet, so dass ich den Jacobian nicht immer analytisch bestimmen muss (wenn es überhaupt möglich ist!).
Unter Verwendung der in Ascher und Petzold 1998 bereitgestellten Beschreibungen habe ich diese Funktion geschrieben, die den Gradienten an einem gegebenen Punkt x bestimmt:
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
Ich habe diese Funktion getestet, indem ich eine multivariate Funktion für das Pendel genommen und den symbolischen Jacobi mit dem numerisch bestimmten Gradienten für einen Bereich von Punkten verglichen habe. Ich war mit den Testergebnissen zufrieden, der Fehler lag bei 1e-10. Als ich die ODE für das Pendel mit dem angenäherten Jacobian löste, funktionierte es auch sehr gut; Ich konnte keinen Unterschied zwischen den beiden feststellen.
Dann habe ich versucht, es mit der folgenden PDE (Fisher-Gleichung in 1D) zu testen:
unter Verwendung einer endlichen Differenzdiskretisierung.
Jetzt explodiert Newtons Methode im ersten Zeitschritt:
/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
Dies passiert für eine Vielzahl von eps-Werten, aber seltsamerweise nur, wenn die räumliche Schrittgröße und die zeitliche Schrittgröße der PDE so eingestellt sind, dass die Courant-Friedrichs-Lewy-Bedingung nicht erfüllt ist. Ansonsten klappt es. (Dies ist das Verhalten, das Sie beim Lösen mit Forward Euler erwarten würden!)
Der Vollständigkeit halber ist hier die Funktion für die Newton-Methode:
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
(Die Funktion la.solve ist scipy.linalg.solve.)
Ich bin zuversichtlich, dass meine Rückwärts-Euler-Implementierung in Ordnung ist, da ich sie mit einer Funktion für den Jacobi getestet habe und stabile Ergebnisse erzielt habe.
Ich kann im Debugger sehen, dass newton () 35 Iterationen verwaltet, bevor der Fehler auftritt. Diese Zahl bleibt für jedes EPS, das ich ausprobiert habe, gleich.
Eine zusätzliche Beobachtung: Wenn ich den Gradienten mit der FDA und einer Funktion unter Verwendung der Anfangsbedingung als Eingabe berechne und die beiden vergleiche, während die Größe von Epsilon variiert, wächst der Fehler mit abnehmendem Epsilon. Ich würde erwarten, dass es zuerst groß wird, dann kleiner und dann wieder größer, wenn epsilon schrumpft. Ein Fehler in meiner Implementierung des Jacobian ist also eine vernünftige Annahme, aber wenn ja, ist er so subtil, dass ich ihn nicht sehen kann. EDIT: Ich habe jacobian () modifiziert, um Forward anstelle von zentralen Unterschieden zu verwenden, und jetzt beobachte ich die erwartete Entwicklung des Fehlers. Newton () konvergiert jedoch immer noch nicht. Wenn ich dx in der Newton-Iteration betrachte, sehe ich, dass es nur wächst, es gibt nicht einmal eine Fluktuation: Es verdoppelt sich fast (Faktor 1,9) mit jedem Schritt, wobei der Faktor zunehmend größer wird.
Ascher und Petzold erwähnen, dass Differenzapproximationen für den Jakobianer nicht immer gut funktionieren. Kann ein angenäherter Jacobianer mit endlichen Differenzen eine Instabilität in Newtons Methode verursachen? Oder liegt die Ursache woanders? Wie könnte ich dieses Problem sonst angehen?