diff --git a/vaspy/incar.py b/vaspy/incar.py index 18b65ae..7d8ae6f 100644 --- a/vaspy/incar.py +++ b/vaspy/incar.py @@ -5,191 +5,210 @@ ======================================================================== Written by PytLab , October 2015 Updated by PytLab , July 2016 +Revised by jyxu , October 2019 ======================================================================== """ +import os +import sys + import logging +import warnings from vaspy import VasPy +from vaspy.vasp_para_db import * -class InCar(VasPy): - def __init__(self, filename='INCAR'): +class Param(VasPy): + def __init__(self, pname, pvalue='', pcomm=''): """ - Create a INCAR file class. + Create a INCAR parameter class. Example: - - >>> a = InCar() + >>> para = Param('SYSTEM') Class attributes descriptions ======================================================= Attribute Description ============ ======================================= - filename string, name of INCAR file + pname string, parameter name + pvalue string/int/flaot, parameter value + pcomm string, parameter comment ============ ======================================= + """ - super(self.__class__, self).__init__(filename) - self.filename = filename - self.load() + __slots__ = ('para', 'pname', 'pvalue', 'pcomm') - # Set logger. - self.__logger = logging.getLogger("vaspy.InCar") + self._pname, self._pvalue, self._pcomm = None, None, None - def load(self): - "Load all data in INCAR." - tot_pnames, tot_datas = [], [] - with open(self.filename, 'r') as f: - for line in f: - matched = self.rdata(line) - if matched: - pnames, datas = matched - tot_pnames.extend(pnames) - tot_datas.extend(datas) - # set attrs - for pname, data in zip(tot_pnames, tot_datas): - setattr(self, pname, data) - - # Set parameter names and data lists. -# sorted_pnames, sorted_datas = self.__sort_two_lists(tot_pnames, tot_datas) -# self.pnames = sorted_pnames -# self.datas = sorted_datas - self.pnames = tot_pnames - self.datas = tot_datas + self.pname = pname + self.pvalue, self.pcomm = str(pvalue), str(pcomm) - return + self.__logger = logging.getLogger("vaspy.Param") - def __sort_two_lists(self, list1, list2): - """ - Private helper function to sort two lists. - """ - assert len(list1) == len(list2) - # Sort the pairs according the entries of list1. - sorted_pairs = sorted(zip(list1, list2), key=lambda pair: pair[0]) - sorted_list1, sorted_list2 = [list(x) for x in zip(*sorted_pairs)] + @property + def pname(self): + """Parameter Name""" + return self._pname - return sorted_list1, sorted_list2 + @pname.setter + def pname(self, name): + """""" + name = name.upper() + if name not in INCAR_PARAMETERS.keys(): + raise ValueError("Unknown Parameter <%s> in VASP INCAR Database." %name) + self._pname = name - @staticmethod - def rdata(line): - "Get INCAR data(s) in a line." - line = line.strip() - if not line or line.startswith(('!', '#')): - return None + return + + @property + def pvalue(self): + """Parameter Value""" + return self._pvalue + + @pvalue.setter + def pvalue(self, value): + """""" + optional_values = [str(v) for v in INCAR_PARAMETERS[self.pname][1]] + + # check optional values, under development + ''' + if optional_values == [] or value in optional_values: + pass else: - if '#' in line: - line = line.split('#')[0].strip() - if '!' in line: - line = line.split('!')[0].strip() - elif '!' in line: - line = line.split('!')[0].strip() - # get parameter name and data - if ';' in line: - params = [param.strip() for param in line.split(';')] - else: - params = [line] - pnames, datas = [], [] - for param in params: - pname, data = [i.strip() for i in param.split('=')] - pnames.append(pname) - datas.append(data) + raise ValueError("Unknown Value <%s> for Parameter <%s>." \ + %(value, self.pname)) + ''' + + if value != '': + default_value = value + else: + default_value = INCAR_PARAMETERS[self.pname][0] - return pnames, datas + self._pvalue = str(default_value) - def set(self, pname, data): - """ - Set a named parameter of InCar object. + return - Example: - -------- - >>> incar_obj.set("ISIF", 2) - """ - if not hasattr(self, pname): - raise ValueError('%s is not in INCAR, ' + - 'Use add() instead.' % pname) - setattr(self, pname, str(data)) + @property + def pcomm(self): + """Parameter Comment""" + return self._pcomm - return + @pcomm.setter + def pcomm(self, comm): + """""" + if comm == '': + comm = " ".join((COMMSIGN, INCAR_PARAMETERS[self.pname][2])) + else: + if not comm.startswith(COMMSIGN): + comm = " ".join((COMMSIGN, comm)) - def add(self, pname, data): - """ - Add a new parameter name to InCar object. + self._pcomm = comm - Example: - -------- - >>> incar_obj.add("ISIF", 2) + return + + @property + def para(self): + """""" + return (self.pname, self.pvalue, self.pcomm) + + def __add__(self, another): """ - data = str(data) - if hasattr(self, pname): - msg = "{} is already in INCAR, set to {}".format(pname, data) - self.__logger.warning(msg) + Overload the add operator, a *Param* adds a *Param*/*Params* equal *Params*. + """ + if isinstance(another, Param): + return Params((self, another)) + elif isinstance(another, Params): + return Params((self,) + another.paras) else: - self.pnames.append(pname) - setattr(self, pname, data) + raise ValueError("Add must also be a *Param*/*Params* object.") - return + def __repr__(self): + return str(self.para) - def pop(self, pname): + +class Params(VasPy): + def __init__(self, paras=()): """ - Delete a parameter from InCar object. + Create a INCAR parameters class. + """ + # set logger + self.__logger = logging.getLogger("vaspy.Params") - Returns: - -------- - parameter name, parameter value. + if paras == (): + self.paras = () + elif isinstance(paras, list) or isinstance(paras, tuple): + self.paras = self.remove_duplicates(paras) + else: + self("%s must be a *list* or *tuple* object." %paras) + + def remove_duplicates(self, paras): + """Remove duplicates in paras, return a tuple""" + paras_list = [] + for para in paras: + if isinstance(para, Param): + pnames = [p.pname for p in paras_list] + if para.pname in pnames: + warnings.warn("Duplicate parameter " + \ + "%s, value %s(old) %s(new), the new one is not read."\ + %(para.pname, paras_list[pnames.index(para.pname)], \ + para.pvalue), RuntimeWarning) + else: + paras_list.append(para) + else: + raise ValueError("%s must be a *Param* object." %para) - Example: - -------- - >>> incar_obj.del("ISIF") - """ - if not hasattr(self, pname): - msg = "InCar has no parameter '{}'".format(pname) - self.__logger.warning(msg) - return + return tuple(paras_list) + + @property + def pnames(self): + """Return all parameters' names.""" + self._pnames = [p.pname for p in self.paras] - # Delete from pnames and datas. - idx = self.pnames.index(pname) - self.pnames.pop(idx) - data = self.datas.pop(idx) + return tuple(self._pnames) - # Delete attribute. - del self.__dict__[pname] + @property + def pvalues(self): + """""" + self._pvalues = [p.pvalue for p in self.paras] - return pname, data + return tuple(self._pvalues) - def compare(self, another): - """ - Function to compare two InCar objects. - - Parameters: - ----------- - another: Another InCar object. + def get_para(self, name): + """""" + return self.paras[self.pnames.index(name)] - Returns: - -------- - A tuple of two dictionaries containing difference informations. + def get_pvalue(self, name): + """""" + return self.pvalues[self.pnames.index(name)] + + def __compare(self, another): + """ + Private method to compare two Params object. """ - tot_pnames = set(self.pnames + another.pnames) + self_pnames, another_pnames = self.pnames, another.pnames + tot_pnames = set(list(self_pnames) + list(another_pnames)) self_dict, another_dict = {}, {} for pname in tot_pnames: # If both have, check the difference. - if (pname in self.pnames and pname in another.pnames): - self_data = getattr(self, pname) - another_data = getattr(another, pname) - if self_data != another_data: - self_dict.setdefault(pname, self_data) - another_dict.setdefault(pname, another_data) + if (pname in self_pnames and pname in another_pnames): + self_pvalue = self.get_pvalue(pname) + another_pvalue = self.get_pvalue(pname) + if self_pvalue != another_pvalue: + self_dict.setdefault(pname, self_pvalue) + another_dict.setdefault(pname, another_pvalue) else: # Only in this object. - if pname in self.pnames: - self_data = getattr(self, pname) - self_dict.setdefault(pname, self_data) + if pname in self_pnames: + self_pvalue = self.get_pvalue(pname) + self_dict.setdefault(pname, self_pvalue) another_dict.setdefault(pname, "") # Only in the other object. else: - another_data = getattr(another, pname) - another_dict.setdefault(pname, another_data) + another_pvalue = self.get_pvalue(pname) + another_dict.setdefault(pname, another_pvalue) self_dict.setdefault(pname, "") return self_dict, another_dict @@ -198,7 +217,8 @@ def __eq__(self, another): """ Overload euqal operator function. """ - self_dict, another_dict = self.compare(another) + self_dict, another_dict = self.__compare(another) + # print(self_dict, another_dict) if (not self_dict) and (not another_dict): return True @@ -214,20 +234,288 @@ def __ne__(self, another): else: return True - def tofile(self, filename=None): - "Create INCAR file." + def __add__(self, another): + """ + Overload add operator, append a *Param*/*Params* to a *Params* + """ + if isinstance(another, Param): + paras = self.paras + (another,) + elif isinstance(another, Params): + paras = self.paras + another.paras + + paras = self.remove_duplicates(paras) + + return Params(paras) + + def add(self, pname, pvalue=''): + """ + Add a new parameter to *Params* object. + Now only param object are supported. + Different from __add__, which does not change the *Param* object. + + Example: + -------- + >>> paras.add('ISIF', 2) + """ + para = Param(pname, pvalue) + paras = (self + para).paras + self.paras = paras + + return self + + def __sub__(self, another): + """ + Overload sub operator, remove a *Param*/*Params* to a *Params* + """ + if isinstance(another, Param): + paras = (para for para in self.paras if para.pname != another.pname) + elif isinstance(another, Params): + paras = (para for para in self.paras if para.pname not in another.pnames) + + self.paras = tuple(paras) + + return self + + def set(self, name, value): + """ + Set a named parameter of InCar object. + + Example: + -------- + >>> incar_obj.set("ISIF", 2) + """ + if name not in self.pnames: + raise ValueError('%s is not in INCAR, ' + 'Use add() instead.' % name) + + self.get_para(name).pvalue = str(value) + + return + + def pop(self, pname): + """ + Delete a parameter from InCar object. + + Returns: + -------- + parameter name, parameter value. + + Example: + -------- + >>> incar_obj.pop("ISIF") + """ + if pname not in self.pnames: + raise ValueError('%s is not in INCAR, ' + 'Use add() instead.' % name) + + # Delete from pnames and datas. + value = self.get_pvalue(pname) + paras = (self - self.get_para(pname)).paras + self.paras = paras + + return pname, value + + def __len__(self): + """ + Overload len operator, return the number of parameters. + """ + return len(self.paras) + + def __repr__(self): + """ + """ + return str(self.paras) + + +class InCar(Params): + def __init__(self, src='INCAR'): + """ + Create a INCAR file class. + + Example: + + >>> a = InCar() + + Class attributes descriptions + ======================================================= + Attribute Description + ============ ======================================= + src string/Params, incar parameter source + ============ ======================================= + """ + # super(self.__class__, self).__init__(src) + if src == 'INCAR': + if not os.path.exists('INCAR'): + src = None + + if src is None: + self.paras = () + else: + if isinstance(src, str): + if os.path.isfile(src): + self.filename = src + self.paras = self.load() + elif isinstance(src, Params): + self.paras = src.paras + + if self.paras != (): + self.paras = self.remove_duplicates(self.paras) + self.parasets = None + + # set logger + self.__logger = logging.getLogger("vaspy.InCar") + + def load(self): + "Load all data in INCAR." + tot_pnames, tot_pvalues, tot_pcomms = [], [], [] + with open(self.filename, 'r') as reader: + for line in reader: + matched = self.rdata(line) + if matched: + pnames, pvalues, pcomms = matched + tot_pnames.extend(pnames) + tot_pvalues.extend(pvalues) + tot_pcomms.extend(pcomms) + + paras_list = [] + for pname, pvalue, pcomm in zip(tot_pnames, tot_pvalues, tot_pcomms): + para = Param(pname, pvalue, pcomm) + paras_list.append(para) + + return tuple(paras_list) + + @staticmethod + def rdata(line): + "Get INCAR data(s) in a line." + line = line.strip() + if not line or line.startswith(('!', '#')): + return None + else: + pnames, pvalues, pcomms = [], [], [] + value, comm = '', '' + if '#' in line: + splitted_line = line.split('#') + value = splitted_line[0].strip() + comm = splitted_line[1].strip() + if '!' in value: + splitted_value = value.split('!') + value = splitted_value[0].strip() + comm = ' '.join(comm, splitted_value[1].strip()) + elif '!' in line: + splitted_line = line.split('!') + value = splitted_line[0].strip() + comm = splitted_line[1].strip() + if '#' in value: + splitted_value = value.split('#') + value = splitted_value[0].strip() + comm = ' '.join(comm, splitted_value[1].strip()) + else: + value = line + pcomms.append(comm) + # get parameter name and data + if ';' in value: + params = [param.strip() for param in value.split(';')] + else: + params = [value] + for param in params: + pname, pvalue = [i.strip() for i in param.split('=')] + pnames.append(pname) + pvalues.append(pvalue) + + return pnames, pvalues, pcomms + + def quickgen(self, tasks, **kargs): + """Quick generation of INCAR with built-in parameters.""" + # check task + tasks = tasks.strip().split('-') + + # check if SC in tasks + task_basic = ['SC'] + if 'SC' not in tasks: + task_basic.extend(tasks) + tasks = task_basic + + parasets = [] + for task in tasks: + if task in BUILTIN_PARASETS.keys(): + parasets.extend(BUILTIN_PARASETS[task][0]) + else: + msg = "Unsupported INCAR parameter set %s." %task + self.__logger.error(msg) + sys.exit(1) + + self.parasets = parasets + + # load parasets + self.incar_categories = INCAR_PARACATEGORIES + pnames = [] + for pset in self.parasets: + pnames.extend(list(self.incar_categories[pset])) + + paras_list = [] + for pname in pnames: + paras_list.append(Param(pname)) + + self.paras = tuple(paras_list) + + # special settings for some task + paras_list = [] + for task in tasks: + for para in BUILTIN_PARASETS[task][1]: + self.get_para(para[0]).pvalue = para[1] + + # add more parameters along with built-in parasets + for key, value in kargs.items(): + self.set(key, str(value)) + + return + + def tostr(self): + """""" content = '# Created by VASPy\n' - for pname in self.pnames: - if not hasattr(self, pname): - raise ValueError('Unknown parameter: %s' % pname) - data = str(getattr(self, pname)) - content += '%s = %s\n' % (pname, data) + if self.parasets: + tot_pnames = [] + for pset in self.parasets: + content += '# %s\n' %pset + pnames = INCAR_PARACATEGORIES[pset] + tot_pnames.extend(pnames) + for pname in pnames: + para = self.get_para(pname) + content += PARAFORMAT.format(para.pname, para.pvalue, para.pcomm) + content += '\n' + + other_pnames = [pname for pname in self.pnames \ + if pname not in tot_pnames] + if len(other_pnames) != 0: + content += '# Additional\n' + for pname in other_pnames: + para = self.get_para(pname) + content += PARAFORMAT.format(para.pname, para.pvalue, para.pcomm) + content += '\n' + else: + for para in self.paras: + content += PARAFORMAT.format(para.pname, para.pvalue, para.pcomm) - # Write to file. + return content + + def tofile(self, filename='INCAR', verbos=True): + "Create INCAR file." if filename is None: - filename = self.filename - with open(filename, 'w') as f: - f.write(content) + self.__logger.error("Filename to write INCAR is needed.") + else: + if os.path.exists(filename): + self.__logger.warning("%s exists will be overrided." %filename) + + content = self.tostr() + with open(filename, 'w') as writer: + writer.write(content) + + self.__logger.info("Succesfully write incar obejct to %s." %filename) + + if verbos: + print(content) return + + def __repr__(self): + """""" + return self.tostr() diff --git a/vaspy/vasp_para_db.py b/vaspy/vasp_para_db.py new file mode 100644 index 0000000..30053d9 --- /dev/null +++ b/vaspy/vasp_para_db.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Golabel Variables Descriptions (Most Frequently Used INCAR Parameters) +=============================================================================== + Attribute Description + ====================== =================================================== + COMMSIGN comment sign in INCAR file + PARAFORMAT format of parameter in INCAR + INCAR_PARAMETERS a dictionary of INCAR parameters + INCAR_PARACATEGORIES different groups of INCAR parameters + BUILTIN_PARASETS built-in parameter groups for quick-generation + ====================== =================================================== +""" + +COMMSIGN = '#' + +PARAFORMAT = " {:<12s} = {:<24s} {:=0 Nosé"], + "NBLOCK" : ["10" , (), + "update XDATCAR every NBLOCK fs"], + # Advanced Molecualr Dynamics + "MDALGO" : ["21" , (0, 1, 2, 3, 11, 21, 13), + "0 standard MD | 21 meta Nose-Hoover"], + "LBLUEOUT" : [".FALSE." , (".TRUE.", ".FALSE."), + "print out free-energy gradient"], + # US + "HILLS_BIN" : ["15000" , (), + "bias potential update interval"], + # slow-grownth + "INCREM" : ["0" , (), + "increment in slow-grownth algo"], + # Smearing + "ISMEAR" : ["0" , (-5, 0, 1), + "-5 DOS | 0 large cell | 1 metal"], + "SIGMA" : ["0.05" , (), + "smearing parameter"], + # ALGO + "ALGO" : ["Fast" , ("ALL", "Normal", "Fast", "Very Fast"), + "algorithms for electronic self-consistent"], + "IALGO" : ["48" , (), + "8 CG | 48 RMM-DIIS"], + "LREAL" : ["Auto" , ("Auto", ".TRUE.", ".FALSE."), + "if calculation done in real spcae"], + "ISYM" : ["2" , (0, 1, 2, 3), + "0 off | 1 on | 2 charge | 3 no charge"], + "NSIM" : ["4" , (), + "blocked band update"], + # ALGO-Mixing + "IMIX" : ["4" , (), + "0 no mix | 1 Kerker | 2 Tchebycheff | 4 Broyden2"], + "AMIX" : ["0.4" , (), + "linear mixing parameter"], + "AMIN" + "BMIX" : ["1.0" , (), + "cutoff wave vector for Kerker mixing scheme"], + "AMIX_MAG" : ["1.6" , (), + "linear mixing parameter for magnetization"], + "BMIX_MAG" : ["1.0" , (), + ""], + "WC" : ["1000" , (), + "weight factor in each step in Broyden"], + "INIMIX" : ["1" , (), + "initial mixing in Broyden"], + "MIXPRE" : ["1" , (), + "preconditioning in Broyden"], + "MAXMIX" : ["-45" , (), + "maximum number steps stored in Broyden mixer"], + # BAND + "LORBIT" : ["11" , (), + "PAW radii for projected DOS"], + "NEDOS" : ["2001" , (), + "DOSCAR points"], + # vdW + "IVDW" : ["11" , (), + "DFT-D3 without BJ damping"], + "VDW_s6" : ["1.0" , (), + "s6-scaling parameter (kept fix at 1.0)"], + "VDW_s8" : ["0.7875" , (), + "s8 for PBE"], + "VDW_a1" : ["0.4289" , (), + "a1 damping parameter for PBE"], + "VDW_a2" : ["4.4407" , (), + "a2 damping parameter for PBE"], + # DFT+U(J) + "LDAU" : [".TRUE." , (".TRUE.", ".FALSE."), + "Enable DFTU calculation"], + "LDAUTYPE" : ["2" , (1, 2, 4), + "1 Liechtenstein | *2 Dudarev | 4"], + "LDAUL" : ["" , (), + "-1 off | 1 p | 2 d | 3 f"], + "LDAUU" : ["" , (), + "coulomb interaction"], + "LDAUJ" : ["" , (), + "exchange interaction"], + "LDAUPRINT" : ["2" , (0, 1, 2), + "*0 silent | 1 occupancy | 2 idem"], + "LMAXMIX" : ["4" , (2, 4, 6), + "twice the max l-quantum number"], + # Hybrid Functional + "LHFCALC" : [".TRUE." , (), + "Enable HF calculation"], + "AEXX" : ["0.25" , (), + "Hartree-Fock percentage"], + "PREFOCK" : ["Accurate" , (), + ""], + "LMAXFOCK" : ["4" , (4, 6), + "4 s/p | 6 f"], + "HFSCREEN" : ["0.2" , (), + "HSE06"], + "TIME" : ["0.4" , (), + ""], + # POTCAR related + "POMASS" : ["" , (), + "mass each atomic species in a.u."], + "ZVAL" : ["" , (), + "valence for each atomic species"], + "RWIGS" : ["" , (), + "Wigner-Seitz radius"], + } + +INCAR_PARACATEGORIES = { + # catname paranames + "GENERAL" : ["SYSTEM", "NWRITE", "ISTART"], + "WRITING" : ["LCHARG", "LWAVE"], + "PARALLEL" : ["NPAR"], + "WAVEFUNC" : ["INIWAV"], + "MAGNETIC" : ["ISPIN"], + "ELECTRONIC": ["ENCUT", "PREC", "EDIFF", "NELM", "NELMIN"], + "IONIC" : ["EDIFFG", "NSW", "IBRION", "ISIF", "POTIM"], + "MD" : ["TEBEG", "TEEND", "SMASS", "NBLOCK"], + "SMEARING" : ["ISMEAR", "SIGMA"], + "VDW" : ["IVDW"], + "VDWBJ" : ["IVDW", "VDW_s6", "VDW_s8", "VDW_a1", "VDW_a2"], + "ALGO" : ["ALGO", "LREAL", "ISYM"], + "UJ" : ["LDAU", "LDAUTYPE", "LDAUL", "LDAUU", "LDAUJ", "LDAUPRINT", "LMAXMIX"], + "HF" : ["LHFCALC", "AEXX", "PREFOCK", "LMAXFOCK", "HFSCREEN", "TIME"], + } + +BASIC_PARAS = ["GENERAL", "WRITING", "PARALLEL", "ELECTRONIC", "MAGNETIC", \ + "SMEARING", "ALGO", "IONIC"] +BUILTIN_PARASETS = { + # task paras specail settings + 'SC' : [BASIC_PARAS , + []], + 'MD' : [["MD"] , + [('IBRION', 0), ('ISIF', 0), ('POTIM', 1.0), ('NSW', 15000), + ('PREC', 'Normal'), ('EDIFFG', -0.01), + ('ALGO', 'Very Fast'), ('ISYM', 0), + ('NELM', '120')]], + 'BAND' : [["BAND"] , []], + 'VDW' : [["VDW"] , []], + 'VDWBJ' : [["VDWBJ"] , []], + 'UJ' : [["UJ"] , []], + 'HF' : [["HF"] , + [('ISYM', 3), ('ALGO', 'ALL')]], + } +