import abc
import logging
import os
import shutil
from functools import wraps
from pathlib import Path
import numpy as np
import yaml
from seekpath import get_path
from gvasp.common.base import Atom
from gvasp.common.constant import GREEN, YELLOW, RESET, RED
from gvasp.common.error import XSDFileNotFoundError, TooManyXSDFileError, ConstrainError
from gvasp.common.file import POSCAR, OUTCAR, ARCFile, XSDFile, KPOINTS, POTCAR, XDATCAR, CHGCAR, AECCAR0, AECCAR2, \
CHGCAR_mag, INCAR, SubmitFile, CONTCAR
from gvasp.common.setting import WorkDir, ConfigManager
from gvasp.neb.path import IdppPath, LinearPath
logger = logging.getLogger(__name__)
[docs]def write_wrapper(func):
@wraps(func)
def wrapper(self, *args, **kargs):
func(self, *args, **kargs)
self.incar.write(name="INCAR")
return wrapper
[docs]def end_symbol(func):
@wraps(func)
def wrapper(self, *args, **kargs):
func(self, *args, **kargs)
if kargs.get("print_end", True):
print(f"------------------------------------------------------------------")
return wrapper
[docs]class BaseTask(metaclass=abc.ABCMeta):
"""
Task Base class, load config.json, generate INCAR, KPOINTS, POSCAR and POTCAR
Note: subclass should have `generate` method
"""
Template, PotDir, Scheduler = ConfigManager().template, ConfigManager().potdir, ConfigManager().scheduler
UValueBase = ConfigManager().UValue
def __init__(self):
self.title = None
self.structure = None
self.elements = None
self.valence = None
# set INCAR template
self._incar = self.Template if self._search_suffix(".incar") is None else self._search_suffix(".incar")
self.incar = INCAR(self._incar)
# set UValue template
self.UValuePath = self.UValueBase if self._search_suffix(".uvalue") is None else self._search_suffix(".uvalue")
with open(self.UValuePath) as f:
self.UValue = yaml.safe_load(f.read())
# set submit template
self.submit = self.Scheduler if self._search_suffix(".submit") is None else self._search_suffix(".submit")
[docs] @staticmethod
def get_all_parents():
def get_parent(path: Path):
parent = path.parent
if path != parent:
yield path
yield from get_parent(parent)
else:
yield path
return [path for path in get_parent(WorkDir.absolute())]
@staticmethod
def _search_suffix(suffix):
"""
Search file with the special suffix in all parents directories
Args:
suffix (str): specify the suffix
Returns:
file (Path): file path with the special suffix
"""
for directory in BaseTask.get_all_parents():
try:
for file in directory.iterdir():
try:
if file.is_file() and file.name.endswith(f"{suffix}"):
return file
except PermissionError:
continue
except PermissionError:
continue
else:
return
[docs] @end_symbol
@abc.abstractmethod
def generate(self, potential: (str, list), continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
generate main method, subclass should inherit or overwrite
"""
if continuous:
self._generate_cdir()
self._generate_POSCAR(continuous=continuous)
self._generate_KPOINTS(gamma=gamma)
self._generate_POTCAR(potential=potential)
self._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self._generate_submit(gamma=gamma)
self._generate_info(potential=potential, gamma=gamma)
def _generate_cdir(self, dir=None, files=None):
Path(dir).mkdir(exist_ok=True)
for file in files:
shutil.copy(file, dir)
os.chdir(dir)
self.incar = INCAR("INCAR")
def _generate_info(self, potential, gamma):
"""
generate short information
"""
print(f"---------------general info (#{self.__class__.__name__})-----------------------")
print(f"Elements Total Relax potential orbital UValue")
potential = [potential] * len(self.elements) if isinstance(potential, str) else potential
index = 0
for element, p in zip(self.elements, potential):
element_index = [index for index, formula in enumerate(self.structure.atoms.formula) if formula == element]
element_tf = np.sum(
np.sum(self.structure.atoms.selective_matrix[element_index] == ["T", "T", "T"], axis=1) == 3)
print(f"{element:^10s}"
f"{self.structure.atoms.size[element]:>6d}"
f"{element_tf:>6d}(T) "
f"{p} "
f"{self.incar.LDAUL[index]:>2d} "
f"{self.incar.LDAUU[index] - self.incar.LDAUJ[index]}")
index += 1
print()
if gamma:
print(f"KPoints: [1 1 1]")
elif self.__class__.__name__ == "BandTask":
print(f"KPoints: line-mode for band structure")
else:
print(f"KPoints: {KPOINTS.min_number(structure=self.structure)}")
print()
print(f"{GREEN}Job Name: {self.title}{RESET}")
print(f"{YELLOW}INCAR template: {self._incar}{RESET}")
print(f"{YELLOW}UValue template: {self.UValuePath}{RESET}")
print(f"{YELLOW}Submit template: {self.submit}{RESET}")
if getattr(self.incar, "IVDW", None) is not None:
print(f"{RED}--> VDW-correction: IVDW = {self.incar.IVDW}{RESET}")
if getattr(self.incar, "LSOL", None) is not None:
print(f"{RED}--> Solvation calculation{RESET}")
if getattr(self.incar, "NELECT", None) is not None:
print(f"{RED}--> Charged system, NELECT = {self.incar.NELECT}{RESET}")
if gamma:
print(f"{RED}--> Gamma-point calculation{RESET}")
def _generate_INCAR(self, vdw, sol, nelect):
"""
generate by copy incar_template, modify the +U parameters
"""
if self.incar.LDAU:
LDAUL, LDAUU, LDAUJ = [], [], []
for element in self.elements:
if self.UValue.get(f'Element {element}', None) is not None:
LDAUL.append(self.UValue[f'Element {element}']['orbital'])
LDAUU.append(self.UValue[f'Element {element}']['U'])
LDAUJ.append(self.UValue[f'Element {element}']['J'])
else:
LDAUL.append(-1)
LDAUU.append(0.0)
LDAUJ.append(0.0)
logger.warning(f"{element} not found in UValue, +U parameters set default: LDAUL = -1, "
f"LDAUU = 0.0, "
f"LDAUJ = 0.0")
self.incar.LDAUL = LDAUL
self.incar.LDAUU = LDAUU
self.incar.LDAUJ = LDAUJ
self.incar.LMAXMIX = 6 if 3 in self.incar.LDAUL else 4
if vdw:
self.incar.IVDW = 12
if sol:
self.incar.LSOL = True # only consider water, not set EB_K
if nelect is not None:
t_valence = sum([num * valence for (_, num), valence in zip(self.structure.atoms.elements, self.valence)])
self.incar.NELECT = t_valence + float(nelect)
def _generate_KPOINTS(self, gamma):
"""
generate KPOINTS, Gamma-centered mesh, number is autogenerated
"""
with open("KPOINTS", "w") as f:
f.write("AutoGenerated \n")
f.write("0 \n")
f.write("Gamma \n")
if gamma:
f.write(f"1 1 1 \n")
else:
f.write(f"{' '.join(list(map(str, KPOINTS.min_number(structure=self.structure))))} \n")
f.write("0 0 0 \n")
def _generate_POSCAR(self, continuous, method=None, check_overlap=None):
"""
generate POSCAR from only one *.xsd file, and register `self.structure` and `self.elements`
"""
if not continuous:
xsd_files = list(WorkDir.glob("*.xsd"))
if not len(xsd_files):
raise XSDFileNotFoundError("*.xsd file is not found, please check workdir")
elif len(xsd_files) > 1:
raise TooManyXSDFileError("exist more than one *.xsd file, please check workdir")
xsd_file = XSDFile(xsd_files[0])
self.title = xsd_file.name.stem
self.structure = xsd_file.structure
self.elements = list(self.structure.atoms.size.keys())
self.structure.write_POSCAR(name="POSCAR")
else:
self.title = f"continuous-{self.__class__.__name__}"
self.structure = CONTCAR("CONTCAR").structure
self.elements = list(self.structure.atoms.size.keys())
self.structure.write_POSCAR(name="POSCAR")
def _generate_POTCAR(self, potential):
"""
generate POTCAR automatically, call the `cat` method of POTCAR
"""
potcar = POTCAR.cat(potentials=potential, elements=self.elements, potdir=self.PotDir)
self.valence = potcar.valence
potcar.write(name="POTCAR")
def _generate_submit(self, gamma=False, low=False, analysis=False):
"""
generate job.submit automatically
"""
content = SubmitFile(self.submit).strings
with open("submit.script", "w") as g:
for line in content:
if line.startswith("#SBATCH -J"):
g.write(f"#SBATCH -J {self.title} \n")
else:
g.write(line)
if gamma:
with open("temp.sh", "w") as f:
f.write("sed -i 's/vasp_std/vasp_gam/g' submit.script \n")
os.system("bash temp.sh")
os.remove("temp.sh")
[docs]class OutputTask(object):
[docs] @staticmethod
def output(name):
"""
Transform the results to .xsd file
"""
XSDFile.write(contcar="CONTCAR", outcar="OUTCAR", name=name)
[docs]class Animatable(metaclass=abc.ABCMeta):
[docs] @staticmethod
@abc.abstractmethod
def movie(name):
"""
make arc file to visualize the optimization steps
"""
XDATCAR("XDATCAR").movie(name=name)
[docs]class OptTask(BaseTask, Animatable):
"""
Optimization task manager, subclass of BaseTask
"""
[docs] @end_symbol
def generate(self, potential="PAW_PBE", continuous=False, low=False, print_end=True, vdw=False, sol=False,
gamma=False, nelect=None):
"""
rewrite BaseTask's generate
"""
if continuous:
self._generate_cdir()
self._generate_POSCAR(continuous)
self._generate_KPOINTS(gamma)
self._generate_POTCAR(potential=potential)
self._generate_INCAR(low=low, vdw=vdw, sol=sol, nelect=nelect)
self._generate_submit(low=low, gamma=gamma)
self._generate_info(potential=potential, gamma=gamma)
if low and print_end:
print(f"{RED}low first{RESET}")
def _generate_cdir(self, dir="opt_cal", files=None):
if files is None:
files = ["INCAR", "CONTCAR"]
super(OptTask, self)._generate_cdir(dir=dir, files=files)
@write_wrapper
def _generate_INCAR(self, low, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, but add wrapper to write INCAR
"""
super(OptTask, self)._generate_INCAR(vdw, sol, nelect)
self.incar._ENCUT = self.incar.ENCUT
if low:
self.incar.ENCUT = 300.
def _generate_submit(self, low=False, gamma=False, analysis=False):
"""
generate job.submit automatically
"""
super(OptTask, self)._generate_submit(low=low, analysis=analysis, gamma=gamma)
run_command = SubmitFile(self.submit).run_command
with open("submit.script", "a+") as g:
if low:
g.write("\n"
"#----------/Low Option/----------#\n"
"success=`grep accuracy OUTCAR | wc -l`"
"if [ $success -ne 1 ];then"
" echo 'Optimization Task Failed!'"
" exit 1"
"fi"
"cp POSCAR POSCAR_300 \n"
"cp CONTCAR POSCAR \n"
"cp OUTCAR OUTCAR_300 \n"
"mv CONTCAR CONTCAR_300 \n"
f"sed -i 's/ENCUT = 300.0/ENCUT = {self.incar._ENCUT}/' INCAR\n"
f"\n"
f"{run_command}")
[docs] @staticmethod
def movie(name="movie.arc"):
"""
fully inherit BaseTask's movie
"""
Animatable.movie(name=name)
[docs]class ChargeTask(BaseTask):
"""
Charge calculation task manager, subclass of BaseTask
"""
[docs] @end_symbol
def generate(self, potential="PAW_PBE", continuous=False, analysis=False, vdw=False, sol=False, gamma=False,
nelect=None):
"""
rewrite BaseTask's generate
"""
if continuous:
self._generate_cdir()
self._generate_POSCAR(continuous)
self._generate_KPOINTS(gamma)
self._generate_POTCAR(potential=potential)
self._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self._generate_submit(analysis=analysis, gamma=gamma)
self._generate_info(potential=potential, gamma=gamma)
def _generate_cdir(self, dir="chg_cal", files=None):
if files is None:
files = ["INCAR", "CONTCAR"]
super(ChargeTask, self)._generate_cdir(dir=dir, files=files)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 1
LAECHG = .TRUE.
LCHARG = .TRUE.
"""
super(ChargeTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 1
self.incar.LAECHG = True
self.incar.LCHARG = True
def _generate_submit(self, analysis=False, gamma=False, low=False):
"""
generate job.submit automatically
"""
super(ChargeTask, self)._generate_submit(gamma=gamma)
if analysis:
ChargeTask.apply_analysis()
[docs] @staticmethod
def apply_analysis():
with open("submit.script", "a+") as g:
g.write("\n"
"#----------/Charge Analysis Option/----------#\n"
"success=`grep accuracy OUTCAR | wc -l`"
"if [ $success -ne 1 ];then"
" echo 'Charge Task Failed!'"
" exit 1"
"fi"
"gvasp sum \n"
"bader CHGCAR -ref CHGCAR_sum \n"
"\n"
"gvasp split \n"
"gvasp grd -d -1 \n")
[docs] @staticmethod
def split():
"""
split CHGCAR to CHGCAR_tot && CHGCAR_mag
"""
CHGCAR("CHGCAR").split()
[docs] @staticmethod
def sum():
"""
sum AECCAR0 and AECCAR2 to CHGCAR_sum
"""
aeccar0 = AECCAR0("AECCAR0")
aeccar2 = AECCAR2("AECCAR2")
chgcar_sum = aeccar0 + aeccar2
chgcar_sum.write()
[docs] @staticmethod
def to_grd(name="vasp.grd", Dencut=250):
"""
transform CHGCAR_mag to grd file
"""
CHGCAR_mag("CHGCAR_mag").to_grd(name=name, DenCut=Dencut)
[docs]class WorkFuncTask(BaseTask):
"""
Work Function calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(WorkFuncTask, self).generate(potential=potential, continuous=continuous, vdw=vdw, sol=sol, gamma=gamma,
nelect=nelect)
def _generate_cdir(self, dir="workfunc", files=None):
if files is None:
files = ["INCAR", "CONTCAR"]
super(WorkFuncTask, self)._generate_cdir(dir=dir, files=files)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
ISTART = 1
ICHARG = 11
IBRION = -1
NSW = 1
LORBIT = 12
NEDOS = 2000
"""
super(WorkFuncTask, self)._generate_INCAR(vdw, sol, nelect)
self.incar.IBRION = -1
self.incar.NSW = 1
self.incar.LVHAR = True
[docs]class BandTask(BaseTask):
"""
Band Structure calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, nelect=None, gamma=False):
"""
fully inherit BaseTask's generate
"""
super(BandTask, self).generate(potential=potential, continuous=continuous, vdw=vdw, sol=sol, nelect=nelect)
def _generate_cdir(self, dir="band_cal", files=None):
if files is None:
files = ["INCAR", "CONTCAR", "CHGCAR"]
super(BandTask, self)._generate_cdir(dir=dir, files=files)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
ISTART = 1
ICHARG = 11
IBRION = -1
NSW = 1
LORBIT = 12
NEDOS = 2000
"""
super(BandTask, self)._generate_INCAR(vdw, sol, nelect)
self.incar.ISTART = 1
self.incar.ICHARG = 11
self.incar.IBRION = -1
self.incar.NSW = 1
self.incar.LCHARG = False
if getattr(self.incar, "LAECHG", None) is not None:
del self.incar.LAECHG
def _generate_KPOINTS(self, gamma=False, points=21):
"""
generate KPOINTS in line-mode
"""
lattice = self.structure.lattice.matrix
positions = self.structure.atoms.frac_coord
numbers = self.structure.atoms.number
spglib_structure = (lattice, positions, numbers)
path = get_path(structure=spglib_structure)
KLabel = path['point_coords']
KPath = path['path']
with open("KPOINTS", "w") as f:
f.write("K Along High Symmetry Lines \n")
f.write(f"{points} \n")
f.write(f"Line-Mode \n")
f.write(f"Rec \n")
for (start, end) in KPath:
start_str = [format(item, "8.5f") for item in KLabel[start]]
end_str = [format(item, "8.5f") for item in KLabel[end]]
f.write(f"{' '.join(start_str)}\t!{start} \n")
f.write(f"{' '.join(end_str)}\t!{end} \n")
f.write("\n")
[docs]class DOSTask(BaseTask):
"""
Density of States (DOS) calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(DOSTask, self).generate(potential=potential, continuous=continuous, vdw=vdw, sol=sol, gamma=gamma,
nelect=nelect)
def _generate_cdir(self, dir="dos_cal", files=None):
if files is None:
files = ["INCAR", "CONTCAR", "CHGCAR"]
super(DOSTask, self)._generate_cdir(dir=dir, files=files)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
ISTART = 1
ICHARG = 11
IBRION = -1
NSW = 1
LORBIT = 12
NEDOS = 2000
"""
super(DOSTask, self)._generate_INCAR(vdw, sol, nelect)
self.incar.ISTART = 1
self.incar.ICHARG = 11
self.incar.IBRION = -1
self.incar.NSW = 1
self.incar.LORBIT = 12
self.incar.NEDOS = 2000
self.incar.LCHARG = False
if getattr(self.incar, "LAECHG", None) is not None:
del self.incar.LAECHG
[docs]class FreqTask(BaseTask, Animatable):
"""
Frequency calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(FreqTask, self).generate(potential=potential, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 5
ISYM = 0
NSW = 1
NFREE = 2
POTIM = 0.015
"""
super(FreqTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 5
self.incar.ISYM = 0
self.incar.NSW = 1
self.incar.NFREE = 2
self.incar.POTIM = 0.015
[docs] @staticmethod
def movie(file="OUTCAR", freq="image"):
"""
visualize the frequency vibration, default: image
"""
outcar = OUTCAR(file)
outcar.animation_freq(freq=freq)
[docs]class MDTask(BaseTask, Animatable):
"""
ab-initio molecular dynamics (AIMD) calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(MDTask, self).generate(potential=potential, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 0
NSW = 100000
POTIM = 0.5
SMASS = 2.
MDALGO = 2
TEBEG = 300.
TEEND = 300.
"""
super(MDTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 0
self.incar.NSW = 100000
self.incar.POTIM = 0.5
self.incar.SMASS = 2.
self.incar.MDALGO = 2
self.incar.TEBEG = 300.
self.incar.TEEND = 300.
[docs] @staticmethod
def movie(name="movie.arc"):
"""
fully inherit BaseTask's movie
"""
super().movie(name=name)
[docs]class STMTask(BaseTask):
"""
Scanning Tunneling Microscope (STM) image modelling calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(STMTask, self).generate(potential=potential, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
ISTART = 1
IBRION = -1
NSW = 0
LPARD = .TRUE.
NBMOD = -3
EINT = 5.
LSEPB = .FALSE.
LSEPK = .FALSE.
"""
super(STMTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.ISTART = 1
self.incar.IBRION = -1
self.incar.NSW = 0
self.incar.LPARD = True
self.incar.NBMOD = -3
self.incar.EINT = 5.
self.incar.LSEPB = False
self.incar.LSEPK = False
[docs]class ConTSTask(BaseTask, Animatable):
"""
Constrain transition state (Con-TS) calculation task manager, subclass of BaseTask
"""
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(ConTSTask, self).generate(potential=potential, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
self._generate_fort()
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 1
"""
super(ConTSTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 1
def _generate_fort(self):
constrain_atom = [atom for atom in self.structure.atoms if atom.constrain]
if len(constrain_atom) != 2:
raise ConstrainError("Number of constrain atoms should equal to 2")
distance = Atom.distance(constrain_atom[0], constrain_atom[1], lattice=self.structure.lattice)
with open("fort.188", "w") as f:
f.write("1 \n")
f.write("3 \n")
f.write("6 \n")
f.write("3 \n")
f.write("0.03 \n")
f.write(f"{constrain_atom[0].order + 1} {constrain_atom[1].order + 1} {distance:.4f}\n")
f.write("0 \n")
print(f"Constrain Information: {constrain_atom[0].order + 1}-{constrain_atom[1].order + 1}, "
f"distance = {distance:.4f}")
[docs] @staticmethod
def movie(name="movie.arc"):
"""
fully inherit BaseTask's movie
"""
super().movie(name=name)
[docs]class NEBTask(BaseTask, Animatable):
"""
Nudged Elastic Band (NEB) calculation (no-climbing) task manager, subclass of BaseTask
"""
def __init__(self, ini_poscar=None, fni_poscar=None, images=4):
super(NEBTask, self).__init__()
self.ini_poscar = ini_poscar
self.fni_poscar = fni_poscar
self.images = images
self.structure = POSCAR(self.ini_poscar).structure
self.elements = list(zip(*self.structure.atoms.elements))[0]
[docs] @staticmethod
def sort(ini_poscar, fni_poscar):
"""
Tailor the atoms' order for neb task
@param:
ini_poscar: initial POSCAR file name
fni_poscar: final POSCAR file name
"""
POSCAR.align(ini_poscar, fni_poscar)
[docs] @end_symbol
def generate(self, method="linear", check_overlap=True, potential="PAW_PBE", vdw=False, sol=False, gamma=False,
nelect=None):
"""
Overwrite BaseTask's generate, add `method` and `check_overlap` parameters
"""
self._generate_POSCAR(method=method, check_overlap=check_overlap)
self._generate_KPOINTS(gamma)
self._generate_POTCAR(potential=potential)
self._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self._generate_info(potential=potential, gamma=gamma)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 3
POTIM = 0.
SPRING = -5.
LCLIMB = .FALSE.
ICHAIN = 0
IOPT = 3
MAXMOVE = 0.03
IMAGES =
"""
super(NEBTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 3
self.incar.POTIM = 0.
self.incar.SPRING = -5.
self.incar.LCLIMB = False
self.incar.ICHAIN = 0
self.incar.IOPT = 3
self.incar.MAXMOVE = 0.03
self.incar.IMAGES = self.images
def _generate_POSCAR(self, method=None, check_overlap=None, continuous=None):
"""
Generate NEB-task images && check their structure overlap
"""
if method == "idpp":
self._generate_idpp()
elif method == "linear":
self._generate_liner()
else:
raise NotImplementedError(f"{method} has not been implemented for NEB task")
if check_overlap:
NEBTask._check_overlap()
def _generate_idpp(self):
"""
Generate NEB-task images by idpp method (J. Chem. Phys. 140, 214106 (2014))
"""
idpp_path = IdppPath.from_linear(self.ini_poscar, self.fni_poscar, self.images)
idpp_path.run()
idpp_path.write()
logger.info("Improved interpolation of NEB initial guess has been generated.")
def _generate_liner(self):
"""
Generate NEB-task images by linear interpolation method
"""
linear_path = LinearPath(self.ini_poscar, self.fni_poscar, self.images)
linear_path.run()
linear_path.write()
logger.info("Linear interpolation of NEB initial guess has been generated.")
@staticmethod
def _search_neb_dir(workdir=None):
"""
Search neb task directories from workdir
"""
if workdir is None:
workdir = WorkDir
neb_dirs = []
for directory in workdir.iterdir():
if Path(directory).is_dir() and Path(directory).stem.isdigit():
neb_dirs.append(directory)
return sorted(neb_dirs)
@staticmethod
def _check_overlap():
"""
Check if two atoms' distance is too small, (following may add method to tailor their distances)
"""
logger.info("Check structures overlap")
neb_dirs = NEBTask._search_neb_dir()
for image in neb_dirs:
structure = POSCAR(Path(f"{image}/POSCAR")).structure
logger.info(f"check {image.stem} dir...")
structure.check_overlap()
logger.info("All structures don't have overlap")
[docs] @staticmethod
def monitor():
"""
Monitor tangent, energy and barrier in the NEB-task
"""
neb_dirs = NEBTask._search_neb_dir()
ini_energy = 0.
print("image tangent energy barrier")
for image in neb_dirs:
outcar = OUTCAR(f"{image}/OUTCAR")
if not int(image.stem):
ini_energy = outcar.last_energy
barrier = outcar.last_energy - ini_energy
print(f" {image.stem} \t {outcar.last_tangent:>10.6f} \t {outcar.last_energy} \t {barrier:.6f}")
[docs] @staticmethod
def movie(name="movie.arc", file="CONTCAR", workdir=None):
"""
Generate arc file from images/[POSCAR|CONTCAR] files
"""
neb_dirs = NEBTask._search_neb_dir(workdir)
structures = []
for image in neb_dirs:
posfile = "CONTCAR" if file == "CONTCAR" and Path(f"{image}/CONTCAR").exists() else "POSCAR"
structures.append(POSCAR(f"{image}/{posfile}").structure)
ARCFile.write(name=name, structure=structures, lattice=structures[0].lattice)
[docs]class DimerTask(BaseTask, Animatable):
[docs] def generate(self, potential="PAW_PBE", continuous=False, vdw=False, sol=False, gamma=False, nelect=None):
"""
fully inherit BaseTask's generate
"""
super(DimerTask, self).generate(potential=potential, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
@write_wrapper
def _generate_INCAR(self, vdw, sol, nelect):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 3
POTIM = 0.
ISYM = 0
ICHAIN = 2
DdR = 0.005
DRotMax = 10
DFNMax = 1.
DFNMin = 0.01
IOPT = 2
"""
super(DimerTask, self)._generate_INCAR(vdw=vdw, sol=sol, nelect=nelect)
self.incar.IBRION = 3
self.incar.POTIM = 0.
self.incar.ISYM = 0
self.incar.ICHAIN = 2
self.incar.DdR = 0.005
self.incar.DRotMax = 10
self.incar.DFNMax = 1.
self.incar.DFNMin = 0.01
self.incar.IOPT = 2
[docs] @staticmethod
def movie(name="movie.arc"):
super().movie(name=name)
[docs]class SequentialTask(object):
"""
Apply Sequential Task from `opt => chg` or `opt => dos`
"""
def __init__(self, end):
"""
Args:
end: specify the end task, optional: [opt, chg, dos]
"""
self.end = end
[docs] @end_symbol
def generate(self, potential="PAW_PBE", low=False, analysis=False, vdw=False, sol=False, gamma=False, nelect=None):
task = OptTask()
task.generate(potential=potential, low=low, print_end=False, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect)
if self.end == "chg" or self.end == "dos":
low_string = "low first, " if low else ""
analysis_string = "apply analysis" if analysis else ""
print(f"{RED}Sequential Task: opt => {self.end}, " + low_string + analysis_string + RESET)
run_command = SubmitFile("submit.script").run_command
with open("submit.script", "a+") as g:
g.write("\n"
"#----------/Charge Option/----------#\n"
"success=`grep accuracy OUTCAR | wc -l`"
"if [ $success -ne 1 ];then"
" echo 'Optimization Task Failed!'"
" exit 1"
"fi"
"mkdir chg_cal \n"
"cp OUTCAR OUTCAR_backup \n"
"cp INCAR KPOINTS POTCAR chg_cal \n"
"cp CONTCAR chg_cal/POSCAR \n"
f"sed -i '/IBRION/c\ IBRION = 1' chg_cal/INCAR \n"
f"sed -i '/LCHARG/c\ LCHARG = .TRUE.' chg_cal/INCAR \n"
f"sed -i '/LCHARG/a\ LAECHG = .TRUE.' chg_cal/INCAR \n"
f"cd chg_cal || return \n"
f"\n"
f"{run_command}")
if analysis:
ChargeTask.apply_analysis()
if self.end == "wf":
low_string = "low first, " if low else ""
print(f"{RED}Sequential Task: opt => {self.end}, " + low_string + RESET)
run_command = SubmitFile("submit.script").run_command
with open("submit.script", "a+") as g:
g.write("\n"
"#----------/WorkFunc Option/----------#\n"
"success=`grep accuracy OUTCAR | wc -l`"
"if [ $success -ne 1 ];then"
" echo 'Optimization Task Failed!'"
" exit 1"
"fi"
"mkdir workfunc \n"
"cp OUTCAR OUTCAR_backup \n"
"cp INCAR KPOINTS POTCAR workfunc \n"
"cp CONTCAR workfunc/POSCAR \n"
f"sed -i '/IBRION/c\ IBRION = -1' workfunc/INCAR \n"
f"sed -i '/NSW/c\ NSW = 1' workfunc/INCAR \n"
f"sed -i '/NSW/a\ LVHAR = .TRUE.' workfunc/INCAR \n"
f"cd workfunc || return \n"
f"\n"
f"{run_command}")
if self.end == "dos":
run_command = SubmitFile("submit.script").run_command
with open("submit.script", "a+") as g:
g.write("\n"
"#----------/DOS Option/----------#\n"
"success=`grep accuracy OUTCAR | wc -l`"
"if [ $success -ne 1 ];then"
" echo 'Charge Task Failed!'"
" exit 1"
"fi"
"mkdir dos_cal \n"
"cp OUTCAR OUTCAR_backup \n"
"cp INCAR KPOINTS POTCAR CHGCAR dos_cal \n"
"cp CONTCAR dos_cal/POSCAR \n"
f"sed -i '/ISTART/c\ ISTART = 1' dos_cal/INCAR \n"
f"sed -i '/NSW/c\ NSW = 1' dos_cal/INCAR \n"
f"sed -i '/IBRION/c\ IBRION = -1' dos_cal/INCAR \n"
f"sed -i '/LCHARG/c\ LCHARG = .FALSE.' dos_cal/INCAR \n"
f"sed -i '/LCHARG/a\ LAECHG = .FALSE.' dos_cal/INCAR \n"
f"sed -i '/+U/i\ ICHARG = 11' dos_cal/INCAR \n"
f"sed -i '/ICHARG/a\ LORBIT = 12' dos_cal/INCAR \n"
f"sed -i '/ICHARG/a\ NEDOS = 2000' dos_cal/INCAR \n"
f"cd dos_cal || return \n"
f"\n"
f"{run_command}")
if self.end not in ['opt', 'chg', 'wf', 'dos']:
raise TypeError(f"Unsupported Sequential Task to {self.end}, should be [opt, chg, wf, dos]")