Source code for pyscf.df.grad.casdm2_util

#!/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.
#
# Author: MRH <mrhermes@uchicago.edu>
#

import numpy as np
from scipy import linalg
from pyscf import gto
from pyscf import lib
from pyscf import scf
from pyscf import df
from pyscf import mcscf
from pyscf.ao2mo import _ao2mo
from pyscf.grad.rhf import GradientsBase
from pyscf.df.grad.rhf import _int3c_wrapper
from pyscf.ao2mo.outcore import balance_partition
from pyscf.ao2mo.incore import _conc_mos
from pyscf import __config__
from functools import reduce

[docs] def get_int3c_mo (mol, auxmol, mo_coeff, compact=getattr(__config__, 'df_df_DF_ao2mo_compact', True), max_memory=None): ''' Evaluate (P|uv) c_ui c_vj -> (P|ij) Args: mol: gto.Mole auxmol: gto.Mole, contains auxbasis mo_coeff: ndarray, list, or tuple containing MO coefficients if two ndarrays mo_coeff = (mo0, mo1) are provided, mo0 and mo1 are used for the two AO dimensions Kwargs: compact: bool If true, will return only unique ERIs along the two MO dimensions. Does nothing if mo_coeff contains two different sets of orbitals. max_memory: int Maximum memory consumption in MB Returns: int3c: ndarray of shape (naux, nmo0, nmo1) or (naux, nmo*(nmo+1)//2) ''' nao, naux, nbas = mol.nao, auxmol.nao, mol.nbas npair = nao * (nao + 1) // 2 if max_memory is None: max_memory = mol.max_memory # Separate mo_coeff if isinstance (mo_coeff, np.ndarray) and mo_coeff.ndim == 2: mo0 = mo1 = mo_coeff else: mo0, mo1 = mo_coeff[0], mo_coeff[1] nmo0, nmo1 = mo0.shape[-1], mo1.shape[-1] mosym, nmo_pair, mo_conc, mo_slice = _conc_mos(mo0, mo1, compact=compact) # (P|uv) -> (P|ij) get_int3c = _int3c_wrapper(mol, auxmol, 'int3c2e', 's2ij') int3c = np.zeros ((naux, nmo_pair), dtype=mo0.dtype) max_memory -= lib.current_memory()[0] blksize = int (min (max (max_memory * 1e6 / 8 / (npair*2), 20), 240)) aux_loc = auxmol.ao_loc aux_ranges = balance_partition(aux_loc, blksize) for shl0, shl1, nL in aux_ranges: int3c_ao = get_int3c ((0, nbas, 0, nbas, shl0, shl1)) # (uv|P) p0, p1 = aux_loc[shl0], aux_loc[shl1] int3c_ao = int3c_ao.T # should make me c-contiguous int3c[p0:p1] = _ao2mo.nr_e2(int3c_ao, mo_conc, mo_slice, aosym='s2', mosym=mosym, out=int3c[p0:p1]) int3c_ao = None # Shape and return if 's1' in mosym: int3c = int3c.reshape (naux, nmo0, nmo1) return int3c
[docs] def solve_df_rdm2 (mc_or_mc_grad, mo_cas=None, ci=None, casdm2=None): ''' Solve (P|Q)d_Qij = (P|kl)d_ijkl for d_Qij in the MO basis. Args: mc_or_mc_grad: DF-MCSCF energy or gradients method object. Kwargs: mo_cas: ndarray, tuple, or list containing active mo coefficients. if two ndarrays mo_cas = (mo0, mo1) are provided, mo0 and mo1 are assumed to correspond to casdm2's LAST two dimensions in that order, regardless of len (ci) or len (casdm2). (This will facilitate SA-CASSCF gradients at some point. Note the difference from grad_elec_dferi!) ci: ndarray, tuple, or list containing CI coefficients in mo_cas basis. Not used if casdm2 is provided. casdm2: ndarray, tuple, or list containing rdm2 in mo_cas basis. Computed by mc_or_mc_grad.fcisolver.make_rdm12 (ci,...) if omitted. compact: bool If true, tries to return d_Pqr in lower-triangular form if possible Returns: dfcasdm2: ndarray or list containing 3-center 2RDM, d_Pqr, where P is auxbasis index and q, r are mo_cas basis indices. ''' # Initialize mol and auxmol mol = mc_or_mc_grad.mol if isinstance (mc_or_mc_grad, GradientsBase): mc = mc_or_mc_grad.base else: mc = mc_or_mc_grad auxmol = mc.with_df.auxmol if auxmol is None: auxmol = df.addons.make_auxmol(mc.with_df.mol, mc.with_df.auxbasis) naux = auxmol.nao ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas nocc = ncore + ncas # Initialize casdm2 and mo_cas if mo_cas is None: mo_cas = mc.mo_coeff[:,ncore:nocc] if ci is None: ci = mc.ci if casdm2 is None: casdm2 = mc.fcisolver.make_rdm12 (ci, ncas, nelecas) if np.asarray (casdm2).ndim == 4: casdm2 = [casdm2] # (P|Q) and (P|ij) int2c = linalg.cho_factor(auxmol.intor('int2c2e', aosym='s1')) int3c = get_int3c_mo (mol, auxmol, mo_cas, compact=True, max_memory=mc_or_mc_grad.max_memory) # Solve (P|Q) d_Qij = (P|kl) d_ijkl dfcasdm2 = [] for dm2 in casdm2: nmo = tuple (dm2.shape) # make sure it copies if int3c.ndim == 2: # I'm not going to use the memory-efficient version because this is meant to be small nmo_pair = nmo[2] * (nmo[2] + 1) // 2 dm2 = dm2.copy ().reshape ((-1, nmo[2], nmo[3])) dm2 += dm2.transpose (0,2,1) diag_idx = np.arange(nmo[-1]) diag_idx = diag_idx * (diag_idx+1) // 2 + diag_idx dm2 = lib.pack_tril (np.ascontiguousarray (dm2)) dm2[:,diag_idx] *= 0.5 elif int3c.ndim == 3: nmo_pair = nmo[2] * nmo[3] int3c = int3c.reshape (naux, nmo_pair) else: raise RuntimeError ('int3c.shape = {}'.format (int3c.shape)) dm2 = dm2.reshape (nmo[0]*nmo[1], nmo_pair).T int3c_dm2 = np.dot (int3c, dm2) dfcasdm2.append (linalg.cho_solve (int2c, int3c_dm2).reshape (naux, nmo[0], nmo[1])) return dfcasdm2
[docs] def solve_df_eri (mc_or_mc_grad, mo_cas=None, compact=True): ''' Solve (P|Q) g_Qij = (P|ij) for g_Qij using MOs i,j. I mean this should be a basic function but whatever. ''' # Initialize mol and auxmol mol = mc_or_mc_grad.mol if isinstance (mc_or_mc_grad, GradientsBase): mc = mc_or_mc_grad.base else: mc = mc_or_mc_grad auxmol = mc.with_df.auxmol if auxmol is None: auxmol = df.addons.make_auxmol(mc.with_df.mol, mc.with_df.auxbasis) naux, ncore, ncas = auxmol.nao, mc.ncore, mc.ncas nocc = ncore + ncas if mo_cas is None: mo_cas = mc.mo_coeff[:,ncore:nocc] if isinstance (mo_cas, np.ndarray) and mo_cas.ndim == 2: nmo = (mo_cas.shape[1], mo_cas.shape[1]) else: nmo = (mo_cas[0].shape[1], mo_cas[1].shape[1]) # (P|Q) and (P|ij) int2c = linalg.cho_factor(auxmol.intor('int2c2e', aosym='s1')) int3c = get_int3c_mo (mol, auxmol, mo_cas, compact=compact, max_memory=mc_or_mc_grad.max_memory) # Solve (P|Q) g_Qij = (P|ij) dferi = linalg.cho_solve (int2c, int3c) if int3c.ndim == 2: dferi = dferi.reshape (naux, -1) else: dferi = dferi.reshape (naux, nmo[0], nmo[1]) return dferi
[docs] def energy_elec_dferi (mc, mo_cas=None, ci=None, dfcasdm2=None, casdm2=None): ''' Evaluate E2 = (P|ij) d_Pij / 2, where d_Pij is the DF-2rdm obtained by solve_df_rdm2. For testing purposes. Note that the only index permutation this function understands is (P|ij) = (P|ji) if i and j span the same range of MOs. The caller has to handle everything else, including, for instance, multiplication by 2 if a nonsymmetric slice of the 2RDM is used. Args: mc: MC-SCF energy method object Kwargs: mo_cas: ndarray, list, or tuple containing active-space MO coefficients If a tuple of length 2, the same pair of MO sets are assumed to apply to the internally-contracted and externally-contracted indices of the DF-2rdm: (P|Q)d_Qij = (P|kl)d_ijkl -> (P|Q)d_Qij = (P|ij)d_ijij If a tuple of length 4, the 4 MO sets are applied to ijkl above in that order (first two external, last two internal). ci: ndarray, tuple, or list containing CI coefficients in mo_cas basis. Not used if dfcasdm2 is provided. dfcasdm2: ndarray, tuple, or list containing DF-2rdm in mo_cas basis. Computed by solve_df_rdm2 if omitted. casdm2: ndarray, tuple, or list containing rdm2 in mo_cas basis. Computed by mc_or_mc_grad.fcisolver.make_rdm12 (ci,...) if omitted. Returns: energy: list List of energies corresponding to the dfcasdm2s, E = (P|ij) d_Pij / 2 = (P|ij) (P|Q)^-1 (Q|kl) d_ijkl / 2 ''' if isinstance (mc, GradientsBase): mc = mc.base if mo_cas is None: ncore = mc.ncore nocc = ncore + mc.ncas mo_cas = mc.mo_coeff[:,ncore:nocc] if isinstance (mo_cas, np.ndarray) and mo_cas.ndim == 2: mo_cas = (mo_cas,)*4 elif len (mo_cas) == 2: mo_cas = (mo_cas[0], mo_cas[1], mo_cas[0], mo_cas[1]) elif len (mo_cas) == 4: mo_cas = tuple (mo_cas) else: raise RuntimeError ('Invalid shape of np.asarray (mo_cas): {}'.format (mo_cas.shape)) nmo = [mo.shape[1] for mo in mo_cas] if ci is None: ci = mc.ci if dfcasdm2 is None: dfcasdm2 = solve_df_rdm2 (mc, mo_cas=mo_cas[2:], ci=ci, casdm2=casdm2) int3c = get_int3c_mo (mc.mol, mc.with_df.auxmol, mo_cas[:2], compact=True, max_memory=mc.max_memory) symm = (int3c.ndim == 2) int3c = np.ravel (int3c) energy = [] for dm2 in dfcasdm2: if symm: dm2 += dm2.transpose (0,2,1) diag_idx = np.arange(nmo[1]) diag_idx = diag_idx * (diag_idx+1) // 2 + diag_idx dm2 = lib.pack_tril (np.ascontiguousarray (dm2)) dm2[:,diag_idx] *= 0.5 energy.append (np.dot (int3c, dm2.ravel ()) / 2) return energy
[docs] def gfock_dferi (mc, mo_cas=None, ci=None, dfcasdm2=None, casdm2=None, max_memory=None, ao_basis=True): ''' Evaluate F_ij = (P|ik) d_Pjk - this was a giant reinvention of the wheel that didn't need to happen because with_df._cderi is plenty good enough to calculate gfock. Oh well. Args: mc_grad: MC-SCF gradients method object Kwargs: mc_cas: ndarray, list, or tuple containing active-space MO coefficients If a tuple of length 2, the same pair of MO sets are assumed to apply to the internally-contracted and externally-contracted indices of the DF-2rdm: (P|Q)d_Qij = (P|kl)d_ijkl -> (P|Q)d_Qij = (P|ij)d_ijij If a tuple of length 4, the 4 MO sets are applied to ijkl above in that order (first two external, last two internal). ci: ndarray, tuple, or list containing CI coefficients in mo_cas basis. Not used if dfcasdm2 is provided. dfcasdm2: ndarray, tuple, or list containing DF-2rdm in mo_cas basis. Computed by solve_df_rdm2 if omitted. casdm2: ndarray, tuple, or list containing rdm2 in mo_cas basis. Computed by mc_grad.fcisolver.make_rdm12 (ci,...) if omitted. max_memory: int Maximum memory usage in MB ao_basis: bool If true, return gfock in AO basis Returns: gfock: ndarray of shape (nset, nmo[0], nmo[1]) or (nset, nao, nao) ''' if isinstance (mc, GradientsBase): mc = mc.base if mo_cas is None: ncore = mc.ncore nocc = ncore + mc.ncas mo_cas = mc.mo_coeff[:,ncore:nocc] if isinstance (mo_cas, np.ndarray) and mo_cas.ndim == 2: mo_cas = (mo_cas,)*4 elif len (mo_cas) == 2: mo_cas = (mo_cas[0], mo_cas[1], mo_cas[0], mo_cas[1]) elif len (mo_cas) == 4: mo_cas = tuple (mo_cas) else: raise RuntimeError ('Invalid shape of np.asarray (mo_cas): {}'.format (mo_cas.shape)) if ci is None: ci = mc.ci if dfcasdm2 is None: dfcasdm2 = solve_df_rdm2 (mc, mo_cas=mo_cas[2:], ci=ci, casdm2=casdm2) dfcasdm2 = np.asarray (dfcasdm2) int3c = get_int3c_mo (mc.mol, mc.with_df.auxmol, mo_cas[:2], compact=False, max_memory=max_memory) assert (int3c.ndim == 3) gfock = np.einsum ('pik,npkj->nij', int3c, dfcasdm2) if ao_basis: gfock = np.einsum ('ui,nij,vj->nuv', mo_cas[0], gfock, mo_cas[2].conjugate ()) return gfock
[docs] def grad_elec_auxresponse_dferi (mc_grad, mo_cas=None, ci=None, dfcasdm2=None, casdm2=None, atmlst=None, max_memory=None, dferi=None, incl_2c=True): ''' Evaluate the [(P'|ij) + (P'|Q) g_Qij] d_Pij contribution to the electronic gradient, where d_Pij is the DF-2RDM obtained by solve_df_rdm2 and g_Qij solves (P|Q) g_Qij = (P|ij). The caller must symmetrize if necessary (i.e., (P|Q) d_Qij = (P|kl) d_ijkl <-> (P|Q) d_Qkl = (P|ij) d_ijkl in order to get at Q'). Args: mc_grad: MC-SCF gradients method object Kwargs: mc_cas: ndarray, list, or tuple containing active-space MO coefficients If a tuple of length 2, the same pair of MO sets are assumed to apply to the internally-contracted and externally-contracted indices of the DF-2rdm: (P|Q)d_Qij = (P|kl)d_ijkl -> (P|Q)d_Qij = (P|ij)d_ijij If a tuple of length 4, the 4 MO sets are applied to ijkl above in that order (first two external, last two internal). ci: ndarray, tuple, or list containing CI coefficients in mo_cas basis. Not used if dfcasdm2 is provided. dfcasdm2: ndarray, tuple, or list containing DF-2rdm in mo_cas basis. Computed by solve_df_rdm2 if omitted. casdm2: ndarray, tuple, or list containing rdm2 in mo_cas basis. Computed by mc_grad.fcisolver.make_rdm12 (ci,...) if omitted. atmlst: list of integers List of nonfrozen atoms, as in grad_elec functions. Defaults to list (range (mol.natm)) max_memory: int Maximum memory usage in MB dferi: ndarray containing g_Pij for optional precalculation incl_2c: bool If False, omit the terms depending on (P'|Q) Returns: dE: list of ndarray of shape (len (atmlst), 3) ''' if isinstance (mc_grad, GradientsBase): mc = mc_grad.base else: mc = mc_grad mol = mc_grad.mol auxmol = mc.with_df.auxmol ncore, ncas, nao, naux, nbas = mc.ncore, mc.ncas, mol.nao, auxmol.nao, mol.nbas nocc = ncore + ncas npair = nao * (nao + 1) // 2 if mo_cas is None: mo_cas = mc.mo_coeff[:,ncore:nocc] if max_memory is None: max_memory = mc.max_memory if isinstance (mo_cas, np.ndarray) and mo_cas.ndim == 2: mo_cas = (mo_cas,)*4 elif len (mo_cas) == 2: mo_cas = (mo_cas[0], mo_cas[1], mo_cas[0], mo_cas[1]) elif len (mo_cas) == 4: mo_cas = tuple (mo_cas) else: raise RuntimeError ('Invalid shape of np.asarray (mo_cas): {}'.format (mo_cas.shape)) nmo = [mo.shape[1] for mo in mo_cas] if atmlst is None: atmlst = list (range (mol.natm)) if ci is None: ci = mc.ci if dfcasdm2 is None: dfcasdm2 = solve_df_rdm2 (mc, mo_cas=mo_cas[2:], ci=ci, casdm2=casdm2) nset = len (dfcasdm2) dE = np.zeros ((nset, naux, 3)) dfcasdm2 = np.array (dfcasdm2) # Shape dfcasdm2 mosym, nmo_pair, mo_conc, mo_slice = _conc_mos(mo_cas[0], mo_cas[1], compact=True) if 's2' in mosym: assert (nmo[0] == nmo[1]), 'How did I get {} with nmo[0] = {} and nmo[1] = {}'.format (mosym, nmo[0], nmo[1]) dfcasdm2 = dfcasdm2.reshape (nset*naux, nmo[0], nmo[1]) dfcasdm2 += dfcasdm2.transpose (0,2,1) diag_idx = np.arange(nmo[0]) diag_idx = diag_idx * (diag_idx+1) // 2 + diag_idx dfcasdm2 = lib.pack_tril (np.ascontiguousarray (dfcasdm2)) dfcasdm2[:,diag_idx] *= 0.5 dfcasdm2 = dfcasdm2.reshape (nset, naux, nmo_pair) # Do 2c part. Assume memory is no object if incl_2c: int2c = auxmol.intor('int2c2e_ip1') if (dferi is None): dferi = solve_df_eri (mc, mo_cas=mo_cas[:2]).reshape (naux, nmo_pair) int3c = np.dot (int2c, dferi) # (P'|Q) g_Qij dE += lib.einsum ('npi,xpi->npx', dfcasdm2, int3c) # d_Pij (P'|Q) g_Qij int2c = int3c = dferi = None # Set up 3c part get_int3c = _int3c_wrapper(mol, auxmol, 'int3c2e_ip2', 's2ij') max_memory -= lib.current_memory()[0] blklen = 6*npair blksize = int (min (max (max_memory * 1e6 / 8 / blklen, 20), 240)) aux_loc = auxmol.ao_loc aux_ranges = balance_partition(aux_loc, blksize) # Iterate over auxbasis range and do 3c part for shl0, shl1, nL in aux_ranges: p0, p1 = aux_loc[shl0], aux_loc[shl1] int3c = get_int3c ((0, nbas, 0, nbas, shl0, shl1)) # (uv|P'); shape = (3,npair,p1-p0) int3c = np.ascontiguousarray (int3c.transpose (0,2,1).reshape (3*(p1-p0), npair)) int3c = _ao2mo.nr_e2(int3c, mo_conc, mo_slice, aosym='s2', mosym=mosym) int3c = int3c.reshape (3,p1-p0,nmo_pair) int3c = np.ascontiguousarray (int3c) dE[:,p0:p1,:] -= lib.einsum ('npi,xpi->npx', dfcasdm2[:,p0:p1,:], int3c) # Ravel to atoms auxslices = auxmol.aoslice_by_atom () dE = np.array ([dE[:,p0:p1].sum (axis=1) for p0, p1 in auxslices[:,2:]]).transpose (1,0,2) return np.ascontiguousarray (dE)
[docs] def grad_elec_dferi (mc_grad, mo_cas=None, ci=None, dfcasdm2=None, casdm2=None, atmlst=None, max_memory=None): ''' Evaluate the (P|i'j) d_Pij contribution to the electronic gradient, where d_Pij is the DF-2RDM obtained by solve_df_rdm2. The caller must symmetrize (i.e., [(P|i'j) + (P|ij')] d_Pij / 2) if necessary. Args: mc_grad: MC-SCF gradients method object Kwargs: mc_cas: ndarray, list, or tuple containing active-space MO coefficients If a tuple of length 2, the same pair of MO sets are assumed to apply to the internally-contracted and externally-contracted indices of the DF-2rdm: (P|Q)d_Qij = (P|kl)d_ijkl -> (P|Q)d_Qij = (P|ij)d_ijij If a tuple of length 4, the 4 MO sets are applied to ijkl above in that order (first two external, last two internal). ci: ndarray, tuple, or list containing CI coefficients in mo_cas basis. Not used if dfcasdm2 is provided. dfcasdm2: ndarray, tuple, or list containing DF-2rdm in mo_cas basis. Computed by solve_df_rdm2 if omitted. casdm2: ndarray, tuple, or list containing rdm2 in mo_cas basis. Computed by mc_grad.fcisolver.make_rdm12 (ci,...) if omitted. atmlst: list of integers List of nonfrozen atoms, as in grad_elec functions. Defaults to list (range (mol.natm)) max_memory: int Maximum memory usage in MB Returns: dE: ndarray of shape (len (dfcasdm2), len (atmlst), 3) ''' if isinstance (mc_grad, GradientsBase): mc = mc_grad.base else: mc = mc_grad mol = mc_grad.mol auxmol = mc.with_df.auxmol ncore, ncas, nao, nbas = mc.ncore, mc.ncas, mol.nao, mol.nbas nocc = ncore + ncas if mo_cas is None: mo_cas = mc.mo_coeff[:,ncore:nocc] if max_memory is None: max_memory = mc_grad.max_memory if isinstance (mo_cas, np.ndarray) and mo_cas.ndim == 2: mo_cas = (mo_cas,)*4 elif len (mo_cas) == 2: mo_cas = (mo_cas[0], mo_cas[1], mo_cas[0], mo_cas[1]) elif len (mo_cas) == 4: mo_cas = tuple (mo_cas) else: raise RuntimeError ('Invalid shape of np.asarray (mo_cas): {}'.format (mo_cas.shape)) nmo = [mo.shape[1] for mo in mo_cas] if atmlst is None: atmlst = list (range (mol.natm)) if ci is None: ci = mc.ci if dfcasdm2 is None: dfcasdm2 = solve_df_rdm2 (mc, mo_cas=mo_cas[2:], ci=ci, casdm2=casdm2) # d_Pij nset = len (dfcasdm2) dE = np.zeros ((nset, nao, 3)) dfcasdm2 = np.array (dfcasdm2) # Set up (P|u'v) calculation get_int3c = _int3c_wrapper(mol, auxmol, 'int3c2e_ip1', 's1') max_memory -= lib.current_memory()[0] blklen = nao*((3*nao) + (3*nmo[1]) + (nset*nmo[1])) blksize = int (min (max (max_memory * 1e6 / 8 / blklen, 20), 240)) aux_loc = auxmol.ao_loc aux_ranges = balance_partition(aux_loc, blksize) # Iterate over auxbasis range for shl0, shl1, nL in aux_ranges: p0, p1 = aux_loc[shl0], aux_loc[shl1] int3c = get_int3c ((0, nbas, 0, nbas, shl0, shl1)) # (u'v|P); shape = (3,nao,nao,p1-p0) intbuf = lib.einsum ('xuvp,vj->xupj', int3c, mo_cas[1]) dm2buf = lib.einsum ('ui,npij->nupj', mo_cas[0], dfcasdm2[:,p0:p1,:,:]) dE -= np.einsum ('nupj,xupj->nux', dm2buf, intbuf) intbuf = dm2buf = None intbuf = lib.einsum ('xuvp,vj->xupj', int3c, mo_cas[0]) dm2buf = lib.einsum ('uj,npij->nupi', mo_cas[1], dfcasdm2[:,p0:p1,:,:]) dE -= np.einsum ('nupj,xupj->nux', dm2buf, intbuf) intbuf = dm2buf = int3c = None aoslices = mol.aoslice_by_atom () dE = np.array ([dE[:,p0:p1].sum (axis=1) for p0, p1 in aoslices[:,2:]]).transpose (1,0,2) return np.ascontiguousarray (dE)
if __name__ == '__main__': from pyscf.tools import molden from pyscf.lib import logger, param h2co_casscf66_631g_xyz = '''C 0.534004 0.000000 0.000000 O -0.676110 0.000000 0.000000 H 1.102430 0.000000 0.920125 H 1.102430 0.000000 -0.920125''' mol = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = False, verbose = logger.INFO, output = 'h2co_casscf66_631g_grad.log') mf = scf.RHF (mol).density_fit (auxbasis = df.aug_etb (mol)).run () mc = mcscf.CASSCF (mf, 11, 16) mc.conv_tol = 1e-10 mc.kernel () ncore, ncas = mc.ncore, mc.ncas nocc = ncore + ncas nmo = mc.mo_coeff.shape[-1] mo_cas = mc.mo_coeff[:,ncore:nocc] casdm1, casdm2 = mc.fcisolver.make_rdm12 (mc.ci, mc.ncas, mc.nelecas) eri_cas = mc.with_df.ao2mo (mo_cas, compact=False).reshape ((ncas,)*4) # Full energy e2_ref = np.dot (casdm2.ravel (), eri_cas.ravel ()) / 2 e2_test = energy_elec_dferi (mc, mo_cas=mo_cas, casdm2=casdm2)[0] e2_err = e2_test - e2_ref print ("Testing full energy calculation: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) # 2-RDM slices (see: SA-CASSCF gradients) eri_cas_sl = eri_cas[0:2,1:3,0:4,1:6] casdm2_sl = casdm2[0:2,1:3,0:4,1:6] mo_cas_sl = (mo_cas[:,0:2], mo_cas[:,1:3], mo_cas[:,0:4], mo_cas[:,1:6]) e2_ref = np.dot (casdm2_sl.ravel (), eri_cas_sl.ravel ()) e2_test = energy_elec_dferi (mc, mo_cas=mo_cas_sl, casdm2=casdm2_sl)[0] * 2 e2_err = e2_test - e2_ref print ("Testing slice calculation: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) # 2-RDM slice complex conjugate casdm2_sl = casdm2[1:3,0:2,1:6,0:4] eri_cas_sl = eri_cas[1:3,0:2,1:6,0:4] mo_cas_sl = (mo_cas[:,1:3], mo_cas[:,0:2], mo_cas[:,1:6], mo_cas[:,0:4]) e2_test = np.dot (casdm2_sl.ravel (), eri_cas_sl.ravel ()) e2_err = e2_test - e2_ref print ("Testing slice c.c. ERI: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) e2_test = energy_elec_dferi (mc, mo_cas=mo_cas_sl, casdm2=casdm2_sl)[0] * 2 print ("Testing slice c.c. DFERI: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) # 2-RDM slice electron interchange casdm2_sl = casdm2[0:4,1:6,0:2,1:3] eri_cas_sl = eri_cas[0:4,1:6,0:2,1:3] mo_cas_sl = (mo_cas[:,0:4], mo_cas[:,1:6], mo_cas[:,0:2], mo_cas[:,1:3]) e2_test = np.dot (casdm2_sl.ravel (), eri_cas_sl.ravel ()) e2_err = e2_test - e2_ref print ("Testing slice 1<->2 ERI: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) e2_test = energy_elec_dferi (mc, mo_cas=mo_cas_sl, casdm2=casdm2_sl)[0] * 2 print ("Testing slice 1<->2 DFERI: e2_test - e2_ref = {:13.6e} - {:13.6e} = {:13.6e}".format ( e2_test, e2_ref, e2_err)) mc_grad_conv = mcscf.CASSCF (scf.RHF (mol).run (), 11, 16) mc_grad_conv.conv_tol = 1e-10 mc_grad_conv = mc_grad_conv.run ().nuc_grad_method () dm1 = mc.make_rdm1 () hcore_deriv = mc_grad_conv.hcore_generator (mol) dE_conv = mc_grad_conv.kernel () ### dE_nuc = mc_grad_conv.grad_nuc (atmlst=list (range (mol.natm))) dE_hcore = np.stack ([np.dot (hcore_deriv (iatm).reshape (3,-1), dm1.ravel ()) for iatm in range (mol.natm)], axis=0) dE_ao = grad_elec_dferi (mc, mo_cas=mo_cas, casdm2=casdm2) dE_aux = grad_elec_auxresponse_dferi (mc, mo_cas=mo_cas, casdm2=casdm2) h1 = reduce (np.dot, (mc.mo_coeff.conjugate ().T, mc.get_hcore (), mo_cas)) gfock = np.zeros ((nmo,nmo), dtype=mc.mo_coeff.dtype) gfock[:,:nocc] += np.dot (h1, casdm1) gfock[:,:nocc] += np.squeeze (gfock_dferi (mc, mo_cas=(mc.mo_coeff, mo_cas, mo_cas, mo_cas), casdm2=casdm2, ao_basis=False)) gfock = (gfock + gfock.T) / 2 gfock = reduce (np.dot, (mc.mo_coeff, gfock, mc.mo_coeff.conjugate ().T)) dE_renorm = np.einsum ('xij,ij->xi', mc_grad_conv.get_ovlp (mol), gfock) aoslices = mol.aoslice_by_atom () dE_renorm = -2*np.array ([dE_renorm[:,p0:p1].sum (axis=1) for p0, p1 in aoslices[:,2:]]) dE = dE_ao + dE_aux + dE_nuc + dE_hcore + dE_renorm ### #from pyscf.grad.numeric import Gradients as NumGrad #mc_numgrad = NumGrad (mc) dE_num = 0 print ('Putative analytical DF-CASSCF gradient:\n', dE) print ('Numerical DF-CASSCF gradient:\n', dE_num) print ('Analytical CASSCF gradient:\n', dE_conv) #print ('DF-CASSCF analytical-numerical disagreement:\n', dE-dE_num) print ('Analytical DF-CASSCF - CASSCF disagreement:\n', dE-dE_conv)