#!/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 copy import deepcopy
from pyscf import ao2mo, fci, mcscf, lib, __config__
from pyscf.lib import logger
from pyscf.dft import gen_grid
from pyscf.mcscf import mc1step
from pyscf.mcscf.addons import StateAverageMCSCFSolver, state_average_mix
from pyscf.mcscf.addons import state_average_mix_, StateAverageMixFCISolver
from pyscf.mcscf.df import _DFCASSCF, _DFCAS
from pyscf.mcpdft import pdft_veff, pdft_feff
from pyscf.mcpdft.otfnal import transfnal, get_transfnal
from pyscf.mcpdft import _dms
from pyscf.mcpdft import chkfile
[docs]
def energy_tot(mc, mo_coeff=None, ci=None, ot=None, state=0, verbose=None):
'''Calculate MC-PDFT total energy
Args:
mc : an instance of CASSCF or CASCI class
Note: this function does not currently run the CASSCF or
CASCI calculation itself prior to calculating the MC-PDFT
energy. Call mc.kernel () before passing to this function!
Kwargs:
mo_coeff : ndarray of shape (nao, nmo)
Molecular orbital coefficients
ci : ndarray or list of length (nroots)
CI vector or vectors.
ot : an instance of on-top functional class - see otfnal.py
state : int
If mc describes a state-averaged calculation, select the
state (0-indexed).
verbose : int
Verbosity of logger output; defaults to mc.verbose
Returns:
e_tot : float
Total MC-PDFT energy including nuclear repulsion energy
E_ot : float
On-top (cf. exchange-correlation) energy
'''
if ot is None: ot = mc.otfnal
ot.reset(mol=mc.mol) # scanner mode safety
if mo_coeff is None: mo_coeff = mc.mo_coeff
if ci is None: ci = mc.ci
if verbose is None: verbose = mc.verbose
t0 = (logger.process_clock(), logger.perf_counter())
# Allow MC-PDFT to be subclassed, and also allow this function to be
# called without mc being an instance of MC-PDFT class
casdm1s = mc.make_one_casdm1s(ci, state=state)
casdm2 = mc.make_one_casdm2(ci, state=state)
t0 = logger.timer(ot, 'rdms', *t0)
if callable(getattr(mc, 'energy_mcwfn', None)):
e_mcwfn = mc.energy_mcwfn(ot=ot, mo_coeff=mo_coeff, casdm1s=casdm1s,
casdm2=casdm2, verbose=verbose)
else:
e_mcwfn = energy_mcwfn(ot=ot, mo_coeff=mo_coeff, casdm1s=casdm1s,
casdm2=casdm2, verbose=verbose)
t0 = logger.timer(ot, 'MC wfn energy', *t0)
if callable(getattr(mc, 'energy_dft', None)):
e_dft = mc.energy_dft(ot=ot, mo_coeff=mo_coeff, casdm1s=casdm1s,
casdm2=casdm2)
else:
e_dft = energy_dft(ot=ot, mo_coeff=mo_coeff, casdm1s=casdm1s,
casdm2=casdm2)
t0 = logger.timer(ot, 'E_ot', *t0)
e_tot = e_mcwfn + e_dft
return e_tot, e_dft
# Consistency with PySCF convention
kernel = energy_tot # backwards compatibility
[docs]
def energy_elec(mc, *args, **kwargs):
e_tot, E_ot = energy_tot(mc, *args, **kwargs)
e_elec = e_tot - mc._scf.energy_nuc()
return e_elec, E_ot
[docs]
def energy_mcwfn(mc, mo_coeff=None, ci=None, ot=None, state=0, casdm1s=None,
casdm2=None, verbose=None):
'''Compute the parts of the MC-PDFT energy arising from the wave
function
Args:
mc : an instance of CASSCF or CASCI class
Note: this function does not currently run the CASSCF or
CASCI calculation itself prior to calculating the MC-PDFT
energy. Call mc.kernel () before passing to this function!
Kwargs:
mo_coeff : ndarray of shape (nao, nmo)
contains molecular orbital coefficients
ci : list or ndarray
contains ci vectors
ot : an instance of on-top functional class - see otfnal.py
state : int
If mc describes a state-averaged calculation, select the
state (0-indexed).
casdm1s : ndarray or compatible of shape (2,ncas,ncas)
Contains spin-separated active-space 1RDM
casdm2 : ndarray or compatible of shape [ncas,]*4
Contains spin-summed active-space 2RDM
Returns:
e_mcwfn : float
Energy from the multiconfigurational wave function:
nuclear repulsion + 1e + coulomb
'''
if ot is None: ot = mc.otfnal
if mo_coeff is None: mo_coeff = mc.mo_coeff
if ci is None: ci = mc.ci
if verbose is None: verbose = mc.verbose
if casdm1s is None: casdm1s = mc.make_one_casdm1s(ci=ci, state=state)
if casdm2 is None: casdm2 = mc.make_one_casdm2(ci=ci, state=state)
log = logger.new_logger(mc, verbose=verbose)
dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s, mo_coeff=mo_coeff)
cascm2 = _dms.dm2_cumulant(casdm2, casdm1s)
hyb_x, hyb_c = ot._numint.rsh_and_hybrid_coeff(ot.otxc, mc.mol.spin)[2]
Vnn = mc._scf.energy_nuc()
h = mc._scf.get_hcore()
dm1 = dm1s[0] + dm1s[1]
if log.verbose >= logger.DEBUG or abs(hyb_x) > 1e-10:
vj, vk = mc._scf.get_jk(dm=dm1s)
vj = vj[0] + vj[1]
else:
vj = mc._scf.get_j(dm=dm1)
Te_Vne = np.tensordot(h, dm1)
# (vj_a + vj_b) * (dm_a + dm_b)
E_j = np.tensordot(vj, dm1) / 2
log.debug('CAS energy decomposition:')
log.debug('Vnn = %s', Vnn)
log.debug('Te + Vne = %s', Te_Vne)
log.debug('E_j = %s', E_j)
if abs(hyb_x - hyb_c) > 1e-10:
log.warn("exchange and correlation hybridization differ")
log.warn("may lead to unphysical results, see https://github.com/pyscf/pyscf-forge/issues/128")
# Note: this is not the true exchange energy, but just the HF-like exchange
E_x = 0.0
if log.verbose >= logger.DEBUG or abs(hyb_x) > 1e-10:
# (vk_a * dm_a) + (vk_b * dm_b)
E_x = -(np.tensordot(vk[0], dm1s[0]) + np.tensordot(vk[1], dm1s[1]))
E_x /= 2.0
log.debug("E_x = %s", E_x)
log.debug("Adding (%s) * E_x = %s", hyb_x, hyb_x * E_x)
# This is not correlation, but the 2-body cumulant tensored with the eri's:
# g_pqrs * l_pqrs / 2
E_c = 0.0
if log.verbose >= logger.DEBUG or abs(hyb_c) > 1e-10:
aeri = ao2mo.restore(1, mc.get_h2eff(mo_coeff), mc.ncas)
E_c = np.tensordot(aeri, cascm2, axes=4) / 2
log.debug("E_c = %s", E_c)
log.debug("Adding (%s) * E_c = %s", hyb_c, hyb_c * E_c)
e_mcwfn = Vnn + Te_Vne + E_j + (hyb_x * E_x) + (hyb_c * E_c)
return e_mcwfn
[docs]
def energy_dft(mc, mo_coeff=None, ci=None, ot=None, state=0, casdm1s=None,
casdm2=None, max_memory=None, hermi=1):
''' Wrap to ot.energy_ot for subclassing. '''
if ot is None: ot = mc.otfnal
if mo_coeff is None: mo_coeff = mc.mo_coeff
if ci is None: ci = mc.ci
if casdm1s is None: casdm1s = mc.make_one_casdm1s(ci, state=state)
if casdm2 is None: casdm2 = mc.make_one_casdm2(ci, state=state)
if max_memory is None: max_memory = mc.max_memory
return ot.energy_ot(casdm1s, casdm2, mo_coeff, mc.ncore,
max_memory=max_memory, hermi=hermi)
[docs]
def get_energy_decomposition(mc, mo_coeff=None, ci=None, ot=None, otxc=None,
grids_level=None, grids_attr=None,
split_x_c=None, verbose=None):
'''Compute a decomposition of the MC-PDFT energy into nuclear
potential (E0), one-electron (E1), Coulomb (E2c), exchange (EOTx),
correlation (EOTc) terms, and additionally the nonclassical part
(E2nc) of the MC-SCF energy:
E(MC-SCF) = E0 + E1 + E2c + Enc
E(MC-PDFT) = E0 + E1 + E2c + EOTx + EOTc
For hybrid functionals,
E(MC-PDFT) = E0 + E1 + E2c + EOTx + EOTc + Enc
Where the Enc and EOTx/c terms are premultiplied by the hybrid factor. If
mc.fcisolver.nroots > 1, lists are returned for everything except the
nuclear potential energy.
Args:
mc : an instance of CASSCF or CASCI class
Kwargs:
mo_coeff : ndarray
Contains MO coefficients
ci : ndarray or list of length nroots
Contains CI vectors
ot : an instance of (translated) on-top density fnal class
otxc : string
identity of translated functional; overrides ot
grids_level : integer
level preset for DFT quadrature grids
grids_attr : dictionary
general attributes for DFT quadrature grids
split_x_c : logical
whether to split the exchange and correlation parts of the
ot functional into two separate contributions
Returns:
e_nuc : float
E0 = sum_A>B ZA*ZB/rAB
e_1e : float or list of length nroots
E1 = <T+sum_A ZA/rA>
e_coul : float or list of length nroots
E2c = 1/2 int rho(1)rho(2)/r12 d1d2
e_otxc : float or list of length nroots
EOTxc = translated functional energy
if split_x_c == True, this is instead
EOTx = exchange part of translated functional energy
e_otc : float or list of length nroots
only returned if split_x_c == True
EOTc = correlation part of translated functional
e_ncwfn : float or list of length nroots
E2ncc = <H> - E0 - E1 - E2c
If hybrid functional, this term is weighted appropriately. For pure
functionals, it is the full NC component
'''
if verbose is None: verbose = mc.verbose
log = logger.new_logger(mc, verbose)
if mo_coeff is None: mo_coeff = mc.mo_coeff
if ci is None: ci = mc.ci
if grids_attr is None: grids_attr = {}
if grids_level is not None: grids_attr['level'] = grids_level
if len(grids_attr) or (otxc is not None):
old_ot = ot if (ot is not None) else mc.otfnal
old_grids = old_ot.grids
# TODO: general compatibility with arbitrary (non-translated) fnals
if otxc is None: otxc = old_ot.otxc
new_ot = get_transfnal(mc.mol, otxc)
new_ot.grids.__dict__.update(old_grids.__dict__)
new_ot.grids.__dict__.update(**grids_attr)
ot = new_ot
if ot is None: ot = mc.otfnal
if split_x_c is None:
split_x_c = True
log.warn(
'Currently, split_x_c in get_energy_decomposition defaults to '
'True.\nThis default will change to False in the near future.'
)
hyb_x, hyb_c = ot._numint.hybrid_coeff(ot.otxc)
if abs(hyb_x - hyb_c) > 1e-11:
raise NotImplementedError("hybrid functionals with different exchange, correlations components")
if not isinstance(ot, transfnal):
raise NotImplementedError("Decomp for non-translated PDFT fnals")
if split_x_c:
ot = list(ot.split_x_c())
else:
ot = [ot, ]
nroots = getattr(mc.fcisolver, 'nroots', 1)
e_nuc = mc._scf.energy_nuc()
if nroots > 1:
e_1e = []
e_coul = []
e_otxc = []
e_ncwfn = []
for ix in range(nroots):
row = _get_e_decomp(mc, mo_coeff, ci, ot, state=ix)
e_1e.append(row[0])
e_coul.append(row[1])
e_otxc.append(row[2])
e_ncwfn.append(row[3])
e_otxc = [[e[i] for e in e_otxc] for i in range(len(e_otxc[0]))]
else:
e_1e, e_coul, e_otxc, e_ncwfn = _get_e_decomp(mc, mo_coeff, ci, ot)
if split_x_c:
e_otx, e_otc = e_otxc
return e_nuc, e_1e, e_coul, e_otx, e_otc, e_ncwfn
else:
return e_nuc, e_1e, e_coul, e_otxc[0], e_ncwfn
def _get_e_decomp(mc, mo_coeff=None, ci=None, ot=None, state=0, verbose=None):
ncore = mc.ncore
ncas = mc.ncas
if ot is None: ot = [mc.otfnal,]
if mo_coeff is None: mo_coeff = mc.mo_coeff
if ci is None: ci = mc.ci
if verbose is None: verbose = mc.verbose
if len(ot) == 1:
hyb_x, hyb_c = ot[0]._numint.hybrid_coeff(ot[0].otxc)
elif len(ot) == 2:
hyb_x, hyb_c = [
fnal._numint.hybrid_coeff(fnal.otxc)[idx] for idx, fnal in enumerate(ot)
]
else:
raise ValueError("ot must be length of 1 or 2")
if abs(hyb_x - hyb_c) > 1e-11:
raise NotImplementedError(
"hybrid functionals with different exchange, correlations components"
)
casdm1s = mc.make_one_casdm1s(ci, state=state)
casdm1 = casdm1s[0] + casdm1s[1]
casdm2 = mc.make_one_casdm2(ci, state=state)
dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s, mo_coeff=mo_coeff,
ncore=ncore, ncas=ncas)
dm1 = dm1s[0] + dm1s[1]
e_nuc = mc._scf.energy_nuc()
h = mc.get_hcore()
h1, h0 = mc.h1e_for_cas()
h2 = ao2mo.restore(1, mc.get_h2eff(), ncas)
j = mc._scf.get_j(dm=dm1)
e_1e = np.dot(h.ravel(), dm1.ravel())
e_coul = np.dot(j.ravel(), dm1.ravel()) / 2
e_mcscf = h0 + np.dot(h1.ravel(), casdm1.ravel()) + (
np.dot(h2.ravel(), casdm2.ravel()) * 0.5)
e_otxc = [fnal.energy_ot(casdm1s, casdm2, mo_coeff, ncore,
max_memory=mc.max_memory)
for fnal in ot]
e_ncwfn = e_mcscf - e_nuc - e_1e - e_coul
if abs(hyb_x) > 1e-10:
e_ncwfn = hyb_x * e_ncwfn
return e_1e, e_coul, e_otxc, e_ncwfn
class _mcscf_env:
'''Prevent MC-SCF step of MC-PDFT from overwriting redefined
quantities e_states and e_tot '''
def __init__(self, mc):
self.mc = mc
self.e_tot = deepcopy(self.mc.e_tot)
self.e_states = deepcopy(getattr(self.mc, 'e_states', None))
def __enter__(self):
self.mc._in_mcscf_env = True
def __exit__(self, type, value, traceback):
self.mc.e_tot = self.e_tot
if getattr(self.mc, 'e_states', None) is not None:
self.mc.e_mcscf = np.array(self.mc.e_states)
if self.e_states is not None:
try:
self.mc.e_states = self.e_states
except AttributeError as e:
self.mc.fcisolver.e_states = self.e_states
assert (self.mc.e_states is self.e_states), str(e)
# TODO: redesign this. MC-SCF e_states is stapled to
# fcisolver.e_states, but I don't want MS-PDFT to be
# because that makes no sense
self.mc._in_mcscf_env = False
class _PDFT:
# Metaclass parent; unusable on its own
'''MC-PDFT child class. All total energy quantities (e_tot,
e_states) are MC-PDFT total energies:
E = Vnn + h_pq*D_pq + g_pqrs*D_pq*D_rs/2 + E_ot
= T + Vnn + Vne + E_Coul + E_ot
= E_classical + E_ot
Extra attributes:
otfnal : instance of :class:`otfnal`
The on-top energy functional class
otxc : string
Synonym for `otfnal.otxc`
grids : instance of :class:`Grids`
Synonym for `otfnal.grids`
Additional saved results:
e_mcscf : float or list of length nroots
MC-SCF total energy or energies:
Vnn + h_pq*D_pq + g_pqrs*d_pqrs/2
e_ot : float or list of length nroots
On-top nonclassical term in the MC-PDFT energy
'''
_mc_class = None
def __init__(self, scf, ncas, nelecas, my_ot=None, grids_level=None,
grids_attr=None, **kwargs):
# Keep the same initialization pattern for backwards-compatibility.
# Use a separate intializer for the ot functional
if grids_attr is None: grids_attr = {}
_mc_class_no_df = self._mc_class
if issubclass (_mc_class_no_df, _DFCAS):
_mc_class_no_df = lib.drop_class (_mc_class_no_df, _DFCAS)
_mc_class_no_df.__init__(self, scf, ncas, nelecas)
if issubclass (self._mc_class, _DFCAS):
self._mc_class.__init__(self, self, scf.with_df)
keys = set(('e_ot', 'e_mcscf', 'get_pdft_veff', 'get_pdft_feff', 'e_states', 'otfnal',
'grids', 'max_cycle_fp', 'conv_tol_ci_fp', 'mcscf_kernel', 'chkfile'))
self.max_cycle_fp = getattr(__config__, 'mcscf_mcpdft_max_cycle_fp',
50)
self.conv_tol_ci_fp = getattr(__config__,
'mcscf_mcpdft_conv_tol_ci_fp', 1e-8)
self.mcscf_kernel = self._mc_class.kernel
self.chkfile = self._scf.chkfile
self._in_mcscf_env = False
self._keys = set(self.__dict__.keys()).union(keys)
if grids_level is not None:
grids_attr['level'] = grids_level
if my_ot is not None:
self._init_ot_grids(my_ot, grids_attr=grids_attr)
def _init_ot_grids(self, my_ot, grids_attr=None):
if grids_attr is None: grids_attr = {}
old_grids = getattr(self, 'grids', None)
if isinstance(my_ot, (str, np.bytes_)):
self.otfnal = get_transfnal(self.mol, my_ot)
else:
self.otfnal = my_ot
if isinstance(old_grids, gen_grid.Grids):
self.otfnal.grids = old_grids
# self.grids = self.otfnal.grids
self.grids.__dict__.update(grids_attr)
for key in grids_attr:
assert (getattr(self.grids, key, None) == getattr(
self.otfnal.grids, key, None))
# Make sure verbose and stdout don't accidentally change
# (i.e., in scanner mode)
self.otfnal.verbose = self.verbose
self.otfnal.stdout = self.stdout
def get_rhf_base (self):
from pyscf.scf.hf import RHF
from pyscf.scf.rohf import ROHF
from pyscf.scf.hf_symm import SymAdaptedRHF
from pyscf.scf.hf_symm import SymAdaptedROHF
rhf_cls = self._scf.__class__
if issubclass (rhf_cls, SymAdaptedROHF):
rhf_cls = lib.replace_class (rhf_cls, SymAdaptedROHF, SymAdaptedRHF)
if issubclass (rhf_cls, ROHF):
rhf_cls = lib.replace_class (rhf_cls, ROHF, RHF)
return lib.view (self._scf, rhf_cls)
@property
def grids(self):
return self.otfnal.grids
@grids.setter
def grids(self, x):
self.otfnal.grids = x
return self.otfnal.grids
def optimize_mcscf_(self, mo_coeff=None, ci0=None, **kwargs):
'''Optimize the MC-SCF wave function underlying an MC-PDFT calculation.
Has the same calling signature as the parent kernel method. '''
with _mcscf_env(self):
self.e_mcscf, self.e_cas, self.ci, self.mo_coeff, self.mo_energy = \
self._mc_class.kernel(self, mo_coeff, ci0=ci0, **kwargs)
return self.e_mcscf, self.e_cas, self.ci, self.mo_coeff, self.mo_energy
def compute_pdft_energy_(self, mo_coeff=None, ci=None, ot=None, otxc=None,
grids_level=None, grids_attr=None, dump_chk=True, verbose=None, **kwargs):
'''Compute the MC-PDFT energy(ies) (and update stored data)
with the MC-SCF wave function fixed. '''
if mo_coeff is not None: self.mo_coeff = mo_coeff
if ci is not None: self.ci = ci
if ot is not None: self.otfnal = ot
if otxc is not None: self.otxc = otxc
if grids_attr is None: grids_attr = {}
if grids_level is not None: grids_attr['level'] = grids_level
if len(grids_attr): self.grids.__dict__.update(**grids_attr)
if verbose is None: verbose = self.verbose
self.verbose = self.otfnal.verbose = verbose
nroots = getattr(self.fcisolver, 'nroots', 1)
epdft = [self.energy_tot(mo_coeff=self.mo_coeff, ci=self.ci, state=ix,
logger_tag='MC-PDFT state {}'.format(ix))
for ix in range(nroots)]
self.e_ot = [e_ot for e_tot, e_ot in epdft]
if isinstance(self, StateAverageMCSCFSolver):
e_states = [e_tot for e_tot, e_ot in epdft]
try:
self.e_states = e_states
except AttributeError as e:
self.fcisolver.e_states = e_states
assert (self.e_states is e_states), str(e)
# TODO: redesign this. MC-SCF e_states is stapled to
# fcisolver.e_states, but I don't want MS-PDFT to be
# because that makes no sense
self.e_tot = np.dot(e_states, self.weights)
e_states = self.e_states
elif nroots > 1: # nroots>1 CASCI
self.e_tot = [e_tot for e_tot, e_ot in epdft]
e_states = self.e_tot
else: # nroots==1 not StateAverage class
self.e_tot, self.e_ot = epdft[0]
e_states = [self.e_tot]
if dump_chk:
e_tot = self.e_tot
e_ot = self.e_ot
self.dump_chk(locals())
return self.e_tot, self.e_ot, e_states
def kernel(self, mo_coeff=None, ci0=None, otxc=None, grids_attr=None,
grids_level=None, **kwargs):
self.optimize_mcscf_(mo_coeff=mo_coeff, ci0=ci0, **kwargs)
self.compute_pdft_energy_(otxc=otxc, grids_attr=grids_attr,
grids_level=grids_level, **kwargs)
# TODO: edit StateAverageMCSCF._finalize in pyscf.mcscf.addons
# to use the proper name of the class rather than "CASCI", so
# that I can meaningfully play with "finalize" here
return (self.e_tot, self.e_ot, self.e_mcscf, self.e_cas, self.ci,
self.mo_coeff, self.mo_energy)
def dump_flags(self, verbose=None):
self._mc_class.dump_flags(self, verbose=verbose)
log = logger.new_logger(self, verbose)
log.info('on-top pair density exchange-correlation functional: %s',
self.otfnal.otxc)
def get_pdft_veff(self, mo=None, ci=None, state=0, casdm1s=None,
casdm2=None, incl_coul=False, paaa_only=False, aaaa_only=False,
jk_pc=False, drop_mcwfn=False, incl_energy=False, ot=None):
'''Get the 1- and 2-body MC-PDFT effective potentials for a set
of mos and ci vectors
Kwargs:
mo : ndarray of shape (nao,nmo)
A full set of molecular orbital coefficients. Taken from
self if not provided
ci : list or ndarray
CI vectors. Taken from self if not provided
state : integer
Indexes a specific state in state-averaged calculations.
casdm1s : ndarray of shape (2,ncas,ncas)
Spin-separated 1-RDM in the active space
casdm2 : ndarray of shape (ncas,ncas,ncas,ncas)
Spin-summed 2-RDM in the active space
incl_coul : logical
If true, includes the Coulomb repulsion energy in the
1-body effective potential.
paaa_only : logical
If true, only the paaa 2-body effective potential
elements are evaluated; the rest of ppaa are filled with
zeros.
aaaa_only : logical
If true, only the aaaa 2-body effective potential
elements are evaluated; the rest of ppaa are filled with
zeros.
jk_pc : logical
If true, calculate the ppii=pipi 2-body effective
potential in veff2.j_pc and veff2.k_pc. Otherwise these
arrays are filled with zeroes.
drop_mcwfn : logical
If true, drops the normal CASSCF wave function contribution
(ie the ``Hartree exchange-correlation'') from the response
incl_energy : logical
If true, includes the on-top potential energy as a 3rd return argument
ot : an instance of otfnal class
Returns:
veff1 : ndarray of shape (nao, nao)
1-body effective potential in the AO basis
May include classical Coulomb potential term (see
incl_coul kwarg)
veff2 : pyscf.mcscf.mc_ao2mo._ERIS instance
Relevant 2-body effective potential in the MO basis
E_ot : float
On-top energy. Only included if incl_energy is true.
'''
t0 = (logger.process_clock(), logger.perf_counter())
if mo is None:
mo = self.mo_coeff
if ci is None:
ci = self.ci
if casdm1s is None:
casdm1s = self.make_one_casdm1s(ci, state=state)
if casdm2 is None:
casdm2 = self.make_one_casdm2(ci, state=state)
if ot is None:
ot = self.otfnal
ncore, ncas = self.ncore, self.ncas
dm1s = _dms.casdm1s_to_dm1s(self, casdm1s, mo_coeff=mo,
ncore=ncore, ncas=ncas)
cascm2 = _dms.dm2_cumulant(casdm2, casdm1s)
spin = abs(self.nelecas[0] - self.nelecas[1])
omega, _, hyb = ot._numint.rsh_and_hybrid_coeff(ot.otxc, spin=spin)
if abs(omega) > 1e-11:
raise NotImplementedError("range-separated on-top 1e potentials")
if abs(hyb[0] > hyb[1]) > 1e-11:
raise NotImplementedError("hybrid functionals with different exchange, correlation components")
cas_hyb = hyb[0]
ot_hyb = 1.0-cas_hyb
E_ot, pdft_veff1, pdft_veff2 = pdft_veff.kernel(
ot,
dm1s,
cascm2,
mo,
ncore,
ncas,
max_memory=self.max_memory,
paaa_only=paaa_only,
aaaa_only=aaaa_only,
jk_pc=jk_pc,
)
if incl_coul:
pdft_veff1 += ot_hyb*self._scf.get_j(self.mol, dm1s[0] + dm1s[1])
if not drop_mcwfn and cas_hyb > 1e-11:
raise NotImplementedError("adding mcwfn response to pdft_veff2")
logger.timer(self, 'get_pdft_veff', *t0)
if incl_energy:
return pdft_veff1, pdft_veff2, E_ot
else:
return pdft_veff1, pdft_veff2
def get_pdft_feff(self, mo=None, ci=None, state=0, casdm1s=None,
casdm2=None, c_dm1s=None, c_cascm2=None,
paaa_only=False, aaaa_only=False, jk_pc=False, incl_coul=False, delta=False):
"""casdm1s and casdm2 are the values that are put into the kernel
whereas the c_dm1s and c_cascm2 are the densities which multiply the
kernel function (ie the contraction in terms of normal 1 and 2-rdm
quantities.)
incl_coul includes the coulomb interaction with the contracting density!
delta actually sets contracted density to contracted_density - density (like delta in lpdft grads)
"""
t0 = (logger.process_clock(), logger.perf_counter())
if mo is None: mo = self.mo_coeff
if ci is None: ci = self.ci
if casdm1s is None: casdm1s = self.make_one_casdm1s(ci, state=state)
if casdm2 is None: casdm2 = self.make_one_casdm2(ci, state=state)
ncore, ncas = self.ncore, self.ncas
dm1s = _dms.casdm1s_to_dm1s(self, casdm1s, mo_coeff=mo, ncore=ncore,
ncas=ncas)
cascm2 = _dms.dm2_cumulant(casdm2, casdm1s)
if c_dm1s is None:
c_dm1s = dm1s
if c_cascm2 is None:
c_cascm2 = cascm2
pdft_feff1, pdft_feff2 = pdft_feff.kernel(self.otfnal, dm1s, cascm2,
c_dm1s, c_cascm2, mo, ncore,
ncas,
max_memory=self.max_memory,
paaa_only=paaa_only,
aaaa_only=aaaa_only,
jk_pc=jk_pc, delta=delta)
if incl_coul:
if delta:
c_dm1s -= dm1s
pdft_feff1 += self._scf.get_j(self.mol, c_dm1s[0] + c_dm1s[1])
logger.timer(self, 'get_pdft_feff', *t0)
return pdft_feff1, pdft_feff2
def _state_average_nuc_grad_method(self, state=None):
if not isinstance(self, mc1step.CASSCF):
raise NotImplementedError("CASCI-based PDFT nuclear gradients")
elif getattr(self, 'frozen', None) is not None:
raise NotImplementedError("PDFT nuclear gradients with frozen orbitals")
elif isinstance(self, _DFCASSCF):
from pyscf.df.grad.mcpdft import Gradients
else:
from pyscf.grad.mcpdft import Gradients
return Gradients(self, state=state)
def nuc_grad_method(self):
return self._state_average_nuc_grad_method(state=None)
Gradients=nuc_grad_method
def dip_moment(self, unit='Debye', origin='Coord_Center', state=0):
if not isinstance(self, mc1step.CASSCF):
raise NotImplementedError("CASCI-based PDFT dipole moments")
elif getattr(self, 'frozen', None) is not None:
raise NotImplementedError("PDFT dipole moments with frozen orbitals")
elif isinstance(self, _DFCASSCF):
raise NotImplementedError("PDFT dipole moments with density-fitting ERIs")
# Monkeypatch for double prop folders
# TODO: more elegant solution
import os
mypath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
myproppath = os.path.join(mypath, 'prop')
# suppress irrelevant warnings when 'properties' ext mod installed
import warnings
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="Module.*is under testing")
from pyscf import prop
prop.__path__.append(myproppath)
prop.__path__ = list(set(prop.__path__))
from pyscf.prop.dip_moment.mcpdft import ElectricDipole
dip_obj = ElectricDipole(self)
mol_dipole = dip_obj.kernel(state=state, unit=unit, origin=origin)
return mol_dipole
def get_energy_decomposition(self, mo_coeff=None, ci=None, ot=None,
otxc=None, grids_level=None,
grids_attr=None, split_x_c=None,
verbose=None):
if mo_coeff is None: mo_coeff = self.mo_coeff
if ci is None: ci = self.ci
if verbose is None: verbose = self.verbose
return get_energy_decomposition(
self, mo_coeff=mo_coeff, ci=ci, ot=ot, otxc=otxc,
grids_level=grids_level, grids_attr=grids_attr,
split_x_c=split_x_c, verbose=verbose
)
def state_average_mix(self, fcisolvers=None, weights=(0.5, 0.5)):
return state_average_mix(self, fcisolvers, weights)
def state_average_mix_(self, fcisolvers=None, weights=(0.5, 0.5)):
state_average_mix_(self, fcisolvers, weights)
return self
def multi_state_mix(self, fcisolvers=None, weights=(0.5, 0.5), method='LIN'):
if method.upper() == "LIN":
from pyscf.mcpdft.lpdft import linear_multi_state_mix
return linear_multi_state_mix(self, fcisolvers=fcisolvers, weights=weights)
else:
raise NotImplementedError(f"StateAverageMix not available for {method}")
def multi_state(self, weights=(0.5, 0.5), method='LIN'):
if method.upper() == "LIN":
from pyscf.mcpdft.lpdft import linear_multi_state
return linear_multi_state(self, weights=weights)
else:
from pyscf.mcpdft.mspdft import multi_state
return multi_state(self, weights=weights,
diabatization=method)
def state_interaction(self, weights=(0.5, 0.5), diabatization='CMS'):
logger.warn(self, ('"state_interaction" for multi-state PDFT is '
'deprecated. Use multi_state instead. In the '
'future this will raise an error.'))
return self.multi_state(weights=weights, diabatization=diabatization)
@property
def otxc(self):
return self.otfnal.otxc
@otxc.setter
def otxc(self, x):
self._init_ot_grids(x)
make_one_casdm1s = _dms.make_one_casdm1s
make_one_casdm2 = _dms.make_one_casdm2
energy_mcwfn = energy_mcwfn
energy_dft = energy_dft
def energy_tot(self, mo_coeff=None, ci=None, ot=None, state=0,
verbose=None, otxc=None, grids_level=None, grids_attr=None,
logger_tag='MC-PDFT'):
''' Compute the MC-PDFT energy of a single state '''
if mo_coeff is None: mo_coeff = self.mo_coeff
if ci is None: ci = self.ci
if grids_attr is None: grids_attr = {}
if grids_level is not None: grids_attr['level'] = grids_level
if len(grids_attr) or (otxc is not None):
old_ot = ot if (ot is not None) else self.otfnal
old_grids = old_ot.grids
# TODO: general compatibility with arbitrary (non-translated) fnals
if otxc is None: otxc = old_ot.otxc
new_ot = get_transfnal(self.mol, otxc)
new_ot.grids.__dict__.update(old_grids.__dict__)
new_ot.grids.__dict__.update(**grids_attr)
ot = new_ot
elif ot is None:
ot = self.otfnal
e_tot, e_ot = energy_tot(self, mo_coeff=mo_coeff, ot=ot, ci=ci,
state=state, verbose=verbose)
logger.note(self, '%s E = %s, Eot(%s) = %s', logger_tag,
e_tot, ot.otxc, e_ot)
return e_tot, e_ot
def dump_chk(self, envs):
"""
Dumps information to the chkfile. If called within mcscf environment,
it forwards to the mcscf dump_chk. Else, it dumps only the pdft
information.
"""
if not self.chkfile:
return self
# Hack, basically if we are optimizing mcscf, then call that dump
# Otherwise, we need to dump the pdft dump...
if self._in_mcscf_env:
self._mc_class.dump_chk(self, envs)
else:
e_states = None
if len(envs["e_states"]) > 1:
e_states = envs["e_states"]
chkfile.dump_mcpdft(
self,
chkfile=self.chkfile,
key="pdft",
e_tot=envs["e_tot"],
e_ot=envs["e_ot"],
e_states=e_states,
e_mcscf=self.e_mcscf,
)
return self
def update_from_chk(self, chkfile=None, pdft_key="pdft"):
if chkfile is None:
chkfile = self.chkfile
# When the chkfile is saved, we utilize hard links to the mcscf data
self.__dict__.update(lib.chkfile.load(chkfile, pdft_key))
return self
update = update_from_chk
[docs]
def get_mcpdft_child_class(mc, ot, **kwargs):
# Inheritance magic
mc_doc = (mc.__class__.__doc__ or
'No docstring for MC-SCF parent method')
class PDFT(_PDFT, mc.__class__):
__doc__ = mc_doc + '\n\n' + _PDFT.__doc__
_mc_class = mc.__class__
pdft = PDFT(mc._scf, mc.ncas, mc.nelecas, my_ot=ot, **kwargs)
_keys = pdft._keys.copy()
pdft.__dict__.update(mc.__dict__)
pdft._keys = pdft._keys.union(_keys)
return pdft