Skip to content

Commit 520f64c

Browse files
committed
Working cupy methods (power method and eighs)
1 parent 67643a1 commit 520f64c

File tree

10 files changed

+110
-59
lines changed

10 files changed

+110
-59
lines changed

.github/workflows/docs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ on:
55
branches:
66
- main
77
- final_project
8+
- gpu_branch
89

910
pull_request:
1011
branches:
1112
- main
1213
- final_project
14+
- gpu_branch
1315

1416
jobs:
1517
docs:

.github/workflows/formatter.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ on:
55
branches:
66
- main
77
- final_project
8+
- gpu_branch
89

910
pull_request:
1011
branches:
1112
- main
1213
- final_project
14+
- gpu_branch
1315

1416
jobs:
1517
linter:

.github/workflows/tests.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ jobs:
2323
steps:
2424
- uses: actions/checkout@v3
2525

26+
- name: Install system dependencies
27+
run: |
28+
apt-get update && apt-get install -y python3-venv python3-pip
29+
2630
- name: Install additional dependencies
2731
run: |
2832
python3 -m venv venv
2933
source venv/bin/activate
3034
python -m pip install .
35+
shell: bash -e {0}
3136

3237
- name: Test with pytest
3338
run: |
3439
source venv/bin/activate
35-
python -m pytest
40+
python -m pytest
41+
shell: bash -e {0}

experiments/config.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
dimension: 1000
2-
1+
dim: 1000
32
density: 0.1
3+
tol: 0.001
4+
max_iter: 100

scripts/run.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626

2727
kwargs = read_config(filename)
28-
dim = kwargs["dimension"]
28+
dim = kwargs["dim"]
2929
density = kwargs["density"]
30+
# also tol and max iter
3031

3132

3233
matrix = sp.random(dim, dim, density=density, format="csr")

shell/submit.sbatch

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ export SLURM_NTASKS_PER_NODE=1 # due to Ulysses's bug
2929
module load cuda/12.1
3030
conda init
3131
source ~/.bashrc
32-
conda activate ~/miniconda3/envs/devtools_scicomp
33-
pip install cupy-cuda12x
34-
python -m pip freeze > requirements.txt
35-
# Run the script
32+
conda activate /scratch/ltomada/miniconda3/envs/devtools_scicomp
33+
python -m pytest -v
3634
#python scripts/run.py fit --config=experiments/config.yaml

src/pyclassify/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"eigenvalues_cp",
55
"power_method",
66
"power_method_numba",
7+
"power_method_cp",
78
]
89

910
from .eigenvalues import (
@@ -12,4 +13,5 @@
1213
eigenvalues_cp,
1314
power_method,
1415
power_method_numba,
15-
)
16+
power_method_cp,
17+
)

src/pyclassify/eigenvalues.py

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
import scipy.linalg as spla
44
from line_profiler import profile
55
from numpy.linalg import eig, eigh
6-
from pyclassify.utils import check_square_matrix, check_symm_square, power_method_numba_helper
6+
from pyclassify.utils import (
7+
check_square_matrix,
8+
check_symm_square,
9+
power_method_numba_helper,
10+
)
711
import cupy as cp
12+
813
cp.cuda.Device(0)
914
import cupy.linalg as cpla
1015
import cupyx.scipy.sparse as cpsp
16+
from cupyx.scipy.sparse.linalg._eigen import eigsh as cp_eigsh
1117

1218

1319
@profile
@@ -60,9 +66,9 @@ def eigenvalues_sp(A, symmetric=True):
6066
"""
6167
check_square_matrix(A)
6268
eigenvalues, _ = (
63-
spla.eigsh(A, k=A.shape[0] - 1)
69+
sp.linalg.eigsh(A, k=A.shape[0] - 1)
6470
if symmetric
65-
else spla.eigs(A, k=A.shape[0] - 1)
71+
else sp.linalg.eigs(A, k=A.shape[0] - 1)
6672
)
6773
return eigenvalues
6874

@@ -73,8 +79,16 @@ def eigenvalues_cp(A):
7379
Compute the eigenvalues of a sparse matrix using CuPy's `eigsh` function.
7480
7581
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.
82+
CuPy's sparse linear algebra solvers. It uses `eigsh` for more efficient computation.
83+
IMPORTANT: it important to underline a couple of things regarding this function:
84+
- installing using the command
85+
.. code-block:: shell
86+
python -m pip install cupy-cuda12x
87+
does not allow, for some reason, to import cupyx.scipy.sparse.linalg, and it necessary to import the
88+
function manually form the source code. The problem can be observed using python3.10, while for
89+
python3.12 things seem to work.
90+
- the eighs function in this case does not allow to compute *all* the eigenvalues, but only a number
91+
$m<n$, so here just a reduced portion is computed (starting form the ones which are greater in magnitude).
7892
7993
Args:
8094
A (cpsp.spmatrix): A square sparse matrix whose eigenvalues are to be computed.
@@ -87,7 +101,8 @@ def eigenvalues_cp(A):
87101
ValueError: If number of rows != number of columns.
88102
"""
89103
check_symm_square(A)
90-
eigenvalues, _ = cpla.eigsh(A, k=A.shape[0] - 1)
104+
k = 5 if A.shape[0] > 5 else A.shape[0] - 2
105+
eigenvalues, _ = cp_eigsh(A, k=k)
91106
return eigenvalues
92107

93108

@@ -239,7 +254,6 @@ def Lanczos_PRO(A, q, m=None, toll=np.sqrt(np.finfo(float).eps)):
239254
if np.abs(beta[j]) < 1e-15:
240255
return Q, alpha, beta[:-1]
241256
return Q, alpha, beta[:-1]
242-
243257

244258

245259
def QR_method(A_copy, tol=1e-10, max_iter=100):
@@ -264,31 +278,45 @@ def QR_method(A_copy, tol=1e-10, max_iter=100):
264278
ValueError: If the input matrix A_copy is not square.
265279
"""
266280

267-
A=A_copy.copy()
268-
T=A.copy()
269-
A=np.array(A)
270-
iter=0
271-
272-
while np.linalg.norm(np.diag(A, -1), np.inf) > tol and iter<max_iter:
273-
Matrix_trigonometry=np.array([])
274-
QQ, RR=np.linalg.qr(T)
275-
T=RR@QQ
276-
for i in range(A.shape[0]-1):
277-
c=A[i, i]/np.sqrt(A[i, i]**2+A[i+1, i]**2)
278-
s=-A[i+1, i]/np.sqrt(A[i, i]**2+A[i+1, i]**2)
279-
Matrix_trigonometry=np.vstack((Matrix_trigonometry, [c, s])) if Matrix_trigonometry.size else np.array([[c, s]])
280-
281-
R=np.array([[c, -s], [s, c]])
282-
A[i:i+2, i:i+3]=R@A[i:i+2, i:i+3]
283-
A[i+1, i]=0
284-
Q=np.eye(A.shape[0])
285-
i=0
286-
Q[0:2, 0:2]=np.array([[Matrix_trigonometry[i, 0], Matrix_trigonometry[i, 1]], [-Matrix_trigonometry[i, 1], Matrix_trigonometry[i, 0]]])
287-
for i in range(1, A.shape[0]-1):
288-
R=np.eye(A.shape[0])
289-
R[i: i+2, i:i+2]=np.array([[Matrix_trigonometry[i, 0], Matrix_trigonometry[i, 1]], [-Matrix_trigonometry[i, 1], Matrix_trigonometry[i, 0]]])
290-
Q = Q@R
291-
A=A@Q
292-
iter+=1
293-
294-
return np.diag(A), Q
281+
A = A_copy.copy()
282+
T = A.copy()
283+
A = np.array(A)
284+
iter = 0
285+
286+
while np.linalg.norm(np.diag(A, -1), np.inf) > tol and iter < max_iter:
287+
Matrix_trigonometry = np.array([])
288+
QQ, RR = np.linalg.qr(T)
289+
T = RR @ QQ
290+
for i in range(A.shape[0] - 1):
291+
c = A[i, i] / np.sqrt(A[i, i] ** 2 + A[i + 1, i] ** 2)
292+
s = -A[i + 1, i] / np.sqrt(A[i, i] ** 2 + A[i + 1, i] ** 2)
293+
Matrix_trigonometry = (
294+
np.vstack((Matrix_trigonometry, [c, s]))
295+
if Matrix_trigonometry.size
296+
else np.array([[c, s]])
297+
)
298+
299+
R = np.array([[c, -s], [s, c]])
300+
A[i : i + 2, i : i + 3] = R @ A[i : i + 2, i : i + 3]
301+
A[i + 1, i] = 0
302+
Q = np.eye(A.shape[0])
303+
i = 0
304+
Q[0:2, 0:2] = np.array(
305+
[
306+
[Matrix_trigonometry[i, 0], Matrix_trigonometry[i, 1]],
307+
[-Matrix_trigonometry[i, 1], Matrix_trigonometry[i, 0]],
308+
]
309+
)
310+
for i in range(1, A.shape[0] - 1):
311+
R = np.eye(A.shape[0])
312+
R[i : i + 2, i : i + 2] = np.array(
313+
[
314+
[Matrix_trigonometry[i, 0], Matrix_trigonometry[i, 1]],
315+
[-Matrix_trigonometry[i, 1], Matrix_trigonometry[i, 0]],
316+
]
317+
)
318+
Q = Q @ R
319+
A = A @ Q
320+
iter += 1
321+
322+
return np.diag(A), Q

src/pyclassify/utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
def check_square_matrix(A):
1515
"""
16-
Checks if the input matrix is a square matrix of type NumPy ndarray or SciPy sparse matrix.
16+
Checks if the input matrix is a square matrix of type NumPy ndarray or SciPy or CuPy sparse matrix.
1717
This is done to ensure that the input matrix `A` is both:
18-
1. Of type `np.ndarray` (NumPy array) or `scipy.sparse.spmatrix` (SciPy sparse matrix) or 'cupyx.scipy.sparse.spmatrix'.
18+
1. Of type `np.ndarray` (NumPy array) or `scipy.sparse.spmatrix` (SciPy sparse matrix) or 'cupyx.scipy.sparse.spmatrix' (CuPy sparse matrix).
1919
2. A square matrix.
2020
2121
Args:
@@ -26,7 +26,9 @@ def check_square_matrix(A):
2626
ValueError: If number of rows != number of columns.
2727
"""
2828
if not isinstance(A, (np.ndarray, sp.spmatrix, cpsp.spmatrix)):
29-
raise TypeError("Input matrix must be a NumPy array or a SciPy/CuPy sparse matrix!")
29+
raise TypeError(
30+
"Input matrix must be a NumPy array or a SciPy/CuPy sparse matrix!"
31+
)
3032
if A.shape[0] != A.shape[1]:
3133
raise ValueError("Matrix must be square!")
3234

@@ -72,7 +74,7 @@ def check_symm_square(A):
7274
check_square_matrix(A)
7375
if isinstance(A, sp.spmatrix) and not np.allclose(A.toarray(), A.toarray().T):
7476
raise ValueError("Matrix must be symmetric!")
75-
elif isinstance(A, cpsp.spmatrix) and not cp.allclose(A.get(), A.get().T):
77+
elif isinstance(A, cpsp.spmatrix) and not cp.allclose(A.toarray(), A.toarray().T):
7678
raise ValueError("Matrix must be symmetric!")
7779

7880

@@ -134,4 +136,4 @@ def read_config(file: str) -> dict:
134136
filepath = os.path.abspath(f"{file}.yaml")
135137
with open(filepath, "r") as stream:
136138
kwargs = yaml.safe_load(stream)
137-
return kwargs
139+
return kwargs

test/test_.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import numpy as np
22
import scipy.sparse as sp
3-
import cupy as cp
4-
import os
5-
os.environ["CUPY_ACCELERATORS"] = "cub"
6-
cp.use_default_memory_pool = False
73
import cupyx.scipy.sparse as cpsp
84
import pytest
95
from pyclassify import (
@@ -12,6 +8,7 @@
128
eigenvalues_cp,
139
power_method,
1410
power_method_numba,
11+
power_method_cp,
1512
)
1613
from pyclassify.utils import check_square_matrix, make_symmetric, check_symm_square
1714

@@ -26,7 +23,7 @@
2623
@pytest.mark.parametrize("size", sizes)
2724
@pytest.mark.parametrize("density", densities)
2825
def test_checks_on_A(size, density):
29-
ugly_nonsquare_matrix = np.random.rand(size, size+1)
26+
ugly_nonsquare_matrix = np.random.rand(size, size + 1)
3027

3128
with pytest.raises(ValueError):
3229
check_square_matrix(ugly_nonsquare_matrix)
@@ -83,8 +80,20 @@ def test_implementations_power_method(size, density):
8380

8481
biggest_eigenvalue_pm = power_method(matrix)
8582
biggest_eigenvalue_pm_numba = power_method_numba(matrix.toarray())
86-
87-
assert np.isclose(biggest_eigenvalue_np, biggest_eigenvalue_sp, rtol=1e-4)
88-
assert np.isclose(biggest_eigenvalue_pm, biggest_eigenvalue_sp, rtol=1e-4)
89-
assert np.isclose(biggest_eigenvalue_cp, biggest_eigenvalue_sp, rtol=1e-4)
90-
assert np.isclose(biggest_eigenvalue_pm_numba, biggest_eigenvalue_sp, rtol=1e-4)
83+
biggest_eigenvalue_pm_cp = power_method_cp(cp_matrix)
84+
85+
assert np.isclose(
86+
biggest_eigenvalue_np, biggest_eigenvalue_sp, rtol=1e-4
87+
) # ensure numpy and scipy implementations are consistent
88+
assert np.isclose(
89+
biggest_eigenvalue_cp, biggest_eigenvalue_sp, rtol=1e-4
90+
) # ensure cupy and scipy implementations are consistent
91+
assert np.isclose(
92+
biggest_eigenvalue_pm, biggest_eigenvalue_sp, rtol=1e-4
93+
) # ensure power method and scipy implementation are consistent
94+
assert np.isclose(
95+
biggest_eigenvalue_pm_numba, biggest_eigenvalue_sp, rtol=1e-4
96+
) # ensure numba power method and scipy implementation are consistent
97+
assert np.isclose(
98+
biggest_eigenvalue_pm_cp, biggest_eigenvalue_sp, rtol=1e-4
99+
) # ensure cupy power method and scipy implementation are consistent

0 commit comments

Comments
 (0)