|
| 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