Ejemplo n.º 1
0
def main():
    args = parse_args()

    fn_h5, grp_name = parse_h5(args.output, 'output')
    # check if the group is already present (and not empty) in the output file
    if check_output(fn_h5, grp_name, args.overwrite):
        return

    # Load the system
    sys = System.from_file(args.wfn)

    # Define a list of optional arguments for the WPartClass:
    WPartClass = wpart_schemes[args.scheme]
    kwargs = dict((key, val) for key, val in vars(args).iteritems() if key in WPartClass.options)

    # Load the proatomdb
    if args.atoms is not None:
        proatomdb = ProAtomDB.from_file(args.atoms)
        proatomdb.normalize()
        kwargs['proatomdb'] = proatomdb
    else:
        proatomdb = None

    # Run the partitioning
    agspec = AtomicGridSpec(args.grid)
    molgrid = BeckeMolGrid(sys, agspec, mode='only')
    sys.update_grid(molgrid) # for the grid to be written to the output
    wpart = wpart_schemes[args.scheme](sys, molgrid, **kwargs)
    names = wpart.do_all()

    write_part_output(fn_h5, grp_name, wpart, names, args)
Ejemplo n.º 2
0
    def getBeckeGrid(self, resolution='fine', new=False, **kwargs):
        """
    coarse, medium, fine, veryfine, ultrafine and insane
    """
        if not hasattr(self, 'molecule'):
            qtk.exit("no molecule structure found")
        if new or not hasattr(self, 'grid'):
            molecule = self.molecule
            coord = np.array(np.atleast_2d(molecule.R * 1.8897261245650618))
            self.grid = BeckeMolGrid(coord, molecule.Z.astype(int), molecule.Z,
                                     resolution)

            mol_str = []
            for i in range(molecule.N):
                atm_str = [molecule.type_list[i]]
                for j in range(3):
                    atm_str.append(str(molecule.R[i, j]))
                mol_str.append(' '.join(atm_str))
            mol_str = '; '.join(mol_str)

            if 'gto_kwargs' in kwargs:
                mol = gto.Mole(**kwargs['gto_kwargs'])
            else:
                mol = gto.Mole()

            #mol.build(atom=mol_str, basis=self.setting['basis_set'])
            if hasattr(self, 'basis_name'):
                basis = self.basis_name
            self.basisFormat()
            mol.build(atom=mol_str, basis=self.pybasis)
            self.mol = mol
            del_list = ['_phi', '_psi', '_dphi', '_dpsi', '_rho', '_drho']
            for p in del_list:
                if hasattr(self, p):
                    delattr(self, p)
Ejemplo n.º 3
0
    def __init__(self,
                 coordinates,
                 numbers,
                 pseudo_numbers,
                 specs='medium',
                 k=3,
                 rotate=False):
        """Initialize class.

        Parameters
        ----------
        coordinates : np.ndarray, shape=(M, 3)
            Cartesian coordinates of `M` atoms in the molecule.
        numbers : np.ndarray, shape=(M,)
            Atomic number of `M` atoms in the molecule.
        pseudo_numbers : np.ndarray, shape=(M,)
            Pseudo-number of `M` atoms in the molecule.
        specs : str, optional
            Specification of grid. Either choose from ['coarse', 'medium', 'fine', 'veryfine',
            'ultrafine', 'insane'] or provide a string of 'rname:rmin:rmax:nrad:nang' format.
            Here 'rname' denotes the type of radial grid and can be chosen from ['linear', 'exp',
            'power'], 'rmin' and 'rmax' specify the first and last radial grid points in angstrom,
            'nrad' specify the number of radial grid points, and 'nang' specify the number of
            angular Lebedev-Laikov grid. The 'nang' can be chosen from (6, 14, 26, 38, 50, 74, 86,
            110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, 1454, 1730, 2030,
            2354, 2702, 3074, 3470, 3890, 4334, 4802, 5294, 5810).
        k : int, optional
            The order of the switching function in Becke's weighting scheme.
        rotate : bool, optional
            Whether to randomly rotate spherical grids.

        """
        self._coordinates = coordinates
        self._numbers = numbers
        self._pseudo_numbers = pseudo_numbers
        self._k = k
        self._rotate = rotate
        self.specs = specs

        self._grid = BeckeMolGrid(self.coordinates,
                                  self.numbers,
                                  self.pseudo_numbers,
                                  agspec=self.specs,
                                  k=k,
                                  random_rotate=rotate,
                                  mode='keep')
Ejemplo n.º 4
0
 def from_molecule(cls, mol, scheme=None, grid=None, spin="ab", **kwargs):
     if grid is None:
         grid = BeckeMolGrid(mol.coordinates, mol.numbers, mol.pseudo_numbers,
                             agspec="fine", random_rotate=False, mode='keep')
     else:
         check_molecule_grid(mol, grid)
     # compute molecular electron density
     dens = mol.compute_density(grid.points, spin=spin)
     if mol.pesudo_numbers is None:
         pesudo_numbers = mol.numbers
     else:
         pesudo_numbers = mol.pesudo_numbers
     return cls(mol.coordinates, mol.numbers, pesudo_numbers, dens, grid, scheme, **kwargs)
Ejemplo n.º 5
0
def main():
    args = parse_args()

    fn_h5, grp_name = parse_h5(args.output, 'output')
    # check if the group is already present (and not empty) in the output file
    if check_output(fn_h5, grp_name, args.overwrite):
        return

    # Load the system
    mol = IOData.from_file(args.wfn)

    # Define a list of optional arguments for the WPartClass:
    WPartClass = wpart_schemes[args.scheme]
    kwargs = dict((key, val) for key, val in vars(args).iteritems()
                  if key in WPartClass.options)

    # Load the proatomdb
    if args.atoms is not None:
        proatomdb = ProAtomDB.from_file(args.atoms)
        proatomdb.normalize()
        kwargs['proatomdb'] = proatomdb
    else:
        proatomdb = None

    # Run the partitioning
    agspec = AtomicGridSpec(args.grid)
    grid = BeckeMolGrid(mol.coordinates,
                        mol.numbers,
                        mol.pseudo_numbers,
                        agspec,
                        mode='only')
    dm_full = mol.get_dm_full()
    moldens = mol.obasis.compute_grid_density_dm(dm_full,
                                                 grid.points,
                                                 epsilon=args.epsilon)
    dm_spin = mol.get_dm_spin()
    if dm_spin is not None:
        kwargs['spindens'] = mol.obasis.compute_grid_density_dm(
            dm_spin, grid.points, epsilon=args.epsilon)
    wpart = wpart_schemes[args.scheme](mol.coordinates, mol.numbers,
                                       mol.pseudo_numbers, grid, moldens,
                                       **kwargs)
    keys = wpart.do_all()

    if args.slow:
        # ugly hack for the slow analysis involving the AIM overlap operators.
        wpart_slow_analysis(wpart, mol)
        keys = list(wpart.cache.iterkeys(tags='o'))

    write_part_output(fn_h5, grp_name, wpart, keys, args)
Ejemplo n.º 6
0
  def getBeckeGrid(self, resolution='fine', new=False, **kwargs):
    """
    coarse, medium, fine, veryfine, ultrafine and insane
    """
    if not hasattr(self, 'molecule'):
      qtk.exit("no molecule structure found")
    if new or not hasattr(self, 'grid'):
      molecule = self.molecule
      coord = np.array(np.atleast_2d(molecule.R*1.8897261245650618))
      self.grid = BeckeMolGrid(coord, 
                               molecule.Z.astype(int), 
                               molecule.Z,
                               resolution)
  
      mol_str = []
      for i in range(molecule.N):
        atm_str = [molecule.type_list[i]]
        for j in range(3):
           atm_str.append(str(molecule.R[i,j]))
        mol_str.append(' '.join(atm_str))
      mol_str = '; '.join(mol_str)
  
      if 'gto_kwargs' in kwargs:
        mol = gto.Mole(**kwargs['gto_kwargs'])
      else:
        mol = gto.Mole()

      #mol.build(atom=mol_str, basis=self.setting['basis_set'])
      if hasattr(self, 'basis_name'):
        basis = self.basis_name
      self.basisFormat()
      mol.build(atom=mol_str, basis=self.pybasis)
      self.mol = mol
      del_list = ['_phi', '_psi', '_dphi', '_dpsi', '_rho', '_drho']
      for p in del_list:
        if hasattr(self, p):
          delattr(self, p)
Ejemplo n.º 7
0
    def __init__(self, molecule, **kwargs):
        if not ht_found:
            qtk.exit("horton module not found.")
        if not ps_found:
            qtk.exit("pyscf module not found.")
        if not xc_found:
            print xcpath
            print xc_found
            qtk.exit("libxc not found.")
        if 'wf_convergence' not in kwargs:
            kwargs['wf_convergence'] = 1e-06

        if 'kinetic_functional' not in kwargs:
            kwargs['kinetic_functional'] = 'LLP'

        if 'aufbau' in kwargs and kwargs['aufbau']:
            self.aufbau = True
            self.orbitals = []
        else:
            self.aufbau = False

        GaussianBasisInput.__init__(self, molecule, **kwargs)
        self.setting.update(kwargs)
        self.backup()

        if 'dft_setting' not in kwargs:
            if not self.aufbau:
                kf = self.setting['kinetic_functional']
                if kf == 'LLP':
                    self.setting['dft_setting'] = {
                        'K': {
                            1.0: 'XC_GGA_K_LLP'
                        },
                    }
            else:
                self.setting['dft_setting'] = {'K': {1.0: 'XC_GGA_K_VW'}}

            ss = self.setting['theory']
            sdft = self.setting['dft_setting']
            if ss == 'pbe':
                sdft['X'] = 'XC_GGA_X_PBE'
                sdft['C'] = 'XC_GGA_C_PBE'
            elif ss == 'blyp':
                sdft['X'] = 'XC_GGA_X_B88'
                sdft['C'] = 'XC_GGA_C_LYP'

        dft = self.setting['dft_setting']
        for k, v in dft.iteritems():
            if type(dft[k]) is str:
                dft[k] = xc_dict[v]
            elif type(dft[k]) is dict:
                for key, value in dft[k].iteritems():
                    if type(value) is str:
                        dft[k][key] = xc_dict[value]

        mol_str = []
        for i in range(molecule.N):
            atm_str = [molecule.type_list[i]]
            for j in range(3):
                atm_str.append(str(molecule.R[i, j]))
            mol_str.append(' '.join(atm_str))
        mol_str = '; '.join(mol_str)

        mol = gto.Mole()
        mol.build(atom=mol_str, basis=self.setting['basis_set'])
        ig = ps.scf.hf.get_init_guess(mol, key='atom')
        ovl = mol.intor_symmetric("cint1e_ovlp_sph")
        kin = mol.intor_symmetric("cint1e_kin_sph")
        ext = mol.intor_symmetric("cint1e_nuc_sph")
        # 4D array, can be contracted by numpy.tensordot (td)
        # nao x nao integrated density Coulomb kernel can be computed via
        # int_vee_rho = td(dm, vee, axes=([0,1], [0,1]))
        # the final electron-electron cam be computed via
        # Vee = 0.5 * trace(dm, int_vee_rho)
        nao = mol.nao_nr()
        vee = mol.intor(
            "cint2e_sph",
            comp=1,
            hermi=1,
        ).reshape(nao, nao, nao, nao)

        coord = np.array(np.atleast_2d(molecule.R * 1.8897261245650618))
        grid = BeckeMolGrid(coord, molecule.Z.astype(int), molecule.Z)

        self.molecule = molecule
        self.mol = mol
        self.ovl = ovl
        self.kin = kin
        self.ext = ext
        self.vee = vee
        self.nao = nao
        self.dv = self.normalize(sqrt(diag(ig)))
        self.initial_guess = copy.deepcopy(self.dv)
        self.ig = ig
        self.grid = grid
        self.update(self.dv)
        self.old_deltadv = None
        self.new_deltadv = None
        self.old_E = None
        self.dv_list = []
        self.psi_list = []
        self.dpsi_list = []

        self.optimizers = {
            'tnc': opt.fmin_tnc,
            'ncg': opt.fmin_ncg,
            'cg': opt.fmin_cg,
            'bfgs': opt.fmin_bfgs,
            'l_bfgs_b': opt.fmin_l_bfgs_b,
            'simplex': opt.fmin,
        }
        self.optimizer_settings = {
            'tnc': {
                'xtol': 0.0,
                'pgtol': 0.0,
                'maxfun': 1000
            },
            'simplex': {
                'xtol': 1E-10,
                'ftol': 1E-10,
                'maxfun': 1000
            },
        }

        self.orbitals = []
Ejemplo n.º 8
0
class MolecularGrid(object):
    """Becke-Lebedev molecular grid for numerical integrations."""
    def __init__(self,
                 coordinates,
                 numbers,
                 pseudo_numbers,
                 specs='medium',
                 k=3,
                 rotate=False):
        """Initialize class.

        Parameters
        ----------
        coordinates : np.ndarray, shape=(M, 3)
            Cartesian coordinates of `M` atoms in the molecule.
        numbers : np.ndarray, shape=(M,)
            Atomic number of `M` atoms in the molecule.
        pseudo_numbers : np.ndarray, shape=(M,)
            Pseudo-number of `M` atoms in the molecule.
        specs : str, optional
            Specification of grid. Either choose from ['coarse', 'medium', 'fine', 'veryfine',
            'ultrafine', 'insane'] or provide a string of 'rname:rmin:rmax:nrad:nang' format.
            Here 'rname' denotes the type of radial grid and can be chosen from ['linear', 'exp',
            'power'], 'rmin' and 'rmax' specify the first and last radial grid points in angstrom,
            'nrad' specify the number of radial grid points, and 'nang' specify the number of
            angular Lebedev-Laikov grid. The 'nang' can be chosen from (6, 14, 26, 38, 50, 74, 86,
            110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, 1454, 1730, 2030,
            2354, 2702, 3074, 3470, 3890, 4334, 4802, 5294, 5810).
        k : int, optional
            The order of the switching function in Becke's weighting scheme.
        rotate : bool, optional
            Whether to randomly rotate spherical grids.

        """
        self._coordinates = coordinates
        self._numbers = numbers
        self._pseudo_numbers = pseudo_numbers
        self._k = k
        self._rotate = rotate
        self.specs = specs

        self._grid = BeckeMolGrid(self.coordinates,
                                  self.numbers,
                                  self.pseudo_numbers,
                                  agspec=self.specs,
                                  k=k,
                                  random_rotate=rotate,
                                  mode='keep')

    @classmethod
    def from_molecule(cls, molecule, specs='medium', k=3, rotate=False):
        """Initialize the class given an instance of Molecule.

        Parameters
        ----------
        molecule : instance of Molecule
            Instance of Molecule class.
        specs : str, optional
            Specification of grid. Either choose from ['coarse', 'medium', 'fine', 'veryfine',
            'ultrafine', 'insane'] or provide a string of 'rname:rmin:rmax:nrad:nang' format.
            Here 'rname' denotes the type of radial grid and can be chosen from ['linear', 'exp',
            'power'], 'rmin' and 'rmax' specify the first and last radial grid points in angstrom,
            'nrad' specify the number of radial grid points, and 'nang' specify the number of
            angular Lebedev-Laikov grid. The 'nang' can be chosen from (6, 14, 26, 38, 50, 74, 86,
            110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, 1454, 1730, 2030,
            2354, 2702, 3074, 3470, 3890, 4334, 4802, 5294, 5810).
        k : int, optional
            The order of the switching function in Becke's weighting scheme.
        rotate : bool, optional
            Whether to randomly rotate spherical grids.

        """
        if not isinstance(molecule, Molecule):
            raise TypeError(
                'Argument molecule should be an instance of Molecule class.')
        coords, nums, pnums = molecule.coordinates, molecule.numbers, molecule.pseudo_numbers
        return cls(coords, nums, pnums, specs, k, rotate)

    @classmethod
    def from_file(cls, fname, specs='medium', k=3, rotate=False):
        """Initialize the class given an instance of Molecule.

        Parameters
        ----------
        fname : str
            Path to molecule's files.
        specs : str, optional
            Specification of grid. Either choose from ['coarse', 'medium', 'fine', 'veryfine',
            'ultrafine', 'insane'] or provide a string of 'rname:rmin:rmax:nrad:nang' format.
            Here 'rname' denotes the type of radial grid and can be chosen from ['linear', 'exp',
            'power'], 'rmin' and 'rmax' specify the first and last radial grid points in angstrom,
            'nrad' specify the number of radial grid points, and 'nang' specify the number of
            angular Lebedev-Laikov grid. The 'nang' can be chosen from (6, 14, 26, 38, 50, 74, 86,
            110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, 1454, 1730, 2030,
            2354, 2702, 3074, 3470, 3890, 4334, 4802, 5294, 5810).
        k : int, optional
            The order of the switching function in Becke's weighting scheme.
        rotate : bool, optional
            Whether to randomly rotate spherical grids.

        """
        mol = Molecule.from_file(fname)
        return cls.from_molecule(mol, specs, k, rotate)

    def __getattr__(self, item):
        return getattr(self._grid, item)

    @property
    def center(self):
        """Cartesian coordinates of atomic centers."""
        return self._coordinates

    @property
    def coordinates(self):
        """Cartesian coordinates of atomic centers."""
        return self._coordinates

    @property
    def numbers(self):
        """Atomic number of atomic centers."""
        return self._numbers

    @property
    def pseudo_numbers(self):
        """Pseudo atomic number of atomic centers."""
        return self._pseudo_numbers

    @property
    def npoints(self):
        """Number of grid points."""
        return self._grid.points.shape[0]

    @property
    def points(self):
        """Cartesian coordinates of grid points."""
        return self._grid.points

    @property
    def weights(self):
        """Integration weight of grid points."""
        return self._grid.weights

    def integrate(self, *value):
        """Integrate the property evaluated on the grid points.

        Parameters
        ----------
        value : np.ndarray
           Property value evaluated on the grid points.

        """
        # temporary hack because of HORTON
        if not isinstance(value, np.ndarray):
            temp = value[0]
            for item in value[1:]:
                if item is not None:
                    temp = temp * item
            value = temp
        if value.ndim != 1:
            raise ValueError('Argument value should be a 1D array.')
        if value.shape != (self.npoints, ):
            raise ValueError('Argument value should have ({0},) shape!'.format(
                self.npoints))
        return self._grid.integrate(value)

    def compute_spherical_average(self, value):
        """Compute spherical average of given value evaluated on the grid points.

        Note: This method only works for atomic systems with one nuclear center.

        Parameters
        ----------
        value : np.ndarray
           Property value evaluated on the grid points.

        """
        if len(self.numbers) != 1:
            raise NotImplementedError(
                'This method only works for systems with one atom!')
        if value.ndim != 1:
            raise ValueError('Argument value should be a 1D array.')
        if value.shape != (self.npoints, ):
            raise ValueError('Argument value should have ({0},) shape!'.format(
                self.npoints))
        return self._grid.get_spherical_average(value)
Ejemplo n.º 9
0
class GaussianBasisOutput(GenericQMOutput):
  def __init__(self, output=None, **kwargs):
    GenericQMOutput.__init__(self, output, **kwargs)

  def copy(self):
    if hasattr(self, 'mol'):
      del self.mol
    if hasattr(self, 'grid'):
      del self.grid
    return copy.deepcopy(self)

  def mo_g09_nwchem(self):
    mo = self.mo_vectors
    ind = np.arange(len(mo[0]))
    itr = 0

    # hard coded reordering for d and f orbitals
    order = {
     'd': [0, 3, 4, 1, 5, 2],
     'f': [0, 4, 5, 3, 9, 6, 1, 8, 7, 2],
    }

    while itr < len(mo[0]):
      bStr = self.basis[itr]['type']
      if len(bStr) > 2:
        key = bStr[0]
        if key in order.keys():
          ind_itr = ind[itr: itr+len(order[key])]
          ind_itr = ind_itr[order[key]]
          for i in range(len(order[bStr[0]])):
            ind[itr + i] = ind_itr[i]
          itr += len(order[bStr[0]]) - 1
        else:
          qtk.exit("basis reordering for %s orbital " % key\
                 + "not implemented yet")
      itr += 1
    return mo[:, ind]

  def basisFormat():
    if not hasattr(self, 'basis'):
      qtk.exit("no basis found")
    pass

  def getBeckeGrid(self, resolution='fine', new=False, **kwargs):
    """
    coarse, medium, fine, veryfine, ultrafine and insane
    """
    if not hasattr(self, 'molecule'):
      qtk.exit("no molecule structure found")
    if new or not hasattr(self, 'grid'):
      molecule = self.molecule
      coord = np.array(np.atleast_2d(molecule.R*1.8897261245650618))
      self.grid = BeckeMolGrid(coord, 
                               molecule.Z.astype(int), 
                               molecule.Z,
                               resolution)
  
      mol_str = []
      for i in range(molecule.N):
        atm_str = [molecule.type_list[i]]
        for j in range(3):
           atm_str.append(str(molecule.R[i,j]))
        mol_str.append(' '.join(atm_str))
      mol_str = '; '.join(mol_str)
  
      if 'gto_kwargs' in kwargs:
        mol = gto.Mole(**kwargs['gto_kwargs'])
      else:
        mol = gto.Mole()

      #mol.build(atom=mol_str, basis=self.setting['basis_set'])
      if hasattr(self, 'basis_name'):
        basis = self.basis_name
      mol.build(atom=mol_str, basis=basis)
      self.mol = mol
      del_list = ['_phi', '_psi', '_dphi', '_dpsi', '_rho', '_drho']
      for p in del_list:
        if hasattr(self, p):
          delattr(self, p)

  def getPhi(self, cartesian=True, resolution='fine', new=False, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_phi'):
      self.getBeckeGrid(resolution, new, **kwargs)
      coords = self.grid.points
      if 'gridpoints' not in kwargs:
        grid_coords = None
      else:
        grid_coords = np.array(kwargs['gridpoints']).astype(float)
      if cartesian:
        mode = "GTOval_cart"
      else:
        mode = "GTOval_sph"
      self._phi = self.mol.eval_gto(mode, coords).T
      norm = np.dot(self._phi * self.grid.weights, self._phi.T)

      if grid_coords is not None:
        self._phi = self.mol.eval_gto(mode, grid_coords).T
        self._phi = self._phi / np.sqrt(np.diag(norm))[:, np.newaxis]
      else:
        self._phi = self._phi / np.sqrt(np.diag(norm))[:, np.newaxis]
    return self._phi

  def getDPhi(self, cartesian=True, resolution='fine', new=False, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_dphi'):
      self.getBeckeGrid(resolution, new, **kwargs)
      if 'gridpoints' not in kwargs:
        coords = self.grid.points
      else:
        coords = np.array(kwargs['gridpoints']).astype(float)
      if cartesian:
        mode = "GTOval_ip_cart"
      else:
        mode = "GTOval_ip_sph"
      self._dphi = self.mol.eval_gto(mode, coords, comp=3).T
      
    return self._dphi

  def getPsi(self, cartesian=True, resolution='fine', new=False, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_psi'):
      self.getPhi(cartesian, resolution, new, **kwargs)
      if not hasattr(self, 'mo_vectors'):
        qtk.exit('mo_vectors not found')
      mo = self.mo_vectors
      if hasattr(self, 'program'):
        if self.program == 'gaussian':
          mo = self.mo_g09_nwchem()
      self._psi = np.dot(mo, self._phi)
    return self._psi

  def getDPsi(self, cartesian=True, resolution='fine', new=False, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_dpsi'):
      self.getDPhi(cartesian, resolution, new, **kwargs)
      if not hasattr(self, 'mo_vectors'):
        qtk.exit('mo_vectors not found')
      mo = self.mo_vectors
      if hasattr(self, 'program'):
        if self.program == 'gaussian':
          mo = self.mo_g09_nwchem()
      self._dpsi = np.dot(mo, np.swapaxes(self._dphi, 0, 1))
    return self._dpsi

  def getRho(self, cartesian=True, resolution='fine', new=False, occupation=None, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_rho'):
      self.getPsi(cartesian, resolution, new, **kwargs)
      if not hasattr(self, 'occupation'):
        qtk.exit("occupation number not found")
      if occupation is None:
        occ = np.array(self.occupation)
      else:
        occ = np.array(occupation)
      self._rho = np.sum(self._psi**2 * occ[:, np.newaxis], axis = 0)
    return self._rho

  def getDRho(self, cartesian=True, resolution='fine', new=False, occupation=None, **kwargs):
    if 'gridpoints' in kwargs:
      new = True
    if new or not hasattr(self, '_drho'):
      if not hasattr(self, '_psi'):
        _psi = self.getPsi(cartesian, resolution, new, **kwargs)
      if not hasattr(self, '_dpsi'):
        _dpsi = self.getDPsi(cartesian, resolution, new, **kwargs)
      if not hasattr(self, 'occupation'):
        qtk.exit("occupation number not found")
      self._psi, self._dpsi = _psi, _dpsi
      if occupation is None:
        occ = np.array(self.occupation)
      else:
        occ = np.array(occupation)
      self._drho = 2 * np.sum(
        self._psi[..., np.newaxis] * self._dpsi \
        * occ[:, np.newaxis, np.newaxis],
        axis = 0
      )
    return self._drho

  def freeRho(self, coord, gridpoints, **kwargs):
    assert self.molecule.N == 1
    new = self.copy()
    new.molecule.R[0] = np.array(coord) / 1.8897261245650618
    if 'spin' not in kwargs:
      if new.molecule.getValenceElectrons() % 2 != 0:
        kw = {'gto_kwargs': {'spin': 1}}
      else:
        kw = {}
    else:
      kw = {'gto_kwargs': {'spin': kwargs['spin']}}
    if type(gridpoints[0][0]) is not float:
      pl = np.array(gridpoints).astype(float)
    else:
      pl = gridpoints
    return new.getRho(gridpoints = pl, **kw)

  def _e_setting(self, gridpoints, **kwargs):
    new = self.copy()
    kw = {}
    if 'spin' not in kwargs:
      if new.molecule.getValenceElectrons() % 2 != 0:
        kw['gto_kwargs'] = {'spin': 1}
    else:
      kw['gto_kwargs'] = {'spin': spin}
    if 'resolution' in kwargs:
      kw['resolution'] = kwargs['resolution']
    if 'cartesian' in kwargs:
      kw['cartesian'] = kwargs['cartesian']
    occ = np.array(new.occupation)
    gridpoints = np.atleast_2d(gridpoints).astype(float)
    return new, occ, kw, gridpoints

  def e_kin(self, gridpoints, **kwargs):
    """
    kinetic energy density
    """
    new, occ, kw, gridpoints = self._e_setting(gridpoints, **kwargs)
    dpsi = new.getDPsi(gridpoints = gridpoints, **kw)
    dpsi2 = np.linalg.norm(dpsi, axis=-1) ** 2
    return (0.5 * dpsi2 * occ[:, np.newaxis]).sum(axis=0)

  def e_ext(self, gridpoints, **kwargs):
    """
    external potential energy density
    """
    new, occ, kw, gridpoints = self._e_setting(gridpoints, **kwargs)

    ext = np.zeros(len(gridpoints))
    for I in range(self.molecule.N):
      RI = self.molecule.R[I] * 1.8897261245650618
      ZI = self.molecule.Z[I]
      R_list = gridpoints - RI
      ext -= ZI / np.linalg.norm(R_list, axis=1)

    psi = new.getPsi(gridpoints = gridpoints, **kw)
    return (psi * occ[:, np.newaxis]).sum(axis=0) * ext

  def e_xc(self, gridpoints, dft='pbe', **kwargs):
    new, occ, kw, gridpoints = self._e_setting(gridpoints, **kwargs)
    xc_list = [xc_dict[f] for f in qtk.setting.libxc_dict[dft]]

    drho = new.getDRho(gridpoints=gridpoints, new=True, **kw)
    sigma = np.sum(drho ** 2, axis = 1)
    rho = new.getRho(gridpoints = gridpoints, **kw)
    x = libxc_exc(rho, sigma, len(rho), xc_list[0])
    c = libxc_exc(rho, sigma, len(rho), xc_list[1])
    return x, c

  def e_coulomb(self, gridpoints, **kwargs):
    new, occ, kw, gridpoints = self._e_setting(gridpoints, **kwargs)
    kernel = self.coulombKernel(gridpoints / 1.8897261245650618, **kwargs)
    rho = new.getRho(gridpoints = gridpoints, **kw)
    return 0.5 * rho * kernel

  def epsilon(self, gridpoints, dft='pbe', **kwargs):
    new, occ, kw, gridpoints = self._e_setting(gridpoints, **kwargs)
    kin = self.e_kin(gridpoints, **kw)
    ext = self.e_ext(gridpoints, **kw)
    x, c = self.e_xc(gridpoints, **kw)
    coulomb = self.e_coulomb(gridpoints, **kw)
    return kin + ext + x + c + coulomb

  def getDipole(self, cartesian=True, resolution='fine', unit='debye'):
    if not hasattr(self, 'molecule'):
      qtk.exit('molecule structure not found')
    if not hasattr(self, '_rho'):
      self.getRho(cartesian, resolution)
    pQ = np.array(
      [sum(self.molecule.Z * self.molecule.R[:,i]) for i in range(3)]
    )
    pQ = pQ * 1.8897259885789
    pq = np.array(
      [self.grid.integrate(self._rho * self.grid.points[:,i]) 
       for i in range(3)]
    )
    pq = pq 
    mu = pQ - pq
    if unit == 'debye':
      return mu / 0.393430307
    else:
      return mu

  def keMatrix(self):
    return keMatrix(self.basis)

  def densityMatrix(self):
    return densityMatrix(self)

  def veMatrix(self, coord = None, Z = None):
    if coord is None:
      coord = self.molecule.R
    if Z is None:
      Z = self.molecule.Z
    return veMatrix(self.basis, coord, Z)

  def eeKernel(self, coord=None, batch_size=1):
    if coord is None:
      coord = self.molecule.R
    else:
      coord = np.atleast_2d(coord).astype(float)
    out = []
    itr = 1
    for chunk in np.array_split(coord, batch_size):
      if itr > 1:
        qtk.progress("eeKernel", "processing batch: %d\n" % itr)
      itr += 1
      out.append(eeKernel(self.basis, chunk))
    return np.concatenate(out)

  def coulombKernel(self, coord = None, **kwargs):
    k = self.eeKernel(coord, **kwargs)
    mo = self.mo_vectors
    # out = np.diagonal(
    #   np.einsum('is,jl,kls->kij', mo, mo, k),
    #   axis1=1, axis2=2,
    # ).sum(1)
    # 
    # diagonal: np.einsum('is,il, kls->ki', mo, mo, k)
    #   or np.einsum('ii->i', a) for diagonal
    # sum diagonal: np.einsum('is,il, kls->k', mo, mo, k)
    #   or np.einsum('ii', a) for trace
    out = np.einsum('is,il, kls->k', mo, mo, k)
    return out

  def eeMatrix(self):
    if not hasattr(self, '_eeMatrix'):
      self._eeMatrix = eeMatrix(self.basis)
    return self._eeMatrix

  def EJ(self):
    ee = 2*self.eeMatrix()
    td = np.tensordot
    mo = self.mo_vectors
    out = td(mo, ee, axes=(1, 0))
    out = td(mo, out, axes=(1, 1))
    out = td(mo, out, axes=(1, 2))
    out = td(mo, out, axes=(1, 3))
    occ = int(np.sum(self.occupation) / 2.)
    EJ = [out[a,a,b,b] for a in range(occ) for b in range(occ)]
    return sum(EJ)

  def EX(self):
    ex = -np.swapaxes(self.eeMatrix(), 1,2)
    td = np.tensordot
    mo = self.mo_vectors
    out = td(mo, ex, axes=(1, 0))
    out = td(mo, out, axes=(1, 1))
    out = td(mo, out, axes=(1, 2))
    out = td(mo, out, axes=(1, 3))
    occ = int(np.sum(self.occupation) / 2.)
    EX = [out[a,a,b,b] for a in range(occ) for b in range(occ)]
    return sum(EX)

  def EK(self):
    km = self.keMatrix()
    dm = self.densityMatrix()
    return np.trace(np.dot(dm, km))

  def Eext(self):
    vext = self.veMatrix()
    dm = self.densityMatrix()
    return np.trace(np.dot(dm, vext))