Source code for pyscf.mcpdft.cmspdft

#!/usr/bin/env python
# Copyright 2014-2022 The PySCF Developers. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
from itertools import product
from scipy import linalg
from pyscf import gto, dft, ao2mo, fci, mcscf, lib
from pyscf.lib import logger, temporary_env
from pyscf.mcscf.addons import StateAverageMCSCFSolver, state_average_mix, state_average_mix_, state_average
from pyscf.fci import direct_spin1
from pyscf import mcpdft

[docs] def coulomb_tensor (mc, mo_coeff=None, ci=None, h2eff=None, eris=None): '''Compute w_IJKL = (tu|vx) D^IJ_tu D^KL_vx Args: mc : mcscf method instance Kwargs: mo_coeff : ndarray of shape (nao,nmo) Contains molecular orbital coefficients ci : list of ndarrays of shape (ndeta,ndetb) Contains CI vectors h2eff : ndarray of shape [ncas,]*4 Contains active-space ERIs eris : mc_ao2mo.ERI object Contains active-space ERIs. Ignored if h2eff is passed; if h2eff is not passed then it is constructed from eris.ppaa Returns: w : ndarray of shape [nroots,]*4 ''' if mo_coeff is None: mo_coeff=mc.mo_coeff if ci is None: ci = mc.ci # TODO: state-average mix extension ci = np.asarray (ci) ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas nroots, nocc = mc.fcisolver.nroots, ncore + ncas if h2eff is None: if eris is None: h2eff = mc.get_h2eff (mo_coeff=mo_coeff) else: h2eff = np.asarray (eris.ppaa[ncore:nocc,ncore:nocc,:,:]) h2eff = ao2mo.restore (1, h2eff, ncas) row, col = np.tril_indices (nroots) tdm1 = np.stack (mc.fcisolver.states_trans_rdm12(ci[col], ci[row], ncas, nelecas)[0], axis=0) w = np.tensordot (tdm1, h2eff, axes=2) w = np.tensordot (w, tdm1, axes=((1,2),(1,2))) return ao2mo.restore (1, w, nroots)
[docs] def e_coul (mc, mo_coeff=None, ci=None, h2eff=None, eris=None): '''Compute the sum of active-space Coulomb energies (the diabatizer function for CMS-PDFT) and its first and second derivatives Args: mc : mcscf method instance Kwargs: mo_coeff : ndarray of shape (nao,nmo) Contains molecular orbital coefficients ci : list of ndarrays of shape (ndeta,ndetb) Contains CI vectors h2eff : ndarray of shape [ncas,]*4 Contains active-space ERIs eris : mc_ao2mo.ERI object Contains active-space ERIs. Ignored if h2eff is passed; if h2eff is not passed then it is constructed from eris.ppaa Returns: Qaa : float sum of Coulomb energies dQaa : ndarray of shape npair = nroots*(nroots-1)/2 first derivatives of J wrt interstate rotation d2Qaa : ndarray of shape (npair,npair) Hessian of J wrt interstate rotation Qaa_update : callable Takes a unitary matrix of shape (nroots, nroots) and returns Qaa, dQaa, and d2Qaa as above using the stored Coulomb tensor intermediate from this function. ''' nroots = mc.fcisolver.nroots w0 = coulomb_tensor (mc, mo_coeff=mo_coeff, ci=ci, h2eff=h2eff, eris=eris) Qaa0, dQaa0, d2Qaa0 = _e_coul (w0, nroots) def Qaa_update (u=1): w1 = ao2mo.incore.full (w0, u, compact=False) return _e_coul (w1, nroots) return Qaa0, dQaa0, d2Qaa0, Qaa_update
def _e_coul (w_IJKL, nroots): npair = nroots * (nroots - 1) // 2 w_IJKK = np.diagonal (w_IJKL, axis1=2, axis2=3) w_IKJK = np.diagonal (w_IJKL, axis1=1, axis2=3) w_IJJJ = np.diagonal (w_IJKK, axis1=1, axis2=2) Qaa = np.trace (w_IJJJ) / 2.0 tril_mask = np.zeros ([nroots,nroots], dtype=np.bool_) tril_mask[np.tril_indices (nroots,k=-1)] = True dQaa = 2*(w_IJJJ.T-w_IJJJ)[tril_mask] # My sign convention is row idx = source state; col idx = dest # state, lower-triangular positive. The Newton iteration is designed # with this in mind and breaks if I flip it. However, regardless of # sign convention, the unitary operator parameterized this way # always comes out with destination on the rows and source on the # columns, because that's just what the word "operator" means: # |f> = U|i>. So when I exponentiate later, I transpose. Don't let # the fact that that transpose is mathematically the same as # flipping this sign confuse you: the sign here is CORRECT. v_IJ_K = -4*w_IKJK - 2*w_IJKK v_IJ_K += (w_IJJJ+w_IJJJ.T)[:,:,None] d2Qaa = np.zeros_like (w_IJKL) for k in range (nroots): d2Qaa[:,k,k,:] = v_IJ_K[:,:,k] d2Qaa -= d2Qaa.transpose (0,1,3,2) d2Qaa -= d2Qaa.transpose (1,0,2,3) tril_mask2 = np.logical_and.outer (tril_mask, tril_mask) d2Qaa = d2Qaa[tril_mask2].reshape (npair, npair) return Qaa, dQaa, d2Qaa
[docs] def e_coul_o0 (mc,ci): # Old implementation nroots = mc.fcisolver.nroots ncas, ncore = mc.ncas,mc.ncore nocc = ncas + ncore rows,col = np.tril_indices(nroots,k=-1) pairs = len(rows) mo_cas = mc.mo_coeff[:,ncore:nocc] ci_array = np.array(ci) casdm1 = mc.fcisolver.states_make_rdm1 (ci,ncas,mc.nelecas) dm1 = np.dot(casdm1,mo_cas.T) dm1 = np.dot(mo_cas,dm1).transpose(1,0,2) j = mc._scf.get_j (dm=dm1) e_coul = (j*dm1).sum((1,2)) / 2 trans12_tdm1, trans12_tdm2 = mc.fcisolver.states_trans_rdm12(ci_array[col], ci_array[rows],ncas,mc.nelecas) trans12_tdm1_array = np.array(trans12_tdm1) tdm1 = np.dot(trans12_tdm1_array,mo_cas.T) tdm1 = np.dot(mo_cas,tdm1).transpose(1,0,2) rowscol2ind = np.zeros ((nroots, nroots), dtype=int) rowscol2ind[(rows,col)] = list (range (pairs)) rowscol2ind += rowscol2ind.T rowscol2ind[np.diag_indices(nroots)] = -1 def w_klmn(k,l,m,n,dm,tdm): d = dm[k] if k==l else tdm[rowscol2ind[k,l]] dm1_g = mc._scf.get_j (dm=d) d = dm[m] if m==n else tdm[rowscol2ind[m,n]] w = (dm1_g*d).sum ((0,1)) return w def v_klmn(k,l,m,n,dm,tdm): if l==m: v = (w_klmn(k,n,k,k,dm,tdm)-w_klmn(k,n,l,l,dm,tdm) +w_klmn(n,k,n,n,dm,tdm)-w_klmn(k,n,m,m,dm,tdm) -4*w_klmn(k,l,m,n,dm,tdm)) else: v = 0 return v dg = mc._scf.get_j (dm=tdm1) grad1 = (dg*dm1[rows]).sum((1,2)) grad2 = (dg*dm1[col]).sum((1,2)) e_grad = np.zeros(pairs) e_grad = 2*(grad1 - grad2) e_hess = np.zeros((pairs,pairs)) for (i, (k,l)), (j, (m,n)) in product (enumerate (zip (rows, col)), repeat=2): e_hess[i,j] = (v_klmn(k,l,m,n,dm1,tdm1)+v_klmn(l,k,n,m,dm1,tdm1) -v_klmn(k,l,n,m,dm1,tdm1)-v_klmn(l,k,m,n,dm1,tdm1)) return sum (e_coul), e_grad, e_hess