@@ -75,7 +75,7 @@ def default_print(state, identifier, file=sys.stdout):
75
75
76
76
77
77
# TODO This function should be merged with eigsh
78
- def davidson_iterations (matrix , state , max_subspace , max_iter , n_ep ,
78
+ def davidson_iterations (matrix , state , max_subspace , max_iter , n_ep , n_block ,
79
79
is_converged , which , callback = None , preconditioner = None ,
80
80
preconditioning_method = "Davidson" , debug_checks = False ,
81
81
residual_min_norm = None , explicit_symmetrisation = None ):
@@ -87,12 +87,15 @@ def davidson_iterations(matrix, state, max_subspace, max_iter, n_ep,
87
87
Matrix to diagonalise
88
88
state
89
89
DavidsonState containing the eigenvector guess
90
- max_subspace : int or NoneType, optional
90
+ max_subspace : int
91
91
Maximal subspace size
92
- max_iter : int, optional
92
+ max_iter : int
93
93
Maximal number of iterations
94
- n_ep : int or NoneType, optional
94
+ n_ep : int
95
95
Number of eigenpairs to be computed
96
+ n_block : int
97
+ Davidson block size: the number of vectors that are added to the subspace
98
+ in each iteration
96
99
is_converged
97
100
Function to test for convergence
98
101
callback : callable, optional
@@ -131,11 +134,11 @@ def callback(state, identifier):
131
134
# The problem size
132
135
n_problem = matrix .shape [1 ]
133
136
134
- # The block size
135
- n_block = len (state .subspace_vectors )
137
+ # The current subspace size == Number of guesses
138
+ n_ss_vec = len (state .subspace_vectors )
136
139
137
- # The current subspace size
138
- n_ss_vec = n_block
140
+ # Sanity checks for block size
141
+ assert n_block >= n_ep and n_block <= n_ss_vec
139
142
140
143
# The current subspace
141
144
SS = state .subspace_vectors
@@ -157,21 +160,23 @@ def callback(state, identifier):
157
160
Ax = evaluate (matrix @ SS )
158
161
state .n_applies += n_ss_vec
159
162
163
+ # Get the worksize view for the first iteration
164
+ Ass = Ass_cont [:n_ss_vec , :n_ss_vec ]
165
+
166
+ # Initiall projection of Ax onto the subspace exploiting the hermiticity
167
+ with state .timer .record ("projection" ):
168
+ for i in range (n_ss_vec ):
169
+ for j in range (i , n_ss_vec ):
170
+ Ass [i , j ] = SS [i ] @ Ax [j ]
171
+ if i != j :
172
+ Ass [j , i ] = Ass [i , j ]
173
+
160
174
while state .n_iter < max_iter :
161
175
state .n_iter += 1
162
176
163
177
assert len (SS ) >= n_block
164
178
assert len (SS ) <= max_subspace
165
179
166
- # Project A onto the subspace, keeping in mind
167
- # that the values Ass[:-n_block, :-n_block] are already valid,
168
- # since they have been computed in the previous iterations already.
169
- with state .timer .record ("projection" ):
170
- Ass = Ass_cont [:n_ss_vec , :n_ss_vec ] # Increase the work view size
171
- for i in range (n_block ):
172
- Ass [:, - n_block + i ] = Ax [- n_block + i ] @ SS
173
- Ass [- n_block :, :] = np .transpose (Ass [:, - n_block :])
174
-
175
180
# Compute the which(== largest, smallest, ...) eigenpair of Ass
176
181
# and the associated ritz vector as well as residual
177
182
with state .timer .record ("rayleigh_ritz" ):
@@ -237,7 +242,10 @@ def form_residual(rval, rvec):
237
242
# Update projection of ADC matrix A onto subspace
238
243
Ass = Ass_cont [:n_ss_vec , :n_ss_vec ]
239
244
for i in range (n_ss_vec ):
240
- Ass [:, i ] = Ax [i ] @ SS
245
+ for j in range (i , n_ss_vec ):
246
+ Ass [i , j ] = SS [i ] @ Ax [j ]
247
+ if i != j :
248
+ Ass [j , i ] = Ass [i , j ]
241
249
# continue to add residuals to space
242
250
243
251
with state .timer .record ("preconditioner" ):
@@ -266,12 +274,29 @@ def form_residual(rval, rvec):
266
274
n_ss_added = 0
267
275
for i in range (n_block ):
268
276
pvec = preconds [i ]
269
- # Project out the components of the current subspace
277
+ # Project out the components of the current subspace using
278
+ # conventional Gram-Schmidt (CGS) procedure.
270
279
# That is form (1 - SS * SS^T) * pvec = pvec + SS * (-SS^T * pvec)
271
280
coefficients = np .hstack (([1 ], - (pvec @ SS )))
272
281
pvec = lincomb (coefficients , [pvec ] + SS , evaluate = True )
273
282
pnorm = np .sqrt (pvec @ pvec )
274
- if pnorm > residual_min_norm :
283
+ if pnorm < residual_min_norm :
284
+ continue
285
+ # Perform reorthogonalisation if loss of orthogonality is
286
+ # detected; this comes at the expense of computing n_ss_vec
287
+ # additional scalar products but avoids linear dependence
288
+ # within the subspace.
289
+ with state .timer .record ("reorthogonalisation" ):
290
+ ss_overlap = np .array (pvec @ SS )
291
+ max_ortho_loss = np .max (np .abs (ss_overlap )) / pnorm
292
+ if max_ortho_loss > n_problem * eps :
293
+ # Update pvec by instance reorthogonalised against SS
294
+ # using a second CGS. Also update pnorm.
295
+ coefficients = np .hstack (([1 ], - ss_overlap ))
296
+ pvec = lincomb (coefficients , [pvec ] + SS , evaluate = True )
297
+ pnorm = np .sqrt (pvec @ pvec )
298
+ state .reortho_triggers .append (max_ortho_loss )
299
+ if pnorm >= residual_min_norm :
275
300
# Extend the subspace
276
301
SS .append (evaluate (pvec / pnorm ))
277
302
n_ss_added += 1
@@ -284,8 +309,10 @@ def form_residual(rval, rvec):
284
309
state .subspace_orthogonality = np .max (np .abs (orth ))
285
310
if state .subspace_orthogonality > n_problem * eps :
286
311
warnings .warn (la .LinAlgWarning (
287
- "Subspace in davidson has lost orthogonality. "
288
- "Expect inaccurate results."
312
+ "Subspace in Davidson has lost orthogonality. "
313
+ "Max. deviation from orthogonality is {:.4E}. "
314
+ "Expect inaccurate results." .format (
315
+ state .subspace_orthogonality )
289
316
))
290
317
291
318
if n_ss_added == 0 :
@@ -300,12 +327,26 @@ def form_residual(rval, rvec):
300
327
"be aborted without convergence. Try a different guess." ))
301
328
return state
302
329
330
+ # Matrix applies for the new vectors
303
331
with state .timer .record ("projection" ):
304
332
Ax .extend (matrix @ SS [- n_ss_added :])
305
333
state .n_applies += n_ss_added
306
334
335
+ # Update the worksize view for the next iteration
336
+ Ass = Ass_cont [:n_ss_vec , :n_ss_vec ]
307
337
308
- def eigsh (matrix , guesses , n_ep = None , max_subspace = None ,
338
+ # Project Ax onto the subspace, keeping in mind
339
+ # that the values Ass[:-n_ss_added, :-n_ss_added] are already valid,
340
+ # since they have been computed in the previous iterations already.
341
+ with state .timer .record ("projection" ):
342
+ for i in range (n_ss_vec - n_ss_added , n_ss_vec ):
343
+ for j in range (i + 1 ):
344
+ Ass [i , j ] = SS [i ] @ Ax [j ]
345
+ if i != j :
346
+ Ass [j , i ] = Ass [i , j ]
347
+
348
+
349
+ def eigsh (matrix , guesses , n_ep = None , n_block = None , max_subspace = None ,
309
350
conv_tol = 1e-9 , which = "SA" , max_iter = 70 ,
310
351
callback = None , preconditioner = None ,
311
352
preconditioning_method = "Davidson" , debug_checks = False ,
@@ -320,6 +361,9 @@ def eigsh(matrix, guesses, n_ep=None, max_subspace=None,
320
361
Guess vectors (fixes also the Davidson block size)
321
362
n_ep : int or NoneType, optional
322
363
Number of eigenpairs to be computed
364
+ n_block : int or NoneType, optional
365
+ The solver block size: the number of vectors that are added to the subspace
366
+ in each iteration
323
367
max_subspace : int or NoneType, optional
324
368
Maximal subspace size
325
369
conv_tol : float, optional
@@ -364,11 +408,28 @@ def eigsh(matrix, guesses, n_ep=None, max_subspace=None,
364
408
if n_ep is None :
365
409
n_ep = len (guesses )
366
410
elif n_ep > len (guesses ):
367
- raise ValueError ("n_ep cannot exceed the number of guess vectors." )
411
+ raise ValueError (f"n_ep (= { n_ep } ) cannot exceed the number of guess "
412
+ f"vectors (= { len (guesses )} )." )
413
+
414
+ if n_block is None :
415
+ n_block = n_ep
416
+ elif n_block < n_ep :
417
+ raise ValueError (f"n_block (= { n_block } ) cannot be smaller than the number "
418
+ f"of states requested (= { n_ep } )." )
419
+ elif n_block > len (guesses ):
420
+ raise ValueError (f"n_block (= { n_block } ) cannot exceed the number of guess "
421
+ f"vectors (= { len (guesses )} )." )
422
+
368
423
if not max_subspace :
369
424
# TODO Arnoldi uses this:
370
425
# max_subspace = max(2 * n_ep + 1, 20)
371
426
max_subspace = max (6 * n_ep , 20 , 5 * len (guesses ))
427
+ elif max_subspace < 2 * n_block :
428
+ raise ValueError (f"max_subspace (= { max_subspace } ) needs to be at least "
429
+ f"twice as large as n_block (n_block = { n_block } )." )
430
+ elif max_subspace < len (guesses ):
431
+ raise ValueError (f"max_subspace (= { max_subspace } ) cannot be smaller than "
432
+ f"the number of guess vectors (= { len (guesses )} )." )
372
433
373
434
def convergence_test (state ):
374
435
state .residuals_converged = state .residual_norms < conv_tol
@@ -385,7 +446,7 @@ def convergence_test(state):
385
446
386
447
state = DavidsonState (matrix , guesses )
387
448
davidson_iterations (matrix , state , max_subspace , max_iter ,
388
- n_ep = n_ep , is_converged = convergence_test ,
449
+ n_ep = n_ep , n_block = n_block , is_converged = convergence_test ,
389
450
callback = callback , which = which ,
390
451
preconditioner = preconditioner ,
391
452
preconditioning_method = preconditioning_method ,
0 commit comments