Skip to content

Commit 41b5c04

Browse files
committed
Adding power method and tests
1 parent 2c901da commit 41b5c04

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ iniconfig==2.0.0
1515
Jinja2==3.1.5
1616
kiwisolver==1.4.8
1717
line_profiler==4.2.0
18+
llvmlite==0.44.0
1819
MarkupSafe==3.0.2
1920
matplotlib==3.10.0
2021
mypy-extensions==1.0.0
21-
numpy==2.2.3
22+
numba==0.61.0
23+
numpy==2.1.3
2224
packaging==24.2
2325
pathspec==0.12.1
2426
pillow==11.1.0

src/pyclassify/utils.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import numpy as np
2+
import scipy.sparse as sp
3+
from numpy.linalg import eig, eigh
4+
5+
6+
7+
def make_symmetric(A):
8+
if not sp.isspmatrix(A):
9+
raise TypeError("Input must be a scipy sparse matrix")
10+
11+
A_sym = (A + A.T) / 2
12+
return A_sym
13+
14+
15+
16+
def eigenvalues_np(A, symmetric=True):
17+
if A.shape[0] != A.shape[1]:
18+
raise ValueError("Matrix must be square!")
19+
if not isinstance(A, (np.ndarray, sp.spmatrix)):
20+
raise TypeError("Input matrix must be a NumPy array or a SciPy sparse matrix!")
21+
22+
eigenvalues, _ = eigh(A) if symmetric else eig(A)
23+
return eigenvalues
24+
25+
26+
27+
def eigenvalues_sp(A, symmetric=True):
28+
if A.shape[0] != A.shape[1]:
29+
raise ValueError("Matrix must be square!")
30+
if not isinstance(A, (np.ndarray, sp.spmatrix)):
31+
raise TypeError("Input matrix must be a NumPy array or a SciPy sparse matrix!")
32+
33+
eigenvalues, _ = sp.linalg.eigsh(A, k=A.shape[0] - 1) if symmetric else sp.linalg.eigs(A, k=A.shape[0] - 1)
34+
return eigenvalues
35+
36+
37+
38+
def power_method(A, max_iter=1000, tol=1e-4):
39+
assert A.shape[0] == A.shape[1], "Matrix must be square"
40+
41+
x = np.random.rand(A.shape[0])
42+
x /= np.linalg.norm(x)
43+
x_old = x
44+
45+
iteration = 0
46+
update_norm = tol + 1
47+
48+
while iteration < max_iter and update_norm > tol:
49+
x = A @ x
50+
x /= np.linalg.norm(x)
51+
update_norm = np.linalg.norm(x - x_old)/np.linalg.norm(x_old)
52+
x_old = x.copy()
53+
iteration += 1
54+
55+
return x @ A @ x

test/test_.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import numpy as np
2+
import scipy.sparse as sp
3+
import pytest
4+
from pyclassify.utils import eigenvalues_np, eigenvalues_sp, power_method, make_symmetric
5+
6+
7+
np.random.seed(42)
8+
sizes = [10, 100, 1000]
9+
densities = [0.1, 0.5]
10+
11+
12+
13+
@pytest.mark.parametrize("size", sizes)
14+
@pytest.mark.parametrize("density", densities)
15+
def test_make_symmetric(size, density):
16+
matrix = sp.random(size, size, density=density, format="csr")
17+
symmetric_matrix = make_symmetric(matrix)
18+
19+
assert np.allclose(symmetric_matrix.toarray(), symmetric_matrix.toarray().T)
20+
assert symmetric_matrix.shape == matrix.shape
21+
22+
with pytest.raises(TypeError):
23+
_ = make_symmetric("banana")
24+
25+
26+
27+
@pytest.mark.parametrize("size", sizes)
28+
@pytest.mark.parametrize("density", densities)
29+
def test_power_method(size, density):
30+
matrix = sp.random(size, size, density=density, format="csr")
31+
matrix = make_symmetric(matrix)
32+
33+
eigs_np = eigenvalues_np(matrix.toarray(), symmetric=True)
34+
eigs_sp = eigenvalues_sp(matrix, symmetric=True)
35+
36+
index_np = np.argmax(np.abs(eigs_np))
37+
index_sp = np.argmax(np.abs(eigs_sp))
38+
39+
biggest_eigenvalue_np = eigs_np[index_np]
40+
biggest_eigenvalue_sp = eigs_sp[index_sp]
41+
42+
biggest_eigenvalue_pm = power_method(matrix)
43+
44+
assert np.isclose(biggest_eigenvalue_np, biggest_eigenvalue_sp, rtol=1e-4)
45+
assert np.isclose(biggest_eigenvalue_pm, biggest_eigenvalue_sp, rtol=1e-4)
46+
47+
ugly_nonsquare_matrix = np.random.rand(5, 3)
48+
49+
with pytest.raises(ValueError):
50+
_ = eigenvalues_np(eigenvalues_np(ugly_nonsquare_matrix, symmetric=True))
51+
52+
with pytest.raises(ValueError):
53+
_ = eigenvalues_sp(eigenvalues_sp(ugly_nonsquare_matrix, symmetric=True))

0 commit comments

Comments
 (0)