import abc
import copy
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.utils import str_list
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, Fort188File
from gvasp.common.setting import WorkDir, ConfigManager
from gvasp.neb.path import IdppPath, LinearPath
logger = logging.getLogger(__name__)
[docs]
def write_wrapper(file):
def inner(func):
@wraps(func)
def wrapper(*args, **kargs):
self = args[0]
func(self, *args[1:], **kargs)
if file == 'INCAR':
self.incar.write(name=file)
elif file == 'KPOINTS':
self.kpoints.write(name=file)
elif file == 'submit.script':
with open(file, 'w') as f:
f.write(self.submit.pipe(self.submit.submit2write))
return wrapper
return inner
[docs]
def end_symbol(func):
@wraps(func)
def wrapper(*args, **kargs):
self = args[0]
func(self, *args[1:], **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)
# init kpoints
self.kpoints = KPOINTS.from_strings(['AutoGenerated \n', '0 \n', 'Gamma \n', '1 1 1 \n', '0 0 0 \n'])
# 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')
self.submit = SubmitFile(self._submit)
self.finish = None
[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) = 'PAW_PBE', continuous: bool = False, low: bool = False,
print_end: bool = True, analysis: bool = False, vdw: bool = False, sol: bool = False,
gamma: bool = False, mag: bool = False, hse: bool = False, static: bool = False, nelect=None,
points: int = 21):
"""
generate main method, subclass should inherit or overwrite
"""
if continuous:
self._generate_cdir(hse=hse)
self._generate_POSCAR(continuous=continuous)
self._generate_KPOINTS(low=low, gamma=gamma, points=points)
self._generate_POTCAR(potential=potential)
self._generate_INCAR(low=low, vdw=vdw, sol=sol, nelect=nelect, mag=mag, hse=hse, static=static)
self._generate_fort(continuous=continuous)
self._generate_submit(low=low, analysis=analysis, gamma=gamma)
self._generate_info(potential=potential)
if low and print_end and self.__class__.__name__ in ['OptTask', 'ConTSTask']:
print(f'{RED}low first{RESET}')
def _generate_cdir(self, directory=None, files=None, **kargs):
Path(directory).mkdir(exist_ok=True)
for file in files:
shutil.copy(file, directory)
os.chdir(directory)
self.incar = INCAR('INCAR')
def _generate_info(self, potential):
"""
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] == ['F', 'F', 'F'], axis=1) == 3)
print(f'{element:^10s}'
f'{self.structure.atoms.size[element]:>6d}'
f'{element_tf:>6d}(F) '
f'{p} ', end='')
if self.incar.LHFCALC is None:
print(f'{self.incar.LDAUL[index]:>2d} '
f'{self.incar.LDAUU[index] - self.incar.LDAUJ[index]}')
else:
print(f'{-1:>2d} '
f'{0.0}')
index += 1
print()
if self.__class__.__name__ == 'BandTask':
print(f'KPoints: line-mode for band structure')
else:
print(f'KPoints: [{str_list(self.kpoints.number)}]')
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 self.incar.LHFCALC is not None:
print(f'{RED}--> HSE06 calculation{RESET}')
if self.kpoints.number == [1, 1, 1]:
print(f'{RED}--> Gamma-point calculation{RESET}')
if self.incar.NSW == 1:
print(f'{RED}--> Static calculation{RESET}')
def _generate_INCAR(self, vdw=False, sol=False, nelect=None, mag=False, hse=False, static=False, **kargs):
"""
generate by copy incar_template, modify the +U parameters
"""
if self.incar.LDAU and not hse:
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)
if mag:
try:
self.incar.MAGMOM = list(self.structure.atoms.spin)
except TypeError:
logger.warning("Can't obtain the `MAGMOM` field, setting `MAG calculation` failed")
if hse:
self.incar.LHFCALC = True
self.incar.HFSCREEN = 0.2
self.incar.TIME = 0.4
self.incar.PRECFOCK = 'Fast'
if static:
self.incar.NSW = 1
@write_wrapper(file='KPOINTS')
def _generate_KPOINTS(self, gamma=False, **kargs):
"""
generate KPOINTS, Gamma-centered mesh, number is autogenerated
"""
if gamma:
_number = [1, 1, 1]
else:
_number = list(map(str, KPOINTS.min_number(structure=self.structure)))
self.kpoints.number = _number
def _generate_POSCAR(self, continuous=False, **kargs):
"""
generate POSCAR from only one *.xsd file, and register `self.structure` and `self.elements`
"""
if not continuous:
CurrentDir = Path.cwd() # added for pytest
xsd_files = list(set(list(CurrentDir.glob('*.xsd')) + 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.structure.atoms.spin = self.incar.MAGMOM
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')
@write_wrapper(file='submit.script')
def _generate_submit(self, gamma=False, **kargs):
"""
generate job.submit automatically
"""
kpoints = getattr(self, '_kpoints', self.kpoints)
gamma = True if kpoints.number == [1, 1, 1] else gamma
self.submit.title = self.title
self.submit.task = self.__class__.__name__.replace('Task', '')
self.submit.incar = self.incar
self.submit.kpoints = kpoints
self.submit = self.submit.build
self.submit.vasp_line = self.submit.vasp_gam_line if gamma else self.submit.vasp_std_line
self.submit.submit2write = ['head_lines', '\n', 'env_lines', '\n', 'pre_process_lines', '\n', 'vasp_line',
'run_line', '\n', 'post_process_lines', '\n', 'finish_line']
def _generate_fort(self, continuous=False):
"""
Only Valid for Con-TS Task
"""
pass
[docs]
class Animatable(metaclass=abc.ABCMeta):
[docs]
@staticmethod
@abc.abstractmethod
def movie(name):
"""
Abstractmethod: Generate the *.arc file to be loaded by Material Studio
Args:
name (str): the name of the output *.arc file
"""
pass
[docs]
class XDATMovie(Animatable):
def __new__(cls, *args, **kwargs):
if cls.__name__ == 'XDATMovie':
raise TypeError(f'<{cls.__name__} class> may not be instantiated')
return super().__new__(cls)
[docs]
@staticmethod
def movie(name='movie.arc'):
"""
make arc file to visualize the optimization steps
"""
XDATCAR('XDATCAR').movie(name=name)
[docs]
class NormalTask(BaseTask):
def __new__(cls, *args, **kwargs):
if cls.__name__ == 'NormalTask':
raise TypeError(f'<{cls.__name__} class> may not be instantiated')
return super().__new__(cls)
[docs]
def generate(self, *args, **kargs):
"""
fully inherit BaseTask's generate
"""
super().generate(*args, **kargs)
[docs]
class OptTask(NormalTask, XDATMovie):
"""
Optimization task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='opt_cal', files=None, hse=False):
if files is None:
files = ['INCAR', 'CONTCAR']
if hse:
directory = 'hse'
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, low=False, **kargs):
"""
Inherit BaseTask's _generate_INCAR, but add wrapper to write INCAR
"""
super()._generate_INCAR(low=low, **kargs)
self.incar._ENCUT = self.incar.ENCUT
self.incar._PREC = self.incar.PREC
if low:
self.incar.ENCUT = 300.
self.incar.PREC = 'Low'
@write_wrapper(file='KPOINTS')
def _generate_KPOINTS(self, low=False, **kargs):
"""
Inherit BaseTask's _generate_INCAR, but add wrapper to write INCAR
"""
super()._generate_KPOINTS(low=False, **kargs)
self._kpoints = copy.deepcopy(self.kpoints)
if low:
self.kpoints.number = [1, 1, 1]
@write_wrapper(file='submit.script')
def _generate_submit(self, low=False, **kargs):
"""
Rewrite NormalTask's _generate_submit
"""
super()._generate_submit(low=low, **kargs)
if low:
self.submit.submit2write = ['head_lines', '\n', 'env_lines', '\n', 'pre_process_lines', '\n',
f'#{"/Low Calculation/".center(50, "-")}# \n',
'vasp_gam_line', 'run_line', '\n',
f'#{"/Normal Prepare/".center(50, "-")}# \n',
'check_success_lines', 'backup_lines', 'modify_lines', '\n',
f'#{"/Normal Calculation/".center(50, "-")}# \n',
'vasp_line', 'run_line', '\n', 'post_process_lines', '\n',
'finish_line']
[docs]
class ConTSTask(OptTask, XDATMovie):
"""
Constrain transition state (Con-TS) calculation task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='ts_cal', files=None, **kargs):
if files is None:
files = ['INCAR', 'CONTCAR']
check_files = ['fort.188', 'OUTCAR']
for file in check_files:
if not Path(file).exists():
raise FileNotFoundError(f'{file} is not exist, continuous task failed!')
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, low=False, **kargs):
"""
Inherit OptTask's _generate_INCAR, modify parameters and rewrite to INCAR
parameters setting:
IBRION = 1
"""
super()._generate_INCAR(low=low, **kargs)
self.incar.IBRION = 1
def _generate_fort(self, continuous=False):
"""
Implement <_generate_fort method> => generate the fort.188 file
"""
if not continuous:
constrain_atom = [atom for atom in self.structure.atoms if atom.constrain]
if len(constrain_atom) != 2:
raise ConstrainError(
f'Number of constrain atoms should equal to 2, but this is `{len(constrain_atom)}`')
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')
logger.info(f'Constrain Information: {constrain_atom[0].order + 1}-{constrain_atom[1].order + 1}, '
f'distance = {distance:.4f}')
else:
fort188 = Fort188File('../fort.188')
outcar = OUTCAR('../OUTCAR')
fort188.constrain = fort188.constrain[:2] + [str(outcar.last_condist)]
fort188.write()
logger.info(f'Constrain Information: {fort188.constrain[0]}-{fort188.constrain[1]}, '
f'distance = {float(fort188.constrain[2]):.4f}')
def _generate_submit(self, low=False, **kargs):
"""
Add constrain information to OptTask._generate_submit
"""
fort188 = Fort188File('fort.188')
self.submit.constrain = fort188.constrain
super()._generate_submit(low=low, **kargs)
[docs]
class ChargeTask(NormalTask):
"""
Charge calculation task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='chg_cal', files=None, **kargs):
if files is None:
files = ['INCAR', 'CONTCAR']
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = 1
LAECHG = .TRUE.
LCHARG = .TRUE.
"""
super()._generate_INCAR(**kargs)
self.incar.IBRION = 1
self.incar.LAECHG = True
self.incar.LCHARG = True
@write_wrapper(file='submit.script')
def _generate_submit(self, analysis=False, gamma=False, low=False):
"""
generate job.submit automatically
"""
super()._generate_submit(gamma=gamma)
if analysis:
ChargeTask.apply_analysis(self.submit)
[docs]
@staticmethod
def apply_analysis(submit):
try:
submit.submit2write = submit.submit2write[:-3]
except ValueError:
pass
submit._task, submit.task = submit.task, 'Charge'
_check_success_lines = submit.pipe(['check_success_lines'])
submit.task = submit._task
submit.submit2write += [f'#{"/Charge Analysis Calculation/".center(50, "-")}# \n',
f'{_check_success_lines}',
'bader_lines', '\n', 'spin_lines', '\n', 'post_process_lines', '\n', 'finish_line']
[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(NormalTask):
"""
Work Function calculation task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='workfunc', files=None, hse=False):
if files is None:
files = ['INCAR', 'CONTCAR']
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
Inherit BaseTask's _generate_INCAR, modify parameters and write to INCAR
parameters setting:
IBRION = -1
NSW = 1
LVHAR = .TRUE.
"""
super()._generate_INCAR(**kargs)
self.incar.IBRION = -1
self.incar.NSW = 1
self.incar.LVHAR = True
[docs]
class BandTask(NormalTask):
"""
Band Structure calculation task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='band_cal', files=None, hse=False):
if files is None:
files = ['INCAR', 'CONTCAR', 'CHGCAR']
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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, low=False, 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(NormalTask):
"""
Density of States (DOS) calculation task manager, subclass of NormalTask
"""
def _generate_cdir(self, directory='dos_cal', files=None, hse=False):
if files is None:
files = ['INCAR', 'CONTCAR', 'CHGCAR']
super()._generate_cdir(directory=directory, files=files)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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(NormalTask, Animatable):
"""
Frequency calculation task manager, subclass of NormalTask
"""
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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(NormalTask, XDATMovie):
"""
ab-initio molecular dynamics (AIMD) calculation task manager, subclass of NormalTask
"""
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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]
class STMTask(NormalTask):
"""
Scanning Tunneling Microscope (STM) image modelling calculation task manager, subclass of NormalTask
"""
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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 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().__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, mag=False, hse=False, static=False):
"""
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, mag=mag, hse=hse, static=static)
self._generate_info(potential=potential)
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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 = Path.cwd()
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(NormalTask, XDATMovie):
@write_wrapper(file='INCAR')
def _generate_INCAR(self, **kargs):
"""
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()._generate_INCAR(**kargs)
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]
class SequentialTask:
"""
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
self.submit = None
[docs]
@end_symbol
@write_wrapper(file='submit.script')
def generate(self, potential='PAW_PBE', low=False, analysis=False, vdw=False, sol=False, gamma=False, nelect=None,
hse=False, static=False):
task = OptTask()
task.generate(potential=potential, low=low, print_end=False, vdw=vdw, sol=sol, gamma=gamma, nelect=nelect,
hse=hse, static=static)
self.submit = task.submit
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)
self.submit.submit2write = self.submit.submit2write[:-3]
self.submit.submit2write += [f'#{"/Charge Calculation/".center(50, "-")}# \n',
'check_success_lines',
'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', '\n',
'vasp_line', 'run_line', '\n', 'post_process_lines', '\n', 'finish_line']
if analysis:
ChargeTask.apply_analysis(self.submit)
if self.end == 'wf':
low_string = 'low first, ' if low else ''
print(f'{RED}Sequential Task: opt => {self.end}, ' + low_string + RESET)
self.submit.submit2write = self.submit.submit2write[:-3]
self.submit.submit2write += [f'#{"/WorkFunc Calculation/".center(50, "-")}# \n',
'check_success_lines',
'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', '\n',
'vasp_line', 'run_line', '\n', 'post_process_lines', '\n', 'finish_line']
if self.end == 'dos':
self.submit.submit2write = self.submit.submit2write[:-3]
self.submit._task, self.submit.task = self.submit.task, 'Charge'
_check_success_lines = self.submit.pipe(['check_success_lines'])
self.submit.task = self.submit._task
self.submit.submit2write += [f'#{"/DOS Calculation/".center(50, "-")}# \n',
f'{_check_success_lines}',
'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', '\n',
'vasp_line', 'run_line', '\n', 'post_process_lines', '\n', 'finish_line']
if self.end not in ['opt', 'chg', 'wf', 'dos']:
raise TypeError(f'Unsupported Sequential Task to {self.end}, should be [opt, chg, wf, dos]')
[docs]
class OutputTask:
[docs]
@staticmethod
def output(name):
"""
Transform the results to .xsd file
"""
XSDFile.write(contcar='CONTCAR', outcar='OUTCAR', name=name)