Skip to content

Commit f6e15df

Browse files
MoseyQAQmohanchen
andauthored
[Feature] Add NEP as esolver (#6603)
* update NEP_CPU * update toolchain and document * update cmake file * update cmake file * update test * update toolchain script * remove nep_cpu; update test case and toolchain script * update unit test * update test.yml * update related doc. --------- Co-authored-by: Mohan Chen <[email protected]>
1 parent 0733e2d commit f6e15df

File tree

119 files changed

+3148
-16
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+3148
-16
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@ jobs:
132132
run: |
133133
cmake --build build --target test ARGS="-V --timeout 1700 -R 03_NAO_multik"
134134
135-
- name: 04_LJ_DP Test
135+
- name: 04_FF Test
136136
env:
137137
GTEST_COLOR: 'yes'
138138
OMP_NUM_THREADS: '2'
139139
run: |
140-
cmake --build build --target test ARGS="-V --timeout 1700 -R 04_LJ_DP"
140+
cmake --build build --target test ARGS="-V --timeout 1700 -R 04_FF"
141141
142142
- name: 05_rtTDDFT Test
143143
env:
@@ -186,4 +186,4 @@ jobs:
186186
GTEST_COLOR: 'yes'
187187
OMP_NUM_THREADS: '2'
188188
run: |
189-
cmake --build build --target test ARGS="-V --timeout 1700 -E 'integrate_test|01_PW|02_NAO_Gamma|03_NAO_multik|04_LJ_DP|05_rtTDDFT|06_SDFT|07_OFDFT|08_EXX|09_DeePKS|10_others|11_PW_GPU|12_NAO_Gamma_GPU|13_NAO_multik_GPU|15_rtTDDFT_GPU|16_SDFT_GPU|MODULE_BASE|MODULE_IO|MODULE_HSOLVER|MODULE_CELL|MODULE_MD|source_psi|MODULE_RI'"
189+
cmake --build build --target test ARGS="-V --timeout 1700 -E 'integrate_test|01_PW|02_NAO_Gamma|03_NAO_multik|04_FF|05_rtTDDFT|06_SDFT|07_OFDFT|08_EXX|09_DeePKS|10_others|11_PW_GPU|12_NAO_Gamma_GPU|13_NAO_multik_GPU|15_rtTDDFT_GPU|16_SDFT_GPU|MODULE_BASE|MODULE_IO|MODULE_HSOLVER|MODULE_CELL|MODULE_MD|source_psi|MODULE_RI'"

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,15 @@ if(DEFINED DeePMD_DIR)
654654
endif()
655655
endif()
656656

657+
if(DEFINED NEP_DIR)
658+
find_package(NEP REQUIRED)
659+
660+
if(NEP_FOUND)
661+
add_compile_definitions(__NEP)
662+
target_link_libraries(${ABACUS_BIN_NAME} NEP::nep)
663+
endif()
664+
endif()
665+
657666
if(DEFINED TensorFlow_DIR)
658667
find_package(TensorFlow REQUIRED)
659668
include_directories(${TensorFlow_DIR}/include)

cmake/FindNEP.cmake

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
###############################################################################
2+
# - Find NEP
3+
# Finds the NEP header and library.
4+
#
5+
# This module will search for the NEP library, looking for a hint
6+
# from the NEP_DIR environment variable or CMake variable.
7+
#
8+
# This module defines the following variables:
9+
#
10+
# NEP_FOUND - True if the NEP library and headers were found.
11+
# NEP_INCLUDE_DIR - The directory where nep.h is located.
12+
# NEP_LIBRARY - The full path to the NEP library.
13+
#
14+
# It also defines the following imported target:
15+
#
16+
# NEP::nep - The NEP library target.
17+
#
18+
###############################################################################
19+
# Note: Currently only CPU version is supported, Since the NEP interface with GPU support is not available yet.
20+
# In feature, if available, we can use USE_CUDA to switch between CPU and GPU version.
21+
22+
find_path(NEP_INCLUDE_DIR nep.h
23+
HINTS ${NEP_DIR}
24+
PATH_SUFFIXES "include"
25+
)
26+
27+
find_library(NEP_LIBRARY
28+
NAMES nep
29+
HINTS ${NEP_DIR}
30+
PATH_SUFFIXES "lib"
31+
)
32+
33+
include(FindPackageHandleStandardArgs)
34+
find_package_handle_standard_args(NEP
35+
DEFAULT_MSG
36+
NEP_LIBRARY NEP_INCLUDE_DIR)
37+
38+
if(NEP_FOUND)
39+
if(NOT TARGET NEP::nep)
40+
add_library(NEP::nep UNKNOWN IMPORTED)
41+
set_target_properties(NEP::nep PROPERTIES
42+
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
43+
IMPORTED_LOCATION "${NEP_LIBRARY}"
44+
INTERFACE_INCLUDE_DIRECTORIES "${NEP_INCLUDE_DIR}"
45+
)
46+
endif()
47+
endif()
48+
49+
mark_as_advanced(NEP_INCLUDE_DIR NEP_LIBRARY)

docs/advanced/input_files/input-main.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ These variables are used to control general system parameters.
535535
- tddft: real-time time-dependent density functional theory (TDDFT)
536536
- lj: Leonard Jones potential
537537
- dp: DeeP potential, see details in [md.md](../md.md#dpmd)
538+
- nep: Neuroevolution Potential, see details in [md.md](../md.md#nep)
538539
- ks-lr: Kohn-Sham density functional theory + LR-TDDFT (Under Development Feature)
539540
- lr: LR-TDDFT with given KS orbitals (Under Development Feature)
540541
- **Default**: ksdft
@@ -3327,7 +3328,7 @@ These variables are used to control molecular dynamics calculations. For more in
33273328
### pot_file
33283329

33293330
- **Type**: String
3330-
- **Description**: The filename of DP potential files, see [md.md](../md.md#dpmd) in detail.
3331+
- **Description**: The filename of DP/NEP potential files, see [md.md](../md.md#dpmd) in detail.
33313332
- **Default**: graph.pb
33323333

33333334
### dp_rescaling

docs/advanced/install.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ Similarly, DeePMD-kit supports PyTorch backend but its libraries are placed at a
5555
cmake -B build -DDeePMD_DIR=/dir_to_deepmd-kit -DTorch_DIR=/dir_to_pytorch
5656
```
5757

58+
## Build with NEP
59+
This interface enables running MD simulations with the NEP model. It requires the [NEP_CPU](https://github.com/brucefan1983/NEP_CPU) library, which can be easily installed using toolchain as shown below:
60+
```bash
61+
./install_abacus_toolchain.sh --with-nep=install
62+
```
63+
64+
To build ABACUS:
65+
```bash
66+
cmake -B build -DNEP_DIR=/path/to/nep_cpu
67+
```
68+
5869
## Build with LibRI and LibComm
5970

6071
The new EXX implementation depends on two external libraries:

docs/advanced/md.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ ABACUS performs the [Multi-Scale Shock Technique (MSST) integration](https://jou
8787
Compiling ABACUS with [DeePMD-kit](https://github.com/deepmodeling/deepmd-kit), MD calculations based on machine learning DP model is enabled.
8888

8989
To employ DPMD calculations, [esolver_type](./input_files/input-main.md#esolver_type) should be set to `dp`.
90-
And the filename of DP model is specified by keyword [pot_file](./input_files/input-main.md#pot_file).
90+
And the filename of DP model is specified by keyword [pot_file](./input_files/input-main.md#pot_file).
91+
92+
## NEP
93+
94+
If ABACUS is compiled with the Neuroevolution Potential ([NEP](https://gpumd.org/potentials/nep.html)), MD simulations using NEP models are enabled. To use this feature, set [esolver_type](./input_files/input-main.md#esolver_type) to `nep` and specify the potential file path with the [pot_file](./input_files/input-main.md#pot_file) keyword in your INPUT file.

source/Makefile.Objects

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ OBJS_ESOLVER=esolver.o\
267267
esolver_sdft_pw.o\
268268
esolver_lj.o\
269269
esolver_dp.o\
270+
esolver_nep.o\
270271
esolver_of.o\
271272
esolver_of_tddft.o\
272273
esolver_of_tool.o\

source/source_esolver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ list(APPEND objects
77
esolver_sdft_pw.cpp
88
esolver_lj.cpp
99
esolver_dp.cpp
10+
esolver_nep.cpp
1011
esolver_of.cpp
1112
esolver_of_tddft.cpp
1213
esolver_of_interface.cpp

source/source_esolver/esolver.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern "C"
1818
}
1919
#endif
2020
#include "esolver_dp.h"
21+
#include "esolver_nep.h"
2122
#include "esolver_lj.h"
2223
#include "esolver_of.h"
2324
#include "esolver_of_tddft.h"
@@ -97,6 +98,10 @@ std::string determine_type()
9798
{
9899
esolver_type = "dp_pot";
99100
}
101+
else if (PARAM.inp.esolver_type == "nep")
102+
{
103+
esolver_type = "nep_pot";
104+
}
100105
else if (esolver_type == "none")
101106
{
102107
ModuleBase::WARNING_QUIT("ESolver", "No such esolver_type combined with basis_type");
@@ -338,6 +343,10 @@ ESolver* init_esolver(const Input_para& inp, UnitCell& ucell)
338343
{
339344
return new ESolver_DP(PARAM.mdp.pot_file);
340345
}
346+
else if (esolver_type == "nep_pot")
347+
{
348+
return new ESolver_NEP(PARAM.mdp.pot_file);
349+
}
341350
throw std::invalid_argument("esolver_type = " + std::string(esolver_type) + ". Wrong in " + std::string(__FILE__)
342351
+ " line " + std::to_string(__LINE__));
343352
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* @file esolver_nep.cpp
3+
#include "source_io/module_parameter/parameter.h"
4+
* @brief Implementation of ESolver_NEP class for neuroevolution potential (NEP).
5+
*
6+
* This file contains the implementation of the ESolver_NEP class, which is used for solving the energy and forces in a
7+
* NEP simulation.
8+
* NEP is a method for training deep neural networks to accurately predict the potential energy surface of a
9+
* molecular system.
10+
*
11+
* For more information about NEP, see the following reference:
12+
* 1. https://gpumd.org/potentials/nep.html
13+
* 2. https://doi.org/10.1002/mgea.70028
14+
*
15+
* @author MoseyQAQ
16+
* @date 2025-10-10
17+
*/
18+
#include "esolver_nep.h"
19+
20+
#include "source_base/parallel_common.h"
21+
#include "source_base/timer.h"
22+
#include "source_io/output_log.h"
23+
#include "source_io/cif_io.h"
24+
25+
#include <numeric>
26+
#include <unordered_map>
27+
28+
using namespace ModuleESolver;
29+
30+
void ESolver_NEP::before_all_runners(UnitCell& ucell, const Input_para& inp)
31+
{
32+
nep_potential = 0.0;
33+
nep_force.create(ucell.nat, 3);
34+
nep_virial.create(3, 3);
35+
atype.resize(ucell.nat);
36+
_e.resize(ucell.nat);
37+
_f.resize(3 * ucell.nat);
38+
_v.resize(9 * ucell.nat);
39+
40+
ModuleIO::CifParser::write(PARAM.globalv.global_out_dir + "STRU.cif",
41+
ucell,
42+
"# Generated by ABACUS ModuleIO::CifParser",
43+
"data_?");
44+
45+
#ifdef __NEP
46+
/// determine the type map from STRU to NEP model
47+
type_map(ucell);
48+
#endif
49+
}
50+
51+
void ESolver_NEP::runner(UnitCell& ucell, const int istep)
52+
{
53+
ModuleBase::TITLE("ESolver_NEP", "runner");
54+
ModuleBase::timer::tick("ESolver_NEP", "runner");
55+
56+
// note that NEP are column major, thus a transpose is needed
57+
// cell
58+
std::vector<double> cell(9, 0.0);
59+
cell[0] = ucell.latvec.e11 * ucell.lat0_angstrom;
60+
cell[1] = ucell.latvec.e21 * ucell.lat0_angstrom;
61+
cell[2] = ucell.latvec.e31 * ucell.lat0_angstrom;
62+
cell[3] = ucell.latvec.e12 * ucell.lat0_angstrom;
63+
cell[4] = ucell.latvec.e22 * ucell.lat0_angstrom;
64+
cell[5] = ucell.latvec.e32 * ucell.lat0_angstrom;
65+
cell[6] = ucell.latvec.e13 * ucell.lat0_angstrom;
66+
cell[7] = ucell.latvec.e23 * ucell.lat0_angstrom;
67+
cell[8] = ucell.latvec.e33 * ucell.lat0_angstrom;
68+
69+
// coord
70+
std::vector<double> coord(3 * ucell.nat, 0.0);
71+
int iat = 0;
72+
const int nat = ucell.nat;
73+
for (int it = 0; it < ucell.ntype; ++it)
74+
{
75+
for (int ia = 0; ia < ucell.atoms[it].na; ++ia)
76+
{
77+
coord[iat] = ucell.atoms[it].tau[ia].x * ucell.lat0_angstrom;
78+
coord[iat + nat] = ucell.atoms[it].tau[ia].y * ucell.lat0_angstrom;
79+
coord[iat + 2 * nat] = ucell.atoms[it].tau[ia].z * ucell.lat0_angstrom;
80+
iat++;
81+
}
82+
}
83+
assert(ucell.nat == iat);
84+
85+
#ifdef __NEP
86+
nep_potential = 0.0;
87+
nep_force.zero_out();
88+
nep_virial.zero_out();
89+
90+
nep.compute(atype, cell, coord, _e, _f, _v);
91+
92+
// unit conversion
93+
const double fact_e = 1.0 / ModuleBase::Ry_to_eV;
94+
const double fact_f = 1.0 / (ModuleBase::Ry_to_eV * ModuleBase::ANGSTROM_AU);
95+
const double fact_v = 1.0 / (ucell.omega * ModuleBase::Ry_to_eV);
96+
97+
98+
// potential energy
99+
nep_potential = fact_e * std::accumulate(_e.begin(), _e.end(), 0.0) ;
100+
GlobalV::ofs_running << " #TOTAL ENERGY# " << std::setprecision(11) << nep_potential * ModuleBase::Ry_to_eV << " eV"
101+
<< std::endl;
102+
103+
// forces
104+
for (int i = 0; i < nat; ++i)
105+
{
106+
nep_force(i, 0) = _f[i] * fact_f;
107+
nep_force(i, 1) = _f[i + nat] * fact_f;
108+
nep_force(i, 2) = _f[i + 2 * nat] * fact_f;
109+
}
110+
111+
// virial
112+
std::vector<double> v_sum(9, 0.0);
113+
for (int j = 0; j < 9; ++j)
114+
{
115+
for (int i = 0; i < nat; ++i)
116+
{
117+
int index = j * nat + i;
118+
v_sum[j] += _v[index];
119+
}
120+
}
121+
122+
// virial -> stress
123+
for (int i = 0; i < 3; ++i)
124+
{
125+
for (int j = 0; j < 3; ++j)
126+
{
127+
nep_virial(i, j) = v_sum[3 * i + j] * fact_v;
128+
}
129+
}
130+
#else
131+
ModuleBase::WARNING_QUIT("ESolver_NEP", "Please recompile with -D__NEP");
132+
#endif
133+
ModuleBase::timer::tick("ESolver_NEP", "runner");
134+
}
135+
136+
double ESolver_NEP::cal_energy()
137+
{
138+
return nep_potential;
139+
}
140+
141+
void ESolver_NEP::cal_force(UnitCell& ucell, ModuleBase::matrix& force)
142+
{
143+
force = nep_force;
144+
ModuleIO::print_force(GlobalV::ofs_running, ucell, "TOTAL-FORCE (eV/Angstrom)", force, false);
145+
}
146+
147+
void ESolver_NEP::cal_stress(UnitCell& ucell, ModuleBase::matrix& stress)
148+
{
149+
stress = nep_virial;
150+
ModuleIO::print_stress("TOTAL-STRESS", stress, true, false, GlobalV::ofs_running);
151+
152+
// external stress
153+
double unit_transform = ModuleBase::RYDBERG_SI / pow(ModuleBase::BOHR_RADIUS_SI, 3) * 1.0e-8;
154+
double external_stress[3] = {PARAM.inp.press1, PARAM.inp.press2, PARAM.inp.press3};
155+
for (int i = 0; i < 3; i++)
156+
{
157+
stress(i, i) -= external_stress[i] / unit_transform;
158+
}
159+
}
160+
161+
void ESolver_NEP::after_all_runners(UnitCell& ucell)
162+
{
163+
GlobalV::ofs_running << "\n --------------------------------------------" << std::endl;
164+
GlobalV::ofs_running << std::setprecision(16);
165+
GlobalV::ofs_running << " !FINAL_ETOT_IS " << nep_potential * ModuleBase::Ry_to_eV << " eV" << std::endl;
166+
GlobalV::ofs_running << " --------------------------------------------\n\n" << std::endl;
167+
}
168+
169+
#ifdef __NEP
170+
void ESolver_NEP::type_map(const UnitCell& ucell)
171+
{
172+
// parse the element list from NEP model file
173+
std::unordered_map<std::string, int> label;
174+
std::string temp;
175+
for (int i = 0; i < nep.element_list.size(); ++i)
176+
{
177+
label[nep.element_list[i]] = i; //> label: map from element string to index int.
178+
}
179+
180+
std::cout << "\n Element list of model file " << nep_file << " " << std::endl;
181+
std::cout << " ----------------------------------------------------------------";
182+
int count = 0;
183+
for (auto it = label.begin(); it != label.end(); ++it)
184+
{
185+
if (count % 5 == 0)
186+
{
187+
std::cout << std::endl;
188+
std::cout << " ";
189+
}
190+
count++;
191+
temp = it->first + ": " + std::to_string(it->second);
192+
std::cout << std::left << std::setw(10) << temp;
193+
}
194+
std::cout << "\n -----------------------------------------------------------------" << std::endl;
195+
196+
// parse the atype based on the element list
197+
int iat = 0;
198+
for (int it = 0; it < ucell.ntype; ++it)
199+
{
200+
for (int ia = 0; ia < ucell.atoms[it].na; ++ia)
201+
{
202+
if (label.find(ucell.atoms[it].label) == label.end())
203+
{
204+
ModuleBase::WARNING_QUIT("ESolver_NEP",
205+
"The label " + ucell.atoms[it].label + " is not found in the type map.");
206+
}
207+
atype[iat] = label[ucell.atoms[it].label];
208+
iat++;
209+
}
210+
}
211+
assert(ucell.nat == iat);
212+
}
213+
#endif

0 commit comments

Comments
 (0)