############################### # This file is part of PyLaDa. # # Copyright (C) 2013 National Renewable Energy Lab # # PyLaDa is a high throughput computational platform for Physics. It aims to make it easier to submit # large numbers of jobs on supercomputers. It provides a python interface to physical input, such as # crystal structures, as well as to a number of DFT (VASP, CRYSTAL) and atomic potential programs. It # is able to organise and launch computational jobs on PBS and SLURM. # # PyLaDa is free software: you can redistribute it and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # PyLaDa is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along with PyLaDa. If not, see # <http://www.gnu.org/licenses/>. ############################### from quantities import eV from ..tools.input import BoolKeyword as BaseBoolKeyword, ValueKeyword, \ TypedKeyword, AliasKeyword, ChoiceKeyword, \ BaseKeyword, QuantityKeyword class BoolKeyword(BaseBoolKeyword): """ Boolean keyword. If True, the keyword is present. If False, it is not. """ def __init__(self, keyword=None, value=None): """ Initializes FullOptG keyword. """ super(BoolKeyword, self).__init__(keyword=keyword, value=value) def output_map(self, **kwargs): """ Map keyword, value """ if self.value is None: return None if getattr(self, 'keyword', None) is None: return None return {self.keyword: '.TRUE.' if self.value else '.FALSE.'} class Magmom(ValueKeyword): """ Sets the initial magnetic moments on each atom. There are three types of usage: - if None or False, does nothing - if calculations are not spin-polarized, does nothing. - if a string, uses that as for the MAGMOM_ keyword - if True and at least one atom in the structure has a non-zero ``magmom`` attribute, then creates the relevant moment input for VASP_ If the calculation is **not** spin-polarized, then the magnetic moment tag is not set. .. note:: Please set by hand for non-collinear calculations .. seealso:: MAGMOM_ .. _MAGMOM: http://cms.mpi.univie.ac.at/wiki/index.php/MAGMOM """ keyword = 'MAGMOM' 'VASP keyword' def __init__(self, value=None): super(Magmom, self).__init__(value=value) @property def value(self): """ MAGMOM value, or whether to compute it. - if None or False, does nothing - if calculations are not spin-polarized, does nothing. - if a string, uses that as for the MAGMOM_ keyword - if True and at least one atom in the structure has a non-zero ``magmom`` attribute, then creates the relevant moment input for VASP_ """ return self._value @value.setter def value(self, value): if value is None: self._value = None elif value is True or value is False: self._value = value elif not isinstance(value, str): raise ValueError('Unknown value for magmom {0}. ' 'Should be True, False, None, or an adequate string' .format(value)) else: self._value = value def output_map(self, **kwargs): """ MAGMOM input for VASP. """ from ..crystal import specieset if self.value is None or self.value == False: return None if kwargs['vasp'].ispin == 1: return None if isinstance(self.value, str): return {self.keyword: str(self.value)} structure = kwargs['structure'] if all(not hasattr(u, 'magmom') for u in structure): return None result = "" for specie in specieset(structure): # Example specie: Fe moments: [-4.0, -4.0, -4.0, 4.0, -4.0, -4.0] moments = [getattr(u, 'magmom', 0e0) for u in structure if u.type == specie] # Get tupled = [[3, -4.0], [1, 4.0], [2, -4.0]] tupled = [[1, moments[0]]] for m in moments[1:]: # Change precision from 1.e-12 to 1.e-1, per Vladan, 2013-10-09 #if abs(m - tupled[-1][1]) < 1e-12: tupled[-1][0] += 1 if abs(m - tupled[-1][1]) < 1e-1: tupled[-1][0] += 1 else: tupled.append([1, m]) # Create result = '3*-4.00 4.00 2*-4.00 ' for i, m in tupled: # Change format from .2f to .1f, per Vladan, 2013-10-09 if i == 1: result += "{0:.1f} ".format(m) else: result += "{0}*{1:.1f} ".format(i, m) return {self.keyword: result.rstrip()} class System(ValueKeyword): """ System title to use for calculation. - If None and ... - if the structure has a ``name`` attribute, uses that as the calculations title - else does not use SYSTEM_ tag - If something else which is convertible to a string, and ... - if the structure has a ``name`` attribute, uses 'string: name' as the title - otherwise, uses the string .. seealso:: SYSTEM_ .. _SYSTEM: http://cms.mpi.univie.ac.at/vasp/guide/node94.html> """ keyword = 'system' """ VASP keyword """ def __init__(self, value=None): super(System, self).__init__(value=value) def output_map(self, **kwargs): """ Tries to return sensible title. Never throws. """ try: if self.value is None: if len(getattr(kwargs['structure'], 'name', '')) == 0: return None return {self.keyword: str(kwargs['structure'].name)} elif len(getattr(kwargs['structure'], 'name', '')) == 0: try: return {self.keyword: str(self.value)} except: return None try: return {self.keyword: '{0}: {1}'.format(str(self.value), kwargs['structure'].name)} except: return {self.keyword: str(kwargs['structure'].name)} except: return None class Npar(ValueKeyword): """ Parallelization over bands. Npar defines how many nodes work on one band. It can be set to a particular number: >>> vasp.npar = 2 Or it can be deduced automatically. Different schemes are available: - power of two: npar is set to the largest power of 2 which divides the number of processors. >>> vasp.npar = "power of two" If the number of processors is not a power of two, prints nothing. - square root: npar is set to the square root of the number of processors. >>> vasp.npar = "sqrt" .. seealso: `NPAR <http://cms.mpi.univie.ac.at/vasp/guide/node138.html>`_ """ keyword = 'NPAR' """ VASP keyword """ def __init__(self, value=None): super(Npar, self).__init__(value=value) def output_map(self, **kwargs): from math import log, sqrt if self.value is None: return None if not isinstance(self.value, str): if self.value < 1: return None return {self.keyword: str(self.value)} comm = kwargs.get('comm', None) if comm is None: return None n = comm['n'] if n == 1: return None if self.value == "power of two": m = int(log(n) / log(2)) for i in range(m, 0, -1): if n % 2**i == 0: return {self.keyword: str(i)} return None if self.value == "sqrt": return {self.keyword: str(int(sqrt(n) + 0.001))} raise ValueError("Unknown request npar = {0}".format(self.value)) class ExtraElectron(TypedKeyword): """ Sets number of electrons relative to neutral system. Gets the number of electrons in the (neutral) system. Then adds value to it and computes with the resulting number of electrons. >>> vasp.extraelectron = 0 # charge neutral system >>> vasp.extraelectron = 1 # charge -1 (1 extra electron) >>> vasp.extraelectron = -1 # charge +1 (1 extra hole) Disables :py:attr:`pylada.vasp.functional.Functional.nelect` if set to something other than None. .. seealso:: nelect_, NELECT_ .. _NELECT: http://cms.mpi.univie.ac.at/wiki/index.php/NELECT .. _nelect: :py:attr:`~pylada.vasp.functional.Vasp.nelect` """ type = float """ Type of this input. """ keyword = 'nelect' """ VASP keyword. """ def __init__(self, value=None): super(ExtraElectron, self).__init__(value=value) def __set__(self, instance, value): if value is not None: instance.nelect = None return super(ExtraElectron, self).__set__(instance, value) def nelectrons(self, vasp, structure): """ Total number of electrons in the system """ from math import fsum # constructs dictionnary of valence charge valence = {} for key, value in vasp.species.items(): valence[key] = value.valence # sums up charge. return fsum(valence[atom.type] for atom in structure) def output_map(self, **kwargs): if self.value is None: return None if self.value == 0: return None # gets number of electrons. charge_neutral = self.nelectrons(kwargs['vasp'], kwargs['structure']) # then prints incar string. return {self.keyword: str(charge_neutral + self.value)} class NElect(TypedKeyword): """ Sets the absolute number of electrons. Disables :py:attr:`pylada.vasp.functional.Functional.extraelectron` if set to something other than None. .. seealso:: extraelectron_, NELECT_ .. _NELECT: http://cms.mpi.univie.ac.at/wiki/index.php/NELECT .. _extraelectron: :py:attr:`~pylada.vasp.functional.Vasp.extraelectron` """ type = float """ Type of this input. """ keyword = 'nelect' """ VASP keyword. """ def __init__(self, value=None): super(NElect, self).__init__(value=value) def __set__(self, instance, value): if value is not None: instance.extraelectron = None return super(NElect, self).__set__(instance, value) class Algo(ValueKeyword): """ Electronic minimization. Defines the kind of algorithm vasp will run. - very fast - fast, f (default) - normal, n - all, a - damped, d - Diag - conjugate, c (vasp 5) - subrot (vasp 5) - eigenval (vasp 5) - Nothing (vasp 5) - Exact (vasp 5) - chi - gw - gw0 - scgw - scgw0 If :py:data:`is_vasp_4 <pylada.is_vasp_4>` is an existing configuration variable of :py:mod:`pylada` the parameters marked as vasp 5 will fail. .. warning:: The string None is not allowed, as it would lead to confusion with the python object None. Please use "Nothing" instead. The python object None will simply not print the ALGO keyword to the INCAR file. .. note:: By special request, "fast" is the default algorithm. .. seealso:: `ALGO <http://cms.mpi.univie.ac.at/vasp/vasp/ALGO_tag.html>`_ """ keyword = 'algo' """ VASP keyword. """ def __init__(self, value="fast"): super(Algo, self).__init__(value=value) @property def value(self): return self._value @value.setter def value(self, value): if value is None: self._value = None return None from pylada import is_vasp_4 if not hasattr(value, 'lower'): raise TypeError("ALGO cannot be set with {0}.".format(value)) lower = value.lower().rstrip().lstrip() lower = lower.replace('_', '') lower = lower.replace('-', '') if is_vasp_4 \ and (lower[0] in ['c', 's', 'e'] or lower in ["nothing", "subrot", "exact", "gw", "gw0", "chi", "scgw", "scgw0"]): raise ValueError("algo value ({0}) is not valid with VASP 4.6.".format(value)) if lower == "diag": value = "Diag" elif lower == "none": value = "None" elif lower == "nothing": value = "Nothing" elif lower == "chi": value = "chi" elif lower == "gw": value = "GW" elif lower == "gw0": value = "GW0" elif lower == "scgw": value = "scGW" elif lower == "scgw0": value = "scGW0" elif lower == "exact": value = "Exact" elif lower[0] == 'v': value = "Very_Fast" if is_vasp_4 else 'VeryFast' elif lower[0] == 'f': value = "Fast" elif lower[0] == 'n': value = "Normal" elif lower[0] == 'd': value = "Damped" elif lower[0] == 'a': value = "All" elif lower[0] == 'c': value = "Conjugate" elif lower[0] == 's': value = "Subrot" elif lower[0] == 'e': value = "Eigenval" else: self._value = None raise ValueError("algo value ({0!r}) is invalid.".format(value)) self._value = value class Ediff(TypedKeyword): """ Sets the absolute energy convergence criteria for electronic minimization. EDIFF_ is set to this value in the INCAR. Sets ediff_per_atom_ to None. .. seealso:: EDIFF_, ediff_per_atom_ .. _EDIFF: http://cms.mpi.univie.ac.at/wiki/index.php/EDIFFG .. _ediff_per_atom: :py:attr:`~pylada.vasp.functional.Vasp.ediff_per_atom` """ type = float """ Type of the value """ keyword = 'ediff' """ VASP keyword """ def __init__(self, value=None): """ Creates *per atom* tolerance. """ super(Ediff, self).__init__(value=value) def __set__(self, instance, value): if value is None: self.value = None return instance.ediff_per_atom = None if value < 0e0: value = 0 return super(Ediff, self).__set__(instance, value) class EdiffPerAtom(TypedKeyword): """ Sets the relative energy convergence criteria for electronic minimization. EDIFF_ is set to this value *times* the number of atoms in the structure. This approach is more sensible than straight-off ediff_ when doing high-throughput over many structures. Sets ediff_ to None. .. seealso:: EDIFF_, ediff_ .. _EDIFF: http://cms.mpi.univie.ac.at/wiki/index.php/EDIFFG .. _ediff: :py:attr:`~pylada.vasp.functional.Vasp.ediff` """ type = float """ Type of the value """ keyword = 'ediff' """ VASP keyword """ def __init__(self, value=None): """ Creates *per atom* tolerance. """ super(EdiffPerAtom, self).__init__(value=value) def __set__(self, instance, value): if value is None: self.value = None return instance.ediff = None if value < 0e0: value = 0 return super(EdiffPerAtom, self).__set__(instance, value) def output_map(self, **kwargs): if self.value is None: return return {self.keyword: str(self.value * float(len(kwargs["structure"])))} class Ediffg(TypedKeyword): """ Sets the absolute energy convergence criteria for ionic relaxation. EDIFFG_ is set to this value in the INCAR. Sets ediffg_per_atom_ to None. .. seealso:: EDIFFG_, ediffg_per_atom_ .. _EDIFFG: http://cms.mpi.univie.ac.at/vasp/guide/node105.html .. _ediffg_per_atom: :py:attr:`~pylada.vasp.functional.Vasp.ediffg_per_atom` """ type = float """ Type of the value """ keyword = 'ediffg' """ VASP keyword """ def __init__(self, value=None): """ Creates *per atom* tolerance. """ super(Ediffg, self).__init__(value=value) def __set__(self, instance, value): if value is None: self.value = None return instance.ediffg_per_atom = None return super(Ediffg, self).__set__(instance, value) class EdiffgPerAtom(TypedKeyword): """ Sets the relative energy convergence criteria for ionic relaxation. - if positive: EDIFFG_ is set to this value *times* the number of atoms in the structure. This means that the criteria is for the total energy per atom. - if negative: same as a negative EDIFFG_, since that convergence criteria is already per atom. This approach is more sensible than straight-off ediffg_ when doing high-throughput over many structures. Sets ediffg_ to None. .. seealso:: EDIFFG_, ediff_ .. _EDIFFG: http://cms.mpi.univie.ac.at/wiki/index.php/EDIFFG .. _ediffg: :py:attr:`~pylada.vasp.functional.Vasp.ediffg` """ type = float """ Type of the value """ keyword = 'ediffg' """ VASP keyword """ def __init__(self, value=None): """ Creates *per atom* tolerance. """ super(EdiffgPerAtom, self).__init__(value=value) def __set__(self, instance, value): if value is None: self.value = None return instance.ediffg = None return super(EdiffgPerAtom, self).__set__(instance, value) def output_map(self, **kwargs): if self.value is None: return if self.value > 0e0: return {self.keyword: str(self.value * float(len(kwargs["structure"])))} else: return {self.keyword: str(self.value)} class Encut(ValueKeyword): """ Defines cutoff factor for calculation. There are three ways to set this parameter: - if value is floating point and 0 < value <= 3: then the cutoff is ``value * ENMAX``, where ENMAX is the maximum recommended cutoff for the species in the system. - if value > 3 eV, then prints encut is exactly value (in eV). Any energy unit is acceptable. - if value < 0 eV or None, does not print anything to INCAR. .. seealso:: `ENCUT <http://cms.mpi.univie.ac.at/vasp/vasp/ENCUT_tag.html>`_ """ keyword = "encut" """ Corresponding VASP key. """ def __init__(self, value=None): super(Encut, self).__init__(value=value) def output_map(self, **kwargs): from quantities import eV from ..crystal import specieset value = self.value if hasattr(self.value, 'units'): value = self.value.rescale(eV).magnitude return {self.keyword: str(value)} if value > 1e-12 else None if value is None: return None elif value < 1e-12: return None elif value >= 1e-12 and value <= 3.0: types = specieset(kwargs["structure"]) encut = max(kwargs["vasp"].species[type].enmax for type in types) if hasattr(encut, 'rescale'): encut = float(encut.rescale(eV)) return {self.keyword: str(encut * value)} return {self.keyword: str(value)} # ? gwmod? class EncutGW(Encut): """ Defines cutoff factor for GW calculation. There are three ways to set this parameter: - if value is floating point and 0 < value <= 3: then the cutoff is ``value * ENMAX``, where ENMAX is the maximum recommended cutoff for the species in the system. - if value > 3 eV, then prints encut is exactly value (in eV). Any energy unit is acceptable. - if value < 0 eV or None, does not print anything to INCAR. .. seealso:: `ENCUTGW <http://cms.mpi.univie.ac.at/vasp/vasp/ENCUTGW_energy_cutoff_response_function.html>`_ """ keyword = 'encutgw' class ICharg(AliasKeyword): """ Charge from which to start. It is best to keep this attribute set to -1, in which case, Pylada takes care of copying the relevant files. - -1: (Default) Automatically determined by Pylada. Depends on the value of restart_ and the existence of the relevant files. Also takes care of non-scf bit. - 0: Tries to restart from wavefunctions. Uses the latest WAVECAR file between the one currently in the output directory and the one in the restart directory (if specified). Sets nonscf_ to False. .. note:: CHGCAR is also copied, just in case. - 1: Tries to restart from wavefunctions. Uses the latest WAVECAR file between the one currently in the output directory and the one in the restart directory (if specified). Sets nonscf_ to False. - 2: Superimposition of atomic charge densities. Sets nonscf_ to False. - 4: Reads potential from POT file (VASP-5.1 only). The POT file is deduced the same way as for CHGAR and WAVECAR above. Sets nonscf_ to False. - 10, 11, 12: Same as 0, 1, 2 above, but also sets nonscf_ to True. This is a shortcut. The value is actually kept to 0, 1, or 2: >>> vasp.icharg = 10 >>> vasp.nonscf, vasp.icharg (True, 0) .. note:: Files are copied right before the calculation takes place, not before. .. seealso:: ICHARG_, nonscf_, restart_, istruc_, istart_ .. _ICHARG: http://cms.mpi.univie.ac.at/wiki/index.php/ICHARG .. _nonscf: :py:attr:`~pylada.vasp.functional.Functional.nonscf` .. _restart: :py:attr:`~pylada.vasp.functional.Functional.restart` .. _istruc: :py:attr:`~pylada.vasp.functional.Functional.istruc` .. _istart: :py:attr:`~pylada.vasp.functional.Functional.istart` """ keyword = 'icharg' """ VASP keyword """ aliases = {-1: ['auto', -1], 0: ['wfns', 'wavefunction', 'wavefunctions', 'wfn', 0], 1: ['chgcar', 'CHGCAR', 'file', 1], 2: ['atomic', 'scratch', 2], 4: ['pot', 4], 10: [10], 11: [11], 12: [12], 14: [14]} """ Mapping of aliases. """ def __init__(self, value='auto'): super(ICharg, self).__init__(value=value) def __set__(self, instance, value): """ Sets internal value. Makes sure that the input value is allowed, and that the nonscf_ attribute is set properly. .. _nonscf: :py:attr:`~pylada.vasp.functional.Functional.nonscf` """ super(ICharg, self).__set__(instance, value) if self._value is None: return if self._value > 10: self._value -= 10 instance.nonscf = True elif self._value >= 0: instance.nonscf = False def output_map(self, **kwargs): from os.path import join from ..misc import latest_file, copyfile from ..error import ValueError from . import files icharge = self._value if icharge is None: return None # some files will be copied. if icharge not in [2, 12]: # determines directories to look into. vasp = kwargs['vasp'] outdir = kwargs['outdir'] hasrestart = getattr(vasp.restart, 'success', False) directories = [outdir] if hasrestart: directories += [vasp.restart.directory] # determines which files exist last_wfn = None if icharge in [1, 11] \ else latest_file(*[join(u, files.WAVECAR) for u in directories]) last_chg = latest_file(*[join(u, files.CHGCAR) for u in directories]) last_pot = None if icharge not in [-1, 4, 14] \ else latest_file(*[join(u, files.POT) for u in directories]) # determines icharge depending on file. if icharge < 0: if last_wfn is not None: icharge = 10 if vasp.nonscf else 0 elif last_chg is not None: icharge = 11 if vasp.nonscf else 1 elif last_pot is not None: icharge = 4 if vasp.nonscf else 14 else: icharge = 2 elif icharge == 1 and last_chg is None: raise ValueError('CHGCAR could not be found, yet ISTART=1 requested.') # copies relevant files. if icharge in [0, 10] and last_wfn is not None: copyfile(last_wfn, outdir, nothrow='same') if icharge in [0, 1, 10, 11, 4, 14] and last_chg is not None: copyfile(last_chg, outdir, nothrow='same') if icharge in [4, 14] and last_pot is not None: copyfile(last_pot, outdir, nothrow='same') # Haowei, bad behavior here, which will copy WAVEDER if it exist even it is not needed # But if a calculation ends up with a WAVEDER, and there is a following calculation # it is most likely a GW calculation # anyway, better solution needed here try: last_wavder = latest_file(*[join(u, files.WAVEDER) for u in directories]) copyfile(last_wavder, outdir, nothrow='same') except: pass return {self.keyword: str(icharge)} class IStart(AliasKeyword): """ Starting wavefunctions. It is best to keep this attribute set to -1, in which case, Pylada takes care of copying the relevant files. - -1: Automatically determined by Pylada. Depends on the value of restart_ and the existence of the relevant files. - 0: Start from scratch. - 1: Restart with constant cutoff. - 2: Restart with constant basis. - 3: Full restart, including TMPCAR. .. note:: Files are copied right before the calculation takes place, not before. .. seealso:: ISTART_, icharg_, istruc_, restart_ .. _ISTART: http://cms.mpi.univie.ac.at/wiki/index.php/ISTART .. _restart: :py:attr:`~pylada.vasp.functional.Functional.restart` .. _icharg: :py:attr:`~pylada.vasp.functional.Functional.icharg` .. _istruc: :py:attr:`~pylada.vasp.functional.Functional.istruc` """ keyword = 'istart' """ VASP keyword """ aliases = {-1: ['auto', -1], 0: ['scratch', 0], 1: ['cutoff', 1], 2: ['basis', 2], 3: ['tmpcar', 'full', 3]} """ Mapping of aliases. """ def __init__(self, value=-1): super(IStart, self).__init__(value=value) def output_map(self, **kwargs): from os.path import dirname, join from ..misc import latest_file, copyfile from ..error import ValueError from . import files istart = self._value if istart is None: return None # some files will be copied. if istart != 0: # determines directories to look into. vasp = kwargs['vasp'] outdir = kwargs['outdir'] hasrestart = getattr(vasp.restart, 'success', False) directories = [outdir] if hasrestart: directories += [vasp.restart.directory] # determines which files exist last_wfn = latest_file(*[join(u, files.WAVECAR) for u in directories]) # Validity of TMPCAR depends on existence of WAVECAR if last_wfn is None: last_tmp = None else: last_tmp = latest_file(join(dirname(last_wfn), files.TMPCAR)) # determines icharge depending on file. if last_wfn is None and istart > 0: raise ValueError('Wavefunction does not exist, ' 'yet ISTART={0} requested.'.format(istart)) if last_tmp is None and istart == 3: raise ValueError('TMPCAR file does not exist and ISTART={0}' .format(istart)) if last_wfn is not None: copyfile(last_wfn, outdir, nothrow='same') if last_tmp is not None: copyfile(last_tmp, outdir, nothrow='same') if istart == -1: if last_wfn is not None and last_tmp is not None: istart = 3 elif last_wfn is not None: istart = 1 else: istart = 0 return {self.keyword: str(istart)} class IStruc(AliasKeyword): """ Initial structure. Determines which structure is written to the POSCAR. In practice, it makes it possible to restart a crashed job from the latest CONTCAR_. There are two possible options: - auto: Pylada determines automatically what to use. The structure can be read from the following files, in order of priority: - CONTCAR of the current - OUTCAR of the restart If ``overwrite==True`` when calling the vasp functional, then never read from CONTCAR. - scratch: Always uses input/restart structure. - contcar: Always use CONTCAR_ structure unless ``overwrite == True``. - input: Always use input structure, never restart_ or CONTCAR_ structure. Only positions and cells are used from CONTCAR_. All other attributes (magnetization, etc) are those of the input structure. Hence, the atoms of any given specie should be same order in the CONTCAR and in the input/restart. .. note:: There is no VASP equivalent to this option. .. seealso:: :py:attr:`restart`, :py:attr:`icharg`, :py:attr:`istart` .. _CONTCAR: http://cms.mpi.univie.ac.at/vasp/guide/node60.html """ aliases = {-1: ['auto', 0], 0: ['scratch', 1], 1: ['contcar', 1], 2: ['input', 2]} """ Aliases for the same option. """ keyword = None """ Does not correspond to a VASP keyword """ def __init__(self, value='auto'): super(IStruc, self).__init__(value=value) def output_map(self, **kwargs): from os.path import join from ..misc import latest_file from ..error import ValueError from ..crystal import write, read, specieset from . import files from pylada import is_vasp_4 istruc = self._value if istruc is None: istruc = 0 if kwargs.get('overwrite', False): istruc = 0 structure = kwargs['structure'] outdir = kwargs['outdir'] vasp = kwargs['vasp'] has_restart = getattr(vasp, 'restart', None) is not None and istruc != 2 if has_restart: has_restart = vasp.restart.success if has_restart: structure = vasp.restart.structure # determines which CONTCAR is the latest, if any exist. if istruc in [-1, 1]: last_contcar = latest_file(join(outdir, files.CONTCAR)) # if a contcar exists and we should re-read, then modifies structure # accordingly. It is expected that the structures are equivalent, in the # sense that they have the same atoms in the same order (more # specifically, the order of the atoms of a given specie are the same). if last_contcar is not None: other = read.poscar(path=last_contcar, types=specieset(structure)) if len(other) != len(structure): raise ValueError('CONTCAR and input structure differ in size.') for type in specieset(other): A = [a for a in structure if a.type == type] B = [a for a in other if a.type == type] if len(A) != len(B): raise ValueError('CONTCAR and input structure differ ' 'in number of {0} atoms'.format(type)) for a, b in zip(A, B): if a.type != type: continue a.pos = b.pos structure.cell = other.cell structure.scale = other.scale print("setting cell and scale!") # Depending on different options and what's available, writes structure or # copies contcar. if len(structure) == 0: raise ValueError('Structure is empty') if structure.scale < 1e-8: raise ValueError('Structure scale is zero') if structure.volume < 1e-8: raise ValueError('Structure volume is zero') write.poscar(structure, join(outdir, 'POSCAR'), vasp5=not is_vasp_4) return None class LDAU(BoolKeyword): """ Sets U, nlep, and enlep parameters. The U, nlep, and enlep parameters of the atomic species are set at the same time as the pseudo-potentials. This object merely sets up the incar with the right input if the species are defined with U or NLEP_ parameters. *If* there are species with NLEP parameters, then: >>> vasp.ldau = True will add the right input to the incar (``vasp.ldau`` is True by default). If there are no such species, then the line above will *not* result in adding anything to the incar. .. note:: NLEP_ requires VASP to be patched for it. Furthermore, it requires vasp_has_nlep_ to set to True (False by default) in your pylada configuration file. .. seealso:: LDAU_, LDAUTYPE_, LDAUL_, LDAUJ_ .. _LDAU: http://cms.mpi.univie.ac.at/wiki/index.php/LDAU .. _LDAUTYPE: http://cms.mpi.univie.ac.at/wiki/index.php/LDAUTYPE .. _LDAUL: http://cms.mpi.univie.ac.at/wiki/index.php/LDAUL .. _LDAUU: http://cms.mpi.univie.ac.at/wiki/index.php/LDAUU .. _LDAUJ: http://cms.mpi.univie.ac.at/wiki/index.php/LDAUJ .. _NLEP: http://prb.aps.org/abstract/PRB/v77/i24/e241201 """ keyword = 'LDAU' """ VASP keyword corresponding to the value. """ def __init__(self, value=True): super(LDAU, self).__init__(value=value) def output_map(self, **kwargs): from ..crystal import specieset from ..error import ValueError, ConfigError, internal import pylada vasp = kwargs['vasp'] has_nlep = getattr(pylada, 'vasp_has_nlep', False) has_nlep = getattr(vasp, 'has_nlep', has_nlep) # print "vasp/keywords: LDAU.output: keyword: %s value: %s" \ # % (self.keyword, self.value,) if self.value is None or self.value is False: return types = specieset(kwargs['structure']) species = kwargs['vasp'].species # existence and sanity check has_U, which_type = False, None for type in types: specie = species[type] if len(specie.U) == 0: continue if len(specie.U) > 4: raise ValueError("More than 4 channels for U/NLEP parameters") has_U = True # check whether running NLEP without NLEP VASP. if not has_nlep: if len(specie.U) > 1: raise ValueError('has_nlep is False. There can be only U parameter') if specie.U[0]['func'] != 'U': raise ConfigError('has_nlep is False. Cannot use NLEP.') # checks consistency. which_type = specie.U[0]["type"] for l in specie.U[1:]: if which_type != l["type"]: raise ValueError("LDA+U/NLEP types are not consistent across species.") if not has_U: return None # parameters other than U and NLEP themselves. result = super(LDAU, self).output_map(**kwargs) result['LDAU'] = '.TRUE.' result['LDAUTYPE'] = str(which_type) # U and NLEP themselves. if has_nlep: for i in range(max(len(species[type].U) for type in types)): ldul, lduu, lduj, lduo = [], [], [], [] for type in types: specie = species[type] # a = 4 tuple, for func=='U': # ldul component = U['l'], default = -1 # lduu component = U['U'], default = 0 # lduj component = U['J'], default = 0 # lduo component = 1 a = -1, 0e0, 0e0, 1 if len(specie.U) <= i: pass elif specie.U[i]["func"] == "U": a = [specie.U[i]["l"], specie.U[i]["U"], specie.U[i]["J"], 1] elif specie.U[i]["func"] == "nlep": a = [specie.U[i]["l"], specie.U[i]["U0"], 0e0, 2] elif specie.U[i]["func"] == "enlep": a = [specie.U[i]["l"], specie.U[i]["U0"], specie.U[i]["U1"], 3] else: raise internal("Debug Error.") if hasattr(a[1], "rescale"): a[1] = a[1].rescale("eV") if hasattr(a[2], "rescale"): a[2] = a[2].rescale("eV") ldul.append('{0[0]}'.format(a)) lduu.append('{0[1]:18.10e}'.format(a)) lduj.append('{0[2]:18.10e}'.format(a)) lduo.append('{0[3]}'.format(a)) result['LDUL{0}'.format(i + 1)] = ' '.join(ldul) result['LDUU{0}'.format(i + 1)] = ' '.join(lduu) result['LDUJ{0}'.format(i + 1)] = ' '.join(lduj) result['LDUO{0}'.format(i + 1)] = ' '.join(lduo) else: ldaul, ldauu, ldauj = [], [], [] for type in types: specie = species[type] a = -1, 0e0, 0e0, 1 if len(specie.U) <= 0: pass elif specie.U[0]["func"] == "U": a = [specie.U[0]["l"], specie.U[0]["U"], specie.U[0]["J"], 1] if hasattr(a[1], "rescale"): a[1] = a[1].rescale("eV") if hasattr(a[2], "rescale"): a[2] = a[2].rescale("eV") ldaul.append('{0[0]}'.format(a)) ldauu.append('{0[1]:18.10e}'.format(a)) ldauj.append('{0[2]:18.10e}'.format(a)) result['LDAUL'] = ' '.join(ldaul) result['LDAUU'] = ' '.join(ldauu) result['LDAUJ'] = ' '.join(ldauj) return result class PrecFock(AliasKeyword): aliases = {'Low': ['low'], 'Medium': ['medium'], 'Fast': ['fast'], 'Normal': ['normal'], 'Accurate': ['accurate']} """ Aliases for the values of the VASP keyword. """ keyword = 'PRECFOCK' """ Vasp keyword. """ class Precision(AliasKeyword): aliases = {'Accurate': ['Accurate', 'accurate'], 'Low': ['Low', 'low'], 'Normal': ['Normal', 'normal'], 'Medium': ['Medium', 'medium'], 'High': ['High', 'high'], 'Single': ['Single', 'single']} """ Aliases for the values of the VASP keyword. """ keyword = 'PREC' """ Vasp keyword. """ class Nsw(TypedKeyword): type = int """ Type of the keyword. """ keyword = 'nsw' """ VASP keyword. """ class Isif(ChoiceKeyword): keyword = 'isif' values = list(range(8)) class IBrion(BaseKeyword): keyword = 'ibrion' """ VASP keyword """ def __init__(self, value=None): super(IBrion, self).__init__() self.value = value def __get__(self, instance, owner=None): return self.value def __set__(self, instance, value): from ..error import ValueError try: dummy = int(value) except: raise ValueError('ibrion accepts only integer values') else: value = dummy if value < -1 or (value > 8 and value != 44): raise ValueError('Unexpected value for IBRION') self.value = value def output_map(self, **kwargs): vasp = kwargs['vasp'] if vasp.relaxation == 'static': return {self.keyword: str(-1)} if self.value is None: return None return {self.keyword: str(self.value)} class Relaxation(BaseKeyword): """ Simple relaxation parameter It accepts two parameters: - static: for calculation without geometric relaxation. - combination of ionic, volume, cellshape: for the type of relaxation requested. It makes sure that isif_, ibrion_, and nsw_ take the right value for the kind of relaxation. """ keyword = None """ Just an alias for ISIF. """ def __init__(self, value=None): super(Relaxation, self).__init__() self.value = value def __get__(self, instance, owner=None): nsw = instance.nsw if instance.nsw is not None else 0 ibrion = instance.ibrion if instance.ibrion is not None \ else (-1 if nsw <= 0 else 2) if nsw <= 0 or ibrion == -1: return 'static' return {None: 'ionic', 0: 'ionic', 1: 'ionic', 2: 'ionic', 3: 'cellshape ionic volume', 4: 'cellshape ionic', 5: 'cellshape', 6: 'cellshape volume', 7: 'volume'}[instance.isif] def __set__(self, instance, value): from ..misc import Iterable from ..error import ValueError if value is None: value = 'static' if isinstance(value, Iterable) and not isinstance(value, str): value = ' '.join([str(u) for u in value]) # try integer value try: dummy = int(value) except: pass else: value = {0: 'ionic', 1: 'ionic', 2: 'ionic', 3: 'cellshape ionic volume', 4: 'cellshape ionic', 5: 'cellshape', 6: 'cellshape volume', 7: 'volume'}[dummy] value = set(value.lower().replace(',', ' ').rstrip().lstrip().split()) result = [] if 'all' in value: #result = 'ionic cellshape volume'.split() result = 'ionic cellshape volume gwcalc'.split() # gwmod else: if 'ion' in value or 'ions' in value or 'ionic' in value: result.append('ionic') if 'cell' in value or 'cellshape' in value or 'cell-shape' in value: result.append('cellshape') if 'volume' in value: result.append('volume') if 'gwcalc' in value: result.append('gwcalc') # gwmod result = ', '.join(result) # static case if len(result) == 0: # gwmod?: if len(result) == 0 or result == ['gwcalc']: instance.nsw = 0 if instance.ibrion is not None: instance.ibrion = -1 if instance.isif is not None: if instance.isif > 2: instance.isif = 2 return # non-static if instance.nsw is None or instance.nsw <= 0: instance.nsw = 50 if instance.ibrion is None or instance.ibrion == -1: instance.ibrion = 2 ionic = 'ionic' in result cellshape = 'cellshape' in result volume = 'volume' in result gwcalc = 'gwcalc' in result # gwmod instance.isif = 0 # gwmod if ionic and (not cellshape) and (not volume): instance.isif = 2 elif ionic and cellshape and (not volume): instance.isif = 4 elif ionic and cellshape and volume: instance.isif = 3 elif (not ionic) and cellshape and volume: instance.isif = 6 elif (not ionic) and cellshape and (not volume): instance.isif = 5 elif (not ionic) and (not cellshape) and volume: instance.isif = 7 elif ionic and (not cellshape) and volume: raise ValueError("VASP does not allow relaxation of atomic position " "and volume at constant cell-shape.\n") elif gwcalc: instance.isif = 0 else: instance.isif = 2 # gwmod: if 'gwcalc' in value and instance.isif != 0: raise ValueError("cannot combine gw with other relaxation") def output_map(self, **kwargs): return None class ISmear(AliasKeyword): keyword = 'ismear' aliases = {-5: ['metal'], -4: ['tetra'], -3: ['dynamic'], -1: ['fermi'], -2: ['fixresults'], 0: ['gaussian'], 1: ['mp', 'mp1', 'mp 1'], 2: ['mp 2', 'mp2'], 3: ['mp3', 'mp 3']} class Sigma(QuantityKeyword): keyword = 'sigma' units = eV class LSorbit(BoolKeyword): """ Run calculation with spin-orbit coupling. Accepts None, True, or False. If True, then sets :py:attr:`~pylada.vasp.incar.Incar.nonscf` to True. When printing INCAR stuff, checks for valid prior calculation. And sets lmaxmix to value of prior calculation. """ keyword = 'lsorbit' """ VASP keyword """ def __init__(self, value=None): super(LSorbit, self).__init__(value=value) def __get__(self, instance, owner=None): return self.value def __set__(self, instance, value): if value is None: self._value = None return self.value = value == True if self.value: instance.ispin = 2 instance.nonscf = True def output_map(self, **kwargs): if self.value is None or self.value == False: return None vasp = kwargs['vasp'] if not vasp.nonscf: raise ValueError('Expected non-self-consistent ' 'calculation with LSORBIT = True') if vasp.restart is None: raise ValueError('Expected to restart from other ' 'calculation with LSORBIT = True') if vasp.restart.success == False: raise ValueError('Self-consistent calculation was unsuccessful. ' 'Cannot perform LSORBIT = True calculation.') vasp.lmaxmix = vasp.restart.lmaxmix vasp.lvhar = vasp.restart.lvhar return super(LSorbit, self).output_map(**kwargs) class NonScf(BoolKeyword): keyword = None """ Does not correspond to a VASP keyword. """ def __init__(self, value=False): super(NonScf, self).__init__(value=False) class LMaxMix(TypedKeyword): keyword = 'lmaxmix' type = int class LVHar(BoolKeyword): keyword = 'LVHAR' def __init__(self, value=False): super(LVHar, self).__init__(value=value)