Skip to content

Commit d0b25ea

Browse files
authored
Feature: Add diago_dav_subspace module to pyabacus (deepmodeling#4883)
* <Feature>[pyabacus]: Add diago_dav_subspace module to pyabacus This commit adds the `diago_dav_subspace` module to the `pyabacus` package, which is responsible for diagonalizing the Hamiltonian matrix using Davison subspace method. The current Pythonization version targets matrix diagonalization. The original algorithm does not require passing a matrix; instead, it uses a function pointer to pass a linear operator for diagonalization. So the current implementation still needs to wrap std::function<void(T*, T*, const int, const int, const int, const int)> to a Python callable type, which requires further iteration. * Add Python function and class signatures for ModuleBase and hsolver to improve readability and enhance type checking in Python * modify dav_subspace, now the module could work but the result is wrong * Fix memory issue in Diago_DavSubspace destructor The destructor of Diago_DavSubspace was causing a memory issue, preventing the program from running correctly. Lines 72-74 were previously commented out to avoid the issue, but this led to a memory leak. This commit addresses the root cause of the memory issue and ensures proper memory deallocation without causing a crash. - Uncommented lines 72-74 in the destructor. This ensures that all allocated memory is properly deallocated, preventing memory leaks but still unable to run the `pyabacus::hsolver::dav_subspace()` * add pbdoc to the pybind file and add document to the python func signatures * modify some docs * fixed the memory issue in dav_subspace, add an example(python/pyabacus/diago_matrix.py) * add pytest to diago_dav_subspace * delete an matrix * modify _hsolver.py * Refactor diagonalization to accept operators instead of explicit matrices Previously, our module could only accept explicit matrices for diagonalization. I have now modified the code to accept operators as parameters for diagonalization, eliminating the need to store the entire matrix explicitly. This allows us to compute eigenvalues for larger Hamiltonians using the sparse matrix storage provided by `SciPy`. The operator is a function pointer that accepts a vector and returns a vector. To diagonalize an operator A, we define a function or lambda function `mv_op` such that `mv_op(x) = Ax`, which can then be used in the diagonalization process. Additionally, I have added a new test case for the Hamiltonian matrix corresponding to H2O. The matrix size is `67024x67024`, which previously could not be stored explicitly due to memory constraints. The updated operator method now supports the computation of eigenvalues for such large matrices. Test cases have been updated to reflect these changes. * Refactor: Replace manual loop with std::copy for better readability and potential performance improvement - Replaced manual loop with `std::copy` to copy elements from `psi_in` to `psi` and from `hpsi_ptr` to `hpsi_out`. - This change simplifies the code, enhances readability and may improve performance due to optimized standard library `std::copy` implementations. * Refactor hpsi_func to support matrix-matrix multiplication operator and rename two variables' name - Modified `hpsi_func` to handle matrix-matrix multiplication instead of vector operations. - This vectorization significantly improved computation speed. - Renamed variables in the Python frontend interface: - `nbasis` to `dim` - `nband` to `num_eigs` - These changes align with the module's design as a pure mathematical interface. * Enhance test cases to improve precision requirements - Updated test cases to ensure absolute error is less than 1e-8. - Added a new test case for random sparse matrices to validate the implementation. * update README.md * update `README.md` and `pyproject.toml`
1 parent 3d44f1f commit d0b25ea

19 files changed

+730
-10
lines changed

python/pyabacus/CMakeLists.txt

+30-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ find_package(pybind11 CONFIG REQUIRED)
1111
set(ABACUS_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../source")
1212
set(BASE_PATH "${ABACUS_SOURCE_DIR}/module_base")
1313
set(NAO_PATH "${ABACUS_SOURCE_DIR}/module_basis/module_nao")
14+
set(HSOLVER_PATH "${ABACUS_SOURCE_DIR}/module_hsolver")
15+
set(HAMILT_PATH "${ABACUS_SOURCE_DIR}/module_hamilt_general")
16+
set(PSI_PATH "${ABACUS_SOURCE_DIR}/module_psi")
1417
set(ENABLE_LCAO ON)
1518
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../cmake")
1619

@@ -100,6 +103,29 @@ list(APPEND _naos
100103
add_library(naopack SHARED
101104
${_naos}
102105
)
106+
# add diago shared library
107+
list(APPEND _diago
108+
${HSOLVER_PATH}/diago_dav_subspace.cpp
109+
${HSOLVER_PATH}/diag_const_nums.cpp
110+
${HSOLVER_PATH}/diago_iter_assist.cpp
111+
112+
${HSOLVER_PATH}/kernels/dngvd_op.cpp
113+
${HSOLVER_PATH}/kernels/math_kernel_op.cpp
114+
# dependency
115+
${BASE_PATH}/module_device/device.cpp
116+
${BASE_PATH}/module_device/memory_op.cpp
117+
118+
${HAMILT_PATH}/operator.cpp
119+
${PSI_PATH}/psi.cpp
120+
)
121+
add_library(diagopack SHARED
122+
${_diago}
123+
)
124+
target_link_libraries(diagopack
125+
PRIVATE
126+
${OpenBLAS_LIBRARIES}
127+
${LAPACK_LIBRARIES}
128+
)
103129
# link math_libs
104130
if(MKLROOT)
105131
target_link_libraries(naopack
@@ -125,9 +151,10 @@ list(APPEND _sources
125151
${PROJECT_SOURCE_DIR}/src/py_abacus.cpp
126152
${PROJECT_SOURCE_DIR}/src/py_base_math.cpp
127153
${PROJECT_SOURCE_DIR}/src/py_m_nao.cpp
154+
${PROJECT_SOURCE_DIR}/src/py_diago_dav_subspace.cpp
128155
)
129156
pybind11_add_module(_core MODULE ${_sources})
130-
target_link_libraries(_core PRIVATE pybind11::headers naopack)
157+
target_link_libraries(_core PRIVATE pybind11::headers naopack diagopack)
131158
target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION})
132159
# set RPATH
133160
execute_process(
@@ -141,5 +168,7 @@ set(TARGET_PACK pyabacus)
141168
set(CMAKE_INSTALL_RPATH "${PYTHON_SITE_PACKAGES}/${TARGET_PACK}")
142169
set_target_properties(_core PROPERTIES INSTALL_RPATH "$ORIGIN")
143170
set_target_properties(naopack PROPERTIES INSTALL_RPATH "$ORIGIN")
171+
set_target_properties(diagopack PROPERTIES INSTALL_RPATH "$ORIGIN")
144172
install(TARGETS _core naopack DESTINATION ${TARGET_PACK})
173+
install(TARGETS _core diagopack DESTINATION ${TARGET_PACK})
145174

python/pyabacus/README.md

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
Build Example: TwoCenterIntegral Section in ABACUS
2-
==============
2+
==================================================
33

44
An example project built with [pybind11](https://github.com/pybind/pybind11)
55
and scikit-build-core. Python 3.7+ (see older commits for older versions of
66
Python).
77

8-
98
Installation
109
------------
1110

1211
- Create and activate a new conda env, e.g. `conda create -n myenv python=3.8 & conda activate myenv`.
1312
- Clone ABACUS main repository and `cd abacus-develop/python/pyabacus`.
1413
- Build pyabacus by `pip install -v .` or install test dependencies & build pyabacus by `pip install .[test]`. (Use `pip install -v .[test] -i https://pypi.tuna.tsinghua.edu.cn/simple` to accelerate installation process.)
1514

16-
1715
CI Examples
1816
-----------
1917

2018
There are examples for CI in `.github/workflows`. A simple way to produces
2119
binary "wheels" for all platforms is illustrated in the "wheels.yml" file,
22-
using [`cibuildwheel`][].
20+
using .
2321

2422
Use `pytest -v` to run all the unit tests for pyabacus in the local machine.
23+
2524
```shell
2625
$ cd tests/
2726
$ pytest -v
2827
```
2928

3029
Run `python vis_nao.py` to visualize the numerical orbital.
30+
3131
```shell
3232
$ cd examples/
3333
$ python vis_nao.py
@@ -41,6 +41,16 @@ $ python ex_s_rotate.py
4141
norm(S_e3 - S_numer) = 3.341208104032616e-15
4242
```
4343

44+
Run `python diago_matrix.py` in `examples` to check the diagonalization of a matrix.
45+
46+
```shell
47+
$ cd examples/
48+
$ 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]
52+
```
53+
4454
License
4555
-------
4656

@@ -58,4 +68,4 @@ s.sphbesj(1, 0.0)
5868
0.0
5969
```
6070

61-
[`cibuildwheel`]: https://cibuildwheel.readthedocs.io
71+
[`cibuildwheel`]: https://cibuildwheel.readthedocs.io

python/pyabacus/examples/Si2.mat

47.3 KB
Binary file not shown.
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from pyabacus import hsolver
2+
import numpy as np
3+
import scipy
4+
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)

python/pyabacus/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ description="A minimal two_center_integral package (with pybind11)"
1010
readme = "README.md"
1111
authors = [
1212
{ name = "Jie Li", email = "[email protected]" },
13+
{ name = "Chenxu Bai", email = "[email protected]" },
1314
]
1415
requires-python = ">=3.7"
1516
classifiers = [

python/pyabacus/src/py_abacus.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -5,10 +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);
89

910
PYBIND11_MODULE(_core, m)
1011
{
1112
m.doc() = "Python extension for ABACUS built with pybind11 and scikit-build.";
1213
bind_base_math(m);
1314
bind_m_nao(m);
15+
bind_diago_dav_subspace(m);
1416
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include <complex>
2+
#include <functional>
3+
#include <pybind11/pybind11.h>
4+
#include <pybind11/stl.h>
5+
#include <pybind11/complex.h>
6+
#include <pybind11/numpy.h>
7+
8+
#include "module_hsolver/diago_dav_subspace.h"
9+
#include "module_hsolver/kernels/math_kernel_op.h"
10+
#include "module_base/module_device/types.h"
11+
12+
#include "./py_diago_dav_subspace.hpp"
13+
14+
namespace py = pybind11;
15+
using namespace pybind11::literals;
16+
17+
void bind_diago_dav_subspace(py::module& m)
18+
{
19+
py::module hsolver = m.def_submodule("hsolver");
20+
21+
py::class_<hsolver::diag_comm_info>(hsolver, "diag_comm_info")
22+
.def(py::init<const int, const int>(), "rank"_a, "nproc"_a)
23+
.def_readonly("rank", &hsolver::diag_comm_info::rank)
24+
.def_readonly("nproc", &hsolver::diag_comm_info::nproc);
25+
26+
py::class_<py_hsolver::PyDiagoDavSubspace>(hsolver, "diago_dav_subspace")
27+
.def(py::init<int, int>(), R"pbdoc(
28+
Constructor of diago_dav_subspace, a class for diagonalizing
29+
a linear operator using the Davidson-Subspace Method.
30+
31+
This class serves as a backend computation class. The interface
32+
for invoking this class is a function defined in _hsolver.py,
33+
which uses this class to perform the calculations.
34+
35+
Parameters
36+
----------
37+
nbasis : int
38+
The number of basis functions.
39+
nband : int
40+
The number of bands to be calculated.
41+
)pbdoc", "nbasis"_a, "nband"_a)
42+
.def("diag", &py_hsolver::PyDiagoDavSubspace::diag, R"pbdoc(
43+
Diagonalize the linear operator using the Davidson-Subspace Method.
44+
45+
Parameters
46+
----------
47+
mm_op : Callable[[NDArray[np.complex128]], NDArray[np.complex128]],
48+
The operator to be diagonalized, which is a function that takes a matrix as input
49+
and returns a matrix mv_op(X) = H * X as output.
50+
precond_vec : np.ndarray
51+
The preconditioner vector.
52+
dav_ndim : int
53+
The number of vectors, which is a multiple of the number of
54+
eigenvectors to be calculated.
55+
tol : double
56+
The tolerance for the convergence.
57+
max_iter : int
58+
The maximum number of iterations.
59+
need_subspace : bool
60+
Whether to use the subspace function.
61+
is_occupied : list[bool]
62+
A list of boolean values indicating whether the band is occupied,
63+
meaning that the corresponding eigenvalue is to be calculated.
64+
scf_type : bool
65+
Whether to use the SCF type, which is used to determine the
66+
convergence criterion.
67+
If true, it indicates a self-consistent field (SCF) calculation,
68+
where the initial precision of eigenvalue calculation can be coarse.
69+
If false, it indicates a non-self-consistent field (non-SCF) calculation,
70+
where high precision in eigenvalue calculation is required from the start.
71+
)pbdoc",
72+
"mm_op"_a,
73+
"precond_vec"_a,
74+
"dav_ndim"_a,
75+
"tol"_a,
76+
"max_iter"_a,
77+
"need_subspace"_a,
78+
"is_occupied"_a,
79+
"scf_type"_a,
80+
"comm_info"_a)
81+
.def("set_psi", &py_hsolver::PyDiagoDavSubspace::set_psi, R"pbdoc(
82+
Set the initial guess of the eigenvectors, i.e. the wave functions.
83+
)pbdoc", "psi_in"_a)
84+
.def("get_psi", &py_hsolver::PyDiagoDavSubspace::get_psi, R"pbdoc(
85+
Get the eigenvectors.
86+
)pbdoc")
87+
.def("init_eigenvalue", &py_hsolver::PyDiagoDavSubspace::init_eigenvalue, R"pbdoc(
88+
Initialize the eigenvalues as zero.
89+
)pbdoc")
90+
.def("get_eigenvalue", &py_hsolver::PyDiagoDavSubspace::get_eigenvalue, R"pbdoc(
91+
Get the eigenvalues.
92+
)pbdoc");
93+
}

0 commit comments

Comments
 (0)