Skip to content

Commit 8382659

Browse files
committed
Add CONTRIBUTING.md to facilitate contributing to pyabacus project
1 parent 6e3ed8c commit 8382659

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

python/pyabacus/CONTRIBUTING.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
# Developer Guide
2+
3+
## Introduction
4+
5+
Welcome to the `pyabacus` project! This document provides guidelines and instructions for developers who want to contribute to this project.
6+
7+
## Project Structure
8+
9+
The project is organized as follows:
10+
11+
```
12+
pyabacus/
13+
├── CMakeLists.txt
14+
└── src
15+
└── {your_module}
16+
└── CMakeLists.txt
17+
```
18+
19+
### Root CMake Configuration
20+
21+
The `CMakeLists.txt` in root directory is the main configuration file for the pyabacus project. It sets up the project, finds necessary dependencies, configures build options, and includes subdirectories for different modules. Below is a detailed explanation of each section of the file:
22+
23+
```cmake
24+
cmake_minimum_required(VERSION 3.15...3.26)
25+
26+
# Project settings
27+
project(
28+
${SKBUILD_PROJECT_NAME}
29+
VERSION ${SKBUILD_PROJECT_VERSION}
30+
LANGUAGES CXX)
31+
```
32+
- This section sets the project name, version, and the programming languages used (C++ in this case). The project name and version are obtained from the `SKBUILD_PROJECT_NAME` and `SKBUILD_PROJECT_VERSION` variables, respectively.
33+
34+
```cmake
35+
# Find Python and pybind11
36+
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
37+
find_package(pybind11 CONFIG REQUIRED)
38+
```
39+
- This section finds the required Python and pybind11 packages.
40+
41+
```cmake
42+
# Set source path
43+
set(ABACUS_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../source")
44+
set(BASE_PATH "${ABACUS_SOURCE_DIR}/module_base")
45+
set(NAO_PATH "${ABACUS_SOURCE_DIR}/module_basis/module_nao")
46+
set(HSOLVER_PATH "${ABACUS_SOURCE_DIR}/module_hsolver")
47+
set(PSI_PATH "${ABACUS_SOURCE_DIR}/module_psi")
48+
set(ENABLE_LCAO ON)
49+
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../cmake")
50+
```
51+
- This section sets various source paths and configuration options. It defines the paths to different modules and appends the custom CMake module path.
52+
53+
```cmake
54+
# Add math_libs
55+
if(DEFINED ENV{MKLROOT} AND NOT DEFINED MKLROOT)
56+
set(MKLROOT "$ENV{MKLROOT}")
57+
endif()
58+
if(MKLROOT)
59+
set(MKL_INTERFACE lp64)
60+
set(ENABLE_MPI ON)
61+
if (ENABLE_MPI)
62+
find_package(MPI REQUIRED)
63+
include_directories(${MPI_CXX_INCLUDE_PATH})
64+
endif()
65+
66+
set(USE_OPENMP ON)
67+
if(USE_OPENMP)
68+
find_package(OpenMP REQUIRED)
69+
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
70+
add_link_options(${OpenMP_CXX_LIBRARIES})
71+
endif()
72+
find_package(MKL REQUIRED)
73+
add_definitions(-D__MKL)
74+
include_directories(${MKL_INCLUDE} ${MKL_INCLUDE}/fftw)
75+
76+
if(NOT ENABLE_DEEPKS)
77+
list(APPEND math_libs IntelMKL::MKL)
78+
endif()
79+
80+
if(CMAKE_CXX_COMPILER_ID MATCHES Intel)
81+
list(APPEND math_libs -lifcore)
82+
endif()
83+
else()
84+
find_package(FFTW3 REQUIRED)
85+
add_compile_definitions(__FFTW3)
86+
find_package(LAPACK REQUIRED)
87+
include_directories(${FFTW3_INCLUDE_DIRS})
88+
list(APPEND math_libs FFTW3::FFTW3 LAPACK::LAPACK)
89+
90+
if(ENABLE_LCAO)
91+
find_package(ScaLAPACK REQUIRED)
92+
list(APPEND math_libs ScaLAPACK::ScaLAPACK)
93+
endif()
94+
endif()
95+
```
96+
- This section configures the math libraries. It checks for the presence of the Intel Math Kernel Library (MKL) and configures it if available. If MKL is not available, it falls back to using FFTW3 and LAPACK. It also configures MPI and OpenMP if enabled.
97+
98+
```cmake
99+
# Add include directories
100+
include_directories(
101+
${BASE_PATH}
102+
${ABACUS_SOURCE_DIR}
103+
${ABACUS_SOURCE_DIR}/module_base/module_container
104+
)
105+
```
106+
- This section adds the necessary include directories for the project.
107+
108+
```cmake
109+
# Add basic libraries
110+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
111+
# Add base
112+
set(BASE_BINARY_DIR "${PROJECT_SOURCE_DIR}/build/base")
113+
add_subdirectory(${ABACUS_SOURCE_DIR}/module_base ${BASE_BINARY_DIR})
114+
# Add parameter
115+
set(PARAMETER_BINARY_DIR "${PROJECT_SOURCE_DIR}/build/parameter")
116+
add_subdirectory(${ABACUS_SOURCE_DIR}/module_parameter ${PARAMETER_BINARY_DIR})
117+
# Add orb
118+
set(ORB_BINARY_DIR "${PROJECT_SOURCE_DIR}/build/orb")
119+
add_subdirectory(${ABACUS_SOURCE_DIR}/module_basis/module_ao ${ORB_BINARY_DIR})
120+
```
121+
- This section sets the position-independent code flag and adds subdirectories for the base, parameter, and orb modules. It specifies the build directories for these modules.
122+
123+
```cmake
124+
# Set RPATH
125+
execute_process(
126+
COMMAND "${PYTHON_EXECUTABLE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
127+
OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
128+
OUTPUT_STRIP_TRAILING_WHITESPACE
129+
)
130+
```
131+
- This section sets the runtime search path (RPATH) for the Python site-packages directory. It uses a Python command to get the site-packages path and stores it in the `PYTHON_SITE_PACKAGES` variable.
132+
133+
```cmake
134+
# Set package name to pyabacus
135+
set(TARGET_PACK pyabacus)
136+
set(CMAKE_INSTALL_RPATH "${PYTHON_SITE_PACKAGES}/${TARGET_PACK}")
137+
```
138+
- This section sets the package name to `pyabacus` and configures the install RPATH to include the Python site-packages directory.
139+
140+
```cmake
141+
# Add subdirectories for submodules
142+
add_subdirectory(${PROJECT_SOURCE_DIR}/src/hsolver)
143+
add_subdirectory(${PROJECT_SOURCE_DIR}/src/ModuleBase)
144+
add_subdirectory(${PROJECT_SOURCE_DIR}/src/ModuleNAO)
145+
```
146+
- This section adds subdirectories for modules. Each subdirectory contains its own `CMakeLists.txt` for further configuration.
147+
148+
By following this structure, the `CMakeLists.txt` file ensures that all necessary dependencies are found, configured, and included in the build process. It also sets up the project environment and includes submodules for different components of the `pyabacus` project.
149+
150+
### Module CMake Configuration
151+
152+
I'll show you a `CMakeLists.txt` for example (pyabacus.hsolver)
153+
154+
```cmake
155+
# Add diago shared library
156+
list(APPEND _diago
157+
${HSOLVER_PATH}/diago_dav_subspace.cpp
158+
${HSOLVER_PATH}/diago_david.cpp
159+
${HSOLVER_PATH}/diag_const_nums.cpp
160+
${HSOLVER_PATH}/diago_iter_assist.cpp
161+
${HSOLVER_PATH}/kernels/dngvd_op.cpp
162+
${HSOLVER_PATH}/kernels/math_kernel_op.cpp
163+
${BASE_PATH}/kernels/math_op.cpp
164+
${BASE_PATH}/module_device/device.cpp
165+
${BASE_PATH}/module_device/memory_op.cpp
166+
${PSI_PATH}/psi.cpp
167+
)
168+
add_library(diagopack SHARED ${_diago})
169+
target_link_libraries(diagopack
170+
base
171+
parameter
172+
container
173+
orb
174+
${math_libs}
175+
${OpenBLAS_LIBRARIES}
176+
${LAPACK_LIBRARIES}
177+
)
178+
179+
list(APPEND pymodule_hsolver
180+
${PROJECT_SOURCE_DIR}/src/hsolver/py_hsolver.cpp
181+
)
182+
183+
# Use pybind11 to add python module
184+
pybind11_add_module(_hsolver_pack MODULE ${pymodule_hsolver})
185+
# Link your dependencies and pybind11 libraries to your module
186+
target_link_libraries(_hsolver_pack PRIVATE pybind11::headers diagopack)
187+
target_compile_definitions(_hsolver_pack PRIVATE VERSION_INFO=${PROJECT_VERSION})
188+
189+
set_target_properties(diagopack PROPERTIES INSTALL_RPATH "$ORIGIN")
190+
set_target_properties(_hsolver_pack PROPERTIES INSTALL_RPATH "$ORIGIN")
191+
192+
# Install your module package to destination path
193+
install(TARGETS _hsolver_pack diagopack DESTINATION ${TARGET_PACK}/hsolver)
194+
```
195+
196+
You can refer to the `CMakeLists.txt` files in other modules for guidance on how to configure your module.
197+
198+
## Development Process
199+
200+
To contribute to the `pyabacus` project, follow these steps:
201+
202+
1. **Check the issues**:
203+
- Look for issues to ensure that you are not working on something that is already in progress.
204+
- If you want to work on a new feature or bug fix, create an issue first to discuss it with the maintainers.
205+
206+
2. **Create a new folder for your module**:
207+
- If you want to add a new module with pure Python code, create a new folder in the `src/pyabacus` directory.
208+
- If you want to add a new module with C++ code, create a new folder in the `src` directory and a corresponding directory in the `src/pyabacus` directory.
209+
210+
3. **Write source code using pybind11**:
211+
- Follow the structure of other modules.
212+
- Manage dependencies and installation paths in the `CMakeLists.txt` file.
213+
214+
3. **Modify `src/pyabacus/__init__.py`**:
215+
- Add the name of your module to the `__submodules__` list and import the module in the `__getattr__` function.
216+
217+
```python
218+
from __future__ import annotations
219+
220+
__submodules__ = ["ModuleBase", "ModuleNAO", "hsolver", "{module_name}"]
221+
222+
__all__ = list(__submodules__)
223+
224+
def __getattr__(attr):
225+
if attr == "ModuleBase":
226+
import pyabacus.ModuleBase as ModuleBase
227+
return ModuleBase
228+
elif attr == "ModuleNAO":
229+
import pyabacus.ModuleNAO as ModuleNAO
230+
return ModuleNAO
231+
elif attr == "hsolver":
232+
import pyabacus.hsolver as hsolver
233+
return hsolver
234+
elif attr == '{module_name}':
235+
import pyabacus.{module_name} as {module_name}
236+
return {module_name}
237+
else:
238+
raise AttributeError(f"module {__name__} has no attribute {attr}")
239+
```
240+
241+
4. **Create two files in `src/pyabacus/{module_name}`**:
242+
- `__init__.py`: This file allows Python to recognize the folder as a module.
243+
- `_{module_name}.py`: This file is responsible for designing the Python interface (frontend).
244+
245+
**Example `__init__.py`**:
246+
247+
```python
248+
from __future__ import annotations
249+
from ._{module_name} import *
250+
251+
__all__ = ["{class_name}", "{func_name}", ...]
252+
```
253+
254+
**Example `_{module_name}.py`**:
255+
256+
```python
257+
from .{module_library_name} import {your_class} as _your_class, ...
258+
259+
"""
260+
Your class should inherit from the corresponding class in the C++ library.
261+
All methods should be overridden to provide type hints and auto-completion.
262+
You can use the `super()` method to call the base class(C++ class) methods.
263+
"""
264+
class {your_class}(_your_class):
265+
def __init__(self) -> None:
266+
super().__init__()
267+
268+
def foo(self, arg1, arg2, ...) -> RetType:
269+
return super().foo(arg1, arg2, ...)
270+
271+
def bar(self, arg1, arg2, ...):
272+
super().bar(arg1, arg2, ...)
273+
```
274+
275+
For a class, if you do not declare the interface in the frontend, the IDE will not provide type hints and auto-completion. However, if the interface name matches the name binding in pybind11, it will be overridden. To address this, you can use the method as shown above.
276+
277+
5. **Handle overloaded functions in C++**:
278+
- Since Python does not support function overloading with different parameters, use the following method:
279+
280+
```python
281+
@overload
282+
def foo(self, x: float) -> float: ...
283+
@overload
284+
def foo(self, n: int, x: float, y: float) -> float: ...
285+
286+
def foo(self, *args, **kwargs):
287+
return super().foo(*args, **kwargs)
288+
```
289+
290+
**Example Python Interface**:
291+
292+
```python
293+
class diag_comm_info(_diag_comm_info):
294+
def __init__(self, rank: int, nproc: int):
295+
super().__init__(rank, nproc)
296+
297+
@property
298+
def rank(self) -> int:
299+
return super().rank
300+
301+
@property
302+
def nproc(self) -> int:
303+
return super().nproc
304+
305+
class Sphbes(_Sphbes):
306+
def __init__(self) -> None:
307+
super().__init__()
308+
309+
@overload
310+
@staticmethod
311+
def sphbesj(l: int, x: float) -> float: ...
312+
@overload
313+
@staticmethod
314+
def sphbesj(
315+
n: int,
316+
r: NDArray[np.float64],
317+
q: int,
318+
l: int,
319+
jl: NDArray[np.float64]
320+
) -> None: ...
321+
322+
def sphbesj(self, *args, **kwargs):
323+
return super().sphbesj(*args, **kwargs)
324+
325+
@overload
326+
@staticmethod
327+
def dsphbesj(l: int, x: float) -> float: ...
328+
@overload
329+
@staticmethod
330+
def dsphbesj(
331+
n: int,
332+
r: NDArray[np.float64],
333+
q: int,
334+
l: int,
335+
djl: NDArray[np.float64]
336+
) -> None: ...
337+
338+
def dsphbesj(self, *args, **kwargs):
339+
return super().dsphbesj(*args, **kwargs)
340+
341+
@staticmethod
342+
def sphbes_zeros(l: int, n: int, zeros: NDArray[np.float64]) -> None:
343+
super().sphbes_zeros(l, n, zeros)
344+
```
345+
346+
## Conclusion
347+
348+
By following this guide, you can effectively contribute to the `pyabacus` project. Ensure that you follow the structure and conventions outlined here to maintain consistency and readability in the codebase. Happy coding!

0 commit comments

Comments
 (0)