Coverage for gpaw/test/parallel/test_scalapack.py: 96%
112 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
1"""Test of ScaLAPACK diagonalize and inverse cholesky.
3The test generates a random symmetric matrix H0 and
4positive definite matrix S0 on a 1-by-1 BLACS grid. They
5are redistributed to a mprocs-by-nprocs BLACS grid,
6diagonalized in parallel, and eigenvalues are compared
7against LAPACK. Eigenvectors are not compared.
8"""
10import pytest
11import numpy as np
12from scipy.linalg import eigh, inv, cholesky
14from gpaw.mpi import world, rank
15from gpaw.blacs import BlacsGrid, Redistributor
16from gpaw.utilities.tools import tri2full
17from gpaw.utilities import compiled_with_sl
18from gpaw.utilities.blas import rk
19from gpaw.utilities.scalapack import \
20 scalapack_general_diagonalize_dc, \
21 scalapack_diagonalize_dc, \
22 scalapack_inverse_cholesky, \
23 scalapack_inverse, \
24 scalapack_solve
26from .test_pblas import \
27 initialize_random, initialize_matrix, \
28 calculate_error, mnprocs_i
31pytestmark = pytest.mark.skipif(not compiled_with_sl(),
32 reason='not compiled with scalapack')
35tol = 1.0e-8
38@pytest.mark.parametrize('mprocs, nprocs', mnprocs_i)
39@pytest.mark.parametrize('dtype', [float, complex])
40def test_scalapack_diagonalize_inverse(dtype, mprocs, nprocs,
41 N=72, seed=42):
42 gen = np.random.RandomState(seed)
43 grid = BlacsGrid(world, mprocs, nprocs)
45 if dtype == complex:
46 epsilon = 1.0j
47 else:
48 epsilon = 0.0
50 # Create descriptors for matrices on master:
51 glob = grid.new_descriptor(N, N, N, N)
53 # print globA.asarray()
54 # Populate matrices local to master:
55 H0 = glob.zeros(dtype=dtype) + gen.rand(*glob.shape)
56 S0 = glob.zeros(dtype=dtype) + gen.rand(*glob.shape)
57 C0 = glob.empty(dtype=dtype)
58 if rank == 0:
59 # Complex case must have real numbers on the diagonal.
60 # We make a simple complex Hermitian matrix below.
61 H0 = H0 + epsilon * (0.1 * np.tri(N, N, k=-N // nprocs) +
62 0.3 * np.tri(N, N, k=-1))
63 S0 = S0 + epsilon * (0.2 * np.tri(N, N, k=-N // nprocs) +
64 0.4 * np.tri(N, N, k=-1))
65 # Make matrices symmetric
66 rk(1.0, H0.copy(), 0.0, H0)
67 rk(1.0, S0.copy(), 0.0, S0)
68 # Overlap matrix must be semi-positive definite
69 S0 = S0 + 50.0 * np.eye(N, N, 0)
70 # Hamiltonian is usually diagonally dominant
71 H0 = H0 + 75.0 * np.eye(N, N, 0)
72 C0 = S0.copy()
73 S0_inv = S0.copy()
75 # Local result matrices
76 W0 = np.empty((N), dtype=float)
77 W0_g = np.empty((N), dtype=float)
79 # Calculate eigenvalues / other serial results
80 print(rank, C0.shape, C0.dtype)
81 if rank == 0:
82 W0 = eigh(H0, eigvals_only=True)
83 W0_g = eigh(H0, S0, eigvals_only=True)
84 C0 = inv(cholesky(C0, lower=True)).copy() # result in lower triangle
85 tri2full(S0_inv, 'L')
86 S0_inv = inv(S0_inv)
87 # tri2full(C0) # symmetrize
89 print(rank, C0.shape, C0.dtype)
90 assert glob.check(H0)
91 assert glob.check(S0)
92 assert glob.check(C0)
94 # Create distributed destriptors with various block sizes:
95 dist = grid.new_descriptor(N, N, 8, 8)
97 # Distributed matrices:
98 # We can use empty here, but end up with garbage on
99 # on the other half of the triangle when we redistribute.
100 # This is fine because ScaLAPACK does not care.
102 H = dist.empty(dtype=dtype)
103 S = dist.empty(dtype=dtype)
104 Sinv = dist.empty(dtype=dtype)
105 Z = dist.empty(dtype=dtype)
106 C = dist.empty(dtype=dtype)
107 Sinv = dist.empty(dtype=dtype)
109 # Eigenvalues are non-BLACS matrices
110 # W = np.empty((N), dtype=float)
111 W_dc = np.empty((N), dtype=float)
112 # W_mr3 = np.empty((N), dtype=float)
113 # W_g = np.empty((N), dtype=float)
114 W_g_dc = np.empty((N), dtype=float)
115 # W_g_mr3 = np.empty((N), dtype=float)
117 Glob2dist = Redistributor(world, glob, dist)
118 Glob2dist.redistribute(H0, H, uplo='L')
119 Glob2dist.redistribute(S0, S, uplo='L')
120 Glob2dist.redistribute(S0, C, uplo='L') # C0 was previously overwritten
121 Glob2dist.redistribute(S0, Sinv, uplo='L')
123 # we don't test the expert drivers anymore since there
124 # might be a buffer overflow error
125 # scalapack_diagonalize_ex(dist, H.copy(), Z, W, 'L')
126 scalapack_diagonalize_dc(dist, H.copy(), Z, W_dc, 'L')
127 # scalapack_diagonalize_mr3(dist, H.copy(), Z, W_mr3, 'L')
128 # scalapack_general_diagonalize_ex(dist, H.copy(), S.copy(), Z, W_g, 'L')
129 scalapack_general_diagonalize_dc(dist, H.copy(), S.copy(), Z, W_g_dc, 'L')
131 scalapack_inverse_cholesky(dist, C, 'L')
133 if dtype == complex: # Only supported for complex for now
134 scalapack_inverse(dist, Sinv, 'L')
135 # Undo redistribute
136 C_test = glob.empty(dtype=dtype)
137 Sinv_test = glob.empty(dtype=dtype)
138 Dist2glob = Redistributor(world, dist, glob)
139 Dist2glob.redistribute(C, C_test)
140 Dist2glob.redistribute(Sinv, Sinv_test)
142 if rank == 0:
143 diag_dc_err = abs(W_dc - W0).max()
144 # diag_mr3_err = abs(W_mr3 - W0).max()
145 # general_diag_ex_err = abs(W_g - W0_g).max()
146 general_diag_dc_err = abs(W_g_dc - W0_g).max()
147 # general_diag_mr3_err = abs(W_g_mr3 - W0_g).max()
148 print(C_test[:2, :2])
149 print(C0[:2, :2])
150 inverse_chol_err = abs(C_test - C0).max()
152 tri2full(Sinv_test, 'L')
153 inverse_err = abs(Sinv_test - S0_inv).max()
154 # print 'diagonalize ex err', diag_ex_err
155 print('diagonalize dc err', diag_dc_err)
156 # print 'diagonalize mr3 err', diag_mr3_err
157 # print 'general diagonalize ex err', general_diag_ex_err
158 print('general diagonalize dc err', general_diag_dc_err)
159 # print 'general diagonalize mr3 err', general_diag_mr3_err
160 print('inverse chol err', inverse_chol_err)
161 if dtype == complex:
162 print('inverse err', inverse_err)
163 else:
164 # diag_ex_err = 0.0
165 diag_dc_err = 0.0
166 # diag_mr3_err = 0.0
167 # general_diag_ex_err = 0.0
168 general_diag_dc_err = 0.0
169 # general_diag_mr3_err = 0.0
170 inverse_chol_err = 0.0
171 inverse_err = 0.0
173 # We don't like exceptions on only one cpu
174 # diag_ex_err = world.sum(diag_ex_err)
175 diag_dc_err = world.sum_scalar(diag_dc_err)
176 # diag_mr3_err = world.sum(diag_mr3_err)
177 # general_diag_ex_err = world.sum(general_diag_ex_err)
178 general_diag_dc_err = world.sum_scalar(general_diag_dc_err)
179 # general_diag_mr3_err = world.sum(general_diag_mr3_err)
180 inverse_chol_err = world.sum_scalar(inverse_chol_err)
181 inverse_err = world.sum_scalar(inverse_err)
182 # assert diag_ex_err < tol
183 assert diag_dc_err < tol
184 # assert diag_mr3_err < tol
185 # assert general_diag_ex_err < tol
186 assert general_diag_dc_err < tol
187 # assert general_diag_mr3_err < tol
188 assert inverse_chol_err < tol
189 if dtype == complex:
190 assert inverse_err < tol
193@pytest.mark.parametrize('mprocs, nprocs', mnprocs_i)
194@pytest.mark.parametrize('dtype', [float, complex])
195def test_scalapack_solve(dtype, mprocs, nprocs,
196 M=160, K=120, seed=42):
197 """Test scalapack_solve()"""
198 random = initialize_random(seed, dtype)
199 grid = BlacsGrid(world, mprocs, nprocs)
201 # Initialize matrices
202 shapeA = (M, M)
203 shapeB = (K, M)
204 A0, A, descA = initialize_matrix(grid, *shapeA, 2, 2, random)
205 B0, B, descB = initialize_matrix(grid, *shapeB, 3, 2, random)
207 if grid.comm.rank == 0:
208 # Calculate reference with numpy
209 ref_B0 = np.linalg.solve(A0.T, B0.T).T
210 else:
211 ref_B0 = None
213 # Calculate with scalapack
214 scalapack_solve(descA, descB, A, B)
216 # Check error
217 err = calculate_error(ref_B0, B, descB)
218 tol = {float: 8e-12, complex: 2e-13}[dtype]
219 assert err < tol