Skip to content

Commit a29afee

Browse files
authored
Feature: Add davidson function to pyabacus (#5112)
* add PyDiagoDavid class * add pybind11 wrapper for `hsolver::DiagoDavid` * add `PyDiagoDavid` class as the pybind wrapper for `hsolver::DiagoDavid` class * add python function signature and python tests * fix some bugs in pytest:test_hsolver.py * Add example for diagonalizing sparse matrices using Davidson method and update README.md with relevant documentation * update example for hsolver and README.md * Revert unintended changes to file
1 parent 6424f68 commit a29afee

File tree

8 files changed

+404
-52
lines changed

8 files changed

+404
-52
lines changed

python/pyabacus/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ add_library(naopack SHARED
106106
# add diago shared library
107107
list(APPEND _diago
108108
${HSOLVER_PATH}/diago_dav_subspace.cpp
109+
${HSOLVER_PATH}/diago_david.cpp
109110
${HSOLVER_PATH}/diag_const_nums.cpp
110111
${HSOLVER_PATH}/diago_iter_assist.cpp
111112

@@ -151,7 +152,7 @@ list(APPEND _sources
151152
${PROJECT_SOURCE_DIR}/src/py_abacus.cpp
152153
${PROJECT_SOURCE_DIR}/src/py_base_math.cpp
153154
${PROJECT_SOURCE_DIR}/src/py_m_nao.cpp
154-
${PROJECT_SOURCE_DIR}/src/py_diago_dav_subspace.cpp
155+
${PROJECT_SOURCE_DIR}/src/py_hsolver.cpp
155156
)
156157
pybind11_add_module(_core MODULE ${_sources})
157158
target_link_libraries(_core PRIVATE pybind11::headers naopack diagopack)

python/pyabacus/README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,29 @@ Run `python diago_matrix.py` in `examples` to check the diagonalization of a mat
4646
```shell
4747
$ cd examples/
4848
$ python diago_matrix.py
49-
eigenvalues calculated by pyabacus: [-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950155 1.11142051 1.12462152]
50-
eigenvalues calculated by scipy: [-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950154 1.11142051 1.12462151]
51-
error: [9.26164700e-12 2.42959514e-10 2.96529468e-11 7.77933273e-12 7.53686002e-12 2.95628810e-09 1.04678111e-09 7.79106313e-09]
49+
50+
====== Calculating eigenvalues using davidson method... ======
51+
eigenvalues calculated by pyabacus-davidson is:
52+
[-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950154
53+
1.11142053 1.12462153]
54+
eigenvalues calculated by scipy is:
55+
[-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950154
56+
1.11142051 1.12462151]
57+
eigenvalues difference:
58+
[4.47258897e-12 5.67104697e-12 8.48299209e-12 1.08900666e-11
59+
1.87927451e-12 3.15688586e-10 2.11438165e-08 2.68884972e-08]
60+
61+
====== Calculating eigenvalues using dav_subspace method... ======
62+
enter diag... is_subspace = 0, ntry = 0
63+
eigenvalues calculated by pyabacus-dav_subspace is:
64+
[-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950154
65+
1.11142051 1.12462153]
66+
eigenvalues calculated by scipy is:
67+
[-0.38440611 0.24221155 0.31593272 0.53144616 0.85155108 1.06950154
68+
1.11142051 1.12462151]
69+
eigenvalues difference:
70+
[ 4.64694949e-12 2.14706031e-12 1.09236509e-11 4.66293670e-13
71+
-8.94295749e-12 4.71351846e-11 5.39378986e-10 1.97244101e-08]
5272
```
5373

5474
License

python/pyabacus/examples/diago_matrix.py

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,61 @@
22
import numpy as np
33
import scipy
44

5-
h_mat = scipy.io.loadmat('./Si2.mat')['Problem']['A'][0, 0]
6-
7-
nbasis = h_mat.shape[0]
8-
nband = 8
9-
10-
v0 = np.random.rand(nbasis, nband)
11-
12-
diag_elem = h_mat.diagonal()
13-
diag_elem = np.where(np.abs(diag_elem) < 1e-8, 1e-8, diag_elem)
14-
precond = 1.0 / np.abs(diag_elem)
15-
16-
17-
def mm_op(x):
18-
return h_mat.dot(x)
19-
20-
e, v = hsolver.dav_subspace(
21-
mm_op,
22-
v0,
23-
nbasis,
24-
nband,
25-
precond,
26-
dav_ndim=8,
27-
tol=1e-8,
28-
max_iter=1000,
29-
scf_type=False
30-
)
31-
32-
print('eigenvalues calculated by pyabacus: ', e)
33-
34-
e_scipy, v_scipy = scipy.sparse.linalg.eigsh(h_mat, k=nband, which='SA', maxiter=1000)
35-
e_scipy = np.sort(e_scipy)
36-
print('eigenvalues calculated by scipy: ', e_scipy)
37-
38-
print('error:', e - e_scipy)
5+
def load_mat(mat_file):
6+
h_mat = scipy.io.loadmat(mat_file)['Problem']['A'][0, 0]
7+
nbasis = h_mat.shape[0]
8+
nband = 8
9+
10+
return h_mat, nbasis, nband
11+
12+
def calc_eig_pyabacus(mat_file, method):
13+
algo = {
14+
'dav_subspace': hsolver.dav_subspace,
15+
'davidson': hsolver.davidson
16+
}
17+
18+
h_mat, nbasis, nband = load_mat(mat_file)
19+
20+
v0 = np.random.rand(nbasis, nband)
21+
diag_elem = h_mat.diagonal()
22+
diag_elem = np.where(np.abs(diag_elem) < 1e-8, 1e-8, diag_elem)
23+
precond = 1.0 / np.abs(diag_elem)
24+
25+
def mm_op(x):
26+
return h_mat.dot(x)
27+
28+
e, _ = algo[method](
29+
mm_op,
30+
v0,
31+
nbasis,
32+
nband,
33+
precond,
34+
dav_ndim=8,
35+
tol=1e-8,
36+
max_iter=1000
37+
)
38+
39+
print(f'eigenvalues calculated by pyabacus-{method} is: \n', e)
40+
41+
return e
42+
43+
def calc_eig_scipy(mat_file):
44+
h_mat, _, nband = load_mat(mat_file)
45+
e, _ = scipy.sparse.linalg.eigsh(h_mat, k=nband, which='SA', maxiter=1000)
46+
e = np.sort(e)
47+
print('eigenvalues calculated by scipy is: \n', e)
48+
49+
return e
50+
51+
if __name__ == '__main__':
52+
mat_file = './Si2.mat'
53+
method = ['davidson', 'dav_subspace']
54+
55+
for m in method:
56+
print(f'\n====== Calculating eigenvalues using {m} method... ======')
57+
e_pyabacus = calc_eig_pyabacus(mat_file, m)
58+
e_scipy = calc_eig_scipy(mat_file)
59+
60+
print('eigenvalues difference: \n', e_pyabacus - e_scipy)
61+
62+

python/pyabacus/src/py_abacus.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ namespace py = pybind11;
55

66
void bind_base_math(py::module& m);
77
void bind_m_nao(py::module& m);
8-
void bind_diago_dav_subspace(py::module& m);
8+
void bind_hsolver(py::module& m);
99

1010
PYBIND11_MODULE(_core, m)
1111
{
1212
m.doc() = "Python extension for ABACUS built with pybind11 and scikit-build.";
1313
bind_base_math(m);
1414
bind_m_nao(m);
15-
bind_diago_dav_subspace(m);
15+
bind_hsolver(m);
1616
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#ifndef PYTHON_PYABACUS_SRC_PY_DIAGO_DAVID_HPP
2+
#define PYTHON_PYABACUS_SRC_PY_DIAGO_DAVID_HPP
3+
4+
#include <complex>
5+
#include <functional>
6+
7+
#include <pybind11/pybind11.h>
8+
#include <pybind11/complex.h>
9+
#include <pybind11/functional.h>
10+
#include <pybind11/numpy.h>
11+
#include <pybind11/stl.h>
12+
13+
#include "module_hsolver/diago_david.h"
14+
15+
namespace py = pybind11;
16+
17+
namespace py_hsolver
18+
{
19+
20+
class PyDiagoDavid
21+
{
22+
public:
23+
PyDiagoDavid(int nbasis, int nband) : nbasis(nbasis), nband(nband)
24+
{
25+
psi = new std::complex<double>[nbasis * nband];
26+
eigenvalue = new double[nband];
27+
}
28+
29+
PyDiagoDavid(const PyDiagoDavid&) = delete;
30+
PyDiagoDavid& operator=(const PyDiagoDavid&) = delete;
31+
PyDiagoDavid(PyDiagoDavid&& other) : nbasis(other.nbasis), nband(other.nband)
32+
{
33+
psi = other.psi;
34+
eigenvalue = other.eigenvalue;
35+
36+
other.psi = nullptr;
37+
other.eigenvalue = nullptr;
38+
}
39+
40+
~PyDiagoDavid()
41+
{
42+
if (psi != nullptr)
43+
{
44+
delete[] psi;
45+
psi = nullptr;
46+
}
47+
if (eigenvalue != nullptr)
48+
{
49+
delete[] eigenvalue;
50+
eigenvalue = nullptr;
51+
}
52+
}
53+
54+
void set_psi(py::array_t<std::complex<double>> psi_in)
55+
{
56+
assert(psi_in.size() == nbasis * nband);
57+
58+
for (size_t i = 0; i < nbasis * nband; ++i)
59+
{
60+
psi[i] = psi_in.at(i);
61+
}
62+
}
63+
64+
py::array_t<std::complex<double>> get_psi()
65+
{
66+
py::array_t<std::complex<double>> psi_out(nband * nbasis);
67+
py::buffer_info psi_out_buf = psi_out.request();
68+
69+
std::complex<double>* psi_out_ptr = static_cast<std::complex<double>*>(psi_out_buf.ptr);
70+
71+
for (size_t i = 0; i < nband * nbasis; ++i)
72+
{
73+
psi_out_ptr[i] = psi[i];
74+
}
75+
76+
return psi_out;
77+
}
78+
79+
void init_eigenvalue()
80+
{
81+
for (size_t i = 0; i < nband; ++i)
82+
{
83+
eigenvalue[i] = 0.0;
84+
}
85+
}
86+
87+
py::array_t<double> get_eigenvalue()
88+
{
89+
py::array_t<double> eigenvalue_out(nband);
90+
py::buffer_info eigenvalue_out_buf = eigenvalue_out.request();
91+
92+
double* eigenvalue_out_ptr = static_cast<double*>(eigenvalue_out_buf.ptr);
93+
94+
for (size_t i = 0; i < nband; ++i)
95+
{
96+
eigenvalue_out_ptr[i] = eigenvalue[i];
97+
}
98+
99+
return eigenvalue_out;
100+
}
101+
102+
int diag(
103+
std::function<py::array_t<std::complex<double>>(py::array_t<std::complex<double>>)> mm_op,
104+
std::vector<double> precond_vec,
105+
int dav_ndim,
106+
double tol,
107+
int max_iter,
108+
bool use_paw,
109+
hsolver::diag_comm_info comm_info
110+
) {
111+
auto hpsi_func = [mm_op] (
112+
std::complex<double> *hpsi_out,
113+
std::complex<double> *psi_in,
114+
const int nband_in,
115+
const int nbasis_in,
116+
const int band_index1,
117+
const int band_index2
118+
) {
119+
// Note: numpy's py::array_t is row-major, but
120+
// our raw pointer-array is column-major
121+
py::array_t<std::complex<double>, py::array::f_style> psi({nbasis_in, band_index2 - band_index1 + 1});
122+
py::buffer_info psi_buf = psi.request();
123+
std::complex<double>* psi_ptr = static_cast<std::complex<double>*>(psi_buf.ptr);
124+
std::copy(psi_in + band_index1 * nbasis_in, psi_in + (band_index2 + 1) * nbasis_in, psi_ptr);
125+
126+
py::array_t<std::complex<double>, py::array::f_style> hpsi = mm_op(psi);
127+
128+
py::buffer_info hpsi_buf = hpsi.request();
129+
std::complex<double>* hpsi_ptr = static_cast<std::complex<double>*>(hpsi_buf.ptr);
130+
std::copy(hpsi_ptr, hpsi_ptr + (band_index2 - band_index1 + 1) * nbasis_in, hpsi_out);
131+
};
132+
133+
auto spsi_func = [this] (
134+
const std::complex<double> *psi_in,
135+
std::complex<double> *spsi_out,
136+
const int nrow,
137+
const int npw,
138+
const int nbands
139+
) {
140+
syncmem_op()(this->ctx, this->ctx, spsi_out, psi_in, static_cast<size_t>(nbands * nrow));
141+
};
142+
143+
obj = std::make_unique<hsolver::DiagoDavid<std::complex<double>, base_device::DEVICE_CPU>>(
144+
precond_vec.data(),
145+
nband,
146+
nbasis,
147+
dav_ndim,
148+
use_paw,
149+
comm_info
150+
);
151+
152+
return obj->diag(hpsi_func, spsi_func, nbasis, psi, eigenvalue, tol, max_iter);
153+
}
154+
155+
private:
156+
std::complex<double>* psi = nullptr;
157+
double* eigenvalue = nullptr;
158+
159+
int nbasis;
160+
int nband;
161+
162+
std::unique_ptr<hsolver::DiagoDavid<std::complex<double>, base_device::DEVICE_CPU>> obj;
163+
164+
base_device::DEVICE_CPU* ctx = {};
165+
using syncmem_op = base_device::memory::synchronize_memory_op<std::complex<double>, base_device::DEVICE_CPU, base_device::DEVICE_CPU>;
166+
};
167+
168+
} // namespace py_hsolver
169+
170+
#endif

0 commit comments

Comments
 (0)