Source code for pyscf.mp.dfmp2

#!/usr/bin/env python
# Copyright 2014-2020 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.

'''
density fitting MP2,  3-center integrals incore.
'''

import h5py
import ctypes
import numpy as np
import scipy.linalg
from functools import reduce
from pyscf import lib
from pyscf.lib import logger
from pyscf.ao2mo import _ao2mo
from pyscf import df
from pyscf.mp import mp2
from pyscf.mp.mp2 import make_rdm1, make_rdm2, _mo_splitter
from pyscf import __config__

WITH_T2 = getattr(__config__, 'mp_dfmp2_with_t2', True)
THRESH_LINDEP = getattr(__config__, 'mp_dfmp2_thresh_lindep', 1e-10)

libmp = lib.load_library('libmp')


[docs] def kernel(mp, mo_energy=None, mo_coeff=None, eris=None, with_t2=WITH_T2, verbose=None): log = logger.new_logger(mp, verbose) if eris is None: eris = mp.ao2mo() nocc, nvir, naux = eris.nocc, eris.nvir, eris.naux occ_energy, vir_energy = mp.split_mo_energy()[1:3] moevv = np.asarray(vir_energy[:,None] + vir_energy, order='C') mem_avail = mp.max_memory - lib.current_memory()[0] if with_t2: t2 = np.zeros((nocc,nocc,nvir,nvir), dtype=eris.dtype) t2_ptr = t2.ctypes.data_as(ctypes.c_void_p) mem_avail -= t2.size * eris.dsize / 1e6 else: t2 = None t2_ptr = lib.c_null_ptr() if mem_avail < 0: log.error('Insufficient memory for holding t2 incore. Please rerun with `with_t2 = False`.') raise MemoryError emp2_ss = emp2_os = 0 drv = libmp.MP2_contract_d # determine occ blksize if isinstance(eris.ovL, np.ndarray): # incore ovL occ_blksize = nocc else: # outcore ovL # 3*V^2 (for C driver) + 2*[O]XV (for iaL & jaL) = mem occ_blksize = int(np.floor((mem_avail*0.6*1e6/eris.dsize - 3*nvir**2)/(2*naux*nvir))) occ_blksize = min(nocc, max(1, occ_blksize)) log.debug('occ blksize for %s loop: %d/%d', mp.__class__.__name__, occ_blksize, nocc) cput1 = (logger.process_clock(), logger.perf_counter()) emp2_ss = emp2_os = 0 for ibatch,(i0,i1) in enumerate(lib.prange(0,nocc,occ_blksize)): nocci = i1-i0 iaL = eris.get_occ_blk(i0,i1) for jbatch,(j0,j1) in enumerate(lib.prange(0,nocc,occ_blksize)): noccj = j1-j0 if ibatch == jbatch: jbL = iaL else: jbL = eris.get_occ_blk(j0,j1) ed = np.zeros(1, dtype=np.float64) ex = np.zeros(1, dtype=np.float64) moeoo_block = np.asarray( occ_energy[i0:i1,None] + occ_energy[j0:j1], order='C') s2symm = 1 t2_ex = 0 drv( ed.ctypes.data_as(ctypes.c_void_p), ex.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(s2symm), iaL.ctypes.data_as(ctypes.c_void_p), jbL.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(i0), ctypes.c_int(j0), ctypes.c_int(nocci), ctypes.c_int(noccj), ctypes.c_int(nocc), ctypes.c_int(nvir), ctypes.c_int(naux), moeoo_block.ctypes.data_as(ctypes.c_void_p), moevv.ctypes.data_as(ctypes.c_void_p), t2_ptr, ctypes.c_int(t2_ex) ) emp2_ss += ed[0] + ex[0] emp2_os += ed[0] jbL = None iaL = None cput1 = log.timer_debug1('i-block [%d:%d]/%d' % (i0,i1,nocc), *cput1) emp2_ss = emp2_ss.real emp2_os = emp2_os.real emp2 = lib.tag_array(emp2_ss+emp2_os, e_corr_ss=emp2_ss, e_corr_os=emp2_os) return emp2, t2
[docs] class DFMP2(mp2.MP2): _keys = {'with_df', 'mo_energy', 'force_outcore'} def __init__(self, mf, frozen=None, mo_coeff=None, mo_occ=None, mo_energy=None): mp2.MP2.__init__(self, mf, frozen, mo_coeff, mo_occ) self.mo_energy = get_mo_energy(mf, self.mo_coeff, self.mo_occ, mo_energy) if getattr(mf, 'with_df', None): self.with_df = mf.with_df else: self.with_df = df.DF(mf.mol) self.with_df.auxbasis = df.make_auxbasis(mf.mol, mp2fit=True) # DEBUG: self.force_outcore = False
[docs] def kernel(self, mo_energy=None, mo_coeff=None, eris=None, with_t2=WITH_T2): return mp2.MP2.kernel(self, mo_energy, mo_coeff, eris, with_t2)
[docs] def split_mo_coeff(self, mo_coeff=None): if mo_coeff is None: mo_coeff = self.mo_coeff masks = _mo_splitter(self) return [mo_coeff[:,m] for m in masks]
[docs] def split_mo_energy(self, mo_energy=None): if mo_energy is None: mo_energy = self.mo_energy masks = _mo_splitter(self) return [mo_energy[m] for m in masks]
[docs] def split_mo_occ(self, mo_occ=None): if mo_occ is None: mo_occ = self.mo_occ masks = _mo_splitter(self) return [mo_occ[m] for m in masks]
[docs] def reset(self, mol=None): self.with_df.reset(mol) return mp2.MP2.reset(self, mol)
[docs] def ao2mo(self, mo_coeff=None, ovL=None, ovL_to_save=None): return _make_df_eris(self, mo_coeff, ovL, ovL_to_save)
[docs] def make_rdm1(self, t2=None, ao_repr=False, with_frozen=True): if t2 is None: t2 = self.t2 assert t2 is not None return make_rdm1(self, t2, ao_repr=ao_repr, with_frozen=with_frozen)
[docs] def make_rdm2(self, t2=None, ao_repr=False): if t2 is None: t2 = self.t2 assert t2 is not None return make_rdm2(self, t2, ao_repr=ao_repr)
[docs] def nuc_grad_method(self): raise NotImplementedError
# For non-canonical MP2
[docs] def update_amps(self, t2, eris): raise NotImplementedError
[docs] def init_amps(self, mo_energy=None, mo_coeff=None, eris=None, with_t2=WITH_T2): return kernel(self, mo_energy, mo_coeff, eris, with_t2)
MP2 = DFMP2 from pyscf import scf scf.hf.RHF.DFMP2 = lib.class_as_method(DFMP2) scf.rohf.ROHF.DFMP2 = None scf.uhf.UHF.DFMP2 = None del (WITH_T2)
[docs] def get_mo_energy(mf, mo_coeff, mo_occ, mo_energy=None): if mo_energy is not None: return mo_energy if mo_coeff is mf.mo_coeff and mf.converged: return mf.mo_energy # rebuild fock dm = mf.make_rdm1(mo_coeff, mo_occ) vhf = mf.get_veff(dm=dm) fockao = mf.get_fock(vhf=vhf, dm=dm) if np.asarray(dm).ndim == 2: # RHF return np.diag(reduce(lib.dot, (mo_coeff.T.conj(), fockao, mo_coeff))).real else: return [np.diag(reduce(lib.dot, (mo_coeff[i].T.conj(), fockao[i], mo_coeff[i]))).real for i in [0,1]]
def _make_df_eris(mp, mo_coeff=None, ovL=None, ovL_to_save=None, verbose=None): log = logger.new_logger(mp, verbose) with_df = getattr(mp, 'with_df', None) assert( with_df is not None ) if with_df._cderi is None: log.debug('Caching ovL-type integrals directly') if with_df.auxmol is None: with_df.auxmol = df.addons.make_auxmol(with_df.mol, with_df.auxbasis) else: log.debug('Caching ovL-type integrals by transforming saved AO 3c integrals.') if mo_coeff is None: mo_coeff = mp.mo_coeff occ_coeff, vir_coeff = mp.split_mo_coeff()[1:3] # determine incore or outcore nocc = occ_coeff.shape[1] nvir = vir_coeff.shape[1] naux = with_df.auxmol.nao_nr() if ovL is not None: if isinstance(ovL, np.ndarray): outcore = False elif isinstance(ovL, str): outcore = True else: log.error('Unknown data type %s for input `ovL` (should be np.ndarray or str).', type(ovL)) raise TypeError else: mem_now = mp.max_memory - lib.current_memory()[0] mem_df = nocc*nvir*naux*8/1024**2. log.debug('ao2mo est mem= %.2f MB avail mem= %.2f MB', mem_df, mem_now) # DEBUG: if mp.force_outcore: outcore = True else: outcore = (ovL_to_save is not None) or (mem_now*0.8 < mem_df) log.debug('ovL-type integrals are cached %s', 'outcore' if outcore else 'incore') if outcore: eris = _DFOUTCOREERIS(with_df, occ_coeff, vir_coeff, mp.max_memory, ovL=ovL, ovL_to_save=ovL_to_save, verbose=log.verbose, stdout=log.stdout) else: eris = _DFINCOREERIS(with_df, occ_coeff, vir_coeff, mp.max_memory, ovL=ovL, verbose=log.verbose, stdout=log.stdout) eris.build() return eris class _DFINCOREERIS: def __init__(self, with_df, occ_coeff, vir_coeff, max_memory, ovL=None, verbose=None, stdout=None): self.with_df = with_df self.occ_coeff = occ_coeff self.vir_coeff = vir_coeff self.max_memory = max_memory self.verbose = verbose self.stdout = stdout self.dtype = self.occ_coeff.dtype assert( self.dtype == np.float64 ) # FIXME: support complex self.dsize = 8 self.ovL = ovL @property def nocc(self): return self.occ_coeff.shape[1] @property def nvir(self): return self.vir_coeff.shape[1] @property def naux(self): return self.ovL.shape[-1] def build(self): log = logger.new_logger(self) if self.ovL is None: if self.with_df._cderi is None: self.ovL = _init_mp_df_eris_direct(self.with_df, self.occ_coeff, self.vir_coeff, self.max_memory, log=log) else: self.ovL = _init_mp_df_eris(self.with_df, self.occ_coeff, self.vir_coeff, self.max_memory, log=log) def get_occ_blk(self, i0,i1): nvir, naux = self.nvir, self.naux return np.asarray(self.ovL[i0*nvir:i1*nvir], order='C').reshape(i1-i0,nvir,naux) def get_ov_blk(self, ia0,ia1): return np.asarray(self.ovL[ia0:ia1], order='C') class _DFOUTCOREERIS(_DFINCOREERIS): def __init__(self, with_df, occ_coeff, vir_coeff, max_memory, ovL=None, ovL_to_save=None, verbose=None, stdout=None): _DFINCOREERIS.__init__(self, with_df, occ_coeff, vir_coeff, max_memory, None, verbose, stdout) self._ovL = ovL self._ovL_to_save = ovL_to_save def build(self): log = logger.new_logger(self) with_df = self.with_df if self._ovL is None: if isinstance(self._ovL_to_save, str): self.feri = lib.H5FileWrap(self._ovL_to_save, 'w') else: self.feri = lib.H5TmpFile() log.debug('ovL is saved to %s', self.feri.filename) if with_df._cderi is None: _init_mp_df_eris_direct(with_df, self.occ_coeff, self.vir_coeff, self.max_memory, h5obj=self.feri, log=log) else: _init_mp_df_eris(with_df, self.occ_coeff, self.vir_coeff, self.max_memory, h5obj=self.feri, log=log) self.ovL = self.feri['ovL'] elif isinstance(self._ovL, str): self.feri = h5py.File(self._ovL, 'r') log.debug('ovL is read from %s', self.feri.filename) assert( 'ovL' in self.feri ) self.ovL = self.feri['ovL'] else: raise RuntimeError def _init_mp_df_eris(with_df, occ_coeff, vir_coeff, max_memory, h5obj=None, log=None): if log is None: log = logger.new_logger(with_df) nao,nocc = occ_coeff.shape nvir = vir_coeff.shape[1] nmo = nocc + nvir nao_pair = nao**2 naux = with_df.get_naoaux() dtype = occ_coeff.dtype assert( dtype == np.float64 ) dsize = 8 mo = np.asarray(np.hstack((occ_coeff,vir_coeff)), order='F') ijslice = (0, nocc, nocc, nmo) if h5obj is None: # incore ovL = np.empty((nocc*nvir,naux), dtype=dtype) else: ovL_shape = (nocc*nvir,naux) ovL = h5obj.create_dataset('ovL', ovL_shape, dtype=dtype, chunks=(1,*ovL_shape[1:])) mem_avail = max_memory - lib.current_memory()[0] if isinstance(ovL, np.ndarray): # incore: batching aux (OV + Nao_pair) * [X] = M mem_auxblk = (nao_pair+nocc*nvir) * dsize/1e6 aux_blksize = min(naux, max(1, int(np.floor(mem_avail*0.5 / mem_auxblk)))) log.debug('aux blksize for incore ao2mo: %d/%d', aux_blksize, naux) buf = np.empty(aux_blksize*nocc*nvir, dtype=dtype) ijslice = (0,nocc,nocc,nmo) p1 = 0 for Lpq in with_df.loop(blksize=aux_blksize): p0, p1 = p1, p1+Lpq.shape[0] out = _ao2mo.nr_e2(Lpq, mo, ijslice, aosym='s2', out=buf) ovL[:,p0:p1] = out.T Lpq = out = None buf = None else: # outcore: batching occ [O]XV and aux ([O]V + Nao_pair)*[X] mem_occblk = naux*nvir * dsize/1e6 occ_blksize = min(nocc, max(1, int(np.floor(mem_avail*0.6 / mem_occblk)))) mem_auxblk = (occ_blksize*nvir+nao_pair) * dsize/1e6 aux_blksize = min(naux, max(1, int(np.floor(mem_avail*0.3 / mem_auxblk)))) log.debug('occ blksize for outcore ao2mo: %d/%d', occ_blksize, nocc) log.debug('aux blksize for outcore ao2mo: %d/%d', aux_blksize, naux) buf = np.empty(naux*occ_blksize*nvir, dtype=dtype) buf2 = np.empty(aux_blksize*occ_blksize*nvir, dtype=dtype) for i0,i1 in lib.prange(0,nocc,occ_blksize): nocci = i1-i0 ijslice = (i0,i1,nocc,nmo) p1 = 0 OvL = np.ndarray((nocci*nvir,naux), dtype=dtype, buffer=buf) for Lpq in with_df.loop(blksize=aux_blksize): p0, p1 = p1, p1+Lpq.shape[0] out = _ao2mo.nr_e2(Lpq, mo, ijslice, aosym='s2', out=buf2) OvL[:,p0:p1] = out.T Lpq = out = None ovL[i0*nvir:i1*nvir] = OvL # this avoids slow operations like ovL[i0:i1,:,p0:p1] = ... OvL = None buf = buf2 = None return ovL def _init_mp_df_eris_direct(with_df, occ_coeff, vir_coeff, max_memory, h5obj=None, log=None): from pyscf import gto from pyscf.df.incore import fill_2c2e from pyscf.ao2mo.outcore import balance_partition if log is None: log = logger.new_logger(with_df) mol = with_df.mol auxmol = with_df.auxmol nbas = mol.nbas nao,nocc = occ_coeff.shape nvir = vir_coeff.shape[1] nmo = nocc + nvir nao_pair = nao*(nao+1)//2 naoaux = auxmol.nao_nr() dtype = occ_coeff.dtype assert( dtype == np.float64 ) dsize = 8 mo = np.asarray(np.hstack((occ_coeff,vir_coeff)), order='F') ijslice = (0, nocc, nocc, nmo) tspans = np.zeros((5,2)) tnames = ['j2c', 'j3c', 'xform', 'save', 'fit'] tick = (logger.process_clock(), logger.perf_counter()) # precompute for fitting j2c = fill_2c2e(mol, auxmol) try: m2c = scipy.linalg.cholesky(j2c, lower=True) tag = 'cd' except scipy.linalg.LinAlgError: e, u = np.linalg.eigh(j2c) cond = abs(e).max() / abs(e).min() keep = abs(e) > THRESH_LINDEP log.debug('cond(j2c) = %g', cond) log.debug('keep %d/%d cderi vectors', np.count_nonzero(keep), keep.size) e = e[keep] u = u[:,keep] m2c = lib.dot(u*e**-0.5, u.T.conj()) tag = 'eig' j2c = None naux = m2c.shape[1] tock = (logger.process_clock(), logger.perf_counter()) tspans[0] += np.asarray(tock) - np.asarray(tick) mem_avail = max_memory - lib.current_memory()[0] incore = h5obj is None if incore: ovL = np.empty((nocc*nvir,naoaux), dtype=dtype) mem_avail -= ovL.size * dsize / 1e6 else: ovL_shape = (nocc*nvir,naux) ovL = h5obj.create_dataset('ovL', ovL_shape, dtype=dtype, chunks=(1,*ovL_shape[1:])) h5tmp = lib.H5TmpFile() Lov0_shape = (naoaux,nocc*nvir) Lov0 = h5tmp.create_dataset('Lov0', Lov0_shape, dtype=dtype, chunks=(1,*Lov0_shape[1:])) # buffer mem_blk = nao_pair*2 * dsize / 1e6 aux_blksize = max(1, min(naoaux, int(np.floor(mem_avail*0.7 / mem_blk)))) auxshl_range = balance_partition(auxmol.ao_loc, aux_blksize) auxlen = max([x[2] for x in auxshl_range]) log.info('mem_avail = %.2f mem_blk = %.2f auxlen = %d', mem_avail, mem_blk, auxlen) buf0 = np.empty(auxlen*nao_pair, dtype=dtype) buf0T = np.empty(auxlen*nao_pair, dtype=dtype) # precompute for j3c comp = 1 aosym = 's2ij' int3c = gto.moleintor.ascint3(mol._add_suffix('int3c2e')) atm_f, bas_f, env_f = gto.mole.conc_env(mol._atm, mol._bas, mol._env, auxmol._atm, auxmol._bas, auxmol._env) ao_loc_f = gto.moleintor.make_loc(bas_f, int3c) cintopt = gto.moleintor.make_cintopt(atm_f, bas_f, env_f, int3c) def calc_j3c_ao(kshl0, kshl1): shls_slice = (0, nbas, 0, nbas, nbas+kshl0, nbas+kshl1) pqL = gto.moleintor.getints3c(int3c, atm_f, bas_f, env_f, shls_slice, comp, aosym, ao_loc_f, cintopt, out=buf0) Lpq = lib.transpose(pqL, out=buf0T) pqL = None return Lpq # transform k1 = 0 for auxshl_rg in auxshl_range: kshl0, kshl1, dk = auxshl_rg k0, k1 = k1, k1+dk log.debug('kshl = [%d:%d/%d] [%d:%d/%d]', kshl0, kshl1, auxmol.nbas, k0, k1, naoaux) tick = (logger.process_clock(), logger.perf_counter()) lpq = calc_j3c_ao(kshl0, kshl1) tock = (logger.process_clock(), logger.perf_counter()) tspans[1] += np.asarray(tock) - np.asarray(tick) lov = _ao2mo.nr_e2(lpq, mo, ijslice, aosym='s2', out=buf0) tick = (logger.process_clock(), logger.perf_counter()) tspans[2] += np.asarray(tick) - np.asarray(tock) if incore: ovl = lib.transpose(lov, out=buf0T) ovL[:,k0:k1] = ovl ovl = None else: Lov0[k0:k1] = lov lpq = lov = None tock = (logger.process_clock(), logger.perf_counter()) tspans[3] += np.asarray(tock) - np.asarray(tick) buf0 = buf0T = None tick = (logger.process_clock(), logger.perf_counter()) # fit if tag == 'cd': drv = getattr(libmp, 'trisolve_parallel_grp', None) if incore: if tag == 'cd': if drv is None: ovL = scipy.linalg.solve_triangular(m2c, ovL.T, lower=True, overwrite_b=True, check_finite=False).T else: assert m2c.flags.f_contiguous grpfac = 10 drv( m2c.ctypes.data_as(ctypes.c_void_p), ovL.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(naux), ctypes.c_int(nocc*nvir), ctypes.c_int(grpfac) ) else: nvxao = nvir*naoaux nvx = nvir*naux mem_blk = nvx * dsize/1e6 occ_blksize = max(1, min(nocc, int(np.floor(mem_avail*0.5/mem_blk)))) buf = np.empty(occ_blksize*nvx, dtype=dtype) ovL = ovL.reshape(-1) for i0,i1 in lib.prange(0,nocc,occ_blksize): nocci = i1-i0 out = np.ndarray((nocci*nvir,naux), dtype=dtype, buffer=buf) lib.dot(ovL[i0*nvxao:i1*nvxao].reshape(nocci*nvir,naoaux), m2c, c=out) ovL[i0*nvx:i1*nvx] = out.reshape(-1) ovL = ovL[:nocc*nvx].reshape(nocc*nvir,naux) buf = None else: nvxao = nvir*naoaux nvx = nvir*naux mem_blk = nvxao * dsize / 1e6 occ_blksize = max(1, min(nocc, int(np.floor(mem_avail*0.4/mem_blk)))) for i0,i1 in lib.prange(0, nocc, occ_blksize): nocci = i1-i0 ivL = np.asarray(Lov0[:,i0*nvir:i1*nvir].T, order='C') if tag == 'cd': if drv is None: ivL = scipy.linalg.solve_triangular(m2c, ivL.T, lower=True, overwrite_b=True, check_finite=False).T else: assert m2c.flags.f_contiguous grpfac = 10 drv( m2c.ctypes.data_as(ctypes.c_void_p), ivL.ctypes.data_as(ctypes.c_void_p), ctypes.c_int(naux), ctypes.c_int(nocci*nvir), ctypes.c_int(grpfac) ) else: ivL = lib.dot(ivL.reshape(nocci*nvir,naoaux), m2c) ovL[i0*nvir:i1*nvir] = ivL del h5tmp['Lov0'] h5tmp.close() Lov0 = None tock = (logger.process_clock(), logger.perf_counter()) tspans[4] += np.asarray(tock) - np.asarray(tick) for tspan,tname in zip(tspans,tnames): log.debug('ao2mo CPU time for %-10s %9.2f sec wall time %9.2f sec', tname, *tspan) log.info('') return ovL if __name__ == '__main__': from pyscf import scf from pyscf import gto mol = gto.Mole() mol.verbose = 0 mol.atom = [ [8 , (0. , 0. , 0.)], [1 , (0. , -0.757 , 0.587)], [1 , (0. , 0.757 , 0.587)]] mol.basis = 'cc-pvdz' mol.build() mf = scf.RHF(mol).run() pt = DFMP2(mf) emp2, t2 = pt.kernel() print(emp2 - -0.204004830285) pt.with_df = df.DF(mol) pt.with_df.auxbasis = 'weigend' emp2, t2 = pt.kernel() print(emp2 - -0.204254500453) mf = scf.density_fit(scf.RHF(mol), 'weigend') mf.kernel() pt = DFMP2(mf) emp2, t2 = pt.kernel() print(emp2 - -0.203986171133) pt.with_df = df.DF(mol) pt.with_df.auxbasis = df.make_auxbasis(mol, mp2fit=True) emp2, t2 = pt.kernel() print(emp2 - -0.203738031827) pt.frozen = 2 emp2, t2 = pt.kernel() print(emp2 - -0.14433975122418313)