Beispiel #1
0
def pbc_h1():
    # get the hamiltonian for pbc system
    # setup the environment
    pos = torch.tensor([0.0, 0.0, 0.0], dtype=dtype)
    atomz = 2
    atom = "He"
    bases = loadbasis("%d:3-21G" % atomz, dtype=dtype)
    atombases = [AtomCGTOBasis(atomz=atomz, bases=bases, pos=pos)]
    kpts = torch.tensor([[0.0, 0.0, 0.0], [0.1, 0.2, 0.15]], dtype=dtype)

    # setup the lattice
    a = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], dtype=dtype) * 3
    latt = Lattice(a)

    # setup the auxbasis
    auxbases = loadbasis("%d:def2-sv(p)-jkfit" % atomz, dtype=dtype)
    atomauxbases = [AtomCGTOBasis(atomz=atomz, bases=auxbases, pos=pos)]
    df = DensityFitInfo(method="gdf", auxbases=atomauxbases)

    # build the hamiltonian
    h = HamiltonCGTO_PBC(atombases, latt=latt, df=df, kpts=kpts,
                         lattsum_opt={"precision": 1e-8})
    h.build()

    # pyscf system build
    mol = pyscf.pbc.gto.C(atom="%s 0 0 0" % atom, a=a.numpy(), basis="3-21G", unit="Bohr", spin=0)
    df = pyscf.pbc.df.GDF(mol, kpts=kpts.detach().numpy())
    df.auxbasis = "def2-svp-jkfit"
    df.build()

    return h, df
Beispiel #2
0
def _renormalize_auxbases(
        auxbases: List[AtomCGTOBasis]) -> List[AtomCGTOBasis]:
    # density basis renormalization, following pyscf here:
    # https://github.com/pyscf/pyscf/blob/7be5e015b2b40181755c71d888449db936604660/pyscf/pbc/df/df.py#L95
    # this renormalization makes the integral of auxbases (not auxbases * auxbases)
    # to be 1

    res: List[AtomCGTOBasis] = []
    # libcint multiply np.sqrt(4*pi) to the basis
    half_sph_norm = 0.5 / np.sqrt(np.pi)
    for atb in auxbases:  # atb is AtomCGTOBasis
        bases: List[CGTOBasis] = []
        for bas in atb.bases:  # bas is CGTOBasis
            assert bas.normalized
            int1 = gaussian_int(bas.angmom * 2 + 2, bas.alphas)
            s = torch.sum(bas.coeffs * int1)
            coeffs = bas.coeffs * (half_sph_norm / s)
            b2 = CGTOBasis(angmom=bas.angmom,
                           coeffs=coeffs,
                           alphas=bas.alphas,
                           normalized=True)
            bases.append(b2)
        atb2 = AtomCGTOBasis(atomz=atb.atomz, bases=bases, pos=atb.pos)
        res.append(atb2)
    return res
Beispiel #3
0
    def _create_compensating_bases(self, atombases: List[AtomCGTOBasis], eta: float) -> List[AtomCGTOBasis]:
        # create the list of atom bases containing the compensating basis with
        # given `eta` as the exponentials
        # see make_modchg_basis in
        # https://github.com/pyscf/pyscf/blob/c9aa2be600d75a97410c3203abf35046af8ca615/pyscf/pbc/df/df.py#L116

        # pre-calculate the norms up to angmom 6
        half_sph_norm = 0.5 / np.sqrt(np.pi)
        norms = [half_sph_norm / gaussian_int(2 * angmom + 2, eta) for angmom in range(7)]
        norms_t = [torch.tensor([nrm], dtype=self.dtype, device=self.device) for nrm in norms]

        res: List[AtomCGTOBasis] = []
        alphas = torch.tensor([eta], dtype=self.dtype, device=self.device)
        for atb in atombases:
            # TODO: use reduced bases to optimize the integration time
            # angmoms = set(bas.angmom for bas in atb.bases)
            # bases = [
            #     CGTOBasis(angmom=angmom, alphas=alphas, coeffs=norms[angmom], normalized=True) \
            #     for angmom in angmoms
            # ]
            bases: List[CGTOBasis] = []
            for bas in atb.bases:
                # calculate the integral of the basis
                int1 = gaussian_int(bas.angmom * 2 + 2, bas.alphas)
                s = torch.sum(bas.coeffs * int1) / half_sph_norm

                # set the coefficients of the compensating basis to have
                # the same integral
                coeffs = s * norms_t[bas.angmom]
                b2 = CGTOBasis(angmom=bas.angmom, alphas=alphas,
                               coeffs=coeffs,
                               normalized=True)
                bases.append(b2)
            res.append(AtomCGTOBasis(atomz=0, bases=bases, pos=atb.pos))
        return res
Beispiel #4
0
    def __init__(
            self,
            soldesc: Union[str, Tuple[AtomZsType, AtomPosType]],
            alattice: torch.Tensor,
            basis: Union[str, List[CGTOBasis], List[str],
                         List[List[CGTOBasis]]],
            *,
            grid: Union[int, str] = "sg3",
            spin: Optional[ZType] = None,
            lattsum_opt: Optional[Union[PBCIntOption, Dict]] = None,
            dtype: torch.dtype = torch.float64,
            device: torch.device = torch.device('cpu'),
    ):
        self._dtype = dtype
        self._device = device
        self._grid_inp = grid
        self._grid: Optional[BaseGrid] = None
        charge = 0  # we can't have charged solids for now

        # get the AtomCGTOBasis & the hamiltonian
        # atomzs: (natoms,) dtype: torch.int or dtype for floating point
        # atompos: (natoms, ndim)
        atomzs, atompos = parse_moldesc(soldesc, dtype, device)
        allbases = _parse_basis(atomzs, basis)  # list of list of CGTOBasis
        atombases = [
            AtomCGTOBasis(atomz=atz, bases=bas, pos=atpos)
            for (atz, bas, atpos) in zip(atomzs, allbases, atompos)
        ]
        self._atombases = atombases
        self._atompos = atompos  # (natoms, ndim)
        self._atomzs = atomzs  # (natoms,) int-type
        nelecs_tot: torch.Tensor = torch.sum(atomzs)

        # get the number of electrons and spin and orbital weights
        nelecs, spin, frac_mode = _get_nelecs_spin(nelecs_tot, spin, charge)
        assert not frac_mode, "Fractional Z mode for pbc is not supported"
        _orb_weights, _orb_weights_u, _orb_weights_d = _get_orb_weights(
            nelecs, spin, frac_mode, dtype, device)

        # initialize cache
        self._cache = Cache()

        # save the system's properties
        self._spin = spin
        self._charge = charge
        self._numel = nelecs
        self._orb_weights = _orb_weights
        self._orb_weights_u = _orb_weights_u
        self._orb_weights_d = _orb_weights_d
        self._lattice = Lattice(alattice)
        self._lattsum_opt = PBCIntOption.get_default(lattsum_opt)
Beispiel #5
0
 def _create_fake_nucl_bases(self, alpha: float,
                             chargemult: int) -> List[AtomCGTOBasis]:
     # create a list of basis (of s-type) at every nuclei positions
     res: List[AtomCGTOBasis] = []
     alphas = torch.tensor([alpha], dtype=self.dtype, device=self.device)
     # normalizing so the integral of the cgto is 1
     # 0.5 / np.sqrt(np.pi) * 2 / scipy.special.gamma(1.5) * alphas ** 1.5
     norm_coeff = 0.6366197723675814 * alphas**1.5
     for atb in self._atombases:
         # put the charge in the coefficients
         coeffs = atb.atomz * norm_coeff
         basis = CGTOBasis(angmom=0,
                           alphas=alphas,
                           coeffs=coeffs,
                           normalized=True)
         res.append(AtomCGTOBasis(atomz=0, bases=[basis], pos=atb.pos))
     return res
Beispiel #6
0
    def densityfit(self,
                   method: Optional[str] = None,
                   auxbasis: Optional[BasisInpType] = None) -> BaseSystem:
        """
        Indicate that the system's Hamiltonian uses density fit for its integral.

        Arguments
        ---------
        method: Optional[str]
            Density fitting method. Available methods in this class are:

            * "coulomb": Minimizing the Coulomb inner product, i.e. min <p-p_fit|r_12|p-p_fit>
              Ref: Eichkorn, et al. Chem. Phys. Lett. 240 (1995) 283-290.
              (default)
            * "overlap": Minimizing the overlap inner product, i.e. min <p-p_fit|p-p_fit>

        auxbasis: Optional[BasisInpType]
            Auxiliary basis for the density fit. If not specified, then it uses
            "cc-pvtz-jkfit".
        """
        if method is None:
            method = "coulomb"
        if auxbasis is None:
            # TODO: choose the auxbasis properly
            auxbasis = "cc-pvtz-jkfit"

        # get the auxiliary basis
        assert auxbasis is not None
        auxbasis_lst = _parse_basis(self._atomzs_int, auxbasis)
        atomauxbases = [
            AtomCGTOBasis(atomz=atz, bases=bas, pos=atpos)
            for (atz, bas,
                 atpos) in zip(self._atomzs, auxbasis_lst, self._atompos)
        ]

        # change the hamiltonian to have density fit
        df = DensityFitInfo(method=method, auxbases=atomauxbases)
        self._hamilton = HamiltonCGTO(self._atombases,
                                      df=df,
                                      efield=self._efield,
                                      cache=self._cache.add_prefix("hamilton"))
        return self
Beispiel #7
0
    def densityfit(self,
                   method: Optional[str] = None,
                   auxbasis: Optional[BasisInpType] = None) -> BaseSystem:
        """
        Indicate that the system's Hamiltonian uses density fit for its integral.

        Arguments
        ---------
        method: Optional[str]
            Density fitting method. Available methods in this class are:

            * ``"gdf"``: Density fit with gdf compensating charge to perform
                the lattice sum. Ref https://doi.org/10.1063/1.4998644 (default)

        auxbasis: Optional[BasisInpType]
            Auxiliary basis for the density fit. If not specified, then it uses
            ``"cc-pvtz-jkfit"``.
        """
        if method is None:
            method = "gdf"
        if auxbasis is None:
            # TODO: choose the auxbasis properly
            auxbasis = "cc-pvtz-jkfit"

        # get the auxiliary basis
        assert auxbasis is not None
        auxbasis_lst = _parse_basis(self._atomzs, auxbasis)
        atomauxbases = [
            AtomCGTOBasis(atomz=atz, bases=bas, pos=atpos)
            for (atz, bas,
                 atpos) in zip(self._atomzs, auxbasis_lst, self._atompos)
        ]

        # change the hamiltonian to have density fit
        df = DensityFitInfo(method=method, auxbases=atomauxbases)
        self._hamilton = HamiltonCGTO_PBC(
            self._atombases,
            df=df,
            latt=self._lattice,
            lattsum_opt=self._lattsum_opt,
            cache=self._cache.add_prefix("hamilton"))
        return self
Beispiel #8
0
    def get_uncontracted_wrapper(self) -> Tuple[LibcintWrapper, torch.Tensor]:
        # returns the uncontracted LibcintWrapper as well as the mapping from
        # uncontracted atomic orbital (relative index) to the relative index
        # of the atomic orbital
        new_atombases = []
        for atombasis in self.atombases:
            atomz = atombasis.atomz
            pos = atombasis.pos
            new_bases = []
            for shell in atombasis.bases:
                angmom = shell.angmom
                alphas = shell.alphas
                coeffs = shell.coeffs
                normalized = shell.normalized
                new_bases.extend([
                    CGTOBasis(angmom,
                              alpha[None],
                              coeff[None],
                              normalized=normalized)
                    for (alpha, coeff) in zip(alphas, coeffs)
                ])
            new_atombases.append(
                AtomCGTOBasis(atomz=atomz, bases=new_bases, pos=pos))
        uncontr_wrapper = LibcintWrapper(new_atombases,
                                         spherical=self.spherical)

        # get the mapping uncontracted ao to the contracted ao
        uao2ao: List[int] = []
        idx_ao = 0
        # iterate over shells
        for i in range(len(self)):
            nao = self._nao_at_shell(i)
            uao2ao += list(range(idx_ao,
                                 idx_ao + nao)) * self.ngauss_at_shell[i]
            idx_ao += nao
        uao2ao_res = torch.tensor(uao2ao, dtype=torch.long, device=self.device)
        return uncontr_wrapper, uao2ao_res
Beispiel #9
0
    def __init__(self,
                 moldesc: Union[str, Tuple[AtomZsType, AtomPosType]],
                 basis: BasisInpType,
                 *,
                 orthogonalize_basis: bool = True,
                 ao_parameterizer: str = "qr",

                 grid: Union[int, str] = "sg3",
                 spin: Optional[ZType] = None,
                 charge: ZType = 0,
                 orb_weights: Optional[SpinParam[torch.Tensor]] = None,
                 efield: Union[torch.Tensor, Tuple[torch.Tensor, ...], None] = None,
                 vext: Optional[torch.Tensor] = None,
                 dtype: torch.dtype = torch.float64,
                 device: torch.device = torch.device('cpu'),
                 ):
        self._dtype = dtype
        self._device = device
        self._grid_inp = grid
        self._basis_inp = basis
        self._grid: Optional[BaseGrid] = None
        self._vext = vext

        # make efield a tuple
        self._efield = _normalize_efield(efield)
        self._preproc_efield = _preprocess_efield(self._efield)

        # initialize cache
        self._cache = Cache()

        # get the AtomCGTOBasis & the hamiltonian
        # atomzs: (natoms,) dtype: torch.int or dtype for floating point
        # atompos: (natoms, ndim)
        atomzs, atompos = parse_moldesc(
            moldesc, dtype=dtype, device=device)
        atomzs_int = torch.round(atomzs).to(torch.int) if atomzs.is_floating_point() else atomzs
        allbases = _parse_basis(atomzs_int, basis)  # list of list of CGTOBasis
        atombases = [AtomCGTOBasis(atomz=atz, bases=bas, pos=atpos)
                     for (atz, bas, atpos) in zip(atomzs, allbases, atompos)]
        self._atombases = atombases
        self._hamilton = HamiltonCGTO(atombases, efield=self._preproc_efield,
                                      vext=self._vext,
                                      cache=self._cache.add_prefix("hamilton"),
                                      orthozer=orthogonalize_basis,
                                      aoparamzer=ao_parameterizer)
        self._orthogonalize_basis = orthogonalize_basis
        self._aoparamzer = ao_parameterizer
        self._atompos = atompos  # (natoms, ndim)
        self._atomzs = atomzs  # (natoms,) int-type or dtype if floating point
        self._atomzs_int = atomzs_int  # (natoms,) int-type rounded from atomzs
        nelecs_tot: torch.Tensor = torch.sum(atomzs)

        # orb_weights is not specified, so determine it from spin and charge
        if orb_weights is None:
            # get the number of electrons and spin
            nelecs, spin, frac_mode = _get_nelecs_spin(nelecs_tot, spin, charge)
            _orb_weights, _orb_weights_u, _orb_weights_d = _get_orb_weights(
                nelecs, spin, frac_mode, dtype, device)

            # save the system's properties
            self._spin = spin
            self._charge = charge
            self._numel = nelecs
            self._orb_weights = _orb_weights
            self._orb_weights_u = _orb_weights_u
            self._orb_weights_d = _orb_weights_d

        # orb_weights is specified, so calculate the spin and charge from it
        else:
            if not isinstance(orb_weights, SpinParam):
                raise TypeError("Specifying orb_weights must be in SpinParam type")
            assert orb_weights.u.ndim == 1
            assert orb_weights.d.ndim == 1
            assert len(orb_weights.u) == len(orb_weights.d)

            # check if it is decreasing
            orb_u_dec = torch.all(orb_weights.u[:-1] - orb_weights.u[1:] > -1e-4)
            orb_d_dec = torch.all(orb_weights.d[:-1] - orb_weights.d[1:] > -1e-4)
            if not (orb_u_dec and orb_d_dec):
                # if not decreasing, the variational might give the wrong results
                warnings.warn("The orbitals should be ordered in a non-increasing manner. "
                              "Otherwise, some calculations might be wrong.")

            utot = orb_weights.u.sum()
            dtot = orb_weights.d.sum()
            self._numel = utot + dtot
            self._spin = utot - dtot
            self._charge = nelecs_tot - self._numel

            self._orb_weights_u = orb_weights.u
            self._orb_weights_d = orb_weights.d
            self._orb_weights = orb_weights.u + orb_weights.d
Beispiel #10
0
def gto_ft_evaluator(wrapper: LibcintWrapper,
                     Gvgrid: torch.Tensor) -> torch.Tensor:
    # evaluate Fourier Transform of the basis which is defined as
    # FT(f(r)) = integral(f(r) * exp(-ik.r) dr)

    # NOTE: this function do not propagate gradient and should only be used
    # in this file only
    # this is mainly from PySCF
    # https://github.com/pyscf/pyscf/blob/c9aa2be600d75a97410c3203abf35046af8ca615/pyscf/gto/ft_ao.py#L107

    assert Gvgrid.ndim == 2
    assert Gvgrid.shape[-1] == NDIM

    # Gvgrid: (ngrid, ndim)
    # returns: (nao, ngrid)
    dtype = wrapper.dtype
    device = wrapper.device

    fill = CGTO.GTO_ft_fill_s1
    if wrapper.spherical:
        intor = CGTO.GTO_ft_ovlp_sph
    else:
        intor = CGTO.GTO_ft_ovlp_cart
    fn = CGTO.GTO_ft_fill_drv

    eval_gz = CGTO.GTO_Gv_general
    p_gxyzT = c_null_ptr()
    p_gs = (ctypes.c_int * 3)(0, 0, 0)
    p_b = (ctypes.c_double * 1)(0)

    # add another dummy basis to provide the multiplier
    c = np.sqrt(4 * np.pi)  # s-type normalization
    ghost_basis = CGTOBasis(
        angmom=0,
        alphas=torch.tensor([0.], dtype=dtype, device=device),
        coeffs=torch.tensor([c], dtype=dtype, device=device),
        normalized=True,
    )
    ghost_atom_basis = AtomCGTOBasis(atomz=0,
                                     bases=[ghost_basis],
                                     pos=torch.tensor([0.0, 0.0, 0.0],
                                                      dtype=dtype,
                                                      device=device))
    ghost_wrapper = LibcintWrapper([ghost_atom_basis],
                                   spherical=wrapper.spherical,
                                   lattice=wrapper.lattice)
    wrapper, ghost_wrapper = LibcintWrapper.concatenate(wrapper, ghost_wrapper)
    shls_slice = (*wrapper.shell_idxs, *ghost_wrapper.shell_idxs)
    ao_loc = wrapper.full_shell_to_aoloc
    atm, bas, env = wrapper.atm_bas_env

    # prepare the Gvgrid
    GvT = np.asarray(Gvgrid.detach().numpy().T, order="C")
    nGv = Gvgrid.shape[0]

    # prepare the output matrix
    outshape = (wrapper.nao(), nGv)
    out = np.zeros(outshape, dtype=np.complex128, order="C")

    fn(intor, eval_gz, fill, np2ctypes(out), int2ctypes(1),
       (ctypes.c_int * len(shls_slice))(*shls_slice), np2ctypes(ao_loc),
       ctypes.c_double(0), np2ctypes(GvT), p_b, p_gxyzT, p_gs, int2ctypes(nGv),
       np2ctypes(atm), int2ctypes(len(atm)), np2ctypes(bas),
       int2ctypes(len(bas)), np2ctypes(env))

    return torch.as_tensor(out, dtype=get_complex_dtype(dtype), device=device)
Beispiel #11
0
    def __init__(
            self,
            moldesc: Union[str, Tuple[AtomZsType, AtomPosType]],
            basis: BasisInpType,
            *,
            grid: Union[int, str] = "sg3",
            spin: Optional[ZType] = None,
            charge: ZType = 0,
            orb_weights: Optional[SpinParam[torch.Tensor]] = None,
            efield: Optional[torch.Tensor] = None,
            dtype: torch.dtype = torch.float64,
            device: torch.device = torch.device('cpu'),
    ):
        self._dtype = dtype
        self._device = device
        self._grid_inp = grid
        self._basis_inp = basis
        self._grid: Optional[BaseGrid] = None
        self._efield = efield

        # initialize cache
        self._cache = Cache()

        # get the AtomCGTOBasis & the hamiltonian
        # atomzs: (natoms,) dtype: torch.int or dtype for floating point
        # atompos: (natoms, ndim)
        atomzs, atompos = _parse_moldesc(moldesc, dtype, device)
        atomzs_int = torch.round(atomzs).to(
            torch.int) if atomzs.is_floating_point() else atomzs
        allbases = _parse_basis(atomzs_int, basis)  # list of list of CGTOBasis
        atombases = [
            AtomCGTOBasis(atomz=atz, bases=bas, pos=atpos)
            for (atz, bas, atpos) in zip(atomzs, allbases, atompos)
        ]
        self._atombases = atombases
        self._hamilton = HamiltonCGTO(atombases,
                                      efield=efield,
                                      cache=self._cache.add_prefix("hamilton"))
        self._atompos = atompos  # (natoms, ndim)
        self._atomzs = atomzs  # (natoms,) int-type or dtype if floating point
        self._atomzs_int = atomzs_int  # (natoms,) int-type rounded from atomzs
        nelecs_tot: torch.Tensor = torch.sum(atomzs)

        # orb_weights is not specified, so determine it from spin and charge
        if orb_weights is None:
            # get the number of electrons and spin
            nelecs, spin, frac_mode = _get_nelecs_spin(nelecs_tot, spin,
                                                       charge)
            _orb_weights, _orb_weights_u, _orb_weights_d = _get_orb_weights(
                nelecs, spin, frac_mode, dtype, device)

            # save the system's properties
            self._spin = spin
            self._charge = charge
            self._numel = nelecs
            self._orb_weights = _orb_weights
            self._orb_weights_u = _orb_weights_u
            self._orb_weights_d = _orb_weights_d

        # orb_weights is specified, so calculate the spin and charge from it
        else:
            assert isinstance(orb_weights, SpinParam)
            assert orb_weights.u.ndim == 1
            assert orb_weights.d.ndim == 1
            assert len(orb_weights.u) == len(orb_weights.d)

            utot = orb_weights.u.sum()
            dtot = orb_weights.d.sum()
            self._numel = utot + dtot
            self._spin = utot - dtot
            self._charge = nelecs_tot - self._numel

            self._orb_weights_u = orb_weights.u
            self._orb_weights_d = orb_weights.d
            self._orb_weights = orb_weights.u + orb_weights.d