Source code for pyscf.mcscf.umc1step

#!/usr/bin/env python
# Copyright 2014-2019 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: Qiming Sun <osirpt.sun@gmail.com>
#

'''
UCASSCF (CASSCF without spin-degeneracy between alpha and beta orbitals)
1-step optimization algorithm
'''

import sys

from functools import reduce
import numpy
import pyscf.gto
import pyscf.scf
from pyscf import lib
from pyscf.lib import logger
from pyscf.mcscf import ucasci
from pyscf.mcscf.mc1step import expmat, rotate_orb_cc, max_stepsize_scheduler, as_scanner
from pyscf.mcscf import umc_ao2mo
from pyscf.mcscf import chkfile
from pyscf import __config__

#FIXME:  when the number of core orbitals are different for alpha and beta,
# the convergence are very unstable and slow

# gradients, hessian operator and hessian diagonal
[docs] def gen_g_hop(casscf, mo, u, casdm1s, casdm2s, eris): ncas = casscf.ncas ncore = casscf.ncore nocc = (ncas + ncore[0], ncas + ncore[1]) nmo = casscf.mo_coeff[0].shape[1] dm1 = numpy.zeros((2,nmo,nmo)) idx = numpy.arange(ncore[0]) dm1[0,idx,idx] = 1 idx = numpy.arange(ncore[1]) dm1[1,idx,idx] = 1 dm1[0,ncore[0]:nocc[0],ncore[0]:nocc[0]] = casdm1s[0] dm1[1,ncore[1]:nocc[1],ncore[1]:nocc[1]] = casdm1s[1] # part2, part3 vhf_c = eris.vhf_c vhf_ca = ((vhf_c[0] + numpy.einsum('uvpq,uv->pq', eris.aapp, casdm1s[0]) - numpy.einsum('upqv,uv->pq', eris.appa, casdm1s[0]) + numpy.einsum('uvpq,uv->pq', eris.AApp, casdm1s[1])), (vhf_c[1] + numpy.einsum('uvpq,uv->pq', eris.aaPP, casdm1s[0]) + numpy.einsum('uvpq,uv->pq', eris.AAPP, casdm1s[1]) - numpy.einsum('upqv,uv->pq', eris.APPA, casdm1s[1])),) ################# gradient ################# hdm2 = [(numpy.einsum('tuvw,vwpq->tupq', casdm2s[0], eris.aapp) + numpy.einsum('tuvw,vwpq->tupq', casdm2s[1], eris.AApp)), (numpy.einsum('vwtu,vwpq->tupq', casdm2s[1], eris.aaPP) + numpy.einsum('tuvw,vwpq->tupq', casdm2s[2], eris.AAPP))] hcore = casscf.get_hcore() h1e_mo = (reduce(numpy.dot, (mo[0].T, hcore[0], mo[0])), reduce(numpy.dot, (mo[1].T, hcore[1], mo[1]))) g = [numpy.dot(h1e_mo[0], dm1[0]), numpy.dot(h1e_mo[1], dm1[1])] def gpart(m): g[m][:,:ncore[m]] += vhf_ca[m][:,:ncore[m]] g[m][:,ncore[m]:nocc[m]] += \ numpy.einsum('vuuq->qv', hdm2[m][:,:,ncore[m]:nocc[m]]) \ + numpy.dot(vhf_c[m][:,ncore[m]:nocc[m]], casdm1s[m]) gpart(0) gpart(1) def gorb_update(u, fcivec): r0 = casscf.pack_uniq_var(u) return g_orb + h_op(r0) ############## hessian, diagonal ########### # part1 tmp = casdm2s[0].transpose(1,2,0,3) + casdm2s[0].transpose(0,2,1,3) hdm2apap = numpy.einsum('uvtw,tpqw->upvq', tmp, eris.appa) hdm2apap += hdm2[0].transpose(0,2,1,3) hdm2[0] = hdm2apap tmp = casdm2s[1].transpose(1,2,0,3) + casdm2s[1].transpose(0,2,1,3) # (jp|RK) *[e(jq,SK) + e(jq,LS)] => qSpR hdm2apAP = numpy.einsum('uvtw,tpqw->upvq', tmp, eris.apPA) # (JP|rk) *[e(sk,JQ) + e(ls,JQ)] => QsPr #hdm2APap = hdm2apAP.transpose(2,3,0,1) tmp = casdm2s[2].transpose(1,2,0,3) + casdm2s[2].transpose(0,2,1,3) hdm2APAP = numpy.einsum('uvtw,tpqw->upvq', tmp, eris.APPA) hdm2APAP += hdm2[1].transpose(0,2,1,3) hdm2[1] = hdm2APAP # part7 # h_diag[0] ~ alpha-alpha h_diag = [numpy.einsum('ii,jj->ij', h1e_mo[0], dm1[0]) - h1e_mo[0] * dm1[0], numpy.einsum('ii,jj->ij', h1e_mo[1], dm1[1]) - h1e_mo[1] * dm1[1]] h_diag[0] = h_diag[0] + h_diag[0].T h_diag[1] = h_diag[1] + h_diag[1].T # part8 idx = numpy.arange(nmo) g_diag = g[0].diagonal() h_diag[0] -= g_diag + g_diag.reshape(-1,1) h_diag[0][idx,idx] += g_diag * 2 g_diag = g[1].diagonal() h_diag[1] -= g_diag + g_diag.reshape(-1,1) h_diag[1][idx,idx] += g_diag * 2 # part2, part3 def fpart2(m): v_diag = vhf_ca[m].diagonal() # (pr|kl) * e(sq,lk) h_diag[m][:,:ncore[m]] += v_diag.reshape(-1,1) h_diag[m][:ncore[m]] += v_diag idx = numpy.arange(ncore[m]) # (V_{qr} delta_{ps} + V_{ps} delta_{qr}) delta_{pr} delta_{sq} h_diag[m][idx,idx] -= v_diag[:ncore[m]] * 2 fpart2(0) fpart2(1) def fpart3(m): # V_{pr} e_{sq} tmp = numpy.einsum('ii,jj->ij', vhf_c[m], casdm1s[m]) h_diag[m][:,ncore[m]:nocc[m]] += tmp h_diag[m][ncore[m]:nocc[m],:] += tmp.T tmp = -vhf_c[m][ncore[m]:nocc[m],ncore[m]:nocc[m]] * casdm1s[m] h_diag[m][ncore[m]:nocc[m],ncore[m]:nocc[m]] += tmp + tmp.T fpart3(0) fpart3(1) # part4 def fpart4(jkcpp, m): # (qp|rs)-(pr|sq) rp in core tmp = -numpy.einsum('cpp->cp', jkcpp) # (qp|sr) - (qr|sp) rp in core => 0 h_diag[m][:ncore[m],:] += tmp h_diag[m][:,:ncore[m]] += tmp.T h_diag[m][:ncore[m],:ncore[m]] -= tmp[:,:ncore[m]] * 2 fpart4(eris.jkcpp, 0) fpart4(eris.jkcPP, 1) # part5 and part6 diag #+(qr|kp) e_s^k p in core, sk in active #+(qr|sl) e_l^p s in core, pl in active #-(qj|sr) e_j^p s in core, jp in active #-(qp|kr) e_s^k p in core, sk in active #+(qj|rs) e_j^p s in core, jp in active #+(qp|rl) e_l^s p in core, ls in active #-(qs|rl) e_l^p s in core, lp in active #-(qj|rp) e_j^s p in core, js in active def fpart5(jkcpp, m): jkcaa = jkcpp[:,ncore[m]:nocc[m],ncore[m]:nocc[m]] tmp = -2 * numpy.einsum('jik,ik->ji', jkcaa, casdm1s[m]) h_diag[m][:ncore[m],ncore[m]:nocc[m]] -= tmp h_diag[m][ncore[m]:nocc[m],:ncore[m]] -= tmp.T fpart5(eris.jkcpp, 0) fpart5(eris.jkcPP, 1) def fpart1(m): v_diag = numpy.einsum('ijij->ij', hdm2[m]) h_diag[m][ncore[m]:nocc[m],:] += v_diag h_diag[m][:,ncore[m]:nocc[m]] += v_diag.T fpart1(0) fpart1(1) g_orb = casscf.pack_uniq_var((g[0]-g[0].T, g[1]-g[1].T)) h_diag = casscf.pack_uniq_var(h_diag) def h_op(x): x1a, x1b = casscf.unpack_uniq_var(x) xa_cu = x1a[:ncore[0],ncore[0]:] xa_av = x1a[ncore[0]:nocc[0],nocc[0]:] xa_ac = x1a[ncore[0]:nocc[0],:ncore[0]] xb_cu = x1b[:ncore[1],ncore[1]:] xb_av = x1b[ncore[1]:nocc[1],nocc[1]:] xb_ac = x1b[ncore[1]:nocc[1],:ncore[1]] # part7 x2a = reduce(numpy.dot, (h1e_mo[0], x1a, dm1[0])) x2b = reduce(numpy.dot, (h1e_mo[1], x1b, dm1[1])) # part8, the hessian gives #x2a -= numpy.dot(g[0], x1a) #x2b -= numpy.dot(g[1], x1b) # it may ruin the hermitian of hessian unless g == g.T. So symmetrize it # x_{pq} -= g_{pr} \delta_{qs} x_{rs} * .5 # x_{rs} -= g_{rp} \delta_{sq} x_{pq} * .5 x2a -= numpy.dot(g[0].T, x1a) x2b -= numpy.dot(g[1].T, x1b) # part2 x2a[:ncore[0]] += numpy.dot(xa_cu, vhf_ca[0][ncore[0]:]) x2b[:ncore[1]] += numpy.dot(xb_cu, vhf_ca[1][ncore[1]:]) # part3 def fpart3(m, x2, x_av, x_ac): x2[ncore[m]:nocc[m]] += reduce(numpy.dot, (casdm1s[m], x_av, vhf_c[m][nocc[m]:])) \ + reduce(numpy.dot, (casdm1s[m], x_ac, vhf_c[m][:ncore[m]])) fpart3(0, x2a, xa_av, xa_ac) fpart3(1, x2b, xb_av, xb_ac) # part1 x2a[ncore[0]:nocc[0]] += numpy.einsum('upvr,vr->up', hdm2apap, x1a[ncore[0]:nocc[0]]) x2a[ncore[0]:nocc[0]] += numpy.einsum('upvr,vr->up', hdm2apAP, x1b[ncore[1]:nocc[1]]) x2b[ncore[1]:nocc[1]] += numpy.einsum('vrup,vr->up', hdm2apAP, x1a[ncore[0]:nocc[0]]) x2b[ncore[1]:nocc[1]] += numpy.einsum('upvr,vr->up', hdm2APAP, x1b[ncore[1]:nocc[1]]) # part4, part5, part6 if ncore[0] > 0 or ncore[1] > 0: va, vc = casscf.update_jk_in_ah(mo, (x1a,x1b), casdm1s, eris) x2a[ncore[0]:nocc[0]] += va[0] x2b[ncore[1]:nocc[1]] += va[1] x2a[:ncore[0],ncore[0]:] += vc[0] x2b[:ncore[1],ncore[1]:] += vc[1] x2a = x2a - x2a.T x2b = x2b - x2b.T return casscf.pack_uniq_var((x2a,x2b)) return g_orb, gorb_update, h_op, h_diag
[docs] def kernel(casscf, mo_coeff, tol=1e-7, conv_tol_grad=None, ci0=None, callback=None, verbose=None, dump_chk=True): if verbose is None: verbose = casscf.verbose log = logger.Logger(casscf.stdout, verbose) cput0 = (logger.process_clock(), logger.perf_counter()) log.debug('Start 1-step CASSCF') mo = mo_coeff nmo = mo[0].shape[1] #TODO: lazy evaluate eris, to leave enough memory for FCI solver eris = casscf.ao2mo(mo) e_tot, e_cas, fcivec = casscf.casci(mo, ci0, eris, log, locals()) if casscf.ncas == nmo and not casscf.internal_rotation: return True, e_tot, e_cas, fcivec, mo if conv_tol_grad is None: conv_tol_grad = numpy.sqrt(tol) logger.info(casscf, 'Set conv_tol_grad to %g', conv_tol_grad) conv_tol_ddm = conv_tol_grad * 3 conv = False totmicro = totinner = 0 norm_gorb = norm_gci = 0 de, elast = e_tot, e_tot r0 = None t1m = log.timer('Initializing 1-step CASSCF', *cput0) casdm1, casdm2 = casscf.fcisolver.make_rdm12s(fcivec, casscf.ncas, casscf.nelecas) norm_ddm = 1e2 casdm1_last = casdm1 t3m = t2m = log.timer('CAS DM', *t1m) imacro = 0 while not conv and imacro < casscf.max_cycle_macro: imacro += 1 max_cycle_micro = casscf.micro_cycle_scheduler(locals()) max_stepsize = casscf.max_stepsize_scheduler(locals()) imicro = 0 rota = casscf.rotate_orb_cc(mo, lambda:fcivec, lambda:casdm1, lambda:casdm2, eris, r0, conv_tol_grad*.3, max_stepsize, log) for u, g_orb, njk, r0 in rota: imicro += 1 norm_gorb = numpy.linalg.norm(g_orb) if imicro == 1: norm_gorb0 = norm_gorb norm_t = numpy.linalg.norm(u-numpy.eye(nmo)) if imicro >= max_cycle_micro: log.debug('micro %d |u-1|=%5.3g |g[o]|=%5.3g ', imicro, norm_t, norm_gorb) break casdm1, casdm2, gci, fcivec = casscf.update_casdm(mo, u, fcivec, e_cas, eris) norm_ddm = (numpy.linalg.norm(casdm1[0] - casdm1_last[0]) + numpy.linalg.norm(casdm1[1] - casdm1_last[1])) t3m = log.timer('update CAS DM', *t3m) if isinstance(gci, numpy.ndarray): norm_gci = numpy.linalg.norm(gci) log.debug('micro %d |u-1|=%5.3g |g[o]|=%5.3g |g[c]|=%5.3g |ddm|=%5.3g', imicro, norm_t, norm_gorb, norm_gci, norm_ddm) else: norm_gci = None log.debug('micro %d |u-1|=%5.3g |g[o]|=%5.3g |g[c]|=%s |ddm|=%5.3g', imicro, norm_t, norm_gorb, norm_gci, norm_ddm) if callable(callback): callback(locals()) t3m = log.timer('micro iter %d'%imicro, *t3m) if (norm_t < 1e-4 or (norm_gorb < conv_tol_grad*.5 and norm_ddm < conv_tol_ddm*.4)): break rota.close() rota = None totmicro += imicro totinner += njk eris = None mo = casscf.rotate_mo(mo, u, log) eris = casscf.ao2mo(mo) t2m = log.timer('update eri', *t3m) e_tot, e_cas, fcivec = casscf.casci(mo, fcivec, eris, log, locals()) casdm1, casdm2 = casscf.fcisolver.make_rdm12s(fcivec, casscf.ncas, casscf.nelecas) norm_ddm = (numpy.linalg.norm(casdm1[0] - casdm1_last[0]) + numpy.linalg.norm(casdm1[1] - casdm1_last[1])) casdm1_last = casdm1 log.timer('CASCI solver', *t2m) t2m = t1m = log.timer('macro iter %d'%imacro, *t1m) de, elast = e_tot - elast, e_tot if (abs(de) < tol and (norm_gorb0 < conv_tol_grad and norm_ddm < conv_tol_ddm)): conv = True if dump_chk: casscf.dump_chk(locals()) if callable(callback): callback(locals()) if conv: log.info('1-step CASSCF converged in %d macro (%d JK %d micro) steps', imacro+1, totinner, totmicro) else: log.info('1-step CASSCF not converged, %d macro (%d JK %d micro) steps', imacro+1, totinner, totmicro) log.timer('1-step CASSCF', *cput0) return conv, e_tot, e_cas, fcivec, mo
[docs] class UCASSCF(ucasci.UCASBase): max_stepsize = getattr(__config__, 'mcscf_umc1step_UCASSCF_max_stepsize', .02) max_cycle_macro = getattr(__config__, 'mcscf_umc1step_UCASSCF_max_cycle_macro', 50) max_cycle_micro = getattr(__config__, 'mcscf_umc1step_UCASSCF_max_cycle_micro', 4) conv_tol = getattr(__config__, 'mcscf_umc1step_UCASSCF_conv_tol', 1e-7) conv_tol_grad = getattr(__config__, 'mcscf_umc1step_UCASSCF_conv_tol_grad', None) # for augmented hessian ah_level_shift = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_level_shift', 1e-8) ah_conv_tol = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_conv_tol', 1e-12) ah_max_cycle = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_max_cycle', 30) ah_lindep = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_lindep', 1e-14) ah_start_tol = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_start_tol', 2.5) ah_start_cycle = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_start_cycle', 3) ah_grad_trust_region = getattr(__config__, 'mcscf_umc1step_UCASSCF_ah_grad_trust_region', 3.0) internal_rotation = getattr(__config__, 'mcscf_umc1step_UCASSCF_internal_rotation', False) ci_response_space = getattr(__config__, 'mcscf_umc1step_UCASSCF_ci_response_space', 4) with_dep4 = getattr(__config__, 'mcscf_umc1step_UCASSCF_with_dep4', False) chk_ci = getattr(__config__, 'mcscf_umc1step_UCASSCF_chk_ci', False) kf_interval = getattr(__config__, 'mcscf_umc1step_UCASSCF_kf_interval', 4) kf_trust_region = getattr(__config__, 'mcscf_umc1step_UCASSCF_kf_trust_region', 3.0) natorb = getattr(__config__, 'mcscf_umc1step_UCASSCF_natorb', False) #canonicalization = getattr(__config__, 'mcscf_umc1step_UCASSCF_canonicalization', True) #sorting_mo_energy = getattr(__config__, 'mcscf_umc1step_UCASSCF_sorting_mo_energy', False) callback = None _keys = { 'max_stepsize', 'max_cycle_macro', 'max_cycle_micro', 'conv_tol', 'conv_tol_grad', 'ah_level_shift', 'ah_conv_tol', 'ah_max_cycle', 'ah_lindep', 'ah_start_tol', 'ah_start_cycle', 'ah_grad_trust_region', 'internal_rotation', 'ci_response_space', 'with_dep4', 'chk_ci', 'kf_interval', 'kf_trust_region', 'natorb', 'callback', 'canonicalization', 'sorting_mo_energy', } def __init__(self, mf_or_mol, ncas=0, nelecas=0, ncore=None, frozen=None): ucasci.UCASBase.__init__(self, mf_or_mol, ncas, nelecas, ncore) self.frozen = frozen self.chkfile = self._scf.chkfile self.fcisolver.max_cycle = getattr(__config__, 'mcscf_umc1step_UCASSCF_fcisolver_max_cycle', 50) self.fcisolver.conv_tol = getattr(__config__, 'mcscf_umc1step_UCASSCF_fcisolver_conv_tol', 1e-8) ################################################## # don't modify the following attributes, they are not input options self.e_tot = None self.e_cas = None self.ci = None self.mo_coeff = self._scf.mo_coeff self.converged = False self._max_stepsize = None __getstate__, __setstate__ = lib.generate_pickle_methods( excludes=('chkfile', 'callback'))
[docs] def dump_flags(self, verbose=None): log = logger.new_logger(self, verbose) log.info('') log.info('******** UHF-CASSCF flags ********') nmo = self.mo_coeff[0].shape[1] ncore = self.ncore ncas = self.ncas nvir_alpha = nmo - ncore[0] - ncas nvir_beta = nmo - ncore[1] - ncas log.info('CAS (%de+%de, %do), ncore = [%d+%d], nvir = [%d+%d]', self.nelecas[0], self.nelecas[1], ncas, ncore[0], ncore[1], nvir_alpha, nvir_beta) if ncore[0] != ncore[1]: log.warn('converge might be slow since num alpha core %d != num beta core %d', ncore[0], ncore[1]) if self.frozen is not None: log.info('frozen orbitals %s', str(self.frozen)) log.info('max. macro cycles = %d', self.max_cycle_macro) log.info('max. micro cycles = %d', self.max_cycle_micro) log.info('conv_tol = %g', self.conv_tol) log.info('conv_tol_grad = %s', self.conv_tol_grad) log.info('max. orb step = %g', self.max_stepsize) log.info('augmented hessian max_cycle = %d', self.ah_max_cycle) log.info('augmented hessian conv_tol = %g', self.ah_conv_tol) log.info('augmented hessian linear dependence = %g', self.ah_lindep) log.info('augmented hessian level shift = %g', self.ah_level_shift) log.info('augmented hessian start_tol = %g', self.ah_start_tol) log.info('augmented hessian start_cycle = %d', self.ah_start_cycle) log.info('augmented hessian grad_trust_region = %g', self.ah_grad_trust_region) log.info('kf_trust_region = %g', self.kf_trust_region) log.info('kf_interval = %d', self.kf_interval) log.info('ci_response_space = %d', self.ci_response_space) #log.info('diis = %s', self.diis) log.info('chkfile = %s', self.chkfile) #log.info('natorb = %s', self.natorb) log.info('max_memory %d MB (current use %d MB)', self.max_memory, pyscf.lib.current_memory()[0]) log.info('internal_rotation = %s', self.internal_rotation) try: self.fcisolver.dump_flags(self.verbose) except AttributeError: pass
[docs] def kernel(self, mo_coeff=None, ci0=None, callback=None, _kern=kernel): if mo_coeff is None: mo_coeff = self.mo_coeff else: self.mo_coeff = mo_coeff if callback is None: callback = self.callback self.check_sanity() self.dump_flags() self.converged, self.e_tot, self.e_cas, self.ci, self.mo_coeff = \ _kern(self, mo_coeff, tol=self.conv_tol, conv_tol_grad=self.conv_tol_grad, ci0=ci0, callback=callback, verbose=self.verbose) logger.note(self, 'UCASSCF energy = %.15g', self.e_tot) #if self.verbose >= logger.INFO: # self.analyze(mo_coeff, self.ci, verbose=self.verbose) self._finalize() return self.e_tot, self.e_cas, self.ci, self.mo_coeff
[docs] def mc1step(self, mo_coeff=None, ci0=None, callback=None): return self.kernel(mo_coeff, ci0, callback)
[docs] def mc2step(self, mo_coeff=None, ci0=None, callback=None): from pyscf.mcscf import umc2step return self.kernel(mo_coeff, ci0, callback, umc2step.kernel)
get_h2eff = ucasci.UCASCI.get_h2eff
[docs] def casci(self, mo_coeff, ci0=None, eris=None, verbose=None, envs=None): log = logger.new_logger(self, verbose) fcasci = _fake_h_for_fast_casci(self, mo_coeff, eris) e_tot, e_cas, fcivec = ucasci.kernel(fcasci, mo_coeff, ci0, log, envs=envs) if envs is not None and log.verbose >= logger.INFO: log.debug('CAS space CI energy = %.15g', e_cas) if 'imicro' in envs: # Within CASSCF iteration log.info('macro iter %d (%d JK %d micro), ' 'UCASSCF E = %.15g dE = %.8g', envs['imacro'], envs['njk'], envs['imicro'], e_tot, e_tot-envs['elast']) if 'norm_gci' in envs and envs['norm_gci'] is not None: log.info(' |grad[o]|=%5.3g ' '|grad[c]|=%5.3g |ddm|=%5.3g', envs['norm_gorb0'], envs['norm_gci'], envs['norm_ddm']) else: log.info(' |grad[o]|=%5.3g |ddm|=%5.3g', envs['norm_gorb0'], envs['norm_ddm']) else: # Initialization step log.info('UCASCI E = %.15g', e_tot) return e_tot, e_cas, fcivec
[docs] def uniq_var_indices(self, nmo, ncore, ncas, frozen): nocc = ncore + ncas mask = numpy.zeros((nmo,nmo),dtype=bool) mask[ncore:nocc,:ncore] = True mask[nocc:,:nocc] = True if self.internal_rotation: raise NotImplementedError('internal_rotation') if frozen is not None: if isinstance(frozen, (int, numpy.integer)): mask[:frozen] = mask[:,:frozen] = False else: mask[frozen] = mask[:,frozen] = False return mask
[docs] def pack_uniq_var(self, mat): nmo = self.mo_coeff[0].shape[1] ncore = self.ncore ncas = self.ncas idxa = self.uniq_var_indices(nmo, ncore[0], ncas, self.frozen) idxb = self.uniq_var_indices(nmo, ncore[1], ncas, self.frozen) return numpy.hstack((mat[0][idxa], mat[1][idxb]))
# to anti symmetric matrix
[docs] def unpack_uniq_var(self, v): nmo = self.mo_coeff[0].shape[1] ncore = self.ncore ncas = self.ncas idx = numpy.empty((2,nmo,nmo), dtype=bool) idx[0] = self.uniq_var_indices(nmo, ncore[0], ncas, self.frozen) idx[1] = self.uniq_var_indices(nmo, ncore[1], ncas, self.frozen) mat = numpy.zeros((2,nmo,nmo)) mat[idx] = v mat[0] = mat[0] - mat[0].T mat[1] = mat[1] - mat[1].T return mat
[docs] def update_rotate_matrix(self, dx, u0=1): if isinstance(u0, int) and u0 == 1: u0 = (1,1) dr = self.unpack_uniq_var(dx) ua = numpy.dot(u0[0], expmat(dr[0])) ub = numpy.dot(u0[1], expmat(dr[1])) return (ua, ub)
[docs] def gen_g_hop(self, *args): return gen_g_hop(self, *args)
rotate_orb_cc = rotate_orb_cc
[docs] def ao2mo(self, mo_coeff=None): if mo_coeff is None: mo_coeff = self.mo_coeff # nmo = mo[0].shape[1] # ncore = self.ncore # ncas = self.ncas # nocc = (ncas + ncore[0], ncas + ncore[1]) # eriaa = pyscf.ao2mo.incore.full(self._scf._eri, mo[0]) # eriab = pyscf.ao2mo.incore.general(self._scf._eri, (mo[0],mo[0],mo[1],mo[1])) # eribb = pyscf.ao2mo.incore.full(self._scf._eri, mo[1]) # eriaa = pyscf.ao2mo.restore(1, eriaa, nmo) # eriab = pyscf.ao2mo.restore(1, eriab, nmo) # eribb = pyscf.ao2mo.restore(1, eribb, nmo) # eris = lambda:None # eris.jkcpp = numpy.einsum('iipq->ipq', eriaa[:ncore[0],:ncore[0],:,:]) \ # - numpy.einsum('ipqi->ipq', eriaa[:ncore[0],:,:,:ncore[0]]) # eris.jkcPP = numpy.einsum('iipq->ipq', eribb[:ncore[1],:ncore[1],:,:]) \ # - numpy.einsum('ipqi->ipq', eribb[:ncore[1],:,:,:ncore[1]]) # eris.jC_pp = numpy.einsum('pqii->pq', eriab[:,:,:ncore[1],:ncore[1]]) # eris.jc_PP = numpy.einsum('iipq->pq', eriab[:ncore[0],:ncore[0],:,:]) # eris.aapp = numpy.copy(eriaa[ncore[0]:nocc[0],ncore[0]:nocc[0],:,:]) # eris.aaPP = numpy.copy(eriab[ncore[0]:nocc[0],ncore[0]:nocc[0],:,:]) # eris.AApp = numpy.copy(eriab[:,:,ncore[1]:nocc[1],ncore[1]:nocc[1]].transpose(2,3,0,1)) # eris.AAPP = numpy.copy(eribb[ncore[1]:nocc[1],ncore[1]:nocc[1],:,:]) # eris.appa = numpy.copy(eriaa[ncore[0]:nocc[0],:,:,ncore[0]:nocc[0]]) # eris.apPA = numpy.copy(eriab[ncore[0]:nocc[0],:,:,ncore[1]:nocc[1]]) # eris.APPA = numpy.copy(eribb[ncore[1]:nocc[1],:,:,ncore[1]:nocc[1]]) # # eris.cvCV = numpy.copy(eriab[:ncore[0],ncore[0]:,:ncore[1],ncore[1]:]) # eris.Icvcv = eriaa[:ncore[0],ncore[0]:,:ncore[0],ncore[0]:] * 2\ # - eriaa[:ncore[0],:ncore[0],ncore[0]:,ncore[0]:].transpose(0,3,1,2) \ # - eriaa[:ncore[0],ncore[0]:,:ncore[0],ncore[0]:].transpose(0,3,2,1) # eris.ICVCV = eribb[:ncore[1],ncore[1]:,:ncore[1],ncore[1]:] * 2\ # - eribb[:ncore[1],:ncore[1],ncore[1]:,ncore[1]:].transpose(0,3,1,2) \ # - eribb[:ncore[1],ncore[1]:,:ncore[1],ncore[1]:].transpose(0,3,2,1) # # eris.Iapcv = eriaa[ncore[0]:nocc[0],:,:ncore[0],ncore[0]:] * 2 \ # - eriaa[:,ncore[0]:,:ncore[0],ncore[0]:nocc[0]].transpose(3,0,2,1) \ # - eriaa[:,:ncore[0],ncore[0]:,ncore[0]:nocc[0]].transpose(3,0,1,2) # eris.IAPCV = eribb[ncore[1]:nocc[1],:,:ncore[1],ncore[1]:] * 2 \ # - eribb[:,ncore[1]:,:ncore[1],ncore[1]:nocc[1]].transpose(3,0,2,1) \ # - eribb[:,:ncore[1],ncore[1]:,ncore[1]:nocc[1]].transpose(3,0,1,2) # eris.apCV = numpy.copy(eriab[ncore[0]:nocc[0],:,:ncore[1],ncore[1]:]) # eris.APcv = numpy.copy(eriab[:ncore[0],ncore[0]:,ncore[1]:nocc[1],:].transpose(2,3,0,1)) # return eris return umc_ao2mo._ERIS(self, mo_coeff)
[docs] def update_jk_in_ah(self, mo, r, casdm1s, eris): ncas = self.ncas ncore = self.ncore nocc = (ncas + ncore[0], ncas + ncore[1]) ra, rb = r vhf3ca = numpy.einsum('srqp,sr->qp', eris.Icvcv, ra[:ncore[0],ncore[0]:]) vhf3ca += numpy.einsum('qpsr,sr->qp', eris.cvCV, rb[:ncore[1],ncore[1]:]) * 2 vhf3cb = numpy.einsum('srqp,sr->qp', eris.ICVCV, rb[:ncore[1],ncore[1]:]) vhf3cb += numpy.einsum('srqp,sr->qp', eris.cvCV, ra[:ncore[0],ncore[0]:]) * 2 vhf3aa = numpy.einsum('kpsr,sr->kp', eris.Iapcv, ra[:ncore[0],ncore[0]:]) vhf3aa += numpy.einsum('kpsr,sr->kp', eris.apCV, rb[:ncore[1],ncore[1]:]) * 2 vhf3ab = numpy.einsum('kpsr,sr->kp', eris.IAPCV, rb[:ncore[1],ncore[1]:]) vhf3ab += numpy.einsum('kpsr,sr->kp', eris.APcv, ra[:ncore[0],ncore[0]:]) * 2 dm4 = (numpy.dot(casdm1s[0], ra[ncore[0]:nocc[0]]), numpy.dot(casdm1s[1], rb[ncore[1]:nocc[1]])) vhf4a = numpy.einsum('krqp,kr->qp', eris.Iapcv, dm4[0]) vhf4a += numpy.einsum('krqp,kr->qp', eris.APcv, dm4[1]) * 2 vhf4b = numpy.einsum('krqp,kr->qp', eris.IAPCV, dm4[1]) vhf4b += numpy.einsum('krqp,kr->qp', eris.apCV, dm4[0]) * 2 va = (numpy.dot(casdm1s[0], vhf3aa), numpy.dot(casdm1s[1], vhf3ab)) vc = (vhf3ca + vhf4a, vhf3cb + vhf4b) return va, vc
[docs] def update_casdm(self, mo, u, fcivec, e_cas, eris): ecore, h1cas, h2cas = self.approx_cas_integral(mo, u, eris) ci1, g = self.solve_approx_ci(h1cas, h2cas, fcivec, ecore, e_cas) casdm1, casdm2 = self.fcisolver.make_rdm12s(ci1, self.ncas, self.nelecas) return casdm1, casdm2, g, ci1
[docs] def approx_cas_integral(self, mo, u, eris): ncas = self.ncas ncore = self.ncore nocc = (ncas + ncore[0], ncas + ncore[1]) nmo = mo[0].shape[1] rmat = u - numpy.eye(nmo) mocas = (mo[0][:,ncore[0]:nocc[0]], mo[1][:,ncore[1]:nocc[1]]) hcore = self.get_hcore() h1effa = reduce(numpy.dot, (rmat[0][:,:nocc[0]].T, mo[0].T, hcore[0], mo[0][:,:nocc[0]])) h1effb = reduce(numpy.dot, (rmat[1][:,:nocc[1]].T, mo[1].T, hcore[1], mo[1][:,:nocc[1]])) h1effa = h1effa + h1effa.T h1effb = h1effb + h1effb.T aapc = eris.aapp[:,:,:,:ncore[0]] aaPC = eris.aaPP[:,:,:,:ncore[1]] AApc = eris.AApp[:,:,:,:ncore[0]] AAPC = eris.AAPP[:,:,:,:ncore[1]] apca = eris.appa[:,:,:ncore[0],:] APCA = eris.APPA[:,:,:ncore[1],:] jka = numpy.einsum('iup->up', eris.jkcpp[:,:nocc[0]]) + eris.jC_pp[:nocc[0]] v1a = (numpy.einsum('up,pv->uv', jka[ncore[0]:], rmat[0][:,ncore[0]:nocc[0]]) + numpy.einsum('uvpi,pi->uv', aapc-apca.transpose(0,3,1,2), rmat[0][:,:ncore[0]]) + numpy.einsum('uvpi,pi->uv', aaPC, rmat[1][:,:ncore[1]])) jkb = numpy.einsum('iup->up', eris.jkcPP[:,:nocc[1]]) + eris.jc_PP[:nocc[1]] v1b = (numpy.einsum('up,pv->uv', jkb[ncore[1]:], rmat[1][:,ncore[1]:nocc[1]]) + numpy.einsum('uvpi,pi->uv', AApc, rmat[0][:,:ncore[0]]) + numpy.einsum('uvpi,pi->uv', AAPC-APCA.transpose(0,3,1,2), rmat[1][:,:ncore[1]])) h1casa = (h1effa[ncore[0]:,ncore[0]:] + (v1a + v1a.T) + reduce(numpy.dot, (mocas[0].T, hcore[0], mocas[0])) + eris.vhf_c[0][ncore[0]:nocc[0],ncore[0]:nocc[0]]) h1casb = (h1effb[ncore[1]:,ncore[1]:] + (v1b + v1b.T) + reduce(numpy.dot, (mocas[1].T, hcore[1], mocas[1])) + eris.vhf_c[1][ncore[1]:nocc[1],ncore[1]:nocc[1]]) h1cas = (h1casa, h1casb) aaap = eris.aapp[:,:,ncore[0]:nocc[0],:] aaAP = eris.aaPP[:,:,ncore[1]:nocc[1],:] AAap = eris.AApp[:,:,ncore[1]:nocc[1],:] AAAP = eris.AAPP[:,:,ncore[1]:nocc[1],:] aaaa = numpy.einsum('tuvp,pw->tuvw', aaap, rmat[0][:,ncore[0]:nocc[0]]) aaaa = aaaa + aaaa.transpose(0,1,3,2) aaaa = aaaa + aaaa.transpose(2,3,0,1) aaaa += aaap[:,:,:,ncore[0]:nocc[0]] AAAA = numpy.einsum('tuvp,pw->tuvw', AAAP, rmat[1][:,ncore[1]:nocc[1]]) AAAA = AAAA + AAAA.transpose(0,1,3,2) AAAA = AAAA + AAAA.transpose(2,3,0,1) AAAA += AAAP[:,:,:,ncore[1]:nocc[1]] tmp = (numpy.einsum('vwtp,pu->tuvw', AAap, rmat[0][:,ncore[0]:nocc[0]]), numpy.einsum('tuvp,pw->tuvw', aaAP, rmat[1][:,ncore[1]:nocc[1]])) aaAA = (tmp[0] + tmp[0].transpose(1,0,2,3) + tmp[1] + tmp[1].transpose(0,1,3,2)) aaAA += aaAP[:,:,:,ncore[1]:nocc[1]] # pure core response ecore = (h1effa[:ncore[0]].trace() + h1effb[:ncore[1]].trace() + numpy.einsum('jp,pj->', jka[:ncore[0]], rmat[0][:,:ncore[0]])*2 + numpy.einsum('jp,pj->', jkb[:ncore[1]], rmat[1][:,:ncore[1]])*2) return ecore, h1cas, (aaaa, aaAA, AAAA)
[docs] def solve_approx_ci(self, h1, h2, ci0, ecore, e_cas): ''' Solve CI eigenvalue/response problem approximately ''' ncas = self.ncas nelecas = self.nelecas if getattr(self.fcisolver, 'approx_kernel', None): ci1 = self.fcisolver.approx_kernel(h1, h2, ncas, nelecas, ci0=ci0)[1] return ci1, None h2eff = self.fcisolver.absorb_h1e(h1, h2, ncas, nelecas, .5) hc = self.fcisolver.contract_2e(h2eff, ci0, ncas, nelecas).ravel() g = hc - (e_cas-ecore) * ci0.ravel() if self.ci_response_space > 6: logger.debug(self, 'CI step by full response') # full response e, ci1 = self.fcisolver.kernel(h1, h2, ncas, nelecas, ci0=ci0, max_memory=self.max_memory) else: nd = self.ci_response_space xs = [ci0.ravel()] ax = [hc] heff = numpy.empty((nd,nd)) seff = numpy.empty((nd,nd)) heff[0,0] = numpy.dot(xs[0], ax[0]) seff[0,0] = 1 for i in range(1, nd): dx = ax[i-1] - xs[i-1] * e_cas if numpy.linalg.norm(dx) < 1e-3: break xs.append(dx) ax.append(self.fcisolver.contract_2e(h2eff, xs[i], ncas, nelecas).ravel()) for j in range(i+1): heff[i,j] = heff[j,i] = numpy.dot(xs[i], ax[j]) seff[i,j] = seff[j,i] = numpy.dot(xs[i], xs[j]) nd = len(xs) e, v = pyscf.lib.safe_eigh(heff[:nd,:nd], seff[:nd,:nd])[:2] ci1 = 0 for i in range(nd): ci1 += xs[i] * v[i,0] return ci1, g
[docs] def dump_chk(self, envs_or_file): '''Serialize the MCSCF object and save it to the specified chkfile. Args: envs_or_file: If this argument is a file path, the serialized MCSCF object is saved to the file specified by this argument. If this attribute is a dict (created by locals()), the necessary variables are saved to the file specified by the attribute .chkfile. ''' if isinstance(envs_or_file, str): envs = None chk_file = envs_or_file else: envs = envs_or_file chk_file = self.chkfile if not chk_file: return self e_tot = mo_coeff = mo_occ = mo_energy = e_cas = civec = casdm1 = None ncore = self.ncore ncas = self.ncas nocca = ncore[0] + ncas noccb = ncore[1] + ncas if envs is not None: if self.chk_ci: civec = envs['fcivec'] if 'mo' in envs: mo_coeff = envs['mo'] else: mo_coeff = envs['mo_coeff'] mo_occ = numpy.zeros((2,envs['mo'][0].shape[1])) mo_occ[0,:ncore[0]] = 1 mo_occ[1,:ncore[1]] = 1 if self.natorb: occa, ucas = self._eig(-casdm1[0], ncore[0], nocca) occb, ucas = self._eig(-casdm1[1], ncore[1], noccb) mo_occ[0,ncore[0]:nocca] = -occa mo_occ[1,ncore[1]:noccb] = -occb else: mo_occ[0,ncore[0]:nocca] = casdm1[0].diagonal() mo_occ[1,ncore[1]:noccb] = casdm1[1].diagonal() chkfile.dump_mcscf(self, self.chkfile, 'mcscf', e_tot, mo_coeff, ncore, ncas, mo_occ, mo_energy, e_cas, civec, casdm1, overwrite_mol=(envs is None)) return self
[docs] def rotate_mo(self, mo, u, log=None): '''Rotate orbitals with the given unitary matrix''' mo_a = numpy.dot(mo[0], u[0]) mo_b = numpy.dot(mo[1], u[1]) if log is not None and log.verbose >= logger.DEBUG: ncore = self.ncore[0] ncas = self.ncas nocc = ncore + ncas s = reduce(numpy.dot, (mo_a[:,ncore:nocc].T, self._scf.get_ovlp(), self.mo_coeff[0][:,ncore:nocc])) log.debug('Alpha active space overlap to initial guess, SVD = %s', numpy.linalg.svd(s)[1]) log.debug('Alpha active space overlap to last step, SVD = %s', numpy.linalg.svd(u[0][ncore:nocc,ncore:nocc])[1]) return mo_a, mo_b
[docs] def micro_cycle_scheduler(self, envs): #log_norm_ddm = numpy.log(envs['norm_ddm']) #return max(self.max_cycle_micro, int(self.max_cycle_micro-1-log_norm_ddm)) return self.max_cycle_micro
max_stepsize_scheduler=max_stepsize_scheduler as_scanner=as_scanner @property def max_orb_stepsize(self): # pragma: no cover return self.max_stepsize @max_orb_stepsize.setter def max_orb_stepsize(self, x): # pragma: no cover sys.stderr.write('WARN: Attribute "max_orb_stepsize" was replaced by "max_stepsize"\n') self.max_stepsize = x
[docs] def reset(self, mol=None): ucasci.UCASBase.reset(self, mol=mol) self._max_stepsize = None
CASSCF = UCASSCF # to avoid calculating AO integrals def _fake_h_for_fast_casci(casscf, mo, eris): mc = casscf.view(ucasci.UCASCI) mc.mo_coeff = mo if eris is None: return mc # vhf for core density matrix s = mc._scf.get_ovlp() mo_inv = (numpy.dot(mo[0].T, s), numpy.dot(mo[1].T, s)) vjk =(numpy.einsum('ipq->pq', eris.jkcpp) + eris.jC_pp, numpy.einsum('ipq->pq', eris.jkcPP) + eris.jc_PP) vhf =(reduce(numpy.dot, (mo_inv[0].T, vjk[0], mo_inv[0])), reduce(numpy.dot, (mo_inv[1].T, vjk[1], mo_inv[1]))) mc.get_veff = lambda *args: vhf ncas = casscf.ncas ncore = casscf.ncore nocc = (ncas + ncore[0], ncas + ncore[1]) eri_cas = (eris.aapp[:,:,ncore[0]:nocc[0],ncore[0]:nocc[0]].copy(), eris.aaPP[:,:,ncore[1]:nocc[1],ncore[1]:nocc[1]].copy(), eris.AAPP[:,:,ncore[1]:nocc[1],ncore[1]:nocc[1]].copy()) mc.get_h2eff = lambda *args: eri_cas return mc if __name__ == '__main__': from pyscf import gto from pyscf import scf from pyscf.mcscf import addons mol = gto.Mole() mol.verbose = 0 mol.output = None#"out_h2o" mol.atom = [ ['H', ( 1.,-1. , 0. )], ['H', ( 0.,-1. ,-1. )], ['H', ( 1.,-0.5 ,-1. )], ['H', ( 0.,-0.5 ,-1. )], ['H', ( 0.,-0.5 ,-0. )], ['H', ( 0.,-0. ,-1. )], ['H', ( 1.,-0.5 , 0. )], ['H', ( 0., 1. , 1. )], ] mol.basis = {'H': 'sto-3g'} mol.charge = 1 mol.spin = 1 mol.build() m = scf.UHF(mol) ehf = m.scf() mc = UCASSCF(m, 4, (2,1)) #mo = m.mo_coeff mo = addons.sort_mo(mc, m.mo_coeff, [(3,4,5,6),(3,4,6,7)], 1) emc = kernel(mc, mo, verbose=4)[1] print(ehf, emc, emc-ehf) print(emc - -2.9782774463926618) mol.atom = [ ['O', ( 0., 0. , 0. )], ['H', ( 0., -0.757, 0.587)], ['H', ( 0., 0.757 , 0.587)],] mol.basis = {'H': 'cc-pvdz', 'O': 'cc-pvdz',} mol.symmetry = 1 mol.charge = 1 mol.spin = 1 mol.build() m = scf.UHF(mol) ehf = m.scf() mc = UCASSCF(m, 4, (2,1)) mc.verbose = 4 emc = mc.mc1step()[0] print(ehf, emc, emc-ehf) print(emc - -75.5644202701263, emc - -75.573930418500652, emc - -75.574137883405612, emc - -75.648547447838951) mc = UCASSCF(m, 4, (2,1)) mc.verbose = 4 mo = mc.sort_mo((3,4,6,7)) emc = mc.mc1step(mo)[0] print(ehf, emc, emc-ehf) print(emc - -75.5644202701263, emc - -75.573930418500652, emc - -75.574137883405612, emc - -75.648547447838951)