#!/usr/bin/env python
# Copyright 2014-2018,2021 The PySCF Developers. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Qiming Sun <osirpt.sun@gmail.com>
#
'''
Analytical Fourier transformation AO-pair product for PBC
'''
import ctypes
import numpy as np
from pyscf import lib
from pyscf import gto
from pyscf.lib import logger
from pyscf.pbc import gto as pbcgto
from pyscf.gto.ft_ao import ft_ao as mol_ft_ao
from pyscf.pbc.tools import k2gamma
from pyscf.pbc.tools import pbc as pbctools
from pyscf.pbc.lib.kpts_helper import is_zero, gamma_point
from pyscf import __config__
RCUT_THRESHOLD = getattr(__config__, 'pbc_scf_rsjk_rcut_threshold', 1.0)
# kecut=10 can roughly converge GTO with alpha=0.5
KECUT_THRESHOLD = getattr(__config__, 'pbc_scf_rsjk_kecut_threshold', 10.0)
STEEP_BASIS = 0
LOCAL_BASIS = 1
SMOOTH_BASIS = 2
libpbc = lib.load_library('libpbc')
#
# \int mu*nu*exp(-ik*r) dr
#
[docs]
def ft_aopair(cell, Gv, shls_slice=None, aosym='s1',
b=None, gxyz=None, Gvbase=None, kpti_kptj=np.zeros((2,3)),
q=None, intor='GTO_ft_ovlp', comp=1, verbose=None):
r'''
Fourier transform AO pair for a pair of k-points
\sum_T exp(-i k_j * T) \int exp(-i(G+q)r) i(r) j(r-T) dr^3
'''
kpti, kptj = kpti_kptj
if q is None:
q = kptj - kpti
val = ft_aopair_kpts(cell, Gv, shls_slice, aosym, b, gxyz, Gvbase,
q, kptj.reshape(1,3), intor, comp)
return val[0]
[docs]
def ft_aopair_kpts(cell, Gv, shls_slice=None, aosym='s1',
b=None, gxyz=None, Gvbase=None, q=np.zeros(3),
kptjs=np.zeros((1,3)), intor='GTO_ft_ovlp', comp=1,
bvk_kmesh=None, out=None):
r'''
Fourier transform AO pair for a group of k-points
\sum_T exp(-i k_j * T) \int exp(-i(G+q)r) i(r) j(r-T) dr^3
The return array holds the AO pair
corresponding to the kpoints given by kptjs
'''
log = logger.new_logger(cell)
kptjs = np.asarray(kptjs, order='C').reshape(-1,3)
rs_cell = _RangeSeparatedCell.from_cell(cell, KECUT_THRESHOLD,
RCUT_THRESHOLD, log)
if bvk_kmesh is None:
bvk_kmesh = k2gamma.kpts_to_kmesh(cell, kptjs)
log.debug2('Set bvk_kmesh = %s', bvk_kmesh)
rcut = estimate_rcut(rs_cell)
supmol = ExtendedMole.from_cell(rs_cell, bvk_kmesh, rcut.max(), log)
supmol = supmol.strip_basis(rcut)
ft_kern = supmol.gen_ft_kernel(aosym, intor=intor, comp=comp,
return_complex=True, verbose=log)
return ft_kern(Gv, gxyz, Gvbase, q, kptjs, shls_slice)
[docs]
@lib.with_doc(mol_ft_ao.__doc__)
def ft_ao(mol, Gv, shls_slice=None, b=None,
gxyz=None, Gvbase=None, kpt=np.zeros(3), verbose=None):
if gamma_point(kpt):
return mol_ft_ao(mol, Gv, shls_slice, b, gxyz, Gvbase, verbose)
else:
kG = Gv + kpt
return mol_ft_ao(mol, kG, shls_slice, None, None, None, verbose)
[docs]
def gen_ft_kernel(supmol, aosym='s1', intor='GTO_ft_ovlp', comp=1,
return_complex=False, kpts=None, verbose=None):
r'''
Generate the analytical fourier transform kernel for AO products
\sum_T exp(-i k_j * T) \int exp(-i(G+q)r) i(r) j(r-T) dr^3
'''
log = logger.new_logger(supmol)
cput0 = logger.process_clock(), logger.perf_counter()
rs_cell = supmol.rs_cell
assert isinstance(rs_cell, _RangeSeparatedCell)
# The number of basis in the original cell
nbasp = rs_cell.ref_cell.nbas
cell0_ao_loc = rs_cell.ref_cell.ao_loc
bvk_ncells, rs_nbas, nimgs = supmol.bas_mask.shape
ovlp_mask = supmol.get_ovlp_mask()
bvk_ovlp_mask = lib.condense('np.any', ovlp_mask, rs_cell.sh_loc, supmol.sh_loc)
cell0_ovlp_mask = bvk_ovlp_mask.reshape(nbasp, bvk_ncells, nbasp).any(axis=1)
ovlp_mask = ovlp_mask.astype(np.int8)
cell0_ovlp_mask = cell0_ovlp_mask.astype(np.int8)
if kpts is not None:
expLk = np.exp(1j*np.dot(supmol.bvkmesh_Ls, kpts.T))
expLkR = np.asarray(expLk.real, order='C')
expLkI = np.asarray(expLk.imag, order='C')
_expLk = (expLkR, expLkI)
else:
_expLk = None
b = rs_cell.reciprocal_vectors()
if abs(b-np.diag(b.diagonal())).sum() < 1e-8:
_eval_gz = 'GTO_Gv_orth'
else:
_eval_gz = 'GTO_Gv_nonorth'
drv = libpbc.PBC_ft_bvk_drv
cintor = getattr(libpbc, rs_cell._add_suffix(intor))
log.timer_debug1('ft_ao kernel initialization', *cput0)
# TODO: use Gv = b * gxyz + q in c code
# TODO: add zfill
def ft_kernel(Gv, gxyz=None, Gvbase=None, q=np.zeros(3), kptjs=None,
shls_slice=None, aosym=aosym, out=None):
'''
Analytical FT for orbital products. The output tensor has the shape [nGv, nao, nao]
'''
cput0 = logger.process_clock(), logger.perf_counter()
assert q.ndim == 1
if kptjs is None:
if _expLk is None:
expLkR = np.ones((nimgs,1))
expLkI = np.zeros((nimgs,1))
else:
expLkR, expLkI = _expLk
else:
kptjs = np.asarray(kptjs, order='C').reshape(-1,3)
expLk = np.exp(1j*np.dot(supmol.bvkmesh_Ls, kptjs.T))
expLkR = np.asarray(expLk.real, order='C')
expLkI = np.asarray(expLk.imag, order='C')
expLk = None
nkpts = expLkR.shape[1]
GvT = np.asarray(Gv.T + q[:,None], order='C')
nGv = GvT.shape[1]
if shls_slice is None:
shls_slice = (0, nbasp, 0, nbasp)
ni = cell0_ao_loc[shls_slice[1]] - cell0_ao_loc[shls_slice[0]]
nj = cell0_ao_loc[shls_slice[3]] - cell0_ao_loc[shls_slice[2]]
shape = (nkpts, comp, ni, nj, nGv)
aosym = aosym[:2]
if aosym == 's1hermi':
# Gamma point only
assert is_zero(q) and is_zero(kptjs) and ni == nj
# Theoretically, hermitian symmetry can be also found for kpti == kptj != 0:
# f_ji(G) = \int f_ji exp(-iGr) = \int f_ij^* exp(-iGr) = [f_ij(-G)]^*
# hermi operation needs to reorder axis-0. It is inefficient.
elif aosym == 's2':
i0 = cell0_ao_loc[shls_slice[0]]
i1 = cell0_ao_loc[shls_slice[1]]
nij = i1*(i1+1)//2 - i0*(i0+1)//2
shape = (nkpts, comp, nij, nGv)
if gxyz is None or Gvbase is None or (abs(q).sum() > 1e-9):
p_gxyzT = lib.c_null_ptr()
p_mesh = (ctypes.c_int*3)(0,0,0)
p_b = (ctypes.c_double*1)(0)
eval_gz = 'GTO_Gv_general'
else:
eval_gz = _eval_gz
gxyzT = np.asarray(gxyz.T, order='C', dtype=np.int32)
p_gxyzT = gxyzT.ctypes.data_as(ctypes.c_void_p)
bqGv = np.hstack((b.ravel(), q) + Gvbase)
p_b = bqGv.ctypes.data_as(ctypes.c_void_p)
p_mesh = (ctypes.c_int*3)(*[len(x) for x in Gvbase])
eval_gz = getattr(libpbc, eval_gz)
if nkpts == 1:
fill = getattr(libpbc, 'PBC_ft_bvk_nk1'+aosym)
else:
fill = getattr(libpbc, 'PBC_ft_bvk_k'+aosym)
if return_complex:
fsort = getattr(libpbc, 'PBC_ft_zsort_' + aosym)
out = np.ndarray(shape, dtype=np.complex128, buffer=out)
else:
fsort = getattr(libpbc, 'PBC_ft_dsort_' + aosym)
out = np.ndarray((2,) + shape, buffer=out)
if nGv > 0:
drv(cintor, eval_gz, fill, fsort,
out.ctypes.data_as(ctypes.c_void_p),
expLkR.ctypes.data_as(ctypes.c_void_p),
expLkI.ctypes.data_as(ctypes.c_void_p),
ctypes.c_int(bvk_ncells), ctypes.c_int(nimgs),
ctypes.c_int(nkpts), ctypes.c_int(nbasp), ctypes.c_int(comp),
supmol.seg_loc.ctypes.data_as(ctypes.c_void_p),
supmol.seg2sh.ctypes.data_as(ctypes.c_void_p),
cell0_ao_loc.ctypes.data_as(ctypes.c_void_p),
(ctypes.c_int*4)(*shls_slice),
ovlp_mask.ctypes.data_as(ctypes.c_void_p),
cell0_ovlp_mask.ctypes.data_as(ctypes.c_void_p),
GvT.ctypes.data_as(ctypes.c_void_p), p_b, p_gxyzT, p_mesh, ctypes.c_int(nGv),
supmol._atm.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(supmol.natm),
supmol._bas.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(supmol.nbas),
supmol._env.ctypes.data_as(ctypes.c_void_p))
log.timer_debug1(f'ft_ao intor {intor}', *cput0)
if return_complex:
if aosym == 's1hermi':
for i in range(1, ni):
out[:,:,:i,i] = out[:,:,i,:i]
out = np.rollaxis(out, -1, 2)
if comp == 1:
out = out[:,0]
return out
else:
if aosym == 's1hermi':
for i in range(1, ni):
out[:,:,:,:i,i] = out[:,:,:,i,:i]
out = np.rollaxis(out, -1, 3)
if comp == 1:
out = out[:,:,0]
return out
return ft_kernel
class _RangeSeparatedCell(pbcgto.Cell):
'''Cell with partially de-contracted basis'''
def __init__(self):
# ref_cell is the original cell of which the basis to be de-contracted
self.ref_cell = None
# For each de-contracted basis, the shell Id in the original cell
self.bas_map = None
# Type of each de-contracted basis
self.bas_type = None
# Each shell in the original cell can have several segments in the rs-cell.
# sh_loc indicates the shell Id in rs-cell for each shell in cell.
self.sh_loc = None
@classmethod
def from_cell(cls, cell, ke_cut_threshold=None, rcut_threshold=None,
in_rsjk=False, verbose=None):
from pyscf.pbc.df import aft
rs_cell = cls()
rs_cell.__dict__.update(cell.__dict__)
rs_cell.ref_cell = cell
if ke_cut_threshold is None:
rs_cell.bas_map = np.arange(cell.nbas, dtype=np.int32)
rs_cell.bas_type = np.empty(cell.nbas, dtype=np.int32)
rs_cell.bas_type[:] = LOCAL_BASIS
rs_cell.sh_loc = np.arange(cell.nbas + 1, dtype=np.int32)
return rs_cell
log = logger.new_logger(cell, verbose)
if not isinstance(ke_cut_threshold, float):
ke_cut_threshold = np.min(ke_cut_threshold)
precision = cell.precision
# preserves all environments defined in cell (e.g. omega, gauge origin)
_env = cell._env.copy()
decontracted_bas = []
bas_type = []
# For each basis of rs_cell, bas_map gives the basis in cell
bas_map = []
# For each basis of cell, bas_loc gives the first basis in rs_cell
bas_loc = [0]
def _append_to_decontracted_bas(orig_id, e_offset, nprim, btype):
new_bas = cell._bas[orig_id].copy()
new_bas[gto.PTR_EXP] += e_offset
new_bas[gto.PTR_COEFF] += e_offset * new_bas[gto.NCTR_OF]
new_bas[gto.NPRIM_OF] = nprim
decontracted_bas.append(new_bas)
bas_type.append(btype)
bas_map.append(orig_id)
for ib, orig_bas in enumerate(cell._bas):
nprim = orig_bas[gto.NPRIM_OF]
nctr = orig_bas[gto.NCTR_OF]
l = orig_bas[gto.ANG_OF]
es = cell.bas_exp(ib)
# Sort exponents because integral screening of rsjk method relies on
# the dscending order in exponents
es_idx = es.argsort()[::-1]
es = es[es_idx]
cs = cell._libcint_ctr_coeff(ib)[es_idx]
abs_cs = abs(cs).max(axis=1)
# aft._estimate_ke_cutoff is accurate for 4c2e integrals
# For other integrals such as nuclear attraction.
# aft._estimate_ke_cutoff may put some primitive GTOs of large es
# and small cs in the group SMOOTH_BASIS. These GTOs requires a
# large ke_cutoff (mesh) in _RSNucBuilder or _CCNucBuilder.
if in_rsjk:
ke = aft._estimate_ke_cutoff(es, l, abs_cs, precision)
else:
ke = pbcgto.cell._estimate_ke_cutoff(es, l, abs_cs, precision)
smooth_mask = ke < ke_cut_threshold
if rcut_threshold is None:
local_mask = ~smooth_mask
steep_mask = np.zeros_like(local_mask)
rcut = None
else:
norm_ang = ((2*l+1)/(4*np.pi))**.5
fac = 2*np.pi*abs_cs/cell.vol * norm_ang/es / precision
rcut = cell.rcut
rcut = (np.log(fac * rcut**(l+1) + 1.) / es)**.5
rcut = (np.log(fac * rcut**(l+1) + 1.) / es)**.5
steep_mask = (~smooth_mask) & (rcut < rcut_threshold)
local_mask = (~steep_mask) & (~smooth_mask)
pexp = orig_bas[gto.PTR_EXP]
pcoeff = orig_bas[gto.PTR_COEFF]
c_steep = cs[steep_mask]
c_local = cs[local_mask]
c_smooth = cs[smooth_mask]
_env[pcoeff:pcoeff+nprim*nctr] = np.hstack([
c_steep.T.ravel(),
c_local.T.ravel(),
c_smooth.T.ravel(),
])
_env[pexp:pexp+nprim] = np.hstack([
es[steep_mask],
es[local_mask],
es[smooth_mask],
])
if log.verbose >= logger.DEBUG2:
log.debug2('bas %d rcut %s kecut %s', ib, rcut, ke)
log.debug2('steep %s, %s', np.where(steep_mask)[0], es[steep_mask])
log.debug2('local %s, %s', np.where(local_mask)[0], es[local_mask])
log.debug2('smooth %s, %s', np.where(smooth_mask)[0], es[smooth_mask])
nprim_steep = c_steep.shape[0]
nprim_local = c_local.shape[0]
nprim_smooth = c_smooth.shape[0]
if nprim_steep > 0:
_append_to_decontracted_bas(ib, 0, nprim_steep, STEEP_BASIS)
if nprim_local > 0:
_append_to_decontracted_bas(ib, nprim_steep, nprim_local, LOCAL_BASIS)
if nprim_smooth > 0:
_append_to_decontracted_bas(ib, nprim_steep+nprim_local,
nprim_smooth, SMOOTH_BASIS)
bas_loc.append(len(decontracted_bas))
rs_cell._bas = np.asarray(decontracted_bas, dtype=np.int32, order='C')
# rs_cell._bas might be of size (0, BAS_SLOTS)
rs_cell._bas = rs_cell._bas.reshape(-1, gto.BAS_SLOTS)
rs_cell._env = _env
rs_cell.bas_map = np.asarray(bas_map, dtype=np.int32)
rs_cell.bas_type = np.asarray(bas_type, dtype=np.int32)
rs_cell.sh_loc = np.asarray(bas_loc, dtype=np.int32)
rs_cell.ke_cutoff = ke_cut_threshold
if log.verbose >= logger.DEBUG:
bas_type = rs_cell.bas_type
log.debug('rs_cell.nbas %d nao %d', rs_cell.nbas, rs_cell.nao)
log.debug1('No. steep_bas in rs_cell %d', np.count_nonzero(bas_type == STEEP_BASIS))
log.debug1('No. local_bas in rs_cell %d', np.count_nonzero(bas_type == LOCAL_BASIS))
log.debug('No. smooth_bas in rs_cell %d', np.count_nonzero(bas_type == SMOOTH_BASIS))
map_bas = rs_cell._reverse_bas_map(rs_cell.bas_map)
log.debug2('bas_map from cell to rs_cell %s', map_bas)
assert np.array_equiv(map_bas, bas_loc)
log.debug2('%s.bas_type %s', cls, rs_cell.bas_type)
log.debug2('%s.sh_loc %s', cls, rs_cell.sh_loc)
return rs_cell
@staticmethod
def _reverse_bas_map(bas_map):
'''Map basis between the original cell and the derived rs-cell.
For each shell in the original cell, the first basis Id of the
de-contracted basis in the rs-cell'''
uniq_bas, map_bas = np.unique(bas_map, return_index=True)
assert uniq_bas[-1] == len(uniq_bas) - 1
return np.append(map_bas, len(bas_map)).astype(np.int32)
def smooth_basis_cell(self):
'''Construct a cell with only the smooth part of the AO basis'''
cell_d = self.view(pbcgto.Cell)
mask = self.bas_type == SMOOTH_BASIS
cell_d._bas = self._bas[mask]
segs = np.zeros(self.ref_cell.nbas)
segs[self.bas_map[mask]] = 1
cell_d.sh_loc = np.append(0, np.cumsum(segs)).astype(np.int32)
logger.debug1(self, 'cell_d.nbas %d', cell_d.nbas)
if cell_d.nbas == 0:
return cell_d
cell_d.ke_cutoff = ke_cutoff = pbcgto.estimate_ke_cutoff(cell_d)
cell_d.mesh = cell_d.cutoff_to_mesh(ke_cutoff)
logger.debug1(self, 'cell_d rcut %g ke_cutoff %g, mesh %s',
cell_d.rcut, ke_cutoff, cell_d.mesh)
return cell_d
def compact_basis_cell(self):
'''Construct a cell with only the smooth part of the AO basis'''
cell_c = self.copy(deep=False)
mask = self.bas_type != SMOOTH_BASIS
cell_c._bas = self._bas[mask]
cell_c.bas_map = cell_c.bas_map[mask]
cell_c.bas_type = cell_c.bas_type[mask]
segs = self.sh_loc[1:] - self.sh_loc[:-1]
segs[self.bas_map[~mask]] -= 1
cell_c.sh_loc = np.append(0, np.cumsum(segs)).astype(np.int32)
cell_c.rcut = pbcgto.estimate_rcut(cell_c, self.precision)
return cell_c
def merge_diffused_block(self, aosym='s1'):
'''For AO pair that are evaluated in blocks with using the basis
partitioning self.compact_basis_cell() and self.smooth_basis_cell(),
merge the DD block into the CC, CD, DC blocks (C ~ compact basis,
D ~ diffused basis)
'''
ao_loc = self.ref_cell.ao_loc
smooth_bas_idx = self.bas_map[self.bas_type == SMOOTH_BASIS]
smooth_ao_idx = self.get_ao_indices(smooth_bas_idx, ao_loc)
nao = ao_loc[-1]
naod = smooth_ao_idx.size
drv = getattr(libpbc, f'PBCnr3c_fuse_dd_{aosym}')
def merge(j3c, j3c_dd, shls_slice=None):
if j3c_dd.size == 0:
return j3c
# The AO index in the original cell
if shls_slice is None:
slice_in_cell = (0, nao, 0, nao)
else:
slice_in_cell = ao_loc[list(shls_slice[:4])]
# Then search the corresponding index in the diffused block
slice_in_cell_d = np.searchsorted(smooth_ao_idx, slice_in_cell)
# j3c_dd may be an h5 object. Load j3c_dd to memory
d0, d1 = slice_in_cell_d[:2]
j3c_dd = np.asarray(j3c_dd[d0:d1], order='C')
naux = j3c_dd.shape[-1]
drv(j3c.ctypes.data_as(ctypes.c_void_p),
j3c_dd.ctypes.data_as(ctypes.c_void_p),
smooth_ao_idx.ctypes.data_as(ctypes.c_void_p),
(ctypes.c_int*4)(*slice_in_cell),
(ctypes.c_int*4)(*slice_in_cell_d),
ctypes.c_int(nao), ctypes.c_int(naod), ctypes.c_int(naux))
return j3c
return merge
def recontract(self, dim=1):
'''Recontract the vector evaluated with the RS-cell to the vector
associated to the basis of reference cell
'''
ao_loc = self.ref_cell.ao_loc
ao_map = self.get_ao_indices(self.bas_map, ao_loc)
nao = ao_loc[-1]
if dim == 1:
def recontractor(a):
assert a.ndim == 2
a = np.asarray(a, order='C')
ngrids = a.shape[1]
out = np.zeros((nao, ngrids), dtype=a.dtype)
idx = np.arange(ngrids, dtype=np.int32)
return lib.takebak_2d(out, a, ao_map, idx, thread_safe=False)
elif dim == 2:
def recontractor(a):
assert a.ndim == 2
a = np.asarray(a, order='C')
out = np.zeros((nao, nao), dtype=a.dtype)
return lib.takebak_2d(out, a, ao_map, ao_map, thread_safe=False)
else:
raise NotImplementedError(f'dim = {dim}')
return recontractor
def recontract_1d(self, vec):
'''Recontract the vector evaluated with the RS-cell to the vector
associated to the basis of reference cell
'''
return self.recontract()(vec)
def get_ao_type(self):
'''Assign a label (STEEP_BASIS, LOCAL_BASIS, SMOOTH_BASIS) to each AO function'''
ao_loc = self.ao_loc
nao = ao_loc[-1]
ao_type = np.empty(nao, dtype=int)
def assign(type_code):
ao_idx = self.get_ao_indices(self.bas_type == type_code, ao_loc)
ao_type[ao_idx] = type_code
assign(STEEP_BASIS)
assign(LOCAL_BASIS)
assign(SMOOTH_BASIS)
return ao_type
def decontract_basis(self, to_cart=True, aggregate=False):
pcell, ctr_coeff = self.ref_cell.decontract_basis(to_cart=to_cart,
aggregate=aggregate)
pcell = pcell.view(self.__class__)
pcell.ref_cell = None
# Set bas_type labels for the primitive basis of decontracted cell
smooth_mask = self.bas_type == SMOOTH_BASIS
smooth_exp_thresholds = {}
for ia, (ib0, ib1) in enumerate(self.aoslice_by_atom()[:,:2]):
smooth_bas_ids = ib0 + np.where(smooth_mask[ib0:ib1])[0]
for ib in smooth_bas_ids:
l = self._bas[ib,gto.ANG_OF]
nprim = self._bas[ib,gto.NPRIM_OF]
pexp = self._bas[ib,gto.PTR_EXP]
smooth_exp_thresholds[(ia, l)] = max(
self._env[pexp:pexp+nprim].max(),
smooth_exp_thresholds.get((ia, l), 0))
pcell_ls = pcell._bas[:,gto.ANG_OF]
pcell_exps = pcell._env[pcell._bas[:,gto.PTR_EXP]]
pcell_ao_slices = pcell.aoslice_by_atom()
pcell.bas_type = np.empty(pcell.nbas, dtype=np.int32)
pcell.bas_type[:] = LOCAL_BASIS
for (ia, l), exp_cut in smooth_exp_thresholds.items():
ib0, ib1 = pcell_ao_slices[ia,:2]
smooth_mask = ((pcell_exps[ib0:ib1] <= exp_cut+1e-8) &
(pcell_ls[ib0:ib1] == l))
pcell.bas_type[ib0:ib1][smooth_mask] = SMOOTH_BASIS
pcell.bas_map = np.arange(pcell.nbas, dtype=np.int32)
pcell.sh_loc = np.append(np.arange(pcell.nbas), pcell.nbas).astype(np.int32)
logger.debug3(pcell, 'decontracted cell bas_type %s', pcell.bas_type)
logger.debug3(pcell, 'decontracted cell sh_loc %s', pcell.sh_loc)
return pcell, ctr_coeff
[docs]
class ExtendedMole(gto.Mole):
'''An extended Mole object to mimic periodicity'''
def __init__(self):
# The cell which used to generate the supmole
self.rs_cell: _RangeSeparatedCell = None
self.bvk_kmesh = None
self.Ls = None
self.bvkmesh_Ls = None
# seg_loc maps the shell Id in bvk cell to shell Id in bvk rs-cell.
# seg2sh maps the shell Id in bvk rs-cell to the shell Id in supmol.
# Lattice sum range for each bvk cell shell can be obtained
# (seg2sh[n+1] - seg2sh[n])
self.seg_loc = None
self.seg2sh = None
# whether the basis bas_mask[bvk-cell-id, basis-id, image-id] is
# needed to reproduce the periodicity
self.bas_mask = None
self.precision = None
@property
def sh_loc(self):
# A map for shell in bvk cell to shell Id in supmol
return self.seg2sh[self.seg_loc]
@property
def bas_map(self):
# A map to assign each basis of supmol._bas the index in
# [bvk_cell-id, bas-id, image-id]
return np.where(self.bas_mask.ravel())[0].astype(np.int32)
[docs]
@classmethod
def from_cell(cls, cell, kmesh, rcut=None, verbose=None):
if rcut is None: rcut = cell.rcut
log = logger.new_logger(cell, verbose)
if not isinstance(cell, _RangeSeparatedCell):
cell = _RangeSeparatedCell.from_cell(cell)
bvkcell = pbctools.super_cell(cell, kmesh, wrap_around=True)
Ls = bvkcell.get_lattice_Ls(rcut=rcut)
Ls = Ls[np.linalg.norm(Ls, axis=1).argsort()]
bvkmesh_Ls = k2gamma.translation_vectors_for_kmesh(cell, kmesh, True)
LKs = Ls[:,None,:] + bvkmesh_Ls
nimgs, bvk_ncells = LKs.shape[:2]
log.debug1('Generate supmol with rcut = %g nimgs = %d bvk_ncells = %d',
rcut, nimgs, bvk_ncells)
supmol = cls()
supmol.__dict__.update(cell.__dict__)
supmol = pbctools._build_supcell_(supmol, cell, LKs.reshape(nimgs*bvk_ncells, 3))
supmol.rs_cell = cell
supmol.bvk_kmesh = kmesh
supmol.Ls = Ls
supmol.bvkmesh_Ls = bvkmesh_Ls
bas_mask = np.ones((bvk_ncells, cell.nbas, nimgs), dtype=bool)
supmol.seg_loc, supmol.seg2sh = supmol.bas_mask_to_segment(cell, bas_mask, verbose)
supmol.bas_mask = bas_mask
supmol.precision = cell.precision
supmol._env[gto.PTR_EXPCUTOFF] = -np.log(cell.precision*1e-4)
_bas_reordered = supmol._bas.reshape(
nimgs, bvk_ncells, cell.nbas, gto.BAS_SLOTS).transpose(1,2,0,3)
supmol._bas = np.asarray(_bas_reordered.reshape(-1, gto.BAS_SLOTS),
dtype=np.int32, order='C')
return supmol
[docs]
def strip_basis(self, rcut):
rs_cell = self.rs_cell
dim = rs_cell.dimension
if dim == 0:
return self
# Search the shortest distance to the reference cell for each atom in the supercell.
atom_coords = self.atom_coords()
d = np.linalg.norm(atom_coords[:,None] - rs_cell.atom_coords(), axis=2)
shortest_dist = np.min(d, axis=1)
bas_dist = shortest_dist[self._bas[:,gto.ATOM_OF]]
# filter _bas
nbas0 = self._bas.shape[0]
if bas_dist.size == self.bas_mask.size:
bas_dist = bas_dist.reshape(self.bas_mask.shape)
self.bas_mask = bas_mask = bas_dist < rcut[:,None]
self._bas = self._bas[bas_mask.ravel()]
else:
dr = np.empty(self.bas_mask.shape)
dr[:] = 1e9
dr[self.bas_mask] = bas_dist
bas_mask = dr < rcut[:,None]
self._bas = self._bas[bas_mask[self.bas_mask]]
self.bas_mask = bas_mask
# filter _atm
atm_ids = np.unique(self._bas[:,gto.ATOM_OF])
atm_mapping = np.zeros(self._atm.shape[0], dtype=np.int32)
atm_mapping[atm_ids] = np.arange(atm_ids.size)
self._atm = self._atm[atm_ids]
self._bas[:,gto.ATOM_OF] = atm_mapping[self._bas[:,gto.ATOM_OF]]
nbas1 = self._bas.shape[0]
logger.debug1(self, 'strip_basis %d to %d ', nbas0, nbas1)
self.seg_loc, self.seg2sh = self.bas_mask_to_segment(rs_cell, self.bas_mask)
return self
[docs]
def get_ovlp_mask(self, cutoff=None):
'''integral screening mask for basis product between cell and supmol'''
rs_cell = self.rs_cell
supmol = self
# consider only the most diffused component of a basis
cell_exps, cell_cs = pbcgto.cell._extract_pgto_params(rs_cell, 'min')
cell_l = rs_cell._bas[:,gto.ANG_OF]
cell_bas_coords = rs_cell.atom_coords()[rs_cell._bas[:,gto.ATOM_OF]]
if cutoff is None:
theta_ij = cell_exps.min() / 2
vol = rs_cell.vol
lattice_sum_factor = max(2*np.pi*rs_cell.rcut/(vol*theta_ij), 1)
cutoff = rs_cell.precision/lattice_sum_factor * .1
logger.debug(self, 'Set ft_ao cutoff to %g', cutoff)
supmol_exps, supmol_cs = pbcgto.cell._extract_pgto_params(supmol, 'min')
supmol_bas_coords = supmol.atom_coords()[supmol._bas[:,gto.ATOM_OF]]
supmol_l = supmol._bas[:,gto.ANG_OF]
aij = cell_exps[:,None] + supmol_exps
theta = cell_exps[:,None] * supmol_exps / aij
dr = np.linalg.norm(cell_bas_coords[:,None,:] - supmol_bas_coords, axis=2)
aij1 = 1./aij
aij2 = aij**-.5
dri = supmol_exps*aij1 * dr + aij2
drj = cell_exps[:,None]*aij1 * dr + aij2
norm_i = cell_cs * ((2*cell_l+1)/(4*np.pi))**.5
norm_j = supmol_cs * ((2*supmol_l+1)/(4*np.pi))**.5
fl = 2*np.pi/rs_cell.vol*dr/theta + 1.
ovlp = (np.pi**1.5 * norm_i[:,None]*norm_j * np.exp(-theta*dr**2) *
dri**cell_l[:,None] * drj**supmol_l * aij1**1.5 * fl)
return ovlp > cutoff
[docs]
@staticmethod
def bas_mask_to_segment(rs_cell, bas_mask, verbose=None):
'''
bas_mask shape [bvk_ncells, nbas, nimgs]
'''
log = logger.new_logger(rs_cell, verbose)
bvk_ncells, cell_rs_nbas, nimgs = bas_mask.shape
images_count = np.count_nonzero(bas_mask, axis=2)
# seg_loc maps shell Id in bvk-cell to segment Id in supmol
# seg2sh maps the segment Id to shell Id of supmol
seg_loc = np.arange(bvk_ncells)[:,None] * cell_rs_nbas + rs_cell.sh_loc[:-1]
seg_loc = np.append(seg_loc.ravel(), bvk_ncells * cell_rs_nbas)
seg2sh = np.append(0, np.cumsum(images_count.ravel()))
if log.verbose > logger.DEBUG:
steep_mask = rs_cell.bas_type == STEEP_BASIS
local_mask = rs_cell.bas_type == LOCAL_BASIS
diffused_mask = rs_cell.bas_type == SMOOTH_BASIS
log.debug1('No. steep basis in sup-mol %d', images_count[:,steep_mask].sum())
log.debug1('No. local basis in sup-mol %d', images_count[:,local_mask].sum())
log.debug1('No. diffused basis in sup-mol %d', images_count[:,diffused_mask].sum())
log.debug3('sup-mol seg_loc %s', seg_loc)
log.debug3('sup-mol seg2sh %s', seg2sh)
return seg_loc.astype(np.int32), seg2sh.astype(np.int32)
[docs]
def bas_type_to_indices(self, type_code=SMOOTH_BASIS):
'''Return the basis indices of required bas_type'''
cell0_mask = self.rs_cell.bas_type == type_code
if np.any(cell0_mask):
# (bvk_ncells, rs_cell.nbas, nimgs)
bas_type_mask = np.empty_like(self.bas_mask)
bas_type_mask[:] = cell0_mask[None,:,None]
bas_type_mask = bas_type_mask[self.bas_mask]
return np.where(bas_type_mask)[0]
else:
return np.arange(0)
gen_ft_kernel = gen_ft_kernel
[docs]
def estimate_rcut(cell, precision=None):
'''Estimate rcut for each basis based on Schwarz inequality
Q_ij ~ S_ij * (sqrt(2aij/pi) * aij**(lij*2) * (4*lij-1)!!)**.5
'''
if precision is None:
# The rcut estimated with this function is sufficient to converge
# the integrals to the required precision. Errors around the required
# precision is found when checking hermitian symmetry of the integrals.
# The discrepancy in hermitian symmetry may cause issues in post-HF
# methods which assume the hermitian symmetry in MO integrals.
# Therefore precision is adjusted to ensure hermitian symmetry.
precision = cell.precision * 1e-2
if cell.nbas == 0:
return np.zeros(1)
# consider only the most diffused component of a basis
exps, cs = pbcgto.cell._extract_pgto_params(cell, 'min')
ls = cell._bas[:,gto.ANG_OF]
ai_idx = exps.argmin()
ai = exps[ai_idx]
li = ls[ai_idx]
ci = cs[ai_idx]
aj = exps
lj = ls
cj = cs
aij = ai + aj
lij = li + lj
norm_ang = ((2*li+1)*(2*lj+1))**.5/(4*np.pi)
c1 = ci * cj * norm_ang
theta = ai * aj / aij
aij1 = aij**-.5
fac = np.pi**1.5*c1 * aij1**(lij+3) * (2*aij/np.pi)**.25 * aij**lij
fac /= precision
r0 = cell.rcut
dri = aj*aij1 * r0 + 1.
drj = ai*aij1 * r0 + 1.
fl = 2*np.pi/cell.vol * r0/theta
r0 = (np.log(fac * dri**li * drj**lj * fl + 1.) / theta)**.5
dri = aj*aij1 * r0 + 1.
drj = ai*aij1 * r0 + 1.
fl = 2*np.pi/cell.vol * r0/theta
r0 = (np.log(fac * dri**li * drj**lj * fl + 1.) / theta)**.5
return r0