Skip to content

Commit

Permalink
Merge pull request #69 from BoothGroup/to_cisdtq
Browse files Browse the repository at this point in the history
Added to_cisdtq functionality to RFCI wave function types for extract…
  • Loading branch information
ghb24 authored Apr 15, 2023
2 parents 3d878c2 + 0c962df commit edb1627
Show file tree
Hide file tree
Showing 22 changed files with 2,175 additions and 154 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [master, dev]
pull_request:
branches: [master, dev]
schedule:
- cron: '0 2 * * *'

Expand Down
6 changes: 6 additions & 0 deletions examples/ewf/molecules/04-bath-options.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numpy as np
import pyscf
import pyscf.gto
import pyscf.scf
Expand Down Expand Up @@ -45,6 +46,11 @@
# of BNOs for a given threshold, and results in a smaller bath for a given threshold.
# Other options (not shown) include projecting out all couplings to the DMET bath space, or just projecting
# one index.
# The options below are default
emb_mp2_projdmet = vayesta.ewf.EWF(mf, bath_options=dict(bathtype='mp2', threshold=1e-4,
project_dmet_order=2, project_dmet_mode='squared-entropy'))
emb_mp2_projdmet.kernel()
assert(np.allclose(emb_mp2_projdmet.e_tot, emb_mp2.e_tot))
# To turn off this projection (which was not used in 10.1103/PhysRevX.12.011046),
# use `project_dmet_order = 0`:
bath = dict(bathtype='mp2', threshold=1e-4, project_dmet_order=0)
Expand Down
12 changes: 7 additions & 5 deletions examples/ewf/molecules/11-fragmentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@
mf.kernel()

# Embedded CCSD
emb_iao = vayesta.ewf.EWF(mf, bno_threshold=1e-6)
emb_iao = vayesta.ewf.EWF(mf, bath_options=dict(threshold=1e-6))
# If calling the kernel without initializing the fragmentation,
# IAO fragmentation is used automatically:
# It can be initialized manually by calling emb.iao_fragmentation()
# IAO fragmentation is used automatically.
# It can be initialized manually by calling:
with emb_iao.iao_fragmentation() as f:
f.add_all_atomic_fragments()
emb_iao.kernel()

emb_iaopao = vayesta.ewf.EWF(mf, bno_threshold=1e-6)
emb_iaopao = vayesta.ewf.EWF(mf, bath_options=dict(threshold=1e-6))
# To use IAO+PAOs, call emb.iaopao_fragmentation() before the kernel:
with emb_iaopao.iaopao_fragmentation() as f:
f.add_all_atomic_fragments()
emb_iaopao.kernel()

emb_sao = vayesta.ewf.EWF(mf, bno_threshold=1e-6)
emb_sao = vayesta.ewf.EWF(mf, bath_options=dict(threshold=1e-6))
# To use Lowdin AOs (SAOs), call emb.sao_fragmentation() before the kernel:
with emb_sao.sao_fragmentation() as f:
f.add_all_atomic_fragments()
Expand Down
62 changes: 62 additions & 0 deletions examples/ewf/molecules/25-externally-correct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import numpy as np
import pyscf
import pyscf.gto
import pyscf.scf
import pyscf.cc
import pyscf.fci
import vayesta
import vayesta.ewf
from vayesta.misc import molecules

mol = pyscf.gto.Mole()
mol.atom = """
Se 0.0000 0.0000 0.2807
O 0.0000 1.3464 -0.5965
O 0.0000 -1.3464 -0.5965
"""
mol.basis = 'cc-pVDZ'
mol.output = 'pyscf.out'
mol.build()

# Hartree-Fock
mf = pyscf.scf.RHF(mol)
mf.kernel()

# Reference full system CCSD:
cc = pyscf.cc.CCSD(mf)
cc.kernel()

# 1) Consider setup where you have a complete CCSD, externally corrected by local atomic fragment+DMET FCI clusters
emb = vayesta.ewf.EWF(mf)
with emb.iao_fragmentation() as f:
# Add all atomic FCI fragments with DMET bath
# Store the FCI wave functions as CCSDTQ types, so they can be used for correction later.
# These want to be flagged as 'auxiliary', as they are solved first, and then used as constraints for the
# non-auxiliary fragments.
fci_frags = f.add_all_atomic_fragments(solver='FCI', bath_options=dict(bathtype='dmet'), store_wf_type='CCSDTQ', auxiliary=True)
# Add single 'complete' CCSD fragment covering all IAOs
ccsd_frag = f.add_full_system(solver='CCSD', bath_options=dict(bathtype='full'))
# Setup the external correction from the CCSD fragment.
# Main option is 'projectors', which should be an integer between 0 and 2 (inclusive).
# The larger the number, the more fragment projectors are applied to the correcting T2 contributions, and less
# 'bath' correlation from the FCI clusters is used as a constraint in the external correction of the CCSD clusters.
# For multiple constraining fragments, proj=0 will double-count the correction due to overlapping bath spaces, and
# in this case, only proj=1 will be exact in the limit of enlarging (FCI) bath spaces.
# Note that there is also the option 'low_level_coul' (default True). For the important T3 * V contribution to the
# T2 amplitudes, this determines whether the V is expressed in the FCI or CCSD cluster space. The CCSD cluster is
# larger, and hence this is likely to be better (and is default), as the correction is longer-ranged (though slightly more expensive).
ccsd_frag.add_external_corrections(fci_frags, correction_type='external', projectors=1)
emb.kernel()
print('Total energy from full system CCSD tailored (CCSD Coulomb interaction) by atomic FCI fragments (projectors=1): {}'.format(emb.e_tot))

# 2) Now, we also fragment the CCSD spaces, and use BNOs. These CCSD fragments are individually externally corrected from the FCI clusters.
# Similar set up, but we now have multiple CCSD clusters.
emb = vayesta.ewf.EWF(mf)
with emb.iao_fragmentation() as f:
fci_frags = f.add_all_atomic_fragments(solver='FCI', bath_options=dict(bathtype='dmet'), store_wf_type='CCSDTQ', auxiliary=True)
ccsd_frags = f.add_all_atomic_fragments(solver='CCSD', bath_options=dict(bathtype='mp2', threshold=1.e-5))
# Now add external corrections to all CCSD clusters, and use 'external' correction, with 2 projectors and only contracting with the FCI integrals
for cc_frag in ccsd_frags:
cc_frag.add_external_corrections(fci_frags, correction_type='external', projectors=2, low_level_coul=False)
emb.kernel()
print('Total energy from embedded CCSD tailored (FCI Coulomb interaction) by atomic FCI fragments (projectors=2): {}'.format(emb.e_tot))
111 changes: 111 additions & 0 deletions examples/ewf/other/61-ext-corr-hubbard-1D.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import pyscf
import pyscf.cc
import pyscf.fci
import vayesta
import vayesta.ewf
import vayesta.lattmod

nsite = 10
nelectron = nsite
hubbard_u = 4.0
mol = vayesta.lattmod.Hubbard1D(nsite, nelectron=nelectron, hubbard_u=hubbard_u)
mf = vayesta.lattmod.LatticeMF(mol)
mf.kernel()
assert(mf.converged)

# Reference full system CCSD and FCI
cc = pyscf.cc.CCSD(mf)
cc.kernel()
fci = pyscf.fci.FCI(mf)
fci.threads = 1
fci.conv_tol = 1e-12
fci.davidson_only = True
fci.kernel()

# Perform embedded FCI with two sites
emb_simp = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(bathtype='dmet'))
with emb_simp.site_fragmentation() as f:
f.add_atomic_fragment([0, 1], sym_factor=nsite/2, nelectron_target=2*nelectron/nsite)
emb_simp.kernel()

# Perform full system CCSD, externally corrected by two-site+DMET bath FCI level clusters
emb = vayesta.ewf.EWF(mf)
fci_frags = []
with emb.site_fragmentation() as f:
# Set up a two-site FCI fragmentation of full system as auxiliary clusters
# Ensure the right number of electrons on each fragment space of the FCI calculation.
fci_frags.append(f.add_atomic_fragment([0, 1], solver='FCI', bath_options=dict(bathtype='dmet'), store_wf_type='CCSDTQ', nelectron_target=2*nelectron/nsite, auxiliary=True))
# Add single 'complete' CCSD fragment covering all sites
ccsd_frag = f.add_full_system(solver='CCSD', bath_options=dict(bathtype='full'), solver_options=dict(solve_lambda=False, init_guess='CISD'))
# Add symmetry-derived FCI fragments to avoid multiple calculations
fci_frags.extend(fci_frags[0].add_tsymmetric_fragments(tvecs=[5, 1, 1])

e_extcorr = []
extcorr_conv = []
#Main options: 'projectors', which should be an integer between 0 and 2 (inclusive).
#The larger the number, the more fragment projectors are applied to the correcting T2 contributions, and less
#'bath' correlation from the FCI clusters is used as a constraint in the external correction of the CCSD clusters.
#NOTE that with multiple FCI fragments providing constraints and overlapping bath spaces, proj=0 will
#overcount the correction, so do not use with multiple FCI clusters. It will not e.g. tend to the right answer as
#the FCI bath space becomes complete (for which you must have proj=1). Only use with a single FCI fragment.
ccsd_frag.add_external_corrections(fci_frags, correction_type='external', projectors=1)
emb.kernel()
e_extcorr.append(emb.e_tot); extcorr_conv.append(emb.converged)

# For subsequent calculations where we have just changed the mode/projectors in the external tailoring, we want to avoid having
# to resolve the FCI fragments. Set them to inactive, so just the CCSD fragments will be resolved.
for fci_frag in fci_frags:
fci_frag.active = False

ccsd_frag.clear_external_corrections() # Clear any previous corrections applied
ccsd_frag.add_external_corrections(fci_frags, correction_type='external', projectors=2)
emb.kernel()
e_extcorr.append(emb.e_tot); extcorr_conv.append(emb.converged)

ccsd_frag.clear_external_corrections() # Clear any previous corrections applied
ccsd_frag.add_external_corrections(fci_frags, correction_type='external', projectors=1, low_level_coul=False)
emb.kernel()
e_extcorr.append(emb.e_tot); extcorr_conv.append(emb.converged)

ccsd_frag.clear_external_corrections() # Clear any previous corrections applied
ccsd_frag.add_external_corrections(fci_frags, correction_type='external', projectors=2, low_level_coul=False)
emb.kernel()
e_extcorr.append(emb.e_tot); extcorr_conv.append(emb.converged)

# Compare to a simpler tailoring
e_tailor = []
tailor_conv = []
ccsd_frag.clear_external_corrections()
ccsd_frag.add_external_corrections(fci_frags, correction_type='tailor', projectors=1)
emb.kernel()
e_tailor.append(emb.e_tot); tailor_conv.append(emb.converged)
ccsd_frag.clear_external_corrections()
ccsd_frag.add_external_corrections(fci_frags, correction_type='tailor', projectors=2)
emb.kernel()
e_tailor.append(emb.e_tot); tailor_conv.append(emb.converged)

# Compare to a delta-tailoring, where the correction is the difference between full-system
# CCSD and CCSD in the FCI cluster.
e_dtailor = []
dtailor_conv = []
ccsd_frag.clear_external_corrections()
ccsd_frag.add_external_corrections(fci_frags, correction_type='delta-tailor', projectors=1)
emb.kernel()
e_dtailor.append(emb.e_tot); dtailor_conv.append(emb.converged)
ccsd_frag.clear_external_corrections()
ccsd_frag.add_external_corrections(fci_frags, correction_type='delta-tailor', projectors=2)
emb.kernel()
e_dtailor.append(emb.e_tot); dtailor_conv.append(emb.converged)

print("E(MF)= %+16.8f Ha, conv = %s" % (mf.e_tot/nsite, mf.converged))
print("E(CCSD)= %+16.8f Ha, conv = %s" % (cc.e_tot/nsite, cc.converged))
print("E(FCI)= %+16.8f Ha, conv = %s" % (fci.e_tot/nsite, fci.converged))
print("E(Emb. FCI, 2-site)= %+16.8f Ha, conv = %s" % (emb_simp.e_tot/nsite, emb_simp.converged))
print("E(EC-CCSD, 2-site FCI, 1 proj, ccsd V)= %+16.8f Ha, conv = %s" % ((e_extcorr[0]/nsite), extcorr_conv[0]))
print("E(EC-CCSD, 2-site FCI, 2 proj, ccsd V)= %+16.8f Ha, conv = %s" % ((e_extcorr[1]/nsite), extcorr_conv[1]))
print("E(EC-CCSD, 2-site FCI, 1 proj, fci V)= %+16.8f Ha, conv = %s" % ((e_extcorr[2]/nsite), extcorr_conv[2]))
print("E(EC-CCSD, 2-site FCI, 2 proj, fci V)= %+16.8f Ha, conv = %s" % ((e_extcorr[3]/nsite), extcorr_conv[3]))
print("E(T-CCSD, 2-site FCI, 1 proj)= %+16.8f Ha, conv = %s" % ((e_tailor[0]/nsite), tailor_conv[0]))
print("E(T-CCSD, 2-site FCI, 2 proj)= %+16.8f Ha, conv = %s" % ((e_tailor[1]/nsite), tailor_conv[1]))
print("E(DT-CCSD, 2-site FCI, 1 proj)= %+16.8f Ha, conv = %s" % ((e_dtailor[0]/nsite), dtailor_conv[0]))
print("E(DT-CCSD, 2-site FCI, 2 proj)= %+16.8f Ha, conv = %s" % ((e_dtailor[1]/nsite), dtailor_conv[1]))
8 changes: 5 additions & 3 deletions vayesta/core/qemb/fragment.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,11 @@ def cluster(self, value):
raise RuntimeError("Cannot set attribute cluster in symmetry derived fragment.")
self._cluster = value

def reset(self, reset_bath=True, reset_cluster=True, reset_eris=True):
self.log.debugv("Resetting %s (reset_bath= %r, reset_cluster= %r, reset_eris= %r)",
self, reset_bath, reset_cluster, reset_eris)
def reset(self, reset_bath=True, reset_cluster=True, reset_eris=True, reset_inactive=True):
self.log.debugv("Resetting %s (reset_bath= %r, reset_cluster= %r, reset_eris= %r, reset_inactive= %r)",
self, reset_bath, reset_cluster, reset_eris, reset_inactive)
if not reset_inactive and not self.active:
return
if reset_bath:
self._dmet_bath = None
self._bath_factory_occ = None
Expand Down
2 changes: 1 addition & 1 deletion vayesta/core/qemb/qemb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1572,7 +1572,7 @@ def check_fragment_symmetry(self, dm1, symtol=1e-6):
for child in children:
charge_err, spin_err = parent.get_symmetry_error(child, dm1=dm1)
if (max(charge_err, spin_err) > symtol):
raise RuntimeError("%s and %s not symmetric! Errors: charge= %.2e spin= %.2e"
raise SymmetryError("%s and %s not symmetric! Errors: charge= %.2e spin= %.2e"
% (parent.name, child.name, charge_err, spin_err))
else:
self.log.debugv("Symmetry between %s and %s: Errors: charge= %.2e spin= %.2e",
Expand Down
7 changes: 6 additions & 1 deletion vayesta/core/scmf/scmf.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ def kernel(self, *args, **kwargs):

dm1 = self.mf.make_rdm1()
# Check symmetry
self.emb.check_fragment_symmetry(dm1)
try:
self.emb.check_fragment_symmetry(dm1)
except SymmetryError:
self.log.error("Symmetry check failed in %s", self.name)
self.converged = False
break

# Check convergence
conv, de, ddm = self.check_convergence(e_tot, dm1, e_last, dm1_last)
Expand Down
20 changes: 19 additions & 1 deletion vayesta/core/types/wf/ccsdtq.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,35 @@ def CCSDTQ_WaveFunction(mo, *args, **kwargs):


class RCCSDTQ_WaveFunction(wf_types.WaveFunction):
# TODO: Contract T4's down to intermediates to reduce EC-CC memory overheads.

def __init__(self, mo, t1, t2, t3, t4):
super().__init__(mo)
self.t1 = t1
self.t2 = t2
self.t3 = t3
self.t4 = t4
self._check_amps()

def _check_amps(self):
if not (isinstance(self.t4, tuple) and len(self.t4) == 2):
raise ValueError("t4 definition in RCCSDTQ wfn requires tuple of (abaa, abab) spin signatures")

def as_ccsdtq(self):
return self

def as_ccsd(self):
if self.projector is not None:
raise NotImplementedError
return wf_types.CCSD_WaveFunction(self.mo, self.t1, self.t2)

def as_cisd(self, c0=1.0):
return self.as_ccsd().as_cisd()

class UCCSDTQ_WaveFunction(RCCSDTQ_WaveFunction):
pass
def _check_amps(self):
if not (isinstance(self.t3, tuple) and len(self.t3) == 4):
raise ValueError("t4 definition in UCCSDTQ wfn requires tuple of (aaa, aba, bab, bbb) spin signatures")
if not (isinstance(self.t4, tuple) and len(self.t4) == 5):
raise ValueError(
"t4 definition in UCCSDTQ wfn requires tuple of (aaaa, aaab, abab, abbb, bbbb) spin signatures")
69 changes: 37 additions & 32 deletions vayesta/core/types/wf/cisdtq.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import vayesta
from vayesta.core.util import *
from vayesta.core.types import wf as wf_types
from vayesta.core.types.wf import t_to_c


def CISDTQ_WaveFunction(mo, *args, **kwargs):
Expand All @@ -21,43 +22,47 @@ def __init__(self, mo, c0, c1, c2, c3, c4):
self.c2 = c2
self.c3 = c3
self.c4 = c4
if not (isinstance(c4, tuple) and len(c4) == 2):
raise ValueError("c4 definition in RCISDTQ wfn requires tuple of (abaa, abab) spin signatures")

def as_ccsdtq(self):
t1 = self.c1/self.c0
t2 = self.c2/self.c0 - einsum('ia,jb->ijab', t1, t1)
raise NotImplementedError
# TODO:
# see also THE JOURNAL OF CHEMICAL PHYSICS 147, 154105 (2017)
t3 = self.c3/self.c0 # - C1*C2 - (C1^3)/3
t4 = self.c4/self.c0 # - C1*C3 - (C2^2)/2 - C1^2*C2 - (C1^4)/4
c1 = self.c1 / self.c0
c2 = self.c2 / self.c0
c3 = self.c3 / self.c0
c4 = tuple(c / self.c0 for c in self.c4)

t1 = t_to_c.t1_rhf(c1)
t2 = t_to_c.t2_rhf(t1, c2)
t3 = t_to_c.t3_rhf(t1, t2, c3)
t4 = t_to_c.t4_rhf(t1, t2, t3, c4)

return wf_types.RCCSDTQ_WaveFunction(self.mo, t1=t1, t2=t2, t3=t3, t4=t4)


class UCISDTQ_WaveFunction(RCISDTQ_WaveFunction):
class UCISDTQ_WaveFunction(wf_types.WaveFunction):

def __init__(self, mo, c0, c1, c2, c3, c4):
super().__init__(mo)
self.c0 = c0
self.c1 = c1
self.c2 = c2
self.c3 = c3
self.c4 = c4
if not (isinstance(c3, tuple) and len(c3) == 4):
raise ValueError("c4 definition in UCISDTQ wfn requires tuple of (aaa, aba, bab, bbb) spin signatures")
if not (isinstance(c4, tuple) and len(c4) == 5):
raise ValueError(
"c4 definition in UCISDTQ wfn requires tuple of (aaaa, aaab, abab, abbb, bbbb) spin signatures")

def as_ccsdtq(self):
c1a, c1b = self.c1
c2aa, c2ab, c2bb = self.c2
# TODO
#c3aaa, c3aab, ... = self.c3
#c4aaaa, c4aaab, ... = self.c4

# T1
t1a = c1a/self.c0
t1b = c1b/self.c0
# T2
t2aa = c2aa/self.c0 - einsum('ia,jb->ijab', t1a, t1a) + einsum('ib,ja->ijab', t1a, t1a)
t2bb = c2bb/self.c0 - einsum('ia,jb->ijab', t1b, t1b) + einsum('ib,ja->ijab', t1b, t1b)
t2ab = c2ab/self.c0 - einsum('ia,jb->ijab', t1a, t1b)
# T3
raise NotImplementedError
#t3aaa = c3aaa/self.c0 - einsum('ijab,kc->ijkabc', t2a, t1a) - ...
# T4
#t4aaaa = c4aaaa/self.c0 - einsum('ijkabc,ld->ijklabcd', t3a, t1a) - ...

t1 = (t1a, t1b)
t2 = (t2aa, t2ab, t2bb)
# TODO
#t3 = (t3aaa, t3aab, ...)
#t4 = (t4aaaa, t4aaab, ...)
c1 = tuple(c / self.c0 for c in self.c1)
c2 = tuple(c / self.c0 for c in self.c2)
c3 = tuple(c / self.c0 for c in self.c3)
c4 = tuple(c / self.c0 for c in self.c4)

t1 = t_to_c.t1_uhf(c1)
t2 = t_to_c.t2_uhf(t1, c2)
t3 = t_to_c.t3_uhf(t1, t2, c3)
t4 = t_to_c.t4_uhf(t1, t2, t3, c4)

return wf_types.UCCSDTQ_WaveFunction(self.mo, t1=t1, t2=t2, t3=t3, t4=t4)
Loading

0 comments on commit edb1627

Please sign in to comment.