@@ -58,13 +58,17 @@ def __init__(
58
58
solver : str = "GN" ,
59
59
max_iters : int = 100 ,
60
60
step_tol : float = 1e-7 ,
61
+ ftol : float = None ,
62
+ gradient_tol : float = None ,
61
63
tau : float = 1e-11 ,
62
64
verbose : bool = True ,
63
65
):
64
66
# Set solver parameters
65
67
self .solver = solver
66
68
self .max_iters = max_iters
67
69
self .step_tol = step_tol
70
+ self .ftol = ftol
71
+ self .gradient_tol = gradient_tol
68
72
self .tau = tau
69
73
self .verbose = verbose
70
74
@@ -91,6 +95,27 @@ def __init__(
91
95
# Inverse of information matrix
92
96
self ._covariance_matrix : np .ndarray = None
93
97
98
+ def is_converged (self , delta_cost , cost , dx , grad_norm ) -> bool :
99
+ converged = False
100
+ if delta_cost is not None :
101
+ rel_cost_change = 0.0
102
+ if cost != 0 :
103
+ rel_cost_change = delta_cost / cost
104
+
105
+ if self .step_tol is not None and dx < self .step_tol :
106
+ converged = True
107
+ if self .ftol is not None and delta_cost is not None :
108
+ if rel_cost_change < self .ftol :
109
+ converged = True
110
+ if cost == 0.0 :
111
+ converged = True
112
+ if dx == 0.0 :
113
+ converged = True
114
+ if self .gradient_tol is not None and grad_norm is not None :
115
+ if grad_norm < self .gradient_tol :
116
+ converged = True
117
+ return converged
118
+
94
119
def add_residual (self , residual : Residual , loss : LossFunction = L2Loss ()):
95
120
"""Adds a residual to the problem, along with a robust loss
96
121
function to use. Default loss function is the standard L2Loss.
@@ -182,6 +207,10 @@ def _solve_gauss_newton(self) -> Dict[Hashable, State]:
182
207
"""
183
208
184
209
dx = 10
210
+ delta_cost = None
211
+ rel_cost_decrease = None
212
+ grad_norm = None
213
+
185
214
iter_idx = 0
186
215
cost_list = []
187
216
@@ -193,7 +222,11 @@ def _solve_gauss_newton(self) -> Dict[Hashable, State]:
193
222
header = "Initial cost: " + str (cost )
194
223
print (header )
195
224
196
- while (iter_idx < self .max_iters ) and (dx > self .step_tol ):
225
+ while iter_idx < self .max_iters :
226
+
227
+ if self .is_converged (delta_cost , cost_list [- 1 ], dx , grad_norm ):
228
+ break
229
+
197
230
H_spr = sparse .csr_matrix (H )
198
231
199
232
A = H_spr .T @ H_spr
@@ -208,8 +241,23 @@ def _solve_gauss_newton(self) -> Dict[Hashable, State]:
208
241
cost_list .append (cost )
209
242
210
243
dx = np .linalg .norm (delta_x )
244
+ if len (cost_list ) >= 2 :
245
+ delta_cost = np .abs (cost_list [- 1 ] - cost_list [- 2 ])
246
+ if cost_list [- 1 ] != 0 :
247
+ rel_cost_decrease = delta_cost / cost_list [- 1 ]
248
+ else :
249
+ rel_cost_decrease = 0
250
+ grad_norm = np .max (np .abs ((e .T @ H ).squeeze ()))
251
+
211
252
if self .verbose :
212
- self ._display_header (iter_idx , cost , dx )
253
+ self ._display_header (
254
+ iter_idx ,
255
+ cost ,
256
+ dx ,
257
+ delta_cost ,
258
+ rel_cost_decrease ,
259
+ grad_norm ,
260
+ )
213
261
214
262
iter_idx += 1
215
263
@@ -232,6 +280,10 @@ def _solve_LM(self) -> Dict[Hashable, State]:
232
280
"""
233
281
234
282
e , H , cost = self .compute_error_jac_cost ()
283
+
284
+ delta_cost = None
285
+ rel_cost_decrease = None
286
+ grad_norm = None
235
287
cost_list = [cost ]
236
288
237
289
H_spr = sparse .csr_matrix (H )
@@ -250,9 +302,12 @@ def _solve_LM(self) -> Dict[Hashable, State]:
250
302
print (header )
251
303
252
304
# Main LM loop
253
- while ( iter_idx < self .max_iters ) and ( dx > self . step_tol ) :
305
+ while iter_idx < self .max_iters :
254
306
A_solve = A + mu * sparse .identity (A .shape [0 ])
255
307
delta_x = sparse .linalg .spsolve (A_solve , - b ).reshape ((- 1 , 1 ))
308
+ dx = np .linalg .norm (delta_x )
309
+ if self .is_converged (delta_cost , cost_list [- 1 ], dx , grad_norm ):
310
+ break
256
311
257
312
variables_test = {k : v .copy () for k , v in self .variables .items ()}
258
313
@@ -287,10 +342,26 @@ def _solve_LM(self) -> Dict[Hashable, State]:
287
342
nu = 2 * nu
288
343
status = "Rejected."
289
344
290
- dx = np .linalg .norm (delta_x )
345
+
346
+
347
+ if len (cost_list ) >= 2 :
348
+ delta_cost = np .abs (cost_list [- 1 ] - cost_list [- 2 ])
349
+ if cost_list [- 1 ] != 0 :
350
+ rel_cost_decrease = delta_cost / cost_list [- 1 ]
351
+ else :
352
+ rel_cost_decrease = 0
353
+ grad_norm = np .max (np .abs ((e .T @ H ).squeeze ()))
291
354
292
355
if self .verbose :
293
- self ._display_header (iter_idx + 1 , cost , dx , status = status )
356
+ self ._display_header (
357
+ iter_idx ,
358
+ cost ,
359
+ dx ,
360
+ delta_cost ,
361
+ rel_cost_decrease ,
362
+ grad_norm ,
363
+ status = status ,
364
+ )
294
365
295
366
iter_idx += 1
296
367
@@ -479,7 +550,14 @@ def compute_covariance(self):
479
550
return None
480
551
481
552
def _display_header (
482
- self , iter_idx : int , current_cost : float , dx : float , status : str = None
553
+ self ,
554
+ iter_idx : int ,
555
+ current_cost : float ,
556
+ dx : float ,
557
+ delta_cost : float = None ,
558
+ delta_cost_rel : float = None ,
559
+ grad_norm : float = None ,
560
+ status : str = None ,
483
561
):
484
562
"""Displays the optimization progress.
485
563
@@ -497,7 +575,12 @@ def _display_header(
497
575
header = ("Iter: {0} || Cost: {1:.4e} || Step size: {2:.4e}" ).format (
498
576
iter_idx , current_cost , dx
499
577
)
500
-
578
+ if delta_cost is not None :
579
+ header += " || dC: {0:.4e}" .format (delta_cost )
580
+ if delta_cost_rel is not None :
581
+ header += " || dC/C: {0:.4e}" .format (delta_cost_rel )
582
+ if grad_norm is not None :
583
+ header += " || |grad|_inf: {0:.4e}" .format (grad_norm )
501
584
if status is not None :
502
585
header += " || Status: " + status
503
586
0 commit comments