From 53511781bd27f294a4ae53477acabf94fb9e930f Mon Sep 17 00:00:00 2001 From: matthew-hennefarth Date: Mon, 19 Feb 2024 00:17:11 -0600 Subject: [PATCH] CMS-PDFT Nonadiabatic Coupling (#44) * update and add cmspdft nacs * add example * update doc * fix flake8 * update doc * add cms derivative coupling doi --- doc/mcpdft/README.md | 3 + examples/nac/01-cmspdft_nac.py | 116 ++++++++++++++++ pyscf/mcpdft/__init__.py | 5 +- pyscf/mcpdft/mspdft.py | 12 ++ pyscf/nac/mspdft.py | 196 +++++++++++++++++++++++++++ pyscf/nac/test/test_nac_cmspdft.py | 210 +++++++++++++++++++++++++++++ 6 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 examples/nac/01-cmspdft_nac.py create mode 100644 pyscf/nac/mspdft.py create mode 100644 pyscf/nac/test/test_nac_cmspdft.py diff --git a/doc/mcpdft/README.md b/doc/mcpdft/README.md index 76b06ddf..6dc6a5cd 100644 --- a/doc/mcpdft/README.md +++ b/doc/mcpdft/README.md @@ -65,6 +65,8 @@ Features 3. CMS-PDFT - Transition electric dipole moment (non-hybrid functionals only) for: 1. CMS-PDFT + - Derivative couplings for: + 1. CMS-PDFT [*JPC A* **2024**] * Multi-configuration density-coherence functional theory (MC-DCFT) total energy: [*JCTC* **2021**, *17*, 2775] @@ -81,6 +83,7 @@ Features [*Mol Phys* **2022**, 120]: http://dx.doi.org/10.1080/00268976.2022.2110534 [*Faraday Discuss* **2020**, 224, 348-372]: http://dx.doi.org/10.1039/D0FD00037J [*JCTC* **2023**, *19*, 3172]: https://dx.doi.org/10.1021/acs.jctc.3c00207 +[*JPC A* **2024**]: https://dx.doi.org/10.1021/acs.jpca.3c07048 [comment]: <> (Code hyperlinks) [examples/mcpdft/02-hybrid_functionals.py]: examples/mcpdft/02-hybrid_functionals.py diff --git a/examples/nac/01-cmspdft_nac.py b/examples/nac/01-cmspdft_nac.py new file mode 100644 index 00000000..c97dce8c --- /dev/null +++ b/examples/nac/01-cmspdft_nac.py @@ -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) + diff --git a/pyscf/mcpdft/__init__.py b/pyscf/mcpdft/__init__.py index 0c6560e7..9462373a 100644 --- a/pyscf/mcpdft/__init__.py +++ b/pyscf/mcpdft/__init__.py @@ -73,4 +73,7 @@ class MultiStateMCPDFTSolver : from pyscf.df import grad as df_grad df_grad.__path__.append (mydfgradpath) df_grad.__path__=list(set(df_grad.__path__)) - +mynacpath = os.path.join(mypath, "nac") +from pyscf import nac +nac.__path__.append(mynacpath) +nac.__path__ = list(set(nac.__path__)) diff --git a/pyscf/mcpdft/mspdft.py b/pyscf/mcpdft/mspdft.py index 4496b375..ad0e3239 100644 --- a/pyscf/mcpdft/mspdft.py +++ b/pyscf/mcpdft/mspdft.py @@ -516,6 +516,18 @@ def nuc_grad_method (self): from pyscf.grad.mspdft import Gradients return Gradients (self) + def nac_method(self): + if not isinstance(self, mc1step.CASSCF): + raise NotImplementedError("CASCI-based PDFT NACs") + elif getattr(self, 'frozen', None) is not None: + raise NotImplementedError("PDFT NACs with frozen orbitals") + elif isinstance(self, _DFCASSCF): + raise NotImplementedError("PDFT NACs with density fitting") + else: + from pyscf.nac.mspdft import NonAdiabaticCouplings + + return NonAdiabaticCouplings(self) + def dip_moment (self, unit='Debye', origin='Coord_Center', state=None): if not isinstance (self, mc1step.CASSCF): raise NotImplementedError ("CASCI-based PDFT dipole moments") diff --git a/pyscf/nac/mspdft.py b/pyscf/nac/mspdft.py new file mode 100644 index 00000000..94a29a47 --- /dev/null +++ b/pyscf/nac/mspdft.py @@ -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 + # . I want . + 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 . + 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) + + diff --git a/pyscf/nac/test/test_nac_cmspdft.py b/pyscf/nac/test/test_nac_cmspdft.py new file mode 100644 index 00000000..5b499c49 --- /dev/null +++ b/pyscf/nac/test/test_nac_cmspdft.py @@ -0,0 +1,210 @@ +import numpy as np +from pyscf import gto, scf,mcpdft +import unittest + +om_ta_alpha = [0.8, 0.9, # H, He + 1.8, 1.4, # Li, Be + 1.3, 1.1, 0.9, 0.9, 0.9, 0.9, # B - Ne + 1.4, 1.3, # Na, Mg + 1.3, 1.2, 1.1, 1.0, 1.0, 1.0, # Al - Ar + 1.5, 1.4, # K, Ca + 1.3, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.1, 1.1, 1.1, # Sc - Zn + 1.1, 1.0, 0.9, 0.9, 0.9, 0.9] # Ga - Kr +def om_treutler_ahlrichs(n, chg, *args, **kwargs): + ''' + "Treutler-Ahlrichs" as implemented in OpenMolcas + ''' + r = np.empty(n) + dr = np.empty(n) + alpha = om_ta_alpha[chg-1] + step = 2.0 / (n+1) # = numpy.pi / (n+1) + ln2 = alpha / np.log(2) + for i in range(n): + x = (i+1)*step - 1 # = numpy.cos((i+1)*step) + r [i] = -ln2*(1+x)**.6 * np.log((1-x)/2) + dr[i] = (step #* numpy.sin((i+1)*step) + * ln2*(1+x)**.6 *(-.6/(1+x)*np.log((1-x)/2)+1/(1-x))) + return r[::-1], dr[::-1] + +quasi_ultrafine = {'atom_grid': (99,590), + 'radi_method': om_treutler_ahlrichs, + 'prune': False, + 'radii_adjust': None} + +def diatomic(atom1, atom2, r, basis, ncas, nelecas, nstates, + charge=None, spin=None, symmetry=False, cas_irrep=None): + global mols + xyz = '{:s} 0.0 0.0 0.0; {:s} {:.3f} 0.0 0.0'.format(atom1, atom2, r) + mol = gto.M(atom=xyz, basis=basis, charge=charge, spin=spin, + symmetry=symmetry, verbose=0, output='/dev/null') + mols.append(mol) + + mf = scf.RHF(mol) + + mc = mcpdft.CASSCF(mf.run(), 'ftLDA,VWN3', ncas, nelecas, grids_attr=quasi_ultrafine).set(natorb=True) + # Quasi-ultrafine is ALMOST the same thing as + # ``` + # grid input + # nr=100 + # lmax=41 + # rquad=ta + # nopr + # noro + # end of grid input + # ``` + # in SEWARD + + if spin is not None: + s = spin*0.5 + + else: + s = (mol.nelectron % 2)*0.5 + + mc.fix_spin_(ss=s*(s+1), shift=1) + mc = mc.multi_state([1.0/float(nstates), ]*nstates, 'cms') + mc.conv_tol = mc.conv_tol_diabatize = 1e-10 + mc.max_cycle_macro = 100 + mc.max_cyc_diabatize = 200 + mo = None + + if symmetry and (cas_irrep is not None): + mo = mc.sort_mo_by_irrep(cas_irrep) + + mc.kernel(mo) + return mc.nac_method() + +def setUpModule(): + global mols + mols = [] + +def tearDownModule(): + global mols, diatomic + [m.stdout.close() for m in mols] + del mols, diatomic + + +class KnownValues(unittest.TestCase): + + def test_nac_h2_cms2ftlsda22_sto3g(self): + # z_orb: no + # z_ci: yes + # z_is: no + mc_grad = diatomic('H', 'H', 1.3, 'STO-3G', 2, 2, 2) + + # OpenMolcas v23.02 - PC + de_ref = np.array([[2.24611972496342E-01,2.24611972496344E-01], + [-6.59892598854941E-16, 6.54230823118723E-16]]) + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + + def test_nac_h2_cms3ftlsda22_sto3g(self): + # z_orb: no + # z_ci: no + # z_is: no + mc_grad = diatomic('H', 'H', 1.3, 'STO-3G', 2, 2, 3) + + # OpenMolcas v23.02 - PC + de_ref = np.array([[-2.21241754295429E-01,-2.21241754290091E-01], + [-2.66888744475119E-12, 2.66888744475119E-12]]) + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + def test_nac_h2_cms2ftlsda22_631g(self): + # z_orb: yes + # z_ci: yes + # z_is: no + mc_grad = diatomic('H', 'H', 1.3, '6-31G', 2, 2, 2) + + # OpenMolcas v23.02 - PC + de_ref = np.array([[2.63335709207423E-01,2.63335709207421E-01], + [9.47391702563375E-16,-1.02050903352196E-15]]) + + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + + def test_nac_h2_cms3ftlsda22_631g(self): + # z_orb: yes + # z_ci: no + # z_is: no + mc_grad = diatomic('H', 'H', 1.3, '6-31G', 2, 2, 3) + + # OpenMolcas v23.02 - PC + de_ref = np.array([[-2.56602732575249E-01,-2.56602732575251E-01], + [7.94113968580962E-16, -7.74815822050330E-16]]) + + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + def test_nac_lih_cms2ftlsda22_sto3g(self): + # z_orb: yes + # z_ci: yes + # z_is: yes + mc_grad = diatomic('Li', 'H', 1.5, 'STO-3G', 2, 2, 2) + + # OpenMolcas v23.02 - PC + de_ref = np.array([[1.59470493600856E-01,-4.49149709990789E-02 ], + [6.72530182376632E-02,-6.72530182376630E-02 ]]) + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + def test_nac_lih_cms3ftlsda22_sto3g(self): + # z_orb: yes + # z_ci: no + # z_is: yes + mc_grad = diatomic('Li', 'H', 2.5, 'STO-3G', 2, 2, 3) + + # OpenMolcas v23.02 - + de_ref = np.array([[-2.61694098289507E-01, 5.88264204831044E-02], + [-1.18760840775087E-01, 1.18760840775087E-01]]) + + for i in range(2): + with self.subTest(use_etfs=bool(i)): + de = mc_grad.kernel(state=(0, 1), use_etfs=bool(i))[:, 0] + self.assertTrue (mc_grad.base.converged, 'energy calculation not converged') + self.assertTrue (mc_grad.converged, 'gradient calculation not converged') + de *= np.sign(de[0]) * np.sign(de_ref[i, 0]) + # TODO: somehow confirm sign convention + self.assertAlmostEqual(de[0], de_ref[i, 0], 5) + self.assertAlmostEqual(de[1], de_ref[i, 1], 5) + + +if __name__ == "__main__": + print("Full Tests for CMS-PDFT non-adiabatic couplings of diatomic molecules") + unittest.main()