Source code for pylada.dftcrystal.hamiltonian

__docformat__ = "restructuredtext en"
__all__ = ['Dft']
from .input import AttrBlock, BoolKeyword
from ..tools.input import BaseKeyword, ChoiceKeyword, TypedKeyword

class Exchange(ChoiceKeyword):
  keyword = 'exchange'
  """ Corresponding CRYSTAL keyword. """
  values = ['becke', 'lda', 'pbe', 'pbesol', 'pwgga', 'sogga', 'vbh', 'wcgga']
  """" Set of values from which to choose. """
  def __init__(self, value=None): 
    super(Exchange, self).__init__(value)
    self.excludes = ['b3lyp', 'b3pw', 'pbe0', 'soggaxc']
    """ keywords incompatible with this one. """
  def __set__(self, instance, value): 
    """ Makes sure that excluded keywords are set to False. """
    from ..error import ValueError
    if value is None:
      raise ValueError('Exchange and Correlations cannot be None.')
    super(Exchange, self).__set__(instance, value)
    for u in self.excludes: 
      if getattr(instance, u, False): setattr(instance, u, False)
  def output_map(self, **kwargs):
    """ Print EXCHANGE keyword. 

        Also checks whether one of global keywords is set, in which case does
        not print.
    """
    if self._value is None: return None
    inst = kwargs['crystal'].dft
    if inst.pbe0 or inst.b3lyp or inst.b3pw or inst.soggaxc: return None
    return {self.keyword.upper(): self.value.upper()}

class Correlation(Exchange):
  keyword = 'correlat'
  """ Corresponding CRYSTAL keyword. """
  values = [ 'lyp', 'p86', 'pbe', 'pbesol', 'pwgga', 'pwlsd', 'pz',
             'vbh', 'wl', 'vwn']
  """ Set of values from which to choose. """
  def __init__(self, value=None):
    super(Correlation, self).__init__(value=value)

class GlobalExc(BaseKeyword):
  """ Takes care of global keywords for exchange-correlation. """
  def __init__(self, keyword, values):
    super(GlobalExc, self).__init__(keyword=keyword)
    self.values = values
    """ Values against which to test. """

  def __get__(self, instance, owner=None):
    """ True if global keyword is set. """
    def test(a, b):
      if a is None:
        if b is not None: return False
      else: 
        if b is None: return False
        if abs(a-b) > 1e-8: return False
      return True
    return instance.exchange == self.values[0]                                 \
           and instance.correlat == self.values[1]                             \
           and test(self.values[2], instance.hybrid)                           \
           and test(self.values[3], instance.nonlocal.exchange)                \
           and test(self.values[4], instance.nonlocal.correlation)
  def __set__(self, inst, value):
    """ Sets to global if value is True. 
    
        If False, ignores.
    """
    if value == True:
      inst.exchange, inst.correlat, inst.hybrid,                               \
        inst.nonlocal.exchange, inst.nonlocal.correlation = self.values
  def output_map(self, **kwargs):
    if self.__get__(kwargs['crystal'].dft): 
      return {self.keyword.upper(): None}
  def read_input(self, tree, owner=None, **kwargs):
    """ True if ever read. """
    self.__set__(owner, True)

class NonLocal(BaseKeyword):
  """ Non-local weight parameters. """
  keyword = 'NONLOCAL'
  def __init__(self, xc=None, corr=None):
    super(NonLocal, self).__init__()
    self.exchange = None
    """ Exchange weight. """
    self.correlation = None
    """ Correlation weight. """
  @property
  def raw(self):
    x = 0 if self.exchange is None else self.exchange
    c = 0 if self.correlation is None else self.correlation
    return '{0} {1}\n'.format(x, c)
  @raw.setter
  def raw(self, value):
    value = value.split()
    self.exchange, self.correlation = float(value[0]), float(value[1])
  def disable(self): 
    """ Sets exchange and correlation to None. """
    self.exchange, self.correlation = None, None
  def output_map(self, **kwargs):
    inst = kwargs['crystal'].dft
    if inst.pbe0 or inst.b3lyp or inst.b3pw or inst.soggaxc: return None
    x = 0 if self.exchange is None else self.exchange
    c = 0 if self.correlation is None else self.correlation
    if abs(x) < 1e-8 and abs(c) < 1e-8: return None
    return super(NonLocal, self).output_map(**kwargs)
  def __getitem__(self, index):
    """ list [self.shift, self.lock] """
    from ..error import IndexError
    if index == 0: return self.exchange
    elif index == 1 or index == -1: return self.correlation
    raise IndexError('nonlocal 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.exchange = value
    elif index == 1 or index == -1: self.correlation = value
    else: raise IndexError('nonlocal can be indexed with 0, 1, or -1 only.')
  def __len__(self): return 2
  def __set__(self, value):
    """ Sets from (x, c) tuple. """
    from ..error import ValueError
    if len(value) != 2: raise ValueError('Expected (xchange, corr) tuple')
    self.exchange = value[0]
    self.correlat = value[1]
  def __repr__(self):
    """ Dumps representation of self. """
    args = []
    if self.exchange is None: 
      if self.correlation is not None:
        args.append('corr={0.correlation!r}'.formas(self))
    else:
      args.append(repr(self.exchange))
      if self.correlation is not None: args.append(repr(self.correlation))
    return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
  def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
    from ..tools.uirepr import add_to_imports
    if name is None:
      name = getattr(self, '__ui_name__', self.__class__.__name__.lower())
    if defaults is None or type(defaults) != type(self):
      add_to_imports(self, imports)
      return {name: self.__repr__()}
    if self.correlation is None: issame = defaults.correlation is None
    elif defaults.correlation is None: issame = False
    else: issame = abs(defaults.correlation - self.correlation) < 1e-8 
    if issame:
      if self.exchange is None: issame = defaults.exchange is None
      elif defaults.exchange is None: issame = False
      else: issame = abs(defaults.exchange - self.exchange) < 1e-8 
    if issame: return {}
    return {name: '{0.exchange!r}, {0.correlation!r}'.format(self)}
      
            


class Hybrid(TypedKeyword):
  keyword = 'hybrid'
  """ Crystal input keyword. """
  type = float
  """ Keyword must conform to this type, if not None. """
  def __init__(self, value=None):
    super(Hybrid, self).__init__(value=value)
  def output_map(self, **kwargs):
    inst = kwargs['crystal'].dft
    if inst.pbe0 or inst.b3lyp or inst.b3pw or inst.soggaxc: return None
    return super(Hybrid, self).output_map(**kwargs)

class Radial(BaseKeyword):
  """ Defines radial CRYSTAL keyword. """
  keyword = 'radial'
  def __init__(self, intervals=None, nbpoints=None):
    """ Creates RADIAL keyword """
    super(Radial, self).__init__()
    self.intervals = intervals
    self.nbpoints  = nbpoints
  @property
  def intervals(self): 
    """ Upper limits of the intervals in ascending order """
    return self._intervals
  @intervals.setter
  def intervals(self, value): 
    from numpy import array, all
    from ..error import input
    if value is None: self._intervals = None; return
    self._intervals = [float(u) for u in value]
    # sanity check
    if not all(array(self.intervals[1:]) > array(self.intervals[:-1])):
      raise input('Interval limits are not given in ascending order')
  @property
  def nbpoints(self): 
    """ Number of integration points per interval """
    return self._nbpoints
  @nbpoints.setter
  def nbpoints(self, value): 
    if value is None: self._nbpoints = None; return
    self._nbpoints = [int(u) for u in value]

  @property
  def raw(self):
    """ Raw CRYSTAL input """
    if self.intervals is None or self.nbpoints is None: return ''
    n = min(len(self.intervals), len(self.nbpoints))
    a = (str(u) for u in self.intervals[:n])
    b = (str(u) for u in self.nbpoints[:n])
    return '{0}\n{1}\n{2}'.format(n, ' '.join(a), ' '.join(b))
  @raw.setter
  def raw(self, value):
    """ Sets value from CRYSTAL input """
    value = value.split()
    n = int(value[0])
    self.intervals = value[1:n+1]
    self.nbpoints = value[n+1:]
  def output_map(self, **kwargs):
    """ Prints CRYSTAL input """
    from ..error import input
    if self.intervals is None or self.nbpoints is None: return None
    crystal = kwargs['crystal'].dft
    if crystal.lgrid or crystal.xlgrid or crystal.xxlgrid: return None
    # sanity check
    if len(self.intervals) != len(self.nbpoints):
      raise input('Different number of interval limits and points per interval')
    return super(Radial, self).output_map()
  def __repr__(self):
    """ Dumps representation of self. """
    args = []
    if self.intervals is None: 
      if self.nbpoints is not None:
        args.append('nbpoints={0.nbpoints!r}'.formas(self))
    else:
      args.append(repr(self.intervals))
      if self.nbpoints is not None: args.append(repr(self.nbpoints))
    return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
  def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
    from numpy import array, all, abs
    from ..tools.uirepr import add_to_imports
    if name is None:
      name = getattr(self, '__ui_name__', self.__class__.__name__.lower())
    if defaults is None or type(defaults) != type(self):
      add_to_imports(self, imports)
      return {name: self.__repr__()}
    if self.nbpoints is None: issame = defaults.nbpoints is None
    elif defaults.nbpoints is None: issame = False
    else: issame = all(array(defaults.nbpoints) == self.nbpoints)
    if issame:
      if self.intervals is None: issame = defaults.intervals is None
      elif defaults.intervals is None: issame = False
      else:
        issame = all(abs(array(defaults.intervals) - self.intervals) < 1e-8)
    if issame: return {}
    return { '{0}.intervals'.format(name): repr(self.intervals), 
             '{0}.nbpoints'.format(name): repr(self.nbpoints) }

class Angular(BaseKeyword):
  """ Defines angular CRYSTAL keyword. """
  keyword = 'angular'
  def __init__(self, intervals=None, levels=None):
    """ Creates RADIAL keyword """
    super(Angular, self).__init__()
    self.intervals = intervals
    self.levels  = levels
  @property
  def intervals(self): 
    """ Upper limits of the intervals in ascending order """
    return self._intervals
  @intervals.setter
  def intervals(self, value): 
    from numpy import array, all
    from ..error import input
    if value is None: self._intervals = None; return
    self._intervals = [float(u) for u in value]
    # sanity check
    if not all(array(self.intervals[1:]) > array(self.intervals[:-1])):
      raise input('Interval limits are not given in ascending order')
  @property
  def levels(self): 
    """ Accuracy level of each interval """
    return self._levels
  @levels.setter
  def levels(self, value): 
    if value is None: self._levels = None; return
    self._levels = [int(u) for u in value]

  @property
  def raw(self):
    """ Raw CRYSTAL input """
    if self.intervals is None or self.levels is None: return ''
    n = min(len(self.intervals), len(self.levels))
    a = (str(u) for u in self.intervals[:n])
    b = (str(u) for u in self.levels[:n])
    return '{0}\n{1}\n{2}'.format(n, ' '.join(a), ' '.join(b))
  @raw.setter
  def raw(self, value):
    """ Sets value from CRYSTAL input """
    value = value.split()
    n = int(value[0])
    self.intervals = value[1:n+1]
    self.levels = value[n+1:]
  def output_map(self, **kwargs):
    """ Prints CRYSTAL input """
    from ..error import input
    if self.intervals is None or self.levels is None: return None
    crystal = kwargs['crystal'].dft
    if crystal.lgrid or crystal.xlgrid or crystal.xxlgrid: return None
    # sanity check
    if len(self.intervals) != len(self.levels):
      raise input('Different number of interval limits and points per interval')
    return super(Angular, self).output_map()
  def __repr__(self):
    """ Dumps representation of self. """
    args = []
    if self.intervals is None: 
      if self.levels is not None:
        args.append('levels={0.levels!r}'.formas(self))
    else:
      args.append(repr(self.intervals))
      if self.levels is not None: args.append(repr(self.levels))
    return '{0.__class__.__name__}({1})'.format(self, ', '.join(args))
  def __ui_repr__(self, imports, name=None, defaults=None, exclude=None):
    from numpy import array, all, abs
    from ..tools.uirepr import add_to_imports
    if name is None:
      name = getattr(self, '__ui_name__', self.__class__.__name__.lower())
    if defaults is None or type(defaults) != type(self):
      add_to_imports(self, imports)
      return {name: self.__repr__()}
    if self.levels is None: issame = defaults.levels is None
    elif defaults.levels is None: issame = False
    else: issame = all(array(defaults.levels) == self.levels)
    if issame:
      if self.intervals is None: issame = defaults.intervals is None
      elif defaults.intervals is None: issame = False
      else:
        issame = all(abs(array(defaults.intervals) - self.intervals) < 1e-8)
    if issame: return {}
    return { '{0}.intervals'.format(name): repr(self.intervals), 
             '{0}.levels'.format(name): repr(self.levels) }

class GlobalGridKeyword(BaseKeyword):
  """ Defines global grid keywords """
  def __init__(self, values=None, keyword=None):
    from copy import deepcopy
    super(GlobalGridKeyword, self).__init__(keyword=keyword)
    self.values = deepcopy(values)
    """ Values against which to test. """

  def __get__(self, instance, owner=None):
    """ True if global keyword is set. """
    def test(a, b):
      from numpy import array, abs, any
      if a is None:
        if b is not None: return False
      else: 
        if b is None: return False
        if len(a) != len(b): return False
        if any(abs(array(a)-array(b)) > 1e-8): return False
      return True
    return test(self.values[0], instance.radial.intervals)                     \
           and test(self.values[1], instance.radial.nbpoints)                  \
           and test(self.values[2], instance.angular.intervals)                \
           and test(self.values[3], instance.angular.levels)
  def __set__(self, inst, value):
    """ Sets to global if value is True. 
    
        If False, ignores.
    """
    if value == True:
      inst.radial.intervals, inst.radial.nbpoints,                             \
        inst.angular.intervals, inst.angular.levels =  self.values
  def output_map(self, **kwargs):
    if self.__get__(kwargs['crystal'].dft): 
      return {self.keyword: True}
  def read_input(self, tree, owner=None, **kwargs):
    """ True if ever read. """
    self.__set__(owner, True)

[docs]class Dft(AttrBlock): """ DFT attribute block. Holds parameters relative to the Hamiltonian used in the calculation. These parameters can be accessed indirectly in tha functional: >>> functional.dft.b3lyp = True >>> functional.dft.xxlgrid = True The above would set the Hamiltonian to the B3LYP exchange-correlation functional and the integration grid to 64 fl.oz. """ keyword = 'DFT' """ CRYSTAL keyword. """ def __init__(self): """ Creates the DFT attribute block. """ super(Dft, self).__init__() self.exchange = Exchange() """ Exchange functional. Name of the exchange functional to use. It should be one of the following: 'becke', 'lda', 'pbe', 'pbesol', 'pwgga', 'sogga', 'vbh', 'wcgga', or None(default). """ self.correlat = Correlation() """ Correlation functional. Name of the correlation functional to use. It can be one of the following: 'lyp', 'p86', 'pbe', 'pbesol', 'pwgga', 'pwlsd', 'pz', 'vbh', 'wl', 'vwn', or None. """ self.hybrid = Hybrid() """ Amount of exchange to add to functional. It should be a floating point or None. """ self.nonlocal = NonLocal() """ Non-local weights on exchange-correlation. A tuple of two floating point which sets the weights of the non-local part of the exchange (first) and of the correlations (second). It can also be None. """ self.spin = BoolKeyword(keyword='spin') """ If True, then perform spin-polarized calculation. It should be None(default), True, or False. """ self.b3lyp = GlobalExc('b3lyp', ['becke', 'lyp', 20, 0.9, 0.81]) """ B3LYP global keyword. Sets :py:attr:`exchange`, :py:attr:`correlat`, :py:attr:`nonlocal` correctly for this functional. Since it acts on/checks upon other attributes, it can only be set to True. """ self.b3pw = GlobalExc('b3pw', ['becke', 'pwgga', 20, 0.9, 0.81]) """ B3PW global keyword. Sets :py:attr:`exchange`, :py:attr:`correlat`, :py:attr:`nonlocal` correctly for this functional. Since it acts on/checks upon other attributes, it can only be set to True. """ self.pbe0 = GlobalExc('pbe0', ['pbe', 'pbe', None, None, None]) """ PBE0 global keyword. Sets :py:attr:`exchange`, :py:attr:`correlat`, :py:attr:`nonlocal` correctly for this functional. Since it acts on/checks upon other attributes, it can only be set to True. """ self.soggaxc = GlobalExc('soggaxc', ['sogga', 'pbe', None, None, None]) """ SOGGAXC global keyword. Sets :py:attr:`exchange`, :py:attr:`correlat`, :py:attr:`nonlocal` correctly for this functional. Since it acts on/checks upon other attributes, it can only be set to True. """ self.angular = Angular() """ Angular integration grid. Contains two attributes ``intervals`` and ``levels`` which can be used to set the angular grid. """ self.radial = Radial() """ Radial integration grid. Contains two attributes ``intervals`` and ``nbpoints`` which can be used to set the radial integration grid. """ self.lgrid = GlobalGridKeyword(( [4], [75], [0.1667, 0.5, 0.9, 3.05, 9999.0], [2, 6, 8, 13, 8] )) """ Preset large integration grid. """ self.xlgrid = GlobalGridKeyword(( [4], [75], [0.1667, 0.5, 0.9, 3.5, 9999.0], [2, 8, 12, 16, 12] )) """ Preset extra large integration grid. """ self.xxlgrid = GlobalGridKeyword(( [4], [99], [0.1667, 0.5, 0.9, 3.5, 9999.0], [6, 8, 14, 18, 14] )) """ Preset extra extra large integration grid. """ self.tollgrid = TypedKeyword(type=int) """ DFT grid weight tolerance. Should be None(default) or an integer. """ self.tolldens = TypedKeyword(type=int) """ DFT density tolerance Should be None(default) or an integer. """ def __ui_repr__(self, imports, name=None, defaults=None, exclude=None): """ Creates user friendly representation. """ exclude = [] if exclude is None else list(exclude) if not self.b3lyp: exclude.append('b3lyp') if not self.b3pw: exclude.append('b3pw') if not self.pbe0: exclude.append('pbe0') if not self.soggaxc: exclude.append('soggaxc') if self.b3lyp or self.b3pw or self.pbe0 or self.soggaxc: exclude.extend(['exchange', 'correlat', 'hybrid', 'nonlocal']) if not self.lgrid: exclude.append('lgrid') if not self.xlgrid: exclude.append('xlgrid') if not self.xxlgrid: exclude.append('xxlgrid') if self.lgrid or self.lgrid or self.xxlgrid: exclude.extend(['angular', 'radial']) return super(Dft, self).__ui_repr__(imports, name, defaults, exclude)