@@ -197,7 +197,7 @@ def power_method_cp(A, max_iter=500, tol=1e-4, x=None):
197
197
198
198
199
199
@jit (nopython = True )
200
- def Lanczos_PRO (A , q = None , m = None , toll = np . sqrt ( np . finfo ( float ). eps ) ):
200
+ def Lanczos_PRO (A , q = None , m = None , tol = 1e-8 ):
201
201
r"""
202
202
Perform the Lanczos algorithm for symmetric matrices.
203
203
@@ -210,7 +210,7 @@ def Lanczos_PRO(A, q=None, m=None, toll=np.sqrt(np.finfo(float).eps)):
210
210
q (np.ndarray): Initial vector of size n.
211
211
m (int, optional): Number of eigenvalues to compute. Must be less than or equal to n.
212
212
If None, defaults to the size of A.
213
- toll (float, optional): Tolerance for orthogonality checks (default is sqrt(machine epsilon)).
213
+ tol (float, optional): Tolerance for orthogonality checks (default is sqrt(machine epsilon)).
214
214
215
215
Returns:
216
216
tuple: A tuple (Q, alpha, beta) where:
@@ -251,7 +251,7 @@ def Lanczos_PRO(A, q=None, m=None, toll=np.sqrt(np.finfo(float).eps)):
251
251
252
252
for j in range (1 , m ):
253
253
q = r / beta [j - 1 ]
254
- if np .any (np .abs (q @ Q [: j - 1 ].T ) > toll ):
254
+ if np .any (np .abs (q @ Q [: j - 1 ].T ) > tol ):
255
255
for q_bbasis in Q [: j - 1 ]:
256
256
q = q - (q @ q_bbasis ) * q_bbasis
257
257
@@ -263,9 +263,8 @@ def Lanczos_PRO(A, q=None, m=None, toll=np.sqrt(np.finfo(float).eps)):
263
263
beta .append (np .linalg .norm (r ))
264
264
265
265
if np .abs (beta [j ]) < 1e-15 :
266
-
267
- return Q , alpha , beta [:- 1 ]
268
- return Q , alpha , beta [:- 1 ]
266
+ return Q , np .array (alpha ), np .array (beta [:- 1 ])
267
+ return Q , np .array (alpha ), np .array (beta [:- 1 ])
269
268
270
269
271
270
@jit (nopython = True )
@@ -297,15 +296,15 @@ def QR_method(diag, off_diag, tol=1e-8, max_iter=100):
297
296
Matrix_trigonometric = np .zeros ((n - 1 , 2 ))
298
297
299
298
iter = 0
300
- #eigenvalues_old = np.array(diag)
299
+ # eigenvalues_old = np.array(diag)
301
300
302
301
r , c , s = 0 , 0 , 0
303
302
d , mu = 0 , 0 # mu: Wilkinson shift
304
303
a_m , b_m_1 = 0 , 0
305
- #tmp = 0
304
+ # tmp = 0
306
305
x , y = 0 , 0
307
306
m = n - 1
308
- toll_equivalence = 1e-10
307
+ tol_equivalence = 1e-10
309
308
w , z = 0 , 0
310
309
311
310
while iter < max_iter and m > 0 :
@@ -314,7 +313,7 @@ def QR_method(diag, off_diag, tol=1e-8, max_iter=100):
314
313
b_m_1 = off_diag [m - 1 ]
315
314
d = (diag [m - 1 ] - a_m ) * 0.5
316
315
317
- if np .abs (d ) < toll_equivalence :
316
+ if np .abs (d ) < tol_equivalence :
318
317
mu = diag [m ] - np .abs (b_m_1 )
319
318
else :
320
319
mu = a_m - b_m_1 * b_m_1 / (
@@ -347,7 +346,7 @@ def QR_method(diag, off_diag, tol=1e-8, max_iter=100):
347
346
off_diag [i + 1 ] = c * off_diag [i + 1 ]
348
347
349
348
else :
350
- if abs (d ) < toll_equivalence :
349
+ if abs (d ) < tol_equivalence :
351
350
if off_diag [0 ] * d > 0 :
352
351
c = np .sqrt (2 ) / 2
353
352
s = - np .sqrt (2 ) / 2
@@ -364,7 +363,9 @@ def QR_method(diag, off_diag, tol=1e-8, max_iter=100):
364
363
err_rel = 1
365
364
iter_newton = 0
366
365
while err_rel > 1e-10 and iter_newton < 1000 :
367
- x_new = x_0 - np .cos (x_0 ) * np .cos (x_0 ) * (np .tan (x_0 ) + b_2 / d )
366
+ x_new = x_0 - np .cos (x_0 ) * np .cos (x_0 ) * (
367
+ np .tan (x_0 ) + b_2 / d
368
+ )
368
369
err_rel = np .abs ((x_new - x_0 ) / x_new )
369
370
x_0 = x_new
370
371
iter_newton += 1
@@ -383,7 +384,7 @@ def QR_method(diag, off_diag, tol=1e-8, max_iter=100):
383
384
diag [1 ] = c * c * diag [1 ] + s * s * a_0 + 2 * s * c * b_1
384
385
385
386
# Uncomment to compute the eigenvalue
386
- #Q[:, :m] = Q[:, :m] @ Matrix_trigonometric[:m, :]
387
+ # Q[:, :m] = Q[:, :m] @ Matrix_trigonometric[:m, :]
387
388
388
389
iter += 1
389
390
if abs (off_diag [m - 1 ]) < tol * (np .abs (diag [m ]) + np .abs (diag [m - 1 ])):
@@ -413,7 +414,221 @@ def QR(A, q0=None, tol=1e-8, max_iter=100):
413
414
Raises:
414
415
ValueError: If the input matrix is not square.
415
416
"""
416
- _ , alpha , beta = Lanczos_PRO (A , q = q0 , m = None , toll = 1e-8 )
417
- alpha = np .array (alpha )
418
- beta = np .array (beta )
417
+ _ , alpha , beta = Lanczos_PRO (A , q = q0 , m = None , tol = 1e-8 )
419
418
return QR_method (alpha , beta , tol = tol , max_iter = max_iter )
419
+
420
+
421
+ def Lanczos_PRO_cp (A , q = None , m = None , tol = 1e-8 ):
422
+ r"""
423
+ Perform the Lanczos algorithm for symmetric matrices.
424
+
425
+ This function computes an orthogonal matrix Q and tridiagonal matrix T such that A is approximately
426
+ equal to Q * T * Q.T, where A is a symmetric matrix. The algorithm is useful for finding a few
427
+ eigenvalues and eigenvectors of large symmetric matrices.
428
+
429
+ Args:
430
+ A (cp.ndarray or cpsp.spmatrix): A symmetric square matrix of size n x n.
431
+ q (cp.ndarray): Initial vector of size n.
432
+ m (int, optional): Number of eigenvalues to compute. Must be less than or equal to n.
433
+ If None, defaults to the size of A.
434
+ tol (float, optional): Tolerance for orthogonality checks (default is sqrt(machine epsilon)).
435
+
436
+ Returns:
437
+ tuple: A tuple (Q, alpha, beta) where:
438
+ - Q (cp.ndarray): Orthogonal matrix of size n x m.
439
+ - alpha (cp.ndarray): Vector of size m containing the diagonal elements of the tridiagonal matrix.
440
+ - beta (cp.ndarray): Vector of size m-1 containing the off-diagonal elements of the tridiagonal matrix.
441
+
442
+ Raises:
443
+ ValueError: If the input matrix A is not square or if m is greater than the size of A.
444
+ """
445
+ if q is None :
446
+ q = cp .random .rand (A .shape [0 ])
447
+ if q [0 ] == 0 :
448
+ q [0 ] += 1
449
+
450
+ if m == None :
451
+ m = A .shape [0 ]
452
+
453
+ check_symm_square (A )
454
+
455
+ if A .shape [0 ] != q .shape [0 ]:
456
+ raise ValueError ("Input vector q must have the same size as the matrix A." )
457
+
458
+ q = q / cp .linalg .norm (q )
459
+ # Q=np.array([q])
460
+ Q = cp .zeros ((m , A .shape [0 ]))
461
+ Q [0 ] = q
462
+ r = A @ q
463
+ alpha = []
464
+ beta = []
465
+ alpha .append (q @ r )
466
+ r = r - alpha [0 ] * q
467
+ beta .append (cp .linalg .norm (r ))
468
+
469
+ for j in range (1 , m ):
470
+ q = r / beta [j - 1 ]
471
+ if cp .any (cp .abs (q @ Q [: j - 1 ].T ) > tol ):
472
+ for q_bbasis in Q [: j - 1 ]:
473
+ q = q - (q @ q_bbasis ) * q_bbasis
474
+
475
+ q = q / cp .linalg .norm (q )
476
+ Q [j ] = q
477
+ r = A @ q - beta [j - 1 ] * Q [j - 1 ]
478
+ alpha .append (q @ r )
479
+ r = r - alpha [j ] * q
480
+ beta .append (cp .linalg .norm (r ))
481
+
482
+ if cp .abs (beta [j ]) < 1e-15 :
483
+
484
+ return cp .array (alpha ), cp .array (beta [:- 1 ])
485
+ return Q , cp .array (alpha ), cp .array (beta [:- 1 ])
486
+
487
+
488
+ def QR_method_cp (diag , off_diag , tol = 1e-8 , max_iter = 100 ):
489
+ """
490
+ Compute the eigenvalues of a tridiagonal matrix using the QR algorithm.
491
+
492
+ This function uses the QR decomposition method to iteratively compute the eigenvalues of a given tridiagonal matrix.
493
+ The QR algorithm is an iterative method that computes the eigenvalues of a matrix by decomposing it into a product
494
+ of an orthogonal matrix Q and an upper triangular matrix R, and then updating the matrix as the product of R and Q.
495
+
496
+ Args:
497
+ diag (cp.ndarray): Diagonal elements of the tridiagonal matrix.
498
+ off_diag (cp.ndarray): Off-diagonal elements of the tridiagonal matrix.
499
+ tol (float, optional): Tolerance for convergence based on the off-diagonal elements (default is 1e-10).
500
+ max_iter (int, optional): Maximum number of iterations to perform (default is 100).
501
+
502
+ Returns:
503
+ tuple: A tuple (eigenvalues, Q) where:
504
+ - eigenvalues (cp.ndarray): An array containing the eigenvalues of the matrix.
505
+ - Q (cp.ndarray): The orthogonal matrix Q from the final QR decomposition.
506
+
507
+ Raises:
508
+ ValueError: If the input matrix is not square.
509
+ """
510
+ n = diag .shape [0 ]
511
+ Q = cp .eye (n )
512
+
513
+ Matrix_trigonometric = cp .zeros ((n - 1 , 2 ))
514
+
515
+ iter = 0
516
+ # eigenvalues_old = np.array(diag)
517
+
518
+ r , c , s = 0 , 0 , 0
519
+ d , mu = 0 , 0 # mu: Wilkinson shift
520
+ a_m , b_m_1 = 0 , 0
521
+ x , y = 0 , 0
522
+ m = n - 1
523
+ tol_equivalence = 1e-10
524
+ w , z = 0 , 0
525
+
526
+ while iter < max_iter and m > 0 :
527
+ # prefetching most used value to avoid call overhead
528
+ a_m = diag [m ]
529
+ b_m_1 = off_diag [m - 1 ]
530
+ d = (diag [m - 1 ] - a_m ) * 0.5
531
+
532
+ if cp .abs (d ) < tol_equivalence :
533
+ mu = diag [m ] - cp .abs (b_m_1 )
534
+ else :
535
+ mu = a_m - b_m_1 * b_m_1 / (
536
+ d * (1 + cp .sqrt (d * d + b_m_1 * b_m_1 ) / cp .abs (d ))
537
+ )
538
+
539
+ x = diag [0 ] - mu
540
+ y = off_diag [0 ]
541
+
542
+ for i in range (m ):
543
+ if m > 1 :
544
+ r = np .sqrt (x * x + y * y )
545
+ c = x / r
546
+ s = - y / r
547
+ Matrix_trigonometric [i ][0 ] = c
548
+ Matrix_trigonometric [i ][1 ] = s
549
+
550
+ w = c * x - s * y
551
+ d = diag [i ] - diag [i + 1 ]
552
+ z = (2 * c * off_diag [i ] + d * s ) * s
553
+ diag [i ] -= z
554
+ diag [i + 1 ] += z
555
+ off_diag [i ] = d * c * s + (c * c - s * s ) * off_diag [i ]
556
+ x = off_diag [i ]
557
+ if i > 0 :
558
+ off_diag [i - 1 ] = w
559
+
560
+ if i < m - 1 :
561
+ y = - s * off_diag [i + 1 ]
562
+ off_diag [i + 1 ] = c * off_diag [i + 1 ]
563
+
564
+ else :
565
+ if abs (d ) < tol_equivalence :
566
+ if off_diag [0 ] * d > 0 :
567
+ c = cp .sqrt (2 ) / 2
568
+ s = - cp .sqrt (2 ) / 2
569
+ else :
570
+ c = s = cp .sqrt (2 ) / 2
571
+
572
+ else :
573
+ b_2 = off_diag [0 ]
574
+ if off_diag [0 ] * d > 0 :
575
+ x_0 = - cp .pi / 4
576
+ else :
577
+ x_0 = cp .pi / 4
578
+
579
+ err_rel = 1
580
+ iter_newton = 0
581
+ while err_rel > 1e-10 and iter_newton < 1000 :
582
+ x_new = x_0 - cp .cos (x_0 ) * cp .cos (x_0 ) * (
583
+ cp .tan (x_0 ) + b_2 / d
584
+ )
585
+ err_rel = cp .abs ((x_new - x_0 ) / x_new )
586
+ x_0 = x_new
587
+ iter_newton += 1
588
+
589
+ c = cp .cos (x_new / 2 )
590
+ s = cp .sin (x_new / 2 )
591
+
592
+ Matrix_trigonometric [i ][0 ] = c
593
+ Matrix_trigonometric [i ][1 ] = s
594
+
595
+ a_0 = diag [0 ]
596
+ b_1 = off_diag [0 ]
597
+
598
+ off_diag [0 ] = 0 # c * s * (a_0 - diag[1]) + b_1 * (c * c - s * s)
599
+ diag [0 ] = c * c * a_0 + s * s * diag [1 ] - 2 * s * c * b_1
600
+ diag [1 ] = c * c * diag [1 ] + s * s * a_0 + 2 * s * c * b_1
601
+
602
+ # Uncomment to compute the eigenvalue
603
+ # Q[:, :m] = Q[:, :m] @ Matrix_trigonometric[:m, :]
604
+
605
+ iter += 1
606
+ if abs (off_diag [m - 1 ]) < tol * (cp .abs (diag [m ]) + cp .abs (diag [m - 1 ])):
607
+ m -= 1
608
+
609
+ return diag , Q
610
+
611
+
612
+ @profile
613
+ def QR_cp (A , q0 = None , tol = 1e-8 , max_iter = 100 ):
614
+ """
615
+ Compute the eigenvalues of a square matrix using the QR algorithm.
616
+ Done using the Lanczos algorithm to compute the tridiagonal matrix and then the QR
617
+ algorithm to compute the eigenvalues.
618
+
619
+ Args:
620
+ A (cp.ndarray or cpsp.spmatrix): A square matrix whose eigenvalues are to be computed.
621
+ q0 (cp.ndarray, optional): An initial vector for the Lanczos process. If None, a random vector is used.
622
+ tol (float, optional): Convergence tolerance for the QR algorithm. Default is 1e-8
623
+ max_iter (int, optional): Maximum number of iterations for the QR algorithm. Deault is 100.
624
+
625
+ Returns:
626
+ tuple: A tuple (eigenvalues, Q) where:
627
+ - eigenvalues (cp.ndarray): An array containing the eigenvalues of the matrix.
628
+ - Q (cp.ndarray): The orthogonal matrix Q from the final QR decomposition.
629
+
630
+ Raises:
631
+ ValueError: If the input matrix is not square.
632
+ """
633
+ _ , alpha , beta = Lanczos_PRO_cp (A , q = q0 , m = None , tol = 1e-8 )
634
+ return QR_method_cp (alpha , beta , tol = tol , max_iter = max_iter )
0 commit comments