Source code for pylada.dftcrystal.optgeom

from .input import AttrBlock, BoolKeyword
from ..tools.input import BoolKeyword as BaseBoolKeyword, TypedKeyword

class GeometryOpt(BaseBoolKeyword):
  """ Geometry optimization keyword. 

      When set to True, makes sure that:

        1. MAXCYCLE is non-zero. If it is, sets it to 100.
        2. only one of FULLOPTG, ITATOCELL, ITREDUN, CELLONLY is True.
        3. CVOLOPT is False unless this is a FULLOPTG or CELLONLY calculation.

      \(2\) and (3) are decided by the keyword argument in input.
  """
  def __init__(self, keyword, value=False):
    """ Initializes a geometry optimization keyword. """
    self.keyword = keyword
    """ CRYSTAL keyword. """
    super(GeometryOpt, self).__init__(value=value)

  def __set__(self, instance, value):
    """ Sets keyword to appear or not. 

        Also clears attendant conditions described in :py:class:`GeometryOpt`.
    """
    super(GeometryOpt, self).__set__(instance, value)
    if self.value:
      if instance.maxcycle is not None and instance.maxcycle < 1:
        instance.maxcycle = 100
      if self.keyword != 'fulloptg':  instance.fulloptg  = False
      if self.keyword != 'cellonly':  instance.cellonly  = False
      if self.keyword != 'itatocell': instance.itatocell = False
      if self.keyword != 'intredun':  instance.intredun  = False
      if self.keyword != 'fulloptg' and self.keyword != 'cellonly':
        instance.cvolopt = False

class CVolOpt(BoolKeyword):
  """ Constant volume optimization keyword. 
  
      Only appears if FULLOPTG or CELLONLY exist.
  """
  keyword = 'cvolopt'
  """ Crystal input keyword """
  def output_map(self, **kwargs):
    """ Only prints if FULLOPTG or CELLONLY exist. """
    optgeom = kwargs['crystal'].optgeom
    if optgeom.fulloptg == False and optgeom.cellonly == False:
      return None
    return super(CVolOpt, self).output_map(**kwargs)

class FixCell(BoolKeyword):
  """ Constant volume optimization keyword. 
  
      Only appears for INTREDUN relaxations.
  """
  keyword = 'fixcell'
  """ Crystal input keyword """
  def output_map(self, **kwargs):
    optgeom = kwargs['crystal'].optgeom
    if optgeom.intredun == False: return None
    return super(FixCell, self).output_map(**kwargs)

class MaxCycle(TypedKeyword):
  """ Maxcycle input.

      It is expected to be an integer or None.
  """
  keyword = 'maxcycle'
  """ CRYSTAL keyword. """ 
  type = int
  """ Type of the keyword. """

[docs]class ExclAttrBlock(AttrBlock): """ An attribute block set up to exclude others. Expects both "keyword" and "excludegroup" attributes (or class attributes) to exist in derived instances. This class makes it convenient to describe mutually exclusive blocks such as optgeom and freqcalc. """ excludegroup = 'optgeom', 'freqcalc', 'anharm', 'confcnt', 'cphf', \ 'elastcon', 'eos' """ Groups of blocks of which only one should be enabled. """ def __init__(self): """ Initializes the exclusive attribute block. """ super(ExclAttrBlock, self).__init__() self.enabled = False self._parent = None """ Weak-reference to parent instance. """ @property def enabled(self): """ True if this block is enabled. """ return self._enabled @enabled.setter
[docs] def enabled(self, value): self._enabled = value == True # disable other instances. if self._enabled and self._parent is not None \ and len(getattr(self, 'excludegroup', ())) > 0: parent = self._parent() for u in self.excludegroup: if u == self.keyword: continue if not hasattr(parent, u): continue inst = getattr(parent, u) if not hasattr(inst, 'enabled'): continue inst.enabled = False
def __get__(self, instance, owner=None): """ Sets up weak ref to parent. """ from weakref import ref self._parent = ref(instance) return self
[docs] def read_input(self, tree, owner=None, **kwargs): """ Reads from input. """ if owner is not None: from weakref import ref self._parent = ref(owner) self.enabled = True return super(ExclAttrBlock, self).read_input(tree, owner, **kwargs)
[docs] def output_map(self, **kwargs): """ Does not print if disabled. """ from ..tools.input import Tree if not self.enabled: return None result = super(ExclAttrBlock, self).output_map(**kwargs) if result is None: result = Tree() result[self.keyword] = Tree() return result
def __getstate__(self): d = self.__dict__.copy() d['_parent'] = None return d def __setstate__(self, value): self.__dict__.update(value) def __ui_repr__(self, imports, name=None, defaults=None, exclude=None): """ User-friendly output. """ # make sure enabled is printed. if defaults is not None: defaults.enabled = not self.enabled return super(ExclAttrBlock, self).__ui_repr__( imports, name, \ defaults, exclude )
[docs]class OptGeom(ExclAttrBlock): """ Geometry Optimization block. Defines the input block for geometry optimization. Geometry optimization must be explicitely enabled: >>> functional.optgeom.enabled = True It is disabled by default. When enabled, other sub-blocks within CRYSTAL_'s first input block are automatically disabled (e.g. ``freqcalc`` [*]_). The full set of geometry optimization parameters can be accessed by the user within the :py:attr:`~pylada.dftcrystal.functional.Functional.optgeom` attribute of an :py:class:`~pylada.dftcrystal.functional.Functional` instance: >>> functional.optgeom.fulloptg = True >>> functional.optgeom.maxcycle = 300 >>> functional.optgeom.enabled = True The above would set the optimization method to ``FULLOPTG`` and the number of geometry optimization to 300. The third line would enable the block so that the geomtry optimization can happen. .. note:: Inner keywords can be modified when the block is disabled. However, the block will not appear in the input until is is explicitely enabled. .. [*] Currently, :py:class:`~pylada.dftcrystal.functional.Functional` only contains the :py:attr:`~pylada.dftcrystal.functional.Functional.optgeom` attribute. If implemented, other sub-blocks should derive from :py:class:`~pylada.dftcrystal.optgeom.ExclAttrBlock`. """ keyword = 'optgeom' """ CRYSTAL input keyword (class-attribute) """ def __init__(self, breaksym=None, keepsymm=None): """ Creates an optimization block. """ from quantities import UnitQuantity, hartree, angstrom from ..tools.input import QuantityKeyword from ..error import ValueError super(OptGeom, self).__init__() self.breaksym = True """ Whether to keep or break symmetries. This value can be True, False, or None. If True, then symmetries will be broken. If False, symmetries will be kept during the minimization. By default, symmetries are broken. """ if breaksym is None and keepsymm is not None: self.breaksym = keepsymm == False elif breaksym is not None and keepsymm is None: self.breaksym = breaksym == True elif breaksym is not None and keepsymm is not None \ and breaksym == keepsymm: raise ValueError( 'OptGeom: breaksym and keepsymm ' \ 'give opposite instructions.' ) self.maxcycle = MaxCycle() """ Maxium number of iterations in geometry optimization loop. """ self.fulloptg = GeometryOpt('fulloptg') """ Full optimization. Unless :py:attr:`cellonly` is True, this will optimize both cell internal and cell external degrees of freedom. This option is not compatible with other optimization methods. Setting it will disable other optimization methods: >>> functional.optgeom.fulloptg = True >>> functional.intredun # disables intredun automatically. False """ self.cellonly = GeometryOpt('cellonly') """ Cell-shape optimization only. Constrains optimization to cell-external degrees of freedom only. The fractional coordinates of the atomic sites are kept constant. """ self.itatocell = GeometryOpt('itatocell') """ Iterative cell-shape/atom optimization. Alternates between relaxing cell-internal and cell-external degrees of freedom. This option is not compatible with other optimization methods. Setting it to ``True`` auttomatically disables other optimization methods. >>> functional.optgeom.itatocell = True >>> functional.fulloptg, functional.intredun False, False """ self.intredun = GeometryOpt('intredun') """ Constrained optimization. Relaxes the strain energy by optimizing a complete and redundant set of chemically intuitive parameters, such as bond-length and bond-angles. This option is not compatible with other optimization methods. >>> functional.optgeom.intredun = True >>> functional.fulloptg, functional.itatocell False, False """ self.cvolopt = CVolOpt() """ Constant volume optimization keyword. Only appears if FULLOPTG or CELLONLY exist. """ self.fixcell = FixCell() """ Constant volume optimization keyword. When used in conjunction with :py:attr:`intredun` optimization method, keeps the cell-external degrees of freedom constant. Only appears for :py:attr:`intredun` relaxations. """ self.toldee = TypedKeyword('toldee', int) """ Electronic structure minimization convergence criteria. The criteria is with respect to the total energy. It is logarithmic and should be an integer: :math:`|\\Delta E| < 10^{-\\mathrm{toldee}}`. """ self.toldeg = TypedKeyword('toldeg', float) """ Structural relaxation convergence criteria. Convergence criteria for the root mean square gradient of the total energy with respect to the displacements. Should be a floating point. """ self.toldex = TypedKeyword('toldex', float) """ Structural relaxation convergence criteria. Convergence criteria as the root mean square of the displacements during structural relaxation. Should be a floating point. """ bohr = UnitQuantity('crystal_bohr', 0.5291772083*angstrom, symbol='bohr') """ Bohr radius as defined by CRYSTAL """ self.extpress = QuantityKeyword(units=hartree/bohr**3) """ Hydrostatic pressure in :math:`\\frac{\\text{hartree}}{\\text{bohr}^{3}}`. Sets the hydrostatic pressure for which an structural relaxation is performed. """ self.extstress = QuantityKeyword(units=hartree/bohr**3, shape=(3,3)) """ External stress in :math:`\\frac{\\text{hartree}}{\\text{bohr}^{3}}`. Sets the stress for which a structural relaxation is performed. """ self.onelog = BoolKeyword() """ Whether to print electronic minimization at each geometry step. The electronic minimization steps can be printed to the same file as the geometry optimization step (e.g. the standard output). By default, Pylada prefers to print to the same file, in contrast to CRYSTAL_'s default. """ self.noxyz = BoolKeyword() """ Whether to print cartesian coordinates at the end of optimization. """ self.nosymmops = BoolKeyword() """ Whether to print symmetry operations at the end of optimization. """ self.printforces = BoolKeyword() """ Whether to print forces at the end of optimization. """ self.printhess = BoolKeyword() """ Whether to print Hessian at the end of optimization. """ self.printopt = BoolKeyword() """ Whether to print extended optimization information. """ self.verbose = BoolKeyword(keyword='print') """ Verbose printint. """ @property def keepsymm(self): """ Alias to the opposite of breaksym. """ return self.breaksym != True @keepsymm.setter
[docs] def keepsymm(self, value): self.breaksym = value != True
def __ui_repr__(self, imports, name=None, defaults=None, exclude=None): """ User-friendly output. """ exclude = ['keepsym', 'keepsymm'] return super(OptGeom, self).__ui_repr__(imports, name, defaults, exclude) def output_map(self, **kwargs): """ Reads input tape. """ from ..tools.input import Tree if self.breaksym != kwargs.get('breaksym', True): result = Tree() result['breaksym' if self.breaksym else 'keepsymm'] = True result.update(super(OptGeom, self).output_map(**kwargs)) if len(result) == 0: return None return result return super(OptGeom, self).output_map(**kwargs) def read_input(self, value, **kwargs): """ Reads input tape. """ self.breaksym = kwargs.get('breaksym', True) return super(OptGeom, self).read_input(value, **kwargs)