def solve(frag, guess_1RDM, chempot_imp): # Augment OEI with the chemical potential OEI = frag.impham_OEI_C - chempot_imp # Get the RHF solution mol = gto.Mole() mol.spin = int(round(2 * frag.target_MS)) mol.verbose = 0 if frag.mol_output is None else 4 mol.output = frag.mol_output mol.build() mol.atom.append(('H', (0, 0, 0))) mol.nelectron = frag.nelec_imp #mol.incore_anyway = True #mf.get_hcore = lambda *args: OEI #mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) #mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) h1e = OEI eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) ed = fci.FCI(mol, singlet=(frag.target_S == 0)) if frag.target_S != 0: s2_eval = frag.target_S * (frag.target_S + 1) fix_spin_(ed, ss=s2_eval) # Guess vector ci = None if len(frag.imp_cache) == 1: ci = frag.imp_cache[0] print("Taking initial ci vector from cache") t_start = time.time() ed.conv_tol = 1e-12 E_FCI, ci = ed.kernel(h1e, eri, frag.norbs_imp, frag.nelec_imp, ci0=ci) assert (ed.converged) frag.imp_cache = [ci] t_end = time.time() print( 'Impurity FCI energy (incl chempot): {}; spin multiplicity: {}; time to solve: {}' .format(frag.impham_CONST + E_FCI, ed.spin_square(ci, frag.norbs_imp, frag.nelec_imp)[1], t_end - t_start)) # oneRDM and twoCDM oneRDM_imp, twoRDM_imp = ed.make_rdm12(ci, frag.norbs_imp, frag.nelec_imp) oneRDMs_imp = ed.make_rdm1s(ci, frag.norbs_imp, frag.nelec_imp) twoCDM_imp = get_2CDM_from_2RDM(twoRDM_imp, oneRDMs_imp) # General impurity data frag.oneRDM_loc = symmetrize_tensor( frag.oneRDMfroz_loc + represent_operator_in_basis(oneRDM_imp, frag.imp2loc)) frag.twoCDM_imp = symmetrize_tensor(twoCDM_imp) frag.E_imp = frag.impham_CONST + E_FCI + np.einsum('ab,ab->', chempot_imp, oneRDM_imp) return None
def __init__(self, mc): oneRDMs = mc.make_rdm1s () casdm1s = mc.fcisolver.make_rdm1s (mc.ci, mc.ncas, mc.nelecas) casdm1, casdm2 = mc.fcisolver.make_rdm12 (mc.ci, mc.ncas, mc.nelecas) twoCDM = get_2CDM_from_2RDM (casdm2, casdm1s) ao2amo = mc.mo_coeff[:,mc.ncore:][:,:mc.ncas] self.calculator = HessianCalculator (mc._scf, oneRDMs, twoCDM, ao2amo) self.cas = mc self.cas_mo = mc.mo_coeff self.ncore, self.ncas, self.nelecas = mc.ncore, mc.ncas, mc.nelecas self.nocc = self.ncore + self.ncas self.nmo = self.cas_mo.shape[1] self.hop, self.hdiag = gen_g_hop (mc, self.cas_mo, 1, casdm1, casdm2, mc.ao2mo (self.cas_mo))[2:]
def load_amo_from_aobasis(self, ao2amo, dm, twoRDM=None, twoCDM=None): print("Attempting to load the provided active orbitals to fragment {}". format(self.frag_name)) self.loc2amo = reduce( np.dot, (self.ints.ao2loc.conjugate().T, self.ints.ao_ovlp, ao2amo)) self.oneRDMas_loc = represent_operator_in_basis( dm, self.loc2amo.conjugate().T) if twoRDM is not None: self.twoCDMimp_amo = get_2CDM_from_2RDM(twoRDM, dm) elif twoCDM is not None: self.twoCDMimp_amo = twoCDM else: self.twoCDMimp_amo = np.zeros([self.norbs_as for i in range(4)])
def _get_e_decomp (mc, ot, mo_coeff, ci, e_mcscf, e_nuc, h, xfnal, cfnal): ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas _rdms = mcscf.CASCI (mc._scf, ncas, nelecas) _rdms.fcisolver = fci.solver (mc._scf.mol, singlet = False, symm = False) _rdms.mo_coeff = mo_coeff _rdms.ci = ci _casdms = _rdms.fcisolver dm1s = np.stack (_rdms.make_rdm1s (), axis=0) dm1 = dm1s[0] + dm1s[1] j = _rdms._scf.get_j (dm=dm1) e_core = np.tensordot (h, dm1, axes=2) e_coul = np.tensordot (j, dm1, axes=2) / 2 adm1s = np.stack (_casdms.make_rdm1s (ci, ncas, nelecas), axis=0) adm2 = get_2CDM_from_2RDM (_casdms.make_rdm12 (_rdms.ci, ncas, nelecas)[1], adm1s) mo_cas = mo_coeff[:,ncore:][:,:ncas] e_otx = get_E_ot (xfnal, dm1s, adm2, mo_cas, max_memory=mc.max_memory) e_otc = get_E_ot (cfnal, dm1s, adm2, mo_cas, max_memory=mc.max_memory) e_wfnxc = e_mcscf - e_nuc - e_core - e_coul return e_core, e_coul, e_otx, e_otc, e_wfnxc
def _get_e_decomp(mc, ot, mo_coeff, ci, e_mcscf, e_nuc, h, xfnal, cfnal): mc_1root = mcscf.CASCI(mc._scf, mc.ncas, mc.nelecas) mc_1root.fcisolver = fci.solver(mc._scf.mol, singlet=False, symm=False) mc_1root.mo_coeff = mo_coeff mc_1root.ci = ci dm1s = np.stack(mc_1root.make_rdm1s(), axis=0) dm1 = dm1s[0] + dm1s[1] j = mc_1root._scf.get_j(dm=dm1) e_core = np.tensordot(h, dm1, axes=2) e_coul = np.tensordot(j, dm1, axes=2) / 2 adm1s = np.stack(mc_1root.fcisolver.make_rdm1s(ci, mc.ncas, mc.nelecas), axis=0) adm2 = get_2CDM_from_2RDM( mc_1root.fcisolver.make_rdm12(mc_1root.ci, mc.ncas, mc.nelecas)[1], adm1s) mo_cas = mo_coeff[:, mc.ncore:][:, :mc.ncas] e_otx = get_E_ot(xfnal, dm1s, adm2, mo_cas) e_otc = get_E_ot(cfnal, dm1s, adm2, mo_cas) e_wfnxc = e_mcscf - e_nuc - e_core - e_coul return e_core, e_coul, e_otx, e_otc, e_wfnxc
def get_fragcasscf(frag, mf, loc2mo): ''' Obsolete function, left here just for reference to how I use constrCASSCF object ''' norbs_amo = frag.active_space[1] nelec_amo = frag.active_space[0] mo = np.dot(frag.imp2loc, loc2mo) mf2 = hf_as.RHF(mf.mol) mf2.wo_coeff = np.eye(mf.get_ovlp().shape[0]) mf2.get_hcore = lambda *args: mf.get_hcore() mf2.get_ovlp = lambda *args: mf.get_ovlp() mf2._eri = mf._eri mf2.scf(mf.make_rdm1()) mc = my_mcscf.constrCASSCF(mf2, norbs_amo, nelec_amo, cas_ao=list(range(frag.norbs_frag))) norbs_cmo = mc.ncore norbs_occ = norbs_cmo + norbs_amo t_start = time.time() E_fragCASSCF = mc.kernel(mo)[0] E_fragCASSCF = mc.kernel()[0] t_end = time.time() assert (mc.converged) print('Impurity fragCASSCF energy (incl chempot): {0}; time to solve: {1}'. format(frag.impham_CONST + E_fragCASSCF, t_end - t_start)) casci = mcscf.CASCI(mf2, norbs_amo, nelec_amo) E_testfragCASSCF = casci.kernel(mc.mo_coeff)[0] assert (abs(E_testfragCASSCF - E_fragCASSCF) < 1e-8), E_testfragCASSCF loc2mo = np.dot(frag.loc2imp, mc.mo_coeff) loc2amo = loc2mo[:, norbs_cmo:norbs_occ] oneRDM_amo, twoRDM_amo = mc.fcisolver.make_rdm12(mc.ci, norbs_amo, nelec_amo) oneRDMs_amo = mc.fcisolver.make_rdm1s(mc.ci, mc.ncas, mc.nelecas) twoCDM_amo = get_2CDM_from_2RDM(twoRDM_amo, oneRDMs_amo) return oneRDM_amo, twoCDM_amo, loc2mo, loc2amo
def solve(frag, guess_1RDM, chempot_imp): # Augment OEI with the chemical potential OEI = frag.impham_OEI_C - chempot_imp # Get the RHF solution mol = gto.Mole() mol.build(verbose=0) mol.atom.append(('C', (0, 0, 0))) mol.nelectron = frag.nelec_imp mol.incore_anyway = True mf = scf.RHF(mol) mf.get_hcore = lambda *args: OEI mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) mf.scf(guess_1RDM) DMloc = np.dot(np.dot(mf.mo_coeff, np.diag(mf.mo_occ)), mf.mo_coeff.T) if (mf.converged == False): mf = mf.newton() mf.kernel() # Get the MP2 solution mp2 = mp.MP2(mf) mp2.kernel() imp2mo = mf.mo_coeff mo2imp = imp2mo.conjugate().T oneRDMimp_imp = mf.make_rdm1() twoRDMimp_mo = mp2.make_rdm2() twoRDMimp_imp = represent_operator_in_basis(twoRDMimp_mo, mo2imp) twoCDM_imp = get_2CDM_from_2RDM(twoRDMimp_imp, oneRDMimp_imp) # General impurity data frag.oneRDM_loc = symmetrize_tensor( frag.oneRDMfroz_loc + represent_operator_in_basis(oneRDMimp_imp, frag.imp2loc)) frag.twoCDM_imp = symmetrize_tensor(twoCDM_imp) frag.E_imp = frag.impham_CONST + mp2.e_tot + np.einsum( 'ab,ab->', oneRDMimp_imp, chempot_imp) return None
def kernel(mc, ot, root=-1): ''' Calculate MC-PDFT total energy Args: mc : an instance of CASSCF or CASCI class Note: this function does not currently run the CASSCF or CASCI calculation itself prior to calculating the MC-PDFT energy. Call mc.kernel () before passing to this function! ot : an instance of on-top density functional class - see otfnal.py Kwargs: root : int If mc describes a state-averaged calculation, select the root (0-indexed) Negative number requests state-averaged MC-PDFT results (i.e., using state-averaged density matrices) Returns: Total MC-PDFT energy including nuclear repulsion energy. ''' t0 = (time.clock(), time.time()) amo = mc.mo_coeff[:, mc.ncore:mc.ncore + mc.ncas] # make_rdm12s returns (a, b), (aa, ab, bb) mc_1root = mc if isinstance(mc.ci, list) and root >= 0: mc_1root = mcscf.CASCI(mc._scf, mc.ncas, mc.nelecas) mc_1root.fcisolver = fci.solver(mc._scf.mol, singlet=False, symm=False) mc_1root.mo_coeff = mc.mo_coeff mc_1root.ci = mc.ci[root] mc_1root.e_tot = mc.e_tot dm1s = np.asarray(mc_1root.make_rdm1s()) adm1s = np.stack(mc_1root.fcisolver.make_rdm1s(mc_1root.ci, mc.ncas, mc.nelecas), axis=0) adm2 = get_2CDM_from_2RDM( mc_1root.fcisolver.make_rdm12(mc_1root.ci, mc.ncas, mc.nelecas)[1], adm1s) if ot.verbose >= logger.DEBUG: adm2s = get_2CDMs_from_2RDMs( mc_1root.fcisolver.make_rdm12s(mc_1root.ci, mc.ncas, mc.nelecas)[1], adm1s) adm2s_ss = adm2s[0] + adm2s[2] adm2s_os = adm2s[1] spin = abs(mc.nelecas[0] - mc.nelecas[1]) t0 = logger.timer(ot, 'rdms', *t0) omega, alpha, hyb = ot._numint.rsh_and_hybrid_coeff(ot.otxc, spin=spin) Vnn = mc._scf.energy_nuc() h = mc._scf.get_hcore() dm1 = dm1s[0] + dm1s[1] if ot.verbose >= logger.DEBUG or abs(hyb) > 1e-10: vj, vk = mc._scf.get_jk(dm=dm1s) vj = vj[0] + vj[1] else: vj = mc._scf.get_j(dm=dm1) Te_Vne = np.tensordot(h, dm1) # (vj_a + vj_b) * (dm_a + dm_b) E_j = np.tensordot(vj, dm1) / 2 # (vk_a * dm_a) + (vk_b * dm_b) Mind the difference! if ot.verbose >= logger.DEBUG or abs(hyb) > 1e-10: E_x = -(np.tensordot(vk[0], dm1s[0]) + np.tensordot(vk[1], dm1s[1])) / 2 else: E_x = 0 logger.debug(ot, 'CAS energy decomposition:') logger.debug(ot, 'Vnn = %s', Vnn) logger.debug(ot, 'Te + Vne = %s', Te_Vne) logger.debug(ot, 'E_j = %s', E_j) logger.debug(ot, 'E_x = %s', E_x) if ot.verbose >= logger.DEBUG: # g_pqrs * l_pqrs / 2 #if ot.verbose >= logger.DEBUG: aeri = ao2mo.restore(1, mc.get_h2eff(mc.mo_coeff), mc.ncas) E_c = np.tensordot(aeri, adm2, axes=4) / 2 E_c_ss = np.tensordot(aeri, adm2s_ss, axes=4) / 2 E_c_os = np.tensordot(aeri, adm2s_os, axes=4) # ab + ba -> factor of 2 logger.info(ot, 'E_c = %s', E_c) logger.info(ot, 'E_c (SS) = %s', E_c_ss) logger.info(ot, 'E_c (OS) = %s', E_c_os) e_err = E_c_ss + E_c_os - E_c assert (abs(e_err) < 1e-8), e_err if isinstance(mc_1root.e_tot, float): e_err = mc_1root.e_tot - (Vnn + Te_Vne + E_j + E_x + E_c) assert (abs(e_err) < 1e-8), e_err if abs(hyb) > 1e-10: logger.debug(ot, 'Adding %s * %s CAS exchange to E_ot', hyb, E_x) t0 = logger.timer(ot, 'Vnn, Te, Vne, E_j, E_x', *t0) E_ot = get_E_ot(ot, dm1s, adm2, amo) t0 = logger.timer(ot, 'E_ot', *t0) e_tot = Vnn + Te_Vne + E_j + (hyb * E_x) + E_ot logger.info(ot, 'MC-PDFT E = %s, Eot(%s) = %s', e_tot, ot.otxc, E_ot) return e_tot, E_ot
def get_pdft_veff(self, mo=None, ci=None, incl_coul=False, paaa_only=False): ''' Get the 1- and 2-body MC-PDFT effective potentials for a set of mos and ci vectors Kwargs: mo : ndarray of shape (nao,nmo) A full set of molecular orbital coefficients. Taken from self if not provided ci : list or ndarray CI vectors. Taken from self if not provided incl_coul : logical If true, includes the Coulomb repulsion energy in the 1-body effective potential. In practice they always appear together. paaa_only : logical If true, only the paaa 2-body effective potential elements are evaluated; the rest of ppaa are filled with zeros. Returns: veff1 : ndarray of shape (nao, nao) 1-body effective potential in the AO basis May include classical Coulomb potential term (see incl_coul kwarg) veff2 : pyscf.mcscf.mc_ao2mo._ERIS instance Relevant 2-body effective potential in the MO basis ''' t0 = (time.clock(), time.time()) if mo is None: mo = self.mo_coeff if ci is None: ci = self.ci # If ci is not a list and mc is a state-average solver, use a different fcisolver for make_rdm mc_1root = self if isinstance(self, StateAverageMCSCFSolver) and not isinstance( ci, list): mc_1root = mcscf.CASCI(self._scf, self.ncas, self.nelecas) mc_1root.fcisolver = fci.solver(self._scf.mol, singlet=False, symm=False) mc_1root.mo_coeff = mo mc_1root.ci = ci mc_1root.e_tot = self.e_tot dm1s = np.asarray(mc_1root.make_rdm1s()) adm1s = np.stack(mc_1root.fcisolver.make_rdm1s( ci, self.ncas, self.nelecas), axis=0) adm2 = get_2CDM_from_2RDM( mc_1root.fcisolver.make_rdm12(ci, self.ncas, self.nelecas)[1], adm1s) mo_cas = mo[:, self.ncore:][:, :self.ncas] pdft_veff1, pdft_veff2 = pdft_veff.kernel( self.otfnal, adm1s, adm2, mo, self.ncore, self.ncas, max_memory=self.max_memory, paaa_only=paaa_only) if self.verbose > logger.DEBUG: logger.debug( self, 'Warning: memory-intensive lazy kernel for pdft_veff initiated for ' 'testing purposes; reduce verbosity to decrease memory footprint' ) pdft_veff1_test, _pdft_veff2_test = pdft_veff.lazy_kernel( self.otfnal, dm1s, adm2, mo_cas) old_eri = self._scf._eri self._scf._eri = _pdft_veff2_test with temporary_env(self.mol, incore_anyway=True): pdft_veff2_test = mc_ao2mo._ERIS(self, mo, method='incore') self._scf._eri = old_eri err = linalg.norm(pdft_veff1 - pdft_veff1_test) logger.debug(self, 'veff1 error: {}'.format(err)) err = linalg.norm(pdft_veff2.vhf_c - pdft_veff2_test.vhf_c) logger.debug(self, 'veff2.vhf_c error: {}'.format(err)) err = linalg.norm(pdft_veff2.papa - pdft_veff2_test.papa) logger.debug(self, 'veff2.ppaa error: {}'.format(err)) err = linalg.norm(pdft_veff2.papa - pdft_veff2_test.papa) logger.debug(self, 'veff2.papa error: {}'.format(err)) err = linalg.norm(pdft_veff2.j_pc - pdft_veff2_test.j_pc) logger.debug(self, 'veff2.j_pc error: {}'.format(err)) err = linalg.norm(pdft_veff2.k_pc - pdft_veff2_test.k_pc) logger.debug(self, 'veff2.k_pc error: {}'.format(err)) if incl_coul: pdft_veff1 += self.get_jk(self.mol, dm1s[0] + dm1s[1])[0] logger.timer(self, 'get_pdft_veff', *t0) return pdft_veff1, pdft_veff2
def get_pdft_veff (self, mo=None, ci=None, casdm1s=None, casdm2=None, incl_coul=False, paaa_only=False, aaaa_only=False): ''' Get the 1- and 2-body MC-PDFT effective potentials for a set of mos and ci vectors Kwargs: mo : ndarray of shape (nao,nmo) A full set of molecular orbital coefficients. Taken from self if not provided ci : list or ndarray CI vectors. Taken from self if not provided casdm1s : ndarray of shape (2,ncas,ncas) Spin-separated 1-RDM in the active space. Overrides CI if and only if both this and casdm2 are provided casdm2 : ndarray of shape (ncas,ncas,ncas,ncas) 2-RDM in the active space. Overrides CI if and only if both this and casdm1s are provided incl_coul : logical If true, includes the Coulomb repulsion energy in the 1-body effective potential. In practice they always appear together. paaa_only : logical If true, only the paaa 2-body effective potential elements are evaluated; the rest of ppaa are filled with zeros. aaaa_only : logical If true, only the aaaa 2-body effective potential elements are evaluated; the rest of ppaa are filled with zeros. Returns: veff1 : ndarray of shape (nao, nao) 1-body effective potential in the AO basis May include classical Coulomb potential term (see incl_coul kwarg) veff2 : pyscf.mcscf.mc_ao2mo._ERIS instance Relevant 2-body effective potential in the MO basis ''' t0 = (time.process_time (), time.time ()) if mo is None: mo = self.mo_coeff if ci is None: ci = self.ci ncore, ncas, nelecas = self.ncore, self.ncas, self.nelecas nocc = ncore + ncas if (casdm1s is not None) and (casdm2 is not None): mo_core = mo[:,:ncore] mo_cas = mo[:,ncore:nocc] dm1s = np.dot (mo_cas, casdm1s).transpose (1,0,2) dm1s = np.dot (dm1s, mo_cas.conj ().T) dm1s += (mo_core @ mo_core.conj ().T)[None,:,:] adm1s = casdm1s adm2 = get_2CDM_from_2RDM (casdm2, casdm1s) else: dm_list = self.make_rdms_mcpdft (mo_coeff=mo, ci=ci) dm1s, (adm1s, (adm2, _ss, _os)) = dm_list mo_cas = mo[:,ncore:][:,:ncas] pdft_veff1, pdft_veff2 = pdft_veff.kernel (self.otfnal, adm1s, adm2, mo, ncore, ncas, max_memory=self.max_memory, paaa_only=paaa_only, aaaa_only=aaaa_only) if self.verbose > logger.DEBUG: logger.debug (self, 'Warning: memory-intensive lazy kernel for pdft_veff initiated for ' 'testing purposes; reduce verbosity to decrease memory footprint') pdft_veff1_test, _pdft_veff2_test = pdft_veff.lazy_kernel (self.otfnal, dm1s, adm2, mo_cas) old_eri = self._scf._eri self._scf._eri = _pdft_veff2_test with temporary_env (self.mol, incore_anyway=True): pdft_veff2_test = mc_ao2mo._ERIS (self, mo, method='incore') self._scf._eri = old_eri err = linalg.norm (pdft_veff1 - pdft_veff1_test) logger.debug (self, 'veff1 error: {}'.format (err)) err = linalg.norm (pdft_veff2.vhf_c - pdft_veff2_test.vhf_c) logger.debug (self, 'veff2.vhf_c error: {}'.format (err)) err = linalg.norm (pdft_veff2.papa - pdft_veff2_test.papa) logger.debug (self, 'veff2.ppaa error: {}'.format (err)) err = linalg.norm (pdft_veff2.papa - pdft_veff2_test.papa) logger.debug (self, 'veff2.papa error: {}'.format (err)) err = linalg.norm (pdft_veff2.j_pc - pdft_veff2_test.j_pc) logger.debug (self, 'veff2.j_pc error: {}'.format (err)) err = linalg.norm (pdft_veff2.k_pc - pdft_veff2_test.k_pc) logger.debug (self, 'veff2.k_pc error: {}'.format (err)) if incl_coul: pdft_veff1 += self._scf.get_j (self.mol, dm1s[0] + dm1s[1]) logger.timer (self, 'get_pdft_veff', *t0) return pdft_veff1, pdft_veff2
def solve (frag, guess_1RDM, chempot_imp): # Augment OEI with the chemical potential OEI = frag.impham_OEI_C - chempot_imp # Do I need to get the full RHF solution? guess_orbs_av = len (frag.imp_cache) == 2 or frag.norbs_as > 0 # Get the RHF solution mol = gto.Mole() abs_2MS = int (round (2 * abs (frag.target_MS))) abs_2S = int (round (2 * abs (frag.target_S))) sign_MS = int (np.sign (frag.target_MS)) or 1 mol.spin = abs_2MS mol.verbose = 0 if frag.mol_stdout is None: mol.output = frag.mol_output mol.verbose = 0 if frag.mol_output is None else lib.logger.DEBUG mol.atom.append(('H', (0, 0, 0))) mol.nelectron = frag.nelec_imp if frag.enforce_symmetry: mol.groupname = frag.symmetry mol.symm_orb = get_subspace_symmetry_blocks (frag.loc2imp, frag.loc2symm) mol.irrep_name = frag.ir_names mol.irrep_id = frag.ir_ids mol.max_memory = frag.ints.max_memory mol.build () if frag.mol_stdout is None: frag.mol_stdout = mol.stdout else: mol.stdout = frag.mol_stdout mol.verbose = 0 if frag.mol_output is None else lib.logger.DEBUG if frag.enforce_symmetry: mol.symmetry = True #mol.incore_anyway = True mf = scf.RHF(mol) mf.get_hcore = lambda *args: OEI mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) mf.energy_nuc = lambda *args: frag.impham_CONST if frag.impham_CDERI is not None: mf = mf.density_fit () mf.with_df._cderi = frag.impham_CDERI else: mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) mf = fix_my_RHF_for_nonsinglet_env (mf, frag.impham_OEI_S) mf.__dict__.update (frag.mf_attr) if guess_orbs_av: mf.max_cycle = 2 mf.scf (guess_1RDM) if (not mf.converged) and (not guess_orbs_av): if np.any (np.abs (frag.impham_OEI_S) > 1e-8) and mol.spin != 0: raise NotImplementedError('Gradient and Hessian fixes for nonsinglet environment of Newton-descent ROHF algorithm') print ("CASSCF RHF-step not converged on fixed-point iteration; initiating newton solver") mf = mf.newton () mf.kernel () # Instability check and repeat if not guess_orbs_av: for i in range (frag.num_mf_stab_checks): if np.any (np.abs (frag.impham_OEI_S) > 1e-8) and mol.spin != 0: raise NotImplementedError('ROHF stability-check fixes for nonsinglet environment') mf.mo_coeff = mf.stability ()[0] guess_1RDM = mf.make_rdm1 () mf = scf.RHF(mol) mf.get_hcore = lambda *args: OEI mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) mf = fix_my_RHF_for_nonsinglet_env (mf, frag.impham_OEI_S) mf.scf (guess_1RDM) if not mf.converged: mf = mf.newton () mf.kernel () E_RHF = mf.e_tot print ("CASSCF RHF-step energy: {}".format (E_RHF)) # Get the CASSCF solution CASe = frag.active_space[0] CASorb = frag.active_space[1] checkCAS = (CASe <= frag.nelec_imp) and (CASorb <= frag.norbs_imp) if (checkCAS == False): CASe = frag.nelec_imp CASorb = frag.norbs_imp if (abs_2MS > abs_2S): CASe = ((CASe + sign_MS * abs_2S) // 2, (CASe - sign_MS * abs_2S) // 2) else: CASe = ((CASe + sign_MS * abs_2MS) // 2, (CASe - sign_MS * abs_2MS) // 2) if frag.impham_CDERI is not None: mc = mcscf.DFCASSCF(mf, CASorb, CASe) else: mc = mcscf.CASSCF(mf, CASorb, CASe) smult = abs_2S + 1 if frag.target_S is not None else (frag.nelec_imp % 2) + 1 mc.fcisolver = csf_solver (mf.mol, smult, symm=frag.enforce_symmetry) if frag.enforce_symmetry: mc.fcisolver.wfnsym = frag.wfnsym mc.max_cycle_macro = 50 if frag.imp_maxiter is None else frag.imp_maxiter mc.conv_tol = min (1e-9, frag.conv_tol_grad**2) mc.ah_start_tol = mc.conv_tol / 10 mc.ah_conv_tol = mc.conv_tol / 10 mc.__dict__.update (frag.corr_attr) mc = fix_my_CASSCF_for_nonsinglet_env (mc, frag.impham_OEI_S) norbs_amo = mc.ncas norbs_cmo = mc.ncore norbs_imo = frag.norbs_imp - norbs_amo nelec_amo = sum (mc.nelecas) norbs_occ = norbs_amo + norbs_cmo #mc.natorb = True # Guess orbitals ci0 = None dm_imp = frag.get_oneRDM_imp () fock_imp = mf.get_fock (dm=dm_imp) if len (frag.imp_cache) == 2: imp2mo, ci0 = frag.imp_cache print ("Taking molecular orbitals and ci vector from cache") elif frag.norbs_as > 0: nelec_imp_guess = int (round (np.trace (frag.oneRDMas_loc))) norbs_cmo_guess = (frag.nelec_imp - nelec_imp_guess) // 2 print ("Projecting stored amos (frag.loc2amo; spanning {} electrons) onto the impurity basis and filling the remainder with default guess".format (nelec_imp_guess)) imp2mo, my_occ = project_amo_manually (frag.loc2imp, frag.loc2amo, fock_imp, norbs_cmo_guess, dm=frag.oneRDMas_loc) elif frag.loc2amo_guess is not None: print ("Projecting stored amos (frag.loc2amo_guess) onto the impurity basis (no amo dm available)") imp2mo, my_occ = project_amo_manually (frag.loc2imp, frag.loc2amo_guess, fock_imp, norbs_cmo, dm=None) frag.loc2amo_guess = None else: dm_imp = np.asarray (mf.make_rdm1 ()) while dm_imp.ndim > 2: dm_imp = dm_imp.sum (0) imp2mo = mf.mo_coeff fock_imp = mf.get_fock (dm=dm_imp) fock_mo = represent_operator_in_basis (fock_imp, imp2mo) _, evecs = matrix_eigen_control_options (fock_mo, sort_vecs=1) imp2mo = imp2mo @ evecs my_occ = ((dm_imp @ imp2mo) * imp2mo).sum (0) print ("No stored amos; using mean-field canonical MOs as initial guess") # Guess orbital processing if callable (frag.cas_guess_callback): mo = reduce (np.dot, (frag.ints.ao2loc, frag.loc2imp, imp2mo)) mo = frag.cas_guess_callback (frag.ints.mol, mc, mo) imp2mo = reduce (np.dot, (frag.imp2loc, frag.ints.ao2loc.conjugate ().T, frag.ints.ao_ovlp, mo)) frag.cas_guess_callback = None # Guess CI vector if len (frag.imp_cache) != 2 and frag.ci_as is not None: loc2amo_guess = np.dot (frag.loc2imp, imp2mo[:,norbs_cmo:norbs_occ]) metric = np.arange (CASorb) + 1 gOc = np.dot (loc2amo_guess.conjugate ().T, (frag.ci_as_orb * metric[None,:])) umat_g, svals, umat_c = matrix_svd_control_options (gOc, sort_vecs=1, only_nonzero_vals=True) if (svals.size == norbs_amo): print ("Loading ci guess despite shifted impurity orbitals; singular value error sum: {}".format (np.sum (svals - metric))) imp2mo[:,norbs_cmo:norbs_occ] = np.dot (imp2mo[:,norbs_cmo:norbs_occ], umat_g) ci0 = transform_ci_for_orbital_rotation (frag.ci_as, CASorb, CASe, umat_c) else: print ("Discarding stored ci guess because orbitals are too different (missing {} nonzero svals)".format (norbs_amo-svals.size)) # Symmetry align if possible imp2unac = frag.align_imporbs_symm (np.append (imp2mo[:,:norbs_cmo], imp2mo[:,norbs_occ:], axis=1), sorting_metric=fock_imp, sort_vecs=1, orbital_type='guess unactive', mol=mol)[0] imp2mo[:,:norbs_cmo] = imp2unac[:,:norbs_cmo] imp2mo[:,norbs_occ:] = imp2unac[:,norbs_cmo:] #imp2mo[:,:norbs_cmo] = frag.align_imporbs_symm (imp2mo[:,:norbs_cmo], sorting_metric=fock_imp, sort_vecs=1, orbital_type='guess inactive', mol=mol)[0] imp2mo[:,norbs_cmo:norbs_occ], umat = frag.align_imporbs_symm (imp2mo[:,norbs_cmo:norbs_occ], sorting_metric=fock_imp, sort_vecs=1, orbital_type='guess active', mol=mol) #imp2mo[:,norbs_occ:] = frag.align_imporbs_symm (imp2mo[:,norbs_occ:], sorting_metric=fock_imp, sort_vecs=1, orbital_type='guess external', mol=mol)[0] if frag.enforce_symmetry: imp2mo = cleanup_subspace_symmetry (imp2mo, mol.symm_orb) err_symm = measure_subspace_blockbreaking (imp2mo, mol.symm_orb) err_orth = measure_basis_nonorthonormality (imp2mo) print ("Initial symmetry error after cleanup = {}".format (err_symm)) print ("Initial orthonormality error after cleanup = {}".format (err_orth)) if ci0 is not None: ci0 = transform_ci_for_orbital_rotation (ci0, CASorb, CASe, umat) # Guess orbital printing if frag.mfmo_printed == False and frag.ints.mol.verbose: ao2mfmo = reduce (np.dot, [frag.ints.ao2loc, frag.loc2imp, imp2mo]) print ("Writing {} {} orbital molden".format (frag.frag_name, 'CAS guess')) molden.from_mo (frag.ints.mol, frag.filehead + frag.frag_name + '_mfmorb.molden', ao2mfmo, occ=my_occ) frag.mfmo_printed = True elif len (frag.active_orb_list) > 0: # This is done AFTER everything else so that the _mfmorb.molden always has consistent ordering print('Applying caslst: {}'.format (frag.active_orb_list)) imp2mo = mc.sort_mo(frag.active_orb_list, mo_coeff=imp2mo) frag.active_orb_list = [] if len (frag.frozen_orb_list) > 0: mc.frozen = copy.copy (frag.frozen_orb_list) print ("Applying frozen-orbital list (this macroiteration only): {}".format (frag.frozen_orb_list)) frag.frozen_orb_list = [] if frag.enforce_symmetry: imp2mo = lib.tag_array (imp2mo, orbsym=label_orb_symm (mol, mol.irrep_id, mol.symm_orb, imp2mo, s=mf.get_ovlp (), check=False)) t_start = time.time() E_CASSCF = mc.kernel(imp2mo, ci0)[0] if (not mc.converged) and np.all (np.abs (frag.impham_OEI_S) < 1e-8): mc = mc.newton () E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] if not mc.converged: print ('Assuming ci vector is poisoned; discarding...') imp2mo = mc.mo_coeff.copy () mc = mcscf.CASSCF(mf, CASorb, CASe) smult = abs_2S + 1 if frag.target_S is not None else (frag.nelec_imp % 2) + 1 mc.fcisolver = csf_solver (mf.mol, smult) E_CASSCF = mc.kernel(imp2mo)[0] if not mc.converged: if np.any (np.abs (frag.impham_OEI_S) > 1e-8): raise NotImplementedError('Gradient and Hessian fixes for nonsinglet environment of Newton-descent CASSCF algorithm') mc = mc.newton () E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] assert (mc.converged) ''' mc.conv_tol = 1e-12 mc.ah_start_tol = 1e-10 mc.ah_conv_tol = 1e-12 E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] if not mc.converged: mc = mc.newton () E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] #assert (mc.converged) ''' # Get twoRDM + oneRDM. cs: MC-SCF core, as: MC-SCF active space # I'm going to need to keep some representation of the active-space orbitals # Symmetry align if possible oneRDM_amo, twoRDM_amo = mc.fcisolver.make_rdm12 (mc.ci, mc.ncas, mc.nelecas) fock_imp = mc.get_fock () mc.mo_coeff[:,:norbs_cmo] = frag.align_imporbs_symm (mc.mo_coeff[:,:norbs_cmo], sorting_metric=fock_imp, sort_vecs=1, orbital_type='optimized inactive', mol=mol)[0] mc.mo_coeff[:,norbs_cmo:norbs_occ], umat = frag.align_imporbs_symm (mc.mo_coeff[:,norbs_cmo:norbs_occ], sorting_metric=oneRDM_amo, sort_vecs=-1, orbital_type='optimized active', mol=mol) mc.mo_coeff[:,norbs_occ:] = frag.align_imporbs_symm (mc.mo_coeff[:,norbs_occ:], sorting_metric=fock_imp, sort_vecs=1, orbital_type='optimized external', mol=mol)[0] if frag.enforce_symmetry: amo2imp = mc.mo_coeff[:,norbs_cmo:norbs_occ].conjugate ().T mc.mo_coeff = cleanup_subspace_symmetry (mc.mo_coeff, mol.symm_orb) umat = umat @ (amo2imp @ mc.mo_coeff[:,norbs_cmo:norbs_occ]) err_symm = measure_subspace_blockbreaking (mc.mo_coeff, mol.symm_orb) err_orth = measure_basis_nonorthonormality (mc.mo_coeff) print ("Final symmetry error after cleanup = {}".format (err_symm)) print ("Final orthonormality error after cleanup = {}".format (err_orth)) mc.ci = transform_ci_for_orbital_rotation (mc.ci, CASorb, CASe, umat) # Cache stuff imp2mo = mc.mo_coeff #mc.cas_natorb()[0] loc2mo = np.dot (frag.loc2imp, imp2mo) imp2amo = imp2mo[:,norbs_cmo:norbs_occ] loc2amo = loc2mo[:,norbs_cmo:norbs_occ] frag.imp_cache = [mc.mo_coeff, mc.ci] frag.ci_as = mc.ci frag.ci_as_orb = loc2amo.copy () t_end = time.time() # oneRDM oneRDM_imp = mc.make_rdm1 () # twoCDM oneRDM_amo, twoRDM_amo = mc.fcisolver.make_rdm12 (mc.ci, mc.ncas, mc.nelecas) oneRDMs_amo = np.stack (mc.fcisolver.make_rdm1s (mc.ci, mc.ncas, mc.nelecas), axis=0) oneSDM_amo = oneRDMs_amo[0] - oneRDMs_amo[1] if frag.target_MS >= 0 else oneRDMs_amo[1] - oneRDMs_amo[0] oneSDM_imp = represent_operator_in_basis (oneSDM_amo, imp2amo.conjugate ().T) print ("Norm of spin density: {}".format (linalg.norm (oneSDM_amo))) # Note that I do _not_ do the *real* cumulant decomposition; I do one assuming oneSDM_amo = 0. # This is fine as long as I keep it consistent, since it is only in the orbital gradients for this impurity that # the spin density matters. But it has to stay consistent! twoCDM_amo = get_2CDM_from_2RDM (twoRDM_amo, oneRDM_amo) twoCDM_imp = represent_operator_in_basis (twoCDM_amo, imp2amo.conjugate ().T) print('Impurity CASSCF energy (incl chempot): {}; spin multiplicity: {}; time to solve: {}'.format (E_CASSCF, spin_square (mc)[1], t_end - t_start)) # Active-space RDM data frag.oneRDMas_loc = symmetrize_tensor (represent_operator_in_basis (oneRDM_amo, loc2amo.conjugate ().T)) frag.oneSDMas_loc = symmetrize_tensor (represent_operator_in_basis (oneSDM_amo, loc2amo.conjugate ().T)) frag.twoCDMimp_amo = twoCDM_amo frag.loc2mo = loc2mo frag.loc2amo = loc2amo frag.E2_cum = np.tensordot (ao2mo.restore (1, mc.get_h2eff (), mc.ncas), twoCDM_amo, axes=4) / 2 frag.E2_cum += (mf.get_k (dm=oneSDM_imp) * oneSDM_imp).sum () / 4 # The second line compensates for my incorrect cumulant decomposition. Anything to avoid changing the checkpoint files... # General impurity data frag.oneRDM_loc = frag.oneRDMfroz_loc + symmetrize_tensor (represent_operator_in_basis (oneRDM_imp, frag.imp2loc)) frag.oneSDM_loc = frag.oneSDMfroz_loc + frag.oneSDMas_loc frag.twoCDM_imp = None # Experiment: this tensor is huge. Do I actually need to keep it? In principle, of course not. frag.E_imp = E_CASSCF + np.einsum ('ab,ab->', chempot_imp, oneRDM_imp) return None
def mcpdft_HellmanFeynman_grad(mc, ot, veff1, veff2, mo_coeff=None, ci=None, atmlst=None, mf_grad=None, verbose=None): ''' Modification of pyscf.grad.casscf.kernel to compute instead the Hellman-Feynman gradient terms of MC-PDFT. From the differentiated Hamiltonian matrix elements, only the core and Coulomb energy parts remain. For the renormalization terms, the effective Fock matrix is as in CASSCF, but with the same Hamiltonian substutition that is used for the energy response terms. ''' if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci if mf_grad is None: mf_grad = mc._scf.nuc_grad_method() if mc.frozen is not None: raise NotImplementedError t0 = (time.clock(), time.time()) mol = mc.mol ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas nao, nmo = mo_coeff.shape nao_pair = nao * (nao + 1) // 2 mo_occ = mo_coeff[:, :nocc] mo_core = mo_coeff[:, :ncore] mo_cas = mo_coeff[:, ncore:nocc] casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas) # gfock = Generalized Fock, Adv. Chem. Phys., 69, 63 dm_core = np.dot(mo_core, mo_core.T) * 2 dm_cas = reduce(np.dot, (mo_cas, casdm1, mo_cas.T)) # MRH: I need to replace aapa with the equivalent array from veff2 # I'm not sure how the outcore file-paging system works, but hopefully I can do this # I also need to generate vhf_c and vhf_a from veff2 rather than the molecule's actual integrals # The true Coulomb repulsion should already be in veff1, but I need to generate the "fake" # vj - vk/2 from veff2 h1e_mo = mo_coeff.T @ (mc.get_hcore() + veff1) @ mo_coeff + veff2.vhf_c aapa = np.zeros((ncas, ncas, nmo, ncas), dtype=h1e_mo.dtype) vhf_a = np.zeros((nmo, nmo), dtype=h1e_mo.dtype) for i in range(nmo): jbuf = veff2.ppaa[i] kbuf = veff2.papa[i] aapa[:, :, i, :] = jbuf[ncore:nocc, :, :] vhf_a[i] = np.tensordot(jbuf, casdm1, axes=2) vhf_a *= 0.5 # for this potential, vj = vk: vj - vk/2 = vj - vj/2 = vj/2 gfock = np.zeros((nmo, nmo)) gfock[:, :ncore] = (h1e_mo[:, :ncore] + vhf_a[:, :ncore]) * 2 gfock[:, ncore:nocc] = h1e_mo[:, ncore:nocc] @ casdm1 gfock[:, ncore:nocc] += np.einsum('uviw,vuwt->it', aapa, casdm2) dme0 = reduce(np.dot, (mo_coeff, (gfock + gfock.T) * .5, mo_coeff.T)) aapa = vhf_a = h1e_mo = gfock = None t0 = logger.timer(mc, 'PDFT HlFn gfock', *t0) dm1 = dm_core + dm_cas # MRH: vhf1c and vhf1a should be the TRUE vj_c and vj_a (no vk!) vj = mf_grad.get_jk(dm=dm1)[0] hcore_deriv = mf_grad.hcore_generator(mol) s1 = mf_grad.get_ovlp(mol) if atmlst is None: atmlst = range(mol.natm) aoslices = mol.aoslice_by_atom() de_hcore = np.zeros((len(atmlst), 3)) de_renorm = np.zeros((len(atmlst), 3)) de_coul = np.zeros((len(atmlst), 3)) de_xc = np.zeros((len(atmlst), 3)) de_grid = np.zeros((len(atmlst), 3)) de_wgt = np.zeros((len(atmlst), 3)) de = np.zeros((len(atmlst), 3)) # MRH: Now I have to compute the gradient of the exchange-correlation energy # This involves derivatives of the orbitals that construct rho and Pi and therefore another # set of potentials. It also involves the derivatives of quadrature grid points which # propagate through the densities and therefore yet another set of potentials. # The orbital-derivative part includes all the grid points and some of the orbitals (- sign); # the grid-derivative part includes all of the orbitals and some of the grid points (+ sign). # I'll do a loop over grid sections and make arrays of type (3,nao, nao) and (3,nao, ncas, ncas, ncas). # I'll contract them within the grid loop for the grid derivatives and in the following # orbital loop for the xc derivatives dm1s = mc.make_rdm1s() casdm1s = np.stack(mc.fcisolver.make_rdm1s(ci, ncas, nelecas), axis=0) twoCDM = get_2CDM_from_2RDM(casdm2, casdm1s) casdm1s = None make_rho = tuple( ot._numint._gen_rho_evaluator(mol, dm1s[i], 1) for i in range(2)) make_rho_c = ot._numint._gen_rho_evaluator(mol, dm_core, 1) make_rho_a = ot._numint._gen_rho_evaluator(mol, dm_cas, 1) dv1 = np.zeros( (3, nao, nao)) # Term which should be contracted with the whole density matrix dv1_a = np.zeros( (3, nao, nao) ) # Term which should only be contracted with the core density matrix dv2 = np.zeros((3, nao)) idx = np.array([[1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]], dtype=np.int_) # For addressing particular ao derivatives if ot.xctype == 'LDA': idx = idx[:, 0] # For LDAs no second derivatives diag_idx = np.arange(ncas) # for puvx diag_idx = diag_idx * (diag_idx + 1) // 2 + diag_idx casdm2_pack = (casdm2 + casdm2.transpose(0, 1, 3, 2)).reshape( ncas**2, ncas, ncas) casdm2_pack = pack_tril(casdm2_pack).reshape(ncas, ncas, -1) casdm2_pack[:, :, diag_idx] *= 0.5 diag_idx = np.arange(ncore, dtype=np.int_) * (ncore + 1) # for pqii full_atmlst = -np.ones(mol.natm, dtype=np.int_) t1 = logger.timer(mc, 'PDFT HlFn quadrature setup', *t0) for k, ia in enumerate(atmlst): full_atmlst[ia] = k for ia, (coords, w0, w1) in enumerate(rks_grad.grids_response_cc(ot.grids)): # For the xc potential derivative, I need every grid point in the entire molecule regardless of atmlist. (Because that's about orbitals.) # For the grid and weight derivatives, I only need the gridpoints that are in atmlst mask = gen_grid.make_mask(mol, coords) ao = ot._numint.eval_ao( mol, coords, deriv=ot.dens_deriv + 1, non0tab=mask) # Need 1st derivs for LDA, 2nd for GGA, etc. if ot.xctype == 'LDA': # Might confuse the rho and Pi generators if I don't slice this down aoval = ao[:1] elif ot.xctype == 'GGA': aoval = ao[:4] rho = np.asarray([m[0](0, aoval, mask, ot.xctype) for m in make_rho]) Pi = get_ontop_pair_density(ot, rho, aoval, dm1s, twoCDM, mo_cas, ot.dens_deriv) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} rho/Pi calc'.format(ia), *t1) moval_occ = np.tensordot(aoval, mo_occ, axes=1) moval_core = moval_occ[..., :ncore] moval_cas = moval_occ[..., ncore:] t1 = logger.timer(mc, 'PDFT HlFn quadrature atom {} ao2mo grid'.format(ia), *t1) eot, vrho, vot = ot.eval_ot(rho, Pi, weights=w0) ndpi = vot.shape[0] # Weight response de_wgt += np.tensordot(eot, w1[atmlst], axes=(0, 2)) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} weight response'.format(ia), *t1) # Find the atoms that are a part of the atomlist - grid correction shouldn't be added if they aren't there # The last stuff to vectorize is in get_veff_2body! k = full_atmlst[ia] # Vpq + Vpqii vrho = _contract_vot_rho(vot, make_rho_c[0](0, aoval, mask, ot.xctype), add_vrho=vrho) tmp_dv = np.stack([ ot.get_veff_1body(rho, Pi, [ao[ix], aoval], w0, kern=vrho) for ix in idx ], axis=0) if k >= 0: de_grid[k] += 2 * np.tensordot(tmp_dv, dm1.T, axes=2) # Grid response dv1 -= tmp_dv # XC response t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} Vpq + Vpqii'.format(ia), *t1) # Viiuv * Duv vrho_a = _contract_vot_rho(vot, make_rho_a[0](0, aoval, mask, ot.xctype)) tmp_dv = np.stack([ ot.get_veff_1body(rho, Pi, [ao[ix], aoval], w0, kern=vrho_a) for ix in idx ], axis=0) if k >= 0: de_grid[k] += 2 * np.tensordot(tmp_dv, dm_core.T, axes=2) # Grid response dv1_a -= tmp_dv # XC response t1 = logger.timer(mc, 'PDFT HlFn quadrature atom {} Viiuv'.format(ia), *t1) # Vpuvx tmp_dv = ot.get_veff_2body_kl(rho, Pi, moval_cas, moval_cas, w0, symm=True, kern=vot) # ndpi,ngrids,ncas*(ncas+1)//2 tmp_dv = np.tensordot(tmp_dv, casdm2_pack, axes=(-1, -1)) # ndpi, ngrids, ncas, ncas tmp_dv[0] = (tmp_dv[:ndpi] * moval_cas[:ndpi, :, None, :]).sum( 0) # Chain and product rule tmp_dv[1:ndpi] *= moval_cas[0, :, None, :] # Chain and product rule tmp_dv = tmp_dv.sum(-1) # ndpi, ngrids, ncas tmp_dv = np.tensordot(ao[idx[:, :ndpi]], tmp_dv, axes=((1, 2), (0, 1))) # comp, nao (orb), ncas (dm2) tmp_dv = np.einsum('cpu,pu->cp', tmp_dv, mo_cas) # comp, ncas if k >= 0: de_grid[k] += 2 * tmp_dv.sum(1) dv2 -= tmp_dv # XC response t1 = logger.timer(mc, 'PDFT HlFn quadrature atom {} Vpuvx'.format(ia), *t1) for k, ia in enumerate(atmlst): shl0, shl1, p0, p1 = aoslices[ia] h1ao = hcore_deriv(ia) # MRH: this should be the TRUE hcore de_hcore[k] += np.einsum('xij,ij->x', h1ao, dm1) de_renorm[k] -= np.einsum('xij,ij->x', s1[:, p0:p1], dme0[p0:p1]) * 2 de_coul[k] += np.einsum('xij,ij->x', vj[:, p0:p1], dm1[p0:p1]) * 2 de_xc[k] += np.einsum( 'xij,ij->x', dv1[:, p0:p1], dm1[p0:p1]) * 2 # Full quadrature, only some orbitals de_xc[k] += np.einsum('xij,ij->x', dv1_a[:, p0:p1], dm_core[p0:p1]) * 2 # Ditto de_xc[k] += dv2[:, p0:p1].sum(1) * 2 # Ditto de_nuc = mf_grad.grad_nuc(mol, atmlst) logger.debug(mc, "MC-PDFT Hellmann-Feynman nuclear :\n{}".format(de_nuc)) logger.debug( mc, "MC-PDFT Hellmann-Feynman hcore component:\n{}".format(de_hcore)) logger.debug( mc, "MC-PDFT Hellmann-Feynman coulomb component:\n{}".format(de_coul)) logger.debug(mc, "MC-PDFT Hellmann-Feynman xc component:\n{}".format(de_xc)) logger.debug( mc, "MC-PDFT Hellmann-Feynman quadrature point component:\n{}".format( de_grid)) logger.debug( mc, "MC-PDFT Hellmann-Feynman quadrature weight component:\n{}".format( de_wgt)) logger.debug( mc, "MC-PDFT Hellmann-Feynman renorm component:\n{}".format(de_renorm)) de = de_nuc + de_hcore + de_coul + de_renorm + de_xc + de_grid + de_wgt t1 = logger.timer(mc, 'PDFT HlFn total', *t0) return de
def kernel(mc, nroots): # lroots=nroots+1 for i in range(0, nroots): ci_coeff = mc.ci[i] print("ci", i, nroots, ci_coeff) amo = mc.mo_coeff[:, mc.ncore:mc.ncore + mc.ncas] # make_rdm12s returns (a, b), (aa, ab, bb) mc_1root = mc mc_1root = mcscf.CASCI(mc._scf, mc.ncas, mc.nelecas) mc_1root.fcisolver = fci.solver(mc._scf.mol, singlet=False, symm=False) mc_1root.mo_coeff = mc.mo_coeff nao, nmo = mc.mo_coeff.shape # dm1s = np.asarray (mc_1root.state_average.states_make_rdm1s ()) adm1s = np.stack(mc.fcisolver.make_rdm1s(mc.ci, mc_1root.ncas, mc_1root.nelecas), axis=0) dm1 = mc.states_make_rdm1() dm1_hold = np.ones((nroots, nao, nao)) print("dm1_hold", np.shape(dm1_hold)) print("dm1", np.shape(dm1)) print("adms", np.shape(adm1s)) print("dm1", dm1[1]) print("dm1", dm1[2]) print("dm1", dm1[3]) print("dm1", dm1[4]) # adm1s = np.stack (mc_1root.fcisolver.make_rdm1s (mc.ci, mc.ncas, mc.nelecas), axis=0) # adm2 = get_2CDM_from_2RDM (mc_1root.fcisolver.make_rdm12 (mc_1root.ci, mc.ncas, mc.nelecas)[1], adm1s) # adm2s = get_2CDMs_from_2RDMs (mc_1root.fcisolver.make_rdm12s (mc_1root.ci, mc.ncas, mc.nelecas)[1], adm1s) # adm1 = dm1s[0] + dm1s[1] print("dm1s", dm1) # lroots=nroots+1 for i in range(0, nroots): ci_coeff = mc.ci[i] print("ci", i, nroots, ci_coeff) # print ("casdm1 :", casdm1, np.shape(casdm1)) # print ("dm1s", dm1s) # print ("casdm2 :", casdm2) #Calculates State Pairs Pair = 0 istate = 1 jstate = 1 npairs = int(nroots * (nroots - 1) // 2) statepair = np.zeros((npairs, 2)) print("state pairs", statepair) print("npairs", npairs, " nroots ", nroots) ipair = 0 for i in range(nroots): print("i", i) for j in range(i): print("j", j) print("ipair", ipair, i, j) statepair[ipair, 0] = i statepair[ipair, 1] = j ipair = ipair + 1 print("state pair", statepair) # nci_sum = ci[0] # nci_sum2 = ci[1] # nci_sum3 = np.append(nci_sum2,ci[0]) # nci_sum4 = np.append(nci_sum,ci[1]) # print("result",newci) # print("shape",np.shape(newci)) # print ("shape ci", np.shape(ci)) # print("nci_sum2", nci_sum4) trans12_tdm1, trans12_tdm2 = mc.fcisolver.states_trans_rdm12( mc.ci, mc.ci, mc_1root.ncas, mc_1root.nelecas) print("trans12 tdm1 :", trans12_tdm1) # print("trans12 tdm2 :", trans12_tdm2) #Load in the two-electron integrals aeri = ao2mo.restore(1, mc.get_h2eff(mc.mo_coeff), mc.ncas) # print("aeri", aeri) print("eri shape", np.shape(aeri)) #Initialize rotation matrix u = np.identity(mc.fcisolver.nroots) print("U :", u) #Rotate the States and Corresponding Density Matrices converged = False cmsthresh = 1e-06 cmsiter = 1 print("nao", nao) # print("dm1s",dm1s[1,1,1]) adm1s = np.stack(mc.fcisolver.make_rdm1s(mc.ci, mc_1root.ncas, mc_1root.nelecas), axis=0) adm2 = get_2CDM_from_2RDM( mc.fcisolver.make_rdm12(mc.ci, mc_1root.ncas, mc_1root.nelecas)[1], adm1s) E_c = np.tensordot(aeri, adm2, axes=4) / 2 print("e_c", np.shape(e_c)) print("e_c", E_c) #Calculates old VeeSum # def calcvee(rmat,ddg): # vee=np.zeros(nroots) # # for istate in range (nroots): # for j in range (nroots): # for k in range (nroots): # for l in range (nroots): # for m in range (nroots): # print("i,j,k,l,m", i,j,k,l,m) # vee[i]= vee[i]+rmat[istate,j]*rmat[istate,k]*rmat[istate,l]*rmat[istate,m]*ddg[j,k,l,m] # vee[i]=vee[i]/2 # def getddg(eri): # ddg=np.zeros(nroots**4).reshape(nroots,nroots,nroots,nroots) # adm1s = np.stack (mc.fcisolver.make_rdm1s (mc.ci, mc_1root.ncas,mc_1root.nelecas), axis=0) # adm1s = adm1s[0]+adm1s[1] # print("adm1s shape", np.shape(adm1s)) # print("aeri",np.shape(aeri)) # for i in range (nroots): # for j in range (nroots): # if j > i : # jj=i # ii=j1 # else: # ii=i # jj=j # for k in range (nroots): # for l in range (nroots): # if l > k : # kk=l # ll=k # else: # kk=k # ll=l # for t in range(mc.ncas): # for u in range(mc.ncas): # for v in range(mc.ncas): # for x in range(mc.ncas): # ii = ii*(ii-1)//2+jj # kk = kk*(kk-1)//2+ll # ddg[i,j,k,l]=aeri[t,u,v,x] # ddg[i,j,k,l]=adm1s[ii,t,u]*adm1s[kk,v,x]*aeri[t,u,v,x] # print(i,ii,j,jj,k,kk,l,ll,t,u,v,x) # print("ddg", ddg[i,j,k,l]) # print("dm1_hold", dm1_hold[kk,t,u]) # ddg=getddg(aeri) # print("ddg", ddg) # veesum = calcvee(u,ddg) # print("vsum",veesum) # dm1s = np.stack (mc.fcisolver.make_rdm1s (mc.ci[1],mc_1root.ncas,mc_1root.nelecas), axis=0) # dm1 = dm1s[0] + dm1s[1] # j = mc_1root._scf.get_j (dm=dm1) # for i in range(nroots): j = mc_1root._scf.get_j(dm=dm1) e_coul = np.tensordot(j, dm1s, axes=2) / 2 print("e_coul_1", i, e_coul) #Gradient #Example #np.einsum ('rsij,r->ij', veff_a, las.weights) w = aeri * adm1s print("w", np.shape(w)) # for i in range(statepairs.len()): # trans12_tdm2 = trans12_tdm2 + mc.fcisolver.states_trans_rdm12(mc.ci,mc.ci,mc_1root.ncas,mc_1root.nelecas) # g_noscale = np.tensordot (w,np.appent(trans12_tdm2,axes=4) # print("g_noscale",g_noscale) # g = 4*g_noscale # # print ("g",g) return
def mcpdft_HellmanFeynman_grad(mc, ot, veff1, veff2, mo_coeff=None, ci=None, atmlst=None, mf_grad=None, verbose=None, max_memory=None, auxbasis_response=False): ''' Modification of pyscf.grad.casscf.kernel to compute instead the Hellman-Feynman gradient terms of MC-PDFT. From the differentiated Hamiltonian matrix elements, only the core and Coulomb energy parts remain. For the renormalization terms, the effective Fock matrix is as in CASSCF, but with the same Hamiltonian substutition that is used for the energy response terms. ''' if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci if mf_grad is None: mf_grad = mc._scf.nuc_grad_method() if mc.frozen is not None: raise NotImplementedError if max_memory is None: max_memory = mc.max_memory t0 = (time.process_time(), time.time()) mol = mc.mol ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas nao, nmo = mo_coeff.shape nao_pair = nao * (nao + 1) // 2 mo_occ = mo_coeff[:, :nocc] mo_core = mo_coeff[:, :ncore] mo_cas = mo_coeff[:, ncore:nocc] casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas) # gfock = Generalized Fock, Adv. Chem. Phys., 69, 63 dm_core = np.dot(mo_core, mo_core.T) * 2 dm_cas = reduce(np.dot, (mo_cas, casdm1, mo_cas.T)) # MRH: I need to replace aapa with the equivalent array from veff2 # I'm not sure how the outcore file-paging system works, but hopefully I can do this # I also need to generate vhf_c and vhf_a from veff2 rather than the molecule's actual integrals # The true Coulomb repulsion should already be in veff1, but I need to generate the "fake" # vj - vk/2 from veff2 h1e_mo = mo_coeff.T @ (mc.get_hcore() + veff1) @ mo_coeff + veff2.vhf_c aapa = np.zeros((ncas, ncas, nmo, ncas), dtype=h1e_mo.dtype) vhf_a = np.zeros((nmo, nmo), dtype=h1e_mo.dtype) for i in range(nmo): jbuf = veff2.ppaa[i] kbuf = veff2.papa[i] aapa[:, :, i, :] = jbuf[ncore:nocc, :, :] vhf_a[i] = np.tensordot(jbuf, casdm1, axes=2) vhf_a *= 0.5 # for this potential, vj = vk: vj - vk/2 = vj - vj/2 = vj/2 gfock = np.zeros((nmo, nmo)) gfock[:, :ncore] = (h1e_mo[:, :ncore] + vhf_a[:, :ncore]) * 2 gfock[:, ncore:nocc] = h1e_mo[:, ncore:nocc] @ casdm1 gfock[:, ncore:nocc] += np.einsum('uviw,vuwt->it', aapa, casdm2) dme0 = reduce(np.dot, (mo_coeff, (gfock + gfock.T) * .5, mo_coeff.T)) aapa = vhf_a = h1e_mo = gfock = None if atmlst is None: atmlst = range(mol.natm) aoslices = mol.aoslice_by_atom() de_hcore = np.zeros((len(atmlst), 3)) de_renorm = np.zeros((len(atmlst), 3)) de_coul = np.zeros((len(atmlst), 3)) de_xc = np.zeros((len(atmlst), 3)) de_grid = np.zeros((len(atmlst), 3)) de_wgt = np.zeros((len(atmlst), 3)) de_aux = np.zeros((len(atmlst), 3)) de = np.zeros((len(atmlst), 3)) t0 = logger.timer(mc, 'PDFT HlFn gfock', *t0) mo_coeff, ci, mo_occup = cas_natorb(mc, mo_coeff=mo_coeff, ci=ci) mo_occ = mo_coeff[:, :nocc] mo_core = mo_coeff[:, :ncore] mo_cas = mo_coeff[:, ncore:nocc] dm1 = dm_core + dm_cas dm1 = tag_array(dm1, mo_coeff=mo_coeff, mo_occ=mo_occup) # MRH: vhf1c and vhf1a should be the TRUE vj_c and vj_a (no vk!) vj = mf_grad.get_jk(dm=dm1)[0] hcore_deriv = mf_grad.hcore_generator(mol) s1 = mf_grad.get_ovlp(mol) if auxbasis_response: de_aux += vj.aux # MRH: Now I have to compute the gradient of the exchange-correlation energy # This involves derivatives of the orbitals that construct rho and Pi and therefore another # set of potentials. It also involves the derivatives of quadrature grid points which # propagate through the densities and therefore yet another set of potentials. # The orbital-derivative part includes all the grid points and some of the orbitals (- sign); # the grid-derivative part includes all of the orbitals and some of the grid points (+ sign). # I'll do a loop over grid sections and make arrays of type (3,nao, nao) and (3,nao, ncas, ncas, ncas). # I'll contract them within the grid loop for the grid derivatives and in the following # orbital loop for the xc derivatives # MRH, 05/09/2020: This just in - the actual spin density doesn't matter at all in PDFT! # I could probably save a fair amount of time by not screwing around with the actual spin density! # Also, the cumulant decomposition can always be defined without the spin-density matrices and # it's still valid! But one thing at a time. mo_n = mo_occ * mo_occup[None, :nocc] casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas) twoCDM = get_2CDM_from_2RDM(casdm2, casdm1) dm1s = np.stack((dm1 / 2.0, ) * 2, axis=0) dm1 = tag_array(dm1, mo_coeff=mo_occ, mo_occ=mo_occup[:nocc]) make_rho = ot._numint._gen_rho_evaluator(mol, dm1, 1)[0] dvxc = np.zeros((3, nao)) idx = np.array([[1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]], dtype=np.int_) # For addressing particular ao derivatives if ot.xctype == 'LDA': idx = idx[:, 0:1] # For LDAs no second derivatives diag_idx = np.arange(ncas) # for puvx diag_idx = diag_idx * (diag_idx + 1) // 2 + diag_idx casdm2_pack = (twoCDM + twoCDM.transpose(0, 1, 3, 2)).reshape( ncas**2, ncas, ncas) casdm2_pack = pack_tril(casdm2_pack).reshape(ncas, ncas, -1) casdm2_pack[:, :, diag_idx] *= 0.5 diag_idx = np.arange(ncore, dtype=np.int_) * (ncore + 1) # for pqii full_atmlst = -np.ones(mol.natm, dtype=np.int_) t1 = logger.timer(mc, 'PDFT HlFn quadrature setup', *t0) for k, ia in enumerate(atmlst): full_atmlst[ia] = k for ia, (coords, w0, w1) in enumerate(rks_grad.grids_response_cc(ot.grids)): mask = gen_grid.make_mask(mol, coords) # For the xc potential derivative, I need every grid point in the entire molecule regardless of atmlist. (Because that's about orbitals.) # For the grid and weight derivatives, I only need the gridpoints that are in atmlst # It is conceivable that I can make this more efficient by only doing cross-combinations of grids and AOs, but I don't know how "mask" # works yet or how else I could do this. gc.collect() ngrids = coords.shape[0] ndao = (1, 4)[ot.dens_deriv] ndpi = (1, 4)[ot.Pi_deriv] ncols = 1.05 * 3 * (ndao * (nao + nocc) + max(ndao * nao, ndpi * ncas * ncas)) remaining_floats = (max_memory - current_memory()[0]) * 1e6 / 8 blksize = int(remaining_floats / (ncols * BLKSIZE)) * BLKSIZE blksize = max(BLKSIZE, min(blksize, ngrids, BLKSIZE * 1200)) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} mask and memory setup'.format(ia), *t1) for ip0 in range(0, ngrids, blksize): ip1 = min(ngrids, ip0 + blksize) logger.info( mc, 'PDFT gradient atom {} slice {}-{} of {} total'.format( ia, ip0, ip1, ngrids)) ao = ot._numint.eval_ao( mol, coords[ip0:ip1], deriv=ot.dens_deriv + 1, non0tab=mask) # Need 1st derivs for LDA, 2nd for GGA, etc. t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} ao grids'.format(ia), *t1) if ot.xctype == 'LDA': # Might confuse the rho and Pi generators if I don't slice this down aoval = ao[0] if ot.xctype == 'GGA': aoval = ao[:4] rho = make_rho(0, aoval, mask, ot.xctype) / 2.0 rho = np.stack((rho, ) * 2, axis=0) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} rho calc'.format(ia), *t1) Pi = get_ontop_pair_density(ot, rho, aoval, dm1s, twoCDM, mo_cas, ot.dens_deriv, mask) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} Pi calc'.format(ia), *t1) if ot.xctype == 'LDA': # TODO: consistent format requirements for shape of ao grid aoval = ao[:1] moval_occ = _grid_ao2mo(mol, aoval, mo_occ, mask) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} ao2mo grid'.format(ia), *t1) aoval = np.ascontiguousarray([ ao[ix].transpose(0, 2, 1) for ix in idx[:, :ndao] ]).transpose(0, 1, 3, 2) ao = None t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} ao grid reshape'.format(ia), *t1) eot, vot = ot.eval_ot(rho, Pi, weights=w0[ip0:ip1])[:2] vrho, vPi = vot t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} eval_ot'.format(ia), *t1) puvx_mem = 2 * ndpi * (ip1 - ip0) * ncas * ncas * 8 / 1e6 remaining_mem = max_memory - current_memory()[0] logger.info( mc, 'PDFT gradient memory note: working on {} grid points; estimated puvx usage = {:.1f} of {:.1f} remaining MB' .format((ip1 - ip0), puvx_mem, remaining_mem)) # Weight response de_wgt += np.tensordot(eot, w1[atmlst, ..., ip0:ip1], axes=(0, 2)) t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} weight response'.format(ia), *t1) # Find the atoms that are a part of the atomlist - grid correction shouldn't be added if they aren't there # The last stuff to vectorize is in get_veff_2body! k = full_atmlst[ia] # Vpq + Vpqrs * Drs ; I'm not sure why the list comprehension down there doesn't break ao's stride order but I'm not complaining vrho = _contract_vot_rho(vPi, rho.sum(0), add_vrho=vrho) tmp_dv = np.stack([ ot.get_veff_1body( rho, Pi, [ao_i, moval_occ], w0[ip0:ip1], kern=vrho) for ao_i in aoval ], axis=0) tmp_dv = (tmp_dv * mo_occ[None, :, :] * mo_occup[None, None, :nocc]).sum(2) if k >= 0: de_grid[k] += 2 * tmp_dv.sum(1) # Grid response dvxc -= tmp_dv # XC response vrho = tmp_dv = None t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} Vpq + Vpqrs * Drs'.format(ia), *t1) # Vpuvx * Lpuvx ; remember the stupid slowest->fastest->medium stride order of the ao grid arrays moval_cas = moval_occ = np.ascontiguousarray( moval_occ[..., ncore:].transpose(0, 2, 1)).transpose(0, 2, 1) tmp_dv = ot.get_veff_2body_kl( rho, Pi, moval_cas, moval_cas, w0[ip0:ip1], symm=True, kern=vPi) # ndpi,ngrids,ncas*(ncas+1)//2 tmp_dv = np.tensordot(tmp_dv, casdm2_pack, axes=(-1, -1)) # ndpi, ngrids, ncas, ncas tmp_dv[0] = (tmp_dv[:ndpi] * moval_cas[:ndpi, :, None, :]).sum( 0) # Chain and product rule tmp_dv[1:ndpi] *= moval_cas[0, :, None, :] # Chain and product rule tmp_dv = tmp_dv.sum(-1) # ndpi, ngrids, ncas tmp_dv = np.tensordot(aoval[:, :ndpi], tmp_dv, axes=((1, 2), (0, 1))) # comp, nao (orb), ncas (dm2) tmp_dv = np.einsum( 'cpu,pu->cp', tmp_dv, mo_cas ) # comp, ncas (it's ok to not vectorize this b/c the quadrature grid is gone) if k >= 0: de_grid[k] += 2 * tmp_dv.sum(1) # Grid response dvxc -= tmp_dv # XC response tmp_dv = None t1 = logger.timer( mc, 'PDFT HlFn quadrature atom {} Vpuvx * Lpuvx'.format(ia), *t1) rho = Pi = eot = vot = vPi = aoval = moval_occ = moval_cas = None gc.collect() for k, ia in enumerate(atmlst): shl0, shl1, p0, p1 = aoslices[ia] h1ao = hcore_deriv(ia) # MRH: this should be the TRUE hcore de_hcore[k] += np.einsum('xij,ij->x', h1ao, dm1) de_renorm[k] -= np.einsum('xij,ij->x', s1[:, p0:p1], dme0[p0:p1]) * 2 de_coul[k] += np.einsum('xij,ij->x', vj[:, p0:p1], dm1[p0:p1]) * 2 de_xc[k] += dvxc[:, p0:p1].sum( 1) * 2 # Full quadrature, only some orbitals de_nuc = mf_grad.grad_nuc(mol, atmlst) logger.debug(mc, "MC-PDFT Hellmann-Feynman nuclear :\n{}".format(de_nuc)) logger.debug( mc, "MC-PDFT Hellmann-Feynman hcore component:\n{}".format(de_hcore)) logger.debug( mc, "MC-PDFT Hellmann-Feynman coulomb component:\n{}".format(de_coul)) logger.debug(mc, "MC-PDFT Hellmann-Feynman xc component:\n{}".format(de_xc)) logger.debug( mc, "MC-PDFT Hellmann-Feynman quadrature point component:\n{}".format( de_grid)) logger.debug( mc, "MC-PDFT Hellmann-Feynman quadrature weight component:\n{}".format( de_wgt)) logger.debug( mc, "MC-PDFT Hellmann-Feynman renorm component:\n{}".format(de_renorm)) de = de_nuc + de_hcore + de_coul + de_renorm + de_xc + de_grid + de_wgt if auxbasis_response: de += de_aux logger.debug( mc, "MC-PDFT Hellmann-Feynman aux component:\n{}".format(de_aux)) t1 = logger.timer(mc, 'PDFT HlFn total', *t0) return de
def solve(frag, guess_1RDM, chempot_imp): # Augment OEI with the chemical potential OEI = frag.impham_OEI - chempot_imp # Do I need to get the full RHF solution? guess_orbs_av = len(frag.imp_cache) == 2 or frag.norbs_as > 0 # Get the RHF solution mol = gto.Mole() mol.spin = int(round(2 * frag.target_MS)) mol.verbose = 0 if frag.mol_output is None else lib.logger.DEBUG mol.output = frag.mol_output mol.atom.append(('H', (0, 0, 0))) mol.nelectron = frag.nelec_imp mol.build() #mol.incore_anyway = True mf = scf.RHF(mol) mf.get_hcore = lambda *args: OEI mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) mf.energy_nuc = lambda *args: frag.impham_CONST if frag.impham_CDERI is not None: mf = mf.density_fit() mf.with_df._cderi = frag.impham_CDERI else: mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) mf.__dict__.update(frag.mf_attr) if guess_orbs_av: mf.max_cycle = 2 mf.scf(guess_1RDM) if (not mf.converged) and (not guess_orbs_av): print( "CASSCF RHF-step not converged on fixed-point iteration; initiating newton solver" ) mf = mf.newton() mf.kernel() # Instability check and repeat if not guess_orbs_av: for i in range(frag.num_mf_stab_checks): mf.mo_coeff = mf.stability()[0] guess_1RDM = mf.make_rdm1() mf = scf.RHF(mol) mf.get_hcore = lambda *args: OEI mf.get_ovlp = lambda *args: np.eye(frag.norbs_imp) mf._eri = ao2mo.restore(8, frag.impham_TEI, frag.norbs_imp) mf.scf(guess_1RDM) if not mf.converged: mf = mf.newton() mf.kernel() print("CASSCF RHF-step energy: {}".format(mf.e_tot)) #print(mf.mo_occ) ''' idx = mf.mo_energy.argsort() mf.mo_energy = mf.mo_energy[idx] mf.mo_coeff = mf.mo_coeff[:,idx]''' # Get the CASSCF solution CASe = frag.active_space[0] CASorb = frag.active_space[1] checkCAS = (CASe <= frag.nelec_imp) and (CASorb <= frag.norbs_imp) if (checkCAS == False): CASe = frag.nelec_imp CASorb = frag.norbs_imp if (frag.target_MS > frag.target_S): CASe = ((CASe // 2) + frag.target_S, (CASe // 2) - frag.target_S) else: CASe = ((CASe // 2) + frag.target_MS, (CASe // 2) - frag.target_MS) if frag.impham_CDERI is not None: mc = mcscf.DFCASSCF(mf, CASorb, CASe) else: mc = mcscf.CASSCF(mf, CASorb, CASe) norbs_amo = mc.ncas norbs_cmo = mc.ncore norbs_imo = frag.norbs_imp - norbs_amo nelec_amo = sum(mc.nelecas) norbs_occ = norbs_amo + norbs_cmo #mc.natorb = True # Guess orbitals ci0 = None if len(frag.imp_cache) == 2: imp2mo, ci0 = frag.imp_cache print("Taking molecular orbitals and ci vector from cache") elif frag.norbs_as > 0: nelec_imp_guess = int(round(np.trace(frag.oneRDMas_loc))) norbs_cmo_guess = (frag.nelec_imp - nelec_imp_guess) // 2 print( "Projecting stored amos (frag.loc2amo; spanning {} electrons) onto the impurity basis and filling the remainder with default guess" .format(nelec_imp_guess)) imp2mo, my_occ = project_amo_manually( frag.loc2imp, frag.loc2amo, mf.get_fock(dm=frag.get_oneRDM_imp()), norbs_cmo_guess, dm=frag.oneRDMas_loc) elif frag.loc2amo_guess is not None: print( "Projecting stored amos (frag.loc2amo_guess) onto the impurity basis (no dm available)" ) imp2mo, my_occ = project_amo_manually( frag.loc2imp, frag.loc2amo_guess, mf.get_fock(dm=frag.get_oneRDM_imp()), norbs_cmo, dm=None) frag.loc2amo_guess = None else: imp2mo = mc.mo_coeff my_occ = mf.mo_occ print( "No stored amos; using mean-field canonical MOs as initial guess") # Guess orbital processing if callable(frag.cas_guess_callback): mo = reduce(np.dot, (frag.ints.ao2loc, frag.loc2imp, imp2mo)) mo = frag.cas_guess_callback(frag.ints.mol, mc, mo) imp2mo = reduce(np.dot, (frag.imp2loc, frag.ints.ao2loc.conjugate().T, frag.ints.ao_ovlp, mo)) frag.cas_guess_callback = None elif len(frag.active_orb_list) > 0: print('Applying caslst: {}'.format(frag.active_orb_list)) imp2mo = mc.sort_mo(frag.active_orb_list, mo_coeff=imp2mo) frag.active_orb_list = [] if len(frag.frozen_orb_list) > 0: mc.frozen = copy.copy(frag.frozen_orb_list) print("Applying frozen-orbital list (this macroiteration only): {}". format(frag.frozen_orb_list)) frag.frozen_orb_list = [] # Guess orbital printing if frag.mfmo_printed == False: ao2mfmo = reduce(np.dot, [frag.ints.ao2loc, frag.loc2imp, imp2mo]) molden.from_mo(frag.ints.mol, frag.filehead + frag.frag_name + '_mfmorb.molden', ao2mfmo, occ=my_occ) frag.mfmo_printed = True # Guess CI vector if len(frag.imp_cache) != 2 and frag.ci_as is not None: loc2amo_guess = np.dot(frag.loc2imp, imp2mo[:, norbs_cmo:norbs_occ]) gOc = np.dot(loc2amo_guess.conjugate().T, frag.ci_as_orb) umat_g, svals, umat_c = matrix_svd_control_options( gOc, sort_vecs=-1, only_nonzero_vals=True) if (svals.size == norbs_amo): print( "Loading ci guess despite shifted impurity orbitals; singular value sum: {}" .format(np.sum(svals))) imp2mo[:, norbs_cmo:norbs_occ] = np.dot( imp2mo[:, norbs_cmo:norbs_occ], umat_g) ci0 = transform_ci_for_orbital_rotation(frag.ci_as, CASorb, CASe, umat_c) else: print( "Discarding stored ci guess because orbitals are too different (missing {} nonzero svals)" .format(norbs_amo - svals.size)) t_start = time.time() smult = 2 * frag.target_S + 1 if frag.target_S is not None else ( frag.nelec_imp % 2) + 1 mc.fcisolver = csf_solver(mf.mol, smult) mc.max_cycle_macro = 50 if frag.imp_maxiter is None else frag.imp_maxiter mc.ah_start_tol = 1e-10 mc.ah_conv_tol = 1e-10 mc.conv_tol = 1e-9 mc.__dict__.update(frag.corr_attr) E_CASSCF = mc.kernel(imp2mo, ci0)[0] if not mc.converged: mc = mc.newton() E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] if not mc.converged: print('Assuming ci vector is poisoned; discarding...') imp2mo = mc.mo_coeff.copy() mc = mcscf.CASSCF(mf, CASorb, CASe) smult = 2 * frag.target_S + 1 if frag.target_S is not None else ( frag.nelec_imp % 2) + 1 mc.fcisolver = csf_solver(mf.mol, smult) E_CASSCF = mc.kernel(imp2mo)[0] if not mc.converged: mc = mc.newton() E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] assert (mc.converged) ''' mc.conv_tol = 1e-12 mc.ah_start_tol = 1e-10 mc.ah_conv_tol = 1e-12 E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] if not mc.converged: mc = mc.newton () E_CASSCF = mc.kernel(mc.mo_coeff, mc.ci)[0] #assert (mc.converged) ''' # Get twoRDM + oneRDM. cs: MC-SCF core, as: MC-SCF active space # I'm going to need to keep some representation of the active-space orbitals imp2mo = mc.mo_coeff #mc.cas_natorb()[0] loc2mo = np.dot(frag.loc2imp, imp2mo) imp2amo = imp2mo[:, norbs_cmo:norbs_occ] loc2amo = loc2mo[:, norbs_cmo:norbs_occ] frag.imp_cache = [mc.mo_coeff, mc.ci] frag.ci_as = mc.ci frag.ci_as_orb = loc2amo.copy() t_end = time.time() print( 'Impurity CASSCF energy (incl chempot): {}; spin multiplicity: {}; time to solve: {}' .format(E_CASSCF, spin_square(mc)[1], t_end - t_start)) # oneRDM oneRDM_imp = mc.make_rdm1() # twoCDM oneRDM_amo, twoRDM_amo = mc.fcisolver.make_rdm12(mc.ci, mc.ncas, mc.nelecas) # Note that I do _not_ do the *real* cumulant decomposition; I do one assuming oneRDMs_amo_alpha = oneRDMs_amo_beta # This is fine as long as I keep it consistent, since it is only in the orbital gradients for this impurity that # the spin density matters. But it has to stay consistent! twoCDM_amo = get_2CDM_from_2RDM(twoRDM_amo, oneRDM_amo) twoCDM_imp = represent_operator_in_basis(twoCDM_amo, imp2amo.conjugate().T) # General impurity data frag.oneRDM_loc = symmetrize_tensor( frag.oneRDMfroz_loc + represent_operator_in_basis(oneRDM_imp, frag.imp2loc)) frag.twoCDM_imp = None # Experiment: this tensor is huge. Do I actually need to keep it? In principle, of course not. frag.E_imp = E_CASSCF + np.einsum('ab,ab->', chempot_imp, oneRDM_imp) # Active-space RDM data frag.oneRDMas_loc = symmetrize_tensor( represent_operator_in_basis(oneRDM_amo, loc2amo.conjugate().T)) frag.twoCDMimp_amo = twoCDM_amo frag.loc2mo = loc2mo frag.loc2amo = loc2amo frag.E2_cum = 0.5 * np.tensordot( ao2mo.restore(1, mc.get_h2eff(), mc.ncas), twoCDM_amo, axes=4) return None