def get_vxc_full_response(ni, mol, grids, xc_code, dms, relativity=0, hermi=1, max_memory=2000, verbose=None): '''Full response including the response of the grids''' xctype = ni._xc_type(xc_code) make_rho, nset, nao = ni._gen_rho_evaluator(mol, dms, hermi) ao_loc = mol.ao_loc_nr() excsum = 0 vmat = numpy.zeros((3,nao,nao)) if xctype == 'LDA': ao_deriv = 1 vtmp = numpy.empty((3,nao,nao)) for atm_id, (coords, weight, weight1) in enumerate(grids_response_cc(grids)): ngrids = weight.size mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho = make_rho(0, ao[0], mask, 'LDA') exc, vxc = ni.eval_xc(xc_code, rho, 0, relativity, 1, verbose=verbose)[:2] vrho = vxc[0] vtmp = numpy.zeros((3,nao,nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight*vrho) _d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat += vtmp # response of weights excsum += numpy.einsum('r,r,nxr->nx', exc, rho, weight1) # response of grids coordinates excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms) * 2 rho = vxc = vrho = aow = None elif xctype == 'GGA': ao_deriv = 2 for atm_id, (coords, weight, weight1) in enumerate(grids_response_cc(grids)): ngrids = weight.size mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho = make_rho(0, ao[:4], mask, 'GGA') exc, vxc = ni.eval_xc(xc_code, rho, 0, relativity, 1, verbose=verbose)[:2] vtmp = numpy.zeros((3,nao,nao)) wv = numint._rks_gga_wv0(rho, vxc, weight) _gga_grad_sum_(vtmp, mol, ao, wv, mask, ao_loc) vmat += vtmp # response of weights excsum += numpy.einsum('r,r,nxr->nx', exc, rho[0], weight1) # response of grids coordinates excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms) * 2 rho = vxc = vrho = vsigma = wv = mat = None elif xctype == 'NLC': raise NotImplementedError('NLC') else: raise NotImplementedError('meta-GGA') # - sign because nabla_X = -nabla_x return excsum, -vmat
def get_vxc_full_response(ni, mol, grids, xc_code, dms, relativity=0, hermi=1, max_memory=2000, verbose=None): '''Full response including the response of the grids''' xctype = ni._xc_type(xc_code) make_rho, nset, nao = ni._gen_rho_evaluator(mol, dms, hermi) ao_loc = mol.ao_loc_nr() excsum = 0 vmat = numpy.zeros((3,nao,nao)) if xctype == 'LDA': ao_deriv = 1 vtmp = numpy.empty((3,nao,nao)) for atm_id, (coords, weight, weight1) in enumerate(grids_response_cc(grids)): ngrids = weight.size mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho = make_rho(0, ao[0], mask, 'LDA') exc, vxc = ni.eval_xc(xc_code, rho, 0, relativity, 1, verbose)[:2] vrho = vxc[0] vtmp = numpy.zeros((3,nao,nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight*vrho) _d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat += vtmp # response of weights excsum += numpy.einsum('r,r,nxr->nx', exc, rho, weight1) # response of grids coordinates excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms) * 2 rho = vxc = vrho = aow = None elif xctype == 'GGA': ao_deriv = 2 for atm_id, (coords, weight, weight1) in enumerate(grids_response_cc(grids)): ngrids = weight.size mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho = make_rho(0, ao[:4], mask, 'GGA') exc, vxc = ni.eval_xc(xc_code, rho, 0, relativity, 1, verbose)[:2] vtmp = numpy.zeros((3,nao,nao)) wv = numint._rks_gga_wv0(rho, vxc, weight) _gga_grad_sum_(vtmp, mol, ao, wv, mask, ao_loc) vmat += vtmp # response of weights excsum += numpy.einsum('r,r,nxr->nx', exc, rho[0], weight1) # response of grids coordinates excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms) * 2 rho = vxc = vrho = vsigma = wv = mat = None elif xctype == 'NLC': raise NotImplementedError('NLC') else: raise NotImplementedError('meta-GGA') # - sign because nabla_X = -nabla_x return excsum, -vmat
def test_make_mask(self): grid = gen_grid.Grids(h2o) grid.atom_grid = {"H": (10, 110), "O": (10, 110),} grid.build() coords = grid.coords*10. non0 = gen_grid.make_mask(h2o, coords) self.assertEqual(non0.sum(), 106) self.assertAlmostEqual(lib.finger(non0), -0.81399929716237085, 9)
def test_make_mask(self): grid = gen_grid.Grids(h2o) grid.atom_grid = { "H": (10, 110), "O": (10, 110), } grid.build() coords = grid.coords * 10. non0 = gen_grid.make_mask(h2o, coords) self.assertEqual(non0.sum(), 122) self.assertAlmostEqual(lib.fp(non0), 0.554275491306796, 9)
def test_make_mask(self): grid = gen_grid.Grids(h2o) grid.atom_grid = { "H": (10, 110), "O": (10, 110), } grid.build() coords = grid.coords * 10. non0 = gen_grid.make_mask(h2o, coords) self.assertEqual(non0.sum(), 106) self.assertAlmostEqual(lib.finger(non0), -0.81399929716237085, 9)
def make_e_psi1(pcmobj, dm, r_vdw, ui, ylm_1sph, cached_pol, Xvec, L): mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax nlm = (lmax + 1)**2 grids = pcmobj.grids if not (isinstance(dm, numpy.ndarray) and dm.ndim == 2): dm = dm[0] + dm[1] ni = numint.NumInt() max_memory = pcmobj.max_memory - lib.current_memory()[0] make_rho, nset, nao = ni._gen_rho_evaluator(mol, dm) den = numpy.empty((4, grids.weights.size)) ao_loc = mol.ao_loc_nr() vmat = numpy.zeros((3, nao, nao)) psi1 = numpy.zeros((natm, 3)) i1 = 0 for ia, (coords, weight, weight1) in enumerate(rks_grad.grids_response_cc(grids)): i0, i1 = i1, i1 + weight.size ao = ni.eval_ao(mol, coords, deriv=1) mask = gen_grid.make_mask(mol, coords) den[:, i0:i1] = make_rho(0, ao, mask, 'GGA') fak_pol, leak_idx = cached_pol[mol.atom_symbol(ia)] eta_nj = 0 p1 = 0 for l in range(lmax + 1): fac = 4 * numpy.pi / (l * 2 + 1) p0, p1 = p1, p1 + (l * 2 + 1) eta_nj += fac * numpy.einsum('mn,m->n', fak_pol[l], Xvec[ia, p0:p1]) psi1 -= numpy.einsum('n,n,zxn->zx', den[0, i0:i1], eta_nj, weight1) psi1[ia] -= numpy.einsum('xn,n,n->x', den[1:4, i0:i1], eta_nj, weight) vtmp = numpy.zeros((3, nao, nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight * eta_nj) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat += vtmp aoslices = mol.aoslice_by_atom() for ia in range(natm): shl0, shl1, p0, p1 = aoslices[ia] psi1[ia] += numpy.einsum('xij,ij->x', vmat[:, p0:p1], dm[p0:p1]) * 2 return psi1
def make_e_psi1(pcmobj, dm, r_vdw, ui, grids, ylm_1sph, cached_pol, L_X, L): mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax nlm = (lmax+1)**2 if not (isinstance(dm, numpy.ndarray) and dm.ndim == 2): dm = dm[0] + dm[1] ni = numint.NumInt() max_memory = pcmobj.max_memory - lib.current_memory()[0] make_rho, nset, nao = ni._gen_rho_evaluator(mol, dm) den = numpy.empty((4,grids.weights.size)) ao_loc = mol.ao_loc_nr() vmat = numpy.zeros((3,nao,nao)) psi1 = numpy.zeros((natm,3)) i1 = 0 for ia, (coords, weight, weight1) in enumerate(rks_grad.grids_response_cc(grids)): i0, i1 = i1, i1 + weight.size ao = ni.eval_ao(mol, coords, deriv=1) mask = gen_grid.make_mask(mol, coords) den[:,i0:i1] = make_rho(0, ao, mask, 'GGA') fak_pol, leak_idx = cached_pol[mol.atom_symbol(ia)] eta_nj = 0 p1 = 0 for l in range(lmax+1): fac = 4*numpy.pi/(l*2+1) p0, p1 = p1, p1 + (l*2+1) eta_nj += fac * numpy.einsum('mn,m->n', fak_pol[l], L_X[ia,p0:p1]) psi1 -= numpy.einsum('n,n,zxn->zx', den[0,i0:i1], eta_nj, weight1) psi1[ia] -= numpy.einsum('xn,n,n->x', den[1:4,i0:i1], eta_nj, weight) vtmp = numpy.zeros((3,nao,nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight*eta_nj) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat += vtmp aoslices = mol.aoslice_by_atom() for ia in range(natm): shl0, shl1, p0, p1 = aoslices[ia] psi1[ia] += numpy.einsum('xij,ij->x', vmat[:,p0:p1], dm[p0:p1]) * 2 return psi1
def get_vxc_full_response(ni, mol, grids, xc_code, dms, relativity=0, hermi=1, max_memory=2000, verbose=None): '''Full response including the response of the grids''' xctype = ni._xc_type(xc_code) make_rho, nset, nao = ni._gen_rho_evaluator(mol, dms, hermi) ao_loc = mol.ao_loc_nr() aoslices = mol.aoslice_by_atom() excsum = 0 vmat = numpy.zeros((2, 3, nao, nao)) if xctype == 'LDA': ao_deriv = 1 for atm_id, (coords, weight, weight1) \ in enumerate(rks_grad.grids_response_cc(grids)): ngrids = weight.size sh0, sh1 = aoslices[atm_id][:2] mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho_a = make_rho(0, ao[0], mask, 'LDA') rho_b = make_rho(1, ao[0], mask, 'LDA') exc, vxc = ni.eval_xc(xc_code, (rho_a, rho_b), 1, relativity, 1, verbose)[:2] vrho = vxc[0] vtmp = numpy.zeros((3, nao, nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight * vrho[:, 0]) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat[0] += vtmp excsum += numpy.einsum('r,r,nxr->nx', exc, rho_a + rho_b, weight1) excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[0]) * 2 vtmp = numpy.zeros((3, nao, nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight * vrho[:, 1]) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat[1] += vtmp excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[1]) * 2 rho = vxc = vrho = aow = None elif xctype == 'GGA': ao_deriv = 2 for atm_id, (coords, weight, weight1) \ in enumerate(rks_grad.grids_response_cc(grids)): ngrids = weight.size sh0, sh1 = aoslices[atm_id][:2] mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho_a = make_rho(0, ao[:4], mask, 'GGA') rho_b = make_rho(1, ao[:4], mask, 'GGA') exc, vxc = ni.eval_xc(xc_code, (rho_a, rho_b), 1, relativity, 1, verbose)[:2] wva, wvb = numint._uks_gga_wv0((rho_a, rho_b), vxc, weight) vtmp = numpy.zeros((3, nao, nao)) rks_grad._gga_grad_sum_(vtmp, mol, ao, wva, mask, ao_loc) vmat[0] += vtmp excsum += numpy.einsum('r,r,nxr->nx', exc, rho_a[0] + rho_b[0], weight1) excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[0]) * 2 vtmp = numpy.zeros((3, nao, nao)) rks_grad._gga_grad_sum_(vtmp, mol, ao, wvb, mask, ao_loc) vmat[1] += vtmp excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[1]) * 2 rho_a = rho_b = vxc = wva = wvb = None elif xctype == 'NLC': raise NotImplementedError('NLC') else: raise NotImplementedError('meta-GGA') # - sign because nabla_X = -nabla_x return excsum, -vmat
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 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 get_vxc_full_response(ni, mol, grids, xc_code, dms, relativity=0, hermi=1, max_memory=2000, verbose=None): '''Full response including the response of the grids''' xctype = ni._xc_type(xc_code) make_rho, nset, nao = ni._gen_rho_evaluator(mol, dms, hermi) ao_loc = mol.ao_loc_nr() aoslices = mol.aoslice_by_atom() excsum = 0 vmat = numpy.zeros((2,3,nao,nao)) if xctype == 'LDA': ao_deriv = 1 for atm_id, (coords, weight, weight1) \ in enumerate(rks_grad.grids_response_cc(grids)): ngrids = weight.size sh0, sh1 = aoslices[atm_id][:2] mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho_a = make_rho(0, ao[0], mask, 'LDA') rho_b = make_rho(1, ao[0], mask, 'LDA') exc, vxc = ni.eval_xc(xc_code, (rho_a,rho_b), 1, relativity, 1, verbose)[:2] vrho = vxc[0] vtmp = numpy.zeros((3,nao,nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight*vrho[:,0]) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat[0] += vtmp excsum += numpy.einsum('r,r,nxr->nx', exc, rho_a+rho_b, weight1) excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[0]) * 2 vtmp = numpy.zeros((3,nao,nao)) aow = numpy.einsum('pi,p->pi', ao[0], weight*vrho[:,1]) rks_grad._d1_dot_(vtmp, mol, ao[1:4], aow, mask, ao_loc, True) vmat[1] += vtmp excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[1]) * 2 rho = vxc = vrho = aow = None elif xctype == 'GGA': ao_deriv = 2 for atm_id, (coords, weight, weight1) \ in enumerate(rks_grad.grids_response_cc(grids)): ngrids = weight.size sh0, sh1 = aoslices[atm_id][:2] mask = gen_grid.make_mask(mol, coords) ao = ni.eval_ao(mol, coords, deriv=ao_deriv, non0tab=mask) rho_a = make_rho(0, ao[:4], mask, 'GGA') rho_b = make_rho(1, ao[:4], mask, 'GGA') exc, vxc = ni.eval_xc(xc_code, (rho_a,rho_b), 1, relativity, 1, verbose)[:2] wva, wvb = numint._uks_gga_wv0((rho_a,rho_b), vxc, weight) vtmp = numpy.zeros((3,nao,nao)) rks_grad._gga_grad_sum_(vtmp, mol, ao, wva, mask, ao_loc) vmat[0] += vtmp excsum += numpy.einsum('r,r,nxr->nx', exc, rho_a[0]+rho_b[0], weight1) excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[0]) * 2 vtmp = numpy.zeros((3,nao,nao)) rks_grad._gga_grad_sum_(vtmp, mol, ao, wvb, mask, ao_loc) vmat[1] += vtmp excsum[atm_id] += numpy.einsum('xij,ji->x', vtmp, dms[1]) * 2 rho_a = rho_b = vxc = wva = wvb = None elif xctype == 'NLC': raise NotImplementedError('NLC') else: raise NotImplementedError('meta-GGA') # - sign because nabla_X = -nabla_x return excsum, -vmat