diff --git a/.gitignore b/.gitignore index 43e7a87c8..d0fe1434c 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,9 @@ pyscf.out # Vayesta log files vlog.txt verr.txt +f*_pyscf.txt +f*.out +*.h5 # HDF5 files *.h5 diff --git a/examples/ewdmet/01-sc-qp-ewdmet-hubbard1d-exact-limit.py b/examples/ewdmet/01-sc-qp-ewdmet-hubbard1d-exact-limit.py new file mode 100644 index 000000000..6326f5dca --- /dev/null +++ b/examples/ewdmet/01-sc-qp-ewdmet-hubbard1d-exact-limit.py @@ -0,0 +1,67 @@ +import numpy as np + +import vayesta +import vayesta.ewf +from vayesta.lattmod import Hubbard1D, LatticeRHF + +from dyson import MBLGF, AuxiliaryShift, FCI, MixedMBLGF, NullLogger, Lehmann + +nsite = 10 +nelec = nsite +u = 6 +nfrag = 2 + +nmom_max_fci = (4,4) +nmom_max_bath=1 + + + + +hubbard = Hubbard1D(nsite=nsite, nelectron=nelec, hubbard_u=u, verbose=0) +mf = LatticeRHF(hubbard) +mf.kernel() + +# Full system FCI GF +expr = FCI["1h"](mf) +th = expr.build_gf_moments(nmom_max_fci[0]) +expr = FCI["1p"](mf) +tp = expr.build_gf_moments(nmom_max_fci[1]) + +solverh = MBLGF(th, log=NullLogger()) +solverp = MBLGF(tp, log=NullLogger()) +solver = MixedMBLGF(solverh, solverp) +solver.kernel() +se = solver.get_self_energy() +solver = AuxiliaryShift(th[1]+tp[1], se, nelec, log=NullLogger()) +solver.kernel() +static_potential = se.as_static_potential(mf.mo_energy, eta=1e-2) +gf = solver.get_greens_function() +dm = gf.occupied().moment(0) * 2.0 +nelec_gf = np.trace(dm) +print("Exact GF nelec: %s"%nelec_gf) + +sc = mf.get_ovlp() @ mf.mo_coeff +new_fock = sc @ (th[1] + tp[1] + static_potential) @ sc.T +e, mo_coeff = np.linalg.eigh(new_fock) +chempot = (e[nelec//2-1] + e[nelec//2] ) / 2 +gf_static = Lehmann(e, mo_coeff, chempot=chempot) + +gap = lambda gf: gf.physical().virtual().energies[0] - gf.physical().occupied().energies[-1] +dynamic_gap = gap(gf) +static_gap = gap(gf_static) + +# QP-EwDMET GF +opts = dict(sc=False, store_hist=True, aux_shift=True, store_scfs=True, diis=True, damping=0, static_potential_conv_tol=1e-6, use_sym=False, eta=1e-2) +emb = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(bathtype='full', dmet_threshold=1e-12), solver_options=dict(conv_tol=1e-15, n_moments=nmom_max_fci)) +emb.qpewdmet_scmf(proj=1, maxiter=10, **opts) +nimages = [nsite//nfrag, 1, 1] +emb.symmetry.set_translations(nimages) +with emb.site_fragmentation() as f: + f.add_atomic_fragment(range(nfrag)) +emb.kernel() + +emb_dynamic_gap = gap(emb.with_scmf.gf) +emb_static_gap = gap(emb.with_scmf.gf_qp) +print("Ran for %s iterations"%emb.with_scmf.iteration) +print("Dynamic gap, FCI: %s Emb: %s"%(dynamic_gap, emb_dynamic_gap)) +print("Static gap, FCI: %s Emb: %s"%(static_gap, emb_static_gap)) \ No newline at end of file diff --git a/examples/ewdmet/02-sc-qp-ewdmet-hubbard1d-projectors.py b/examples/ewdmet/02-sc-qp-ewdmet-hubbard1d-projectors.py new file mode 100644 index 000000000..ca70d65bf --- /dev/null +++ b/examples/ewdmet/02-sc-qp-ewdmet-hubbard1d-projectors.py @@ -0,0 +1,65 @@ +import numpy as np + +import vayesta +import vayesta.ewf +from vayesta.lattmod import Hubbard1D, LatticeRHF + +# Plot the spectrum +from dyson.util import build_spectral_function +import matplotlib.pyplot as plt + +nsite = 12 +nelec = nsite +nfrag = 2 +u = 3 + +nmom_max_fci = (4,4) +nmom_max_bath=1 + + +hubbard = Hubbard1D(nsite=nsite, nelectron=nelec, hubbard_u=u, verbose=0) +mf = LatticeRHF(hubbard) +mf.kernel() + +emb = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(bathtype='ewdmet', max_order=1, order=1, dmet_threshold=1e-10), solver_options=dict(conv_tol=1e-12, init_guess='cisd', n_moments=nmom_max_fci)) +nimages = [nsite//nfrag, 1, 1] +emb.symmetry.set_translations(nimages) +with emb.site_fragmentation() as f: + f.add_atomic_fragment(list(range(nfrag))) +emb.qpewdmet_scmf(maxiter=100, proj=1) +emb.kernel() +gf, gf_qp = emb.with_scmf.get_greens_function() + +fig, ax = plt.subplots(2,1, figsize=(16,9)) + +grid = np.linspace(-5, 11, 1024) +sf_hf = build_spectral_function(mf.mo_energy, np.eye(mf.mo_occ.size), grid, eta=0.1) +ax[0].plot(grid, sf_hf, 'r-', label='HF') +ax[1].plot(grid, sf_hf, 'r-', label='HF') + +sf_dynamic = build_spectral_function(gf.energies, gf.couplings, grid, eta=0.1) +sf_static = build_spectral_function(gf_qp.energies, gf_qp.couplings, grid, eta=0.1) +ax[0].plot(grid, sf_dynamic, "b-", label="QP-EwDMET (1 Proj, dynamic)") +ax[1].plot(grid, sf_static, "g-", label="QP-EwDMET (1 Proj, static)") + + +emb = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(bathtype='ewdmet', max_order=1, order=1, dmet_threshold=1e-10), solver_options=dict(conv_tol=1e-12, init_guess='cisd', n_moments=nmom_max_fci)) +nimages = [nsite//nfrag, 1, 1] +emb.symmetry.set_translations(nimages) +with emb.site_fragmentation() as f: + f.add_atomic_fragment(list(range(nfrag))) +emb.qpewdmet_scmf(maxiter=100, proj=2) +emb.kernel() +gf, gf_qp = emb.with_scmf.get_greens_function() + +sf_dynamic = build_spectral_function(gf.energies, gf.couplings, grid, eta=0.1) +sf_static = build_spectral_function(gf_qp.energies, gf_qp.couplings, grid, eta=0.1) +ax[0].plot(grid, sf_dynamic, "m-", label="QP-EwDMET (2 Proj, dynamic)") +ax[1].plot(grid, sf_static, "y-", label="QP-EwDMET (2, Proj, static)") + + +ax[0].set_title('U = %d'%u) +ax[0].legend() +ax[1].legend() + +plt.savefig("hubbard_spectral_function.png") \ No newline at end of file diff --git a/examples/ewdmet/03-sc-qp-ewdmet-hubbard1d-bethe.py b/examples/ewdmet/03-sc-qp-ewdmet-hubbard1d-bethe.py new file mode 100644 index 000000000..f52c80fc8 --- /dev/null +++ b/examples/ewdmet/03-sc-qp-ewdmet-hubbard1d-bethe.py @@ -0,0 +1,42 @@ +import numpy as np +import vayesta +import vayesta.ewf +from vayesta.lattmod import Hubbard1D, LatticeRHF +from vayesta.lattmod.bethe import hubbard1d_bethe_gap +from dyson import Lehmann, FCI, CCSD + +u = 3 +nsite = 128 +nelec = nsite +nfrag = 2 +nmom_max_bath = 1 +nmom_max_fci = (4,4) +solv = 'FCI' +EXPR = FCI if solv=='FCI' else CCSD +hubbard = Hubbard1D(nsite=nsite, nelectron=nelec, hubbard_u=u, verbose=0) +mf = LatticeRHF(hubbard) +mf.kernel() + +chempot = (mf.mo_energy[nsite//2-1] + mf.mo_energy[nsite//2] ) / 2 +gf_hf = Lehmann(mf.mo_energy, np.eye(mf.mo_coeff.shape[0]), chempot=chempot) + + +emb = vayesta.ewf.EWF(mf, solver=solv, bath_options=dict(bathtype='ewdmet', max_order=nmom_max_bath, order=nmom_max_bath, dmet_threshold=1e-12), solver_options=dict(conv_tol=1e-12, n_moments=nmom_max_fci)) +emb.qpewdmet_scmf(proj=2, maxiter=10) +nimages = [nsite//nfrag, 1, 1] +emb.symmetry.set_translations(nimages) +with emb.site_fragmentation() as f: + f.add_atomic_fragment(list(range(nfrag))) +emb.kernel() + +gap = lambda gf: gf.physical().virtual().energies[0] - gf.physical().occupied().energies[-1] +emb_dynamic_gap = gap(emb.with_scmf.gf) +emb_static_gap = gap(emb.with_scmf.gf_qp) +hf_gap = gap(gf_hf) +bethe_gap = hubbard1d_bethe_gap(1,u, interval=(1,200)) + +print("Ran for %s iterations"%emb.with_scmf.iteration) +print("Bethe ansatz gap: %s "%(bethe_gap)) +print("Hartree-Fock gap: %s"%(hf_gap)) +print("Dynamic GF gap: %s"%(emb_dynamic_gap)) +print("Static GF gap: %s"%(emb_static_gap)) diff --git a/examples/ewdmet/10-sc-qp-ewdmet-hydrogen-ring-exact-limit.py b/examples/ewdmet/10-sc-qp-ewdmet-hydrogen-ring-exact-limit.py new file mode 100644 index 000000000..efeee6423 --- /dev/null +++ b/examples/ewdmet/10-sc-qp-ewdmet-hydrogen-ring-exact-limit.py @@ -0,0 +1,77 @@ +import numpy as np +import scipy + +import pyscf +import pyscf.scf + +import vayesta +import vayesta.ewf +from vayesta.misc.molecules import ring +from dyson import MBLGF, AuxiliaryShift, FCI, MixedMBLGF, NullLogger, Lehmann +from dyson.util import build_spectral_function + +a = 1 +natom = 10 +nfrag = 1 +maxiter = 100 +nmom_max_fci = (4,4) +nmom_max_bath=1 + +mol = pyscf.gto.Mole() +mol.atom = ring('H', natom, a) +mol.basis = 'sto-3g' +mol.output = 'pyscf.out' +mol.verbose = 4 +mol.build() + +# Hartree-Fock +mf = pyscf.scf.RHF(mol) +mf.kernel() +assert mf.converged +chempot = (mf.mo_energy[natom//2-1] + mf.mo_energy[natom//2] ) / 2 +gf_hf = Lehmann(mf.mo_energy, np.eye(mf.mo_coeff.shape[0]), chempot=chempot) + +# Full system FCI GF +expr = FCI["1h"](mf) +th = expr.build_gf_moments(nmom_max_fci[0]) +expr = FCI["1p"](mf) +tp = expr.build_gf_moments(nmom_max_fci[1]) + +solverh = MBLGF(th, log=NullLogger()) +solverp = MBLGF(tp, log=NullLogger()) +solver = MixedMBLGF(solverh, solverp) +solver.kernel() +se = solver.get_self_energy() +solver = AuxiliaryShift(th[1]+tp[1], se, natom, log=NullLogger()) +solver.kernel() +static_potential = se.as_static_potential(mf.mo_energy, eta=1e-2) +gf = solver.get_greens_function() +dm = gf.occupied().moment(0) * 2.0 +nelec_gf = np.trace(dm) +print("Exact GF nelec: %s"%nelec_gf) + +sc = mf.get_ovlp() @ mf.mo_coeff +new_fock = sc @ (th[1] + tp[1] + static_potential) @ sc.T +e, mo_coeff = scipy.linalg.eigh(new_fock, mf.get_ovlp()) +chempot = (e[natom//2-1] + e[natom//2] ) / 2 +gf_static = Lehmann(e, np.eye(mf.mo_coeff.shape[0]), chempot=chempot) + +gap = lambda gf: gf.physical().virtual().energies[0] - gf.physical().occupied().energies[-1] +dynamic_gap = gap(gf) +static_gap = gap(gf_static) + +# QP-EwDMET GF +emb = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(bathtype='full', dmet_threshold=1e-12), solver_options=dict(conv_tol=1e-15, n_moments=nmom_max_fci)) +emb.qpewdmet_scmf(proj=1, maxiter=maxiter) +with emb.site_fragmentation() as f: + with f.rotational_symmetry(order=int(natom/nfrag), axis='z') as rot: + f.add_atomic_fragment(range(nfrag)) +emb.kernel() + +hf_gap = gap(gf_hf) +emb_dynamic_gap = gap(emb.with_scmf.gf) +emb_static_gap = gap(emb.with_scmf.gf_qp) +print("Ran for %s iterations"%emb.with_scmf.iteration) +print("Hartree-Fock gap: %s"%hf_gap) +print("Dynamic gap, FCI: %s Emb: %s"%(dynamic_gap, emb_dynamic_gap)) +print("Static gap, FCI: %s Emb: %s"%(static_gap, emb_static_gap)) \ No newline at end of file diff --git a/examples/ewf/molecules/42-fci-incluster-moments.py b/examples/ewf/molecules/42-fci-incluster-moments.py index 718456cb6..be787016f 100644 --- a/examples/ewf/molecules/42-fci-incluster-moments.py +++ b/examples/ewf/molecules/42-fci-incluster-moments.py @@ -12,7 +12,7 @@ H 0.0000 0.7572 -0.4692 H 0.0000 -0.7572 -0.4692 """ -mol.basis = "cc-pVDZ" +mol.basis = "sto-6g" mol.output = "pyscf.txt" mol.build() @@ -20,8 +20,8 @@ mf = pyscf.scf.RHF(mol) mf.kernel() -# Embedded CCSD -emb = vayesta.ewf.EWF(mf, bath_options=dict(threshold=1e-6), solver_options=dict(n_moments=(5, 4), solve_lambda=True)) +# Embedded FCI +emb = vayesta.ewf.EWF(mf, solver='FCI', bath_options=dict(threshold=1e-6), solver_options=dict(n_moments=(5, 4))) emb.kernel() # Reference full system CCSD: diff --git a/vayesta/core/qemb/qemb.py b/vayesta/core/qemb/qemb.py index bd49b1f94..cefeb8731 100644 --- a/vayesta/core/qemb/qemb.py +++ b/vayesta/core/qemb/qemb.py @@ -1732,7 +1732,15 @@ def brueckner_scmf(self, *args, **kwargs): """Decorator for Brueckner-DMET.""" self.with_scmf = Brueckner(self, *args, **kwargs) self.kernel = self.with_scmf.kernel - + def qpewdmet_scmf(self, *args, **kwargs): + """Decorator for QP-EWDMET.""" + try: + from vayesta.core.scmf import QPEWDMET + except ImportError: + self.log.error("QP-EWDMET requires Dyson installed") + return + self.with_scmf = QPEWDMET(self, *args, **kwargs) + self.kernel = self.with_scmf.kernel def check_solver(self, solver): is_uhf = np.ndim(self.mo_coeff[1]) == 2 if self.opts.screening: diff --git a/vayesta/core/qemb/self_energy.py b/vayesta/core/qemb/self_energy.py new file mode 100644 index 000000000..69d95a0c1 --- /dev/null +++ b/vayesta/core/qemb/self_energy.py @@ -0,0 +1,504 @@ +"""Routines to reconstruct the full system self-energy from cluster spectral moments""" + +import numpy as np + +from vayesta.core.util import NotCalculatedError, Object, dot, einsum +try: + from dyson import Lehmann, MBLGF, MixedMBLGF, NullLogger, AuxiliaryShift +except ImportError as e: + print(e) + print("Dyson required for self-energy calculations") + +def make_self_energy_moments(emb, n_se_mom, use_sym=True, proj=1, eta=1e-2): + """ + Construct full system self-energy moments from cluster spectral moments + + Parameters + ---------- + emb : EWF object + Embedding object + n_se_mom : int + Number of self-energy moments + use_sym : bool + Use symmetry to reconstruct self-energy + proj : int + Number of projectors to use (1 or 2) + eta : float + Broadening factor for static potential + + Returns + ------- + self_energy_moms : ndarry (n_se_mom, nmo, nmo) + Full system self-energy moments (MO basis) + static_self_energy : ndarray (nmo,nmo) + Static part of self-energy (MO basis) + static_potential : ndarray (nao,nao) + Static potential (AO basis) + """ + + fock = emb.get_fock() + static_self_energy = np.zeros_like(fock) + static_potential = np.zeros_like(fock) + self_energy_moms = np.zeros((n_se_mom, fock.shape[1], fock.shape[1])) + + fragments = emb.get_fragments(sym_parent=None) if use_sym else emb.get_fragments() + for i, f in enumerate(fragments): + # Calculate self energy from cluster moments + th, tp = f.results.moms + + solverh = MBLGF(th, log=NullLogger()) + solverp = MBLGF(tp, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se = solver.get_self_energy() + se_moms_clus = [se.moment(i) for i in range(n_se_mom)] + + mc = f.get_overlap('mo|cluster') + mf = f.get_overlap('mo|frag') + fc = f.get_overlap('frag|cluster') + cfc = fc.T @ fc + + # Fock matrix in cluster basis + fock_cls = f.cluster.c_active.T @ fock @ f.cluster.c_active + e_cls = np.diag(fock_cls) + + if proj == 1: + # Static potential + v_cls = se.as_static_potential(e_cls, eta=eta) # Static potential (used to update MF for the self-consistnecy) + v_frag = cfc @ v_cls + v_frag = 0.5 * (v_frag + v_frag.T) + static_potential += f.cluster.c_active @ v_frag @ f.cluster.c_active.T + + # Static self-energy + static_se_cls = th[1] + tp[1] - fock_cls + static_self_energy_frag = cfc @ static_se_cls + static_self_energy_frag = 0.5 * (static_self_energy_frag + static_self_energy_frag.T) + static_self_energy += mc @ static_self_energy_frag @ mc.T + + # Self-energy moments + se_moms_frag = [0.5*(cfc @ mom + mom @ cfc) for mom in se_moms_clus] + self_energy_moms += np.array([mc @ mom @ mc.T for mom in se_moms_frag]) + + if use_sym: + for child in f.get_symmetry_children(): + static_potential += child.cluster.c_active @ v_frag @ child.cluster.c_active.T + mc_child = child.get_overlap('mo|cluster') + static_self_energy += mc_child @ static_self_energy_frag @ mc_child.T + self_energy_moms += np.array([mc_child @ mom @ mc_child.T for mom in se_moms_frag]) + + elif proj == 2: + # Static potential + v_cls = se.as_static_potential(e_cls, eta=eta) + v_frag = fc @ v_cls @ fc.T + static_potential += f.c_frag @ v_frag @ f.c_frag.T + + # Static self-energy + static_se_cls = th[1] + tp[1] - fock_cls + static_se_frag = fc @ static_se_cls @ fc.T + static_self_energy += mf @ static_se_frag @ mf.T + + # Self-energy moments + se_moms_frag = [0.5*(fc @ mom @ fc.T) for mom in se_moms_clus] + self_energy_moms += np.array([mf @ mom @ mf.T for mom in se_moms_frag]) + + if use_sym: + for child in f.get_symmetry_children(): + static_potential += child.c_frag @ v_frag @ child.c_frag.T + mf_child = child.get_overlap('mo|frag') + fc_child = child.get_overlap('frag|cluster') + static_self_energy += mf_child @ static_se_frag @ mf_child.T + self_energy_moms += np.array([mf_child @ mom @ mf_child.T for mom in se_moms_frag]) + + return self_energy_moms, static_self_energy, static_potential + +def make_self_energy_1proj(emb, use_sym=True, use_svd=True, eta=1e-2, aux_shift_frag=False, se_degen_tol=1e-4, se_eval_tol=1e-6, drop_non_causal=False): + """ + Construct full system self-energy in Lehmann representation from cluster spectral moments using 1 projector + + TODO: MPI, SVD + + Parameters + ---------- + emb : EWF object + Embedding object + use_sym : bool + Use symmetry to reconstruct self-energy + use_svd : bool + Use SVD to decompose the self-energy as outer product + eta : float + Broadening factor for static potential + se_degen_tol : float + Tolerance for degeneracy in Lehmann representation + se_eval_tol : float + Tolerance for self-energy eigenvalues assumed to be kept + drop_non_causal : bool + Drop non-causal poles (negative eigenvalues) of self-energy + + Returns + ------- + self_energy : Lehmann object + Reconstructed self-energy in Lehmann representation (MO basis) + static_self_energy : ndarray (nmo,nmo) + Static part of self-energy (MO basis) + static_potential : ndarray (nao,nao) + Static potential (AO basis) + """ + + fock = emb.get_fock() + static_self_energy = np.zeros_like(fock) + static_potential = np.zeros_like(fock) + energies = [] + if use_svd: + couplings_l, couplings_r = [], [] + else: + couplings = [] + fragments = emb.get_fragments(sym_parent=None) if use_sym else emb.get_fragments() + for i, f in enumerate(fragments): + # Calculate self energy from cluster moments + th, tp = f.results.moms + + solverh = MBLGF(th, log=emb.log) + solverp = MBLGF(tp, log=emb.log) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se = solver.get_self_energy() + gf = solver.get_greens_function() + dm = gf.occupied().moment(0) * 2 + nelec = np.trace(dm) + emb.log.info("Fragment %s: Electron target %f %f without shift"%(f.id, f.nelectron, nelec)) + if aux_shift_frag: + aux = AuxiliaryShift(th[0]+tp[0], se, f.nelectron, occupancy=2, log=emb.log) + aux.kernel() + se = aux.get_self_energy() + gf = aux.get_greens_function() + dm = gf.occupied().moment(0) * 2 + nelec = np.trace(dm) + emb.log.info("Fragment %s: Electron target %f %f with shift"%(f.id, f.nelectron, nelec)) + + mc = f.get_overlap('mo|cluster') + fc = f.get_overlap('frag|cluster') + cfc = fc.T @ fc + + # Fock matrix in cluster basis + fock_cls = f.cluster.c_active.T @ fock @ f.cluster.c_active + e_cls = np.diag(fock_cls) + + # Static potential + v_cls = se.as_static_potential(e_cls, eta=eta) # Static potential (used to update MF for the self-consistnecy) + v_frag = cfc @ v_cls + v_frag = 0.5 * (v_frag + v_frag.T) + static_potential += f.cluster.c_active @ v_frag @ f.cluster.c_active.T + + # Static self-energy + static_se_cls = th[1] + tp[1] - fock_cls + static_self_energy_frag = cfc @ static_se_cls + static_self_energy_frag = 0.5 * (static_self_energy_frag + static_self_energy_frag.T) + static_self_energy += mc @ static_self_energy_frag @ mc.T + + # Dynamic self-energy + coup_l, coup_r = se._unpack_couplings() + sym_coup = 0.5*(einsum('pa,qa->apq', cfc @ coup_l , coup_r) + einsum('pa,qa->apq', coup_l , cfc @ coup_r)) + + if use_svd: + couplings_l_frag, couplings_r_frag, energies_frag = [], [], [] + for a in range(sym_coup.shape[0]): + m = sym_coup[a] + U, s, Vt = np.linalg.svd(m) + idx = np.abs(s) > se_eval_tol + assert idx.sum() <= 2 + u = U[:,idx] @ np.diag(np.sqrt(s[idx])) + v = Vt.conj().T[:,idx] @ np.diag(np.sqrt(s[idx])) + couplings_l_frag.append(u) + couplings_r_frag.append(v) + energies_frag += [se.energies[a] for e in range(idx.sum())] + + couplings_l_frag, couplings_r_frag = np.hstack(couplings_l_frag), np.hstack(couplings_r_frag) + couplings_l.append(mc @ couplings_l_frag) + couplings_r.append(mc @ couplings_r_frag) + energies.append(energies_frag) + else: + couplings_frag, energies_frag = [], [] + for a in range(sym_coup.shape[0]): + m = sym_coup[a] + val, vec = np.linalg.eigh(m) + idx = np.abs(val) > se_eval_tol + assert idx.sum() <= 2 + w = vec[:,idx] @ np.diag(np.sqrt(val[idx], dtype=np.complex64)) + couplings_frag.append(w) + energies_frag += [se.energies[a] for e in range(idx.sum())] + + couplings_frag = np.hstack(couplings_frag) + + couplings.append(mc @ couplings_frag) + energies.append(energies_frag) + + if use_sym: + for child in f.get_symmetry_children(): + static_potential += child.cluster.c_active @ v_frag @ child.cluster.c_active.T + mc_child = child.get_overlap('mo|cluster') + static_self_energy += mc_child @ static_self_energy_frag @ mc_child.T + energies.append(energies_frag) + if use_svd: + couplings_l.append(mc_child @ couplings_l_frag) + couplings_r.append(mc_child @ couplings_r_frag) + else: + couplings.append(mc_child @ couplings_frag) + + energies = np.concatenate(energies) + if use_svd: + couplings = np.hstack(couplings_l), np.hstack(couplings_r) + else: + couplings = np.hstack(couplings) + self_energy = Lehmann(energies, couplings) + + self_energy = remove_se_degeneracy(emb, self_energy, dtol=se_degen_tol, etol=se_eval_tol, drop_non_causal=drop_non_causal) + + return self_energy, static_self_energy, static_potential + + +def make_self_energy_2proj(emb, use_sym=True, eta=1e-2): + """ + Construct full system self-energy in Lehmann representation from cluster spectral moments using 2 projectors + + TODO: MPI, SVD + + Parameters + ---------- + emb : EWF object + Embedding object + use_sym : bool + Use symmetry to reconstruct self-energy + eta : float + Broadening factor for static potential + + Returns + ------- + self_energy : Lehmann object + Reconstructed self-energy in Lehmann representation (MO basis) + static_self_energy : ndarray (nmo,nmo) + Static part of self-energy (MO basis) + static_potential : ndarray (nao,nao) + Static potential (AO basis) + """ + + fock = emb.get_fock() + static_self_energy = np.zeros_like(fock) + static_potential = np.zeros_like(fock) + couplings, energies = [], [] + + fragments = emb.get_fragments(sym_parent=None) if use_sym else emb.get_fragments() + for i, f in enumerate(fragments): + # Calculate self energy from cluster moments + th, tp = f.results.moms + + solverh = MBLGF(th, log=NullLogger()) + solverp = MBLGF(tp, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se = solver.get_self_energy() + + mf = f.get_overlap('mo|frag') + fc = f.get_overlap('frag|cluster') + + # Fock matrix in cluster basis + fock_cls = f.cluster.c_active.T @ fock @ f.cluster.c_active + e_cls = np.diag(fock_cls) + + # Static potential + v_cls = se.as_static_potential(e_cls, eta=eta) + v_frag = fc @ v_cls @ fc.T + static_potential += f.c_frag @ v_frag @ f.c_frag.T + + # Static self-energy + static_se_cls = th[1] + tp[1] - fock_cls + static_se_frag = fc @ static_se_cls @ fc.T + static_self_energy += mf @ static_se_frag @ mf.T + + # Dynamic self-energy + if type(se.couplings) is tuple: + couplings_l, couplings_r = se.couplings + couplings_l = mf @ fc @ couplings_l + couplings_r = mf @ fc @ couplings_r + couplings.append((couplings_l, couplings_r)) + else: + couplings.append(mf @ fc @ se.couplings) + energies.append(se.energies) + + if use_sym: + for child in f.get_symmetry_children(): + static_potential += child.c_frag @ v_frag @ child.c_frag.T + mf_child = child.get_overlap('mo|frag') + fc_child = child.get_overlap('frag|cluster') + static_self_energy += mf_child @ static_se_frag @ mf_child.T + if type(se.couplings) is tuple: + couplings_l, couplings_r = se.couplings + couplings_l = mf_child @ fc_child @ couplings_l + couplings_r = mf_child @ fc_child @ couplings_r + couplings.append((couplings_l, couplings_r)) + else: + couplings.append(mf_child @ fc_child @ se.couplings) + + energies.append(se.energies) + + if type(couplings[0]) is tuple: + couplings_l, couplings_r = zip(*couplings) + couplings = np.hstack(couplings_l), np.hstack(couplings_r) + else: + couplings = np.hstack(couplings) + energies = np.concatenate(energies) + self_energy = Lehmann(energies, couplings) + #self_energy = remove_se_degeneracy(emb, self_energy)#, dtol=se_degen_tol, etol=se_eval_tol, drop_non_causal=drop_non_causal) + + return self_energy, static_self_energy, static_potential + +def remove_se_degeneracy(emb, se, dtol=1e-8, etol=1e-6, drop_non_causal=False): + + emb.log.debug("Removing degeneracy in self-energy - degenerate energy tol=%e evec tol=%e"%(dtol, etol)) + e = se.energies + couplings_l, couplings_r = se._unpack_couplings() + e_new, slices = get_unique(e, atol=dtol)# + emb.log.debug("Number of energies = %d, unique = %d"%(len(e),len(e_new))) + energies, couplings = [], [] + warn_non_causal = False + for i, s in enumerate(slices): + mat = np.einsum('pa,qa->pq', couplings_l[:,s], couplings_r[:,s]).real + val, vec = np.linalg.eigh(mat) + if drop_non_causal: + idx = val > etol + else: + idx = np.abs(val) > etol + if np.sum(val[idx] < -etol) > 0: + warn_non_causal = True + w = vec[:,idx] @ np.diag(np.sqrt(val[idx], dtype=np.complex64)) + couplings.append(w) + energies += [e_new[i] for _ in range(idx.sum())] + + emb.log.debug(" | E = %e << %s"%(e_new[i],e[s])) + emb.log.debug(" evals: %s"%val) + emb.log.debug(" kept: %s"%(val[idx])) + if warn_non_causal: + emb.log.warning("Non-causal poles found in self-energy") + couplings = np.hstack(couplings).real + return Lehmann(np.array(energies), np.array(couplings)) + +def get_unique(array, atol=1e-15): + + # Find elements of a sorted float array which are unique up to a tolerance + + assert len(array.shape) == 1 + + i = 0 + slices = [] + while i < len(array): + j = 1 + idxs = [i] + while i+j < len(array): + if np.abs(array[i] - array[i+j]) < atol: + idxs.append(i+j) + j += 1 + else: + break + i = i + j + slices.append(np.s_[idxs[0]:idxs[-1]+1]) + new_array = np.array([array[s].mean() for s in slices]) + return new_array, slices + + +def fit_hermitian(se): + """ + Fit a causal self-energy + + Parameters + ---------- + se : Lehmann + Self-energy in Lehmann representation + + Returns + ------- + se : Lehmann + Fitted causal self-energy + """ + + energies = se.energies.copy() + couplings_l, couplings_r = se._unpack_couplings() + couplings_l, couplings_r = couplings_l.copy(), couplings_r.copy() + def f(w): + denom = 1 / (1j*w - energies + 1j * eta) + return np.einsum('pa,qa,a->pq', couplings_l, couplings_r, denom) + + def obj(x): + x = x.reshape(shape) + V, e = x[:-1], x[-1] + def integrand(w): + denom = 1 / (1j*w - energies) + a = np.einsum('pa,qa,a->pq', couplings_l, couplings_r, denom) + + denom = 1 / (1j*w - e) + b = np.einsum('pa,qa,a->pq', V, V, denom) + c = (np.abs(a - b) ** 2).sum() + #print(c) + return c + lim = np.inf + val, err = scipy.integrate.quad(integrand, -lim, lim) + print("obj: %s err: %s"%(val, err)) + return val + + def grad(x): + x = x.reshape(shape) + V, e = x[:-1], x[-1] + def integrand_V(w): + a = np.einsum('pa,qa,a->pq', couplings_l, couplings_r, 1 / (1j*w - energies)) + b = np.einsum('pa,qa,a->pq', V, V, 1 / (1j*w - e)) + d = b - a + omegaRe = e/(w**2 + e**2) + omegaIm = w/(w**2 + e**2) + + ret = np.einsum('rq,qb,b->rb', d.real, V, omegaRe) + ret += np.einsum('pr,pb,b->rb', d.real, V, omegaRe) + ret += np.einsum('rq,qb,b->rb', d.imag, V, omegaIm) + ret += np.einsum('pr,pb,b->rb', d.imag, V, omegaIm) + return -2 * ret + + def integrand_e(w): + a = np.einsum('pa,qa,a->pq', couplings_l, couplings_r, 1 / (1j*w - energies)) + b = np.einsum('pa,qa,a->pq', V, V, 1 / (1j*w - e)) + d = b - a + omegaRe = (e**2 - w**2)/(w**2 + e**2)**2 + omegaIm = 2*e*w/(w**2 + e**2)**2 + + #print(omegaIm) + + ret = 2*np.einsum('pq,pb,qb,b->b', d.real, V, V, omegaRe) + ret += 2*np.einsum('pq,pb,qb,b->b', d.imag, V, V, omegaIm) + return ret + + + integrand = lambda w: np.hstack([integrand_V(w).flatten(), integrand_e(w)]) + lim = np.inf + jac, err_V = scipy.integrate.quad_vec(lambda x: integrand(x), -lim, lim) + print('grad norm: %s err: %s'%(np.linalg.norm(jac),err_V)) + #print(grad) + return jac + + + x0 = np.vstack([couplings_l, energies]) + shape = x0.shape + x0 = x0.flatten() + + xgrad = grad(x0) + print(shape) + print("obj(x0) = %s"%obj(x0)) + print('grad(x0)') + print(xgrad) + #x0 = np.random.randn(*x0.shape) #* 1e-2 + + #x = xgrad.reshape(x0.shape) + + #return xgrad + print(shape) + res = scipy.optimize.minimize(obj, x0, jac=grad, method='BFGS') + #res = scipy.optimize.basinhopping(obj, x0.flatten(), niter=10, minimizer_kwargs=dict(method='BFGS')) + print("Sucess %s, Integral = %s"%(res.success, res.x)) + + x = res.x.reshape(shape) + return Lehmann(x[-1], x[:-1]) diff --git a/vayesta/core/scmf/__init__.py b/vayesta/core/scmf/__init__.py index 7bf3bf655..ddb4d78a5 100644 --- a/vayesta/core/scmf/__init__.py +++ b/vayesta/core/scmf/__init__.py @@ -14,3 +14,12 @@ def Brueckner(emb, *args, **kwargs): if emb.is_rhf: return Brueckner_RHF(emb, *args, **kwargs) return Brueckner_UHF(emb, *args, **kwargs) + +try: + from vayesta.core.scmf.qpewdmet import QPEWDMET_RHF + def QPEWDMET(emb, *args, **kwargs): + if emb.is_rhf: + return QPEWDMET_RHF(emb, *args, **kwargs) + raise NotImplementedError("QP-EWDMET for UHF not implemented") +except ImportError: + pass \ No newline at end of file diff --git a/vayesta/core/scmf/qpewdmet.py b/vayesta/core/scmf/qpewdmet.py new file mode 100644 index 000000000..8e6d7e383 --- /dev/null +++ b/vayesta/core/scmf/qpewdmet.py @@ -0,0 +1,283 @@ +import numpy as np +import scipy.linalg + +import pyscf.scf + +import vayesta +from vayesta.core.scmf.scmf import SCMF +from vayesta.core.foldscf import FoldedSCF +from vayesta.lattmod import LatticeRHF +from vayesta.core.qemb.self_energy import make_self_energy_1proj, make_self_energy_2proj +from dyson import Lehmann, AuxiliaryShift + +class QPEWDMET_RHF(SCMF): + """ Quasi-particle self-consistent energy weighted density matrix embedding """ + name = "QP-EWDMET" + + def __init__(self, emb, proj=2, static_potential_conv_tol=1e-5, global_static_potential=True, eta=1e-2, damping=0, sc=True, aux_shift=False, aux_shift_frag=False, store_hist=True, store_scfs=False, use_sym=False, static_potential_init=None, se_degen_tol=1e-6, se_eval_tol=1e-6, drop_non_causal=False, *args, **kwargs): + """ + Initialize QPEWDMET + + Parameters + ---------- + emb : QEmbedding o + Embedding object on which self consitency is based + proj : int + Number of fragment projectors applied to cluster self-energy + static_potential_conv_tol : float + Convergence threshold for static potential + eta : float + Broadening factor for static potential + damping : float + Damping factor for Fock matrix update + sc : bool + Use self-consistent determination of MOs for new Fock matrix + store_hist : bool + Store history throughout SCMF calculation (for debugging purposes) + use_sym : bool + Use fragment symmetry + static_potential_init : ndarray + Inital static potential + """ + + self.sc_fock = emb.get_fock() + self.static_self_energy = np.zeros_like(self.sc_fock) + self.sc = sc + self.eta = eta # Broadening factor + self.self_energy = None + self.static_potential = None + self.static_potential_last = None + self.static_potential_conv_tol = static_potential_conv_tol + self.proj = proj + self.store_hist = store_hist + self.use_sym = use_sym + self.static_potential_init = static_potential_init + self.store_scfs = store_scfs + self.se_degen_tol = se_degen_tol + self.se_eval_tol = se_eval_tol + self.drop_non_causal = drop_non_causal + self.aux_shift = aux_shift + self.aux_shift_frag = aux_shift_frag + self.global_static_potential = global_static_potential + + super().__init__(emb, *args, **kwargs) + + if self.store_hist: + self.static_potential_hist = [] + self.static_potential_frag_hist = [] + self.fock_hist = [] + self.static_gap_hist = [] + self.dynamic_gap_hist = [] + self.mo_coeff_hist = [] + + self.mom_hist = [] + + if self.store_scfs and self.sc: + self.scfs = [] + + self.damping = damping + + if self.static_potential_init is not None: + + e, mo_coeff = self.fock_scf(self.static_potential_init) + self.emb.update_mf(mo_coeff) + + dm1 = self.mf.make_rdm1() + # Check symmetry - needs fixing + try: + self.emb.check_fragment_symmetry(dm1) + except SymmetryError: + self.log.error("Symmetry check failed in %s", self.name) + self.converged = False + + def update_mo_coeff(self, mo_coeff, mo_occ, diis=None): + + """ + Get new MO coefficients for a SCMF iteration. + + TODO: Should GF and SE be stored in AO basis since the MO basis may change between iterations? + + Parameters + ---------- + mf : PySCF compatible SCF object + Mean-field object. + diis : pyscf.lib.diis.DIIS object, optional + DIIS object. + + Returns + ------- + mo_coeff : ndarray + New MO coefficients. + """ + + if self.static_potential is not None: + self.static_potential_last = self.static_potential.copy() + + self.fock = self.emb.mf.get_fock() + couplings = [] + energies = [] + self.static_potential = np.zeros_like(self.fock) + self.static_self_energy = np.zeros_like(self.fock) + + if self.proj == 1: + self.self_energy, self.static_self_energy, self.static_potential = make_self_energy_1proj(self.emb, use_sym=self.use_sym, eta=self.eta,aux_shift_frag=self.aux_shift_frag, se_degen_tol=self.se_degen_tol, se_eval_tol=self.se_eval_tol) + elif self.proj == 2: + self.self_energy, self.static_self_energy, self.static_potential = make_self_energy_2proj(self.emb, use_sym=self.use_sym, eta=self.eta) + else: + return NotImplementedError() + phys = self.emb.mo_coeff.T @ self.fock @ self.emb.mo_coeff + self.static_self_energy + gf = Lehmann(*self.self_energy.diagonalise_matrix_with_projection(phys), chempot=self.self_energy.chempot) + dm = gf.occupied().moment(0) * 2.0 + nelec_gf = np.trace(dm) + self.emb.log.info('Number of electrons in GF: %f'%nelec_gf) + if self.aux_shift: + aux = AuxiliaryShift(phys, self.self_energy, self.emb.mf.mol.nelectron, occupancy=2, log=self.log) + aux.kernel() + self.self_energy = aux.get_self_energy() + gf = aux.get_greens_function() + dm = gf.occupied().moment(0) * 2.0 + nelec_gf = np.trace(dm) + self.emb.log.info('Number of electrons in (shifted) GF: %f'%nelec_gf) + gap = lambda gf: gf.physical().virtual().energies[0] - gf.physical().occupied().energies[-1] + + + v_old = self.static_potential.copy() + sc = self.emb.mf.get_ovlp() @ self.emb.mo_coeff + if self.global_static_potential: + self.static_potential = self.emb.mo_coeff @ self.self_energy.as_static_potential(self.emb.mf.mo_energy, eta=self.eta) @ self.emb.mo_coeff.T + self.static_potential = self.emb.mf.get_ovlp() @ self.static_potential @ self.emb.mf.get_ovlp() + if diis is not None: + self.static_potential = diis.update(self.static_potential) + + new_fock = self.fock + sc @ self.static_self_energy @ sc.T + self.static_potential + self.sc_fock = self.damping * self.fock + (1-self.damping) * new_fock + #self.sc_fock = self.sc_fock + (1-self.damping) * self.static_potential + self.gf2 = gf + self.gf, self.gf_qp = self.get_greens_function() + + if self.sc: + e, mo_coeff = self.fock_scf(self.static_potential) + else: + e, mo_coeff = scipy.linalg.eigh(self.sc_fock, self.emb.get_ovlp()) + + dynamic_gap = gap(self.gf) + static_gap = gap(self.gf_qp) + if self.store_hist: + #self.static_potential_frag_hist.append(v_frag.copy()) + self.static_potential_hist.append(self.static_potential.copy()) + self.fock_hist.append(self.sc_fock.copy()) + self.static_gap_hist.append(static_gap) + self.dynamic_gap_hist.append(dynamic_gap) + self.mo_coeff_hist.append(mo_coeff.copy()) + + self.log.info("Dynamic Gap = %f"%dynamic_gap) + self.log.info("Static Gap = %f"%static_gap) + return mo_coeff + + def fock_scf(self, v): + """ + Relax density in presence of new static potential + + Parameters + ---------- + v : ndarray + Static potential + + Returns + ------- + mo_coeff : ndarray + New MO coefficients. + """ + + #mf = LatticeRHF(self.emb.mf.mol) + mf_class = type(self.emb.mf) + if mf_class is FoldedSCF: + # Temporary work around for k-SCF + raise NotImplementedError("QP-EWDMET with Fock re-diagonalisation not implemented for FoldedSCF") + mf = mf_class(self.emb.mf.mol) + #mf.get_fock_old = mf.get_fock + #def get_fock(*args, **kwargs): + # return mf.get_fock_old(*args, **kwargs) + self.static_potential + + def get_hcore(*args, **kwargs): + return self.emb.mf.get_hcore() + v + mf.get_hcore = get_hcore + e_tot = mf.kernel() + + + # def get_fock(dm): + # dm_ao = np.linalg.multi_dot((mf.mo_coeff, dm, mf.mo_coeff.T)) + # fock = mf.get_fock(dm=dm_ao) + # return np.linalg.multi_dot((mf.mo_coeff.T, fock, mf.mo_coeff)) + + # # Use the DensityRelaxation class to relax the density matrix such + # # that it is self-consistent with the Fock matrix, and the number of + # # electrons is correct + # solver = DensityRelaxation(get_fock, se, mol.nelectron) + # solver.conv_tol = 1e-10 + # solver.max_cycle_inner = 30 + # solver.kernel()2 + + if mf.converged: + self.log.info("SCF converged, energy: {:.6f}".format(e_tot)) + else: + self.log.warning("SCF NOT converged, energy: {:.6f}".format(e_tot)) + + if self.store_scfs: + self.scfs.append(mf) + return mf.mo_energy, mf.mo_coeff + + def check_convergence(self, e_tot, dm1, e_last=None, dm1_last=None, etol=None, dtol=None): + _, de, ddm = super().check_convergence(e_tot, dm1, e_last=e_last, dm1_last=dm1_last, etol=etol, dtol=dtol) + dv = np.inf + if self.static_potential_last is not None: + dv = np.abs(self.static_potential - self.static_potential_last).sum() + if dv < self.static_potential_conv_tol: + return True, dv, ddm + return False, dv, ddm + + def get_greens_function(self): + """ + Calculate the dynamic and static Green's function in the Lehmann representation + + Returns + ------- + gf : Lehmann + Dynamic Green's function from Block Lanczos + gf_qp : Lehmann + Static Green's function from Fock matrix + Klein potential + """ + + + # Shift final auxiliaries to ensure right particle number + phys = self.emb.mf.mo_coeff.T @ self.emb.mf.get_fock() @ self.emb.mf.mo_coeff + self.static_self_energy + nelec = self.emb.mf.mol.nelectron + shift = AuxiliaryShift(phys, self.self_energy, nelec, occupancy=2, log=self.emb.log) + shift.kernel() + se_shifted = shift.get_self_energy() + vayesta.log.info('Final (shifted) auxiliaries: {} ({}o, {}v)'.format(se_shifted.naux, se_shifted.occupied().naux, se_shifted.virtual().naux)) + self.se_shifted = se_shifted + # Find the Green's function + + gf = Lehmann(*se_shifted.diagonalise_matrix_with_projection(phys), chempot=se_shifted.chempot) + dm = gf.occupied().moment(0) * 2.0 + nelec_gf = np.trace(dm) + if not np.isclose(nelec_gf, gf.occupied().weights(occupancy=2).sum()): + vayesta.log.warning('Number of electrons in final (shifted) GF: %f'%nelec_gf) + else: + vayesta.log.info('Number of electrons in final (shifted) GF with dynamical self-energy: %f'%nelec_gf) + + if not np.isclose(nelec_gf, float(nelec)): + vayesta.log.warning('Number of electrons in final (shifted) GF: %f'%nelec_gf) + + #qp_ham = self.emb.get_fock() + self.static_potential + sc = self.emb.mf.get_ovlp() @ self.emb.mo_coeff + qp_ham = self.fock + sc @ self.static_self_energy @ sc.T + self.static_potential + qp_e, qp_c = scipy.linalg.eigh(qp_ham, self.emb.mf.get_ovlp()) + + self.qpham = qp_ham + qp_mu = (qp_e[nelec//2-1] + qp_e[nelec//2] ) / 2 + self.qpmu = qp_mu + gf_qp = Lehmann(qp_e, np.eye(len(qp_e)), chempot=qp_mu) + + return gf, gf_qp \ No newline at end of file diff --git a/vayesta/core/scmf/scmf.py b/vayesta/core/scmf/scmf.py index d7498b7a2..063406ee4 100644 --- a/vayesta/core/scmf/scmf.py +++ b/vayesta/core/scmf/scmf.py @@ -9,13 +9,15 @@ class SCMF: name = "SCMF" - def __init__(self, emb, etol=1e-8, dtol=1e-6, maxiter=100, damping=0.0, diis=True): + def __init__(self, emb, etol=1e-8, dtol=1e-6, maxiter=100, damping=0.0, diis=True, diis_space=6, diis_min_space=1): self.emb = emb self.etol = etol if etol is not None else np.inf self.dtol = dtol if dtol is not None else np.inf self.maxiter = maxiter self.damping = damping self.diis = diis + self.diis_space = diis_space + self.diis_min_space = diis_min_space self.iteration = 0 # Save original kernel self._kernel_orig = self.emb.kernel @@ -76,7 +78,19 @@ def check_convergence(self, e_tot, dm1, e_last=None, dm1_last=None, etol=None, d return False, de, ddm def kernel(self, *args, **kwargs): - diis = self.get_diis() if self.diis else None + + if self.diis: + diis = self.get_diis() + if type(diis) is tuple: + # Unrestricted + diis[0].space, diis[1].space = self.diis_space, self.diis_space + diis[0].min_space, diis[1].min_space = self.diis_min_space, self.diis_min_space + else: + # Restricted + diis.space = self.diis_space + diis.min_space = self.diis_min_space + else: + diis = None e_last = dm1_last = None for self.iteration in range(1, self.maxiter + 1): diff --git a/vayesta/ewf/fragment.py b/vayesta/ewf/fragment.py index b551702bc..b8e028307 100644 --- a/vayesta/ewf/fragment.py +++ b/vayesta/ewf/fragment.py @@ -282,6 +282,7 @@ def kernel(self, solver=None, init_guess=None): # --- Correlation energy contributions if self.opts.calc_e_wf_corr: ci = wf.as_cisd(c0=1.0) + ci = ci.project(proj) es, ed, results.e_corr = self.get_fragment_energy(ci.c1, ci.c2, hamil=self.hamil) self.log.debug( diff --git a/vayesta/lattmod/bethe.py b/vayesta/lattmod/bethe.py index 86cd3642e..deceece25 100644 --- a/vayesta/lattmod/bethe.py +++ b/vayesta/lattmod/bethe.py @@ -71,3 +71,19 @@ def hubbard1d_bethe_docc_numdiff(t, u, du=1e-10, order=2, **kwargs): e = hubbard1d_bethe_energy(t, u) d = hubbard1d_bethe_docc(t, u) print("U= %6.3f: Energy= %.8f Double occupancy= %.8f" % (u, e, d)) + + +def hubbard1d_bethe_gap(t, u, interval=(1, 100), **kwargs): + """Exact band gap for the 1D Hubbard model at half filling in the thermodynamic limit. + + from DOI: 10.1103/PhysRevB.106.045123""" + + #kwargs['limit'] = kwargs.get('limit', 100) + + def func(x): + return np.sqrt(x**2 - 1) / np.sinh(2*np.pi*t*x/u) + + eg, *res = scipy.integrate.quad(func, *interval, **kwargs) + eg = 16*t**2/u * eg + + return eg \ No newline at end of file diff --git a/vayesta/solver/ccsd.py b/vayesta/solver/ccsd.py index 65119e0f9..b705a7dfc 100644 --- a/vayesta/solver/ccsd.py +++ b/vayesta/solver/ccsd.py @@ -4,7 +4,7 @@ import pyscf.cc from vayesta.core.types import CCSD_WaveFunction -from vayesta.core.util import dot, log_method, einsum +from vayesta.core.util import dot, log_method, log_time, einsum from vayesta.solver._uccsd_eris import uao2mo from vayesta.solver.cisd import CISD_Solver from vayesta.solver.solver import ClusterSolver, UClusterSolver @@ -53,8 +53,8 @@ def kernel(self, t1=None, t2=None, l1=None, l2=None, coupled_fragments=None, t_d t1, t2 = self.generate_init_guess() self.log.info("Solving CCSD-equations %s initial guess...", "with" if (t2 is not None) else "without") - - mycc.kernel(t1=t1, t2=t2) + with log_time(self.log.timing, "Time for T amplitudes: %s"): + mycc.kernel(t1=t1, t2=t2) self.converged = mycc.converged if t_diagnostic: @@ -63,7 +63,8 @@ def kernel(self, t1=None, t2=None, l1=None, l2=None, coupled_fragments=None, t_d self.print_extra_info(mycc) if self.opts.solve_lambda: - l1, l2 = mycc.solve_lambda(l1=l1, l2=l2) + with log_time(self.log.timing, "Time for lambda amplitudes: %s"): + l1, l2 = mycc.solve_lambda(l1=l1, l2=l2) self.converged = self.converged and mycc.converged_lambda else: self.log.info("Using Lambda=T approximation for Lambda-amplitudes.") @@ -81,24 +82,26 @@ def kernel(self, t1=None, t2=None, l1=None, l2=None, coupled_fragments=None, t_d self.log.info("Skipping in-cluster moment calculations") return self.log.info("Calculating in-cluster CCSD moments %s" % str(nmom)) - # expr = CCSD["1h"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) - # vecs_bra = expr.build_gf_vectors(nmom[0], left=True) - # amps_bra = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] - # vecs_ket = expr.build_gf_vectors(nmom[0], left=False) - # amps_ket = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] - # self.ip_moment_amplitudes = (amps_bra, amps_ket) - - # expr = CCSD["1p"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) - # vecs_bra = expr.build_gf_vectors(nmom[0], left=True) - # amps_bra = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] - # vecs_ket = expr.build_gf_vectors(nmom[0], left=False) - # amps_ket = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] - # self.ea_moment_amplitudes = (amps_bra, amps_ket) - - expr = CCSD["1h"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) - self.hole_moments = expr.build_gf_moments(nmom[0]) - expr = CCSD["1p"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) - self.particle_moments = expr.build_gf_moments(nmom[1]) + with log_time(self.log.timing, "Time for hole moments: %s"): + expr = CCSD["1h"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) + self.hole_moments = expr.build_gf_moments(nmom[0]) + + # vecs_bra = expr.build_gf_vectors(nmom[0], left=True) + # amps_bra = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] + # vecs_ket = expr.build_gf_vectors(nmom[0], left=False) + # amps_ket = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] + # self.ip_moment_amplitudes = (amps_bra, amps_ket) + + with log_time(self.log.timing, "Time for hole moments: %s"): + expr = CCSD["1p"](mf_clus, t1=mycc.t1, t2=mycc.t2, l1=l1, l2=l2) + self.particle_moments = expr.build_gf_moments(nmom[1]) + + # vecs_bra = expr.build_gf_vectors(nmom[0], left=True) + # amps_bra = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] + # vecs_ket = expr.build_gf_vectors(nmom[0], left=False) + # amps_ket = [expr.eom.vector_to_amplitudes(amps[n,p], ccm.nmo, ccm.nocc) for p in range(ccm.nmo) for n in range(nmom)] + # self.ea_moment_amplitudes = (amps_bra, amps_ket) + def get_solver_class(self, mf): if hasattr(mf, "with_df") and mf.with_df is not None: diff --git a/vayesta/solver/fci.py b/vayesta/solver/fci.py index 05740d6e4..8f2038f6d 100644 --- a/vayesta/solver/fci.py +++ b/vayesta/solver/fci.py @@ -88,11 +88,10 @@ def kernel(self, ci=None): self.log.error("Dyson not found - required for moment calculations") self.log.info("Skipping cluster moment calculations") return - self.log.info("Calculating in-cluster FCI moments %s" % str(nmom)) - + self.log.info("Calculating cluster FCI moments %s"%str(nmom)) mf_clus, frozen = self.hamil.to_pyscf_mf(allow_dummy_orbs=True, allow_df=True) - + with log_time(self.log.timing, "Time for hole moments: %s"): expr = FCI["1h"](mf_clus, e_ci=e_fci, c_ci=self.civec, h1e=heff, h2e=eris) self.hole_moments = expr.build_gf_moments(nmom[0]) @@ -100,7 +99,6 @@ def kernel(self, ci=None): expr = FCI["1p"](mf_clus, e_ci=e_fci, c_ci=self.civec, h1e=heff, h2e=eris) self.particle_moments = expr.build_gf_moments(nmom[1]) - class UFCI_Solver(UClusterSolver, FCI_Solver): @dataclasses.dataclass class Options(FCI_Solver.Options): diff --git a/vayesta/tests/core/qemb/test_self_energy.py b/vayesta/tests/core/qemb/test_self_energy.py new file mode 100644 index 000000000..4b3328259 --- /dev/null +++ b/vayesta/tests/core/qemb/test_self_energy.py @@ -0,0 +1,165 @@ +import unittest +import pytest + +import numpy as np + +import pyscf.ao2mo +import pyscf.pbc +import vayesta +import vayesta.ewf +from vayesta.core.util import cache +from vayesta.core.qemb.self_energy import make_self_energy_1proj, make_self_energy_2proj +from vayesta.tests import testsystems +from vayesta.tests.common import TestCase + + +class Test_SelfEnergy(TestCase): + @classmethod + def setUpClass(cls): + try: + import dyson + except ImportError: + pytest.skip("Requires dyson") + + def test_fci_hubbard1d_full_frag(self): + # RHF + mf = testsystems.hubb_10_u2.rhf() + + from dyson import MBLGF, MixedMBLGF, NullLogger + from dyson.expressions import FCI + + spectral_moment_order = (4,5) + + fci = FCI["1h"](mf) + th = fci.build_gf_moments(spectral_moment_order[0]) + + fci = FCI["1p"](mf) + tp = fci.build_gf_moments(spectral_moment_order[1]) + + solverh = MBLGF(th, log=NullLogger()) + solverp = MBLGF(tp, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se_fci = solver.get_self_energy() + + # Full bath EWF + ewf = vayesta.ewf.EWF( + mf, bath_options=dict(bathtype="full"), solver_options=dict( n_moments=spectral_moment_order), solver="FCI" + ) + with ewf.site_fragmentation() as f: + f.add_atomic_fragment(list(range(10))) + ewf.kernel() + + se_mom_order = 3 + + se1_ewf, se1_static_ewf, _ = make_self_energy_1proj(ewf, use_sym=False) + se2_ewf, se2_static_ewf, _ = make_self_energy_2proj(ewf, use_sym=False) + + se_mom_fci = [se_fci.moment(i) for i in range(se_mom_order)] + se_mom_ewf_1proj = [se1_ewf.moment(i) for i in range(se_mom_order)] + se_mom_ewf_2proj = [se2_ewf.moment(i) for i in range(se_mom_order)] + + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_1proj, atol=1e-5)) + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_2proj, atol=1e-5)) + + def test_fci_H6_full_bath(self): + # RHF + mf = testsystems.h6_sto6g.rhf() + try: + from dyson import MBLGF, MixedMBLGF, NullLogger + from dyson.expressions import FCI + except ImportError: + pytest.skip("Requires dyson") + + spectral_moment_order = (4,5) + + fci = FCI["1h"](mf) + th = fci.build_gf_moments(spectral_moment_order[0]) + + fci = FCI["1p"](mf) + tp = fci.build_gf_moments(spectral_moment_order[1]) + + solverh = MBLGF(th, log=NullLogger()) + solverp = MBLGF(tp, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se_fci = solver.get_self_energy() + se_static_fci = th[1] + tp[1] - mf.mo_coeff.T @ mf.get_fock() @ mf.mo_coeff + + # Full bath EWF + ewf = vayesta.ewf.EWF( + mf, bath_options=dict(bathtype="full"), solver_options=dict( n_moments=spectral_moment_order), solver="FCI" + ) + with ewf.site_fragmentation() as f: + f.add_all_atomic_fragments() + ewf.kernel() + + se_mom_order = 3 + + se1_ewf, se1_static_ewf, _ = make_self_energy_1proj(ewf, use_sym=False) + se1_ewf_sym, se1_static_ewf_sym, _ = make_self_energy_1proj(ewf, use_sym=True) + + se_mom_fci = [se_fci.moment(i) for i in range(se_mom_order)] + se_mom_ewf_1proj = [se1_ewf.moment(i) for i in range(se_mom_order)] + se_mom_ewf_1proj_sym = [se1_ewf_sym.moment(i) for i in range(se_mom_order)] + + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_1proj, atol=1e-5)) + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_1proj_sym, atol=1e-5)) + + + self.assertTrue(np.allclose(se1_static_ewf, se_static_fci, atol=1e-6)) + self.assertTrue(np.allclose(se1_static_ewf_sym, se_static_fci, atol=1e-6)) + + def test_fci_hubbard_full_bath(self): + # RHF + mf = testsystems.hubb_10_u2.rhf() + try: + from dyson import MBLGF, MixedMBLGF, NullLogger + from dyson.expressions import FCI + except ImportError: + pytest.skip("Requires dyson") + + spectral_moment_order = (4,5) + + fci = FCI["1h"](mf) + th = fci.build_gf_moments(spectral_moment_order[0]) + + fci = FCI["1p"](mf) + tp = fci.build_gf_moments(spectral_moment_order[1]) + + solverh = MBLGF(th, log=NullLogger()) + solverp = MBLGF(tp, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se_fci = solver.get_self_energy() + se_static_fci = th[1] + tp[1] - mf.mo_coeff.T @ mf.get_fock() @ mf.mo_coeff + + # Full bath EWF + ewf = vayesta.ewf.EWF( + mf, bath_options=dict(bathtype="full"), solver_options=dict( n_moments=spectral_moment_order), solver="FCI" + ) + nfrag = 2 + ewf.symmetry.set_translations([mf.mol.nsite//nfrag, 1, 1]) + with ewf.site_fragmentation() as f: + f.add_atomic_fragment(list(range(nfrag))) + ewf.kernel() + + se_mom_order = 3 + + se1_ewf, se1_static_ewf, _ = make_self_energy_1proj(ewf, use_sym=False) + se1_ewf_sym, se1_static_ewf_sym, _ = make_self_energy_1proj(ewf, use_sym=True) + + se_mom_fci = [se_fci.moment(i) for i in range(se_mom_order)] + se_mom_ewf_1proj = [se1_ewf.moment(i) for i in range(se_mom_order)] + se_mom_ewf_1proj_sym = [se1_ewf_sym.moment(i) for i in range(se_mom_order)] + + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_1proj, atol=1e-5)) + self.assertTrue(np.allclose(se_mom_fci, se_mom_ewf_1proj_sym, atol=1e-5)) + + + self.assertTrue(np.allclose(se1_static_ewf, se_static_fci, atol=1e-6)) + self.assertTrue(np.allclose(se1_static_ewf_sym, se_static_fci, atol=1e-6)) + +if __name__ == "__main__": + print("Running %s" % __file__) + unittest.main() diff --git a/vayesta/tests/core/scmf/test_qpewdmet.py b/vayesta/tests/core/scmf/test_qpewdmet.py new file mode 100644 index 000000000..b356cabb4 --- /dev/null +++ b/vayesta/tests/core/scmf/test_qpewdmet.py @@ -0,0 +1,126 @@ +import unittest +import pytest +import numpy as np +import vayesta +import vayesta.ewf +from vayesta.core.util import cache +from vayesta.tests import testsystems +from vayesta.tests.common import TestCase + + +class Test_FCI_H6_1Projector(TestCase): + nmom_fci = (4, 4) + proj = 1 + nfrag = 1 + @classmethod + def setUpClass(cls): + cls.mf = testsystems.h6_sto6g.rhf() + cls.run_dyson() + + @classmethod + def run_dyson(cls): + try: + from dyson import FCI, MBLGF, MixedMBLGF, NullLogger, AuxiliaryShift + except ImportError: + pytest.skip("Requires dyson") + cls.fci_ip = FCI["1h"](cls.mf) + cls.fci_ip_moms = cls.fci_ip.build_gf_moments(cls.nmom_fci[0]) + + cls.fci_ea = FCI["1p"](cls.mf) + cls.fci_ea_moms = cls.fci_ea.build_gf_moments(cls.nmom_fci[1]) + solverh = MBLGF(cls.fci_ip_moms, log=NullLogger()) + solverp = MBLGF(cls.fci_ea_moms, log=NullLogger()) + solver = MixedMBLGF(solverh, solverp) + solver.kernel() + se = solver.get_self_energy() + solver = AuxiliaryShift(cls.fci_ip_moms[1]+cls.fci_ea_moms[1], se, cls.mf.mol.nelectron) + solver.kernel() + cls.gf = solver.get_greens_function() + cls.se = solver.get_self_energy() + + @classmethod + def tearDownClass(cls): + del cls.mf + del cls.fci_ip + del cls.fci_ip_moms + del cls.fci_ea + del cls.fci_ea_moms + cls.emb.cache_clear() + + + @classmethod + @cache + def emb(cls): + emb = vayesta.ewf.EWF(cls.mf, bath_options=dict(bathtype='full'), solver_options=dict(n_moments=cls.nmom_fci), solver="FCI") + emb.qpewdmet_scmf(proj=cls.proj, maxiter=1) + with emb.iaopao_fragmentation() as f: + for i in range(0,cls.mf.mol.natm,cls.nfrag): + f.add_atomic_fragment(list(range(i,i+cls.nfrag))) + emb.kernel() + return emb + + #def test_energy(self): + #emb = self.emb() + #self.assertAllclose(emb.e_tot, self.fci_ip.e_ci, atol=1e-5, rtol=1e-4) + + def test_self_energy(self): + emb = self.emb() + n_se_mom = 4 + se_moms = np.array([self.se.moment(i) for i in range(n_se_mom)]) + emb_se_moms = np.array([emb.with_scmf.se_shifted.moment(i) for i in range(n_se_mom)]) + self.assertTrue(np.allclose(se_moms, emb_se_moms, atol=1e-4)) + + def test_static_self_energy(self): + emb = self.emb() + static_self_energy = self.fci_ip_moms[1] + self.fci_ea_moms[1] + emb_static_self_energy = self.mf.mo_coeff.T @ self.mf.get_fock() @ self.mf.mo_coeff + emb.with_scmf.static_self_energy + self.assertTrue(np.allclose(static_self_energy, emb_static_self_energy, atol=1e-4)) + + def test_static_potential(self): + emb = self.emb() + static_potential = self.se.as_static_potential(self.mf.mo_energy, eta=1e-2) + emb_static_potential = emb.with_scmf.se_shifted.as_static_potential(self.mf.mo_energy, eta=1e-2) + self.assertTrue(np.allclose(static_potential, emb_static_potential, atol=1e-4)) + + def test_greens_function(self): + emb = self.emb() + gf = emb.with_scmf.gf + gf_moms = np.array([gf.moment(i) for i in range(self.nmom_fci[0])]) + self.assertTrue(np.allclose(gf_moms, self.fci_ip_moms+self.fci_ea_moms, atol=1e-4)) + + def test_gap(self): + emb = self.emb() + gap = self.gf.physical().virtual().energies[0] - self.gf.physical().occupied().energies[-1] + emb_gap = emb.with_scmf.gf.physical().virtual().energies[0] - emb.with_scmf.gf.physical().occupied().energies[-1] + self.assertTrue(np.allclose(gap, emb_gap, atol=1e-4)) + + +class Test_FCI_H6_2Projector(Test_FCI_H6_1Projector): + proj = 2 + nfrag = 6 + +class Test_FCI_Hubbard10_1Projector(Test_FCI_H6_1Projector): + @classmethod + def setUpClass(cls): + cls.mf = testsystems.hubb_10_u2.rhf() + cls.run_dyson() + + @classmethod + @cache + def emb(cls): + emb = vayesta.ewf.EWF(cls.mf, bath_options=dict(bathtype='full'), solver_options=dict(n_moments=cls.nmom_fci), solver="FCI") + emb.qpewdmet_scmf(proj=cls.proj, use_sym=True, maxiter=1) + nimages = [cls.mf.mol.natm//cls.nfrag, 1, 1] + emb.symmetry.set_translations(nimages) + with emb.site_fragmentation() as f: + f.add_atomic_fragment(list(range(cls.nfrag))) + emb.kernel() + return emb + +class Test_FCI_Hubbard10_2Projector(Test_FCI_Hubbard10_1Projector): + proj = 2 + nfrag = 10 + +if __name__ == "__main__": + print("Running %s" % __file__) + unittest.main() diff --git a/vayesta/tests/ewf/test_moments.py b/vayesta/tests/ewf/test_moments.py deleted file mode 100644 index aab58ce7e..000000000 --- a/vayesta/tests/ewf/test_moments.py +++ /dev/null @@ -1,90 +0,0 @@ -import unittest -import pytest -import numpy as np - -import pyscf -import pyscf.cc - -import vayesta -import vayesta.ewf -from vayesta.tests.common import TestCase -from vayesta.tests import testsystems - - -class Test_RFCI(TestCase): - def test(self): - # RHF - mf = testsystems.h6_sto6g.rhf() - - try: - from dyson.expressions import FCI - except ImportError: - pytest.skip("Could not import Dyson. Skipping FCI moment tests.") - return - - fci = FCI["1h"](mf) - fci_ip = fci.build_gf_moments(4) - - fci = FCI["1p"](mf) - fci_ea = fci.build_gf_moments(4) - - # Full bath EWF - ewf = vayesta.ewf.EWF( - mf, bath_options=dict(bathtype="full"), solver_options=dict(n_moments=(4, 4)), solver="FCI" - ) - ewf.kernel() - - for f in ewf.fragments: - ip, ea = f.results.moms - - cx = f.get_overlap("mo|cluster") - ip = np.einsum("pP,qQ,nPQ->npq", cx, cx, ip) - ea = np.einsum("pP,qQ,nPQ->npq", cx, cx, ea) - - self.assertTrue(np.allclose(ip, fci_ip, atol=1e-7)) - self.assertTrue(np.allclose(ea, fci_ea, atol=1e-7)) - - -# class Test_RCCSD(TestCase): - -# def test(self): - -# #RHF -# mf = testsystems.water_sto3g.rhf() - -# try: -# from dyson.expressions import CCSD -# except ImportError: -# pytest.skip("Could not import Dyson. Skipping CCSD moment tests.") -# return - -# cc = CCSD["1h"](mf) -# cc_ip = cc.build_gf_moments(4) - -# cc = CCSD["1p"](mf) -# cc_ea = cc.build_gf_moments(4) - -# #Full bath EWF -# ewf = vayesta.ewf.EWF(mf, bath_type='full', solver_options=dict(n_moments=(4,4)), solver='CCSD') -# ewf.kernel() - -# for f in ewf.fragments: -# ip, ea = f.results.moms - -# cx = f.get_overlap('mo|cluster') -# ip = np.einsum('pP,qQ,nPQ->npq', cx, cx, ip) -# ea = np.einsum('pP,qQ,nPQ->npq', cx, cx, ea) - -# for i in range(len(ip)): -# print(i, np.trace(np.dot(ip[i], ip[i])), np.trace(np.dot(cc_ip[i], cc_ip[i])), abs(np.trace(np.dot(ip[i], ip[i])) - np.trace(np.dot(cc_ip[i], cc_ip[i])))) -# print('Norm %f'%np.linalg.norm(ip- cc_ip)) -# print('IP %f'%np.linalg.norm(ea- cc_ea)) -# self.assertAlmostEqual(np.linalg.norm(ip- cc_ip), 0.0) -# self.assertAlmostEqual(np.linalg.norm(ea- cc_ea), 0.0) - -# # self.assertTrue(np.allclose(ip, cc_ip)) -# # self.assertTrue(np.allclose(ea, cc_ea)) - -if __name__ == "__main__": - print("Running %s" % __file__) - unittest.main() diff --git a/vayesta/tests/ewf/test_rdm_energy.py b/vayesta/tests/ewf/test_rdm_energy.py index 7870c980c..50446029a 100644 --- a/vayesta/tests/ewf/test_rdm_energy.py +++ b/vayesta/tests/ewf/test_rdm_energy.py @@ -9,13 +9,12 @@ class Test_RHF(TestCase): - def test(self): + def test_h2o(self): # RHF mf = testsystems.water_631g.rhf() # CCSD - cc = pyscf.cc.CCSD(mf) - cc.kernel() + cc = testsystems.water_631g.rccsd() # Full bath EWF ewf = vayesta.ewf.EWF(mf, bath_options=dict(bathtype="full"), solver_options=dict(solve_lambda=True)) @@ -32,16 +31,36 @@ def test(self): self.assertAlmostEqual(gg, cc.e_corr) self.assertAlmostEqual(ewf.get_dm_energy(), cc.e_tot) + def test_h2_solid(self): + + #RHF + mf = testsystems.h2_sto3g_k311.rhf() + + #CCSD + cc = testsystems.h2_sto3g_k311.rccsd() + + #Full bath EWF + ewf = vayesta.ewf.EWF(mf, bath_options=dict(bathtype="full"), solver_options=dict(solve_lambda=True)) + ewf.kernel() + + ll = ewf._get_dm_corr_energy_old(global_dm1=False, global_dm2=False) + gl = ewf._get_dm_corr_energy_old(global_dm1=True, global_dm2=False) + lg = ewf._get_dm_corr_energy_old(global_dm1=False, global_dm2=True) + gg = ewf._get_dm_corr_energy_old(global_dm1=True, global_dm2=True) + + self.assertAlmostEqual(ll, cc.e_corr) + self.assertAlmostEqual(gl, cc.e_corr) + self.assertAlmostEqual(lg, cc.e_corr) + self.assertAlmostEqual(gg, cc.e_corr) + class Test_UHF(TestCase): - def test(self): + def test_h2o(self): # RHF mf = testsystems.water_cation_631g.uhf() # CCSD - cc = pyscf.cc.UCCSD(mf) - cc.kernel() - + cc = testsystems.water_cation_631g.uccsd() # Full bath EWF ewf = vayesta.ewf.EWF(mf, bath_options=dict(bathtype="full"), solver_options=dict(solve_lambda=True)) ewf.kernel() @@ -57,29 +76,28 @@ def test(self): self.assertAlmostEqual(gg, cc.e_corr) self.assertAlmostEqual(ewf.get_dm_energy(), cc.e_tot) + # EXXDIV maybe? + # def test_h2_solid(self): + + # #RHF + # mf = testsystems.h2_sto3g_k311.uhf() + + # #CCSD + # cc = testsystems.h2_sto3g_k311.uccsd() + + # #Full bath EWF + # ewf = vayesta.ewf.EWF(mf, bath_options=dict(bathtype="full"), solver_options=dict(solve_lambda=True)) + # ewf.kernel() + + # ll = ewf._get_dm_corr_energy_old(global_dm1=False, global_dm2=False) + # gl = ewf._get_dm_corr_energy_old(global_dm1=True, global_dm2=False) + # lg = ewf._get_dm_corr_energy_old(global_dm1=False, global_dm2=True) + # gg = ewf._get_dm_corr_energy_old(global_dm1=True, global_dm2=True) -# def test_h2_solid(self): -# -# #RHF -# mf = testsystems.h2_sto3g_331_2d.rhf() -# -# #CCSD -# cc = pyscf.cc.CCSD(mf) -# cc.kernel() -# -# #Full bath EWF -# ewf = vayesta.ewf.EWF(mf, bno_threshold=-1) -# ewf.kernel() -# -# ll = ewf.get_rdm2_corr_energy(global_dm1=False, global_dm2=False) -# gl = ewf.get_rdm2_corr_energy(global_dm1=True, global_dm2=False) -# lg = ewf.get_rdm2_corr_energy(global_dm1=False, global_dm2=True) -# gg = ewf.get_rdm2_corr_energy(global_dm1=True, global_dm2=True) -# -# self.assertAlmostEqual(ll, cc.e_corr) -# self.assertAlmostEqual(gl, cc.e_corr) -# self.assertAlmostEqual(lg, cc.e_corr) -# self.assertAlmostEqual(gg, cc.e_corr) + # self.assertAlmostEqual(ll, cc.e_corr) + # self.assertAlmostEqual(gl, cc.e_corr) + # self.assertAlmostEqual(lg, cc.e_corr) + # self.assertAlmostEqual(gg, cc.e_corr) if __name__ == "__main__": diff --git a/vayesta/tests/solver/test_moments.py b/vayesta/tests/solver/test_moments.py new file mode 100644 index 000000000..6092dc3d1 --- /dev/null +++ b/vayesta/tests/solver/test_moments.py @@ -0,0 +1,79 @@ +import unittest +import pytest +import numpy as np + +import pyscf.cc + +import vayesta +import vayesta.ewf +from vayesta.tests.common import TestCase +from vayesta.tests import testsystems + + +class Test_Spectral_Moments(TestCase): + @classmethod + def setUpClass(cls): + try: + import dyson + except ImportError: + pytest.skip("Requires dyson") + + def test_fci(self): + # RHF + mf = testsystems.h6_sto6g.rhf() + + from dyson.expressions import FCI + + fci = FCI["1h"](mf) + fci_ip = fci.build_gf_moments(4) + + fci = FCI["1p"](mf) + fci_ea = fci.build_gf_moments(4) + + # Full bath EWF + ewf = vayesta.ewf.EWF( + mf, bath_options=dict(bathtype="full"), solver_options=dict( n_moments=(4, 4)), solver="FCI" + ) + ewf.kernel() + + for f in ewf.fragments: + ip, ea = f.results.moms + + cx = f.get_overlap("mo|cluster") + ip = np.einsum("pP,qQ,nPQ->npq", cx, cx, ip) + ea = np.einsum("pP,qQ,nPQ->npq", cx, cx, ea) + + self.assertTrue(np.allclose(ip, fci_ip)) + self.assertTrue(np.allclose(ea, fci_ea)) + + def test_ccsd(self): + + #RHF + mf = testsystems.water_sto3g.rhf() + + from dyson.expressions import CCSD + + cc = CCSD["1h"](mf) + cc_ip = cc.build_gf_moments(4) + + cc = CCSD["1p"](mf) + cc_ea = cc.build_gf_moments(4) + + #Full bath EWF + ewf = vayesta.ewf.EWF(mf, bath_options=dict(bathtype='full'), solver_options=dict(n_moments=(4,4)), solver='CCSD') + ewf.kernel() + + for f in ewf.fragments: + ip, ea = f.results.moms + + cx = f.get_overlap('mo|cluster') + ip = np.einsum('pP,qQ,nPQ->npq', cx, cx, ip) + ea = np.einsum('pP,qQ,nPQ->npq', cx, cx, ea) + + # High tolerence for github CI + self.assertTrue(np.allclose(ip, cc_ip, atol=1e-3)) + self.assertTrue(np.allclose(ea, cc_ea, atol=1e-6)) + +if __name__ == "__main__": + print("Running %s" % __file__) + unittest.main()