From ead8c97047fc8b9c130c4d2c6ca9074b483df474 Mon Sep 17 00:00:00 2001
From: Thomas Young Cluster RITS Do NOT reset password
 <mmm0586@login04.thomas.ucl.ac.uk>
Date: Mon, 21 Oct 2019 22:03:42 +0100
Subject: [PATCH 1/5] Revise incar.py with two new classes *Param* and
 *Params*, still compatible with previous usage, and make INCAR writing simple
 and extendable with VASP INCAR Parameters DataBase (vasp_para_db.py).

---
 vaspy/incar.py        | 567 +++++++++++++++++++++++++++++++-----------
 vaspy/vasp_para_db.py | 173 +++++++++++++
 2 files changed, 597 insertions(+), 143 deletions(-)
 create mode 100644 vaspy/vasp_para_db.py

diff --git a/vaspy/incar.py b/vaspy/incar.py
index 18b65ae..64a3fa4 100644
--- a/vaspy/incar.py
+++ b/vaspy/incar.py
@@ -5,191 +5,210 @@
 ========================================================================
 Written by PytLab <shaozhengjiang@gmail.com>, October 2015
 Updated by PytLab <shaozhengjiang@gmail.com>, July 2016
+Revised by jyxu <jxu15@qub.ac.uk>, 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):
+        """
+        Overload the add operator, a *Param* adds a *Param*/*Params* equal *Params*.
         """
-        data = str(data)
-        if hasattr(self, pname):
-            msg = "{} is already in INCAR, set to {}".format(pname, data)
-            self.__logger.warning(msg)
+        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):
         """
-        tot_pnames = set(self.pnames + another.pnames)
+        Private method to compare two Params object.
+        """
+        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,281 @@ 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=None):
+        """
+        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 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
+
+        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('-')
+        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..0afb121
--- /dev/null
+++ b/vaspy/vasp_para_db.py
@@ -0,0 +1,173 @@
+#!/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}  {:<s}\n"
+
+INCAR_PARAMETERS = {
+        # paraname   defaultvalue paracomm
+        # GENERAL
+        "SYSTEM"        :  ["example"      , (),
+            "system name"],
+        "NWRITE"        :  ["2"            , (0, 1, 2, 3, 4),
+            "verbosity flag, 0|1|*2|3|4"],
+        "ISTART"        :  ["0"            , (0, 1),
+            "0 New | 1 Restart"],
+        "ICHARG"        :  ["2"            , (0, 1, 2, 4),
+            "0 orbital | 1 CHGCAR | *2 superposition | 4 POT"],
+        # parallel
+        "NPAR"          :  ["6"            , (),
+                ""],
+        # MAGNETIC
+        "ISPIN"         :  ["2"            , (1, 2),
+            "*1 no | 2 yes"],
+        "MAGMOM"        :  [" "            , (),
+            "array, spin per atom"],
+        # WAVEFUNC
+        "INIWAV"        :  ["1"            , (0, 1),
+            "0 jellium | *1 random"],
+        "LWAVE"         :  [".FALSE."      , (".TRUE.", ".FALSE."),
+            "if write WAVECAR"],
+        # CHARGE
+        "LCHARG"        :  [".FALSE."      , (".TRUE.", ".FALSE."),
+            "if write CHGCAR"],
+        # ELECTRONIC
+        "ENCUT"         :  ["450"          , (),
+            "energy cutoff"],
+        "PREC"          :  ["Accurate"     ,
+                ("Low", "Medium", "High", "Normal", "Accurate", "Single"),
+            "precision"],
+        "EDIFF"         :  ["1.0E-5"       , (),
+            "stopping criterion for electronic updates"],
+        "NELM"          :  ["300"          , (),
+            "maximium number of ionic updates"],
+        "NELMIN"        :  ["6"            , (),
+            "minimium number of ionic updates"],
+        # IONIC
+        "EDIFFG"        :  ["-0.05"        , (),
+            "stopping criterion for ionic updates"],
+        "NSW"           :  ["1000"         , (),  
+            "number of steps for ionic updates"],
+        "IBRION"        :  ["2"            , (0,1,2,3,5),
+            "0 MD | 1 quasi-Newton | 2 CG | 3 damped-MD | 5 FC"],
+        "ISIF"          :  ["2"       , (0, 1, 2, 3, 4, 5, 6),
+            "0 MD | *2 | 3 lat opt"],
+        "POTIM"         :  ["0.2"          , (),
+            "ionic step size / MD time step"],
+        # Molecular Dynamics
+        "TEBEG"         :  ["300"          , (),
+            "begin temperature"],
+        "TEEND"         :  ["300"          , (),
+            "end temperature"],
+        "SMASS"         :  ["3"            , (-3, -2, -1, 0, 3),
+            "controls velocities during AIMD"],
+        "NBLOCK"        :  ["10"           , (),
+            "update XDATCAR every NBLOCK fs"],
+        # 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"],
+        # 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"          , (),
+                ""],
+
+        }
+
+INCAR_PARACATEGORIES = {
+        # catname paranames
+        "GENERAL"   :  ["SYSTEM", "NWRITE", "ISTART"],
+        "PARALLEL"  :  ["NPAR"],
+        "WAVEFUNC"  :  ["INIWAV", "LWAVE"],
+        "CHARGE"    :  ["LCHARG"],
+        "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", "LDAUU", "LDAUJ", "LDAUPRINT", "LMAXMIX"],
+        "HF"        :  ["LHFCALC", "AEXX", "PREFOCK", "LMAXFOCK", "HFSCREEN", "TIME"],
+        }
+
+BASIC_PARAS = ["GENERAL", "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),
+             ('ALGO', 'Very Fast'), ('NELM', '60')]],
+        'BAND'     :    [["BAND"]       , []],
+        'VDW'      :    [["VDW"]        , []],
+        'VDWBJ'    :    [["VDWBJ"]      , []],
+        'UJ'       :    [["UJ"]         , []],
+        'HF'       :    [["HF"]         , 
+            [('ISYM', 3), ('ALGO', 'ALL')]],
+        }
+

From 1fe280a51fe73f99605d0e6a5c17895262b5ca17 Mon Sep 17 00:00:00 2001
From: jyxu <ahcigar@foxmail.com>
Date: Tue, 22 Oct 2019 20:50:54 +0100
Subject: [PATCH 2/5] Make *InCar* obeject default value as INCAR, compatable
 with previous usage

---
 vaspy/incar.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vaspy/incar.py b/vaspy/incar.py
index 64a3fa4..b1a7244 100644
--- a/vaspy/incar.py
+++ b/vaspy/incar.py
@@ -326,7 +326,7 @@ def __repr__(self):
 
 
 class InCar(Params):
-    def __init__(self, src=None):
+    def __init__(self, src='INCAR'):
         """
         Create a INCAR file class.
 

From df98ab70b5a73c49f63b50bc6fff765b69b61e21 Mon Sep 17 00:00:00 2001
From: jyxu <ahcigar@foxmail.com>
Date: Wed, 23 Oct 2019 10:29:50 +0100
Subject: [PATCH 3/5] add electronic mixing algo para in vasp_para_db.py

---
 vaspy/vasp_para_db.py | 52 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 47 insertions(+), 5 deletions(-)

diff --git a/vaspy/vasp_para_db.py b/vaspy/vasp_para_db.py
index 0afb121..aedc31c 100644
--- a/vaspy/vasp_para_db.py
+++ b/vaspy/vasp_para_db.py
@@ -73,9 +73,20 @@
         "TEEND"         :  ["300"          , (),
             "end temperature"],
         "SMASS"         :  ["3"            , (-3, -2, -1, 0, 3),
-            "controls velocities during AIMD"],
+            "controls velocities in MD, -3 | -2 | -1 | >=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"],
@@ -90,6 +101,28 @@
                 "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"],
@@ -110,7 +143,8 @@
         "LDAU"          :  [".TRUE."       , (".TRUE.", ".FALSE."),
                 "Enable DFTU calculation"],
         "LDAUTYPE"      :  ["2"            , (1, 2, 4),
-                "1 Liechtenstein | *2 Dudarev | 4"], "LDAUL"         :  [""             , (),
+                "1 Liechtenstein | *2 Dudarev | 4"], 
+        "LDAUL"         :  [""             , (),
                 "-1 off | 1 p | 2 d | 3 f"],
         "LDAUU"         :  [""             , (),
                 "coulomb interaction"],
@@ -133,7 +167,13 @@
                 "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 = {
@@ -161,8 +201,10 @@
         'SC'       :    [BASIC_PARAS    , 
             []],
         'MD'       :    [["MD"]         , 
-            [('IBRION', 0), ('ISIF', 0), ('POTIM', 1.0), ('NSW', 15000),
-             ('ALGO', 'Very Fast'), ('NELM', '60')]],
+            [('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"]      , []],

From 2a9358b7a113e83fc67a0dd38161e27f3c65cb2b Mon Sep 17 00:00:00 2001
From: jyxu <ahcigar@foxmail.com>
Date: Wed, 23 Oct 2019 11:35:08 +0100
Subject: [PATCH 4/5] fix a bug which happens when no src is used when creating
 *InCar* object

---
 vaspy/incar.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/vaspy/incar.py b/vaspy/incar.py
index b1a7244..4858611 100644
--- a/vaspy/incar.py
+++ b/vaspy/incar.py
@@ -342,6 +342,10 @@ def __init__(self, src='INCAR'):
           ============  =======================================
         """
         # super(self.__class__, self).__init__(src)
+        if src == 'INCAR':
+            if not os.path.exists('INCAR'):
+                src = None
+
         if src is None:
             self.paras = ()
         else:
@@ -352,7 +356,8 @@ def __init__(self, src='INCAR'):
             elif isinstance(src, Params):
                 self.paras = src.paras
 
-        self.paras = self.remove_duplicates(self.paras)
+        if self.paras != ():
+            self.paras = self.remove_duplicates(self.paras)
         self.parasets = None
 
         # set logger

From f74184818645df99a510933e1a1cee4c0fde003d Mon Sep 17 00:00:00 2001
From: hsulab <ahcigar@foxmail.com>
Date: Wed, 29 Jul 2020 22:31:27 +0100
Subject: [PATCH 5/5] update few parameters used in INCAR

---
 vaspy/incar.py        |  4 +++-
 vaspy/vasp_para_db.py | 23 ++++++++++++++++-------
 2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/vaspy/incar.py b/vaspy/incar.py
index 4858611..7d8ae6f 100644
--- a/vaspy/incar.py
+++ b/vaspy/incar.py
@@ -426,10 +426,12 @@ 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
+            tasks = task_basic
 
         parasets = []
         for task in tasks:
diff --git a/vaspy/vasp_para_db.py b/vaspy/vasp_para_db.py
index aedc31c..30053d9 100644
--- a/vaspy/vasp_para_db.py
+++ b/vaspy/vasp_para_db.py
@@ -30,7 +30,9 @@
             "0 orbital | 1 CHGCAR | *2 superposition | 4 POT"],
         # parallel
         "NPAR"          :  ["6"            , (),
-                ""],
+                "NCORE=ncores/NPAR"],
+        "NCORE"         :  ["8"            , (),
+                "cores/orbital, Better use NPAR"],
         # MAGNETIC
         "ISPIN"         :  ["2"            , (1, 2),
             "*1 no | 2 yes"],
@@ -39,11 +41,12 @@
         # WAVEFUNC
         "INIWAV"        :  ["1"            , (0, 1),
             "0 jellium | *1 random"],
-        "LWAVE"         :  [".FALSE."      , (".TRUE.", ".FALSE."),
-            "if write WAVECAR"],
         # CHARGE
+        # Writing
         "LCHARG"        :  [".FALSE."      , (".TRUE.", ".FALSE."),
             "if write CHGCAR"],
+        "LWAVE"         :  [".FALSE."      , (".TRUE.", ".FALSE."),
+            "if write WAVECAR"],
         # ELECTRONIC
         "ENCUT"         :  ["450"          , (),
             "energy cutoff"],
@@ -56,6 +59,12 @@
             "maximium number of ionic updates"],
         "NELMIN"        :  ["6"            , (),
             "minimium number of ionic updates"],
+        # dipole correction
+        "IDIPOL"        :  ["3"            , (),
+            "surface 3"],
+        # hole/electron
+        "NELECT"        :  ["0"            , (),
+            "number of electrons"],
         # IONIC
         "EDIFFG"        :  ["-0.05"        , (),
             "stopping criterion for ionic updates"],
@@ -179,9 +188,9 @@
 INCAR_PARACATEGORIES = {
         # catname paranames
         "GENERAL"   :  ["SYSTEM", "NWRITE", "ISTART"],
+        "WRITING"   :  ["LCHARG", "LWAVE"],
         "PARALLEL"  :  ["NPAR"],
-        "WAVEFUNC"  :  ["INIWAV", "LWAVE"],
-        "CHARGE"    :  ["LCHARG"],
+        "WAVEFUNC"  :  ["INIWAV"],
         "MAGNETIC"  :  ["ISPIN"],
         "ELECTRONIC":  ["ENCUT", "PREC", "EDIFF", "NELM", "NELMIN"],
         "IONIC"     :  ["EDIFFG", "NSW", "IBRION", "ISIF", "POTIM"],
@@ -190,11 +199,11 @@
         "VDW"       :  ["IVDW"],
         "VDWBJ"     :  ["IVDW", "VDW_s6", "VDW_s8", "VDW_a1", "VDW_a2"],
         "ALGO"      :  ["ALGO", "LREAL", "ISYM"],
-        "UJ"        :  ["LDAU", "LDAUTYPE", "LDAUU", "LDAUJ", "LDAUPRINT", "LMAXMIX"],
+        "UJ"        :  ["LDAU", "LDAUTYPE", "LDAUL", "LDAUU", "LDAUJ", "LDAUPRINT", "LMAXMIX"],
         "HF"        :  ["LHFCALC", "AEXX", "PREFOCK", "LMAXFOCK", "HFSCREEN", "TIME"],
         }
 
-BASIC_PARAS = ["GENERAL", "PARALLEL", "ELECTRONIC", "MAGNETIC", \
+BASIC_PARAS = ["GENERAL", "WRITING", "PARALLEL", "ELECTRONIC", "MAGNETIC", \
                 "SMEARING", "ALGO", "IONIC"]
 BUILTIN_PARASETS = {
         # task           paras          specail settings