Skip to content

Commit 95b073b

Browse files
Merge pull request #5 from QuantumMisaka/neb-dimer
Neb-dimer workflow
2 parents 964cf55 + d063f89 commit 95b073b

File tree

15 files changed

+329
-42
lines changed

15 files changed

+329
-42
lines changed

README.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# ase-neb-abacus
2-
NEB or CI-NEB workflow by ASE and ABACUS using ASE-ABACUS interface,
3-
Which use enhanced NEB method in ASE, like Dynamic NEB `DyNEB`, improved-tangent method `IT-NEB`, and also `AutoNEB` in ASE.
1+
# ATST-Tools
2+
Advanced ASE Transition State Tools for ABACUS, including:
3+
- NEB and improvement, like Dynamic NEB
4+
- CI-NEB, IT-NEB and others
5+
- AutoNEB
6+
- Dimer
47

5-
Version v1.0.2
8+
Version v1.1
69

710
## Dependencies:
811
- [ASE](https://wiki.fysik.dtu.dk/ase/about.html)
@@ -14,12 +17,13 @@ Notice: GPAW and ABACUS should be dependent on same MPI and libraries environmen
1417
For instance, if your ABACUS is installed by Intel-OneAPI toolchain, your GPAW should NOT be dependent on gcc-toolchain like OpenMPI and OpenBLAS.
1518

1619

17-
## Usage
18-
The workflow is based on 3 main python scripts and 1 workflow submit script. Namely:
20+
## NEB workflow
21+
### Usage
22+
The NEB workflow is based on 3 main python scripts and 1 workflow submit script. Namely:
1923

2024
- `neb_make.py` will make initial guess for NEB calculation, which is based on ABACUS (and other calculator) output files of initial and final state. This script will generate `init_neb_chain.traj` for neb calculation. Also, You can do continuation calculation by using this script. You can get more usage by `python neb_make.py`.
21-
- `neb_run.py` is the key running script, which will run NEB calculation based on `init_neb_chain.traj` generated by `neb_make.py`. This script will generate `neb.traj` for neb calculation. Users should edit this file to set parameters for NEB calculation. sereal running can be performed by `python neb_run.py`, while parallel running can be performed by `mpirun gpaw python neb_run.py`.
22-
When running, the NEB trajectory will be output to `neb.traj`, and NEB images calculation will be doing in `NEB-rank{i}` for each rank which do calculation of each image.
25+
- `neb_run.py` is the key running script of NEB, which will run NEB calculation based on `init_neb_chain.traj` generated by `neb_make.py`. This script will generate `neb.traj` for neb calculation. Users should edit this file to set parameters for NEB calculation. sereal running can be performed by `python neb_run.py`, while parallel running can be performed by `mpirun gpaw python neb_run.py`.
26+
When running, the NEB trajectory will be output to `neb.traj`, and NEB images calculation will be doing in `NEB-rank{i}` directory for each rank which do calculation of each image.
2327
- `neb_post.py` will post-process the NEB calculation result, which will based on `neb.traj` from neb calculation. This script will generate nebplots.pdf to view neb calculation result, and also print out the energy barrier and reaction energy. You can get more usage by `python neb_post.py`. Meanwhile, users can also view result by `ase -T gui neb.traj` or `ase -T gui neb.traj@-{n_images}:` by using ASE-GUI
2428
- `neb_submit.sh` will do all NEB process in one workflow scripts and running NEB calculation in parallel. Users should edit this file to set parameters for NEB calculation. Also this submit script can be used as a template for job submission in HPC. the Default setting is for `slurm` job submission system.
2529

@@ -43,13 +47,25 @@ Also, user can run each step in one script `neb_submit.sh` by `bash neb_submit.s
4347

4448
> Notice: Before you start neb calculation process, make sure that you have check the nodes and cpus setting in `neb_submit.sh` and `neb_run.py` to make sure that you can reach the highest performance !!!
4549
46-
## Method
50+
### Method
4751
- For serial NEB calculation, DyNEB, namely dynamic NEB method `ase.mep.neb.DyNEB` is for default used.
4852
- For parallel NEB calculation, `ase.mep.neb.NEB` traditional method is for default used, and `ase.mep.autoneb.AutoNEB` method should be independently used.
4953
- The Improved Tangent NEB method `IT-NEB` and Climbing Image NEB method `CI-NEB` in ASE are also default used in this workflow, which is highly recommended by Sobervea. But in `AutoNEB`, `eb` method is used for default.
5054
- Users can change lots of parameter for different NEB setting. one can refer to [ASE NEB calculator](https://wiki.fysik.dtu.dk/ase/ase/neb.html#module-ase.neb) for more details:
5155
- Notice: in surface calculation and hexangonal system, the vaccum and c-axis should be set along y-direction but not z-direction, which is much more efficient for ABACUS calculation.
5256

57+
## Dimer workflow
58+
### Usage
59+
The Dimer workflow is based on 2 main python scripts and 2 workflow submit script. Namely:
60+
- `neb2dimer.py` can be used by `python neb2dimer [neb.traj] ([n_max])`, which will transform NEB trajetory `neb.traj` or NEB result trajectory `neb_result.traj` to Dimer input files, including:
61+
- - `dimer_init.traj` for initial state of Dimer calculation
62+
- - `displacement_vector.npy` for displacement vector of Dimer calculation
63+
- `dimer_run.py` is the key running script of Dimer calculation, which will run Dimer calculation based on `dimer_init.traj` and `displacement_vector.npy` generated by `neb2dimer.py` or based on other setting. This script will generate `dimer.traj` for Dimer calculation trajectory. Users should edit this file to set parameters for Dimer calculation, and run Dimer calculation by `python dimer_run.py`. When running, any Dimer images calculation will be doing in `Dimer` directory.
64+
- `dimer_submit.sh` will do Dimer workflow in one scripts. The Default setting is for `slurm` job submission system.
65+
- `neb-dimer_srun.sh` is a try to run NEB + Dimer calculation in one server scripts. The Default setting is for `slurm` job submission system.
66+
67+
### Method
68+
(Waiting for update)
5369

5470
## Examples
5571
- Li-diffu-Si: Li diffusion in Si, an example for running ASE-NEB-ABACUS based on existing ABACUS input files of initial and final state, using ABACUS as SCF calculator and ASE as optimizer and NEB calculator. Also, an dflow example is proposed.
@@ -58,9 +74,9 @@ Also, user can run each step in one script `neb_submit.sh` by `bash neb_submit.s
5874
- CO-Pt111 : CO dissociation on Pt(111) surface, an example for running ASE-NEB-ABACUS based on existing ABACUS input files of initial and final state, use ABACUS as SCF calculator, use ABACUS as optimizer for optimization of initial state and final state, use ASE as NEB calculator
5975
- Cy-Pt_graphene: Cyclohexane dehydrogenation on Pt-doped graphene surface, an example for running ASE-NEB-ABACUS based on existing ABACUS input files of initial and final state, use ABACUS as optimizer for optimization of initial state and final state, use ASE as NEB calculator
6076

77+
AutoNEB example ix on update, Dimer example is preparing
6178

6279
## Next Examples
63-
- `AutoNEB` test
6480
- CO-Fe100: CO dissociation on Fe(100) surface, in this example we will focus on setting `magmom` during NEB calculation, which is important for spin-polarized magnetic system.
6581
- TS of MTM process by H2O2 in Cu/Ag-ZSM5 system, including:
6682
- - proton transfer process
@@ -83,7 +99,8 @@ Also, user can run each step in one script `neb_submit.sh` by `bash neb_submit.s
8399
- [x] More test in surface reaction system
84100
- [x] Parallel computing during images relaxation by `gpaw python`
85101
- [x] `AutoNEB` implementation
102+
- [x] Connected to Dimer method
86103
- [ ] More test in magnetic surface reaction system
87-
- [ ] Connected to Dimer method
104+
- [ ] put calculation setting in an independent file (decoupling run*.py)
88105

89106

dimer/dimer_run.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
from ase import Atom, Atoms
2+
from ase.io import Trajectory, read, write
3+
from ase.mep import DimerControl, MinModeAtoms, MinModeTranslate
4+
from ase.optimize import BFGS, FIRE
5+
from ase.calculators.abacus import Abacus, AbacusProfile
6+
import os, sys
7+
8+
import numpy as np
9+
10+
fmax = 0.05
11+
dimer_input_file = 'dimer_init.traj'
12+
init_eigenmode_method = 'displacement'
13+
displacement_input = 'displacement_vector.npy'
14+
15+
# setting for calculator
16+
mpi = 16
17+
omp = 4
18+
abacus = "abacus"
19+
#lib_dir = "/lustre/home/2201110432/example/abacus"
20+
lib_dir = "/home/james/example"
21+
pseudo_dir = f"{lib_dir}/PP"
22+
basis_dir = f"{lib_dir}/ORB"
23+
pp = {
24+
'H':'H_ONCV_PBE-1.0.upf',
25+
'Au':'Au_ONCV_PBE-1.0.upf',
26+
}
27+
basis = {
28+
'H':'H_gga_6au_100Ry_2s1p.orb',
29+
'Au':'Au_gga_7au_100Ry_4s2p2d1f.orb',
30+
}
31+
kpts = [3, 1, 3]
32+
parameters = {
33+
'calculation': 'scf',
34+
'nspin': 2,
35+
'xc': 'pbe',
36+
'ecutwfc': 100,
37+
'ks_solver': 'genelpa',
38+
'symmetry': 0,
39+
'vdw_method': 'd3_bj',
40+
'smearing_method': 'gaussian',
41+
'smearing_sigma': 0.001,
42+
'basis_type': 'lcao',
43+
'mixing_type': 'broyden',
44+
'scf_thr': 1e-6,
45+
'scf_nmax': 100,
46+
'kpts': kpts,
47+
'pp': pp,
48+
'basis': basis,
49+
'pseudo_dir': pseudo_dir,
50+
'basis_dir': basis_dir,
51+
'cal_force': 1,
52+
'cal_stress': 1,
53+
'out_stru': 1,
54+
'out_chg': 0,
55+
'out_mul': 0,
56+
'out_bandgap': 0,
57+
'efield_flag': 1,
58+
'dip_cor_flag': 1,
59+
'efield_dir': 1,
60+
'efield_pos_max': 0.7
61+
}
62+
63+
64+
class AbacusDimer:
65+
"""Customize Dimer calculation by using ABACUS"""
66+
67+
def __init__(self, init_Atoms, parameters, abacus='abacus',
68+
mpi=1, omp=1, directory='DIMER',
69+
traj_file='dimer.traj',
70+
init_eigenmode_method='displacement',
71+
displacement_vector: np.ndarray = None,):
72+
"""Initialize Dimer method by using ASE-ABACUS
73+
74+
init_Atoms (Atoms object): starting image, can be from every way including NEB result
75+
parameters (dict): settings of abacus input parameters
76+
abacus (str): Abacus executable file. Default: 'abacus'
77+
directory (str): calculator directory name, for parallel calculation {directory}-rank{i} will be the directory name
78+
mpi (int): number of MPI for abacus calculator
79+
omp (int): number of OpenMP for abacus calculator
80+
traj_file (str): trajectory file name for dimer calculation, when running dimer calculation, trajetory will be written to this file, default is 'dimer.traj'
81+
init_eigenmode_method (str): dimer initial eigenmode method. Choose from 'displacement' and 'gauss'.
82+
displacement_vector (np.ndarray): displacement vector for dimer initial eigenmode. Only used when init_eigenmode_method is 'displacement'
83+
"""
84+
self.init_Atoms = init_Atoms
85+
self.parameters = parameters
86+
self.abacus = abacus
87+
self.mpi = mpi
88+
self.omp = omp
89+
self.directory = directory
90+
self.traj_file = traj_file
91+
self.init_eigenmode_method = init_eigenmode_method
92+
self.displacement_vector = displacement_vector
93+
94+
def set_calculator(self):
95+
"""Set Abacus calculators"""
96+
os.environ['OMP_NUM_THREADS'] = f'{self.omp}'
97+
profile = AbacusProfile(
98+
argv=['mpirun', '-np', f'{self.mpi}', self.abacus])
99+
out_directory = self.directory
100+
calc = Abacus(profile=profile, directory=out_directory,
101+
**self.parameters)
102+
return calc
103+
104+
def set_d_mask_by_displacement(self):
105+
"""set mask by displacement"""
106+
print("=== Set mask by displacement vector where displacement is [0,0,0] ===")
107+
d_mask = self.displacement_vector != np.zeros(3)
108+
d_mask = d_mask[:,0].tolist()
109+
return d_mask
110+
111+
def set_d_mask_by_constraint(self):
112+
"""set mask by constraint of Atoms"""
113+
print("=== Set mask by constraint read from init Atoms ===")
114+
dimer_init = self.init_Atoms
115+
const_list = dimer_init._get_constraints()[0]
116+
const_list = const_list.todict()['kwargs']['indices']
117+
d_mask = [True] * len(dimer_init)
118+
for ind in const_list:
119+
d_mask[ind] = False
120+
return d_mask
121+
122+
def run(self, fmax=0.05):
123+
"""run dimer calculation workflow"""
124+
dimer_init = self.init_Atoms
125+
dimer_init.calc = self.set_calculator()
126+
dimer_traj = Trajectory(self.traj_file, 'w', dimer_init)
127+
if self.init_eigenmode_method == "displacement":
128+
# d_mask = self.set_d_mask_by_displacement() # not the best
129+
d_mask = self.set_d_mask_by_constraint()
130+
d_control = DimerControl(initial_eigenmode_method=self.init_eigenmode_method,
131+
displacement_method="vector",
132+
mask=d_mask)
133+
d_atoms = MinModeAtoms(dimer_init, d_control)
134+
d_atoms.displace(displacement_vector=self.displacement_vector)
135+
elif self.init_eigenmode_method == "gauss":
136+
# leave a way for random displacement
137+
d_mask = self.set_d_mask_by_constraint()
138+
d_control = DimerControl(initial_eigenmode_method=self.init_eigenmode_method,
139+
mask=d_mask)
140+
d_atoms = MinModeAtoms(dimer_init, d_control)
141+
else:
142+
raise ValueError("init_eigenmode_method must be displacement or gauss")
143+
dimer_relax = MinModeTranslate(d_atoms, trajectory=dimer_traj)
144+
dimer_relax.run(fmax=fmax)
145+
146+
if __name__ == "__main__":
147+
# running process
148+
# read initial guessed neb chain
149+
dimer_init = read(dimer_input_file)
150+
displacement_vector = np.load(displacement_input)
151+
dimer = AbacusDimer(dimer_init, parameters, abacus=abacus,
152+
mpi=mpi, omp=omp,
153+
init_eigenmode_method=init_eigenmode_method,
154+
displacement_vector=displacement_vector)
155+
dimer.run(fmax=fmax)

dimer/dimer_submit.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash
2+
#SBATCH --nodes=1
3+
#SBATCH --ntasks-per-node=16
4+
#SBATCH --cpus-per-task=4
5+
#SBATCH -J DIMER-ABACUS
6+
#SBATCH -o run_dimer.out
7+
#SBATCH -e run_dimer.err
8+
#SBATCH -p C064M0256G
9+
10+
# JamesMisaka in 2023-11-14
11+
# workflow of ase-abacus-dimer method
12+
# for one calculator, ntasks-per-node for mpi, cpus-per-task for openmp
13+
14+
# in developer's PKU-WM2 server
15+
source /lustre/home/2201110432/apps/miniconda3/etc/profile.d/conda.sh
16+
conda activate gpaw-intel
17+
module load abacus/3.4.2-icx
18+
19+
# if one just done neb calculation
20+
python neb2dimer.py neb.traj # or neb_latest.traj
21+
22+
# Job state
23+
echo $SLURM_JOB_ID > JobRun.state
24+
echo "Start at $(date)" >> JobRun.state
25+
26+
# just do dimer by run !
27+
# do check your ntasks(mpi) and cpu(omp) setting is right
28+
python dimer_run.py
29+
30+
echo "===== Dimer Calculation Done at $(date)! ====="
31+
32+
# Job State
33+
echo "End at $(date)" >> JobRun.state

dimer/neb-dimer_srun.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# full workflow controling NEB and Dimer calculation
4+
5+
NEB_JOBID=$(sbatch neb_submit.sh) | awk '{print $4}' # get the jobid of neb and submit neb job
6+
echo "NEB_JOBID is ${NEB_JOBID}"
7+
DIMER_JOBID=$(sbatch --dependency=afterok:${NEB_JOBID} dimer_submit.sh) | awk '{print $4}' # submit dimer job after neb job done
8+
echo "Dimer Job Submitted and will be run after NEB job !"
9+
echo "DIMER_JOBID is ${DIMER_JOBID}"

dimer/neb2dimer.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import numpy as np
2+
import sys
3+
from ase.io import Trajectory, read, write
4+
from ase.mep.neb import NEBTools
5+
6+
def neb2dimer(neb_traj:list, n_max:int=0, out_traj='dimer_init.traj', out_vec='displacement_vector.npy',
7+
step_before_TS:int=1, step_after_TS:int=1):
8+
'''
9+
Transform neb chain to dimer init
10+
'''
11+
# you should use the latest neb chain or other single neb chain
12+
# but a full neb chain is permitted, in which the latest neb chain will be used
13+
if n_max == 0:
14+
print("=== n_max set to 0, get n_images by using NEBTools ===")
15+
n_images = NEBTools(neb_traj)._guess_nimages()
16+
elif (n_max >= 0) and (type(n_max) == int):
17+
n_images = n_max + 2
18+
else:
19+
raise ValueError("n_max must be a non-negative integer")
20+
if len(neb_traj) < n_images:
21+
raise ValueError(f"n_images ={n_images} is larger than the length of neb_traj ={len(neb_traj)}")
22+
elif len(neb_traj) > n_images:
23+
print(f"=== n_images ={n_images} is smaller than the length of neb_traj ={len(neb_traj)} ! Only the last {n_images} images are used ===")
24+
# used neb_chain is the final traj
25+
neb_chain = neb_traj[-n_images:]
26+
# get TS information from NEB chain
27+
barrier = NEBTools(neb_chain).get_barrier()[0]
28+
fmax = NEBTools(neb_chain).get_fmax()
29+
raw_barrier = max([image.get_potential_energy() for image in neb_chain])
30+
TS_info = [(ind, image)
31+
for ind, image in enumerate(neb_chain)
32+
if image.get_potential_energy() == raw_barrier][0]
33+
print(f"=== Locate TS in {TS_info[0]} of 0-{n_images-1} images ===")
34+
print(f"=== TS Barrier: {barrier:.4f} (eV) ===")
35+
print(f"=== TS Fmax: {fmax:.4f} (eV/A) ===")
36+
print(f"=== TS images is output as {out_traj} for dimer init ===")
37+
# output TS of neb for dimer init
38+
write(out_traj, TS_info[1], format='traj')
39+
# output displancement vector by using the nearest two images of TS
40+
ind_before_TS = TS_info[0] - step_before_TS
41+
ind_after_TS = TS_info[0] + step_after_TS
42+
img_before = neb_chain[ind_before_TS]
43+
img_after = neb_chain[ind_after_TS]
44+
print(f"=== Displacement vector is generated by position minus from {ind_after_TS} image to {ind_before_TS} image ===")
45+
displacement_vector = (img_after.positions - img_before.positions)
46+
print(f"=== Displacement vector is output as {out_vec} for dimer init ===")
47+
np.save(out_vec,displacement_vector)
48+
return
49+
50+
if __name__ == "__main__":
51+
msg = '''
52+
neb2dimer.py is to make dimer inputfile by using neb result
53+
Usage:
54+
python neb2dimer.py [traj_file] ([n_max])
55+
Notice that n_max = n_images - 2
56+
These sctipt will output two files:
57+
dimer_init.traj: TS of neb chain for dimer init-structure
58+
displacement_vector.npy: displacement vector of dimer init
59+
'''
60+
if len(sys.argv) < 2:
61+
print(msg)
62+
elif len(sys.argv) == 2:
63+
if sys.argv == "--help" or sys.argv == "-h":
64+
print(msg)
65+
else:
66+
traj_file = sys.argv[1]
67+
neb_traj = Trajectory(traj_file)
68+
neb2dimer(neb_traj)
69+
else:
70+
traj_file = sys.argv[1]
71+
n_max = int(sys.argv[2])
72+
neb_traj = Trajectory(traj_file)
73+
neb2dimer(neb_traj, n_max)
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)