1
1
import numpy as np
2
2
import scipy .sparse as sp
3
+ import scipy .linalg as spla
3
4
from line_profiler import profile
4
5
from numpy .linalg import eig , eigh
5
- from pyclassify .utils import check_A_square_matrix , power_method_numba_helper
6
+ from pyclassify .utils import check_square_matrix , check_symm_square , power_method_numba_helper
7
+ import cupy as cp
8
+ cp .cuda .Device (0 )
9
+ import cupy .linalg as cpla
10
+ import cupyx .scipy .sparse as cpsp
6
11
7
12
8
13
@profile
9
14
def eigenvalues_np (A , symmetric = True ):
10
15
"""
11
16
Compute the eigenvalues of a square matrix using NumPy's `eig` or `eigh` function.
12
17
13
- This function checks if the input matrix is square (and is actually a matrix) using 'check_A_square_matrix ', and then computes its eigenvalues.
18
+ This function checks if the input matrix is square (and is actually a matrix) using 'check_square_matrix ', and then computes its eigenvalues.
14
19
If the matrix is symmetric, it uses `eigh` (which is more efficient for symmetric matrices).
15
20
Otherwise, it uses `eig`.
16
21
@@ -27,7 +32,7 @@ def eigenvalues_np(A, symmetric=True):
27
32
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
28
33
ValueError: If number of rows != number of columns.
29
34
"""
30
- check_A_square_matrix (A )
35
+ check_square_matrix (A )
31
36
eigenvalues , _ = eigh (A ) if symmetric else eig (A )
32
37
return eigenvalues
33
38
@@ -53,22 +58,46 @@ def eigenvalues_sp(A, symmetric=True):
53
58
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
54
59
ValueError: If number of rows != number of columns.
55
60
"""
56
- check_A_square_matrix (A )
61
+ check_square_matrix (A )
57
62
eigenvalues , _ = (
58
- sp . linalg .eigsh (A , k = A .shape [0 ] - 1 )
63
+ spla .eigsh (A , k = A .shape [0 ] - 1 )
59
64
if symmetric
60
- else sp . linalg .eigs (A , k = A .shape [0 ] - 1 )
65
+ else spla .eigs (A , k = A .shape [0 ] - 1 )
61
66
)
62
67
return eigenvalues
63
68
64
69
70
+ @profile
71
+ def eigenvalues_cp (A ):
72
+ """
73
+ Compute the eigenvalues of a sparse matrix using CuPy's `eigsh` function.
74
+
75
+ This function checks if the input matrix is square and symmetric, then computes its eigenvalues using
76
+ CupY's sparse linear algebra solvers. For symmetric matrices, it uses `eigsh` for
77
+ more efficient computation.
78
+
79
+ Args:
80
+ A (cpsp.spmatrix): A square sparse matrix whose eigenvalues are to be computed.
81
+
82
+ Returns:
83
+ np.ndarray: An array containing the eigenvalues of the sparse matrix `A`.
84
+
85
+ Raises:
86
+ TypeError: If the input is not a CuPy sparse symmetric matrix.
87
+ ValueError: If number of rows != number of columns.
88
+ """
89
+ check_symm_square (A )
90
+ eigenvalues , _ = cpla .eigsh (A , k = A .shape [0 ] - 1 )
91
+ return eigenvalues
92
+
93
+
65
94
@profile
66
95
def power_method (A , max_iter = 500 , tol = 1e-4 , x = None ):
67
96
"""
68
97
Compute the dominant eigenvalue of a square matrix using the power method.
69
98
70
99
Args:
71
- A (np.ndarray or sp.spmatrix): A square matrix whose dominant eigenvalue is to be computed.
100
+ A (np.ndarray or sp.spmatrix or cpsp.spmatrix ): A square matrix whose dominant eigenvalue is to be computed.
72
101
max_iter (int, optional): Maximum number of iterations to perform (default is 500).
73
102
tol (float, optional): Tolerance for convergence based on the relative change between iterations
74
103
(default is 1e-4).
@@ -81,19 +110,19 @@ def power_method(A, max_iter=500, tol=1e-4, x=None):
81
110
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
82
111
ValueError: If number of rows != number of columns.
83
112
"""
84
- check_A_square_matrix (A )
113
+ check_square_matrix (A )
85
114
if x is None :
86
115
x = np .random .rand (A .shape [0 ])
87
- x /= np . linalg .norm (x )
116
+ x /= spla .norm (x )
88
117
x_old = x
89
118
90
119
iteration = 0
91
120
update_norm = tol + 1
92
121
93
122
while iteration < max_iter and update_norm > tol :
94
123
x = A @ x
95
- x /= np . linalg .norm (x )
96
- update_norm = np . linalg . norm (x - x_old ) / np . linalg .norm (x_old )
124
+ x /= spla .norm (x )
125
+ update_norm = spla . norm (x - x_old ) / spla .norm (x_old )
97
126
x_old = x .copy ()
98
127
iteration += 1
99
128
@@ -121,6 +150,44 @@ def power_method_numba(A):
121
150
return power_method_numba_helper (A )
122
151
123
152
153
+ @profile
154
+ def power_method_cp (A , max_iter = 500 , tol = 1e-4 , x = None ):
155
+ """
156
+ Compute the dominant eigenvalue of a square matrix using the power method.
157
+
158
+ Args:
159
+ A (cp.spmatrix): A square matrix whose dominant eigenvalue is to be computed.
160
+ max_iter (int, optional): Maximum number of iterations to perform (default is 500).
161
+ tol (float, optional): Tolerance for convergence based on the relative change between iterations
162
+ (default is 1e-4).
163
+ x (cp.ndarray, optional): Initial guess for the eigenvector. If None, a random vector is generated.
164
+
165
+ Returns:
166
+ float: The approximated dominant eigenvalue of the matrix `A`, computed as the Rayleigh quotient x @ A @ x.
167
+
168
+ Raises:
169
+ TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
170
+ ValueError: If number of rows != number of columns.
171
+ """
172
+ check_square_matrix (A )
173
+ if x is None :
174
+ x = cp .random .rand (A .shape [0 ])
175
+ x /= cpla .norm (x )
176
+ x_old = x
177
+
178
+ iteration = 0
179
+ update_norm = tol + 1
180
+
181
+ while iteration < max_iter and update_norm > tol :
182
+ x = A @ x
183
+ x /= cpla .norm (x )
184
+ update_norm = cpla .norm (x - x_old ) / cpla .norm (x_old )
185
+ x_old = x .copy ()
186
+ iteration += 1
187
+
188
+ return x @ A @ x
189
+
190
+
124
191
def Lanczos_PRO (A , q , m = None , toll = np .sqrt (np .finfo (float ).eps )):
125
192
"""
126
193
Perform the Lanczos algorithm for symmetric matrices.
@@ -223,6 +290,5 @@ def QR_method(A_copy, tol=1e-10, max_iter=100):
223
290
Q = Q @R
224
291
A = A @Q
225
292
iter += 1
226
-
227
293
228
294
return np .diag (A ), Q
0 commit comments