Skip to content

Commit 12f3743

Browse files
committed
decompositions: IncompleteCholesky: Completed tests
1 parent 0594c97 commit 12f3743

File tree

3 files changed

+92
-32
lines changed

3 files changed

+92
-32
lines changed

src/module.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,37 +151,39 @@ NB_MODULE(nanoeigenpy, m) {
151151
solvers, "LeastSquareDiagonalPreconditioner");
152152
#endif
153153

154+
using Eigen::Lower;
155+
154156
using Eigen::BiCGSTAB;
155157
using Eigen::ConjugateGradient;
156158
using Eigen::DiagonalPreconditioner;
157159
using Eigen::IdentityPreconditioner;
158160
using Eigen::LeastSquareDiagonalPreconditioner;
159161
using Eigen::LeastSquaresConjugateGradient;
160-
using Eigen::Lower;
161162
using Eigen::MINRES;
162163

163-
exposeMINRES<MINRES<Matrix, Lower>>(solvers, "MINRES");
164-
exposeBiCGSTAB<BiCGSTAB<Matrix>>(solvers, "BiCGSTAB");
165-
exposeConjugateGradient<ConjugateGradient<Matrix, Lower>>(
166-
solvers, "ConjugateGradient");
167-
exposeLeastSquaresConjugateGradient<LeastSquaresConjugateGradient<Matrix>>(
168-
solvers, "LeastSquaresConjugateGradient");
169-
170-
using IdentityBiCGSTAB = BiCGSTAB<Matrix, IdentityPreconditioner>;
171164
using IdentityConjugateGradient =
172165
ConjugateGradient<Matrix, Lower, IdentityPreconditioner>;
173166
using IdentityLeastSquaresConjugateGradient =
174167
LeastSquaresConjugateGradient<Matrix, IdentityPreconditioner>;
175168
using DiagonalLeastSquaresConjugateGradient =
176169
LeastSquaresConjugateGradient<Matrix, DiagonalPreconditioner<Scalar>>;
170+
using IdentityBiCGSTAB = BiCGSTAB<Matrix, IdentityPreconditioner>;
171+
using DiagonalMINRES = MINRES<Matrix, Lower, DiagonalPreconditioner<Scalar>>;
177172

178-
exposeBiCGSTAB<IdentityBiCGSTAB>(solvers, "IdentityBiCGSTAB");
173+
exposeConjugateGradient<ConjugateGradient<Matrix, Lower>>(
174+
solvers, "ConjugateGradient");
179175
exposeConjugateGradient<IdentityConjugateGradient>(
180176
solvers, "IdentityConjugateGradient");
177+
exposeLeastSquaresConjugateGradient<LeastSquaresConjugateGradient<Matrix>>(
178+
solvers, "LeastSquaresConjugateGradient");
181179
exposeLeastSquaresConjugateGradient<IdentityLeastSquaresConjugateGradient>(
182180
solvers, "IdentityLeastSquaresConjugateGradient");
183181
exposeLeastSquaresConjugateGradient<DiagonalLeastSquaresConjugateGradient>(
184182
solvers, "DiagonalLeastSquaresConjugateGradient");
183+
exposeMINRES<MINRES<Matrix, Lower>>(solvers, "MINRES");
184+
exposeMINRES<DiagonalMINRES>(solvers, "DiagonalMINRES");
185+
exposeBiCGSTAB<BiCGSTAB<Matrix>>(solvers, "BiCGSTAB");
186+
exposeBiCGSTAB<IdentityBiCGSTAB>(solvers, "IdentityBiCGSTAB");
185187

186188
exposeIncompleteLUT<SparseMatrix>(m, "IncompleteLUT");
187189
exposeIncompleteCholesky<SparseMatrix>(m, "IncompleteCholesky");

tests/test_incomplete_cholesky.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
from scipy.sparse import csc_matrix
33
import nanoeigenpy
44

5+
56
dim = 100
6-
rng = np.random.default_rng(42)
7+
rng = np.random.default_rng()
78

89
A = rng.random((dim, dim))
9-
A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim))
10+
A = (A + A.T) * 0.5 + np.diag(5.0 + rng.random(dim))
1011
A = csc_matrix(A)
1112

1213
ichol = nanoeigenpy.IncompleteCholesky(A)
@@ -35,17 +36,34 @@
3536

3637
ichol.analyzePattern(A)
3738
ichol.factorize(A)
39+
ichol.compute(A)
3840
assert ichol.info() == nanoeigenpy.ComputationInfo.Success
3941

40-
ichol_shift = nanoeigenpy.IncompleteCholesky()
41-
ichol_shift.setInitialShift(1e-2)
42-
ichol_shift.compute(A)
43-
assert ichol_shift.info() == nanoeigenpy.ComputationInfo.Success
44-
4542
L = ichol.matrixL()
46-
scaling = ichol.scalingS()
43+
S_diag = ichol.scalingS()
4744
perm = ichol.permutationP()
45+
P = perm.toDenseMatrix()
46+
4847
assert isinstance(L, csc_matrix)
49-
assert isinstance(scaling, np.ndarray)
48+
assert isinstance(S_diag, np.ndarray)
5049
assert L.shape == (dim, dim)
51-
assert scaling.shape == (dim,)
50+
assert S_diag.shape == (dim,)
51+
52+
L_dense = L.toarray()
53+
upper_part = np.triu(L_dense, k=1)
54+
assert np.allclose(upper_part, 0, atol=1e-12)
55+
56+
assert np.all(S_diag > 0)
57+
58+
S = csc_matrix((S_diag, (range(dim), range(dim))), shape=(dim, dim))
59+
60+
PA = P @ A
61+
PAP = PA @ P.T
62+
SPAP = S @ PAP
63+
SPAPS = SPAP @ S
64+
65+
LLT = L @ L.T
66+
67+
diff = SPAPS - LLT
68+
relative_error = np.linalg.norm(diff.data) / np.linalg.norm(SPAPS.data)
69+
assert relative_error < 0.5

tests/test_iterative_solvers.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,50 @@
66
rng = np.random.default_rng()
77
MAX_ITER = 8000
88

9-
_clazzes = [
9+
_classes = [
1010
nanoeigenpy.solvers.ConjugateGradient,
1111
nanoeigenpy.solvers.IdentityConjugateGradient,
1212
nanoeigenpy.solvers.LeastSquaresConjugateGradient,
1313
nanoeigenpy.solvers.IdentityLeastSquaresConjugateGradient,
1414
nanoeigenpy.solvers.DiagonalLeastSquaresConjugateGradient,
1515
nanoeigenpy.solvers.MINRES,
16-
# nanoeigenpy.solvers.BiCGSTAB,
1716
nanoeigenpy.solvers.IdentityBiCGSTAB,
1817
]
1918

19+
_classes_bis = [
20+
nanoeigenpy.solvers.DiagonalMINRES,
21+
nanoeigenpy.solvers.BiCGSTAB,
22+
]
23+
2024

21-
@pytest.mark.parametrize("cls", _clazzes)
25+
@pytest.mark.parametrize("cls", _classes)
2226
def test_solver(cls):
2327
Q = rng.standard_normal((dim, dim))
2428
A = 0.5 * (Q.T + Q)
2529
solver = cls(A)
2630
solver.setMaxIterations(MAX_ITER)
2731

28-
# Vector rhs
29-
3032
x = rng.random(dim)
3133
b = A.dot(x)
3234
x_est = solver.solve(b)
3335

36+
assert solver.info() == nanoeigenpy.ComputationInfo.Success
3437
assert nanoeigenpy.is_approx(b, A.dot(x_est), 1e-6)
3538

36-
# Matrix rhs
37-
3839
X = rng.random((dim, 20))
3940
B = A.dot(X)
4041
X_est = solver.solve(B)
4142

4243
assert nanoeigenpy.is_approx(B, A.dot(X_est), 1e-6)
4344

4445

45-
@pytest.mark.parametrize("cls", _clazzes)
46+
@pytest.mark.parametrize("cls", _classes)
4647
def test_solver_with_guess(cls):
4748
Q = rng.standard_normal((dim, dim))
4849
A = 0.5 * (Q.T + Q)
4950
solver = cls(A)
5051
solver.setMaxIterations(MAX_ITER)
5152

52-
# With guess
53-
# Vector rhs
54-
5553
x = rng.random(dim)
5654
b = A.dot(x)
5755
x_est = solver.solveWithGuess(b, x + 0.01)
@@ -60,7 +58,49 @@ def test_solver_with_guess(cls):
6058
assert nanoeigenpy.is_approx(x, x_est, 1e-6)
6159
assert nanoeigenpy.is_approx(b, A.dot(x_est), 1e-6)
6260

63-
# Matrix rhs
61+
X = rng.random((dim, 20))
62+
B = A.dot(X)
63+
X_est = solver.solveWithGuess(B, X + 0.01)
64+
65+
assert nanoeigenpy.is_approx(X, X_est, 1e-6)
66+
assert nanoeigenpy.is_approx(B, A.dot(X_est), 1e-6)
67+
68+
69+
@pytest.mark.parametrize("cls", _classes_bis)
70+
def test_solver_bis(cls):
71+
Q = rng.standard_normal((dim, dim))
72+
A = Q.T @ Q + np.eye(dim) * 0.1
73+
solver = cls(A)
74+
solver.setMaxIterations(MAX_ITER)
75+
76+
x = rng.random(dim)
77+
b = A.dot(x)
78+
x_est = solver.solve(b)
79+
80+
assert solver.info() == nanoeigenpy.ComputationInfo.Success
81+
assert nanoeigenpy.is_approx(b, A.dot(x_est), 1e-6)
82+
83+
X = rng.random((dim, 20))
84+
B = A.dot(X)
85+
X_est = solver.solve(B)
86+
87+
assert nanoeigenpy.is_approx(B, A.dot(X_est), 1e-6)
88+
89+
90+
@pytest.mark.parametrize("cls", _classes_bis)
91+
def test_solver_with_guess_bis(cls):
92+
Q = rng.standard_normal((dim, dim))
93+
A = Q.T @ Q + np.eye(dim) * 0.1
94+
solver = cls(A)
95+
solver.setMaxIterations(MAX_ITER)
96+
97+
x = rng.random(dim)
98+
b = A.dot(x)
99+
x_est = solver.solveWithGuess(b, x + 0.01)
100+
101+
assert solver.info() == nanoeigenpy.ComputationInfo.Success
102+
assert nanoeigenpy.is_approx(x, x_est, 1e-6)
103+
assert nanoeigenpy.is_approx(b, A.dot(x_est), 1e-6)
64104

65105
X = rng.random((dim, 20))
66106
B = A.dot(X)

0 commit comments

Comments
 (0)