Ho implementato un solutore all'indietro-Euler in Python 3 (usando numpy). Per mia comodità e come esercizio, ho anche scritto una piccola funzione che calcola un'approssimazione di differenza finita del gradiente in modo che non debba sempre determinare analiticamente il giacobino (se è anche possibile!).
Usando le descrizioni fornite in Ascher e Petzold 1998 , ho scritto questa funzione che determina il gradiente in un dato punto 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
Ho testato questa funzione prendendo una funzione multivariata per il pendolo e confrontando il simbolico giacobino con il gradiente determinato numericamente per un intervallo di punti. Sono rimasto soddisfatto dei risultati del test, l'errore era di circa 1e-10. Quando ho risolto l'ODE per il pendolo usando l'approssimativo giacobino, ha funzionato anche molto bene; Non sono riuscito a rilevare alcuna differenza tra i due.
Quindi ho provato a provarlo con il seguente PDE (equazione di Fisher in 1D):
usando una discretizzazione a differenza finita.
Ora il metodo di Newton esplode nel primo timestep:
/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
Ciò accade per una varietà di valori eps, ma stranamente, solo quando la dimensione del passo spaziale PDE e la dimensione del passo temporale sono impostate in modo tale che la condizione Courant – Friedrichs – Lewy non sia soddisfatta. Altrimenti funziona. (Questo è il comportamento che ti aspetteresti se risolvi con Euler in avanti!)
Per completezza, ecco la funzione per il metodo 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 funzione la.solve è scipy.linalg.solve.)
Sono fiducioso che la mia implementazione di Euler all'indietro sia in ordine, perché l'ho testata usando una funzione per il giacobino e ho ottenuto risultati stabili.
Nel debugger posso vedere che newton () gestisce 35 iterazioni prima che si verifichi l'errore. Questo numero rimane lo stesso per ogni eps che ho provato.
Un'osservazione aggiuntiva: quando computo il gradiente con FDA e una funzione usando la condizione iniziale come input e confrontando i due variando la dimensione di epsilon, l'errore cresce man mano che epsilon si restringe. Mi sarei aspettato che fosse grande all'inizio, poi diventasse più piccolo, poi di nuovo più grande man mano che epsilon si restringe. Quindi un errore nella mia implementazione del Jacobian è un presupposto ragionevole, ma in tal caso, è così sottile che non riesco a vederlo. EDIT: ho modificato jacobian () per usare forward invece di differenze centrali, e ora osservo lo sviluppo previsto dell'errore. Tuttavia, newton () non riesce ancora a convergere. Osservando dx nell'iterazione di Newton, vedo che cresce solo, non c'è nemmeno una fluttuazione: quasi raddoppia (fattore 1.9) ad ogni passo, con il fattore che diventa progressivamente più grande.
Ascher e Petzold menzionano che le approssimazioni delle differenze per il giacobino non sempre funzionano bene. Un giacobino approssimativo con differenze finite può causare instabilità nel metodo di Newton? O la causa è altrove? In quale altro modo potrei affrontare questo problema?