""" Subpackage grouping hamiltonian and computational parameters.
In practice, this holds "block 3" input.
"""
__docformat__ = "restructuredtext en"
__all__ = ['Electronic']
from .input import AttrBlock, BoolKeyword
from ..tools.input import TypedKeyword, BaseKeyword
from quantities import UnitQuantity, hartree
class Shrink(BaseKeyword):
""" Implements SHRINK parameter. """
keyword = 'shrink'
""" Crystal keyword. """
def __init__(self, mp=None, gallat=None):
""" Initializes Shrink keyword.
:param mp:
- if an integer, corresponds to IS variable of SHRINK.
- if a sequence of at most three integers, corresponds to IS1, IS2,
IS3. In that case, IS is of course set to 0. If there are only one
or two numbers, then IS2 and/or IS3 is set to one. If None, the
set to 1.
:param int gallat:
Corresponds to ISP variable of SHRINK. If None, then it is set equal
to IS1
"""
super(Shrink, self).__init__()
self.mp = mp
self.gallat = gallat
@property
def mp(self):
""" Defines the Monkhorst-Pack mesh. """
return self._mp
@mp.setter
def mp(self, value):
if value is None: self._mp = None; return
elif hasattr(value, '__iter__'):
mp = [max(int(u), 1) for u in value]
if len(mp) > 3:
raise ValueError('k-point mesh defined by at most three integers.')
elif len(mp) == 0: mp = 1
self._mp = mp
else: self._mp = max(int(value), 1)
@property
def gallat(self):
""" Defines Gallat mesh for density matrix and Fermi energy. """
return self._gallat
@gallat.setter
def gallat(self, value):
self._gallat = None if value is None else int(value)
@property
def raw(self):
""" Prints raw CRYSTAL input. """
if self.mp is None: return ""
if hasattr(self.mp, '__iter__'):
ISP = self.mp[0] if self.gallat is None else self.gallat
IS = ' '.join(str(u) for u in self.mp + [1]*(3-len(self.mp)))
return '0 {0}\n{1}\n'.format(ISP, IS)
else:
ISP = self.mp if self.gallat is None else self.gallat
return '{0} {1}\n'.format(self.mp, ISP)
@raw.setter
def raw(self, value):
""" Sets from raw CRYSTAL input. """
value = value.split()
IS, ISP = int(value[0]), int(value[1])
if IS == 0:
self.mp = [int(u) for u in value[2:5]]
if self.mp[-1] == 1: self.mp = self.mp[:-1]
if self.mp[-1] == 1: self.mp = self.mp[:-1]
if self.mp[-1] == 1:
self.mp = 1
self.gallat = None if ISP == self.mp else ISP
else: self.gallat = None if ISP == self.mp[0] else ISP
else:
self.mp = IS
self.gallat = None if ISP == self.mp else ISP
def __set__(self, instance, value):
""" Sets the keyword more easily. """
if value is None: self.mp = None; return
self.mp = value[0]
self.gallat = value[1]
def __repr__(self):
""" Representation of this instance. """
args = []
if self.mp is None:
if self.gallat is not None:
args.append('gallat={0.gallat!r}'.format(self))
else:
args.append(repr(self.mp))
if self.gallat is not None: args.append('{0.gallat!r}'.format(self))
return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
""" Creates user friendly representation. """
from ..tools.uirepr import add_to_imports
if defaults is not None:
if type(defaults) is not type(self):
add_to_imports(self, imports)
return {name: repr(self)}
if self.gallat == defaults.gallat and self.mp == defaults.mp:
return {}
return {name: '{0.mp!r}, {0.gallat!r}'.format(self)}
elif name is None:
add_to_imports(self, imports)
return {None: 'shrink = {0!r}'.format(self)}
add_to_imports(self, imports)
return {name: self.__repr__()}
def output_map(self, **kwargs):
""" Prints SHRINK keyword """
from .molecule import Molecule
if self.mp is None: return None
# The 'get' piece is to make testing a bit easier
if type(kwargs.get('structure', None)) is Molecule: return None
return super(Shrink, self).output_map(**kwargs)
def _get_list(name):
""" Creates getter method for AtomSpin. """
def getme(this): return getattr(this, '_' + name)
return getme
def _set_list(name):
""" Creates setter method for AtomSpin. """
def setme(this, value):
from collections import Sequence, Iterable
from ..error import TypeError
if value is not None:
if not isinstance(value, Sequence):
raise TypeError( 'Expected a sequence when setting {0} in AtomSpin.' \
.format(name) )
if not isinstance(value, Iterable):
raise TypeError( 'Expected an iterable when setting {0} in AtomSpin.' \
.format(name) )
for v in value:
try: int(v)
except:
raise TypeError( 'Wrong type in list when setting AtomSpin.{0}.' \
'The list should be all integers.'.format(name) )
if len(value) == 0: value = None
setattr(this, '_' + name, value)
return setme
class AtomSpin(BaseKeyword):
keyword = 'atomspin'
def __init__(self, up=None, down=None, other=None):
""" Creates AtomSpin instance """
super(AtomSpin, self).__init__()
self.up = up
self.down = down
self.other = other
def __getitem__(self, index):
from ..error import IndexError
if index in [1, 'up', 'alpha']: return self.up
elif index in [-1, 'down', 'beta']: return self.down
elif index in [0, 'other', 'others', 'irrelevant']: return self.others
raise IndexError('Unknown index to AtomSpin {0}.'.format(index))
def __setitem__(self, index, value):
from ..error import IndexError
if index in [1, 'up', 'alpha']: self.up = value
elif index in [-1, 'down', 'beta']: self.down = value
elif index in [0, 'other', 'others', 'irrelevant']: self.others = value
raise IndexError('Unknown index to AtomSpin {0}.'.format(index))
up = property( _get_list('up'), _set_list('up'),
doc=""" Labels of atoms with spin up. """ )
down = property( _get_list('down'), _set_list('down'),
doc=""" Labels of atoms with spin down. """ )
other = property( _get_list('other'), _set_list('other'),
doc = """ Labels of atoms with irrelevant atoms.
'Irrelevant' is the word used by the CRYSTAL_ userguide.
This feature is likely only relevant to bash-scripts.
""" )
def output_map(self, **kwargs):
""" Creates standard output map for AtomSpin. """
# If disabled returns None
if self.up is None and self.down is None and self.other is None:
return None
# if not a spin polarized calculation, then disabled.
if kwargs['crystal'].dft.spin is False: return None
# If GuessP then disabled.
if 'crystal' in kwargs:
kwargs_copy = kwargs.copy()
kwargs_copy['filework'] = False
map = kwargs['crystal'].scf._input['guessp'].output_map(**kwargs_copy)
if map is not None and map.get('guessp', False) == 'True': return None
# Ok, now add ATOMSPIN stuff.
result = ''
if self.up is not None and len(self.up) > 0:
result += ' 1 '.join(str(u) for u in self.up) + ' 1\n'
if self.down is not None and len(self.down) > 0:
result += ' -1 '.join(str(u) for u in self.down) + ' -1\n'
if self.other is not None and len(self.other) > 0:
result += ' 0 '.join(str(u) for u in self.other) + ' 0\n'
if len(result) == 0: return None
return {self.keyword: '{0}\n{1}'.format(len(result.split())//2, result)}
def read_input(self, value, owner=None, **kwargs):
""" Reads input. """
results = value.split()
N = int(results.pop(0))
self._up, self._down, self._other = [], [], []
for label, spin in zip(results[:2*N:2], results[1:2*N:2]):
if spin == '1': self.up.append(int(label))
elif spin == '-1': self.down.append(int(label))
elif spin == '0': self.other.append(int(label))
else:
raise ValueError( 'Unexpected value when reading AtomSpin data {0}.' \
.format(value) )
if len(self.up) == 0: self.up = None
if len(self.down) == 0: self.down = None
if len(self.other) == 0: self.other = None
def __repr__(self):
""" Representation of the this object. """
args = []
if self.up is not None: args.append(repr(self.up))
if self.down is not None:
if len(args) == 1: args.append(repr(self.down))
else: args.append('down={0.down!r}'.format(self))
if self.other is not None:
if len(args) == 2: args.append(repr(self.other))
else: args.append('other={0.other!r}'.format(self))
return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
""" Creates user friendly representation. """
from ..tools.uirepr import add_to_imports
if defaults is not None:
if type(defaults) is not type(self):
add_to_imports(self.up, imports)
add_to_imports(self.down, imports)
add_to_imports(self.other, imports)
add_to_imports(self, imports)
return {name: repr(self)}
if self.up == defaults.up and self.down == defaults.down \
and self.other == defaults.other:
return {}
results = {}
if self.up is not None:
add_to_imports(self.up, imports)
results[name + '.up'] = '{0.up!r}'.format(self)
if self.down is not None:
add_to_imports(self.down, imports)
results[name + '.down'] = '{0.down!r}'.format(self)
if self.other is not None:
add_to_imports(self.other, imports)
results[name + '.down'] = '{0.other!r}'.format(self)
return results
elif name is None:
add_to_imports(self, imports)
return {None: 'atomspin = {0!r}'.format(self)}
add_to_imports(self, imports)
return {name: self.__repr__()}
class SpinLock(BaseKeyword):
""" Implements SpinLock keyword. """
keyword = 'spinlock'
""" CRYSTAL input keyword. """
def __init__(self, nspin=None, ncycles=None):
""" Creates the LEVSHIFT keyword. """
super(SpinLock, self).__init__()
self.nspin = nspin
self.ncycles = ncycles
@property
def nspin(self):
return self._nspin
@nspin.setter
def nspin(self, nspin):
self._nspin = int(nspin) if nspin is not None else None
@property
def ncycles(self):
""" Whether shift is kept after diagaonalization. """
return self._ncycles
@ncycles.setter
def ncycles(self, ncycles):
self._ncycles = int(ncycles) if ncycles is not None else None
def __set__(self, instance, value):
""" Sets the value of this instance. """
from ..error import ValueError
if value is None: self.nspin, self.ncycles = None, None; return
if not hasattr(value, '__getitem__'):
raise ValueError('Incorrect input to spinlock: {0}.'.format(value))
self.nspin = int(value[0])
self.ncycles = value[1]
def __getitem__(self, index):
""" list [self.nspin, self.ncycles] """
from ..error import IndexError
if index == 0: return self.nspin
elif index == 1 or index == -1: return self.ncycles
raise IndexError('Levshift can be indexed with 0, 1, or -1 only.')
def __setitem__(self, index, value):
""" sets as list [self.nspin, self.ncycles] """
from ..error import IndexError
if index == 0: self.nspin = value
elif index == 1 or index == -1: self.ncycles = value
else: raise IndexError('Levshift can be indexed with 0, 1, or -1 only.')
def __len__(self): return 2
@property
def raw(self):
""" CRYSTAL input as a string """
if self.nspin is None or self.ncycles is None: return ''
return '{0} {1}'.format(self.nspin, self.ncycles)
@raw.setter
def raw(self, value):
""" Sets instance from raw data """
self.nspin = int(value.split()[0])
self.ncycles = int(value.split()[1])
def output_map(self, **kwargs):
if self.nspin is None or self.ncycles is None: return None
if kwargs['crystal'].dft.spin != True: return None
return super(SpinLock, self).output_map(**kwargs)
def __repr__(self):
args = []
if self.nspin is not None: args.append(str(self.nspin))
if self.ncycles is not None:
args.append( 'ncycles={0}'.format(self.ncycles) if len(args) == 0 \
else str(self.ncycles) )
return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
""" Creates user friendly representation. """
from ..tools.uirepr import add_to_imports
if defaults is not None:
if type(defaults) is not type(self):
add_to_imports(self.shift, imports)
add_to_imports(self, imports)
return {name: repr(self)}
if self.nspin is None and self.ncycles is None: return {}
results = {}
if self.nspin is not None:
add_to_imports(self.nspin, imports)
results[name + '.nspin'] = '{0.nspin!r}'.format(self)
if self.ncycles is not None:
add_to_imports(self.ncycles, imports)
results[name + '.ncycles'] = '{0.ncycles!r}'.format(self)
return results
elif name is None:
add_to_imports(self, imports)
return {None: 'levshift = {0!r}'.format(self)}
add_to_imports(self, imports)
return {name: self.__repr__()}
class BetaLock(SpinLock):
keyword = 'betalock'
class LevShift(BaseKeyword):
""" Implements LevShift keyword. """
keyword = 'levshift'
""" CRYSTAL input keyword. """
units = UnitQuantity('decihartree', 0.1*hartree)
""" LEVSHIFT takes 0.1 * hartrees. """
def __init__(self, shift=None, lock=None):
""" Creates the LEVSHIFT keyword. """
super(LevShift, self).__init__()
self.shift = shift
self.lock = lock
@property
def shift(self):
""" Value to which this keyword is set. """
return self._shift
@shift.setter
def shift(self, shift):
""" Artificial shift between conduction and valence bands.
Can be a regular floating point, in which case the units are 0.1 *
hartree, or a scalar signed by an unit with dimensionality of an energy.
In the latter case, the value is rescaled to 0.1 H.
"""
from ..error import ValueError
if shift is None: self._shift = None; return
if hasattr(shift, 'rescale'): shift = shift.rescale(self.units)
else: shift = shift * self.units
if len(shift.shape) != 0:
raise ValueError('shift should be a scalar.')
self._shift = shift
@property
def lock(self):
""" Whether shift is kept after diagaonalization. """
return self._lock
@lock.setter
def lock(self, lock):
self._lock = None if lock is None else (lock == True)
def __set__(self, instance, value):
""" Sets the value of this instance. """
from ..error import ValueError
if value is None: self.shift, self.lock = None, None; return
if not hasattr(value, '__getitem__'):
raise ValueError('Incorrect input to levshift: {0}.'.format(value))
self.shift = value[0]
self.lock = value[1]
def __getitem__(self, index):
""" list [self.shift, self.lock] """
from ..error import IndexError
if index == 0: return self.shift
elif index == 1 or index == -1: return self.lock
raise IndexError('Levshift can be indexed with 0, 1, or -1 only.')
def __setitem__(self, index, value):
""" sets as list [self.shift, self.lock] """
from ..error import IndexError
if index == 0: self.shift = value
elif index == 1 or index == -1: self.lock = value
else: raise IndexError('Levshift can be indexed with 0, 1, or -1 only.')
def __len__(self): return 2
@property
def raw(self):
""" CRYSTAL input as a string """
if self.shift is None or self.lock is None: return ''
return '{0} {1}'.format(int(float(self.shift)+0.01), 1 if self.lock else 0)
@raw.setter
def raw(self, value):
""" Sets instance from raw data """
self.shift = int(value.split()[0])
self.lock = int(value.split()[1]) != 0
def output_map(self, **kwargs):
if self.shift is None or self.lock is None: return None
return super(LevShift, self).output_map(**kwargs)
def __repr__(self):
args = []
if self.shift is not None: args.append(str(float(self.shift)))
if self.lock is not None:
args.append( 'lock={0}'.format(self.lock) if len(args) == 0 \
else str(self.lock) )
return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
""" Creates user friendly representation. """
from ..tools.uirepr import add_to_imports
if defaults is not None:
if type(defaults) is not type(self):
add_to_imports(self.shift, imports)
add_to_imports(self, imports)
return {name: repr(self)}
if self.shift is None and self.lock is None: return {}
results = {}
if self.shift is not None:
results[name + '.shift'] = '{0}'.format(float(self.shift))
if self.lock is not None:
add_to_imports(self.lock, imports)
results[name + '.lock'] = '{0.lock!r}'.format(self)
return results
elif name is None:
add_to_imports(self, imports)
return {None: 'levshift = {0!r}'.format(self)}
add_to_imports(self, imports)
return {name: self.__repr__()}
class GuessP(BoolKeyword):
""" Implements GuessP parameter. """
keyword = 'guessp'
def __init__(self, value=True):
super(GuessP, self).__init__(value=value)
def output_map(self, **kwargs):
from os.path import join
from ..misc import latest_file, copyfile
from .. import CRYSTAL_filenames as filenames
from .properties import Properties
if self.value is None or self.value == False: return None
if kwargs['crystal'].restart is None: return None
directory = kwargs['crystal'].restart.directory
paths = join(directory, filenames['fort.9'].format('crystal')), \
join(directory, filenames['fort.98'].format('crystal'))
filename = latest_file(*paths)
if filename is None: return
if kwargs.get('filework', False) == True:
props = Properties(kwargs['crystal'].restart)
if filename[-3:] == '.f9':
props.fmwf = True
props(outdir='.')
props = Properties('.')
props.rdfmwf = True
props(overwrite=True, outdir='.')
copyfile(filenames['fort.9'].format('crystal'), 'fort.20')
return super(GuessP, self).output_map(**kwargs)
class Broyden(BaseKeyword):
""" Broyden mixing parameters. """
keyword = 'broyden'
""" CRYSTAL keyword. """
def __init__(self, w0=None, imix=None, istart=None):
super(Broyden, self).__init__()
self.w0 = w0
self.imix = imix
self.istart = istart
@property
def w0(self):
""" Anderson mixing parameter. """
return self._w0
@w0.setter
def w0(self, value):
from ..error import ValueError
if value is None: self._w0 = None; return
try: self._w0 = float(value)
except:
raise ValueError('w0 attribute expects a floating point in Broyden.')
@property
def imix(self):
""" Percent mixing when Broyden is switched on. """
return self._imix
@imix.setter
def imix(self, value):
from ..error import ValueError
if value is None: self._imix = None; return
try: dummy = int(value)
except:
raise ValueError('imix attribute expects an integer point in Broyden.')
else:
if dummy < 1 or dummy > 99:
raise ValueError("Broyden's imix should be > 0 and < 100.")
self._imix = dummy
@property
def istart(self):
""" Percent mixing when Broyden is switched on. """
return self._istart
@istart.setter
def istart(self, value):
from ..error import ValueError
if value is None: self._istart = None; return
try: dummy = int(value)
except:
raise ValueError('istart attribute expects an integer point in Broyden.')
else:
if dummy < 2:
raise ValueError("Broyden's istart should be larger than or equal to 2.")
self._istart = dummy
def output_map(self, **kwargs):
if self._w0 is None or self._istart is None or self._imix is None:
return None
return {self.keyword: '{0.w0!r} {0.imix!r} {0.istart!r}'.format(self)}
def read_input(self, value, **kwargs):
self.__set__(None, value.split())
def __set__(self, instance, value):
from collections import Sequence
from ..error import TypeError
if value is None:
self._w0, self._imix, self._istart = None, None, None
return
if not isinstance(value, Sequence):
raise TypeError("Expected a sequence [float, int, int] in Brodyen.")
if len(value) != 3:
raise TypeError("Expected a sequence [float, int, int] in Brodyen.")
self.w0 = value[0]
self.imix = value[1]
self.istart = value[2]
def __getitem__(self, index):
""" Allows access to input as sequence. """
from ..error import IndexError
if index == 0 or index == -3: return self.w0
if index == 1 or index == -2: return self.imix
if index == 2 or index == -1: return self.istart
raise IndexError('Index out-of-range in Broyden.')
def __setitem__(self, index, value):
""" Allows access to input as sequence. """
from ..error import IndexError
if index == 0 or index == -3: self.w0 = value
elif index == 1 or index == -2: self.imix = value
elif index == 2 or index == -1: self.istart = value
else: raise IndexError('Index out-of-range in Broyden.')
def __repr__(self):
""" Prints keyword to string. """
args = []
if self.w0 is not None: args.append("{0.w0!r}".format(self))
if self.imix is not None:
if len(args) == 0: args.append("imix={0.imix!r}".format(self))
else: args.append("{0.imix!r}".format(self))
if self.istart is not None:
if len(args) != 2: args.append("istart={0.istart!r}".format(self))
else: args.append("{0.istart!r}".format(self))
return "{0.__class__.__name__}({1})".format(self, ', '.join(args))
[docs]class Electronic(AttrBlock):
""" DFT attribute block. """
__ui_name__ = 'electronics'
""" Name used when printing this instance with ui_repr """
def __init__(self):
""" Creates the scf attribute block. """
from ..tools.input import ChoiceKeyword, QuantityKeyword
from .input import SetPrint
from .hamiltonian import Dft
super(Electronic, self).__init__()
self.maxcycle = TypedKeyword(type=int)
""" Maximum number of electronic minimization steps.
Should be None(default) or an integer.
"""
self.tolinteg = TypedKeyword(type=[int]*5)
""" Integration truncation criteria.
Should be None(default) or a sequence of 5 integers.
"""
self.toldep = TypedKeyword(type=int)
""" Density matrix convergence criteria.
Should be None(default) or an integer.
"""
self.tolpseud = TypedKeyword(type=int)
""" Pseudopotential truncation criteria.
Should be None(default) or an integer.
"""
self.toldee = TypedKeyword(type=int)
""" Total energy convergence criteria.
Should be None(default) or an integer.
"""
self.testpdim = BoolKeyword()
""" Stop after processing input and performing symmetry analysis.
Should be None(default), True, or False.
"""
self.test = BoolKeyword()
""" Stop after printing ressource requirement.
Should be None(default), True, or False.
"""
self.symadapt = BoolKeyword()
""" Symmetry adapted bloch wavefunctions.
Should be None(default), True, or False.
"""
self.savewf = BoolKeyword()
""" Save wavefunctions to disk.
Should be None(default), True, or False.
"""
self.shrink = Shrink()
""" k-point description -- SHRINK
The IS (or IS1, IS2, IS3) and ISP keywords are mapped to
:py:attr:`~Shrink.mp` and :py:attr:`~Shrink.gallat`.
They can be used as follows::
functional.shrink.mp = 5
functional.shrink.gallat = 10
This will map IS to 5 and ISP to 10.
ISP automatically set to equal IS (or IS1) when :py:attr:`~Shrink.gallat`
is set to None::
functional.shrink.mp = 10
functional.shrink.gallat = None
This will print in the CRYSTAL input:
| SHRINK
| 5 5
Finally, setting :py:attr:`~Shrink.mp` to a sequence of at most three
integers will set IS to 0 and IS1, IS2 (defaults to 1), and IS3 (defaults
to 1) to the relevant values::
functional.shrink.mp = [5, 6]
functional.shrink.gallat = None
This will lead to the following input, where ISP defaulted automatically
to IS1:
| SHRINK
| 0 5
| 5 6 1
Another option it to set :py:attr:`~Shrink.mp` and
:py:attr:`~Shrink.gallat` directly::
functional.shrink = 8, 8
would result in the following ouput
| SHRINK
| 8 8
:py:attr:`~Shrink.mp` will be set to the first item. and
:py:attr:`~Shrink.gallat` to the second. There should always be two
items.
"""
self.fmixing = TypedKeyword(type=int)
""" Fock mixing during electronic minimiztion.
Should be None(default) or an integer.
"""
self.levshift = LevShift()
""" Artificial shift between the valence and conduction band.
Opens the gap between occupied and unoccupied bands for better
numerical behavior. It can be set as follows:
>>> functional.levshift = 5 * decihartree, False
The first parameter is the amount by which to open the gap. It should
either an integer, or a signed energy quantity (see quantities_). In
the latter case, the input is converted to decihartree and rounded to
the nearest integer.
The second parameter specifies whether to keep (True) or remove (False)
the shift after diagonalization.
"""
self.ppan = BoolKeyword()
""" Mulliken population analysis.
Should None(default), True, or False.
"""
self.biposize = TypedKeyword(type=int)
""" Size of buffer for Coulomb integrals bipolar expansions.
Should be None(default), True, or False.
"""
self.exchsize = TypedKeyword(type=int)
""" Size of buffer for exchange integrals bipolar expansions.
Should be None(default), True, or False.
"""
self.scfdir = BoolKeyword()
""" Whether to reevaluate integrals at each electronic step.
Should be None(default), True, or False.
"""
self.poleordr = ChoiceKeyword(values=range(0, 7))
""" Coulomb intergrals pole truncation.
Should be None(default), or an integer between 0 and 6 included.
"""
self.guessp = GuessP(value=True)
""" Reads density matrix from disk.
- If True *and*
:py:attr:`~pylada.dftcrystal.functional.Functional.restart` is not
None, then copies crystal.f9 to fort.20 in the working directory and
adds GUESSP keyword to the input. Alternatively, if a later
crystal.f98 file is found, then it is transformed to an unformatted
file and copied to fort.20.
- If True but :py:attr:`~pylada.dftcrystal.functional.Functional.restart`
is None or the file crystal.f9 does not exist, then does nothing.
- If False or None, does nothing.
.. note::
There seems to be a strange bug, at least on crays, where the
crystal.f9 file is likely to be corrupt as far as the main CRYSTAL_
program can tell. However, it can still be read by PROPERTIES_ and
transformed to a valid f98 file. That file can then be transformed
back to a valid f9 file. To ensure correctness, this procedure is
followed each time an f9 file is found.
"""
self.dft = Dft()
""" Holds definition of the DFT functional itself. """
self.nofmwf = BoolKeyword()
""" Whether to print formatted wavefunctions to disk.
Should be None(default), True, or False.
"""
self.nobipola = BoolKeyword()
""" Whether to compute bielectronic integrals exactly.
Should be None(default), True, or False.
"""
self.nobipcou = BoolKeyword()
""" Whether to compute bielectronic Coulomb integrals exactly.
Should be None(default), True, or False.
"""
self.nobipexc = BoolKeyword()
""" Whether to compute bielectronic exchange integrals exactly.
Should be None(default), True, or False.
"""
self.nomondir = BoolKeyword()
""" Whether store monoelectronic integrals to disk.
If True, the monoelctronic integrals are computed once at the start of
the SCF calculation and re-read from disk at each geometric
optimization step. Should be None(default), True, or False.
"""
self.nosymada = BoolKeyword()
""" Whether to not use symmetry adapted functions.
Should be None(default), True, or False.
"""
self.savewf = BoolKeyword()
""" Whether to save wavefunctions at each step.
Should be None(default), True, or False.
"""
self.mpp = BoolKeyword(value=False)
""" Whether to use MPP or Pcrystal when running mpi.
If True and more than one process is requested, switches to using
MPPcrystal as opposed to Pcrystal.
This only works under the assumption that
:py:data:`pylada.crystal_program` is implemented correctly.
"""
self.spinlock = SpinLock()
""" Difference in occupation between the two spin-channels.
This object takes two values, the difference between of occupations
between the two spin channels, and the number of electronic
minimization steps during which the lock is maintained.
>>> functional.spinlock.nspin = 2
>>> functional.spinlock.ncycles = 30
The above sets the :math:`\\alpha` and :math:`\\beta` occupations to
:math:`\\frac{N+2}{2}` and :math:`\\frac{N-2}{2}` respectively, for 30
iterations of the electronic structure minimization algorithm. It
results in the input:
| SPINLOCK
| 2 30
Alternatively, the same could be done with the 2-tuple syntax:
>>> functional.spinlock = 2, 30
If either the occupation or the cycle-lock is None, then ``SPINLOCK`` is
*disabled*.
"""
self.atomspin = AtomSpin()
""" Atomic spins.
Defines the atomic spins.
>>> functional.atomspin.up = range(5)*2 + 1
>>> functional.atomspin.down = range(5)*2 + 2
For spin-polarized calculations, this would result in the CRYSTAL_
input:
| ATOMSPIN
| 8
| 2 1 4 1 6 1 8 1
| 1 -1 3 -1 5 -1 7 -1
There are two main variables, ``up`` and ``down``, which are lists of
atomic labels. ``up`` contains the atoms which at the start of the
calculation should have an up spin, and ``down`` those atoms with a
down spin. A third variable, ``other``, corresponds to the
''irrelevant'' atoms, as per CRYSTAL_'s input.
Alternatively, the ``up`` and ``down`` variables can be reached via indexing:
>>> functional.atomspin[1] is functional.atomspin.up
True
>>> functional.atomspin[-1] is functional.atomspin.down
True
>>> functional.atomspin[0] is functional.atomspin.other
True
.. note::
A number of settings will disable this keyword:
- py:attr:`~pylada.dftcrystal.functional.dft.spin` is False
- py:attr:`~pylada.dftcrystal.functional.dft.spin` is None or True,
and the appropriate wavefunction exists.
"""
self.betalock = BetaLock()
""" Locks in the number of beta electrons.
This tag presents the same user-interface as :py:attr:`spinlock`.
"""
self.smear = QuantityKeyword(units=hartree)
""" Smearing energy, if any.
Expects None (default, does nothing) or an energy wich defines the
width of the smearing function.
"""
self.anderson = BoolKeyword()
""" Applies Anderson mixing method.
Andersen mixing does not require any input.
It expects None (default, does nothing), True or False. The latter is
equivalent to None.
"""
self.broyden = Broyden()
""" Applies broyden mixing method.
Broyden takes three inputs:
>>> functional.broyden.w0 = 1e-4
>>> functional.broyden.imix = 50
>>> functional.broyden.istart = 2
The above results in:
| BROYDEN
| 0.0001 50 2
The first attribute should be a floating point, whereas the second and
third should be integers. The second item should be between 0 and 100,
excluded, and the third larger than two.
The input can also be set and accessed as an array:
>>> functional.broyden = [1e-4, 50, 2]
>>> functional.broyden[1] = 25
>>> functional.broyden.imix
25
"""
self.setprint = SetPrint()
""" Extended printing request.
Printing options consists of (keyword, value) pairs. As such, they can
be inputed much like arrays:
>>> functional.setprint[66] = -5
Will result in the output:
| SETPRINT
| 1
| 66 -5
which prints the eigenvalues of the first five k-points at the end of
the electronic minimization loop only. The print-out options can be
found in the CRYSTAL_ user-guide.
"""
self.cmplxfac = TypedKeyword(type=float)
""" Computaional weight of complex vs real k-points. """
self.postscf = BoolKeyword()
""" Whether to continue despite lack of electronic convergence. """
def output_map(self, **kwargs):
""" Makes sure DFT subblock is first. """
result = super(Electronic, self).output_map(**kwargs)
for i, item in enumerate(result):
if item[0] == 'DFT': break
if result[i][0] == 'DFT':
dft = result.pop(i)
result.insert(0, dft)
return result