@@ -8,7 +8,7 @@ use crate::{Lapack, Scalar};
8
8
use ndarray:: prelude:: * ;
9
9
use ndarray:: OwnedRepr ;
10
10
use ndarray:: ScalarOperand ;
11
- use num_traits:: { NumCast , Float } ;
11
+ use num_traits:: { Float , NumCast } ;
12
12
13
13
/// Find largest or smallest eigenvalues
14
14
#[ derive( Debug , Clone ) ]
@@ -20,12 +20,12 @@ pub enum Order {
20
20
/// The result of the eigensolver
21
21
///
22
22
/// In the best case the eigensolver has converged with a result better than the given threshold,
23
- /// then a `EigResult ::Ok` gives the eigenvalues, eigenvectors and norms. If an error ocurred
24
- /// during the process, it is returned in `EigResult ::Err`, but the best result is still returned,
25
- /// as it could be usable. If there is no result at all, then `EigResult ::NoResult` is returned.
23
+ /// then a `LobpcgResult ::Ok` gives the eigenvalues, eigenvectors and norms. If an error ocurred
24
+ /// during the process, it is returned in `LobpcgResult ::Err`, but the best result is still returned,
25
+ /// as it could be usable. If there is no result at all, then `LobpcgResult ::NoResult` is returned.
26
26
/// This happens if the algorithm fails in an early stage, for example if the matrix `A` is not SPD
27
27
#[ derive( Debug ) ]
28
- pub enum EigResult < A > {
28
+ pub enum LobpcgResult < A > {
29
29
Ok ( Array1 < A > , Array2 < A > , Vec < A > ) ,
30
30
Err ( Array1 < A > , Array2 < A > , Vec < A > , LinalgError ) ,
31
31
NoResult ( LinalgError ) ,
@@ -61,16 +61,18 @@ fn sorted_eig<A: Scalar + Lapack>(
61
61
fn ndarray_mask < A : Scalar > ( matrix : ArrayView2 < A > , mask : & [ bool ] ) -> Array2 < A > {
62
62
assert_eq ! ( mask. len( ) , matrix. ncols( ) ) ;
63
63
64
- let indices = ( 0 ..mask. len ( ) ) . zip ( mask. into_iter ( ) )
65
- . filter ( |( _, b) | * * b) . map ( |( a, _) | a)
64
+ let indices = ( 0 ..mask. len ( ) )
65
+ . zip ( mask. into_iter ( ) )
66
+ . filter ( |( _, b) | * * b)
67
+ . map ( |( a, _) | a)
66
68
. collect :: < Vec < usize > > ( ) ;
67
69
68
70
matrix. select ( Axis ( 1 ) , & indices)
69
71
}
70
72
71
73
/// Applies constraints ensuring that a matrix is orthogonal to it
72
74
///
73
- /// This functions takes a matrix `v` and constraint matrix `y` and orthogonalize the `v` to `y`.
75
+ /// This functions takes a matrix `v` and constraint- matrix `y` and orthogonalize `v` to `y`.
74
76
fn apply_constraints < A : Scalar + Lapack > (
75
77
mut v : ArrayViewMut < A , Ix2 > ,
76
78
cholesky_yy : & CholeskyFactorized < OwnedRepr < A > > ,
@@ -132,19 +134,23 @@ fn orthonormalize<T: Scalar + Lapack>(v: Array2<T>) -> Result<(Array2<T>, Array2
132
134
/// * `maxiter` - The maximal number of iterations
133
135
/// * `order` - Whether to solve for the largest or lowest eigenvalues
134
136
///
135
- /// The function returns an `EigResult ` with the eigenvalue/eigenvector and achieved residual norm
137
+ /// The function returns an `LobpcgResult ` with the eigenvalue/eigenvector and achieved residual norm
136
138
/// for it. All iterations are tracked and the optimal solution returned. In case of an error a
137
- /// special variant `EigResult ::NotConverged` additionally carries the error. This can happen when
139
+ /// special variant `LobpcgResult ::NotConverged` additionally carries the error. This can happen when
138
140
/// the precision of the matrix is too low (switch then from `f32` to `f64` for example).
139
- pub fn lobpcg < A : Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default , F : Fn ( ArrayView2 < A > ) -> Array2 < A > , G : Fn ( ArrayViewMut2 < A > ) > (
141
+ pub fn lobpcg <
142
+ A : Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default ,
143
+ F : Fn ( ArrayView2 < A > ) -> Array2 < A > ,
144
+ G : Fn ( ArrayViewMut2 < A > ) ,
145
+ > (
140
146
a : F ,
141
147
mut x : Array2 < A > ,
142
148
m : G ,
143
149
y : Option < Array2 < A > > ,
144
150
tol : A :: Real ,
145
151
maxiter : usize ,
146
152
order : Order ,
147
- ) -> EigResult < A > {
153
+ ) -> LobpcgResult < A > {
148
154
// the initital approximation should be maximal square
149
155
// n is the dimensionality of the problem
150
156
let ( n, size_x) = ( x. nrows ( ) , x. ncols ( ) ) ;
@@ -172,7 +178,7 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
172
178
// orthonormalize the initial guess
173
179
let ( x, _) = match orthonormalize ( x) {
174
180
Ok ( x) => x,
175
- Err ( err) => return EigResult :: NoResult ( err) ,
181
+ Err ( err) => return LobpcgResult :: NoResult ( err) ,
176
182
} ;
177
183
178
184
// calculate AX and XAX for Rayleigh quotient
@@ -182,7 +188,7 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
182
188
// perform eigenvalue decomposition of XAX
183
189
let ( mut lambda, eig_block) = match sorted_eig ( xax. view ( ) , None , size_x, & order) {
184
190
Ok ( x) => x,
185
- Err ( err) => return EigResult :: NoResult ( err) ,
191
+ Err ( err) => return LobpcgResult :: NoResult ( err) ,
186
192
} ;
187
193
188
194
// initiate approximation of the eigenvector
@@ -219,12 +225,20 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
219
225
220
226
// compare best result and update if we improved
221
227
let sum_rnorm: A :: Real = residual_norms. iter ( ) . cloned ( ) . sum ( ) ;
222
- if best_result. as_ref ( ) . map ( |x : & ( _ , _ , Vec < A :: Real > ) | x. 2 . iter ( ) . cloned ( ) . sum :: < A :: Real > ( ) > sum_rnorm) . unwrap_or ( true ) {
228
+ if best_result
229
+ . as_ref ( )
230
+ . map ( |x : & ( _ , _ , Vec < A :: Real > ) | x. 2 . iter ( ) . cloned ( ) . sum :: < A :: Real > ( ) > sum_rnorm)
231
+ . unwrap_or ( true )
232
+ {
223
233
best_result = Some ( ( lambda. clone ( ) , x. clone ( ) , residual_norms. clone ( ) ) ) ;
224
234
}
225
235
226
236
// disable eigenvalues which are below the tolerance threshold
227
- activemask = residual_norms. iter ( ) . zip ( activemask. iter ( ) ) . map ( |( x, a) | * x > tol && * a) . collect ( ) ;
237
+ activemask = residual_norms
238
+ . iter ( )
239
+ . zip ( activemask. iter ( ) )
240
+ . map ( |( x, a) | * x > tol && * a)
241
+ . collect ( ) ;
228
242
229
243
// resize identity block if necessary
230
244
let current_block_size = activemask. iter ( ) . filter ( |x| * * x) . count ( ) ;
@@ -279,23 +293,19 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
279
293
rar = ( & rar + & rar. t ( ) ) / two;
280
294
let xax = x. t ( ) . dot ( & ax) ;
281
295
282
- (
283
- ( & xax + & xax. t ( ) ) / two,
284
- x. t ( ) . dot ( & x) ,
285
- r. t ( ) . dot ( & r) ,
286
- x. t ( ) . dot ( & r)
287
- )
296
+ ( ( & xax + & xax. t ( ) ) / two, x. t ( ) . dot ( & x) , r. t ( ) . dot ( & r) , x. t ( ) . dot ( & r) )
288
297
} else {
289
298
(
290
299
lambda_diag,
291
300
ident0. clone ( ) ,
292
301
ident. clone ( ) ,
293
- Array2 :: zeros ( ( size_x, current_block_size) )
302
+ Array2 :: zeros ( ( size_x, current_block_size) ) ,
294
303
)
295
304
} ;
296
305
297
306
// mask and orthonormalize P and AP
298
- let p_ap = previous_p_ap. as_ref ( )
307
+ let p_ap = previous_p_ap
308
+ . as_ref ( )
299
309
. and_then ( |( p, ap) | {
300
310
let active_p = ndarray_mask ( p. view ( ) , & activemask) ;
301
311
let active_ap = ndarray_mask ( ap. view ( ) , & activemask) ;
@@ -318,10 +328,7 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
318
328
let xp = x. t ( ) . dot ( active_p) ;
319
329
let rp = r. t ( ) . dot ( active_p) ;
320
330
let ( pap, pp) = if explicit_gram_flag {
321
- (
322
- ( & pap + & pap. t ( ) ) / two,
323
- active_p. t ( ) . dot ( active_p)
324
- )
331
+ ( ( & pap + & pap. t ( ) ) / two, active_p. t ( ) . dot ( active_p) )
325
332
} else {
326
333
( pap, ident. clone ( ) )
327
334
} ;
@@ -342,16 +349,8 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
342
349
)
343
350
} else {
344
351
(
345
- stack ! [
346
- Axis ( 0 ) ,
347
- stack![ Axis ( 1 ) , xax, xar] ,
348
- stack![ Axis ( 1 ) , xar. t( ) , rar]
349
- ] ,
350
- stack ! [
351
- Axis ( 0 ) ,
352
- stack![ Axis ( 1 ) , xx, xr] ,
353
- stack![ Axis ( 1 ) , xr. t( ) , rr]
354
- ] ,
352
+ stack ! [ Axis ( 0 ) , stack![ Axis ( 1 ) , xax, xar] , stack![ Axis ( 1 ) , xar. t( ) , rar] ] ,
353
+ stack ! [ Axis ( 0 ) , stack![ Axis ( 1 ) , xx, xr] , stack![ Axis ( 1 ) , xr. t( ) , rr] ] ,
355
354
)
356
355
} ;
357
356
@@ -363,16 +362,16 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
363
362
if previous_p_ap. is_some ( ) {
364
363
previous_p_ap = None ;
365
364
continue ;
366
- } else { // or break if restart is not possible
365
+ } else {
366
+ // or break if restart is not possible
367
367
break Err ( err) ;
368
368
}
369
369
}
370
370
} ;
371
371
lambda = new_lambda;
372
372
373
373
// approximate eigenvector X and conjugate vectors P with solution of eigenproblem
374
- let ( p, ap, tau) = if let Some ( ( active_p, active_ap) ) = p_ap
375
- {
374
+ let ( p, ap, tau) = if let Some ( ( active_p, active_ap) ) = p_ap {
376
375
// tau are eigenvalues to basis of X
377
376
let tau = eig_vecs. slice ( s ! [ ..size_x, ..] ) ;
378
377
// alpha are eigenvalues to basis of R
@@ -414,8 +413,8 @@ pub fn lobpcg<A: Float + Scalar + Lapack + ScalarOperand + PartialOrd + Default,
414
413
dbg ! ( & residual_norms_history) ;
415
414
416
415
match final_norm {
417
- Ok ( _) => EigResult :: Ok ( vals, vecs, rnorm) ,
418
- Err ( err) => EigResult :: Err ( vals, vecs, rnorm, err)
416
+ Ok ( _) => LobpcgResult :: Ok ( vals, vecs, rnorm) ,
417
+ Err ( err) => LobpcgResult :: Err ( vals, vecs, rnorm, err) ,
419
418
}
420
419
}
421
420
@@ -425,7 +424,7 @@ mod tests {
425
424
use super :: ndarray_mask;
426
425
use super :: orthonormalize;
427
426
use super :: sorted_eig;
428
- use super :: EigResult ;
427
+ use super :: LobpcgResult ;
429
428
use super :: Order ;
430
429
use crate :: close_l2;
431
430
use crate :: qr:: * ;
@@ -486,7 +485,7 @@ mod tests {
486
485
let result = lobpcg ( |y| a. dot ( & y) , x, |_| { } , None , 1e-5 , n * 2 , order) ;
487
486
dbg ! ( & result) ;
488
487
match result {
489
- EigResult :: Ok ( vals, _, r_norms) | EigResult :: Err ( vals, _, r_norms, _) => {
488
+ LobpcgResult :: Ok ( vals, _, r_norms) | LobpcgResult :: Err ( vals, _, r_norms, _) => {
490
489
// check convergence
491
490
for ( i, norm) in r_norms. into_iter ( ) . enumerate ( ) {
492
491
if norm > 1e-5 {
@@ -501,7 +500,7 @@ mod tests {
501
500
close_l2 ( & Array1 :: from ( ground_truth_eigvals. to_vec ( ) ) , & vals, num as f64 * 5e-4 )
502
501
}
503
502
}
504
- EigResult :: NoResult ( err) => panic ! ( "Did not converge: {:?}" , err) ,
503
+ LobpcgResult :: NoResult ( err) => panic ! ( "Did not converge: {:?}" , err) ,
505
504
}
506
505
}
507
506
@@ -539,11 +538,15 @@ mod tests {
539
538
let diag = arr1 ( & [ 1. , 2. , 3. , 4. , 5. , 6. , 7. , 8. , 9. , 10. ] ) ;
540
539
let a = Array2 :: from_diag ( & diag) ;
541
540
let x: Array2 < f64 > = Array2 :: random ( ( 10 , 1 ) , Uniform :: new ( 0.0 , 1.0 ) ) ;
542
- let y: Array2 < f64 > = arr2 ( & [ [ 1.0 , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ] , [ 0. , 1.0 , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ] ] ) . reversed_axes ( ) ;
541
+ let y: Array2 < f64 > = arr2 ( & [
542
+ [ 1.0 , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ] ,
543
+ [ 0. , 1.0 , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ] ,
544
+ ] )
545
+ . reversed_axes ( ) ;
543
546
544
547
let result = lobpcg ( |y| a. dot ( & y) , x, |_| { } , Some ( y) , 1e-10 , 50 , Order :: Smallest ) ;
545
548
match result {
546
- EigResult :: Ok ( vals, vecs, r_norms) | EigResult :: Err ( vals, vecs, r_norms, _) => {
549
+ LobpcgResult :: Ok ( vals, vecs, r_norms) | LobpcgResult :: Err ( vals, vecs, r_norms, _) => {
547
550
// check convergence
548
551
for ( i, norm) in r_norms. into_iter ( ) . enumerate ( ) {
549
552
if norm > 0.01 {
@@ -561,7 +564,7 @@ mod tests {
561
564
1e-5 ,
562
565
) ;
563
566
}
564
- EigResult :: NoResult ( err) => panic ! ( "Did not converge: {:?}" , err) ,
567
+ LobpcgResult :: NoResult ( err) => panic ! ( "Did not converge: {:?}" , err) ,
565
568
}
566
569
}
567
570
}
0 commit comments