-
Notifications
You must be signed in to change notification settings - Fork 1
/
newton_and_quasinewton_script.py
243 lines (194 loc) · 7.9 KB
/
newton_and_quasinewton_script.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import numpy as np
from numpy import random as rand
import time
import math
from scipy import io, integrate, linalg, signal
from scipy.linalg import lu_factor, lu_solve
import matplotlib.pyplot as plt
def driver():
############################################################################
############################################################################
# Rootfinding example start. You are given F(x)=0.
#First, we define F(x) and its Jacobian.
def F(x):
return np.array([x[0]**2+x[1]**2-4, np.exp(x[0])+x[1]-1]);
def JF(x):
return np.array([[2*x[0], 2*x[1]],
[np.exp(x[0]), 1]]);
# Apply Newton Method:
x0 = np.array([0,0]); tol=1e-14; nmax=100;
(rN,rnN,nfN,nJN) = newton_method_nd(F,JF,x0,tol,nmax,True);
print(rN)
# Apply Lazy Newton (chord iteration)
nmax=1000;
(rLN,rnLN,nfLN,nJLN) = lazy_newton_method_nd(F,JF,x0,tol,nmax,True);
# Apply Broyden Method
Bmat='fwd'; B0 = JF(x0); nmax=100;
(rB,rnB,nfB) = broyden_method_nd(F,B0,x0,tol,nmax,Bmat,True);
# Plots and comparisons
numN = rnN.shape[0];
errN = np.max(np.abs(rnN[0:(numN-1)]-rN),1);
plt.plot(np.arange(numN-1),np.log10(errN+1e-18),'b-o',label='Newton');
plt.title('Newton iteration log10|r-rn|');
plt.legend();
plt.show();
numB = rnB.shape[0];
errB = np.max(np.abs(rnB[0:(numB-1)]-rN),1);
plt.plot(np.arange(numN-1),np.log10(errN+1e-18),'b-o',label='Newton');
plt.plot(np.arange(numB-1),np.log10(errB+1e-18),'g-o',label='Broyden');
plt.title('Newton and Broyden iterations log10|r-rn|');
plt.legend();
plt.show();
numLN = rnLN.shape[0];
errLN = np.max(np.abs(rnLN[0:(numLN-1)]-rN),1);
plt.plot(np.arange(numN-1),np.log10(errN+1e-18),'b-o',label='Newton');
plt.plot(np.arange(numB-1),np.log10(errB+1e-18),'g-o',label='Broyden');
plt.plot(np.arange(numLN-1),np.log10(errLN+1e-18),'r-o',label='Lazy Newton');
plt.title('Newton, Broyden and Lazy Newton iterations log10|r-rn|');
plt.legend();
plt.show();
################################################################################
# Newton method in n dimensions implementation
def newton_method_nd(f,Jf,x0,tol,nmax,verb=False):
# Initialize arrays and function value
xn = x0; #initial guess
rn = x0; #list of iterates
Fn = f(xn); #function value vector
n=0;
nf=1; nJ=0; #function and Jacobian evals
npn=1;
if verb:
print("|--n--|----xn----|---|f(xn)|---|");
while npn>tol and n<=nmax:
# compute n x n Jacobian matrix
Jn = Jf(xn);
nJ+=1;
if verb:
print("|--%d--|%1.7f|%1.7f|" %(n,np.linalg.norm(xn),np.linalg.norm(Fn)));
# Newton step (we could check whether Jn is close to singular here)
pn = -np.linalg.solve(Jn,Fn);
xn = xn + pn;
npn = np.linalg.norm(pn); #size of Newton step
n+=1;
rn = np.vstack((rn,xn));
Fn = f(xn);
nf+=1;
r=xn;
if verb:
if np.linalg.norm(Fn)>tol:
print("Newton method failed to converge, n=%d, |F(xn)|=%1.1e\n" % (nmax,np.linalg.norm(Fn)));
else:
print("Newton method converged, n=%d, |F(xn)|=%1.1e\n" % (n,np.linalg.norm(Fn)));
return (r,rn,nf,nJ);
# Lazy Newton method (chord iteration) in n dimensions implementation
def lazy_newton_method_nd(f,Jf,x0,tol,nmax,verb=False):
# Initialize arrays and function value
xn = x0; #initial guess
rn = x0; #list of iterates
Fn = f(xn); #function value vector
# compute n x n Jacobian matrix (ONLY ONCE)
Jn = Jf(xn);
# Use pivoted LU factorization to solve systems for Jf. Makes lusolve O(n^2)
lu, piv = lu_factor(Jn);
n=0;
nf=1; nJ=1; #function and Jacobian evals
npn=1;
if verb:
print("|--n--|----xn----|---|f(xn)|---|");
while npn>tol and n<=nmax:
if verb:
print("|--%d--|%1.7f|%1.7f|" %(n,np.linalg.norm(xn),np.linalg.norm(Fn)));
# Newton step (we could check whether Jn is close to singular here)
pn = -lu_solve((lu, piv), Fn); #We use lu solve instead of pn = -np.linalg.solve(Jn,Fn);
xn = xn + pn;
npn = np.linalg.norm(pn); #size of Newton step
n+=1;
rn = np.vstack((rn,xn));
Fn = f(xn);
nf+=1;
r=xn;
if verb:
if np.linalg.norm(Fn)>tol:
print("Lazy Newton method failed to converge, n=%d, |F(xn)|=%1.1e\n" % (nmax,np.linalg.norm(Fn)));
else:
print("Lazy Newton method converged, n=%d, |F(xn)|=%1.1e\n" % (n,np.linalg.norm(Fn)));
return (r,rn,nf,nJ);
# Implementation of Broyden method. B0 can either be an approx of Jf(x0) (Bmat='fwd'),
# an approx of its inverse (Bmat='inv') or the identity (Bmat='Id')
def broyden_method_nd(f,B0,x0,tol,nmax,Bmat='Id',verb=False):
# Initialize arrays and function value
d = x0.shape[0];
xn = x0; #initial guess
rn = x0; #list of iterates
Fn = f(xn); #function value vector
n=0;
nf=1;
npn=1;
#####################################################################
# Create functions to apply B0 or its inverse
if Bmat=='fwd':
#B0 is an approximation of Jf(x0)
# Use pivoted LU factorization to solve systems for B0. Makes lusolve O(n^2)
lu, piv = lu_factor(B0);
luT, pivT = lu_factor(B0.T);
def Bapp(x): return lu_solve((lu, piv), x); #np.linalg.solve(B0,x);
def BTapp(x): return lu_solve((luT, pivT), x) #np.linalg.solve(B0.T,x);
elif Bmat=='inv':
#B0 is an approximation of the inverse of Jf(x0)
def Bapp(x): return B0 @ x;
def BTapp(x): return B0.T @ x;
else:
Bmat='Id';
#default is the identity
def Bapp(x): return x;
def BTapp(x): return x;
####################################################################
# Define function that applies Bapp(x)+Un*Vn.T*x depending on inputs
def Inapp(Bapp,Bmat,Un,Vn,x):
rk=Un.shape[0];
if Bmat=='Id':
y=x;
else:
y=Bapp(x);
if rk>0:
y=y+Un.T@(Vn@x);
return y;
#####################################################################
# Initialize low rank matrices Un and Vn
Un = np.zeros((0,d)); Vn=Un;
if verb:
print("|--n--|----xn----|---|f(xn)|---|");
while npn>tol and n<=nmax:
if verb:
print("|--%d--|%1.7f|%1.7f|" % (n,np.linalg.norm(xn),np.linalg.norm(Fn)));
#Broyden step xn = xn -B_n\Fn
dn = -Inapp(Bapp,Bmat,Un,Vn,Fn);
# Update xn
xn = xn + dn;
npn=np.linalg.norm(dn);
###########################################################
###########################################################
# Update In using only the previous I_n-1
#(this is equivalent to the explicit update formula)
Fn1 = f(xn);
dFn = Fn1-Fn;
nf+=1;
I0rn = Inapp(Bapp,Bmat,Un,Vn,dFn); #In^{-1}*(Fn+1 - Fn)
un = dn - I0rn; #un = dn - In^{-1}*dFn
cn = dn.T @ (I0rn); # We divide un by dn^T In^{-1}*dFn
# The end goal is to add the rank 1 u*v' update as the next columns of
# Vn and Un, as is done in, say, the eigendecomposition
Vn = np.vstack((Vn,Inapp(BTapp,Bmat,Vn,Un,dn)));
Un = np.vstack((Un,(1/cn)*un));
n+=1;
Fn=Fn1;
rn = np.vstack((rn,xn));
r=xn;
if verb:
if npn>tol:
print("Broyden method failed to converge, n=%d, |F(xn)|=%1.1e\n" % (nmax,np.linalg.norm(Fn)));
else:
print("Broyden method converged, n=%d, |F(xn)|=%1.1e\n" % (n,np.linalg.norm(Fn)));
return(r,rn,nf)
# Execute driver
driver()