Skip to content

Commit a488645

Browse files
committed
Add float, MPI, and GPU test coverage for PPCG
Bring PPCG to the same test coverage level as CG/Davidson/BPCG: - diago_ppcg_float_test.cpp: single-precision (complex<float>) unit tests for BLOCK_SUBSPACE and CONJUGATE_GRADIENT, covering the float instantiation. - diago_ppcg_parallel_test.cpp + .sh: MPI parallel test that distributes a diagonal matrix across processes and exercises the pooled reduce path. - tests/11_PW_GPU/scf_ppcg: GPU integration case (device gpu + ks_solver ppcg) with reference, registered in CASES_GPU.txt.
1 parent 7f14761 commit a488645

11 files changed

Lines changed: 539 additions & 0 deletions

File tree

source/source_hsolver/test/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,23 @@ AddTest(
127127
LIBS ${math_libs} base device container
128128
SOURCES diago_ppcg_test.cpp ../diago_ppcg.cpp
129129
)
130+
AddTest(
131+
TARGET MODULE_HSOLVER_ppcg_float
132+
LIBS ${math_libs} base device container
133+
SOURCES diago_ppcg_float_test.cpp ../diago_ppcg.cpp
134+
)
130135

131136
if (ENABLE_MPI)
132137
AddTest(
133138
TARGET MODULE_HSOLVER_compare
134139
LIBS parameter base psi device container
135140
SOURCES diago_compare_test.cpp ../diago_cg.cpp ../diago_bpcg.cpp ../diago_david.cpp ../diago_ppcg.cpp ../diago_iter_assist.cpp ../diag_const_nums.cpp ../para_lin_tf.cpp ../../source_basis/module_pw/test/test_tool.cpp
136141
)
142+
AddTest(
143+
TARGET MODULE_HSOLVER_ppcg_parallel
144+
LIBS parameter base psi device container
145+
SOURCES diago_ppcg_parallel_test.cpp ../diago_ppcg.cpp ../../source_basis/module_pw/test/test_tool.cpp
146+
)
137147
endif()
138148

139149
install(FILES H-KPoints-Si2.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
@@ -153,6 +163,7 @@ install(FILES KPoints-Si64-Solution.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
153163
install(FILES diago_cg_parallel_test.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
154164
install(FILES diago_david_parallel_test.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
155165
install(FILES diago_lcao_parallel_test.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
166+
install(FILES diago_ppcg_parallel_test.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
156167

157168
install(FILES PEXSI-H-GammaOnly-Si2.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
158169
install(FILES PEXSI-S-GammaOnly-Si2.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
@@ -212,6 +223,10 @@ if (ENABLE_MPI)
212223
COMMAND ${BASH} diago_david_parallel_test.sh
213224
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
214225
)
226+
add_test(NAME MODULE_HSOLVER_ppcg_parallel_test
227+
COMMAND ${BASH} diago_ppcg_parallel_test.sh
228+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
229+
)
215230
if(ENABLE_LCAO)
216231
add_test(NAME MODULE_HSOLVER_LCAO_parallel
217232
COMMAND ${BASH} diago_lcao_parallel_test.sh
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
/**
2+
* diago_ppcg_float_test.cpp — single-precision unit test for DiagoPPCG.
3+
*
4+
* Exercises the std::complex<float> instantiation of the BLOCK_SUBSPACE and
5+
* CONJUGATE_GRADIENT strategies on dense matrices with analytical reference
6+
* eigenvalues. Tolerances are looser than the double-precision suite because
7+
* single precision has roughly 7 significant digits.
8+
*/
9+
10+
#include "../diago_ppcg.h"
11+
12+
#include <gtest/gtest.h>
13+
#include <cmath>
14+
#include <complex>
15+
#include <limits>
16+
#include <random>
17+
#include <vector>
18+
19+
#ifndef M_PI
20+
#define M_PI 3.14159265358979323846
21+
#endif
22+
23+
using T = std::complex<float>;
24+
using Real = float;
25+
26+
// -----------------------------------------------------------------------------
27+
// Helper: dense H-matrix times a set of column vectors (column-major H).
28+
// -----------------------------------------------------------------------------
29+
static void dense_h_multiply(const T* H_data, int n_dim,
30+
const T* in, T* out, int ld, int ncol)
31+
{
32+
for (int j = 0; j < ncol; ++j)
33+
{
34+
for (int i = 0; i < n_dim; ++i)
35+
{
36+
T sum = T(0.0f, 0.0f);
37+
for (int k = 0; k < n_dim; ++k)
38+
{
39+
sum += H_data[i + k * n_dim] * in[k + j * ld];
40+
}
41+
out[i + j * ld] = sum;
42+
}
43+
}
44+
}
45+
46+
// Orthonormalize columns of psi in-place (S = I).
47+
static void gram_schmidt(std::vector<T>& psi, int ld, int n_dim, int nband)
48+
{
49+
for (int j = 0; j < nband; ++j)
50+
{
51+
for (int k = 0; k < j; ++k)
52+
{
53+
T dot = T(0.0f, 0.0f);
54+
for (int i = 0; i < n_dim; ++i)
55+
{
56+
dot += std::conj(psi[i + k * ld]) * psi[i + j * ld];
57+
}
58+
for (int i = 0; i < n_dim; ++i)
59+
{
60+
psi[i + j * ld] -= dot * psi[i + k * ld];
61+
}
62+
}
63+
Real nrm = 0.0f;
64+
for (int i = 0; i < n_dim; ++i)
65+
{
66+
nrm += std::norm(psi[i + j * ld]);
67+
}
68+
nrm = std::sqrt(nrm);
69+
for (int i = 0; i < n_dim; ++i)
70+
{
71+
psi[i + j * ld] /= nrm;
72+
}
73+
}
74+
}
75+
76+
// -----------------------------------------------------------------------------
77+
// Diagonal matrix: H = diag(1, 2, 3, 4, 5)
78+
// -----------------------------------------------------------------------------
79+
TEST(DiagoPPCGFloatTest, DiagonalBlockSubspace)
80+
{
81+
const int n_dim = 5;
82+
const int nband = 3;
83+
const int ld = n_dim;
84+
85+
std::vector<T> H_mat(n_dim * n_dim, T(0.0f, 0.0f));
86+
for (int i = 0; i < n_dim; ++i)
87+
{
88+
H_mat[i + i * n_dim] = T(Real(i + 1), 0.0f);
89+
}
90+
91+
std::vector<Real> prec(n_dim);
92+
for (int i = 0; i < n_dim; ++i)
93+
{
94+
prec[i] = Real(i + 1);
95+
}
96+
97+
const Real exact[3] = {1.0f, 2.0f, 3.0f};
98+
std::vector<double> ethr(nband, 1e-4);
99+
100+
std::mt19937 rng(42);
101+
std::uniform_real_distribution<Real> dist(-1.0f, 1.0f);
102+
std::vector<T> psi(ld * nband, T(0.0f, 0.0f));
103+
for (int j = 0; j < nband; ++j)
104+
{
105+
for (int i = 0; i < n_dim; ++i)
106+
{
107+
psi[i + j * ld] = T(dist(rng), 0.0f);
108+
}
109+
}
110+
gram_schmidt(psi, ld, n_dim, nband);
111+
112+
std::vector<T> psi_run = psi;
113+
std::vector<Real> eval(nband, 0.0f);
114+
115+
hsolver::DiagoPPCG<T, hsolver::base_device::DEVICE_CPU> solver(
116+
/* diag_thr = */ 1e-5f,
117+
/* max_iter = */ 100,
118+
/* sbsize = */ 3,
119+
/* rr_step = */ 3,
120+
/* gamma_g0 = */ false,
121+
hsolver::PpcgStrategy::BLOCK_SUBSPACE);
122+
123+
auto h_op = [&](T* in, T* out, int ld_in, int ncol) {
124+
dense_h_multiply(H_mat.data(), n_dim, in, out, ld_in, ncol);
125+
};
126+
127+
double avg_iter = solver.diag(h_op, nullptr, ld, nband, n_dim,
128+
psi_run.data(), eval.data(), ethr, prec.data());
129+
130+
for (int i = 0; i < nband; ++i)
131+
{
132+
EXPECT_NEAR(double(eval[i]), double(exact[i]), 1e-4)
133+
<< "Diagonal float BLOCK: eigenvalue[" << i << "] mismatch";
134+
}
135+
EXPECT_LE(avg_iter, 100.0) << "Diagonal float BLOCK: too many iterations";
136+
}
137+
138+
// -----------------------------------------------------------------------------
139+
// Tridiagonal Laplacian: H[i,i]=2, H[i,i±1]=-1, exact λ_k = 2 - 2cos(kπ/(n+1))
140+
// -----------------------------------------------------------------------------
141+
TEST(DiagoPPCGFloatTest, TridiagonalBlockSubspace)
142+
{
143+
const int n_dim = 10;
144+
const int nband = 3;
145+
const int ld = n_dim;
146+
147+
std::vector<T> H_mat(n_dim * n_dim, T(0.0f, 0.0f));
148+
for (int i = 0; i < n_dim; ++i)
149+
{
150+
H_mat[i + i * n_dim] = T(2.0f, 0.0f);
151+
if (i > 0)
152+
{
153+
H_mat[i + (i - 1) * n_dim] = T(-1.0f, 0.0f);
154+
}
155+
if (i < n_dim - 1)
156+
{
157+
H_mat[i + (i + 1) * n_dim] = T(-1.0f, 0.0f);
158+
}
159+
}
160+
161+
std::vector<Real> prec(n_dim, 2.0f);
162+
std::vector<Real> exact(nband);
163+
for (int k = 0; k < nband; ++k)
164+
{
165+
exact[k] = 2.0f - 2.0f * std::cos(Real(k + 1) * M_PI
166+
/ Real(n_dim + 1));
167+
}
168+
std::vector<double> ethr(nband, 1e-4);
169+
170+
std::mt19937 rng(42);
171+
std::uniform_real_distribution<Real> dist(-1.0f, 1.0f);
172+
std::vector<T> psi(ld * nband, T(0.0f, 0.0f));
173+
for (int j = 0; j < nband; ++j)
174+
{
175+
for (int i = 0; i < n_dim; ++i)
176+
{
177+
psi[i + j * ld] = T(dist(rng), 0.0f);
178+
}
179+
}
180+
gram_schmidt(psi, ld, n_dim, nband);
181+
182+
std::vector<T> psi_run = psi;
183+
std::vector<Real> eval(nband, 0.0f);
184+
185+
hsolver::DiagoPPCG<T, hsolver::base_device::DEVICE_CPU> solver(
186+
/* diag_thr = */ 1e-5f,
187+
/* max_iter = */ 100,
188+
/* sbsize = */ 4,
189+
/* rr_step = */ 4,
190+
/* gamma_g0 = */ false,
191+
hsolver::PpcgStrategy::BLOCK_SUBSPACE);
192+
193+
auto h_op = [&](T* in, T* out, int ld_in, int ncol) {
194+
dense_h_multiply(H_mat.data(), n_dim, in, out, ld_in, ncol);
195+
};
196+
197+
double avg_iter = solver.diag(h_op, nullptr, ld, nband, n_dim,
198+
psi_run.data(), eval.data(), ethr, prec.data());
199+
200+
for (int i = 0; i < nband; ++i)
201+
{
202+
EXPECT_NEAR(double(eval[i]), double(exact[i]), 1e-4)
203+
<< "Tridiagonal float BLOCK: eigenvalue[" << i << "] mismatch";
204+
}
205+
EXPECT_LE(avg_iter, 100.0) << "Tridiagonal float BLOCK: too many iterations";
206+
}
207+
208+
// -----------------------------------------------------------------------------
209+
// CONJUGATE_GRADIENT fallback strategy on the diagonal matrix.
210+
// -----------------------------------------------------------------------------
211+
TEST(DiagoPPCGFloatTest, ConjugateGradientFallback)
212+
{
213+
const int n_dim = 5;
214+
const int nband = 3;
215+
const int ld = n_dim;
216+
217+
std::vector<T> H_mat(n_dim * n_dim, T(0.0f, 0.0f));
218+
for (int i = 0; i < n_dim; ++i)
219+
{
220+
H_mat[i + i * n_dim] = T(Real(i + 1), 0.0f);
221+
}
222+
223+
std::vector<Real> prec(n_dim);
224+
for (int i = 0; i < n_dim; ++i)
225+
{
226+
prec[i] = Real(i + 1);
227+
}
228+
229+
const Real exact[3] = {1.0f, 2.0f, 3.0f};
230+
std::vector<double> ethr(nband, 1e-4);
231+
232+
std::mt19937 rng(42);
233+
std::uniform_real_distribution<Real> dist(-1.0f, 1.0f);
234+
std::vector<T> psi(ld * nband, T(0.0f, 0.0f));
235+
for (int j = 0; j < nband; ++j)
236+
{
237+
for (int i = 0; i < n_dim; ++i)
238+
{
239+
psi[i + j * ld] = T(dist(rng), 0.0f);
240+
}
241+
}
242+
gram_schmidt(psi, ld, n_dim, nband);
243+
244+
std::vector<T> psi_run = psi;
245+
std::vector<Real> eval(nband, 0.0f);
246+
247+
hsolver::DiagoPPCG<T, hsolver::base_device::DEVICE_CPU> solver(
248+
/* diag_thr = */ 1e-5f,
249+
/* max_iter = */ 200,
250+
/* sbsize = */ 3,
251+
/* rr_step = */ 3,
252+
/* gamma_g0 = */ false,
253+
hsolver::PpcgStrategy::CONJUGATE_GRADIENT);
254+
255+
auto h_op = [&](T* in, T* out, int ld_in, int ncol) {
256+
dense_h_multiply(H_mat.data(), n_dim, in, out, ld_in, ncol);
257+
};
258+
259+
double avg_iter = solver.diag(h_op, nullptr, ld, nband, n_dim,
260+
psi_run.data(), eval.data(), ethr, prec.data());
261+
262+
for (int i = 0; i < nband; ++i)
263+
{
264+
EXPECT_NEAR(double(eval[i]), double(exact[i]), 1e-4)
265+
<< "Diagonal float CG: eigenvalue[" << i << "] mismatch";
266+
}
267+
EXPECT_LE(avg_iter, 200.0) << "Diagonal float CG: too many iterations";
268+
}
269+
270+
// -----------------------------------------------------------------------------
271+
// Non-finite input validation (throws).
272+
// -----------------------------------------------------------------------------
273+
TEST(DiagoPPCGFloatTest, NonFiniteInputThrows)
274+
{
275+
const int n_dim = 5;
276+
const int nband = 3;
277+
const int ld = n_dim;
278+
279+
std::vector<T> H_mat(n_dim * n_dim, T(0.0f, 0.0f));
280+
for (int i = 0; i < n_dim; ++i)
281+
{
282+
H_mat[i + i * n_dim] = T(Real(i + 1), 0.0f);
283+
}
284+
285+
std::vector<Real> prec(n_dim, 1.0f);
286+
std::vector<T> psi(ld * nband, T(1.0f, 0.0f));
287+
std::vector<Real> eval(nband, 0.0f);
288+
std::vector<double> ethr(nband, 1e-4);
289+
290+
hsolver::DiagoPPCG<T, hsolver::base_device::DEVICE_CPU> solver(
291+
/* diag_thr = */ 1e-5f, 100, 3, 3, false,
292+
hsolver::PpcgStrategy::BLOCK_SUBSPACE);
293+
294+
auto h_op = [&](T* in, T* out, int ld_in, int ncol) {
295+
dense_h_multiply(H_mat.data(), n_dim, in, out, ld_in, ncol);
296+
};
297+
298+
std::vector<double> bad_ethr = ethr;
299+
bad_ethr[0] = std::numeric_limits<double>::quiet_NaN();
300+
EXPECT_THROW(solver.diag(h_op, nullptr, ld, nband, n_dim,
301+
psi.data(), eval.data(), bad_ethr, prec.data()),
302+
std::invalid_argument);
303+
304+
std::vector<Real> bad_prec = prec;
305+
bad_prec[0] = std::numeric_limits<Real>::quiet_NaN();
306+
EXPECT_THROW(solver.diag(h_op, nullptr, ld, nband, n_dim,
307+
psi.data(), eval.data(), ethr, bad_prec.data()),
308+
std::invalid_argument);
309+
}

0 commit comments

Comments
 (0)