-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CMS-PDFT Nonadiabatic Coupling (#44)
* update and add cmspdft nacs * add example * update doc * fix flake8 * update doc * add cms derivative coupling doi
- Loading branch information
1 parent
1077e48
commit 5351178
Showing
6 changed files
with
541 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
from pyscf import gto, scf, lib, mcpdft | ||
|
||
# NAC signs are really, really hard to nail down. | ||
# There are arbitrary signs associated with | ||
# 1. The MO coefficients | ||
# 2. The CI vectors | ||
# 3. Almost any kind of post-processing (natural-orbital analysis, etc.) | ||
# 4. Developer convention on whether the bra index or ket index is 1st | ||
# It MIGHT help comparison to OpenMolcas if you load a rasscf.h5 file | ||
# I TRIED to choose the same convention for #4 as OpenMolcas. | ||
mol = gto.M (atom='Li 0 0 0;H 1.5 0 0', basis='sto-3g', | ||
output='LiH_cms2ftlda22_sto3g.log', verbose=lib.logger.INFO) | ||
|
||
mf = scf.RHF (mol).run () | ||
mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 2, 2, grids_level=3) | ||
# Quasi-ultrafine is ALMOST the same thing as | ||
# ``` | ||
# grid input | ||
# nr=100 | ||
# lmax=41 | ||
# rquad=ta | ||
# nopr | ||
# noro | ||
# end of grid input | ||
# ``` | ||
# in SEWARD | ||
mc.fix_spin_(ss=0, shift=1) | ||
mc = mc.multi_state ([0.5,0.5], 'cms').run (conv_tol=1e-10) | ||
|
||
mc_nacs = mc.nac_method() | ||
|
||
# 1. <1|d0/dR> | ||
# Equivalent OpenMolcas input: | ||
# ``` | ||
# &ALASKA | ||
# NAC=1 2 | ||
# ``` | ||
nac = mc_nacs.kernel (state=(0,1)) | ||
print ("\nNAC <1|d0/dR>:\n",nac) | ||
print ("Notice that according to the NACs printed above, rigidly moving the") | ||
print ("molecule along the bond axis changes the electronic wave function, which") | ||
print ("is obviously unphysical. This broken translational symmetry is due to the") | ||
print ("antisymmetric orbital-overlap derivative in the Hellmann-Feynman part of") | ||
print ("the 'model state contribution'. Omitting the antisymmetric orbital-overlap") | ||
print ("derivative corresponds to the use of the 'electron-translation factors' of") | ||
print ("Fatehi and Subotnik and is requested by passing 'use_etfs=True'.") | ||
|
||
# 2. <1|d0/dR> w/ ETFs (i.e., w/out model-state Hellmann-Feynman contribution) | ||
# Equivalent OpenMolcas input: | ||
# ``` | ||
# &ALASKA | ||
# NAC=1 2 | ||
# NOCSF | ||
# ``` | ||
nac = mc_nacs.kernel (state=(0,1), use_etfs=True) | ||
print ("\nNAC <1|d0/dR> w/ ETFs:\n", nac) | ||
print ("These NACs are much more well-behaved: moving the molecule rigidly around") | ||
print ("in space doesn't induce any change to the electronic wave function.") | ||
|
||
# 3. <0|d1/dR> | ||
# Equivalent OpenMolcas input: | ||
# ``` | ||
# &ALASKA | ||
# NAC=2 1 | ||
# ``` | ||
nac = mc_nacs.kernel (state=(1,0)) | ||
print ("\nThe NACs are antisymmetric with respect to state transposition.") | ||
print ("NAC <0|d1/dR>:\n", nac) | ||
|
||
# 4. <0|d1/dR> w/ ETFs | ||
# Equivalent OpenMolcas input: | ||
# ``` | ||
# &ALASKA | ||
# NAC=2 1 | ||
# NOCSF | ||
# ``` | ||
nac = mc_nacs.kernel (state=(1,0), use_etfs=True) | ||
print ("NAC <0|d1/dR> w/ ETFs:\n", nac) | ||
|
||
|
||
# 5. <1|d0/dR>*(E1-E0) = <0|d1/dR>*(E0-E1) | ||
# I'm not aware of any OpenMolcas equivalent for this, but all the information | ||
# should obviously be in the output file, as long as you aren't right at a CI. | ||
nac_01 = mc_nacs.kernel (state=(0,1), mult_ediff=True) | ||
nac_10 = mc_nacs.kernel (state=(1,0), mult_ediff=True) | ||
print ("\nNACs diverge at conical intersections (CI). The important question") | ||
print ("is how quickly it diverges. You can get at this by calculating NACs") | ||
print ("multiplied by the energy difference using the keyword 'mult_ediff=True'.") | ||
print ("This yields a quantity which is symmetric wrt state interchange and is") | ||
print ("finite at a CI.") | ||
print ("NAC <1|d0/dR>*(E1-E0):\n", nac_01) | ||
print ("NAC <0|d1/dR>*(E0-E1):\n", nac_10) | ||
|
||
# 6. <1|d0/dR>*(E1-E0) w/ ETFs | ||
# For comparison with 7 below. | ||
nac_01 = mc_nacs.kernel (state=(0,1), mult_ediff=True, use_etfs=True) | ||
nac_10 = mc_nacs.kernel (state=(1,0), mult_ediff=True, use_etfs=True) | ||
print ("\nUnlike the SA-CASSCF case, using both 'use_etfs=True' and") | ||
print ("'mult_ediff=True' DOES NOT reproduce the first derivative of the") | ||
print ("off-diagonal element of the potential matrix. This is because the") | ||
print ("model-state contribution generates a symmetric contribution to the") | ||
print ("response equations and changes the values of the Lagrange multipliers,") | ||
print ("even if the Hellmann-Feynman part of the model-state contribution is") | ||
print ("omitted. You can get the gradients of the potential couplings by") | ||
print ("passing a tuple to the gradient method instance instead.") | ||
print ("<1|d0/dR>*(E1-E0) w/ ETFs:\n",nac_01) | ||
print ("<0|d1/dR>*(E0-E1) w/ ETFs:\n",nac_10) | ||
|
||
# 7. <1|dH/dR|0> | ||
# THIS is the quantity one uses to optimize MECIs | ||
mc_grad = mc.nuc_grad_method () | ||
v01 = mc_grad.kernel (state=(0,1)) | ||
v10 = mc_grad.kernel (state=(1,0)) | ||
print ("<1|dH/dR|0>:\n", v01) | ||
print ("<0|dH/dR|1>:\n", v10) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
#!/usr/bin/env python | ||
# Copyright 2014-2024 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 pyscf import mcpdft | ||
from pyscf.grad import mspdft as mspdft_grad | ||
from pyscf import lib | ||
from pyscf.fci import direct_spin1 | ||
from pyscf.nac import sacasscf as sacasscf_nacs | ||
from functools import reduce | ||
|
||
_unpack_state = mspdft_grad._unpack_state | ||
_nac_csf = sacasscf_nacs._nac_csf | ||
|
||
def nac_model (mc_grad, mo_coeff=None, ci=None, si_bra=None, si_ket=None, | ||
mf_grad=None, atmlst=None): | ||
'''Compute the "model-state contribution" to the MS-PDFT NAC''' | ||
mc = mc_grad.base | ||
mol = mc.mol | ||
ci_bra = np.tensordot (si_bra, ci, axes=1) | ||
ci_ket = np.tensordot (si_ket, ci, axes=1) | ||
ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas | ||
castm1 = direct_spin1.trans_rdm1 (ci_bra, ci_ket, ncas, nelecas) | ||
# if PySCF commentary is to be trusted, trans_rdm1[p,q] is | ||
# <bra|q'p|ket>. I want <bra|p'q - q'p|ket>. | ||
castm1 = castm1.conj ().T - castm1 | ||
mo_cas = mo_coeff[:,ncore:][:,:ncas] | ||
tm1 = reduce (np.dot, (mo_cas, castm1, mo_cas.conj ().T)) | ||
return _nac_csf (mol, mf_grad, tm1, atmlst) | ||
|
||
class NonAdiabaticCouplings (mspdft_grad.Gradients): | ||
'''MS-PDFT non-adiabatic couplings (NACs) between states | ||
kwargs/attributes: | ||
state : tuple of length 2 | ||
The NACs returned are <state[1]|d(state[0])/dR>. | ||
In other words, state = (ket, bra). | ||
mult_ediff : logical | ||
If True, returns NACs multiplied by the energy difference. | ||
Useful near conical intersections to avoid numerical problems. | ||
use_etfs : logical | ||
If True, use the ``electron translation factors'' of Fatehi and | ||
Subotnik [JPCL 3, 2039 (2012)], which guarantee conservation of | ||
total electron + nuclear momentum when the nuclei are moving | ||
(i.e., in non-adiabatic molecular dynamics). This corresponds | ||
to omitting the ``model state contribution''. | ||
''' | ||
|
||
def __init__(self, mc, state=None, mult_ediff=False, use_etfs=False): | ||
self.mult_ediff = mult_ediff | ||
self.use_etfs = use_etfs | ||
self.state = state | ||
mspdft_grad.Gradients.__init__(self, mc) | ||
|
||
def get_wfn_response (self, si_bra=None, si_ket=None, state=None, si=None, | ||
verbose=None, **kwargs): | ||
g_all = mspdft_grad.Gradients.get_wfn_response ( | ||
self, si_bra=si_bra, si_ket=si_ket, state=state, si=si, | ||
verbose=verbose, **kwargs | ||
) | ||
g_orb, g_ci, g_is = self.unpack_uniq_var (g_all) | ||
if state is None: state = self.state | ||
ket, bra = _unpack_state (state) | ||
if si is None: si = self.base.si | ||
if si_bra is None: si_bra = si[:,bra] | ||
if si_ket is None: si_ket = si[:,ket] | ||
nroots = self.nroots | ||
log = lib.logger.new_logger (self, verbose) | ||
g_model = np.multiply.outer (si_bra.conj (), si_ket) | ||
g_model -= g_model.T | ||
g_model *= self.base.e_states[bra]-self.base.e_states[ket] | ||
g_model = g_model[np.tril_indices (nroots, k=-1)] | ||
log.debug ("NACs g_is additional component:\n{}".format (g_model)) | ||
return self.pack_uniq_var (g_orb, g_ci, g_is+g_model) | ||
|
||
def get_ham_response (self, **kwargs): | ||
nac = mspdft_grad.Gradients.get_ham_response (self, **kwargs) | ||
use_etfs = kwargs.get ('use_etfs', self.use_etfs) | ||
if not use_etfs: | ||
verbose = kwargs.get ('verbose', self.verbose) | ||
log = lib.logger.new_logger (self, verbose) | ||
nac_model = self.nac_model (**kwargs) | ||
log.info ('NACs model-state contribution:\n{}'.format (nac_model)) | ||
nac += nac_model | ||
return nac | ||
|
||
def nac_model (self, mo_coeff=None, ci=None, si=None, si_bra=None, | ||
si_ket=None, state=None, mf_grad=None, atmlst=None, | ||
**kwargs): | ||
if state is None: state = self.state | ||
ket, bra = _unpack_state (state) | ||
if mo_coeff is None: mo_coeff = self.base.mo_coeff | ||
if ci is None: ci = self.base.ci | ||
if si is None: si = self.base.si | ||
if si_bra is None: si_bra = si[:,bra] | ||
if si_ket is None: si_ket = si[:,ket] | ||
if mf_grad is None: mf_grad = self.base._scf.nuc_grad_method () | ||
if atmlst is None: atmlst = self.atmlst | ||
nac = nac_model (self, mo_coeff=mo_coeff, ci=ci, si_bra=si_bra, | ||
si_ket=si_ket, mf_grad=mf_grad, atmlst=atmlst) | ||
e_bra = self.base.e_states[bra] | ||
e_ket = self.base.e_states[ket] | ||
nac *= e_bra - e_ket | ||
return nac | ||
|
||
def kernel (self, *args, **kwargs): | ||
mult_ediff = kwargs.get ('mult_ediff', self.mult_ediff) | ||
state = kwargs.get ('state', self.state) | ||
nac = mspdft_grad.Gradients.kernel (self, *args, **kwargs) | ||
if not mult_ediff: | ||
ket, bra = _unpack_state (state) | ||
e_bra = self.base.e_states[bra] | ||
e_ket = self.base.e_states[ket] | ||
nac /= e_bra - e_ket | ||
return nac | ||
|
||
if __name__=='__main__': | ||
from pyscf import gto, scf | ||
from mrh.my_pyscf.dft.openmolcas_grids import quasi_ultrafine | ||
from scipy import linalg | ||
mol = gto.M (atom = 'Li 0 0 0; H 0 0 1.5', basis='sto-3g', | ||
output='mspdft_nacs.log', verbose=lib.logger.INFO) | ||
mf = scf.RHF (mol).run () | ||
mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 2, 2, grids_attr=quasi_ultrafine) | ||
mc = mc.fix_spin_(ss=0).multi_state ([0.5,0.5], 'cms').run (conv_tol=1e-10) | ||
#openmolcas_energies = np.array ([-7.85629118, -7.72175252]) | ||
print ("energies:",mc.e_states) | ||
#print ("disagreement w openmolcas:", np.around (mc.e_states-openmolcas_energies, 8)) | ||
mc_nacs = NonAdiabaticCouplings (mc) | ||
print ("no csf contr") | ||
print ("antisym") | ||
nac_01 = mc_nacs.kernel (state=(0,1), use_etfs=True) | ||
nac_10 = mc_nacs.kernel (state=(1,0), use_etfs=True) | ||
print (nac_01) | ||
print (nac_10) | ||
print ("checking antisym:",linalg.norm(nac_01+nac_10)) | ||
print ("sym") | ||
nac_01_mult = mc_nacs.kernel (state=(0,1), use_etfs=True, mult_ediff=True) | ||
nac_10_mult = mc_nacs.kernel (state=(1,0), use_etfs=True, mult_ediff=True) | ||
print (nac_01_mult) | ||
print (nac_10_mult) | ||
print ("checking sym:",linalg.norm(nac_01_mult-nac_10_mult)) | ||
|
||
|
||
print ("incl csf contr") | ||
print ("antisym") | ||
nac_01 = mc_nacs.kernel (state=(0,1), use_etfs=False) | ||
nac_10 = mc_nacs.kernel (state=(1,0), use_etfs=False) | ||
print (nac_01) | ||
print ("checking antisym:",linalg.norm(nac_01+nac_10)) | ||
print ("sym") | ||
nac_01_mult = mc_nacs.kernel (state=(0,1), use_etfs=False, mult_ediff=True) | ||
nac_10_mult = mc_nacs.kernel (state=(1,0), use_etfs=False, mult_ediff=True) | ||
print (nac_01_mult) | ||
print ("checking sym:",linalg.norm(nac_01_mult-nac_10_mult)) | ||
|
||
print ("Check gradients") | ||
mc_grad = mc.nuc_grad_method () | ||
de_0 = mc_grad.kernel (state=0) | ||
print (de_0) | ||
de_1 = mc_grad.kernel (state=1) | ||
print (de_1) | ||
|
||
#from mrh.my_pyscf.tools.molcas2pyscf import * | ||
#mol = get_mol_from_h5 ('LiH_sa2casscf22_sto3g.rasscf.h5', | ||
# output='sacasscf_nacs_fromh5.log', | ||
# verbose=lib.logger.INFO) | ||
#mo = get_mo_from_h5 (mol, 'LiH_sa2casscf22_sto3g.rasscf.h5') | ||
#nac_etfs_ref = np.array ([9.14840490109073E-02, -9.14840490109074E-02]) | ||
#nac_ref = np.array ([1.83701578323929E-01, -6.91459741744125E-02]) | ||
#mf = scf.RHF (mol).run () | ||
#mc = mcscf.CASSCF (mol, 2, 2).fix_spin_(ss=0).state_average ([0.5,0.5]) | ||
#mc.run (mo, natorb=True, conv_tol=1e-10) | ||
#mc_nacs = NonAdiabaticCouplings (mc) | ||
#nac = mc_nacs.kernel (state=(0,1)) | ||
#print (nac) | ||
#print (nac_ref) | ||
#nac_etfs = mc_nacs.kernel (state=(0,1), use_etfs=True) | ||
#print (nac_etfs) | ||
#print (nac_etfs_ref) | ||
|
||
|
Oops, something went wrong.