-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from QuantumMisaka/neb-dimer
Neb-dimer workflow
- Loading branch information
Showing
15 changed files
with
329 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
from ase import Atom, Atoms | ||
from ase.io import Trajectory, read, write | ||
from ase.mep import DimerControl, MinModeAtoms, MinModeTranslate | ||
from ase.optimize import BFGS, FIRE | ||
from ase.calculators.abacus import Abacus, AbacusProfile | ||
import os, sys | ||
|
||
import numpy as np | ||
|
||
fmax = 0.05 | ||
dimer_input_file = 'dimer_init.traj' | ||
init_eigenmode_method = 'displacement' | ||
displacement_input = 'displacement_vector.npy' | ||
|
||
# setting for calculator | ||
mpi = 16 | ||
omp = 4 | ||
abacus = "abacus" | ||
#lib_dir = "/lustre/home/2201110432/example/abacus" | ||
lib_dir = "/home/james/example" | ||
pseudo_dir = f"{lib_dir}/PP" | ||
basis_dir = f"{lib_dir}/ORB" | ||
pp = { | ||
'H':'H_ONCV_PBE-1.0.upf', | ||
'Au':'Au_ONCV_PBE-1.0.upf', | ||
} | ||
basis = { | ||
'H':'H_gga_6au_100Ry_2s1p.orb', | ||
'Au':'Au_gga_7au_100Ry_4s2p2d1f.orb', | ||
} | ||
kpts = [3, 1, 3] | ||
parameters = { | ||
'calculation': 'scf', | ||
'nspin': 2, | ||
'xc': 'pbe', | ||
'ecutwfc': 100, | ||
'ks_solver': 'genelpa', | ||
'symmetry': 0, | ||
'vdw_method': 'd3_bj', | ||
'smearing_method': 'gaussian', | ||
'smearing_sigma': 0.001, | ||
'basis_type': 'lcao', | ||
'mixing_type': 'broyden', | ||
'scf_thr': 1e-6, | ||
'scf_nmax': 100, | ||
'kpts': kpts, | ||
'pp': pp, | ||
'basis': basis, | ||
'pseudo_dir': pseudo_dir, | ||
'basis_dir': basis_dir, | ||
'cal_force': 1, | ||
'cal_stress': 1, | ||
'out_stru': 1, | ||
'out_chg': 0, | ||
'out_mul': 0, | ||
'out_bandgap': 0, | ||
'efield_flag': 1, | ||
'dip_cor_flag': 1, | ||
'efield_dir': 1, | ||
'efield_pos_max': 0.7 | ||
} | ||
|
||
|
||
class AbacusDimer: | ||
"""Customize Dimer calculation by using ABACUS""" | ||
|
||
def __init__(self, init_Atoms, parameters, abacus='abacus', | ||
mpi=1, omp=1, directory='DIMER', | ||
traj_file='dimer.traj', | ||
init_eigenmode_method='displacement', | ||
displacement_vector: np.ndarray = None,): | ||
"""Initialize Dimer method by using ASE-ABACUS | ||
init_Atoms (Atoms object): starting image, can be from every way including NEB result | ||
parameters (dict): settings of abacus input parameters | ||
abacus (str): Abacus executable file. Default: 'abacus' | ||
directory (str): calculator directory name, for parallel calculation {directory}-rank{i} will be the directory name | ||
mpi (int): number of MPI for abacus calculator | ||
omp (int): number of OpenMP for abacus calculator | ||
traj_file (str): trajectory file name for dimer calculation, when running dimer calculation, trajetory will be written to this file, default is 'dimer.traj' | ||
init_eigenmode_method (str): dimer initial eigenmode method. Choose from 'displacement' and 'gauss'. | ||
displacement_vector (np.ndarray): displacement vector for dimer initial eigenmode. Only used when init_eigenmode_method is 'displacement' | ||
""" | ||
self.init_Atoms = init_Atoms | ||
self.parameters = parameters | ||
self.abacus = abacus | ||
self.mpi = mpi | ||
self.omp = omp | ||
self.directory = directory | ||
self.traj_file = traj_file | ||
self.init_eigenmode_method = init_eigenmode_method | ||
self.displacement_vector = displacement_vector | ||
|
||
def set_calculator(self): | ||
"""Set Abacus calculators""" | ||
os.environ['OMP_NUM_THREADS'] = f'{self.omp}' | ||
profile = AbacusProfile( | ||
argv=['mpirun', '-np', f'{self.mpi}', self.abacus]) | ||
out_directory = self.directory | ||
calc = Abacus(profile=profile, directory=out_directory, | ||
**self.parameters) | ||
return calc | ||
|
||
def set_d_mask_by_displacement(self): | ||
"""set mask by displacement""" | ||
print("=== Set mask by displacement vector where displacement is [0,0,0] ===") | ||
d_mask = self.displacement_vector != np.zeros(3) | ||
d_mask = d_mask[:,0].tolist() | ||
return d_mask | ||
|
||
def set_d_mask_by_constraint(self): | ||
"""set mask by constraint of Atoms""" | ||
print("=== Set mask by constraint read from init Atoms ===") | ||
dimer_init = self.init_Atoms | ||
const_list = dimer_init._get_constraints()[0] | ||
const_list = const_list.todict()['kwargs']['indices'] | ||
d_mask = [True] * len(dimer_init) | ||
for ind in const_list: | ||
d_mask[ind] = False | ||
return d_mask | ||
|
||
def run(self, fmax=0.05): | ||
"""run dimer calculation workflow""" | ||
dimer_init = self.init_Atoms | ||
dimer_init.calc = self.set_calculator() | ||
dimer_traj = Trajectory(self.traj_file, 'w', dimer_init) | ||
if self.init_eigenmode_method == "displacement": | ||
# d_mask = self.set_d_mask_by_displacement() # not the best | ||
d_mask = self.set_d_mask_by_constraint() | ||
d_control = DimerControl(initial_eigenmode_method=self.init_eigenmode_method, | ||
displacement_method="vector", | ||
mask=d_mask) | ||
d_atoms = MinModeAtoms(dimer_init, d_control) | ||
d_atoms.displace(displacement_vector=self.displacement_vector) | ||
elif self.init_eigenmode_method == "gauss": | ||
# leave a way for random displacement | ||
d_mask = self.set_d_mask_by_constraint() | ||
d_control = DimerControl(initial_eigenmode_method=self.init_eigenmode_method, | ||
mask=d_mask) | ||
d_atoms = MinModeAtoms(dimer_init, d_control) | ||
else: | ||
raise ValueError("init_eigenmode_method must be displacement or gauss") | ||
dimer_relax = MinModeTranslate(d_atoms, trajectory=dimer_traj) | ||
dimer_relax.run(fmax=fmax) | ||
|
||
if __name__ == "__main__": | ||
# running process | ||
# read initial guessed neb chain | ||
dimer_init = read(dimer_input_file) | ||
displacement_vector = np.load(displacement_input) | ||
dimer = AbacusDimer(dimer_init, parameters, abacus=abacus, | ||
mpi=mpi, omp=omp, | ||
init_eigenmode_method=init_eigenmode_method, | ||
displacement_vector=displacement_vector) | ||
dimer.run(fmax=fmax) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#!/bin/bash | ||
#SBATCH --nodes=1 | ||
#SBATCH --ntasks-per-node=16 | ||
#SBATCH --cpus-per-task=4 | ||
#SBATCH -J DIMER-ABACUS | ||
#SBATCH -o run_dimer.out | ||
#SBATCH -e run_dimer.err | ||
#SBATCH -p C064M0256G | ||
|
||
# JamesMisaka in 2023-11-14 | ||
# workflow of ase-abacus-dimer method | ||
# for one calculator, ntasks-per-node for mpi, cpus-per-task for openmp | ||
|
||
# in developer's PKU-WM2 server | ||
source /lustre/home/2201110432/apps/miniconda3/etc/profile.d/conda.sh | ||
conda activate gpaw-intel | ||
module load abacus/3.4.2-icx | ||
|
||
# if one just done neb calculation | ||
python neb2dimer.py neb.traj # or neb_latest.traj | ||
|
||
# Job state | ||
echo $SLURM_JOB_ID > JobRun.state | ||
echo "Start at $(date)" >> JobRun.state | ||
|
||
# just do dimer by run ! | ||
# do check your ntasks(mpi) and cpu(omp) setting is right | ||
python dimer_run.py | ||
|
||
echo "===== Dimer Calculation Done at $(date)! =====" | ||
|
||
# Job State | ||
echo "End at $(date)" >> JobRun.state |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash | ||
|
||
# full workflow controling NEB and Dimer calculation | ||
|
||
NEB_JOBID=$(sbatch neb_submit.sh) | awk '{print $4}' # get the jobid of neb and submit neb job | ||
echo "NEB_JOBID is ${NEB_JOBID}" | ||
DIMER_JOBID=$(sbatch --dependency=afterok:${NEB_JOBID} dimer_submit.sh) | awk '{print $4}' # submit dimer job after neb job done | ||
echo "Dimer Job Submitted and will be run after NEB job !" | ||
echo "DIMER_JOBID is ${DIMER_JOBID}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import numpy as np | ||
import sys | ||
from ase.io import Trajectory, read, write | ||
from ase.mep.neb import NEBTools | ||
|
||
def neb2dimer(neb_traj:list, n_max:int=0, out_traj='dimer_init.traj', out_vec='displacement_vector.npy', | ||
step_before_TS:int=1, step_after_TS:int=1): | ||
''' | ||
Transform neb chain to dimer init | ||
''' | ||
# you should use the latest neb chain or other single neb chain | ||
# but a full neb chain is permitted, in which the latest neb chain will be used | ||
if n_max == 0: | ||
print("=== n_max set to 0, get n_images by using NEBTools ===") | ||
n_images = NEBTools(neb_traj)._guess_nimages() | ||
elif (n_max >= 0) and (type(n_max) == int): | ||
n_images = n_max + 2 | ||
else: | ||
raise ValueError("n_max must be a non-negative integer") | ||
if len(neb_traj) < n_images: | ||
raise ValueError(f"n_images ={n_images} is larger than the length of neb_traj ={len(neb_traj)}") | ||
elif len(neb_traj) > n_images: | ||
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 ===") | ||
# used neb_chain is the final traj | ||
neb_chain = neb_traj[-n_images:] | ||
# get TS information from NEB chain | ||
barrier = NEBTools(neb_chain).get_barrier()[0] | ||
fmax = NEBTools(neb_chain).get_fmax() | ||
raw_barrier = max([image.get_potential_energy() for image in neb_chain]) | ||
TS_info = [(ind, image) | ||
for ind, image in enumerate(neb_chain) | ||
if image.get_potential_energy() == raw_barrier][0] | ||
print(f"=== Locate TS in {TS_info[0]} of 0-{n_images-1} images ===") | ||
print(f"=== TS Barrier: {barrier:.4f} (eV) ===") | ||
print(f"=== TS Fmax: {fmax:.4f} (eV/A) ===") | ||
print(f"=== TS images is output as {out_traj} for dimer init ===") | ||
# output TS of neb for dimer init | ||
write(out_traj, TS_info[1], format='traj') | ||
# output displancement vector by using the nearest two images of TS | ||
ind_before_TS = TS_info[0] - step_before_TS | ||
ind_after_TS = TS_info[0] + step_after_TS | ||
img_before = neb_chain[ind_before_TS] | ||
img_after = neb_chain[ind_after_TS] | ||
print(f"=== Displacement vector is generated by position minus from {ind_after_TS} image to {ind_before_TS} image ===") | ||
displacement_vector = (img_after.positions - img_before.positions) | ||
print(f"=== Displacement vector is output as {out_vec} for dimer init ===") | ||
np.save(out_vec,displacement_vector) | ||
return | ||
|
||
if __name__ == "__main__": | ||
msg = ''' | ||
neb2dimer.py is to make dimer inputfile by using neb result | ||
Usage: | ||
python neb2dimer.py [traj_file] ([n_max]) | ||
Notice that n_max = n_images - 2 | ||
These sctipt will output two files: | ||
dimer_init.traj: TS of neb chain for dimer init-structure | ||
displacement_vector.npy: displacement vector of dimer init | ||
''' | ||
if len(sys.argv) < 2: | ||
print(msg) | ||
elif len(sys.argv) == 2: | ||
if sys.argv == "--help" or sys.argv == "-h": | ||
print(msg) | ||
else: | ||
traj_file = sys.argv[1] | ||
neb_traj = Trajectory(traj_file) | ||
neb2dimer(neb_traj) | ||
else: | ||
traj_file = sys.argv[1] | ||
n_max = int(sys.argv[2]) | ||
neb_traj = Trajectory(traj_file) | ||
neb2dimer(neb_traj, n_max) |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Oops, something went wrong.