Source code for pyscf.adc.radc

# Copyright 2014-2022 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: Abdelrahman Ahmed <>
#         Samragni Banerjee <samragnibanerjee4@gmail.com>
#         James Serna <jamcar456@gmail.com>
#         Terrence Stahl <>
#         Alexander Sokolov <alexander.y.sokolov@gmail.com>
#

'''
Restricted algebraic diagrammatic construction
'''
import numpy as np
import pyscf.ao2mo as ao2mo
from pyscf import lib
from pyscf.lib import logger
from pyscf.adc import radc_ao2mo
from pyscf.adc import dfadc
from pyscf.adc import radc_amplitudes
from pyscf import __config__
from pyscf import df
from pyscf import symm


# Excited-state kernel
[docs] def kernel(adc, nroots=1, guess=None, eris=None, verbose=None): adc.method = adc.method.lower() if adc.method not in ("adc(2)", "adc(2)-x", "adc(3)"): raise NotImplementedError(adc.method) cput0 = (logger.process_clock(), logger.perf_counter()) log = logger.Logger(adc.stdout, adc.verbose) if adc.verbose >= logger.WARN: adc.check_sanity() adc.dump_flags() if eris is None: eris = adc.transform_integrals() imds = adc.get_imds(eris) matvec, diag = adc.gen_matvec(imds, eris) guess = adc.get_init_guess(nroots, diag, ascending = True) conv, adc.E, U = lib.linalg_helper.davidson_nosym1( lambda xs : [matvec(x) for x in xs], guess, diag, nroots=nroots, verbose=log, tol=adc.conv_tol, max_cycle=adc.max_cycle, max_space=adc.max_space, tol_residual=adc.tol_residual) adc.U = np.array(U).T.copy() if adc.compute_properties: adc.P,adc.X = adc.get_properties(nroots) nfalse = np.shape(conv)[0] - np.sum(conv) header = ("\n*************************************************************" "\n ADC calculation summary" "\n*************************************************************") logger.info(adc, header) for n in range(nroots): print_string = ('%s root %d | Energy (Eh) = %14.10f | Energy (eV) = %12.8f ' % (adc.method, n, adc.E[n], adc.E[n]*27.2114)) if adc.compute_properties: print_string += ("| Spec. factor = %10.8f " % adc.P[n]) print_string += ("| conv = %s" % conv[n]) logger.info(adc, print_string) if nfalse >= 1: logger.warn(adc, "Davidson iterations for " + str(nfalse) + " root(s) did not converge!!!") log.timer('ADC', *cput0) return adc.E, adc.U, adc.P, adc.X
[docs] def make_ref_rdm1(adc): if adc.method not in ("adc(2)", "adc(2)-x", "adc(3)"): raise NotImplementedError(adc.method) t1 = adc.t1 t2 = adc.t2 t2_ce = t1[0][:] t1_ccee = t2[0][:] ###################### einsum_type = True nocc = adc._nocc nvir = adc._nvir nmo = nocc + nvir OPDM = np.zeros((nmo,nmo)) ####### ADC(2) SPIN ADAPTED REF OPDM with SQA ################ ### OCC-OCC ### OPDM[:nocc, :nocc] += lib.einsum('IJ->IJ', np.identity(nocc), optimize = einsum_type).copy() OPDM[:nocc, :nocc] -= 2 * lib.einsum('Iiab,Jiab->IJ', t1_ccee, t1_ccee, optimize = einsum_type) OPDM[:nocc, :nocc] += lib.einsum('Iiab,Jiba->IJ', t1_ccee, t1_ccee, optimize = einsum_type) ### OCC-VIR ### OPDM[:nocc, nocc:] += lib.einsum('IA->IA', t2_ce, optimize = einsum_type).copy() ### VIR-OCC ### OPDM[nocc:, :nocc] += lib.einsum('IA->AI', t2_ce, optimize = einsum_type).copy() ### VIR-VIR ### OPDM[nocc:, nocc:] += 2 * lib.einsum('ijAa,ijBa->AB', t1_ccee, t1_ccee, optimize = einsum_type) OPDM[nocc:, nocc:] -= lib.einsum('ijAa,jiBa->AB', t1_ccee, t1_ccee, optimize = einsum_type) ####### ADC(3) SPIN ADAPTED REF OPDM WITH SQA ################ if adc.method == "adc(3)": t3_ce = adc.t1[1][:] t2_ccee = t2[1][:] #### OCC-OCC ### OPDM[:nocc, :nocc] -= 2 * lib.einsum('Iiab,Jiab->IJ', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[:nocc, :nocc] += lib.einsum('Iiab,Jiba->IJ', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[:nocc, :nocc] -= 2 * lib.einsum('Jiab,Iiab->IJ', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[:nocc, :nocc] += lib.einsum('Jiab,Iiba->IJ', t1_ccee, t2_ccee, optimize = einsum_type) ##### OCC-VIR ### #### OPDM[:nocc, nocc:] += lib.einsum('IA->IA', t3_ce, optimize = einsum_type).copy() OPDM[:nocc, nocc:] += lib.einsum('IiAa,ia->IA', t1_ccee, t2_ce, optimize = einsum_type) OPDM[:nocc, nocc:] -= 1/2 * \ lib.einsum('iIAa,ia->IA', t1_ccee, t2_ce, optimize = einsum_type) ###### VIR-OCC ### OPDM[nocc:, :nocc] += lib.einsum('IA->AI', t3_ce, optimize = einsum_type).copy() OPDM[nocc:, :nocc] += lib.einsum('IiAa,ia->AI', t1_ccee, t2_ce, optimize = einsum_type) OPDM[nocc:, :nocc] -= 1/2 * \ lib.einsum('iIAa,ia->AI', t1_ccee, t2_ce, optimize = einsum_type) ##### VIR=VIR ### OPDM[nocc:, nocc:] += 2 * lib.einsum('ijAa,ijBa->AB', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[nocc:, nocc:] -= lib.einsum('ijAa,jiBa->AB', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[nocc:, nocc:] += 2 * lib.einsum('ijBa,ijAa->AB', t1_ccee, t2_ccee, optimize = einsum_type) OPDM[nocc:, nocc:] -= lib.einsum('ijBa,jiAa->AB', t1_ccee, t2_ccee, optimize = einsum_type) return 2 * OPDM
[docs] class RADC(lib.StreamObject): '''Ground state calculations Attributes: verbose : int Print level. Default value equals to :class:`Mole.verbose` max_memory : float or int Allowed memory in MB. Default value equals to :class:`Mole.max_memory` incore_complete : bool Avoid all I/O. Default is False. method : string nth-order ADC method. Options are : ADC(2), ADC(2)-X, ADC(3). Default is ADC(2). >>> mol = gto.M(atom = 'H 0 0 0; F 0 0 1.1', basis = 'ccpvdz') >>> mf = scf.RHF(mol).run() >>> myadc = adc.RADC(mf).run() Saved results e_corr : float MPn correlation correction e_tot : float Total energy (HF + correlation) t1, t2 : T amplitudes t1[i,a], t2[i,j,a,b] (i,j in occ, a,b in virt) ''' incore_complete = getattr(__config__, 'adc_radc_RADC_incore_complete', False) async_io = getattr(__config__, 'adc_radc_RADC_async_io', True) blkmin = getattr(__config__, 'adc_radc_RADC_blkmin', 4) memorymin = getattr(__config__, 'adc_radc_RADC_memorymin', 2000) _keys = { 'tol_residual','conv_tol', 'e_corr', 'method', 'method_type', 'mo_coeff', 'mol', 'mo_energy', 'incore_complete', 'scf_energy', 'e_tot', 't1', 't2', 'frozen', 'chkfile', 'max_space', 'mo_occ', 'max_cycle', 'imds', 'with_df', 'compute_properties', 'approx_trans_moments', 'evec_print_tol', 'spec_factor_print_tol', 'E', 'U', 'P', 'X', 'ncvs', 'dip_mom', 'dip_mom_nuc' } def __init__(self, mf, frozen=0, mo_coeff=None, mo_occ=None): from pyscf import gto if 'dft' in str(mf.__module__): raise NotImplementedError('DFT reference for UADC') if mo_coeff is None: mo_coeff = mf.mo_coeff if mo_occ is None: mo_occ = mf.mo_occ self.mol = mf.mol self._scf = mf self.verbose = self.mol.verbose self.stdout = self.mol.stdout self.max_memory = mf.max_memory self.max_space = getattr(__config__, 'adc_radc_RADC_max_space', 12) self.max_cycle = getattr(__config__, 'adc_radc_RADC_max_cycle', 50) self.conv_tol = getattr(__config__, 'adc_radc_RADC_conv_tol', 1e-12) self.tol_residual = getattr(__config__, 'adc_radc_RADC_tol_residual', 1e-6) self.scf_energy = mf.e_tot self.frozen = frozen self.incore_complete = self.incore_complete or self.mol.incore_anyway self.mo_coeff = mo_coeff self.mo_occ = mo_occ self.e_corr = None self.t1 = None self.t2 = None self.imds = lambda:None self._nocc = mf.mol.nelectron//2 self._nmo = mo_coeff.shape[1] self._nvir = self._nmo - self._nocc self.mo_energy = mf.mo_energy self.chkfile = mf.chkfile self.method = "adc(2)" self.method_type = "ip" self.with_df = None self.compute_properties = True self.approx_trans_moments = False self.evec_print_tol = 0.1 self.spec_factor_print_tol = 0.1 self.ncvs = None self.E = None self.U = None self.P = None self.X = None dip_ints = -self.mol.intor('int1e_r',comp=3) dip_mom = np.zeros((dip_ints.shape[0], self._nmo, self._nmo)) for i in range(dip_ints.shape[0]): dip = dip_ints[i,:,:] dip_mom[i,:,:] = np.dot(mo_coeff.T, np.dot(dip, mo_coeff)) self.dip_mom = dip_mom charges = self.mol.atom_charges() coords = self.mol.atom_coords() self.dip_mom_nuc = lib.einsum('i,ix->x', charges, coords) compute_amplitudes = radc_amplitudes.compute_amplitudes compute_energy = radc_amplitudes.compute_energy transform_integrals = radc_ao2mo.transform_integrals_incore make_ref_rdm1 = make_ref_rdm1
[docs] def dump_flags(self, verbose=None): logger.info(self, '') logger.info(self, '******** %s ********', self.__class__) logger.info(self, 'max_space = %d', self.max_space) logger.info(self, 'max_cycle = %d', self.max_cycle) logger.info(self, 'conv_tol = %s', self.conv_tol) logger.info(self, 'max_memory %d MB (current use %d MB)', self.max_memory, lib.current_memory()[0]) return self
[docs] def dump_flags_gs(self, verbose=None): logger.info(self, '') logger.info(self, '******** %s ********', self.__class__) logger.info(self, 'max_memory %d MB (current use %d MB)', self.max_memory, lib.current_memory()[0]) return self
[docs] def kernel_gs(self): assert(self.mo_coeff is not None) assert(self.mo_occ is not None) self.method = self.method.lower() if self.method not in ("adc(2)", "adc(2)-x", "adc(3)"): raise NotImplementedError(self.method) if self.verbose >= logger.WARN: self.check_sanity() self.dump_flags_gs() nmo = self._nmo nao = self.mo_coeff.shape[0] nmo_pair = nmo * (nmo+1) // 2 nao_pair = nao * (nao+1) // 2 mem_incore = (max(nao_pair**2, nmo**4) + nmo_pair**2) * 8/1e6 mem_now = lib.current_memory()[0] if getattr(self, 'with_df', None) or getattr(self._scf, 'with_df', None): if getattr(self, 'with_df', None): self.with_df = self.with_df else: self.with_df = self._scf.with_df def df_transform(): return radc_ao2mo.transform_integrals_df(self) self.transform_integrals = df_transform elif (self._scf._eri is None or (mem_incore+mem_now >= self.max_memory and not self.incore_complete)): def outcore_transform(): return radc_ao2mo.transform_integrals_outcore(self) self.transform_integrals = outcore_transform eris = self.transform_integrals() self.e_corr, self.t1, self.t2 = radc_amplitudes.compute_amplitudes_energy( self, eris=eris, verbose=self.verbose) self._finalize() return self.e_corr, self.t1, self.t2
[docs] def kernel(self, nroots=1, guess=None, eris=None): assert(self.mo_coeff is not None) assert(self.mo_occ is not None) self.method = self.method.lower() if self.method not in ("adc(2)", "adc(2)-x", "adc(3)"): raise NotImplementedError(self.method) if self.verbose >= logger.WARN: self.check_sanity() self.dump_flags_gs() nmo = self._nmo nao = self.mo_coeff.shape[0] nmo_pair = nmo * (nmo+1) // 2 nao_pair = nao * (nao+1) // 2 mem_incore = (max(nao_pair**2, nmo**4) + nmo_pair**2) * 8/1e6 mem_now = lib.current_memory()[0] if getattr(self, 'with_df', None) or getattr(self._scf, 'with_df', None): if getattr(self, 'with_df', None): self.with_df = self.with_df else: self.with_df = self._scf.with_df def df_transform(): return radc_ao2mo.transform_integrals_df(self) self.transform_integrals = df_transform elif (self._scf._eri is None or (mem_incore+mem_now >= self.max_memory and not self.incore_complete)): def outcore_transform(): return radc_ao2mo.transform_integrals_outcore(self) self.transform_integrals = outcore_transform eris = self.transform_integrals() self.e_corr, self.t1, self.t2 = radc_amplitudes.compute_amplitudes_energy( self, eris=eris, verbose=self.verbose) self._finalize() self.method_type = self.method_type.lower() if (self.method_type == "ea"): e_exc, v_exc, spec_fac, x, adc_es = self.ea_adc(nroots=nroots, guess=guess, eris=eris) elif(self.method_type == "ip"): if not isinstance(self.ncvs, type(None)) and self.ncvs > 0: e_exc, v_exc, spec_fac, x, adc_es = self.ip_cvs_adc( nroots=nroots, guess=guess, eris=eris) else: e_exc, v_exc, spec_fac, x, adc_es = self.ip_adc( nroots=nroots, guess=guess, eris=eris) else: raise NotImplementedError(self.method_type) self._adc_es = adc_es return e_exc, v_exc, spec_fac, x
def _finalize(self): '''Hook for dumping results and clearing up the object.''' logger.note(self, 'MP%s correlation energy of reference state (a.u.) = %.8f', self.method[4], self.e_corr) return self
[docs] def ea_adc(self, nroots=1, guess=None, eris=None): from pyscf.adc import radc_ea adc_es = radc_ea.RADCEA(self) e_exc, v_exc, spec_fac, x = adc_es.kernel(nroots, guess, eris) return e_exc, v_exc, spec_fac, x, adc_es
[docs] def ip_adc(self, nroots=1, guess=None, eris=None): from pyscf.adc import radc_ip adc_es = radc_ip.RADCIP(self) e_exc, v_exc, spec_fac, x = adc_es.kernel(nroots, guess, eris) return e_exc, v_exc, spec_fac, x, adc_es
[docs] def ip_cvs_adc(self, nroots=1, guess=None, eris=None): from pyscf.adc import radc_ip_cvs adc_es = radc_ip_cvs.RADCIPCVS(self) e_exc, v_exc, spec_fac, x = adc_es.kernel(nroots, guess, eris) return e_exc, v_exc, spec_fac, x, adc_es
[docs] def density_fit(self, auxbasis=None, with_df=None): if with_df is None: self.with_df = df.DF(self._scf.mol) self.with_df.max_memory = self.max_memory self.with_df.stdout = self.stdout self.with_df.verbose = self.verbose if auxbasis is None: self.with_df.auxbasis = self._scf.with_df.auxbasis else: self.with_df.auxbasis = auxbasis else: self.with_df = with_df return self
[docs] def analyze(self): self._adc_es.analyze()
[docs] def compute_dyson_mo(self): return self._adc_es.compute_dyson_mo()
[docs] def make_rdm1(self): return self._adc_es.make_rdm1()
if __name__ == '__main__': from pyscf import scf from pyscf import gto from pyscf import adc r = 1.098 mol = gto.Mole() mol.atom = [ ['N', (0., 0. , -r/2 )], ['N', (0., 0. , r/2)],] mol.basis = {'N':'aug-cc-pvdz'} mol.verbose = 0 mol.build() mf = scf.RHF(mol) mf.conv_tol = 1e-12 mf.kernel() myadc = adc.ADC(mf) ecorr, t_amp1, t_amp2 = myadc.kernel_gs() print(ecorr - -0.32201692360512324) myadcip = adc.radc_ip.RADCIP(myadc) e,v,p = kernel(myadcip,nroots=3) print("ADC(2) IP energies") print (e[0] - 0.5434389910483670) print (e[1] - 0.6240296243595950) print (e[2] - 0.6240296243595956) print("ADC(2) IP spectroscopic factors") print (p[0] - 1.7688097076459075) print (p[1] - 1.8192921131700284) print (p[2] - 1.8192921131700293) myadcea = adc.radc_ea.RADCEA(myadc) e,v,p = kernel(myadcea,nroots=3) print("ADC(2) EA energies") print (e[0] - 0.0961781923822576) print (e[1] - 0.1258326916409743) print (e[2] - 0.1380779405750178) print("ADC(2) EA spectroscopic factors") print (p[0] - 1.9832854445007961) print (p[1] - 1.9634368668786559) print (p[2] - 1.9783719593912672) myadc = adc.ADC(mf) myadc.method = "adc(3)" ecorr, t_amp1, t_amp2 = myadc.kernel_gs() print(ecorr - -0.31694173142858517) myadcip = adc.radc_ip.RADCIP(myadc) e,v,p = kernel(myadcip,nroots=3) print("ADC(3) IP energies") print (e[0] - 0.5667526829981027) print (e[1] - 0.6099995170092525) print (e[2] - 0.6099995170092529) print("ADC(3) IP spectroscopic factors") print (p[0] - 1.8173191958988848) print (p[1] - 1.8429224413853840) print (p[2] - 1.8429224413853851) myadcea = adc.radc_ea.RADCEA(myadc) e,v,p = kernel(myadcea,nroots=3) print("ADC(3) EA energies") print (e[0] - 0.0936790850738445) print (e[1] - 0.0983654552141278) print (e[2] - 0.1295709313652367) print("ADC(3) EA spectroscopic factors") print (p[0] - 1.8324175318668088) print (p[1] - 1.9840991060607487) print (p[2] - 1.9638550014980212) myadc.method = "adc(2)-x" e,v,p = myadc.kernel(nroots=4) print("ADC(2)-x IP energies") print (e[0] - 0.5405255360673724) print (e[1] - 0.6208026698756577) print (e[2] - 0.6208026698756582) print (e[3] - 0.6465332771967947) myadc.method_type = "ea" e,v,p = myadc.kernel(nroots=4) print("ADC(2)-x EA energies") print (e[0] - 0.0953065329985665) print (e[1] - 0.1238833070823509) print (e[2] - 0.1365693811939308) print (e[3] - 0.1365693811939316)