From c4ba677997dbb5176d25f5974a2e750139870ae0 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 17 Jul 2025 11:19:18 -0500 Subject: [PATCH 1/5] Copy tests to be preserved into dft2 folder --- pyscf/dft2/test/test_grad_metagga_mcpdft.py | 141 ++++++++++++ pyscf/dft2/test/test_lpdft.py | 142 ++++++++++++ pyscf/dft2/test/test_mgga.py | 226 ++++++++++++++++++++ 3 files changed, 509 insertions(+) create mode 100644 pyscf/dft2/test/test_grad_metagga_mcpdft.py create mode 100644 pyscf/dft2/test/test_lpdft.py create mode 100644 pyscf/dft2/test/test_mgga.py diff --git a/pyscf/dft2/test/test_grad_metagga_mcpdft.py b/pyscf/dft2/test/test_grad_metagga_mcpdft.py new file mode 100644 index 000000000..a555b3025 --- /dev/null +++ b/pyscf/dft2/test/test_grad_metagga_mcpdft.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# Copyright 2014-2025 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: Matthew Hennefarth + +import unittest + +from pyscf import scf, gto, df, dft +from pyscf.data.nist import BOHR +from pyscf import mcpdft + +def diatomic( + atom1, + atom2, + r, + fnal, + basis, + ncas, + nelecas, + nstates, + charge=None, + spin=None, + symmetry=False, + cas_irrep=None, + density_fit=False, + grids_level=9, +): + """Used for checking diatomic systems to see if the Lagrange Multipliers are working properly.""" + 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) + if density_fit: + mf = mf.density_fit(auxbasis=df.aug_etb(mol)) + + mc = mcpdft.CASSCF(mf.run(), fnal, ncas, nelecas, grids_level=grids_level) + if spin is None: + spin = mol.nelectron % 2 + + ss = spin * (spin + 2) * 0.25 + mc.fix_spin_(ss=ss, shift=2) + + if nstates > 1: + mc = mc.state_average( + [ + 1.0 / float(nstates), + ] + * nstates, + ) + + mc.conv_tol = 1e-12 + mc.conv_grad_tol = 1e-6 + mo = None + if symmetry and (cas_irrep is not None): + mo = mc.sort_mo_by_irrep(cas_irrep) + + mc_grad = mc.run(mo).nuc_grad_method() + mc_grad.conv_rtol = 1e-12 + return mc_grad + + +def setUpModule(): + global mols, original_grids + mols = [] + original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS + dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False + + +def tearDownModule(): + global mols, diatomic, original_grids + [m.stdout.close() for m in mols] + dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids + del mols, diatomic, original_grids + + +class KnownValues(unittest.TestCase): + + def test_grad_lih_ssmc2322_sto3g(self): + mc = diatomic("Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 1, grids_level=1) + de = mc.kernel()[1, 0] / BOHR + + # Numerical from this software + # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 + # PySCF-forge commit: e82ba940654cd0b91f799e889136a316fda34b10 + DE_REF = -1.0641645070 + + self.assertAlmostEqual(de, DE_REF, 5) + + def test_grad_lih_sa2mc2322_sto3g(self): + mc = diatomic("Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 2, grids_level=1) + + # Numerical from this software + # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 + # PySCF-forge commit: e82ba940654cd0b91f799e889136a316fda34b10 + DE_REF = [-1.0510225010, -0.8963063432] + + for state in range(2): + with self.subTest(state=state): + de = mc.kernel(state=state)[1, 0] / BOHR + self.assertAlmostEqual(de, DE_REF[state], 5) + + def test_grad_lih_sa2mc2322_sto3g_df(self): + mc = diatomic( + "Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 2, grids_level=1, density_fit=df + ) + + # Numerical from this software + # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 + # PySCF-forge commit: ee6ac742fbc79d170bc4b63ef2b2c4b49478c53a + DE_REF = [-1.0510303416, -0.8963992331] + + for state in range(2): + with self.subTest(state=state): + de = mc.kernel(state=state)[1, 0] / BOHR + self.assertAlmostEqual(de, DE_REF[state], 5) + + +if __name__ == "__main__": + print("Full Tests for MC-PDFT gradients with meta-GGA functionals") + unittest.main() diff --git a/pyscf/dft2/test/test_lpdft.py b/pyscf/dft2/test/test_lpdft.py new file mode 100644 index 000000000..9cf12d667 --- /dev/null +++ b/pyscf/dft2/test/test_lpdft.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# Copyright 2014-2023 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: Matthew Hennefarth + +import tempfile, h5py +import numpy as np +from pyscf import gto, scf, dft, fci, lib +from pyscf import mcpdft +import unittest + + +def get_lih (r, n_states=2, functional='ftLDA,VWN3', basis='sto3g'): + mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis=basis, + output='/dev/null', verbose=0) + mf = scf.RHF (mol).run () + if n_states == 2: + mc = mcpdft.CASSCF (mf, functional, 2, 2, grids_level=1) + + else: + mc = mcpdft.CASSCF(mf, functional, 5, 2, grids_level=1) + + mc.fix_spin_(ss=0) + weights = [1.0/float(n_states), ] * n_states + + mc = mc.multi_state(weights, "lin") + mc = mc.run() + return mc + +def get_water(functional='tpbe', basis='6-31g'): + mol = gto.M(atom=''' + O 0. 0.000 0.1174 + H 0. 0.757 -0.4696 + H 0. -0.757 -0.4696 + ''',symmetry=True, basis=basis, output='/dev/null', verbose=0) + + mf = scf.RHF(mol).run() + + weights = [0.5, 0.5] + solver1 = fci.direct_spin1_symm.FCI(mol) + solver1.wfnsym = 'A1' + solver1.spin = 0 + solver2 = fci.direct_spin1_symm.FCI(mol) + solver2.wfnsym = 'A2' + solver2.spin = 2 + + mc = mcpdft.CASSCF(mf, functional, 4, 4, grids_level=1) + mc.chkfile = tempfile.NamedTemporaryFile().name + # mc.chk_ci = True + mc = mc.multi_state_mix([solver1, solver2], weights, "lin") + mc.run() + return mc + +def get_water_triplet(functional='tPBE', basis="6-31G"): + mol = gto.M(atom=''' + O 0. 0.000 0.1174 + H 0. 0.757 -0.4696 + H 0. -0.757 -0.4696 + ''', symmetry=True, basis=basis, output='/dev/null', verbose=0) + + mf = scf.RHF(mol).run() + + weights = np.ones(3) / 3 + solver1 = fci.direct_spin1_symm.FCI(mol) + solver1.spin = 2 + solver1 = fci.addons.fix_spin(solver1, shift=.2, ss=2) + solver1.nroots = 1 + solver2 = fci.direct_spin0_symm.FCI(mol) + solver2.spin = 0 + solver2.nroots = 2 + + mc = mcpdft.CASSCF(mf, functional, 4, 4, grids_level=1) + mc.chkfile = tempfile.NamedTemporaryFile().name + # mc.chk_ci = True + mc = mc.multi_state_mix([solver1, solver2], weights, "lin") + mc.run() + return mc + + +def setUpModule(): + global lih, lih_4, lih_tpbe, lih_tpbe0, lih_mc23, water, t_water, original_grids + original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS + dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False + lih = get_lih(1.5) + lih_4 = get_lih(1.5, n_states=4, basis="6-31G") + lih_tpbe = get_lih(1.5, functional="tPBE") + lih_tpbe0 = get_lih(1.5, functional="tPBE0") + lih_mc23 = get_lih(1.5, functional="MC23") + water = get_water() + t_water = get_water_triplet() + +def tearDownModule(): + global lih, lih_4, lih_tpbe0, lih_tpbe, t_water, water, original_grids, lih_mc23 + dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids + lih.mol.stdout.close() + lih_4.mol.stdout.close() + lih_tpbe0.mol.stdout.close() + lih_tpbe.mol.stdout.close() + water.mol.stdout.close() + t_water.mol.stdout.close() + del lih, lih_4, lih_tpbe0, lih_tpbe, t_water, water, original_grids, lih_mc23 + +class KnownValues(unittest.TestCase): + + def assertListAlmostEqual(self, first_list, second_list, expected): + self.assertTrue(len(first_list) == len(second_list)) + for first, second in zip(first_list, second_list): + self.assertAlmostEqual(first, second, expected) + + def test_lih_mc23_adiabat(self): + e_mcscf_mc23_avg = np.dot(lih_mc23.e_mcscf, lih_mc23.weights) + hcoup = abs(lih_mc23.lpdft_ham[1,0]) + hdiag = lih_mc23.get_lpdft_diag() + + # Reference values from + # - PySCF commit 9a0bb6ddded7049bdacdaf4cfe422f7ce826c2c7 + # - PySCF-forge commit eb0ad96f632994d2d1846009ecce047193682526 + E_MCSCF_AVG_EXPECTED = -7.78902182 + E_MC23_EXPECTED = [-7.94539408, -7.80094952] + HCOUP_EXPECTED = 0.01285147 + HDIAG_EXPECTED = [-7.94424147, -7.80210214] + + self.assertAlmostEqual(e_mcscf_mc23_avg, E_MCSCF_AVG_EXPECTED, 7) + self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 7) + self.assertAlmostEqual(lib.fp(hdiag), lib.fp(HDIAG_EXPECTED), 7) + self.assertAlmostEqual(lib.fp(lih_mc23.e_states), lib.fp(E_MC23_EXPECTED), 7) + +if __name__ == "__main__": + print("Full Tests for Linearized-PDFT") + unittest.main() diff --git a/pyscf/dft2/test/test_mgga.py b/pyscf/dft2/test/test_mgga.py new file mode 100644 index 000000000..6df6322e8 --- /dev/null +++ b/pyscf/dft2/test/test_mgga.py @@ -0,0 +1,226 @@ +#!/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. +# +# Author: Bhavnesh Jangid + +import numpy as np +from pyscf import gto, scf, dft, fci +from pyscf import mcpdft +import unittest + +''' +In this unit-test, test the MCPDFT energies calculated for the LiH +molecule at the state-specific and state-average (2-states) using +1. Meta-GGA functional (tM06L) +2. Hybrid-meta-GGA functional tM06L0 +3. MC23 Functional + +Test the MCPDFT energies calculated for the triplet water molecule at the +4. Meta-GGA functional (M06L) +5. MC23 Functional + +Note: The reference values are generated from +OpenMolcas v24.10, tag 682-gf74be507d + +The OpenMolcas results were obtained with this grid settings +&SEWARD +Grid Input +RQuad=TA +NR=100 +LMAX=41 +NOPrun +NOSCreening +''' + +# To be consistent with OpenMolcas Grid Settings. Grids_att is defined as below +# Source: pyscf/mcpdft/test/test_diatomic_energies.py + +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] + +my_grids = {'atom_grid': (99,590), + 'radi_method': om_treutler_ahlrichs, + 'prune': False, + 'radii_adjust': None} + +def get_lih (r, stateaverage=False, functional='tM06L', basis='sto3g'): + mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis=basis, + output='/dev/null', verbose=0) + mf = scf.RHF (mol).run () + if functional == 'tM06L0': + tM06L0 = 't' + mcpdft.hyb('M06L',0.25, hyb_type='average') + mc = mcpdft.CASSCF(mf, tM06L0, 5, 2, grids_attr=my_grids) + else: + mc = mcpdft.CASSCF(mf, functional, 5, 2, grids_attr=my_grids) + + if stateaverage: + mc = mc.state_average_([0.5, 0.5]) + + mc.fix_spin_(ss=0) + mc = mc.run() + return mc + +def get_water_triplet(functional='tM06L', basis='6-31G'): + mol = gto.M(atom=''' + O 0. 0.000 0.1174 + H 0. 0.757 -0.4696 + H 0. -0.757 -0.4696 + ''',basis=basis, spin=2,output='/dev/null', verbose=0) + + mf = scf.RHF(mol).run() + + mc = mcpdft.CASSCF(mf, functional, 2, 2, grids_attr=my_grids) + solver1 = fci.direct_spin1.FCI(mol) + solver1 = fci.addons.fix_spin(solver1, ss=2) + mc.fcisolver = solver1 + mc = mc.run() + return mc + +def setUpModule(): + global get_lih, lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2,lih_tm06l0 + global get_water_triplet, water_tm06l, water_tmc23 + global lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 + + # register otfnal tMC23_2 which is identical to MC23 + mc232_preset = mcpdft.otfnal.OT_PRESET['MC23'] + mcpdft.otfnal.register_otfnal('MC23_2', mc232_preset) + + lih_tm06l = get_lih(1.5, functional='tM06L') + lih_tmc23 = get_lih(1.5, functional='MC23') + lih_tmc23_2 = get_lih(1.5, functional='tMC23_2') + lih_tm06l_sa2 = get_lih(1.5, stateaverage=True, functional='tM06L') + lih_tmc23_sa2 = get_lih(1.5, stateaverage=True, functional='MC23') + lih_tmc23_sa2_2 = get_lih(1.5, stateaverage=True, functional='tmc23_2') + lih_tm06l0 = get_lih(1.5, functional='tM06L0') + water_tm06l = get_water_triplet() + water_tmc23 = get_water_triplet(functional='MC23') + water_tmc23_2 = get_water_triplet(functional='TMc23_2') + +def tearDownModule(): + global lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2 + global lih_tm06l0, water_tm06l, water_tmc23 + global lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 + + lih_tm06l.mol.stdout.close() + lih_tmc23.mol.stdout.close() + lih_tmc23_2.mol.stdout.close() + lih_tm06l_sa2.mol.stdout.close() + lih_tmc23_sa2.mol.stdout.close() + lih_tmc23_sa2_2.mol.stdout.close() + lih_tm06l0.mol.stdout.close() + water_tm06l.mol.stdout.close() + water_tmc23.mol.stdout.close() + water_tmc23_2.mol.stdout.close() + + mcpdft.otfnal.unregister_otfnal('tMC23_2') + + del lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2 + del lih_tm06l0, water_tm06l, water_tmc23 + del lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 + +class KnownValues(unittest.TestCase): + + def assertListAlmostEqual(self, first_list, second_list, expected): + self.assertTrue(len(first_list) == len(second_list)) + for first, second in zip(first_list, second_list): + self.assertAlmostEqual(first, second, expected) + + def test_tmc23(self): + e_mcscf = lih_tmc23.e_mcscf + epdft = lih_tmc23.e_tot + + sa_e_mcscf = lih_tmc23_sa2.e_mcscf + sa_epdft = lih_tmc23_sa2.e_states + + # The CAS and MCPDFT reference values are generated using + # OpenMolcas v24.10, tag 682-gf74be507d + E_CASSCF_EXPECTED = -7.88214917 + E_MCPDFT_EXPECTED = -7.95098727 + SA_E_CASSCF_EXPECTED = [-7.88205449, -7.74391704] + SA_E_MCPDFT_EXPECTED = [-7.95093826, -7.80604012] + + self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) + self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) + self.assertListAlmostEqual(sa_e_mcscf, SA_E_CASSCF_EXPECTED, 6) + self.assertListAlmostEqual(sa_epdft, SA_E_MCPDFT_EXPECTED, 6) + + def test_tmc23_2(self): + e_mcscf = lih_tmc23_2.e_mcscf + epdft = lih_tmc23_2.e_tot + + sa_e_mcscf = lih_tmc23_sa2_2.e_mcscf + sa_epdft = lih_tmc23_sa2_2.e_states + + # The CAS and MCPDFT reference values are generated using + # OpenMolcas v24.10, tag 682-gf74be507d + E_CASSCF_EXPECTED = -7.88214917 + E_MCPDFT_EXPECTED = -7.95098727 + SA_E_CASSCF_EXPECTED = [-7.88205449, -7.74391704] + SA_E_MCPDFT_EXPECTED = [-7.95093826, -7.80604012] + + self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) + self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) + self.assertListAlmostEqual(sa_e_mcscf, SA_E_CASSCF_EXPECTED, 6) + self.assertListAlmostEqual(sa_epdft, SA_E_MCPDFT_EXPECTED, 6) + + def test_water_triplet_tmc23(self): + e_mcscf = water_tmc23.e_mcscf + epdft = water_tmc23.e_tot + + # The CAS and MCPDFT reference values are generated using + # OpenMolcas v24.10, tag 682-gf74be507d + E_CASSCF_EXPECTED = -75.72365496 + E_MCPDFT_EXPECTED = -76.02630019 + + self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) + self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) + + def test_water_triplet_tmc23_2(self): + e_mcscf = water_tmc23_2.e_mcscf + epdft = water_tmc23_2.e_tot + + # The CAS and MCPDFT reference values are generated using + # OpenMolcas v24.10, tag 682-gf74be507d + E_CASSCF_EXPECTED = -75.72365496 + E_MCPDFT_EXPECTED = -76.02630019 + + self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) + self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) + +if __name__ == "__main__": + print("Full Tests for MGGAs, Hybrid-MGGAs, and MC23") + unittest.main() From ec4866718205ab70714e820a04997994cf6b1883 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 17 Jul 2025 11:22:45 -0500 Subject: [PATCH 2/5] Remove files being moved to PySCF core (#144) --- examples/mcpdft/00-simple_mcpdft.py | 41 - examples/mcpdft/01-different_functionals.py | 49 - examples/mcpdft/02-hybrid_functionals.py | 49 - examples/mcpdft/03-metaGGA_functionals.py | 9 - examples/mcpdft/11-grid_scheme.py | 40 - examples/mcpdft/15-state_average.py | 30 - examples/mcpdft/16-multi_state.py | 43 - examples/mcpdft/41-state_average.py | 80 -- examples/mcpdft/42-linearized-pdft.py | 38 - examples/mcpdft/43-multi_state_mix.py | 71 - pyscf/df/grad/lpdft.py | 42 - pyscf/df/grad/mcpdft.py | 41 - pyscf/df/grad/mspdft.py | 54 - pyscf/grad/cmspdft.py | 386 ------ pyscf/grad/lpdft.py | 700 ---------- pyscf/grad/mcpdft.py | 654 --------- pyscf/grad/mspdft.py | 677 --------- .../test/h2co_sa2_tpbe66_631g_grad_num.npy | Bin 512 -> 0 bytes pyscf/grad/test/h2co_tpbe66_631g_grad_num.npy | Bin 320 -> 0 bytes pyscf/grad/test/test_diatomic_gradients.py | 230 ---- pyscf/grad/test/test_grad_cmspdft.py | 124 -- pyscf/grad/test/test_grad_h2co.py | 99 -- pyscf/grad/test/test_grad_h2co_slow.py | 93 -- pyscf/grad/test/test_grad_lpdft.py | 313 ----- pyscf/grad/test/test_grad_mcpdft.py | 179 --- pyscf/grad/test/test_grad_metagga_mcpdft.py | 165 --- pyscf/grad/test/test_grad_mspdft.py | 179 --- pyscf/mcpdft/__init__.py | 113 -- pyscf/mcpdft/_dms.py | 258 ---- pyscf/mcpdft/_libxc.py | 135 -- pyscf/mcpdft/chkfile.py | 130 -- pyscf/mcpdft/cmspdft.py | 191 --- pyscf/mcpdft/lpdft.py | 768 ----------- pyscf/mcpdft/mcpdft.py | 885 ------------ pyscf/mcpdft/mspdft.py | 685 ---------- pyscf/mcpdft/nr_numint.c | 123 -- pyscf/mcpdft/otfnal.py | 1207 ----------------- pyscf/mcpdft/otpd.py | 300 ---- pyscf/mcpdft/pdft_eff.py | 497 ------- pyscf/mcpdft/pdft_feff.py | 415 ------ pyscf/mcpdft/pdft_veff.py | 390 ------ pyscf/mcpdft/test/test_cmspdft.py | 177 --- pyscf/mcpdft/test/test_diatomic_energies.py | 203 --- pyscf/mcpdft/test/test_lpdft.py | 256 ---- pyscf/mcpdft/test/test_mcpdft.py | 771 ----------- pyscf/mcpdft/test/test_mgga.py | 269 ---- pyscf/mcpdft/test/test_mspdft.py | 127 -- pyscf/mcpdft/test/test_otfnal.py | 111 -- pyscf/mcpdft/test/test_otpd.py | 235 ---- pyscf/mcpdft/test/test_pdft_feff.py | 268 ---- pyscf/mcpdft/test/test_pdft_veff.py | 144 -- pyscf/mcpdft/test/test_spingaps_atoms.py | 174 --- pyscf/mcpdft/test/test_xmspdft.py | 150 -- pyscf/mcpdft/tfnal_derivs.py | 830 ------------ pyscf/mcpdft/xmspdft.py | 193 --- pyscf/nac/mspdft.py | 196 --- pyscf/nac/test/test_nac_cmspdft.py | 210 --- 57 files changed, 14797 deletions(-) delete mode 100644 examples/mcpdft/00-simple_mcpdft.py delete mode 100644 examples/mcpdft/01-different_functionals.py delete mode 100644 examples/mcpdft/02-hybrid_functionals.py delete mode 100644 examples/mcpdft/11-grid_scheme.py delete mode 100644 examples/mcpdft/15-state_average.py delete mode 100644 examples/mcpdft/16-multi_state.py delete mode 100644 examples/mcpdft/41-state_average.py delete mode 100644 examples/mcpdft/42-linearized-pdft.py delete mode 100644 examples/mcpdft/43-multi_state_mix.py delete mode 100644 pyscf/df/grad/lpdft.py delete mode 100644 pyscf/df/grad/mcpdft.py delete mode 100644 pyscf/df/grad/mspdft.py delete mode 100644 pyscf/grad/cmspdft.py delete mode 100644 pyscf/grad/lpdft.py delete mode 100644 pyscf/grad/mcpdft.py delete mode 100644 pyscf/grad/mspdft.py delete mode 100644 pyscf/grad/test/h2co_sa2_tpbe66_631g_grad_num.npy delete mode 100644 pyscf/grad/test/h2co_tpbe66_631g_grad_num.npy delete mode 100644 pyscf/grad/test/test_diatomic_gradients.py delete mode 100644 pyscf/grad/test/test_grad_cmspdft.py delete mode 100644 pyscf/grad/test/test_grad_h2co.py delete mode 100644 pyscf/grad/test/test_grad_h2co_slow.py delete mode 100644 pyscf/grad/test/test_grad_lpdft.py delete mode 100644 pyscf/grad/test/test_grad_mcpdft.py delete mode 100644 pyscf/grad/test/test_grad_metagga_mcpdft.py delete mode 100644 pyscf/grad/test/test_grad_mspdft.py delete mode 100644 pyscf/mcpdft/__init__.py delete mode 100644 pyscf/mcpdft/_dms.py delete mode 100644 pyscf/mcpdft/_libxc.py delete mode 100644 pyscf/mcpdft/chkfile.py delete mode 100644 pyscf/mcpdft/cmspdft.py delete mode 100644 pyscf/mcpdft/lpdft.py delete mode 100644 pyscf/mcpdft/mcpdft.py delete mode 100644 pyscf/mcpdft/mspdft.py delete mode 100644 pyscf/mcpdft/nr_numint.c delete mode 100644 pyscf/mcpdft/otfnal.py delete mode 100644 pyscf/mcpdft/otpd.py delete mode 100644 pyscf/mcpdft/pdft_eff.py delete mode 100644 pyscf/mcpdft/pdft_feff.py delete mode 100644 pyscf/mcpdft/pdft_veff.py delete mode 100644 pyscf/mcpdft/test/test_cmspdft.py delete mode 100644 pyscf/mcpdft/test/test_diatomic_energies.py delete mode 100644 pyscf/mcpdft/test/test_lpdft.py delete mode 100644 pyscf/mcpdft/test/test_mcpdft.py delete mode 100644 pyscf/mcpdft/test/test_mgga.py delete mode 100644 pyscf/mcpdft/test/test_mspdft.py delete mode 100644 pyscf/mcpdft/test/test_otfnal.py delete mode 100644 pyscf/mcpdft/test/test_otpd.py delete mode 100644 pyscf/mcpdft/test/test_pdft_feff.py delete mode 100644 pyscf/mcpdft/test/test_pdft_veff.py delete mode 100644 pyscf/mcpdft/test/test_spingaps_atoms.py delete mode 100644 pyscf/mcpdft/test/test_xmspdft.py delete mode 100644 pyscf/mcpdft/tfnal_derivs.py delete mode 100644 pyscf/mcpdft/xmspdft.py delete mode 100644 pyscf/nac/mspdft.py delete mode 100644 pyscf/nac/test/test_nac_cmspdft.py diff --git a/examples/mcpdft/00-simple_mcpdft.py b/examples/mcpdft/00-simple_mcpdft.py deleted file mode 100644 index 6fe893023..000000000 --- a/examples/mcpdft/00-simple_mcpdft.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env/python - -from pyscf import gto, scf, mcpdft - -mol = gto.M ( - atom = 'O 0 0 0; O 0 0 1.2', - basis = 'ccpvdz', - spin = 2) - -mf = scf.RHF (mol).run () - -# 1. CASCI density - -mc0 = mcpdft.CASCI (mf, 'tPBE', 6, 8).run () - -# 2. CASSCF density -# Note that the MC-PDFT energy may not be lower, even though -# E(CASSCF)<=E(CASCI). - -mc1 = mcpdft.CASSCF (mf, 'tPBE', 6, 8).run () - -# 3. analyze () does the same thing as CASSCF analyze () - -mc1.verbose = 4 -mc1.analyze () - -# 4. Energy decomposition for additional analysis - -e_decomp = mc1.get_energy_decomposition (split_x_c=False) -print ("e_nuc =",e_decomp[0]) -print ("e_1e =",e_decomp[1]) -print ("e_Coul =",e_decomp[2]) -print ("e_OT =",e_decomp[3]) -print ("e_ncwfn (not included in total energy) =",e_decomp[4]) -print ("e_PDFT - e_MCSCF =", mc1.e_tot - mc1.e_mcscf) -print ("e_OT - e_ncwfn =", e_decomp[3] - e_decomp[4]) -e_decomp = mc1.get_energy_decomposition (split_x_c=True) -print ("e_OT (x component) = ",e_decomp[3]) -print ("e_OT (c component) = ",e_decomp[4]) - - diff --git a/examples/mcpdft/01-different_functionals.py b/examples/mcpdft/01-different_functionals.py deleted file mode 100644 index adbb1bda8..000000000 --- a/examples/mcpdft/01-different_functionals.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env/python - -from pyscf import gto, scf, mcpdft - -mol = gto.M ( - atom = 'O 0 0 0; O 0 0 1.2', - basis = 'ccpvdz', - spin = 2) - -mf = scf.RHF (mol).run () - -mc = mcpdft.CASCI (mf, 'tPBE', 6, 8).run () - -# 1. Change the functional by setting mc.otxc - -mc.otxc = 'ftBLYP' -mc.kernel () - -# 2. Change the functional and compute energy in one line without -# reoptimizing the wave function using compute_pdft_energy_ - -mc.compute_pdft_energy_(otxc='ftPBE') - -# -# The leading "t" or "ft" identifies either a "translated functional" -# [JCTC 10, 3669 (2014)] or a "fully-translated functional" -# [JCTC 11, 4077 (2015)]. It can be combined with a general PySCF -# xc string containing any number of pure LDA or GGA functionals. -# Meta-GGAs, built-in hybrid functionals ("B3LYP"), and range-separated -# functionals are not supported. -# - -# 3. A translated user-defined compound functional - -mc.compute_pdft_energy_(otxc="t0.3*B88 + 0.7*SLATER,0.4*VWN5+0.6*LYP") - -# 4. A fully-translated functional consisting of "exchange" only - -mc.compute_pdft_energy_(otxc="ftPBE,") - -# 5. A fully-translated functional consisting of "correlation" only - -mc.compute_pdft_energy_(otxc="ft,PBE") - -# 6. The sum of 5 and 6 (look at the "Eot" output) - -mc.compute_pdft_energy_(otxc="ftPBE") - - diff --git a/examples/mcpdft/02-hybrid_functionals.py b/examples/mcpdft/02-hybrid_functionals.py deleted file mode 100644 index b8956afd6..000000000 --- a/examples/mcpdft/02-hybrid_functionals.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env/python - - -# -# JPCL 11, 10158 (2020) -# (but see #5 below) -# - -from pyscf import gto, scf, mcpdft - -mol = gto.M ( - atom = 'O 0 0 0; O 0 0 1.2', - basis = 'ccpvdz', - spin = 2) - -mf = scf.RHF (mol).run () - -# 1. The only two predefined hybrid functionals as of writing - -mc = mcpdft.CASSCF (mf, 'tPBE0', 6, 8).run () -mc.compute_pdft_energy_(otxc='ftPBE0') - -# 2. Other predefined hybrid functionals are not supported - -try: - mc.compute_pdft_energy_(otxc='tB3LYP') -except NotImplementedError as e: - print ("otxc='tB3LYP' results in NotImplementedError:") - print (str (e)) - -# 3. Construct a custom hybrid functional by hand - -my_otxc = 't.8*B88 + .2*HF, .8*LYP + .2*HF' -mc.compute_pdft_energy_(otxc=my_otxc) - -# 4. Construct the same custom functional using helper function - -my_otxc = 't' + mcpdft.hyb ('BLYP', .2) -mc.compute_pdft_energy_(otxc=my_otxc) - -# 5. "lambda-MC-PDFT" of JCTC 16, 2274, 2020. - -my_otxc = 't' + mcpdft.hyb('PBE', .5, hyb_type='lambda') -# = 't0.5*PBE + 0.5*HF, 0.75*PBE + 0.5*HF' -mc.compute_pdft_energy_(otxc=my_otxc) - - - - diff --git a/examples/mcpdft/03-metaGGA_functionals.py b/examples/mcpdft/03-metaGGA_functionals.py index b61c0197e..8b5fac5e2 100644 --- a/examples/mcpdft/03-metaGGA_functionals.py +++ b/examples/mcpdft/03-metaGGA_functionals.py @@ -8,15 +8,6 @@ mf = scf.RHF (mol).run () -# The translation of Meta-GGAs and hybrid-Meta-GGAs [PNAS, 122, 1, 2025, e2419413121; https://doi.org/10.1073/pnas.2419413121] - -# Translated-Meta-GGA -mc = mcpdft.CASCI(mf, 'tM06L', 6, 8).run () - -# Hybrid-Translated-Meta-GGA -tM06L0 = 't' + mcpdft.hyb('M06L',0.25, hyb_type='average') -mc = mcpdft.CASCI(mf, tM06L0, 6, 8).run () - # MC23: meta-hybrid on-top functional [PNAS, 122, 1, 2025, e2419413121; https://doi.org/10.1073/pnas.2419413121] # State-Specific diff --git a/examples/mcpdft/11-grid_scheme.py b/examples/mcpdft/11-grid_scheme.py deleted file mode 100644 index 0fc860cfa..000000000 --- a/examples/mcpdft/11-grid_scheme.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python - -from pyscf import gto, scf, dft -from pyscf import mcpdft - -# See also pyscf/examples/dft/11-grid-scheme.py - -mol = gto.M( - verbose = 0, - atom = ''' - o 0 0. 0. - h 0 -0.757 0.587 - h 0 0.757 0.587''', - basis = '6-31g') -mf = scf.RHF (mol).run () -mc = mcpdft.CASSCF(mf, 'tLDA', 4, 4).run() -print('Default grid setup. E = %.12f' % mc.e_tot) - -# See pyscf/dft/radi.py for more radial grid schemes -mc.grids.radi_method = dft.mura_knowles -print('radi_method = mura_knowles. E = %.12f' % mc.kernel()[0]) - -# All in one command: -mc = mcpdft.CASSCF (mf, 'tLDA', 4, 4, - grids_attr={'radi_method': dft.gauss_chebyshev}) -print('radi_method = gauss_chebyshev. E = %.12f' % mc.kernel ()[0]) - -# Or inline with an already-built mc object: -e = mc.compute_pdft_energy_(grids_attr={'radi_method': dft.delley})[0] -print('radi_method = delley. E = %.12f' % e) - -# All grids attributes can be addressed in any of the ways above -# There is also a shortcut to address grids.level: - -mc = mcpdft.CASSCF(mf, 'tLDA', 4, 4, grids_level=4).run() -print('grids.level = 4. E = %.12f' % mc.e_tot) - -e = mc.compute_pdft_energy_(grids_level=2)[0] -print('grids.level = 2. E = %.12f' % e) - diff --git a/examples/mcpdft/15-state_average.py b/examples/mcpdft/15-state_average.py deleted file mode 100644 index 886bcaa9d..000000000 --- a/examples/mcpdft/15-state_average.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -''' -State average - -This works the same way as mcscf.state_average_, but you -must use the method attribute (mc.state_average, mc.state_average_) -instead of the function call. -''' - -from pyscf import gto, scf, mcpdft - -mol = gto.M( - atom = [ - ['O', ( 0., 0. , 0. )], - ['H', ( 0., -0.757, 0.587)], - ['H', ( 0., 0.757 , 0.587)],], - basis = '6-31g', - symmetry = 1) - -mf = scf.RHF(mol) -mf.kernel() - -mc = mcpdft.CASSCF(mf, 'tPBE', 4, 4).state_average_([.64,.36]).run (verbose=4) - -print ("Average MC-PDFT energy =", mc.e_tot) -print ("E_PDFT-E_CASSCF for state 0 =", mc.e_states[0]-mc.e_mcscf[0]) -print ("E_PDFT-E_CASSCF for state 1 =", mc.e_states[1]-mc.e_mcscf[1]) - - diff --git a/examples/mcpdft/16-multi_state.py b/examples/mcpdft/16-multi_state.py deleted file mode 100644 index 6ec8c4a44..000000000 --- a/examples/mcpdft/16-multi_state.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python - -''' -multi-state - -A "quasidegenerate" extension of MC-PDFT for states close in energy. Currently -only "compressed multi-state" (CMS-) [JCTC 16, 7444 (2020)] and "extended multi- -state" (XMS-) [Faraday Discuss 224, 348-372 (2020)] is supported. -''' - -from pyscf import gto, scf, mcpdft - -mol = gto.M( - atom = [ - ['Li', ( 0., 0. , 0. )], - ['H', ( 0., 0., 3)] - ], basis = 'sto-3g', - symmetry = 0 # symmetry enforcement is not recommended for MS-PDFT - ) - -mf = scf.RHF(mol) -mf.kernel() - -mc = mcpdft.CASSCF(mf, 'tpbe', 2, 2) -mc.fix_spin_(ss=0) # often necessary! -mc_sa = mc.state_average ([.5, .5]).run () - -# For CMS-PDFT -mc_cms = mc.multi_state([.5, .5], "cms").run(verbose=4) - -# For XMS-PDFT -mc_xms = mc.multi_state([.5, .5], "xms").run(verbose=4) - -print ('{:>21s} {:>12s} {:>12s}'.format ('state 0','state 1', 'gap')) -fmt_str = '{:>9s} {:12.9f} {:12.9f} {:12.9f}' -print (fmt_str.format ('CASSCF', mc_sa.e_mcscf[0], mc_sa.e_mcscf[1], - mc_sa.e_mcscf[1]-mc_sa.e_mcscf[0])) -print (fmt_str.format ('MC-PDFT', mc_sa.e_states[0], mc_sa.e_states[1], - mc_sa.e_states[1]-mc_sa.e_states[0])) -print (fmt_str.format ('CMS-PDFT', mc_cms.e_states[0], mc_cms.e_states[1], - mc_cms.e_states[1]-mc_cms.e_states[0])) -print (fmt_str.format ('XMS-PDFT', mc_xms.e_states[0], mc_xms.e_states[1], - mc_xms.e_states[1]-mc_xms.e_states[0])) diff --git a/examples/mcpdft/41-state_average.py b/examples/mcpdft/41-state_average.py deleted file mode 100644 index 693e22422..000000000 --- a/examples/mcpdft/41-state_average.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python - -''' -State average over states of different spins and/or spatial symmetry - -In MC-PDFT, you must use the method attribute rather than the function: - -NO : mc = mcscf.state_average_mix (mc, [solver1, solver2], weights) -NO : mcscf.state_average_mix_(mc, [solver1, solver2], weights) -YES : mc = mc.state_average_mix ([solver1, solver2], weights) -YES : mc.state_average_mix_([solver1, solver2], weights) -''' - -import numpy as np -from pyscf import gto, scf, mcpdft, fci - -r = 1.8 -mol = gto.Mole() -mol.atom = [ - ['C', ( 0., 0. , -r/2 )], - ['C', ( 0., 0. , r/2)],] -mol.basis = 'cc-pvdz' -mol.unit = 'B' -mol.symmetry = True -mol.build() -mf = scf.RHF(mol) -mf.irrep_nelec = {'A1g': 4, 'E1gx': 0, 'E1gy': 0, 'A1u': 4, - 'E1uy': 2, 'E1ux': 2, 'E2gx': 0, 'E2gy': 0, 'E2uy': 0, 'E2ux': 0} -ehf = mf.kernel() -#mf.analyze() - -# -# state-average over 1 triplet + 2 singlets -# Note direct_spin1 solver is called here because the CI solver will take -# spin-mix solution as initial guess which may break the spin symmetry -# required by direct_spin0 solver -# -weights = np.ones(3)/3 -solver1 = fci.direct_spin1_symm.FCI(mol) -solver1.spin = 2 -solver1 = fci.addons.fix_spin(solver1, shift=.2, ss=2) -solver1.nroots = 1 -solver2 = fci.direct_spin0_symm.FCI(mol) -solver2.spin = 0 -solver2.nroots = 2 - -mc = mcpdft.CASSCF(mf, 'tPBE', 8, 8) -mc.state_average_mix_([solver1, solver2], weights) - -# Mute warning msgs -mc.check_sanity = lambda *args: None - -mc.verbose = 4 -mc.kernel() - - -# -# Example 2: Mix FCI wavefunctions with different symmetry irreps -# -mol = gto.Mole() -mol.build(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 -''', symmetry=True, basis='631g') - -mf = scf.RHF(mol).run() - -weights = [.5, .5] -solver1 = fci.direct_spin1_symm.FCI(mol) -solver1.wfnsym= 'A1' -solver1.spin = 0 -solver2 = fci.direct_spin1_symm.FCI(mol) -solver2.wfnsym= 'A2' -solver2.spin = 0 - -mc = mcpdft.CASSCF(mf, 'tPBE', 4, 4) -mc.state_average_mix_([solver1, solver2], weights) -mc.verbose = 4 -mc.kernel() diff --git a/examples/mcpdft/42-linearized-pdft.py b/examples/mcpdft/42-linearized-pdft.py deleted file mode 100644 index 51a51c6a0..000000000 --- a/examples/mcpdft/42-linearized-pdft.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -''' -linearized pair-density functional theory - -A multi-states extension of MC-PDFT for states close in energy. Generates an -effective L-PDFT Hamiltonian through a Taylor expansion of the MC-PDFT energy -expression [J Chem Theory Comput: 10.1021/acs.jctc.3c00207]. -''' - -from pyscf import gto, scf, mcpdft - -mol = gto.M( - atom = [ - ['Li', ( 0., 0., 0.)], - ['H', ( 0., 0., 3)] - ], basis = 'sto-3g', - symmetry = 0 - ) - -mf = scf.RHF(mol) -mf.kernel() - -mc = mcpdft.CASSCF(mf, 'tpbe', 2, 2) -mc.fix_spin_(ss=0) # often necessary! -mc_sa = mc.state_average ([.5, .5]).run () -# Note, this operates in the same way as "CMS-PDFT" but constructs and -# diagonalizes a different matrix. -mc_ms = mc.multi_state ([.5, .5], "lin").run (verbose=4) - -print ('{:>21s} {:>12s} {:>12s}'.format ('state 0','state 1', 'gap')) -fmt_str = '{:>9s} {:12.9f} {:12.9f} {:12.9f}' -print (fmt_str.format ('CASSCF', mc_sa.e_mcscf[0], mc_sa.e_mcscf[1], - mc_sa.e_mcscf[1]-mc_sa.e_mcscf[0])) -print (fmt_str.format ('MC-PDFT', mc_sa.e_states[0], mc_sa.e_states[1], - mc_sa.e_states[1]-mc_sa.e_states[0])) -print (fmt_str.format ('L-PDFT', mc_ms.e_states[0], mc_ms.e_states[1], - mc_ms.e_states[1]-mc_ms.e_states[0])) diff --git a/examples/mcpdft/43-multi_state_mix.py b/examples/mcpdft/43-multi_state_mix.py deleted file mode 100644 index 235f1064c..000000000 --- a/examples/mcpdft/43-multi_state_mix.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python - - -''' -Perform multi-state PDFT averaging over states of different spins and/or -spatial symmetry - -The mcpdft.multi_state function maybe not generate the right spin or spatial -symmetry as one needs. This example shows how to put states with different -spins and spatial symmetry in a state-average solver using the method -multi_state_mix. -''' - -import numpy as np -from pyscf import gto, scf, mcpdft, fci - -mol = gto.M(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 - ''', symmetry=True, basis="6-31G", verbose=3) - -mf = scf.RHF(mol).run() - -# -# state-average over 1 triplet + 2 singlets -# Note direct_spin1 solver is called here because the CI solver will take -# spin-mix solution as initial guess which may break the spin symmetry -# required by direct_spin0 solver -# -weights = np.ones(3) / 3 -solver1 = fci.direct_spin1_symm.FCI(mol) -solver1.spin = 2 -solver1 = fci.addons.fix_spin(solver1, shift=.2, ss=2) -solver1.nroots = 1 -solver2 = fci.direct_spin0_symm.FCI(mol) -solver2.spin = 0 -solver2.nroots = 2 - -mc = mcpdft.CASSCF(mf, "tPBE", 4, 4, grids_level=1) -# Currently, only the Linearized PDFT method is available for -# multi_state_mix -mc = mc.multi_state_mix([solver1, solver2], weights, "lin") -mc.run() - - -# -# Example 2: Mix FCI wavefunctions with different symmetry irreps -# -mol = gto.Mole() -mol.build(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 -''', symmetry=True, basis='631g', verbose=3) - -mf = scf.RHF(mol).run() - -# Also possible to construct 2 solvers of different wfnsym, but -# of the same spin symmetry -weights = [.5, .5] -solver1 = fci.direct_spin1_symm.FCI(mol) -solver1.wfnsym= 'A1' -solver1.spin = 0 -solver2 = fci.direct_spin1_symm.FCI(mol) -solver2.wfnsym= 'A2' -solver2.spin = 2 - -mc = mcpdft.CASSCF(mf, "tPBE", 4, 4, grids_level=1) -mc = mc.multi_state_mix([solver1, solver2], weights, "lin") -mc.kernel() diff --git a/pyscf/df/grad/lpdft.py b/pyscf/df/grad/lpdft.py deleted file mode 100644 index b1541dca2..000000000 --- a/pyscf/df/grad/lpdft.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/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. -# -from pyscf import lib -from pyscf.grad import lpdft as lpdft_grad -from pyscf.df.grad import sacasscf as dfsacasscf_grad -from pyscf.df.grad import rhf as dfrhf_grad -from functools import partial - -# I need to resolve the __init__ and get_ham_response members. Otherwise everything should be fine! -class Gradients (dfsacasscf_grad.Gradients, lpdft_grad.Gradients): - - def __init__(self, pdft, state=None): - self.auxbasis_response = True - lpdft_grad.Gradients.__init__(self, pdft, state=state) - - # TODO: rewrite the partialized fn to take the actual caller, use getattr, - # and delete this - def get_ham_response (self, **kwargs): - pfn = partial (lpdft_grad.lpdft_HellmanFeynman_grad, - auxbasis_response=self.auxbasis_response) - with lib.temporary_env (lpdft_grad, lpdft_HellmanFeynman_grad=pfn): - return lpdft_grad.Gradients.get_ham_response (self, **kwargs) - - kernel = lpdft_grad.Gradients.kernel - get_wfn_response = lpdft_grad.Gradients.get_wfn_response - get_init_guess = lpdft_grad.Gradients.get_init_guess - get_otp_gradient_response = lpdft_grad.Gradients.get_otp_gradient_response - get_Aop_Adiag = lpdft_grad.Gradients.get_Aop_Adiag - diff --git a/pyscf/df/grad/mcpdft.py b/pyscf/df/grad/mcpdft.py deleted file mode 100644 index 0a6293696..000000000 --- a/pyscf/df/grad/mcpdft.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/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. -# -from pyscf import lib -from pyscf.grad import mcpdft as mcpdft_grad -from pyscf.df.grad import sacasscf as dfsacasscf_grad -from pyscf.df.grad import rhf as dfrhf_grad -from functools import partial - -# I need to resolve the __init__ and get_ham_response members. Otherwise everything should be fine! -class Gradients (dfsacasscf_grad.Gradients, mcpdft_grad.Gradients): - - def __init__(self, pdft, state=None): - self.auxbasis_response = True - mcpdft_grad.Gradients.__init__(self, pdft, state=state) - - # TODO: rewrite the partialized fn to take the actual caller, use getattr, - # and delete this - def get_ham_response (self, **kwargs): - pfn = partial (mcpdft_grad.mcpdft_HellmanFeynman_grad, - auxbasis_response=self.auxbasis_response) - with lib.temporary_env (mcpdft_grad, mcpdft_HellmanFeynman_grad=pfn): - return mcpdft_grad.Gradients.get_ham_response (self, **kwargs) - - kernel = mcpdft_grad.Gradients.kernel - get_wfn_response = mcpdft_grad.Gradients.get_wfn_response - get_init_guess = mcpdft_grad.Gradients.get_init_guess - project_Aop = mcpdft_grad.Gradients.project_Aop - diff --git a/pyscf/df/grad/mspdft.py b/pyscf/df/grad/mspdft.py deleted file mode 100644 index 54ffcd0b4..000000000 --- a/pyscf/df/grad/mspdft.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/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. -# -from pyscf import lib -from pyscf.grad import sacasscf as sacasscf_grad -from pyscf.grad import mspdft as mspdft_grad -from pyscf.grad import mcpdft as mcpdft_grad -from pyscf.df.grad import casscf as dfcasscf_grad -from pyscf.df.grad import sacasscf as dfsacasscf_grad -from pyscf.df.grad import rhf as dfrhf_grad -from functools import partial - -# I need to resolve the __init__ and get_ham_response members. Otherwise everything should be fine! -class Gradients (mspdft_grad.Gradients): - - def __init__(self, pdft): - self.auxbasis_response = True - mspdft_grad.Gradients.__init__(self, pdft) - - def make_fcasscf (self, state=None, casscf_attr={}, fcisolver_attr={}): - fcasscf = sacasscf_grad.Gradients.make_fcasscf (self, state=state, - casscf_attr=casscf_attr, fcisolver_attr=fcisolver_attr) - def fcasscf_grad (): - return dfcasscf_grad.Gradients (fcasscf) - fcasscf.nuc_grad_method = fcasscf_grad - return fcasscf - - # TODO: rewrite the partialized fn to take the actual caller, use getattr, - # and delete this - def get_ham_response (self, **kwargs): - pfn = partial (mcpdft_grad.mcpdft_HellmanFeynman_grad, - auxbasis_response=self.auxbasis_response) - with lib.temporary_env (mcpdft_grad, mcpdft_HellmanFeynman_grad=pfn): - return mspdft_grad.Gradients.get_ham_response (self, **kwargs) - - def get_LdotJnuc (self, Lvec, **kwargs): - with lib.temporary_env (sacasscf_grad, - Lci_dot_dgci_dx=dfsacasscf_grad.Lci_dot_dgci_dx, - Lorb_dot_dgorb_dx=dfsacasscf_grad.Lorb_dot_dgorb_dx): - return mspdft_grad.Gradients.get_LdotJnuc (self, Lvec, **kwargs) - - diff --git a/pyscf/grad/cmspdft.py b/pyscf/grad/cmspdft.py deleted file mode 100644 index adb00a079..000000000 --- a/pyscf/grad/cmspdft.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/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 scipy import linalg -from pyscf import ao2mo, lib -from pyscf.lib import logger -from pyscf.mcscf import newton_casscf -import copy -from functools import reduce -from pyscf.mcpdft.cmspdft import coulomb_tensor - -# TODO: docstring? -def diab_response (mc_grad, Lis, mo=None, ci=None, eris=None, **kwargs): - '''Computes the Hessian-vector product of - - Q_a-a = 1/2 sum_I g_pqrs - - where the vector is a vector of intermediate-state rotations and the - external derivatives are with respect to orbital rotations and CI - transfers. - - Args: - mc_grad : object of class Gradients (CASSCF or CASCI) - Lis : ndarray of shape (nroots*(nroots-1)/2,) - Contains step vector for intermediate state rotations - - Kwargs: - mo : ndarray of shape (nao,nmo) - Contains MO coefficients - ci : ndarray or list of length (nroots) - Contains intermediate-state CI vectors - eris : object of class ERIS (CASSCF or CASCI) - Contains (true) ERIs in the MO basis - - Returns: - R : ndarray of shape (mc_grad.ngorb+mc_grad.nci) - Contains Hessian-vector product - ''' - - mc = mc_grad.base - if mo is None: mo = mc.mo_coeff - if ci is None: ci = mc.ci - if eris is None: eris = mc.ao2mo (mo) - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nroots, nocc = mc_grad.nroots, ncore + ncas - nmo = mo.shape[1] - - # CI vector shift - L = np.zeros ((nroots, nroots), dtype=Lis.dtype) - L[np.tril_indices (nroots, k=-1)] = Lis[:] - L -= L.T - ci_arr = np.asarray (ci) - Lci = np.tensordot (L, ci_arr, axes=1) - - # Density matrices - tril_idx = np.tril_indices (nroots) - diag_idx = np.arange (nroots) - diag_idx = diag_idx * (diag_idx+1) // 2 + diag_idx - tdm1 = np.stack (mc.fcisolver.states_trans_rdm12 (ci_arr[tril_idx[0]], - ci_arr[tril_idx[1]], ncas, nelecas)[0], axis=0) - dm1 = tdm1[diag_idx,:,:] - edm1 = np.stack (mc.fcisolver.states_trans_rdm12 (Lci, ci, ncas, - nelecas)[0], axis=0) - edm1 += edm1.transpose (0,2,1) - - # Potentials - aapa = np.zeros ([ncas,ncas,nmo,ncas], dtype=dm1.dtype) - for i in range (ncas): - j = i + ncore - aapa[i,:,:,:] = eris.papa[j][:,:,:] - vj = np.tensordot (dm1, aapa, axes=2) - evj = np.tensordot (edm1, aapa, axes=2) - - # Orbital degree of freedom - Rorb = np.zeros ((nmo,nmo), dtype=vj[0].dtype) - Rorb[:,ncore:nocc] = sum ([np.dot (v, ed) + np.dot (ev, d) - for v, d, ev, ed in zip (vj, dm1, evj, edm1)]) - Rorb -= Rorb.T - - # CI degree of freedom - w = coulomb_tensor (mc, mo_coeff=mo, ci=ci, h2eff=aapa[:,:,ncore:nocc,:]) - const_IJ = -4*np.einsum ('jiik,ik->ij', w, L) - const_IJ -= 2*np.einsum ('iijk,ik->ij', w, L) - const_IJ += 2*np.einsum ('jkkk,ik->ij', w, L) - Rci = np.tensordot (const_IJ, ci_arr, axes=1) # Delta_IJ |J> term - - def contract (v,c): - return mc.fcisolver.contract_1e (v, c, ncas, nelecas) - - vj, evj = vj[:,ncore:nocc,:], evj[:,ncore:nocc,:] - vci = np.stack ([contract (v,c) for v, c in zip (vj, ci)], axis=0) - Rci -= 2 * np.tensordot (L, vci, axes=1) # -2 |zW_I> term - for I in range (nroots): - Rci[I] += 2 * contract (vj[I], Lci[I]) # 2 W^I_I |z_I> term - Rci[I] += 2 * contract (evj[I], ci[I]) # 4 W^zI_I |I> z_IJ term - # (*2 in def. of evj) - - return mc_grad.pack_uniq_var (2*Rorb, Rci) - -# TODO: get rid?? Fix?? Unittest??? -# BROKEN FOR CI AND IS; DO NOT USE -def diab_response_o0 (mc_grad, Lis, mo=None, ci=None, eris=None, **kwargs): - '''Alternate implementation: monkeypatch everything but - active-active Coulomb part of the Hamiltonian and call - newton_casscf.gen_g_hop ()[2]. - ''' - - mc = mc_grad.base - if mo is None: mo = mc.mo_coeff - if ci is None: ci = mc.ci - if eris is None: eris = mc.ao2mo (mo) - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nroots, nocc, nmo = mc_grad.nroots, ncore + ncas, mo.shape[1] - moH = mo.conj ().T - - # CI vector shift - L = np.zeros ((nroots, nroots), dtype=Lis.dtype) - L[np.tril_indices (nroots, k=-1)] = Lis[:] - L -= L.T - ci_arr = np.asarray (ci) - Lci = list (np.tensordot (L, ci_arr, axes=1)) - x = mc_grad.pack_uniq_var (np.zeros ((nmo,nmo)), Lci) - - # Fake Hamiltonian! - h1e_mo = moH @ mc.get_hcore () @ mo - feris = mc.ao2mo (mo) - for i in range (nmo): - feris.papa[i][:,:,:] = 0.0 - feris.ppaa[i][:ncore,:,:] = 0.0 - feris.ppaa[i][nocc:,:,:] = 0.0 - feris.vhf_c[:,:] = -h1e_mo.copy () - from pyscf.mcscf.newton_casscf import _pack_ci_get_H as getH - from pyscf.mcscf import addons - def _pack_ci_get_H (mc1, mo1, ci1): - ci1, _, _Hdiag, linkstrl, linkstr, _pack_ci, _unpack_ci = getH (mc1, - mo1, ci1) - dm1 = mc.fcisolver.states_make_rdm1 (ci1, ncas, nelecas) - _state_arg = addons.StateAverageMixFCISolver_state_args - def _Hci_kernel (s, op1, op2, ci1, ci2, ne, my_L): - h1ci2, h1ci1, h2ci1, c1c2 = [], [], [], [] - for o1, o2, c2, c1 in zip (op1, op2, ci2, ci1): - h1ci2.append (s.contract_1e (o1, c2, ncas, ne)) - h2ci1.append (s.contract_1e (o2, c1, ncas, ne)) - h1ci1.append (s.contract_1e (o1, c1, ncas, ne)) - c1c2.append (c1.dot (c2)) - if np.all (np.asarray (c1c2) < 0.5): # chain rule - h1ci2, h2ci1 = np.asarray (h1ci2), np.asarray (h2ci1) - h1ci1 = np.tensordot (my_L, np.asarray (h1ci1), axes=1) - return list (h1ci2 + h2ci1 - h1ci1) - else: - return h1ci2 - - if isinstance (mc.fcisolver, addons.StateAverageMixFCISolver): - full_idx = np.arange (nroots) - def _Hci (h1, h2, ci2): - hci = [] - tm1 = mc.fcisolver.states_trans_rdm12 (ci2, ci1, ncas, - nelecas)[9] - for s, args, kwargs in enumerate (mc.fcisolver._loop_solver ( - _state_arg (ci2), _state_arg (ci1), _state_arg (dm1), - _state_arg (tm1), _state_arg (full_idx))): - ci2i, ci1i, dm1i, tm1i, idx = args[0:5] - Lsec = L[np.ix_(idx,idx)] - nelec = mc.fcisolver._get_nelec (s, nelecas) - op1 = h1[None,:,:] + np.tensordot (dm1i, h2, axes=2) - op2 = np.tensordot (tm1i + tm1i.transpose (0,2,1), h2, - axes=2) - hci.extend (_Hci_kernel (s, op1, op2, ci1i, ci2i, nelec, - Lsec)) - return hci - else: - def _Hci (h1, h2, ci2): - tm1 = np.asarray (mc.fcisolver.states_trans_rdm12 (ci2, ci1, - ncas, nelecas)[0]) - op1 = h1[None,:,:] + np.tensordot (dm1, h2, axes=2) - op2 = np.tensordot (tm1 + tm1.transpose (0,2,1), h2, axes=2) - return _Hci_kernel (mc.fcisolver, op1, op2, ci1, ci2, nelecas, - L) - return ci1, _Hci, _Hdiag, linkstrl, linkstr, _pack_ci, _unpack_ci - - # Fake 2TDM! - dm1 = mc.fcisolver.states_make_rdm1 (ci, ncas, nelecas) - def trans_rdm12 (ci1, ci0, *args, **kwargs): - tm1, tm2 = mc.fcisolver.states_trans_rdm12 (ci1, ci0, *args, **kwargs) - for t1, t2, d1, w in zip (tm1, tm2, dm1, mc.weights): - t2[:,:,:,:] = w * (np.multiply.outer (t1, d1) - + np.multiply.outer (d1, t1)) - t1[:,:] *= w - return sum (tm1), sum (tm2) - - # Fake Newton CASSCF! - with lib.temporary_env (newton_casscf, _pack_ci_get_H=_pack_ci_get_H): - with lib.temporary_env (mc.fcisolver, trans_rdm12=trans_rdm12): - hx = newton_casscf.gen_g_hop (mc, mo, ci, feris, verbose=0)[2](x) - hx *= nroots - hx_orb, hx_ci = mc_grad.unpack_uniq_var (hx) - hx_ci = np.asarray (hx_ci) - hx_is = lib.einsum ('pab,qab->pq', hx_ci, ci_arr.conj ()) - hx_ci -= np.tensordot(hx_is, ci_arr, axes=1) - - return mc_grad.pack_uniq_var (hx_orb, hx_ci) - -def diab_grad (mc_grad, Lis, atmlst=None, mo=None, ci=None, eris=None, - mf_grad=None, **kwargs): - '''Computes the partial first derivatives of - - Q_a-a = 1/2 sum_I g_pqrs - - with respect to geometry perturbation. - - Args: - mc_grad : object of class Gradients (CASSCF or CASCI) - Lis : ndarray of shape (nroots*(nroots-1)/2,) - Contains step vector for intermediate state rotations - - Kwargs: - atmlst : list - List of atoms whose geometries are perturbed. Defaults - to all atoms in mc_grad.mol. - mo : ndarray of shape (nao,nmo) - Contains MO coefficients - ci : ndarray or list of length (nroots) - Contains intermediate-state CI vectors - eris : object of class ERIS (CASSCF or CASCI) - Contains (true) ERIs in the MO basis - mf_grad: object of class Gradients (RHF) - Defaults to mc_grad.base.get_rhf_base ().nuc_grad_method () - - Returns: - de : ndarray of shape (len (atmlst), 3) - Contains gradient vector - ''' - - mc = mc_grad.base - mol = mc_grad.mol - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nroots, nocc, nmo = mc_grad.nroots, ncore + ncas, mo.shape[1] - moH = mo.conj ().T - mo_cas = mo[:,ncore:nocc] - moH_cas = moH[ncore:nocc,:] - if mf_grad is None: mf_grad = mc.get_rhf_base ().nuc_grad_method() - if atmlst is None: atmlst = list (range(mol.natm)) - - # CI vector shift - L = np.zeros ((nroots, nroots), dtype=Lis.dtype) - L[np.tril_indices (nroots, k=-1)] = Lis[:] - L -= L.T - ci_arr = np.asarray (ci) - Lci = np.tensordot (L, ci_arr, axes=1) - - # Density matrices - dm1 = np.stack (mc.fcisolver.states_make_rdm1 (ci, ncas, nelecas), axis=0) - edm1 = np.stack (mc.fcisolver.states_trans_rdm12 (Lci, ci, ncas, - nelecas)[0], axis=0) - edm1 += edm1.transpose (0,2,1) - dm1_ao = reduce (np.dot, (mo_cas, dm1, moH_cas)).transpose (1,0,2) - edm1_ao = reduce (np.dot, (mo_cas, edm1, moH_cas)).transpose (1,0,2) - - # Potentials and operators - aapa = np.zeros ([nmo,]+[ncas,]*3, dtype=dm1.dtype) - for i in range (nmo): aapa[i] = eris.ppaa[i][ncore:nocc,:,:] - aapa = aapa.transpose (2,3,0,1) - vj = np.tensordot (dm1, aapa, axes=2) - evj = np.tensordot (edm1, aapa, axes=2) - dvj_all = mf_grad.get_j (mc.mol, list(dm1_ao) + list(edm1_ao)) - dvj_aux = getattr (dvj_all, 'aux', np.zeros ((nroots, nroots, mol.natm, - 3))) - dvj = np.stack (dvj_all[:nroots], axis=1) - devj = np.stack (dvj_all[nroots:], axis=1) - - # Generalized Fock and overlap operator - gfock = np.zeros ([nmo,nmo], dtype=vj.dtype) - gfock[:,ncore:nocc] = sum ([np.dot (v, ed) + np.dot (ev, d) - for v, d, ev, ed in zip (vj, dm1, evj, edm1)]) - dme0 = reduce (np.dot, (mo, (gfock+gfock.T)*.5, moH)) - s1 = mf_grad.get_ovlp (mc.mol) - - # Crunch - de_direct = np.zeros ((len (atmlst), 3)) - de_renorm = np.zeros ((len (atmlst), 3)) - aoslices = mol.aoslice_by_atom() - for k, ia in enumerate(atmlst): - shl0, shl1, p0, p1 = aoslices[ia] - de_renorm[k] -= lib.einsum('xpq,pq->x', s1[:,p0:p1], dme0[p0:p1]) * 2 - de_direct[k] += lib.einsum('xipq,ipq->x', dvj[:,:,p0:p1], - edm1_ao[:,p0:p1]) * 2 - de_direct[k] += lib.einsum('xipq,ipq->x', devj[:,:,p0:p1], - dm1_ao[:,p0:p1]) * 2 - dvj_aux = dvj_aux[:,:,atmlst,:] - de_aux = (np.trace (dvj_aux, offset=nroots, axis1=0, axis2=1) - + np.trace (dvj_aux, offset=-nroots, axis1=0, axis2=1)) - - logger.debug (mc, "CMS-PDFT Lis lagrange direct component:\n{}".format ( - de_direct)) - logger.debug (mc, "CMS-PDFT Lis lagrange renorm component:\n{}".format ( - de_renorm)) - logger.debug (mc, "CMS-PDFT Lis lagrange auxbasis component:\n{}".format ( - de_aux)) - de = de_direct + de_aux + de_renorm - return de - -# TODO: get rid? Unittest? -def diab_grad_o0 (mc_grad, Lis, atmlst=None, mo=None, ci=None, eris=None, - mf_grad=None, **kwargs): - ''' Monkeypatch version of diab_grad ''' - mc = mc_grad.base - ncas, nelecas, nroots = mc.ncas, mc.nelecas, mc_grad.nroots - if mf_grad is None: mf_grad = mc.get_rhf_base ().nuc_grad_method() - - # CI vector shift - L = np.zeros ((nroots, nroots), dtype=Lis.dtype) - L[np.tril_indices (nroots, k=-1)] = Lis[:] - L -= L.T - ci_arr = np.asarray (ci) - Lci = list (np.tensordot (L, ci_arr, axes=1)) - - # Fake dms! - dm1 = mc.fcisolver.states_make_rdm1 (ci, ncas, nelecas) - def trans_rdm12 (ci1, ci0, *args, **kwargs): - tm1, tm2 = mc.fcisolver.states_trans_rdm12 (ci1, ci0, *args, **kwargs) - for t1, t2, d1, w in zip (tm1, tm2, dm1, mc.weights): - t2[:,:,:,:] = (np.multiply.outer (t1, d1) - + np.multiply.outer (d1, t1)) - t1[:,:] = 0.0 - return sum (tm1), sum (tm2) - - from pyscf.grad.sacasscf import Lci_dot_dgci_dx - with lib.temporary_env (mc.fcisolver, trans_rdm12=trans_rdm12): - de = Lci_dot_dgci_dx (Lci, mc.weights, mc, mo_coeff=mo, ci=ci, - atmlst=atmlst, eris=eris, mf_grad=mf_grad) - return de - -if __name__ == '__main__': - import math - from pyscf import scf, gto, mcscf - from pyscf.fci import csf_solver - from pyscf.mcpdft.pdft_feff import vector_error - xyz = '''O 0.00000000 0.08111156 0.00000000 - H 0.78620605 0.66349738 0.00000000 - H -0.78620605 0.66349738 0.00000000''' - mol = gto.M (atom=xyz, basis='6-31g', symmetry=False, output='cmspdft.log', - verbose=lib.logger.DEBUG) - mf = scf.RHF (mol).run () - mc = mcscf.CASSCF (mf, 4, 4).set (fcisolver = csf_solver (mol, 1)) - mc = mc.state_average ([1.0/3,]*3).run () - ci_arr = np.asarray (mc.ci) - - mc_grad = mc.nuc_grad_method () - Lis = math.pi * (np.random.rand (3) - 0.5) - eris = mc.ao2mo (mc.mo_coeff) - - dw_test = diab_response (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, - eris=eris) - dworb_test, dwci_test = mc_grad.unpack_uniq_var (dw_test) - dwci_test = np.asarray (dwci_test) - dwis_test = lib.einsum ('pab,qab->pq', dwci_test, ci_arr.conj ()) - dwci_test -= lib.einsum ('pq,qab->pab', dwis_test, ci_arr) - dw_ref = diab_response_o0 (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, - eris=eris) - dworb_ref, dwci_ref = mc_grad.unpack_uniq_var (dw_ref) - dwci_ref = np.asarray (dwci_ref) - dwis_ref = lib.einsum ('pab,qab->pq', dwci_ref, ci_arr.conj ()) - dwci_ref -= lib.einsum ('pq,qab->pab', dwis_ref, ci_arr) - dh_test = diab_grad (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, eris=eris) - dh_ref = diab_grad_o0 (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, eris=eris) - - print ("dworb:", vector_error (dworb_test, dworb_ref), linalg.norm ( - dworb_ref)) - print ("dwci:", vector_error (dwci_test, dwci_ref), linalg.norm (dwci_ref)) - print ("dwis:", vector_error (dwis_test, dwis_ref), linalg.norm (dwis_ref)) - print ("dh:", vector_error (dh_test, dh_ref), linalg.norm (dh_ref)) - - print (dh_test, '\n', dh_ref) diff --git a/pyscf/grad/lpdft.py b/pyscf/grad/lpdft.py deleted file mode 100644 index a9c5eb410..000000000 --- a/pyscf/grad/lpdft.py +++ /dev/null @@ -1,700 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -from pyscf.grad import rks as rks_grad -from pyscf.dft import gen_grid -from pyscf.lib import logger, tag_array, pack_tril, current_memory -from pyscf.mcscf import casci, mc1step, newton_casscf -from pyscf.grad import sacasscf -from pyscf.mcscf.casci import cas_natorb - -from pyscf.mcpdft.otpd import get_ontop_pair_density, _grid_ao2mo -from pyscf.mcpdft.tfnal_derivs import contract_fot, unpack_vot, contract_vot -from pyscf.mcpdft import _dms -from pyscf.mcpdft import mspdft -import pyscf.grad.mcpdft as mcpdft_grad - -import numpy as np -import gc - -BLKSIZE = gen_grid.BLKSIZE - - -def get_ontop_response( - mc, ot, state, atmlst, casdm1, casdm1_0, mo_coeff=None, ci=None, max_memory=None -): - if mo_coeff is None: - mo_coeff = mc.mo_coeff - if ci is None: - ci = mc.ci - if max_memory is None: - max_memory = mc.max_memory - - t0 = (logger.process_clock(), logger.perf_counter()) - - mol = mc.mol - ncore = mc.ncore - ncas = mc.ncas - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - - dvxc = np.zeros((3, nao)) - de_grid = np.zeros((len(atmlst), 3)) - de_wgt = np.zeros((len(atmlst), 3)) - - mo_coeff_0, ci_0, mo_occup_0 = cas_natorb( - mc, mo_coeff=mo_coeff, ci=ci, casdm1=casdm1_0 - ) - mo_coeff, ci, mo_occup = cas_natorb(mc, mo_coeff=mo_coeff, ci=ci, casdm1=casdm1) - - mo_occ = mo_coeff[:, :nocc] - mo_cas = mo_coeff[:, ncore:nocc] - - mo_occ_0 = mo_coeff_0[:, :nocc] - mo_cas_0 = mo_coeff_0[:, ncore:nocc] - - # Need to regenerate these with the updated ci values.... - casdm1s = mc.make_one_casdm1s(ci=ci, state=state) - casdm1 = casdm1s[0] + casdm1s[1] - casdm2 = mc.make_one_casdm2(ci=ci, state=state) - dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s, mo_coeff=mo_coeff, ncore=ncore, ncas=ncas) - dm1 = dm1s[0] + dm1s[1] - dm1 = tag_array(dm1, mo_coeff=mo_coeff[:, :nocc], mo_occ=mo_occup[:nocc]) - - casdm1s_0, casdm2_0 = mc.get_casdm12_0(ci=ci_0) - casdm1_0 = casdm1s_0[0] + casdm1s_0[1] - dm1s_0 = _dms.casdm1s_to_dm1s( - mc, casdm1s_0, mo_coeff=mo_coeff_0, ncore=ncore, ncas=ncas - ) - dm1_0 = dm1s_0[0] + dm1s_0[1] - dm1_0 = tag_array(dm1_0, mo_coeff=mo_coeff_0[:, :nocc], mo_occ=mo_occup_0[:nocc]) - - cascm2 = _dms.dm2_cumulant(casdm2, casdm1) - cascm2_0 = _dms.dm2_cumulant(casdm2_0, casdm1_0) - - make_rho = ot._numint._gen_rho_evaluator(mol, dm1, 1)[0] - make_rho_0 = ot._numint._gen_rho_evaluator(mol, dm1_0, 1)[0] - - idx = np.array([[1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]], dtype=np.int_) - # For addressing particular ao derivatives - if ot.xctype == "LDA": - idx = idx[:, 0:1] # For LDAs, no second derivatives - - casdm2_pack = mcpdft_grad.pack_casdm2(cascm2, ncas) - casdm2_0_pack = mcpdft_grad.pack_casdm2(cascm2_0, ncas) - - full_atmlst = -np.ones(mol.natm, dtype=np.int_) - for k, ia in enumerate(atmlst): - full_atmlst[ia] = k - - t1 = logger.timer(mc, "L-PDFT HlFn quadrature setup", *t0) - - ndao = (1, 4)[ot.dens_deriv] - ndpi = (1, 4)[ot.Pi_deriv] - ncols = 1.05 * 3 * (ndao * nao + nocc) + max(ndao * nao, ndpi * ncas * ncas) - # I have no idea if this is actually the correct number of columns, but I have a feeling it is not since I should - # be accounting for the extra rows from feff stuff... - - for ia, (coords, w0, w1) in enumerate(rks_grad.grids_response_cc(ot.grids)): - gc.collect() - ngrids = coords.shape[0] - remaining_floats = (max_memory - current_memory()[0]) * 1e6 / 8 - blksize = int(remaining_floats / (ncols * BLKSIZE)) * BLKSIZE - blksize = max(BLKSIZE, min(blksize, ngrids, BLKSIZE * 1200)) - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} mask and memory setup".format(ia), *t1 - ) - for ip0 in range(0, ngrids, blksize): - ip1 = min(ngrids, ip0 + blksize) - mask = gen_grid.make_mask(mol, coords[ip0:ip1]) - logger.info( - mc, - "L-PDFT gradient atom {} slice {}-{} of {} total".format( - ia, ip0, ip1, ngrids - ), - ) - ao = ot._numint.eval_ao( - mol, coords[ip0:ip1], deriv=ot.dens_deriv + 1, non0tab=mask - ) - - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} ao grids".format(ia), *t1 - ) - - if ot.xctype == "LDA": - aoval = ao[0] - - if ot.xctype == "GGA": - aoval = ao[:4] - - rho = make_rho(0, aoval, mask, ot.xctype) / 2.0 - rho = np.stack((rho,) * 2, axis=0) - rho_0 = make_rho_0(0, aoval, mask, ot.xctype) / 2.0 - rho_0 = np.stack((rho_0,) * 2, axis=0) - delta_rho = rho - rho_0 - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} rho calc".format(ia), *t1 - ) - - Pi = get_ontop_pair_density( - ot, rho, aoval, cascm2, mo_cas, ot.dens_deriv, mask - ) - Pi_0 = get_ontop_pair_density( - ot, rho_0, aoval, cascm2_0, mo_cas_0, ot.dens_deriv, mask - ) - delta_Pi = Pi - Pi_0 - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} Pi calc".format(ia), *t1 - ) - - if ot.xctype == "LDA": - aoval = ao[:1] - - moval_occ = _grid_ao2mo(mol, aoval, mo_occ, mask) - moval_occ_0 = _grid_ao2mo(mol, aoval, mo_occ_0, mask) - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} ao2mo grids".format(ia), *t1 - ) - - aoval = np.ascontiguousarray( - [ao[ix].transpose(0, 2, 1) for ix in idx[:, :ndao]] - ).transpose(0, 1, 3, 2) - ao = None - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} ao grid reshape".format(ia), *t1 - ) - - eot, vot, fot = ot.eval_ot( - rho_0, Pi_0, weights=w0[ip0:ip1], dderiv=2, _unpack_vot=False - ) - fot = contract_fot( - ot, fot, rho_0, Pi_0, delta_rho, delta_Pi, unpack=True, vot_packed=vot - ) - vot = unpack_vot(vot, rho_0, Pi_0) - # See the equations... - eot += contract_vot(vot, delta_rho, delta_Pi) - t1 = logger.timer( - mc, "PDFT HlFn quadrature atom {} eval_ot".format(ia), *t1 - ) - - puvx_mem = 2 * ndpi * (ip1 - ip0) * ncas * ncas * 8 / 1e6 - remaining_mem = max_memory - current_memory()[0] - logger.info( - mc, - ( - "L-PDFT gradient memory note: working on {} grid points: estimated puvx usage = {:.1f} of {:.1f} " - "remaining MB" - ).format((ip1 - ip0), puvx_mem, remaining_mem), - ) - - # Weight response - de_wgt += np.tensordot(eot, w1[atmlst, ..., ip0:ip1], axes=(0, 2)) - t1 = logger.timer( - mc, "L-PDFT HlFn quadrature atom {} weight response".format(ia), *t1 - ) - - # The mo_occup values might be screwing me here... - # rho_delta * fot * drho_SA/dx - tmp_df = mcpdft_grad.xc_response( - ot, - fot, - rho_0, - Pi_0, - w0[ip0:ip1], - moval_occ_0, - aoval, - mo_occ_0, - mo_occup_0, - ncore, - nocc, - casdm2_0_pack, - ndpi, - mo_cas_0, - ) - # vot * drho_Gamma/dx - tmp_dv = mcpdft_grad.xc_response( - ot, - vot, - rho, - Pi, - w0[ip0:ip1], - moval_occ, - aoval, - mo_occ, - mo_occup, - ncore, - nocc, - casdm2_pack, - ndpi, - mo_cas, - ) - - tmp_dxc = tmp_df + tmp_dv - - # Find the atoms that are part of the atomlist - # grid correction shouldn't be added if they arent there - k = full_atmlst[ia] - if k >= 0: - de_grid[k] += 2 * tmp_dxc.sum(1) # Grid response - - dvxc -= tmp_dxc # XC response - - tmp_dxc = tmp_df = tmp_dv = None - t1 = logger.timer(mc, "L-PDFT HlFn quadrature atom {}".format(ia), *t1) - - rho_0 = Pi_0 = rho = Pi = delta_rho = delta_Pi = None - eot = vot = fot = aoval = moval_occ = moval_occ_0 = None - gc.collect() - - return dvxc, de_wgt, de_grid - - -def lpdft_HellmanFeynman_grad( - mc, - ot, - state, - feff1, - feff2, - mo_coeff=None, - ci=None, - atmlst=None, - mf_grad=None, - verbose=None, - max_memory=None, - auxbasis_response=False, -): - if mo_coeff is None: - mo_coeff = mc.mo_coeff - if ci is None: - ci = mc.ci - if mf_grad is None: - mf_grad = mc.get_rhf_base ().nuc_grad_method() - if mc.frozen is not None: - raise NotImplementedError - mol = mc.mol - if atmlst is None: - atmlst = range(mol.natm) - - t0 = (logger.process_clock(), logger.perf_counter()) - - # Specific state density - casdm1s = mc.make_one_casdm1s(ci=ci, state=state) - casdm1 = casdm1s[0] + casdm1s[1] - dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s=casdm1s, mo_coeff=mo_coeff) - dm1 = dm1s[0] + dm1s[1] - casdm2 = mc.make_one_casdm2(ci=ci, state=state) - - # The model-space density (or state-average density) - casdm1s_0, casdm2_0 = mc.get_casdm12_0() - dm1s_0 = _dms.casdm1s_to_dm1s(mc, casdm1s=casdm1s_0, mo_coeff=mo_coeff) - dm1_0 = dm1s_0[0] + dm1s_0[1] - casdm1_0 = casdm1s_0[0] + casdm1s_0[1] - - # Generate the Generalized Fock Component - gfock_expl = mcpdft_grad.gfock_sym( - mc, mo_coeff, casdm1, casdm2, mc.get_lpdft_hcore(), mc.veff2 - ) - gfock_impl = mcpdft_grad.gfock_sym(mc, mo_coeff, casdm1_0, casdm2_0, feff1, feff2) - gfock = gfock_expl + gfock_impl - - dme0 = mo_coeff @ (0.5 * (gfock + gfock.T)) @ mo_coeff.T - del gfock, gfock_impl, gfock_expl - t0 = logger.timer(mc, "L-PDFT HlFn gfock", *t0) - - # Coulomb potential derivatives generated from zero-order density - dvj_all = mf_grad.get_j(dm=[dm1, dm1_0]) - dvj = dvj_all[0] - dvj_0 = dvj_all[1] - - dvxc, de_wgt, de_grid = get_ontop_response( - mc, - ot, - state, - atmlst, - casdm1, - casdm1_0, - mo_coeff=mo_coeff.copy(), - ci=ci.copy(), - max_memory=max_memory, - ) - - delta_dm1 = dm1 - dm1_0 - - def coul_term(p0, p1): - return 2 * ( - np.tensordot(dvj_0[:, p0:p1], delta_dm1[p0:p1]) - + np.tensordot(dvj[:, p0:p1], dm1_0[p0:p1]) - ) - - de_hcore, de_coul, de_xc, de_nuc, de_renorm = mcpdft_grad.sum_terms( - mf_grad, mol, atmlst, dm1, dme0, coul_term, dvxc - ) - - logger.debug(mc, "L-PDFT Hellmann-Feynman nuclear:\n{}".format(de_nuc)) - logger.debug(mc, "L-PDFT Hellmann-Feynman hcore component:\n{}".format(de_hcore)) - logger.debug(mc, "L-PDFT Hellmann-Feynman coulomb component:\n{}".format(de_coul)) - logger.debug(mc, "L-PDFT Hellmann-Feynman xc component:\n{}".format(de_xc)) - logger.debug( - mc, "L-PDFT Hellmann-Feynman quadrature point component:\n{}".format(de_grid) - ) - logger.debug( - mc, "L-PDFT Hellmann-Feynman quadrature weight component:\n{}".format(de_wgt) - ) - logger.debug(mc, "L-PDFT Hellmann-Feynman renorm component:\n{}".format(de_renorm)) - - de = de_nuc + de_hcore + de_coul + de_renorm + de_xc + de_grid + de_wgt - - if auxbasis_response: - dvj_aux = dvj_all.aux[:,:,atmlst,:] - de_aux = dvj_aux[1, 0] + dvj_aux[0, 1] - dvj_aux[1, 1] - logger.debug(mc, "L-PDFT Hellmann-Feynman aux component:\n{}".format(de_aux)) - de += de_aux - - logger.timer(mc, "L-PDFT HlFn total", *t0) - - return de - - -class Gradients(sacasscf.Gradients): - def __init(self, pdft, state=None): - super().__init__(pdft, state=state) - - if self.state is None and self.nroots == 1: - self.state = 0 - - self.e_mcscf = self.base.e_mcscf - self._not_implemented_check() - - def _not_implemented_check(self): - name = self.__class__.__name__ - if isinstance(self.base, casci.CASCI) and not isinstance( - self.base, mc1step.CASSCF - ): - raise NotImplementedError("{} for CASCI-based MC-PDFT".format(name)) - ot, otxc, nelecas = self.base.otfnal, self.base.otxc, self.base.nelecas - spin = abs(nelecas[0] - nelecas[1]) - omega, alpha, hyb = ot._numint.rsh_and_hybrid_coeff(otxc, spin=spin) - hyb_x, hyb_c = hyb - if hyb_x or hyb_c: - raise NotImplementedError("{} for hybrid MC-PDFT functionals".format(name)) - if omega: - raise NotImplementedError( - "{} for range-separated MC-PDFT functionals".format(name) - ) - - def kernel(self, **kwargs): - state = kwargs["state"] if "state" in kwargs else self.state - if state is None: - raise NotImplementedError("Gradient of LPDFT state-average energy") - self.state = state - mo = kwargs["mo"] if "mo" in kwargs else self.base.mo_coeff - ci = kwargs["ci"] if "ci" in kwargs else self.base.ci - if isinstance(ci, np.ndarray): - ci = [ci] # hack hack hack????? idk - kwargs["ci"] = ci - # need to compute feff1, feff2 if not already in kwargs - if ("feff1" not in kwargs) or ("feff2" not in kwargs): - kwargs["feff1"], kwargs["feff2"] = self.get_otp_gradient_response( - mo, ci, state - ) - - return super().kernel(**kwargs) - - def get_wfn_response( - self, - state=None, - verbose=None, - mo=None, - ci=None, - feff1=None, - feff2=None, - **kwargs - ): - """Returns the derivative of the L-PDFT energy for the given state with respect to MO parameters and CI - parameters. Care is take to account for the implicit and explicit response terms, and make sure the CI - vectors are properly projected out. - - Args: - state : int - Which state energy to get response of. - - mo : ndarray of shape (nao, nmo) - A full set of molecular orbital coefficients. Taken from self if not provided. - - ci : list of ndarrays of length nroots - CI vectors should be from a converged L-PDFT calculation. - - feff1 : ndarray of shape (nao, nao) 1-particle On-top gradient response which as been contracted with the - Delta density generated from state. Should include the Coulomb term as well. - - feff2 : pyscf.mcscf.mc_ao2mo._ERIS instance Relevant 2-body on-top gradient response terms in the MO - basis. Also, partially contracted with the Delta density. - - Returns: g_all : ndarray of shape nlag First sector [:self.ngorb] contains the derivatives with respect to MO - parameters. Second sector [self.ngorb:] contains the derivatives with respect to CI parameters. - """ - if state is None: - state = self.state - - if verbose is None: - verbose = self.verbose - - if mo is None: - mo = self.base.mo_coeff - - if ci is None: - ci = self.base.ci - - if verbose is None: - verbose = self.verbose - - if (feff1 is None) or (feff2 is None): - feff1, feff2 = self.get_otp_gradient_response(mo, ci, state) - - log = logger.new_logger(self, verbose) - - ndet = self.na_states[state] * self.nb_states[state] - fcasscf = self.make_fcasscf(state) - - # Exploit (hopefully) the fact that the zero-order density is - # really just the State Average Density! - fcasscf_sa = self.make_fcasscf_sa() - - fcasscf.mo_coeff = mo - fcasscf.ci = ci[state] - - fcasscf.get_hcore = self.base.get_lpdft_hcore - fcasscf_sa.get_hcore = lambda: feff1 - - g_all_explicit = newton_casscf.gen_g_hop( - fcasscf, mo, ci[state], self.base.veff2, verbose - )[0] - g_all_implicit = newton_casscf.gen_g_hop(fcasscf_sa, mo, ci, feff2, verbose)[0] - - # Debug - log.debug("g_all explicit orb:\n{}".format(g_all_explicit[: self.ngorb])) - log.debug("g_all explicit ci:\n{}".format(g_all_explicit[self.ngorb :])) - log.debug("g_all implicit orb:\n{}".format(g_all_implicit[: self.ngorb])) - log.debug("g_all implicit ci:\n{}".format(g_all_implicit[self.ngorb :])) - - # Need to remove the SA-SA rotations from g_all_implicit CI contributions - spin_states = np.asarray(self.spin_states) - gmo_implicit, gci_implicit = self.unpack_uniq_var(g_all_implicit) - for root in range(self.nroots): - idx_spin = spin_states == spin_states[root] - idx = np.where(idx_spin)[0] - - gci_root = gci_implicit[root].ravel() - - assert root in idx - ci_proj = np.asarray([ci[i].ravel() for i in idx]) - gci_sa = np.dot(ci_proj, gci_root) - gci_root -= np.dot(gci_sa, ci_proj) - - gci_implicit[root] = gci_root - - g_all = self.pack_uniq_var(gmo_implicit, gci_implicit) - - g_all[: self.ngorb] += g_all_explicit[: self.ngorb] - offs = ( - sum( - [ - na * nb - for na, nb in zip(self.na_states[:state], self.nb_states[:state]) - ] - ) - if root > 0 - else 0 - ) - g_all[self.ngorb :][offs:][:ndet] += g_all_explicit[self.ngorb :] - - gorb, gci = self.unpack_uniq_var(g_all) - log.debug("g_all orb:\n{}".format(gorb)) - log.debug("g_all ci:\n{}".format([c.ravel() for c in gci])) - - return g_all - - def get_ham_response( - self, - state=None, - atmlst=None, - verbose=None, - mo=None, - ci=None, - mf_grad=None, - feff1=None, - feff2=None, - **kwargs - ): - if state is None: - state = self.state - - if atmlst is None: - atmlst = self.atmlst - - if verbose is None: - verbose = self.verbose - - if mo is None: - mo = self.base.mo_coeff - - if ci is None: - ci = self.base.ci - - if (feff1 is None) or (feff2 is None): - assert False, kwargs - - return lpdft_HellmanFeynman_grad( - self.base, - self.base.otfnal, - state, - feff1=feff1, - feff2=feff2, - mo_coeff=mo, - ci=ci, - atmlst=atmlst, - mf_grad=mf_grad, - verbose=verbose, - ) - - def get_otp_gradient_response(self, mo=None, ci=None, state=0): - """Generate the 1- and 2-body on-top gradient response terms which have been partially contracted with the - Delta density generated from state. - - Args: - mo : ndarray of shape (nao,nmo) - A full set of molecular orbital coefficients. Taken from self if not provided. - - ci : list of ndarrays of length nroots - CI vectors should be from a converged L-PDFT calculation - - state : int - State to generate the Delta density with - - Returns: - feff1 : ndarray of shape (nao, nao) 1-particle On-top gradient response which as been contracted with the - Delta density generated from state. Should include the Coulomb term as well. - - feff2 : pyscf.mcscf.mc_ao2mo._ERIS instance Relevant 2-body on-top gradient response terms in the MO - basis. Also, partially contracted with the Delta density. - """ - if mo is None: - mo = self.base.mo_coeff - - if ci is None: - ci = self.base.ci - - if state is None: - state = self.state - - # This is the zero-order density - casdm1s_0, casdm2_0 = self.base.get_casdm12_0() - - # This is the density of the state we are differentiating with respect to - casdm1s = self.base.make_one_casdm1s(ci=ci, state=state) - casdm2 = self.base.make_one_casdm2(ci=ci, state=state) - dm1s = _dms.casdm1s_to_dm1s(self.base, casdm1s, mo_coeff=mo) - - cascm2 = _dms.dm2_cumulant(casdm2, casdm1s) - - return self.base.get_pdft_feff( - mo=mo, - ci=ci, - casdm1s=casdm1s_0, - casdm2=casdm2_0, - c_dm1s=dm1s, - c_cascm2=cascm2, - jk_pc=True, - paaa_only=True, - incl_coul=True, - delta=True, - ) - - def get_Aop_Adiag( - self, verbose=None, mo=None, ci=None, eris=None, state=None, **kwargs - ): - """This function accounts for the fact that the CI vectors are no longer eigenstates of the CAS Hamiltonian. - It adds back in the necessary values to the Hessian.""" - if verbose is None: - verbose = self.verbose - - if mo is None: - mo = self.base.mo_coeff - - if ci is None: - ci = self.base.ci - - if state is None: - state = self.state - - if eris is None and self.eris is None: - eris = self.eris = self.base.ao2mo(mo) - - elif eris is None: - eris = self.eris - - ham_od = mspdft.make_heff_mcscf(self.base, mo_coeff=mo, ci=ci) - fcasscf = self.make_fcasscf_sa() - - hop, Adiag = newton_casscf.gen_g_hop(fcasscf, mo, ci, eris, verbose)[2:] - - # TODO: (MR Hennefarth) This is not safe way to check if something is a - # mixed solver, can probably do better than this... - if hasattr(self.base, "_irrep_slices"): - for ham_slice in ham_od: - ham_slice[np.diag_indices_from(ham_slice)] = 0.0 - ham_slice += ( - ham_slice.T - ) # This corresponds to the arbitrary newton_casscf*2 - - def Aop(x): - Ax = hop(x) - x_c = self.unpack_uniq_var(x)[1] - Ax_o, Ax_c = self.unpack_uniq_var(Ax) - - for irrep_slice, ham_slice in zip(self.base._irrep_slices, ham_od): - Ax_c_od_slice = list( - np.tensordot( - -ham_slice, np.stack(x_c[irrep_slice], axis=0), axes=1 - ) - ) - Ax_c[irrep_slice] = [ - a1 + (w * a2) - for a1, a2, w in zip( - Ax_c[irrep_slice], - Ax_c_od_slice, - self.base.weights[irrep_slice], - ) - ] - - return self.pack_uniq_var(Ax_o, Ax_c) - - else: - ham_od[np.diag_indices_from(ham_od)] = 0.0 - ham_od += ham_od.T # This corresponds to the arbitrary newton_casscf*2 - - def Aop(x): - Ax = hop(x) - x_c = self.unpack_uniq_var(x)[1] - Ax_o, Ax_c = self.unpack_uniq_var(Ax) - Ax_c_od = list(np.tensordot(-ham_od, np.stack(x_c, axis=0), axes=1)) - Ax_c = [ - a1 + (w * a2) for a1, a2, w in zip(Ax_c, Ax_c_od, self.base.weights) - ] - return self.pack_uniq_var(Ax_o, Ax_c) - - return self.project_Aop(Aop, ci, state), Adiag diff --git a/pyscf/grad/mcpdft.py b/pyscf/grad/mcpdft.py deleted file mode 100644 index 043d3dcc9..000000000 --- a/pyscf/grad/mcpdft.py +++ /dev/null @@ -1,654 +0,0 @@ -#!/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. -# -from pyscf.mcscf import newton_casscf, casci, mc1step -from pyscf.grad import rks as rks_grad -from pyscf.dft import gen_grid -from pyscf.lib import logger, pack_tril, current_memory, einsum, tag_array -from pyscf.grad import sacasscf -from pyscf.mcscf.casci import cas_natorb - -from pyscf.mcpdft.pdft_eff import _contract_eff_rho -from pyscf.mcpdft.otpd import get_ontop_pair_density, _grid_ao2mo -from pyscf.mcpdft import _dms - -from itertools import product -from scipy import linalg -import numpy as np -import gc - -BLKSIZE = gen_grid.BLKSIZE - -def gfock_sym(mc, mo_coeff, casdm1, casdm2, h1e, eris): - """Assume that h2e v_j = v_k""" - ncore = mc.ncore - ncas = mc.ncas - nocc = ncore + ncas - - nao, nmo = mo_coeff.shape - - # gfock = Generalized Fock, Adv. Chem. Phys., 69, 63 - - # MRH: I need to replace aapa with the equivalent array from veff2 - # I'm not sure how the outcore file-paging system works - # I also need to generate vhf_c and vhf_a from veff2 rather than the - # molecule's actual integrals. The true Coulomb repulsion should already be - # in veff1, but I need to generate the "fake" vj - vk/2 from veff2 - h1e_mo = mo_coeff.T @ h1e @ mo_coeff + eris.vhf_c - aapa = np.zeros((ncas, ncas, nmo, ncas), dtype=h1e_mo.dtype) - vhf_a = np.zeros((nmo, nmo), dtype=h1e_mo.dtype) - - for i in range(nmo): - jbuf = eris.ppaa[i] - aapa[:, :, i, :] = jbuf[ncore:nocc, :, :] - vhf_a[i] = np.tensordot(jbuf, casdm1, axes=2) - - vhf_a *= 0.5 - # we have assumed that vj = vk: vj - vk/2 = vj - vj/2 = vj/2 - gfock = np.zeros((nmo, nmo)) - gfock[:, :ncore] = (h1e_mo[:, :ncore] + vhf_a[:, :ncore]) * 2 - gfock[:, ncore:nocc] = h1e_mo[:, ncore:nocc] @ casdm1 - gfock[:, ncore:nocc] += einsum('uviw,vuwt->it', aapa, casdm2) - - return gfock - -def xc_response(ot, vot, rho, Pi, weights, moval_occ, aoval, mo_occ, mo_occup, ncore, nocc, casdm2_pack, ndpi, mo_cas): - vrho, vPi = vot - - - # Vpq + Vpqrs * Drs ; I'm not sure why the list comprehension down - # there doesn't break ao's stride order but I'm not complaining - vrho = _contract_eff_rho(vPi, rho.sum(0), add_eff_rho=vrho) - - tmp_dv = np.stack( - [ - ot.get_veff_1body(rho, Pi, [ao_i, moval_occ], weights, kern=vrho) - for ao_i in aoval - ], - axis=0, - ) - tmp_dv = (tmp_dv * mo_occ[None, :, :] * mo_occup[None, None, :nocc]).sum(2) - - # Vpuvx * Lpuvx ; remember the stupid slowest->fastest->medium - # stride order of the ao grid arrays - moval_cas = np.ascontiguousarray(moval_occ[..., ncore:].transpose(0,2,1)).transpose(0,2,1) - - tmp_dv1 = ot.get_veff_2body_kl(rho, Pi, moval_cas, moval_cas, weights, symm=True, kern=vPi) - # tmp_dv.shape = ndpi,ngrids,ncas*(ncas+1)//2 - tmp_dv1 = np.tensordot(tmp_dv1, casdm2_pack, axes=(-1,-1)) - # tmp_dv.shape = ndpi, ngrids, ncas, ncas - tmp_dv1[0] = (tmp_dv1[:ndpi] * moval_cas[:ndpi, :, None, :]).sum(0) - # Chain and product rule - tmp_dv1[1:ndpi] *= moval_cas[0, :, None, :] - # Chain and product rule - tmp_dv1 = tmp_dv1.sum(-1) - # tmp_dv.shape = ndpi, ngrids, ncas - tmp_dv1 = np.tensordot(aoval[:, :ndpi], tmp_dv1, axes=((1, 2), (0, 1))) - # tmp_dv.shape = comp, nao (orb), ncas (dm2) - tmp_dv1 = np.einsum('cpu,pu->cp', tmp_dv1, mo_cas) - # tmp_dv.shape = comp, ncas - - return tmp_dv + tmp_dv1 - - -def pack_casdm2(cascm2, ncas): - diag_idx = np.arange(ncas) # for puvx - diag_idx = diag_idx * (diag_idx+1) // 2 + diag_idx - - casdm2_pack = (cascm2 + cascm2.transpose(0, 1, 3, 2)).reshape(ncas**2, ncas, ncas) - casdm2_pack = pack_tril(casdm2_pack).reshape(ncas, ncas, -1) - casdm2_pack[:, :, diag_idx] *= 0.5 - return casdm2_pack - -def sum_terms(mf_grad, mol, atmlst,dm1, gfock, coul_term, dvxc): - de_hcore = np.zeros((len(atmlst), 3)) - de_renorm = np.zeros((len(atmlst), 3)) - de_coul = np.zeros((len(atmlst), 3)) - de_xc = np.zeros((len(atmlst), 3)) - - aoslices = mol.aoslice_by_atom() - hcore_deriv = mf_grad.hcore_generator(mol) - s1 = mf_grad.get_ovlp(mol) - - for k, ia in enumerate(atmlst): - p0, p1 = aoslices[ia][2:] - h1ao = hcore_deriv(ia) - de_hcore[k] += np.tensordot(h1ao, dm1) - de_renorm[k] -= np.tensordot(s1[:, p0:p1], gfock[p0:p1])*2 - de_coul[k] += coul_term(p0, p1) - de_xc[k] += dvxc[:, p0:p1].sum(1)*2 - - de_nuc = mf_grad.grad_nuc(mol, atmlst) - - return de_hcore, de_coul, de_xc, de_nuc, de_renorm, - -def mcpdft_HellmanFeynman_grad (mc, ot, veff1, veff2, mo_coeff=None, ci=None, - atmlst=None, mf_grad=None, verbose=None, max_memory=None, - auxbasis_response=False): - '''Modification of pyscf.grad.casscf.kernel to compute instead the - Hellman-Feynman gradient terms of MC-PDFT. From the differentiated - Hamiltonian matrix elements, only the core and Coulomb energy parts - remain. For the renormalization terms, the effective Fock matrix is - as in CASSCF, but with the same Hamiltonian substutition that is - used for the energy response terms. ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ci is None: ci = mc.ci - if mf_grad is None: mf_grad = mc.get_rhf_base ().nuc_grad_method() - if mc.frozen is not None: - raise NotImplementedError - if max_memory is None: max_memory = mc.max_memory - t0 = (logger.process_clock (), logger.perf_counter ()) - - mol = mc.mol - ncore = mc.ncore - ncas = mc.ncas - nocc = ncore + ncas - nelecas = mc.nelecas - nao, nmo = mo_coeff.shape - - mo_core = mo_coeff[:,:ncore] - mo_cas = mo_coeff[:,ncore:nocc] - - casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas) - - spin = abs(nelecas[0] - 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 functionals") - if abs(hyb[0] - hyb[1]) > 1e-11: - raise NotImplementedError( - "hybrid on-top functionals with different exchange,correlation components" - ) - - cas_hyb = hyb[0] - ot_hyb = 1.0 - cas_hyb - - if cas_hyb > 1e-11: - # TODO: actually implement this in a more efficient manner - # That is, lets not call grad_elec, but lets actually do this our self maybe? - # Can then use get_pdft_veff with drop_mcwfn = False to automatically get - # Generalized fock matrix terms, but then have to deal explicitly with the - # derivative of eri terms and auxbasis stuff. Also, get_pdft_veff with - # drop_mcwfn=False means the get_wfn_response doesn't have to add the - # eris to the veff2 since it should just include it already - if auxbasis_response: - from pyscf.df.grad import casscf as casscf_grad - else: - from pyscf.grad import casscf as casscf_grad - - cas_grad = casscf_grad.Gradients(mc) - - de_cas = cas_hyb * cas_grad.grad_elec( - mo_coeff=mo_coeff, ci=ci, atmlst=atmlst, verbose=verbose - ) - - # gfock = Generalized Fock, Adv. Chem. Phys., 69, 63 - dm_core = 2 * mo_core @ mo_core.T - dm_cas = mo_cas @ casdm1 @ mo_cas.T - - gfock = gfock_sym(mc, mo_coeff, casdm1, casdm2, ot_hyb*mc.get_hcore() + veff1, veff2) - - dme0 = mo_coeff @ (0.5*(gfock+gfock.T)) @ mo_coeff.T - del gfock - - if atmlst is None: - atmlst = range(mol.natm) - - de_grid = np.zeros ((len(atmlst),3)) - de_wgt = np.zeros ((len(atmlst),3)) - de_aux = np.zeros ((len(atmlst),3)) - - t0 = logger.timer (mc, 'PDFT HlFn gfock', *t0) - mo_coeff, ci, mo_occup = cas_natorb (mc, mo_coeff=mo_coeff, ci=ci) - mo_occ = mo_coeff[:,:nocc] - mo_cas = mo_coeff[:,ncore:nocc] - - dm1 = dm_core + dm_cas - dm1 = tag_array (dm1, mo_coeff=mo_coeff, mo_occ=mo_occup) - - # MRH: vhf1c and vhf1a should be the TRUE vj_c and vj_a (no vk!) - vj = mf_grad.get_jk (dm=dm1)[0] - if auxbasis_response: - de_aux += ot_hyb*np.squeeze (vj.aux[:,:,atmlst,:]) - - # MRH: Now I have to compute the gradient of the on-top energy - # This involves derivatives of the orbitals that construct rho and Pi and - # therefore another set of potentials. It also involves the derivatives of - # quadrature grid points which propagate through the densities and - # therefore yet another set of potentials. The orbital-derivative part - # includes all the grid points and some of the orbitals (- sign); the - # grid-derivative part includes all of the orbitals and some of the grid - # points (+ sign). I'll do a loop over grid sections and make arrays of - # type (3,nao, nao) and (3,nao, ncas, ncas, ncas). I'll contract them - # within the grid loop for the grid derivatives and in the following - # orbital loop for the xc derivatives - # MRH, 05/09/2020: The actual spin density doesn't matter at all in PDFT! - # I could probably save a fair amount of time by not screwing around with - # the actual spin density! Also, the cumulant decomposition can always be - # defined without the spin-density matrices and it's still valid! - casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas) - twoCDM = _dms.dm2_cumulant (casdm2, casdm1) - dm1 = tag_array (dm1, mo_coeff=mo_occ, mo_occ=mo_occup[:nocc]) - make_rho = ot._numint._gen_rho_evaluator (mol, dm1, hermi=1, with_lapl=False)[0] - dvxc = np.zeros ((3,nao)) - idx = np.array ([[1,4,5,6],[2,5,7,8],[3,6,8,9]], dtype=np.int_) - # For addressing particular ao derivatives - if ot.xctype == 'LDA': idx = idx[:,0:1] # For LDAs, no second derivatives - - casdm2_pack = pack_casdm2(twoCDM, ncas) - full_atmlst = -np.ones (mol.natm, dtype=np.int_) - - t1 = logger.timer (mc, 'PDFT HlFn quadrature setup', *t0) - for k, ia in enumerate (atmlst): - full_atmlst[ia] = k - - # for LDA we need 1 deriv, GGA: 2 deriv - # for mGGA with tau, we only need 2 deriv - ao_deriv = (1, 2, 2)[ot.dens_deriv] - ndao = (1, 4, 10)[ao_deriv] - ndrho = (1, 4, 5)[ot.dens_deriv] - ndpi = (1, 4)[ot.Pi_deriv] - ncols = 1.05 * 3 * (ndao * (nao + nocc) + max(ndrho * nao, ndpi * ncas * ncas)) - - for ia, (coords, w0, w1) in enumerate (rks_grad.grids_response_cc ( - ot.grids)): - # For the xc potential derivative, I need every grid point in the - # entire molecule regardless of atmlist. (Because that's about orbs.) - # For the grid and weight derivatives, I only need the gridpoints that - # are in atmlst. It is conceivable that I can make this more efficient - # by only doing cross-combinations of grids and AOs, but I don't know - # how "mask" works yet or how else I could do this. - gc.collect () - ngrids = coords.shape[0] - - remaining_floats = (max_memory - current_memory()[0]) * 1e6 / 8 - blksize = int (remaining_floats / (ncols*BLKSIZE)) * BLKSIZE - blksize = max (BLKSIZE, min (blksize, ngrids, BLKSIZE*1200)) - t1 = logger.timer (mc, 'PDFT HlFn quadrature atom {} mask and memory ' - 'setup'.format (ia), *t1) - - for ip0 in range(0, ngrids, blksize): - ip1 = min(ngrids, ip0 + blksize) - mask = gen_grid.make_mask(mol, coords[ip0:ip1]) - logger.info( - mc, - ("PDFT gradient atom {} slice {}-{} of {} total").format( - ia, ip0, ip1, ngrids - ), - ) - - ao = ot._numint.eval_ao(mol, coords[ip0:ip1], deriv=ao_deriv, non0tab=mask) - - # Need 1st derivs for LDA, 2nd for GGA, etc. - t1 = logger.timer( - mc, ("PDFT HlFn quadrature atom {} ao " "grids").format(ia), *t1 - ) - # Slice down ao so as not to confuse the rho and Pi generators - if ot.xctype == "LDA": - aoval = ao[0] - elif ot.xctype == "GGA": - aoval = ao[:4] - elif ot.xctype == "MGGA": - aoval = ao[:4] - else: - raise ValueError("Unknown xctype: {}".format(ot.xctype)) - - rho = make_rho (0, aoval, mask, ot.xctype) / 2.0 - rho = np.stack ((rho,)*2, axis=0) - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} rho ' - 'calc').format (ia), *t1) - Pi = get_ontop_pair_density (ot, rho, aoval, twoCDM, mo_cas, - ot.Pi_deriv, mask) - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} Pi ' - 'calc').format (ia), *t1) - - # TODO: consistent format requirements for shape of ao grid - if ot.xctype == 'LDA': - aoval = ao[:1] - moval_occ = _grid_ao2mo (mol, aoval, mo_occ, mask) - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} ao2mo ' - 'grid').format (ia), *t1) - aoval = np.ascontiguousarray ([ao[ix].transpose (0,2,1) - for ix in idx[:,:ndao]]).transpose (0,1,3,2) - ao = None - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} ao grid ' - 'reshape').format (ia), *t1) - eot, vot = ot.eval_ot (rho, Pi, weights=w0[ip0:ip1])[:2] - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} ' - 'eval_ot').format (ia), *t1) - puvx_mem = 2 * ndpi * (ip1-ip0) * ncas * ncas * 8 / 1e6 - remaining_mem = max_memory - current_memory ()[0] - logger.info (mc, ('PDFT gradient memory note: working on {} grid ' - 'points; estimated puvx usage = {:.1f} of {:.1f} remaining ' - 'MB').format ((ip1-ip0), puvx_mem, remaining_mem)) - - # Weight response - de_wgt += np.tensordot (eot, w1[atmlst,...,ip0:ip1], axes=(0,2)) - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} weight ' - 'response').format (ia), *t1) - - # Find the atoms that are a part of the atomlist - # grid correction shouldn't be added if they aren't there - # The last stuff to vectorize is in get_veff_2body! - k = full_atmlst[ia] - - tmp_dv = xc_response(ot, vot, rho, Pi, w0[ip0:ip1], moval_occ, aoval, mo_occ, mo_occup, ncore, nocc, - casdm2_pack, ndpi, mo_cas) - - if k >=0: de_grid[k] += 2*tmp_dv.sum(1) # Grid response - dvxc -= tmp_dv #XC response - - tmp_dv = None - t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {}').format (ia), *t1) - - rho = Pi = eot = vot = aoval = moval_occ = None - gc.collect () - - def coul_term(p0, p1): - return np.tensordot(vj[:,p0:p1], dm1[p0:p1])*2 - - de_hcore, de_coul, de_xc, de_nuc, de_renorm = sum_terms(mf_grad, mol, atmlst, dm1, dme0, coul_term, - dvxc) - # Deal with hybridization - de_hcore *= ot_hyb - de_coul *= ot_hyb - - logger.debug (mc, "MC-PDFT Hellmann-Feynman nuclear:\n{}".format (de_nuc)) - logger.debug (mc, "MC-PDFT Hellmann-Feynman hcore component:\n{}".format ( - de_hcore)) - logger.debug (mc, "MC-PDFT Hellmann-Feynman coulomb component:\n{}".format - (de_coul)) - logger.debug (mc, "MC-PDFT Hellmann-Feynman xc component:\n{}".format ( - de_xc)) - logger.debug (mc, ("MC-PDFT Hellmann-Feynman quadrature point component:" - "\n{}").format (de_grid)) - logger.debug (mc, ("MC-PDFT Hellmann-Feynman quadrature weight component:" - "\n{}").format (de_wgt)) - logger.debug (mc, "MC-PDFT Hellmann-Feynman renorm component:\n{}".format ( - de_renorm)) - - de = de_nuc + de_hcore + de_coul + de_renorm + de_xc + de_grid + de_wgt - - - if auxbasis_response: - de += de_aux - logger.debug (mc, "MC-PDFT Hellmann-Feynman aux component:\n{}".format - (de_aux)) - - if cas_hyb > 1e-11: - de += de_cas - logger.debug(mc, "MC-PDFT Hellmann-Feynman CAS component:\n{}".format(de_cas)) - - t1 = logger.timer (mc, 'PDFT HlFn total', *t0) - - return de - -# TODO: docstrings (parent classes???) -# TODO: add a consistent threshold for elimination of degenerate-state rotations -class Gradients (sacasscf.Gradients): - - def __init__(self, pdft, state=None): - super().__init__(pdft, state=state) - # TODO: gradient of PDFT state-average energy - # i.e., state = 0 & nroots > 1 case - if self.state is None and self.nroots == 1: - self.state = 0 - self.e_mcscf = self.base.e_mcscf - self._not_implemented_check () - - def _not_implemented_check (self): - name = self.__class__.__name__ - if (isinstance (self.base, casci.CASCI) and not - isinstance (self.base, mc1step.CASSCF)): - raise NotImplementedError ( - "{} for CASCI-based MC-PDFT".format (name) - ) - ot, otxc, nelecas = self.base.otfnal, self.base.otxc, self.base.nelecas - spin = abs (nelecas[0]-nelecas[1]) - omega, alpha, hyb = ot._numint.rsh_and_hybrid_coeff ( - otxc, spin=spin) - hyb_x, hyb_c = hyb - if abs(hyb_x - hyb_c) >1e-11: - raise NotImplementedError ( - "{} for hybrid MC-PDFT functionals with different exchange, correlation".format (name) - ) - if omega: - raise NotImplementedError ( - "{} for range-separated MC-PDFT functionals".format (name) - ) - - def get_wfn_response (self, state=None, verbose=None, mo=None, - ci=None, veff1=None, veff2=None, nlag=None, **kwargs): - if state is None: state = self.state - if verbose is None: verbose = self.verbose - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if nlag is None: nlag = self.nlag - if (veff1 is None) or (veff2 is None): - veff1, veff2 = self.base.get_pdft_veff (mo, ci[state], - incl_coul=True, paaa_only=True, drop_mcwfn=True) - - log = logger.new_logger(self, verbose) - - sing_tol = getattr(self, 'sing_tol_sasa', 1e-8) - fcasscf = self.make_fcasscf(state) - fcasscf.mo_coeff = mo - fcasscf.ci = ci[state] - def my_hcore (): - return self.base.get_hcore () + veff1 - fcasscf.get_hcore = my_hcore - - nelecas = self.base.nelecas - spin = abs (nelecas[0]-nelecas[1]) - omega, alpha, hyb = self.base.otfnal._numint.rsh_and_hybrid_coeff(self.base.otxc, spin=spin) - if omega: - raise NotImplementedError("range-separated MC-PDFT functionals") - if abs(hyb[0] - hyb[1]) > 1e-11: - raise NotImplementedError("hybrid on-top functional with different exchange,correlation components") - cas_hyb = hyb[0] - - if cas_hyb > 1e-11:# and len(ci) > 1: - # For SS-CASSCF, there are no Lagrange multipliers - # This is only needed in SA case - eris = self.base.ao2mo(mo_coeff=mo) - terms = ["vhf_c", "papa", "ppaa", "j_pc", "k_pc"] - for term in terms: - setattr(eris, term, getattr(veff2, term) + cas_hyb*getattr(eris, term)[:]) - veff2.vhf_c - veff2 = eris - - - g_all_state = newton_casscf.gen_g_hop (fcasscf, mo, ci[state], veff2, verbose)[0] - - g_all = np.zeros (nlag) - g_all[:self.ngorb] = g_all_state[:self.ngorb] - # Eliminate gradient of self-rotation and rotation into - # degenerate states - spin_states = np.asarray (self.spin_states) - idx_spin = spin_states==spin_states[state] - e_gap = self.e_mcscf-self.e_mcscf[state] if self.nroots>1 else [0.0] - idx_degen = np.abs (e_gap)0: - offs = sum ([na * nb for na, nb in zip( - self.na_states[:state], self.nb_states[:state])]) - ndet = self.na_states[state]*self.nb_states[state] - gci[offs:][:ndet] += gci_state - - # Debug - log.debug("g_all mo:\n{}".format(g_all[:self.ngorb])) - log.debug("g_all CI:\n{}".format(g_all[self.ngorb:])) - - return g_all - - def get_ham_response (self, state=None, atmlst=None, verbose=None, mo=None, - ci=None, eris=None, mf_grad=None, veff1=None, veff2=None, - **kwargs): - if state is None: state = self.state - if atmlst is None: atmlst = self.atmlst - if verbose is None: verbose = self.verbose - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if (veff1 is None) or (veff2 is None): - assert (False), kwargs - fcasscf = self.make_fcasscf (state) - fcasscf.mo_coeff = mo - fcasscf.ci = ci[state] - - return mcpdft_HellmanFeynman_grad( - fcasscf, - self.base.otfnal, - veff1, - veff2, - mo_coeff=mo, - ci=ci[state], - atmlst=atmlst, - mf_grad=mf_grad, - verbose=verbose, - ) - - def get_init_guess (self, bvec, Adiag, Aop, precond): - '''Initial guess should solve the problem for SA-SA rotations''' - sing_tol = getattr (self, 'sing_tol_sasa', 1e-8) - ci = self.base.ci - state = self.state - if self.nroots == 1: ci = [ci,] - idx_spin = [i for i in range (self.nroots) - if self.spin_states[i]==self.spin_states[state]] - ci_blk = np.asarray ([ci[i].ravel () for i in idx_spin]) - b_orb, b_ci = self.unpack_uniq_var (bvec) - b_ci_blk = np.asarray ([b_ci[i].ravel () for i in idx_spin]) - x0 = np.zeros_like (bvec) - if self.nroots > 1: - b_sa = np.dot (ci_blk.conjugate (), b_ci[state].ravel ()) - A_sa = 2 * self.weights[state] * (self.e_mcscf - - self.e_mcscf[state]) - idx_null = np.abs (A_sa)=sing_tol): - logger.warn (self, 'Singular Hessian in CP-MCPDFT!') - idx_null &= np.abs (b_sa) - sum_i , where O is the 1- and 2-RDM - operator product, and |bra> and |ket> are both states spanning the - vector space of |i>, which are multi-determinantal many-electron - states in an active space. - - Args: - mc : object of class CASCI or CASSCF - Only "ncas" and "nelecas" are used, to determine Hilbert - of ci - ci : ndarray or list of length (nroots) - Contains CI vectors spanning a model space - si_bra : ndarray of shape (nroots) - Coefficients of ci elements for state |bra> - si_ket : ndarray of shape (nroots) - Coefficients of ci elements for state |ket> - - Returns: - casdm1 : ndarray of shape [ncas,]*2 - Contains O = p'q case - casdm2 : ndarray of shape [ncas,]*4 - Contains O = p'q'sr case - ''' - ncas, nelecas = mc.ncas, mc.nelecas - nroots = len (ci) - ci_arr = np.asarray (ci) - ci_bra = np.tensordot (si_bra, ci_arr, axes=1) - ci_ket = np.tensordot (si_ket, ci_arr, axes=1) - casdm1, casdm2 = direct_spin1.trans_rdm12 (ci_bra, ci_ket, ncas, nelecas) - ddm1 = np.zeros ((nroots, ncas, ncas), dtype=casdm1.dtype) - ddm2 = np.zeros ((nroots, ncas, ncas, ncas, ncas), dtype=casdm1.dtype) - for i in range (nroots): - ddm1[i,...], ddm2[i,...] = direct_spin1.make_rdm12 (ci[i], ncas, - nelecas) - si_diag = si_bra * si_ket - casdm1 -= np.tensordot (si_diag, ddm1, axes=1) - casdm2 -= np.tensordot (si_diag, ddm2, axes=1) - return casdm1, casdm2 - -# TODO: docstring? -def mspdft_heff_response (mc_grad, mo=None, ci=None, - si_bra=None, si_ket=None, state=None, - heff_mcscf=None, eris=None): - '''Compute the orbital and intermediate-state rotation response - vector in the context of an MS-PDFT gradient calculation ''' - mc = mc_grad.base - if mo is None: mo = mc_grad.mo_coeff - if ci is None: ci = mc_grad.ci - if state is None: state = mc_grad.state - ket, bra = _unpack_state (state) - if si_bra is None: si_bra = mc.si[:,bra] - if si_ket is None: si_ket = mc.si[:,ket] - if heff_mcscf is None: heff_mcscf = mc.heff_mcscf - if eris is None: eris = mc.ao2mo (mo) - nroots, ncore = mc_grad.nroots, mc.ncore - moH = mo.conj ().T - - # Orbital rotation (no all-core DM terms allowed!) - # (Factor of 2 is convention difference between mc1step and newton_casscf) - casdm1, casdm2 = make_rdm12_heff_offdiag (mc, ci, si_bra, si_ket) - casdm1 = 0.5 * (casdm1 + casdm1.T) - casdm2 = 0.5 * (casdm2 + casdm2.transpose (1,0,3,2)) - vnocore = eris.vhf_c.copy () - vnocore[:,:ncore] = -moH @ mc.get_hcore () @ mo[:,:ncore] - with lib.temporary_env (eris, vhf_c=vnocore): - g_orb = 2 * mc1step.gen_g_hop (mc, mo, 1, casdm1, casdm2, eris)[0] - g_orb = mc.unpack_uniq_var (g_orb) - - # Intermediate state rotation (TODO: state-average-mix generalization) - braH = np.dot (si_bra, heff_mcscf) - Hket = np.dot (heff_mcscf, si_ket) - si2 = si_bra * si_ket - g_is = np.multiply.outer (si_ket, braH) - g_is += np.multiply.outer (si_bra, Hket) - g_is -= 2 * si2[:,None] * heff_mcscf - g_is -= g_is.T - g_is = g_is[np.tril_indices (nroots, k=-1)] - - return g_orb, g_is - -# TODO: docstring? -def mspdft_heff_HellmanFeynman (mc_grad, atmlst=None, mo=None, ci=None, - si=None, si_bra=None, si_ket=None, state=None, eris=None, mf_grad=None, - verbose=None, **kwargs): - mc = mc_grad.base - if atmlst is None: atmlst = mc_grad.atmlst - if mo is None: mo = mc.mo_coeff - if ci is None: ci = mc.ci - if si is None: si = getattr (mc, 'si', None) - if state is None: state = mc_grad.state - ket, bra = _unpack_state (state) - if si_bra is None: si_bra = si[:,bra] - if si_ket is None: si_ket = si[:,ket] - if eris is None: eris = mc.ao2mo (mo) - if mf_grad is None: mf_grad = mc.get_rhf_base ().nuc_grad_method () - if verbose is None: verbose = mc_grad.verbose - ncore = mc.ncore - log = logger.new_logger (mc_grad, verbose) - ci0 = np.zeros_like (ci[0]) - - # CASSCF grad with effective RDMs - t0 = (logger.process_clock (), logger.perf_counter ()) - casdm1, casdm2 = make_rdm12_heff_offdiag (mc, ci, si_bra, si_ket) - casdm1 = 0.5 * (casdm1 + casdm1.T) - casdm2 = 0.5 * (casdm2 + casdm2.transpose (1,0,3,2)) - dm12 = lambda * args: (casdm1, casdm2) - fcasscf = mc_grad.make_fcasscf (state=ket, - fcisolver_attr={'make_rdm12' : dm12}) - # TODO: DFeri functionality - # Perhaps by patching fcasscf.nuc_grad_method? - fcasscf_grad = fcasscf.nuc_grad_method () - #fcasscf_grad = casscf_grad.Gradients (fcasscf) - de = fcasscf_grad.kernel (mo_coeff=mo, ci=ci0, atmlst=atmlst, verbose=0) - - # subtract nuc-nuc and core-core (patching out simplified gfock terms) - moH = mo.conj ().T - f0 = (moH @ mc.get_hcore () @ mo) + eris.vhf_c - mo_energy = f0.diagonal ().copy () - mo_occ = np.zeros_like (mo_energy) - mo_occ[:ncore] = 2.0 - f0 *= mo_occ[None,:] - dme0 = lambda * args: mo @ ((f0+f0.T)*.5) @ moH - with lib.temporary_env (mf_grad, make_rdm1e=dme0, verbose=0): - with lib.temporary_env (mf_grad.base, mo_coeff=mo, mo_occ=mo_occ): - # Second level there should become unnecessary in future, if anyone - # ever gets around to cleaning up pyscf.df.grad.rhf & pyscf.grad.rhf - dde = mf_grad.kernel (mo_coeff=mo, mo_energy=mo_energy, mo_occ=mo_occ, - atmlst=atmlst) - de -= dde - log.debug ('MS-PDFT gradient off-diagonal H-F terms:\n{}'.format (de)) - log.timer ('MS-PDFT gradient off-diagonal H-F terms', *t0) - return de - -def get_diabfns (obj): - '''Interpret the name of the MS-PDFT method as a pair of functions - which compute the derivatives of a particular objective function - with respect to wave function parameters and geometry perturbations, - excluding first and second derivatives wrt intermediate state - rotations, which is handled by the energy-class version of this - function. - - Args: - obj : string - Specify particular MS-PDFT method. Currently, only "CMS" is - supported. Not case-sensitive. - - Returns: - diab_response : callable - Computes the orbital-rotation and CI-transfer sectors of the - Hessian-vector product of the MS objective function for a - vector of intermediate-state rotations - diab_grad : callable - Computes the gradient of the MS objective function wrt - geometry perturbation - ''' - if obj.upper () == 'CMS': - from pyscf.grad.cmspdft import diab_response, diab_grad - else: - raise RuntimeError ('MS-PDFT type not supported') - return diab_response, diab_grad - -# TODO: docstring? especially considering the "si_bra," "si_ket" -# functionality?? -# TODO: figure out how to log the gradients with the right method name! -class Gradients (mcpdft_grad.Gradients): - - # Preconditioner solves the IS problem; hence, get_init_guess rewrite is - # unnecessary - get_init_guess = sacasscf_grad.Gradients.get_init_guess - project_Aop = sacasscf_grad.Gradients.project_Aop - - def __init__(self, mc): - self.conv_rtol = 0 - def_tol0 = getattr (mc, 'conv_tol_grad', None) - if def_tol0 is None: - def_tol0 = np.sqrt (getattr (mc, 'conv_tol', 1e-7)) - def_tol1 = getattr (mc, 'conv_tol_diabatize', - CONV_TOL_DIABATIZE) - self.conv_atol = min (def_tol0, def_tol1) - self.sing_step_tol = SING_STEP_TOL - mcpdft_grad.Gradients.__init__(self, mc) - r, g = get_diabfns (self.base.diabatization) - self._diab_response = r - self._diab_grad = g - self.nlag += self.nis - - @property - def nis (self): - return self.nroots * (self.nroots - 1) // 2 - - def diab_response (self, Lis, **kwargs): - return self._diab_response (self, Lis, **kwargs) - def diab_grad (self, Lis, **kwargs): - return self._diab_grad (self, Lis, **kwargs) - - def kernel (self, state=None, mo=None, ci=None, si=None, _freeze_is=False, - **kwargs): - '''Cache the Hamiltonian and effective Hamiltonian terms, and - pass around the IS hessian - - eris, veff1, veff2, and d2f should be available to all top-level - functions: get_wfn_response, get_Aop_Adiag, get_ham_response, - and get_LdotJnuc - - freeze_is == True sets the is component of the response to zero - for debugging purposes - ''' - if state is None: state = self.state - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if si is None: si = self.base.si - if isinstance (ci, np.ndarray): ci = [ci] # hack hack hack... - if state is None: - raise NotImplementedError ('Gradient of PDFT state-average energy') - self.state = state # Not the best code hygiene maybe - nroots = self.nroots - veff1 = [] - veff2 = [] - d2f = self.base.diabatizer (ci=ci)[2] - for ix in range (nroots): - v1, v2 = self.base.get_pdft_veff (mo, ci, incl_coul=True, - paaa_only=True, state=ix) - veff1.append (v1) - veff2.append (v2) - return mcpdft_grad.Gradients.kernel (self, state=state, mo=mo, ci=ci, - si=si, d2f=d2f, veff1=veff1, veff2=veff2, _freeze_is=_freeze_is, - **kwargs) - - def pack_uniq_var (self, xorb, xci, xis=None): - x = sacasscf_grad.Gradients.pack_uniq_var (self, xorb, xci) - if xis is not None: x = np.append (x, xis) - return x - - def unpack_uniq_var (self, x): - ngorb, nci, nis = self.ngorb, self.nci, self.nis - x, xis = x[:ngorb+nci], x[ngorb+nci:] - xorb, xci = sacasscf_grad.Gradients.unpack_uniq_var (self, x) - if len (xis)==nis: return xorb, xci, xis - return xorb, xci - - def _get_is_component (self, xci, ci=None, symm=-1): - # TODO: state-average-mix - if ci is None: ci = self.base.ci - nroots = self.nroots - xci = np.asarray (xci).reshape (nroots, -1) - ci = np.asarray (ci).reshape (nroots, -1) - xis = np.dot (xci.conj (), ci.T) - if symm > -1: xis -= xis.T - else: - assert (np.amax (np.abs (xis + xis.T)) < 1e-8), '{}'.format (xis) - return xis[np.tril_indices (nroots, k=-1)] - - def _separate_is_component (self, xci, ci=None, symm=-1): - # TODO: state-average-mix - is_list = isinstance (xci, list) - is_tuple = isinstance (xci, tuple) - if ci is None: ci = self.base.ci - nroots = self.nroots - ishape = np.asarray (xci).shape - xci = np.asarray (xci).reshape (nroots, -1) - xci = np.asarray (xci).reshape (nroots, -1) - ci = np.asarray (ci).reshape (nroots, -1) - xis = np.dot (xci.conj (), ci.T) - xci -= np.dot (xis.conj (), ci) - xci = xci.reshape (ishape) - if is_list: xci = list (xci) - elif is_tuple: xci = tuple (xci) - if symm > -1: xis -= xis.T - #else: - # assert (np.amax (np.abs (xis + xis.T)) < 1e-8), '{}'.format (xis) - xis = xis[np.tril_indices (nroots, k=-1)] - return xci, xis - - - def get_wfn_response (self, si_bra=None, si_ket=None, state=None, mo=None, - ci=None, si=None, eris=None, veff1=None, veff2=None, - _freeze_is=False, d2f=None, **kwargs): - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if si is None: si = self.base.si - if state is None: state = self.state - ket, bra = _unpack_state (state) - if si_bra is None: si_bra = si[:,bra] - if si_ket is None: si_ket = si[:,ket] - if d2f is None: d2f = self.base.diabatizer (ci=ci)[2] - log = lib.logger.new_logger (self, self.verbose) - si_diag = si_bra * si_ket - - # Diagonal: PDFT component - nlag = self.nlag-self.nis - g_all_pdft = np.zeros (nlag) - for i, (amp, c, v1, v2) in enumerate (zip (si_diag, ci, veff1, veff2)): - if not amp: continue - g_i = mcpdft_grad.Gradients.get_wfn_response (self, - state=i, mo=mo, ci=ci, veff1=v1, veff2=v2, nlag=nlag, **kwargs) - g_all_pdft += amp * g_i - if self.verbose >= lib.logger.DEBUG: - g_orb, g_ci = self.unpack_uniq_var (g_i) - g_ci, g_is = self._separate_is_component (g_ci, ci=ci, symm=0) - log.debug ('g_is pdft state {} component:\n{} * {}'.format (i, - amp, g_is)) - - # DEBUG - g_orb_pdft, g_ci = self.unpack_uniq_var (g_all_pdft) - g_ci, g_is_pdft = self._separate_is_component (g_ci, ci=ci, symm=0) - - # Off-diagonal: heff component - g_orb_heff, g_is_heff = mspdft_heff_response (self, mo=mo, ci=ci, - si_bra=si_bra, si_ket=si_ket, eris=eris) - - log.debug ('g_is pdft total component:\n{}'.format (g_is_pdft)) - log.debug ('g_is heff component:\n{}'.format (g_is_heff)) - - # Combine - g_orb = g_orb_pdft + g_orb_heff - g_is = g_is_pdft + g_is_heff - if _freeze_is: g_is[:] = 0.0 - g_all = self.pack_uniq_var (g_orb, g_ci, g_is) - - # DEBUG - d2f_evals, d2f_evecs = linalg.eigh (d2f) - g_is_modes = np.dot (g_is, d2f_evecs) - g_is_pdft = np.dot (g_is_pdft, d2f_evecs) - g_is_heff = np.dot (g_is_heff, d2f_evecs) - log.debug ("IS sector Lagrange multiplier solution:") - for i, (denom, num) in enumerate (zip (d2f_evals, g_is_modes)): - log.debug ('%d %e / %e = %e (%e %e)', i, -num, denom, -num/denom, - g_is_pdft[i], g_is_heff[i]) - - return g_all - - def get_Aop_Adiag (self, verbose=None, mo=None, ci=None, eris=None, - level_shift=None, d2f=None, **kwargs): - if verbose is None: verbose = self.verbose - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if eris is None and self.eris is None: - eris = self.eris = self.base.ao2mo (mo) - elif eris is None: - eris = self.eris - if d2f is None: d2f = self.base.diabatizer (ci=ci)[2] - ham_od = self.base.get_heff_offdiag () - ham_od += ham_od.T # This corresponds to the arbitrary newton_casscf*2 - fcasscf = self.make_fcasscf_sa () - hop, Adiag = newton_casscf.gen_g_hop (fcasscf, mo, ci, eris, - verbose)[2:] - ngorb, nci = self.ngorb, self.nci - # TODO: cacheing diab_response? or an x=0 branch? - def Aop (x): - x_v, x_is = x[:ngorb+nci], x[ngorb+nci:] - Ax_v = hop (x_v) + self.diab_response (x_is, mo=mo, ci=ci, - eris=eris) - x_c = self.unpack_uniq_var (x_v)[1] - Ax_is = np.dot (d2f, x_is) - Ax_o, Ax_c = self.unpack_uniq_var (Ax_v) - Ax_c, Ax_is2 = self._separate_is_component (Ax_c) - Ax_c_od = list (np.tensordot (-ham_od, np.stack (x_c, axis=0), - axes=1)) - Ax_c = [a1 + (w*a2) for a1, a2, w in zip (Ax_c, Ax_c_od, - self.base.weights)] - return self.pack_uniq_var (Ax_o, Ax_c, Ax_is) - return Aop, Adiag - - def get_lagrange_precond (self, Adiag, level_shift=None, ci=None, d2f=None, - **kwargs): - if level_shift is None: level_shift = self.level_shift - if ci is None: ci = self.base.ci - if d2f is None: d2f = self.base.diabatizer (ci=ci)[2] - return MSPDFTLagPrec (Adiag=Adiag, level_shift=level_shift, ci=ci, - d2f=d2f, grad_method=self) - - def get_ham_response (self, si_bra=None, si_ket=None, state=None, mo=None, - ci=None, si=None, eris=None, veff1=None, veff2=None, mf_grad=None, - atmlst=None, verbose=None, **kwargs): - '''write mspdft heff Hellmann-Feynman calculator; sum over - diagonal PDFT Hellmann-Feynman terms - ''' - if atmlst is None: atmlst = self.atmlst - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if si is None: si = self.base.si - if state is None: state = self.state - ket, bra = _unpack_state (state) - 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.get_rhf_base ().nuc_grad_method () - if verbose is None: verbose = self.verbose - si_diag = si_bra * si_ket - log = logger.new_logger (self, verbose) - - # Fix messed-up counting of the nuclear part - de_nuc = mf_grad.grad_nuc (self.mol, atmlst) - log.debug ('MS-PDFT gradient n-n terms:\n{}'.format (de_nuc)) - de = si_diag.sum () * de_nuc.copy () - - # Diagonal: PDFT component - for i, (amp, c, v1, v2) in enumerate (zip (si_diag, ci, veff1, veff2)): - if not amp: continue - de_i = mcpdft_grad.Gradients.get_ham_response (self, state=i, - mo=mo, ci=ci, veff1=v1, veff2=v2, eris=eris, mf_grad=mf_grad, - verbose=0, **kwargs) - de_nuc - log.debug ('MS-PDFT gradient int-state {} EPDFT terms:\n{}'.format - (i, de_i)) - log.debug ('Factor for these terms: {}'.format (amp)) - de += amp * de_i - log.debug ('MS-PDFT gradient diag H-F terms:\n{}'.format (de)) - - # Off-diagonal: heff component - de_o = mspdft_heff_HellmanFeynman (self, mo_coeff=mo, ci=ci, - si_bra=si_bra, si_ket=si_ket, eris=eris, state=state, - mf_grad=mf_grad, **kwargs) - log.debug ('MS-PDFT gradient offdiag H-F terms:\n{}'.format (de_o)) - de += de_o - - return de - - def get_LdotJnuc (self, Lvec, atmlst=None, verbose=None, mo=None, - ci=None, eris=None, mf_grad=None, d2f=None, **kwargs): - ''' Add the IS component ''' - if atmlst is None: atmlst = self.atmlst - if verbose is None: verbose = self.verbose - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if eris is None and self.eris is None: - eris = self.eris = self.base.ao2mo (mo) - elif eris is None: - eris = self.eris - - ngorb, nci = self.ngorb, self.nci - Lvec_v, Lvec_is = Lvec[:ngorb+nci], Lvec[ngorb+nci:] - - # Double-check Lvec_v sanity - Lvec_orb, Lvec_ci = self.unpack_uniq_var (Lvec_v) - Lvec_is2 = self._get_is_component (Lvec_ci, symm=0) - assert (np.amax (np.abs (Lvec_is2)) < 1e-8), '{} {}'.format (Lvec_is, - Lvec_is2) - - # Orbital and CI components - de_Lv = sacasscf_grad.Gradients.get_LdotJnuc (self, Lvec_v, - atmlst=atmlst, verbose=verbose, ci=ci, eris=eris, mf_grad=mf_grad, - **kwargs) - - # SI component - t0 = (logger.process_clock(), logger.perf_counter()) - de_Lis = self.diab_grad (Lvec_is, atmlst=atmlst, mf_grad=mf_grad, - eris=eris, mo=mo, ci=ci, **kwargs) - logger.info (self, - '--------------- %s gradient Lagrange IS response ---------------', - self.base.__class__.__name__) - if verbose >= logger.INFO: - rhf_grad._write(self, self.mol, de_Lis, atmlst) - logger.info (self, - '----------------------------------------------------------------') - t0 = logger.timer (self, '{} gradient Lagrange IS response'.format ( - self.base.__class__.__name__), *t0) - return de_Lv + de_Lis - - def get_lagrange_callback (self, Lvec_last, itvec, geff_op): - # TODO: state-average-mix - log = logger.new_logger (self, self.verbose) - if isinstance (self.base.fcisolver, CSFFCISolver): - transf = self.base.fcisolver.transformer - def _debug_csfs (xci, tag): - xci_csf = transf.vec_det2csf (xci, normalize=False) - xci_p = transf.vec_csf2det (xci_csf, normalize=False) - xci_bs_norm = linalg.norm (np.concatenate ( - [x.ravel () - y.ravel () for x, y in zip (xci_p, xci)])) - log.debug ('Broken-spin |{}| = {}'.format (tag, xci_bs_norm)) - else: - def _debug_csfs (xci, tag): - pass - def my_call (x): - itvec[0] += 1 - geff = geff_op (x) - deltax = x - Lvec_last - gorb, gci, gis = self.unpack_uniq_var (geff) - deltaorb, deltaci, deltais = self.unpack_uniq_var (deltax) - gci_norm = linalg.norm (np.asarray (gci).ravel ()) - deltaci_norm = linalg.norm (np.asarray (deltaci).ravel ()) - logger.info(self, ('Lagrange optimization iteration {}, |gorb| = ' - '{}, |gci| = {}, |gis| = {} |dLorb| = {}, |dLci| = {}, |dLis|' - ' = {}').format (itvec[0], linalg.norm (gorb), - gci_norm, linalg.norm (gis), linalg.norm (deltaorb), - deltaci_norm, linalg.norm (deltais))) - _debug_csfs (gci, 'gci') - _debug_csfs (deltaci, 'dLci') - Lvec_last[:] = x[:] - return my_call - - def debug_lagrange (self, Lvec, bvec, Aop, Adiag, state=None, mo=None, - ci=None, d2f=None, verbose=None, eris=None, **kwargs): - if state is None: state = self.state - if mo is None: mo = self.base.mo_coeff - if ci is None: ci = self.base.ci - if eris is None: eris = self.base.ao2mo (mo) - if verbose is None: verbose = self.verbose - nroots = self.nroots - log = logger.new_logger (self, verbose) - if isinstance (self.base.fcisolver, CSFFCISolver): - transf = self.base.fcisolver.transformer - def _debug_csfs (xci, label, normalize=False): - strs, vecs = transf.printable_largest_csf (np.asarray (xci), - 10, isdet=True, normalize=normalize, order='C') - log.debug ('Leading CSFs for %s', label) - for iroot in range (nroots): - log.debug (' Root %d', iroot) - for s, v in zip (strs[iroot], vecs[iroot]): - log.debug (' |%s>: %s', s, v) - else: - def _debug_csfs (*args, **kwargs): - pass - def _debug_cispace (xci, label): - log.debug ('%s', label) - xci_norm = [np.dot (c.ravel (), c.ravel ()) for c in xci] - try: - xci_ss = self.base.fcisolver.states_spin_square (xci, - self.base.ncas, self.base.nelecas)[0] - except AttributeError: - from pyscf.fci.direct_spin1 import _unpack_nelec, spin_square - nelec = sum (_unpack_nelec(self.base.nelecas)) - xci_ss = [spin_square (x, self.base.ncas, - ((nelec+m)//2,(nelec-m)//2))[0] - for x, m in zip (xci, self.spin_states)] - - xci_ss = [x / max (y, 1e-8) for x, y in zip (xci_ss, xci_norm)] - xci_multip = [np.sqrt (x+.25) - .5 for x in xci_ss] - xci_norm = np.sqrt (xci_norm) - for ix, (norm, ss, multip) in enumerate (zip (xci_norm, xci_ss, - xci_multip)): - log.debug ((' State {} norm = {:.7e} ; = {:.7f} ; 2S+1' - ' = {:.7f}').format (ix, norm, ss, multip)) - ovlp = np.zeros ((nroots, nroots), dtype=xci[0].dtype) - for i, j in product (range (nroots), repeat=2): - if self.spin_states[i] != self.spin_states[j]: continue - ovlp[i,j] = np.dot (xci[i].ravel (), ci[j].ravel ()) - log.debug (' Overlap matrix with CI array:') - fmt_str = ' ' + ' '.join (['{:8.1e}',]*nroots) - for row in ovlp: log.debug (fmt_str.format (*row)) - _debug_csfs (ci, 'CI vector', normalize=True) - borb, bci, bis = self.unpack_uniq_var (bvec) - log.debug ('Orbital rotation gradient (b) norm = {:.6e}'.format ( - linalg.norm (borb))) - _debug_cispace (bci, 'CI gradient (b)') - _debug_csfs (bci, 'CI gradient (b)') - Aorb, Aci = self.unpack_uniq_var (Adiag) - log.debug ('Orbital rotation Hessian (A) diagonal norm = {:.7e}'.format - (linalg.norm (Aorb))) - _debug_cispace (Aci, 'CI Hessian (A) diagonal') - _debug_csfs (Aci, 'CI Hessian (A) diagonal') - Lorb, Lci, Lis = self.unpack_uniq_var (Lvec) - log.debug ('Orbital rotation Lagrange vector (x) norm = {:.7e}'.format - (linalg.norm (Lorb))) - _debug_cispace (Lci, 'CI Lagrange (x) vector') - _debug_csfs (Lci, 'CI Lagrange (x) vector') - log.debug ('{} Constraint Jacobian (A):'.format (self.base.__class__.__name__)) - fmt = ' ' + ' '.join (['{:12.5e}' for i in range (self.nis)]) - for row in d2f: log.debug (fmt.format (*row)) - log.debug (' {:>12s} {:>12s}'.format ('Gradient (b)', 'Vector (x)')) - for g, v in zip (bis, Lis): - log.debug (' {:12.5e} {:12.5e}'.format (g, v)) - -class MSPDFTLagPrec (sacasscf_grad.SACASLagPrec): - ''' Solve IS part exactly, then do everything else the same ''' - - def __init__(self, Adiag=None, level_shift=None, ci=None, grad_method=None, - d2f=None, **kwargs): - sacasscf_grad.SACASLagPrec.__init__(self, Adiag=Adiag, - level_shift=level_shift, ci=ci, grad_method=grad_method) - self.grad_method = grad_method - self.sing_tol = getattr (grad_method.base, 'sing_tol_diabatize', - SING_TOL_DIABATIZE) - self.sing_step_tol = getattr (grad_method.base, 'sing_step_tol', - SING_STEP_TOL) - self.sing_warned = False - self.log = logger.new_logger (self.grad_method, - self.grad_method.verbose) - self._init_d2f (d2f=d2f, **kwargs) - self.verbose = self.grad_method.verbose - - def _init_d2f (self, d2f=None, **kwargs): - self.d2f=d2f - self.d2f_evals, self.d2f_evecs = linalg.eigh (d2f) - idx_sing = np.abs (self.d2f_evals) < self.sing_tol - self.log.debug ('IS component Hessian eigenvalues: {}'.format ( - self.d2f_evals)) - if np.any (idx_sing): self.do_sing_warn () - self.d2f_evals = self.d2f_evals[~idx_sing] - self.d2f_evecs = self.d2f_evecs[:,~idx_sing] - - def unpack_uniq_var (self, x): - return self.grad_method.unpack_uniq_var (x) - - def pack_uniq_var (self, x0, x1, x2=None): - return self.grad_method.pack_uniq_var (x0, x1, x2) - - def __call__(self, x): - xorb, xci, xis = self.unpack_uniq_var (x) - Mxorb = self.orb_prec (xorb) - Mxci = self.ci_prec (xci) - Mxis = self.is_prec (xis) - return self.pack_uniq_var (Mxorb, Mxci, Mxis) - - def is_prec (self, xis): - xis = np.dot (xis, self.d2f_evecs) - Mxis = xis/self.d2f_evals - idx_sing = (np.abs (Mxis) >= self.sing_step_tol) - if np.any (idx_sing): self.do_sing_warn () - Mxis[idx_sing] = 0 - Mxis = np.dot (self.d2f_evecs, Mxis) - return Mxis - - def do_sing_warn (self): - if self.sing_warned: return - self.log.warn ('Model-space frame-rotation Hessian is singular! ' - 'Response equations may not be solvable to arbitrary ' - 'precision!') - self.sing_warned = True - - - -if __name__ == '__main__': - # Test mspdft_heff_response and mspdft_heff_HellmannFeynman by trying to - # reproduce SA-CASSCF derivatives in an arbitrary basis - import math - from pyscf import scf, gto, mcscf - from pyscf.fci import csf_solver - xyz = '''O 0.00000000 0.08111156 0.00000000 - H 0.78620605 0.66349738 0.00000000 - H -0.78620605 0.66349738 0.00000000''' - mol = gto.M (atom=xyz, basis='6-31g', symmetry=False, output='mspdft.log', - verbose=lib.logger.DEBUG) - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'tPBE', 4, 4).set (fcisolver = csf_solver (mol, 1)) - mc = mc.multi_state ([1.0/3,]*3, 'cms').run () - mc_grad = Gradients (mc) - de = np.stack ([mc_grad.kernel (state=i) for i in range (3)], axis=0) - diff --git a/pyscf/grad/test/h2co_sa2_tpbe66_631g_grad_num.npy b/pyscf/grad/test/h2co_sa2_tpbe66_631g_grad_num.npy deleted file mode 100644 index e7667b9c174bca96945589b69d7663f413c2fbdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoB!tf`|=t3V#$YL61WU3YZ${*EcjRz~~Y+-LgCsVHgXF+1-oS)B#1 zSK3R-pPTq+$_+b~XD^jgUTm;4nZ$okK(J%KeyNXc`kQ_`zlox|^4|6A|3CGD(<_0F z{n}B*+U9b-c5V!lE;PUEvF{MxobxLE=zbf$H(NQTZn0}}$?so$YyQ3?dj%t|+&gNY zldNq~ud{kz#--!~@@IC~MU}QFMVyP>&wNTkMEd0gyO%HQCfUtv-fwY&aq8q_vHNW~ zg;RDtzF^nBT7Q-Myk`4gS@y?I-p<}%zuW1<*`#axwy`ajFR(st=My6ma`)v*d)*xl z)AmGOvwPkz9^IU`!OpQ!^JeeU_WcZ;)mNS#nqs$~N2#Pepm+bPSJ5A0fa0qc`Si7U zPquq1x9<0}fL?pCfThLPXB^!h|0`I>bJ|9`&VWCUxX#Vrx6@JmP8G*7dsTrbbGEo; z`xeh%+Q;*3o82qt=iRM;qW8P|p9|UW<$~RxS$7|OpWD1&x<#DJ_-pk3FMIT~JKkNe M(>J=$#s+jh0K5C$-v9sr diff --git a/pyscf/grad/test/h2co_tpbe66_631g_grad_num.npy b/pyscf/grad/test/h2co_tpbe66_631g_grad_num.npy deleted file mode 100644 index 9f977340dead6553b304792c87cd9bfa5dd963e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$7ItnH_3dWi`3bhL40j^(VpYI=^J7<6JCy#G#^Y_`c-gz^Tv1ivl$tIuT z0QuGS{gJI=EdMX>+rLBP;k}}T`%L!knfZP}=l+)yH?eU^OtDLe^Xt0LKVkn?1_LWD zpm^TbuajfH9o(0?@J{AAfeH4EX5M@GPR-fRI_2N}#$Vg_O@Dlj;ik*6eFtC5w;xwr zZ6CmKC;ZXcdv-z9OO#}OuijU(@b1^9%Fg}Cm8Q{oJ9pcKZnzl8YdvBAOfKVJ_i8%# Ve+spXOix*`PoQd_R-nxUdjO0nfq?)3 diff --git a/pyscf/grad/test/test_diatomic_gradients.py b/pyscf/grad/test/test_diatomic_gradients.py deleted file mode 100644 index e3636a0eb..000000000 --- a/pyscf/grad/test/test_diatomic_gradients.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/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. -# -from pyscf import gto, scf, df, dft -from pyscf.data.nist import BOHR -from pyscf import mcpdft -from pyscf.fci.addons import _unpack_nelec -import unittest - -def diatomic (atom1, atom2, r, fnal, basis, ncas, nelecas, nstates, - charge=None, spin=None, symmetry=False, cas_irrep=None, - density_fit=False, grids_level=9): - 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) - - if density_fit: - mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - - mc = mcpdft.CASSCF (mf.run (), fnal, ncas, nelecas, grids_level=grids_level) - #if spin is not None: smult = spin+1 - #else: smult = (mol.nelectron % 2) + 1 - #mc.fcisolver = csf_solver (mol, smult=smult) - neleca, nelecb = _unpack_nelec (nelecas, spin=spin) - spin = neleca-nelecb - ss=spin*(spin+2)*0.25 - mc = mc.multi_state ([1.0/float(nstates),]*nstates, 'cms') - mc.fix_spin_(ss=ss, shift=1) - mc.conv_tol = mc.conv_tol_diabatize = 1e-12 - mo = None - if symmetry and (cas_irrep is not None): - mo = mc.sort_mo_by_irrep (cas_irrep) - mc.kernel (mo) - return mc.nuc_grad_method () - -def setUpModule(): - global mols, diatomic, original_grids - mols = [] - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - -def tearDownModule(): - global mols, diatomic, original_grids - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - [m.stdout.close() for m in mols] - del diatomic, original_grids, mols - -# The purpose of these separate test functions is to narrow down an error to specific degrees of -# freedom. But if the lih_cms2ftpbe and _df cases pass, then almost certainly, they all pass. - -class KnownValues(unittest.TestCase): - - def test_grad_h2_cms3ftlda22_sto3g_slow (self): - # z_orb: no - # z_ci: no - # z_is: no - mc_grad = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', 'STO-3G', 2, 2, 3) - de_ref = [0.226842531, -0.100538192, -0.594129499] - # Numerical from this software - for i in range (3): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_grad_h2_cms2ftlda22_sto3g_slow (self): - # z_orb: no - # z_ci: yes - # z_is: no - mc_grad = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2) - de_ref = [0.125068648, -0.181916973] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 6) - - def test_grad_h2_cms3ftlda22_631g_slow (self): - # z_orb: yes - # z_ci: no - # z_is: no - mc_grad = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', '6-31G', 2, 2, 3) - de_ref = [0.1717391582, -0.05578044075, -0.418332932] - # Numerical from this software - for i in range (3): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_grad_h2_cms2ftlda22_631g_slow (self): - # z_orb: yes - # z_ci: yes - # z_is: no - mc_grad = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', '6-31G', 2, 2, 2) - de_ref = [0.1046653372, -0.07056592067] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_grad_lih_cms2ftlda44_sto3g_slow (self): - # z_orb: no - # z_ci: yes - # z_is: yes - mc_grad = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', 'STO-3G', 4, 4, 2, symmetry=True, cas_irrep={'A1': 4}) - de_ref = [0.0659740768, -0.005995224082] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 6) - - def test_grad_lih_cms3ftlda22_sto3g_slow (self): - # z_orb: yes - # z_ci: no - # z_is: yes - mc_grad = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 3) - de_ref = [0.09307779491, 0.07169985876, -0.08034177097] - # Numerical from this software - for i in range (3): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 6) - - def test_grad_lih_cms2ftlda22_sto3g_slow (self): - # z_orb: yes - # z_ci: yes - # z_is: yes - mc_grad = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2) - de_ref = [0.1071803011, 0.03972321867] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_grad_lih_cms2ftpbe22_sto3g (self): - # z_orb: yes - # z_ci: yes - # z_is: yes - mc_grad = diatomic ('Li', 'H', 2.5, 'ftPBE', 'STO-3G', 2, 2, 2) - de_ref = [0.10045064, 0.03648704] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_grad_lih_cms2tm06l22_sto3g (self): - # z_orb: yes - # z_ci: yes - # z_is: yes - mc_grad = diatomic ('Li', 'H', 0.8, 'tM06L', 'STO-3G', 2, 2, 2, grids_level=1) - de_ref = [-1.03428938, -0.88278628] - - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0]/BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - # MRH 05/05/2023: currently, the only other test which uses DF-MC-PDFT features is - # test_grad_h2co, which is slower than this. Therefore I am restoring it. - def test_grad_lih_cms2ftlda22_sto3g_df (self): - # z_orb: yes - # z_ci: yes - # z_is: yes - mc_grad = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2, density_fit=True) - de_ref = [0.1074553399, 0.03956955205] - # Numerical from this software - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref[i], 5) - - def test_rohf_sanity (self): - mc_grad = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', '6-31g', 4, 2, 2, symmetry=True, - cas_irrep={'A1': 4}, spin=2) - mc_grad_ref = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', '6-31g', 4, (2,0), 2, - symmetry=True, cas_irrep={'A1': 4}) - de_num_ref = [-0.039806,-0.024193] - # Numerical from this software - # PySCF commit: bee0ce288a655105e27fcb0293b203939b7aecc9 - # PySCF-forge commit: 50bc1da117ced9613948bee14a99a02c7b2c5769 - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_num_ref[i], 4) - de_ref = mc_grad_ref.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref, 6) - - def test_dfrohf_sanity (self): - mc_grad = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', '6-31g', 4, 2, 2, symmetry=True, - density_fit=True, cas_irrep={'A1': 4}, spin=2) - mc_grad_ref = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', '6-31g', 4, (2,0), 2, - symmetry=True, density_fit=True, cas_irrep={'A1': 4}) - de_num_ref = [-0.039721,-0.024139] - # Numerical from this software - # PySCF commit: bee0ce288a655105e27fcb0293b203939b7aecc9 - # PySCF-forge commit: 50bc1da117ced9613948bee14a99a02c7b2c5769 - for i in range (2): - with self.subTest (state=i): - de = mc_grad.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_num_ref[i], 4) - de_ref = mc_grad_ref.kernel (state=i) [1,0] / BOHR - self.assertAlmostEqual (de, de_ref, 6) - -if __name__ == "__main__": - print("Full Tests for CMS-PDFT gradients of diatomic molecules") - unittest.main() - - - - - - diff --git a/pyscf/grad/test/test_grad_cmspdft.py b/pyscf/grad/test/test_grad_cmspdft.py deleted file mode 100644 index 14d28f788..000000000 --- a/pyscf/grad/test/test_grad_cmspdft.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, df, mcscf, lib, fci -from pyscf.fci.addons import fix_spin_, initguess_triplet -from pyscf.data.nist import BOHR -from pyscf import mcpdft -#from pyscf.fci import csf_solver -from pyscf.grad.cmspdft import diab_response, diab_grad, diab_response_o0, diab_grad_o0 -from pyscf.grad import mspdft as mspdft_grad -#from pyscf.df.grad import dfsacasscf, dfmspdft -import unittest, math - -h2co_casscf66_631g_xyz = '''C 0.534004 0.000000 0.000000 -O -0.676110 0.000000 0.000000 -H 1.102430 0.000000 0.920125 -H 1.102430 0.000000 -0.920125''' -mol_nosymm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = False, output='/dev/null', verbose = 0) -mol_symm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = True, output='/dev/null', verbose = 0) -Lis = math.pi * (np.random.rand (1) - 0.5) -def get_mc_ref (mol, ri=False, sam=False): - mf = scf.RHF (mol) - if ri: mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - mc = mcscf.CASSCF (mf.run (), 6, 6) - mo = None - ci0 = None - if sam: - #fcisolvers = [csf_solver (mol, smult=((2*i)+1)) for i in (0,1)] - fcisolvers = [fci.solver (mol), fci.solver (mol)] - if mol.symmetry: - fcisolvers[0].wfnsym = 'A1' - fcisolvers[1].wfnsym = 'A2' - else: - h1, h0 = mc.get_h1cas () - h2 = mc.get_h2cas () - hdiag = mc.fcisolver.make_hdiag (h1, h2, 6, 6) - ci0 = [mc.fcisolver.get_init_guess (6, 6, 1, hdiag), - initguess_triplet (6, 6, '1011')] - mc = mcscf.addons.state_average_mix (mc, fcisolvers, [0.5,0.5]) - else: - #mc.fcisolver = csf_solver (mol, smult=1) - if mol.symmetry: - mc.fcisolver.wfnsym = 'A1' - mc = mc.state_average ([0.5,0.5]) - mc.fix_spin_(ss=0) - mc.conv_tol = 1e-12 - return mc.run (mo, ci0) -#mc_list = [[[get_mc_ref (m, ri=i, sam=j) for i in (0,1)] for j in (0,1)] for m in (mol_nosymm, mol_symm)] -mc_list = [] # Crunch within unittest.main for accurate clock -def get_mc_list (): - if len (mc_list) == 0: - for m in [mol_nosymm, mol_symm]: - mc_list.append ([[get_mc_ref (m, ri=i, sam=j) for i in (0,1)] for j in (0,1)]) - return mc_list - -def tearDownModule(): - global mol_nosymm, mol_symm, mc_list, Lis, get_mc_list - mol_nosymm.stdout.close () - mol_symm.stdout.close () - del mol_nosymm, mol_symm, mc_list, Lis, get_mc_list - -class KnownValues(unittest.TestCase): - - def test_diab_response_sanity (self): - for mcs, stype in zip (get_mc_list (), ('nosymm','symm')): - for mca, atype in zip (mcs, ('nomix','mix')): - if atype == 'mix': continue # TODO: enable state-average-mix - for mc, itype in zip (mca, ('conv', 'DF')): - ci_arr = np.asarray (mc.ci) - if itype == 'conv': mc_grad = mc.nuc_grad_method () - else: continue #mc_grad = dfsacasscf.Gradients (mc) - eris = mc.ao2mo (mc.mo_coeff) - with self.subTest (symm=stype, solver=atype, eri=itype, check='energy convergence'): - self.assertTrue (mc.converged) - def _crunch (fn): - dw = fn (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, eris=eris) - dworb, dwci = mc_grad.unpack_uniq_var (dw) - return dworb, dwci - with self.subTest (symm=stype, solver=atype, eri=itype): - dworb_test, dwci_test = _crunch (diab_response) - dworb_ref, dwci_ref = _crunch (diab_response_o0) - with self.subTest (symm=stype, solver=atype, eri=itype, check='orb'): - self.assertAlmostEqual (lib.fp (dworb_test), lib.fp (dworb_ref), 8) - with self.subTest (symm=stype, solver=atype, eri=itype, check='CI'): - self.assertAlmostEqual (lib.fp (dwci_test), lib.fp (dwci_ref), 8) - - def test_diab_grad_sanity (self): - for mcs, stype in zip (get_mc_list (), ('nosymm','symm')): - for mca, atype in zip (mcs, ('nomix','mix')): - for mc, itype in zip (mca, ('conv', 'DF')): - ci_arr = np.asarray (mc.ci) - if itype == 'conv': mc_grad = mc.nuc_grad_method () - else: continue #mc_grad = dfsacasscf.Gradients (mc) - # TODO: proper DF functionality - eris = mc.ao2mo (mc.mo_coeff) - mf_grad = mc._scf.nuc_grad_method () - with self.subTest (symm=stype, solver=atype, eri=itype): - dh_test = diab_grad (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, eris=eris, mf_grad=mf_grad) - dh_ref = diab_grad_o0 (mc_grad, Lis, mo=mc.mo_coeff, ci=mc.ci, eris=eris, mf_grad=mf_grad) - self.assertAlmostEqual (lib.fp (dh_test), lib.fp (dh_ref), 8) - -if __name__ == "__main__": - print("Full Tests for CMS-PDFT gradient objective fn derivatives") - unittest.main() - - - - - - diff --git a/pyscf/grad/test/test_grad_h2co.py b/pyscf/grad/test/test_grad_h2co.py deleted file mode 100644 index f88b167c6..000000000 --- a/pyscf/grad/test/test_grad_h2co.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/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 os -import numpy as np -from scipy import linalg -from pyscf import gto, scf, df, dft, fci, lib -from pyscf.fci.addons import fix_spin_ -from pyscf import mcpdft -#from pyscf.fci import csf_solver -import unittest - -h2co_casscf66_631g_xyz = '''C 0.534004 0.000000 0.000000 -O -0.676110 0.000000 0.000000 -H 1.102430 0.000000 0.920125 -H 1.102430 0.000000 -0.920125''' -mol_nosymm = gto.M (atom = h2co_casscf66_631g_xyz, basis = 'sto-3g', symmetry = False, output='/dev/null', verbose = 0) -mol_symm = gto.M (atom = h2co_casscf66_631g_xyz, basis = 'sto-3g', symmetry = True, output='/dev/null', verbose = 0) -def get_mc_ref (mol, ri=False, sa2=False, mo0=None): - mf = scf.RHF (mol) - if ri: mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - mc = mcpdft.CASSCF (mf.run (), 'tPBE', 2, 2, grids_level=1) - if sa2: - #fcisolvers = [csf_solver (mol, smult=((2*i)+1)) for i in (0,1)] - fcisolvers = [fix_spin_(fci.solver (mol), ss=0), - fix_spin_(fci.solver (mol).set (spin=2), ss=2)] - if mol.symmetry: - fcisolvers[0].wfnsym = 'A1' - fcisolvers[1].wfnsym = 'A2' - mc = mc.state_average_mix_(fcisolvers, [0.5,0.5]) - return mc.run (mo0) - -def setUpModule(): - global mol_nosymm, mol_symm, mo0, original_grids - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - mc_symm = get_mc_ref (mol_symm) - mo0 = mc_symm.mo_coeff.copy () - del mc_symm - -def tearDownModule(): - global mol_nosymm, mol_symm, mo0, original_grids - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - mol_nosymm.stdout.close () - mol_symm.stdout.close () - del mol_nosymm, mol_symm, mo0, original_grids - -class KnownValues(unittest.TestCase): - - def test_ss (self): - ref_nosymm = [-0.14738492029847025, -0.14788287172179898] - ref_symm = [-0.14738492029577735, -0.14788570886155353] - for mol, ref in zip ((mol_nosymm, mol_symm), (ref_nosymm, ref_symm)): - ref_conv, ref_df = ref - mc_conv = get_mc_ref (mol, ri=False, sa2=False, mo0=mo0) - mc_conv_grad = mc_conv.nuc_grad_method () - mc_df = get_mc_ref (mol, ri=True, sa2=False, mo0=mo0) - mc_df_grad = mc_df.nuc_grad_method () - for lbl, mc_grad, ref in (('conv', mc_conv_grad, ref_conv), ('DF', mc_df_grad, ref_df)): - with self.subTest (symm=mol.symmetry, eri=lbl): - test = mc_grad.kernel () - self.assertAlmostEqual (lib.fp (test), ref, 4) - - def test_sa (self): - ref_nosymm = [-0.5126958322662911, -0.5156542636903004] - ref_symm = [-0.512695920915627, -0.5156482433418408] - for mol, ref in zip ((mol_nosymm, mol_symm), (ref_nosymm, ref_symm)): - ref_conv, ref_df = ref - mc_conv = get_mc_ref (mol, ri=False, sa2=True, mo0=mo0) - mc_conv_grad = mc_conv.nuc_grad_method () - mc_df = get_mc_ref (mol, ri=True, sa2=True, mo0=mo0) - mc_df_grad = mc_df.nuc_grad_method () - for lbl, mc_grad, ref in (('conv', mc_conv_grad, ref_conv), ('DF', mc_df_grad, ref_df)): - with self.subTest (symm=mol.symmetry, eri=lbl): - test = np.stack ((mc_grad.kernel (state=0), - mc_grad.kernel (state=1)), axis=0) - self.assertAlmostEqual (lib.fp (test), ref, 4) - -if __name__ == "__main__": - print("Full Tests for MC-PDFT gradients of H2CO molecule") - unittest.main() - - - - - - diff --git a/pyscf/grad/test/test_grad_h2co_slow.py b/pyscf/grad/test/test_grad_h2co_slow.py deleted file mode 100644 index e2371f864..000000000 --- a/pyscf/grad/test/test_grad_h2co_slow.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/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 os -import numpy as np -from scipy import linalg -from pyscf import gto, scf, df, fci -from pyscf.fci.addons import fix_spin_ -from pyscf import mcpdft -#from pyscf.fci import csf_solver -import unittest -topdir = os.path.abspath (os.path.join (__file__, '..')) - -h2co_casscf66_631g_xyz = '''C 0.534004 0.000000 0.000000 -O -0.676110 0.000000 0.000000 -H 1.102430 0.000000 0.920125 -H 1.102430 0.000000 -0.920125''' -mol_nosymm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = False, output='/dev/null', verbose = 0) -mol_symm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = True, output='/dev/null', verbose = 0) -def get_mc_ref (mol, ri=False, sa2=False): - mf = scf.RHF (mol) - if ri: mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - mc = mcpdft.CASSCF (mf.run (), 'tPBE', 6, 6, grids_level=6) - if sa2: - #fcisolvers = [csf_solver (mol, smult=((2*i)+1)) for i in (0,1)] - fcisolvers = [fix_spin_(fci.solver (mol), ss=0), - fix_spin_(fci.solver (mol).set (spin=2), ss=2)] - if mol.symmetry: - fcisolvers[0].wfnsym = 'A1' - fcisolvers[1].wfnsym = 'A2' - mc = mc.state_average_mix_(fcisolvers, [0.5,0.5]) - ref = np.load (os.path.join (topdir, 'h2co_sa2_tpbe66_631g_grad_num.npy')) - ref = ref.reshape (2,2,4,3)[int(ri)] - else: - ref = np.load (os.path.join (topdir, 'h2co_tpbe66_631g_grad_num.npy'))[int(ri)] - return mc.run (), ref - -def tearDownModule(): - global mol_nosymm, mol_symm - mol_nosymm.stdout.close () - mol_symm.stdout.close () - del mol_nosymm, mol_symm - -class KnownValues(unittest.TestCase): - - def test_ss (self): - for mol in (mol_nosymm, mol_symm): - mc_conv, ref_conv = get_mc_ref (mol, ri=False, sa2=False) - mc_conv_grad = mc_conv.nuc_grad_method () - mc_df, ref_df = get_mc_ref (mol, ri=True, sa2=False) - mc_df_grad = mc_df.nuc_grad_method () - for lbl, mc_grad, ref in (('conv', mc_conv_grad, ref_conv), ('DF', mc_df_grad, ref_df)): - if lbl=="DF": continue #TODO: DF support - with self.subTest (symm=mol.symmetry, eri=lbl): - test = mc_grad.kernel () - self.assertLessEqual (linalg.norm (test-ref), 1e-4) - - def test_sa (self): - for mol in (mol_nosymm, mol_symm): - mc_conv, ref_conv = get_mc_ref (mol, ri=False, sa2=True) - mc_conv_grad = mc_conv.nuc_grad_method () - mc_df, ref_df = get_mc_ref (mol, ri=True, sa2=True) - mc_df_grad = mc_df.nuc_grad_method () - for lbl, mc_grad, ref in (('conv', mc_conv_grad, ref_conv), ('DF', mc_df_grad, ref_df)): - if lbl=="DF": continue #TODO: DF support - with self.subTest (symm=mol.symmetry, eri=lbl): - test = mc_grad.kernel (state=0) - self.assertLessEqual (linalg.norm (test-ref[0]), 1e-4) - test = mc_grad.kernel (state=1) - self.assertLessEqual (linalg.norm (test-ref[1]), 1e-4) - - -if __name__ == "__main__": - print("Full Tests for MC-PDFT gradients of H2CO molecule") - unittest.main() - - - - - - diff --git a/pyscf/grad/test/test_grad_lpdft.py b/pyscf/grad/test/test_grad_lpdft.py deleted file mode 100644 index 0ced79fe0..000000000 --- a/pyscf/grad/test/test_grad_lpdft.py +++ /dev/null @@ -1,313 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -# The following tests are broken down into a couple of different categories. -# 1. Check accuracy of analytical gradients to numerical gradients for different Lagrange multiplier situations. Some -# tests are redundant since as long as the tests with both MO and CI Lagrange multipliers pass, then everything -# should be good. All other tests are marked as slow and used for thorough debugging. -# 2. Check API as scanner object. -# 3. L-PDFT gradients for multi_state_mix type objects. - -import unittest - -from pyscf import scf, gto, df, dft, lib -from pyscf import mcpdft -from pyscf.fci.addons import _unpack_nelec - -def diatomic( - atom1, - atom2, - r, - fnal, - basis, - ncas, - nelecas, - nstates, - charge=None, - spin=None, - symmetry=False, - cas_irrep=None, - density_fit=False, - grids_level=9, -): - """Used for checking diatomic systems to see if the Lagrange Multipliers are working properly.""" - 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) - if density_fit: - mf = mf.density_fit(auxbasis=df.aug_etb(mol)) - - mc = mcpdft.CASSCF(mf.run(), fnal, ncas, nelecas, grids_level=grids_level) - neleca, nelecb = _unpack_nelec (nelecas, spin=spin) - spin = neleca-nelecb - - ss = spin * (spin + 2) * 0.25 - mc = mc.multi_state( - [ - 1.0 / float(nstates), - ] - * nstates, - "lin", - ) - mc.fix_spin_(ss=ss, shift=2) - mc.conv_tol = 1e-12 - mc.conv_grad_tol = 1e-6 - mo = None - if symmetry and (cas_irrep is not None): - mo = mc.sort_mo_by_irrep(cas_irrep) - - mc_grad = mc.run(mo).nuc_grad_method() - mc_grad.conv_rtol = 1e-12 - return mc_grad - - -def setUpModule(): - global mols, original_grids - mols = [] - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - -def tearDownModule(): - global mols, diatomic, original_grids - [m.stdout.close() for m in mols] - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - del mols, diatomic, original_grids - - -class KnownValues(unittest.TestCase): - - def test_grad_hhe_lin3ftlda22_631g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: no - """ - n_states = 3 - mc_grad = diatomic( - "He", "H", 1.4, "ftLDA,VWN3", "6-31G", 2, 2, n_states, charge=1 - ) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [-0.0744181053, -0.0840211222, -0.0936241392] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 7) - - def test_grad_hhe_lin2ftlda24_631g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: no - ci: yes - """ - n_states = 2 - mc_grad = diatomic( - "He", "H", 1.4, "ftLDA,VWN3", "6-31G", 4, 2, n_states, charge=1 - ) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [0.0025153073, -0.1444551635] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 7) - - def test_grad_hhe_lin2ftlda22_631g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: yes - """ - n_states = 2 - # The L-PDFT ground state is flat at 1.4, so shift it slightly - mc_grad = diatomic( - "He", "H", 1.2, "ftLDA,VWN3", "6-31G", 2, 2, n_states, charge=1 - ) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [0.012903562, -0.239149778] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 5) - - def test_grad_lih_lin3ftlda22_sto3g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: no - """ - n_states = 3 - mc_grad = diatomic("Li", "H", 1.4, "ftLDA,VWN3", "STO-3G", 2, 2, n_states) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [-0.0269959347, -0.052808735, -0.0785029927] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 6) - - def test_grad_lih_lin2ftlda46_sto3g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: no - ci: yes - """ - n_states = 2 - mc_grad = diatomic("Li", "H", 1.4, "ftLDA,VWN3", "STO-3G", 6, 4, n_states) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [-0.0289711885, -0.0525535764] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 7) - - def test_grad_lih_lin2ftlda22_sto3g_slow(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: yes - """ - n_states = 2 - mc_grad = diatomic("Li", "H", 1.4, "ftLDA,VWN3", "STO-3G", 2, 2, n_states) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [-0.0302731558, -0.0528615182] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 5) - - def test_grad_lih_lin2ftpbe22_sto3g(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: yes - """ - n_states = 2 - mc_grad = diatomic("Li", "H", 1.4, "ftpbe", "STO-3G", 2, 2, n_states) - - # Numerical from this software - # PySCF commit: 6c1ea86eb60b9527d6731efa65ef99a66b8f84d2 - # PySCF-forge commit: ea0a4c164de21e84eeb30007afcb45344cfc04ff - NUM_REF = [-0.0318512447, -0.0544779213] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 5) - - def test_grad_scanner(self): - # Tests API and Scanner capabilities - n_states = 2 - mc_grad1 = diatomic( - "Li", "H", 1.5, "ftLDA,VWN3", "STO-3G", 2, 2, n_states, grids_level=1 - ) - mol1 = mc_grad1.base.mol - mc_grad2 = diatomic( - "Li", "H", 1.6, "ftLDA,VWN3", "STO-3G", 2, 2, n_states, grids_level=1 - ).as_scanner() - - for state in range(n_states): - with self.subTest(state=state): - de1 = mc_grad1.kernel(state=state) - e1 = mc_grad1.base.e_states[state] - e2, de2 = mc_grad2(mol1, state=state) - self.assertTrue(mc_grad1.converged) - self.assertTrue(mc_grad2.converged) - self.assertAlmostEqual(e1, e2, 6) - self.assertAlmostEqual(lib.fp(de1), lib.fp(de2), 6) - - def test_grad_lih_lin2ftpbe22_sto3g_df(self): - """System has the following Lagrange multiplier sectors: - orb: yes - ci: yes - """ - n_states = 2 - mc_grad = diatomic( - "Li", "H", 1.4, "ftpbe", "STO-3G", 2, 2, n_states, density_fit=True - ) - - # Numerical from this software - # PySCF commit: eafc3575234aca3832d270f4e1193bec2119d2b4 - # PySCF-forge commit: 2e1596dc919783c751c3cf24bb776e4efcb51a34 - NUM_REF = [-0.03209560095886714, -0.05453488593608611] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 5) - - def test_rohf_sanity (self): - n_states = 3 - mc_grad = diatomic( - "Li", "H", 1.4, "ftpbe", "6-31g", 4, 2, n_states, density_fit=False, spin=2 - ) - mc_grad_ref = diatomic( - "Li", "H", 1.4, "ftpbe", "6-31g", 4, (2,0), n_states, density_fit=False - ) - - # Numerical from this software - # PySCF commit: bee0ce288a655105e27fcb0293b203939b7aecc9 - # PySCF-forge commit: 50bc1da117ced9613948bee14a99a02c7b2c5769 - NUM_REF = [-0.062533, -0.058472, -0.058472] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 4) - de_ref = mc_grad_ref.kernel(state=i)[1, 0] - self.assertAlmostEqual (de, de_ref, 6) - - def test_dfrohf_sanity (self): - n_states = 3 - mc_grad = diatomic( - "Li", "H", 1.4, "ftpbe", "6-31g", 4, 2, n_states, density_fit=True, spin=2 - ) - mc_grad_ref = diatomic( - "Li", "H", 1.4, "ftpbe", "6-31g", 4, (2,0), n_states, density_fit=True, - ) - - # Numerical from this software - # PySCF commit: bee0ce288a655105e27fcb0293b203939b7aecc9 - # PySCF-forge commit: 50bc1da117ced9613948bee14a99a02c7b2c5769 - NUM_REF = [-0.062548, -0.058501, -0.058501] - for i in range(n_states): - with self.subTest(state=i): - de = mc_grad.kernel(state=i)[1, 0] - self.assertAlmostEqual(de, NUM_REF[i], 4) - de_ref = mc_grad_ref.kernel(state=i)[1, 0] - self.assertAlmostEqual (de, de_ref, 6) - - - -if __name__ == "__main__": - print("Full Tests for L-PDFT gradients API") - unittest.main() diff --git a/pyscf/grad/test/test_grad_mcpdft.py b/pyscf/grad/test/test_grad_mcpdft.py deleted file mode 100644 index af65a7ea3..000000000 --- a/pyscf/grad/test/test_grad_mcpdft.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/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. -# -# Test API: -# 0. Initialize from mol, mf, and mc (done) -# 1. kernel (done) -# 2. optimize_mcscf_ (done) -# 3. compute_pdft_ (done) -# 4. energy_tot (done) -# 5. get_energy_decomposition (done) -# 6. checkpoint stuff -# 7. get_pdft_veff (maybe this elsewhere?) -# In the context of: -# 1. CASSCF, CASCI -# 2. Symmetry, with and without -# 3. State average, state average mix w/ different spin states - -# Some assertAlmostTrue thresholds are loose because we are only -# trying to test the API here; we need tight convergence and grids -# to reproduce well when OMP is on. -import numpy as np -from pyscf import gto, scf, mcscf, lib, fci, dft -from pyscf import mcpdft -import unittest - - -mol_nosym = mol_sym = mf_nosym = mf_sym = mc_nosym = mc_sym = mcp = None -def auto_setup (xyz='Li 0 0 0\nH 1.5 0 0'): - mol_nosym = gto.M (atom = xyz, basis = 'sto3g', - output = '/dev/null', verbose = 0) - mol_sym = gto.M (atom = xyz, basis = 'sto3g', symmetry=True, - output = '/dev/null', verbose = 0) - mf_nosym = scf.RHF (mol_nosym).run () - mc_nosym = mcscf.CASSCF (mf_nosym, 5, 2).run () - mf_sym = scf.RHF (mol_sym).run () - mc_sym = mcscf.CASSCF (mf_sym, 5, 2).run () - mcp_ss_nosym = mcpdft.CASSCF (mc_nosym, 'ftLDA,VWN3', 5, 2, - grids_level=1).run () - mcp_ss_sym = mcpdft.CASSCF (mc_sym, 'ftLDA,VWN3', 5, 2, - grids_level=1).run () - mcp_sa_0 = mcp_ss_nosym.state_average ([1.0/5,]*5).run () - solver_S = fci.solver (mol_nosym, singlet=True).set (spin=0, nroots=2) - solver_T = fci.solver (mol_nosym, singlet=False).set (spin=2, nroots=3) - mcp_sa_1 = mcp_ss_nosym.state_average_mix ( - [solver_S,solver_T], [1.0/5,]*5).set(ci=None).run () - solver_A1 = fci.solver (mol_sym).set (wfnsym='A1', nroots=3) - solver_E1x = fci.solver (mol_sym).set (wfnsym='E1x', nroots=1, spin=2) - solver_E1y = fci.solver (mol_sym).set (wfnsym='E1y', nroots=1, spin=2) - mcp_sa_2 = mcp_ss_sym.state_average_mix ( - [solver_A1,solver_E1x,solver_E1y], [1.0/5,]*5).set(ci=None).run () - mcp = [[mcp_ss_nosym, mcp_ss_sym], [mcp_sa_0, mcp_sa_1, mcp_sa_2]] - nosym = [mol_nosym, mf_nosym, mc_nosym] - sym = [mol_sym, mf_sym, mc_sym] - return nosym, sym, mcp - -def setUpModule(): - global mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, original_grids - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - nosym, sym, mcp = auto_setup () - mol_nosym, mf_nosym, mc_nosym = nosym - mol_sym, mf_sym, mc_sym = sym - -def tearDownModule(): - global mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, original_grids - mol_nosym.stdout.close () - mol_sym.stdout.close () - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - del mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, original_grids - -class KnownValues(unittest.TestCase): - - def test_scanner (self): - mcp1 = auto_setup (xyz='Li 0 0 0\nH 1.55 0 0')[-1] - for mol0, mc0, mc1 in zip ([mol_nosym, mol_sym], mcp[0], mcp1[0]): - mc0_grad = mc0.nuc_grad_method () - mc1_gradscanner = mc1.nuc_grad_method ().as_scanner () - de0 = lib.fp (mc0_grad.kernel ()) - e1, de1 = mc1_gradscanner (mol0) - de1 = lib.fp (de1) - with self.subTest (case='SS', symm=mol0.symmetry): - self.assertTrue(mc0_grad.converged) - self.assertTrue(mc1_gradscanner.converged) - self.assertAlmostEqual (de0, de1, delta=1e-6) - for ix, (mc0, mc1) in enumerate (zip (mcp[1], mcp1[1])): - tms = (0,1,'mixed')[ix] - sym = bool (ix//2) - mol0 = [mol_nosym, mol_sym][int(sym)] - mc0_grad = mc0.nuc_grad_method () - mc1_gradscanner = mc1.nuc_grad_method ().as_scanner () - for state in range (5): - with self.subTest (case='SA', state=state, symm=mol0.symmetry, triplet_ms=tms): - de0 = lib.fp (mc0_grad.kernel (state=state)) - e1, de1 = mc1_gradscanner (mol0, state=state) - de1 = lib.fp (de1) - self.assertTrue(mc0_grad.converged) - self.assertTrue(mc1_gradscanner.converged) - self.assertAlmostEqual (de0, de1, delta=1e-5) - - def test_gradients (self): - ref_ss = 5.29903936e-03 - ref_sa = [5.66392595e-03,3.67724051e-02,3.62698260e-02,2.53851408e-02,2.53848341e-02] - # Source: numerical @ this program - for mc, symm in zip (mcp[0], (False, True)): - with self.subTest (case='SS', symmetry=symm): - de = mc.nuc_grad_method ().kernel ()[0,0] - self.assertAlmostEqual (de, ref_ss, 6) - for ix, mc in enumerate (mcp[1]): - tms = (0,1,'mixed')[ix] - sym = bool (ix//2) - mc_grad = mc.nuc_grad_method () - for state in range (5): - with self.subTest (case='SA', state=state, symmetry=sym, triplet_ms=tms): - i = np.argsort (mc.e_states)[state] - de = mc_grad.kernel (state=i)[0,0] - self.assertAlmostEqual (de, ref_sa[state], 5) - - def test_triplet_mol (self): - '''Check that energies & gradients do not depend on if the parent MF is RHF or ROHF''' - mc = mcpdft.CASSCF (mf_nosym, 'ftLDA,VWN3', 5, (2,0), - grids_level=1).run (mo_coeff=mcp[1][0].mo_coeff.copy ()) - mc_grad = mc.nuc_grad_method () - e_ref = mc.e_tot - de_ref = mc_grad.kernel () - self.assertTrue (mc.converged) - self.assertTrue (mc_grad.converged) - mo_coeff = mc.mo_coeff.copy () - mol = mol_nosym.copy () - mol.spin = 2 - mol.build () - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 5, (2,0), - grids_level=1).run (mo_coeff=mo_coeff) - self.assertTrue (mc.converged) - self.assertAlmostEqual (mc.e_tot, e_ref, 6) - mc_grad = mc.nuc_grad_method () - self.assertAlmostEqual (lib.fp (mc_grad.kernel ()), lib.fp (de_ref), 6) - self.assertTrue (mc_grad.converged) - - def test_triplet_mol_df (self): - '''Check that energies & gradients do not depend on if the parent MF is RHF or ROHF - Density fitting can cause a weird interaction.''' - mc = mcpdft.CASSCF (mf_nosym.density_fit ().run (), 'ftLDA,VWN3', 5, (2,0), - grids_level=1).run (mo_coeff=mcp[1][0].mo_coeff.copy ()) - mc_grad = mc.nuc_grad_method () - e_ref = mc.e_tot - de_ref = mc_grad.kernel () - self.assertTrue (mc.converged) - self.assertTrue (mc_grad.converged) - mo_coeff = mc.mo_coeff.copy () - mol = mol_nosym.copy () - mol.spin = 2 - mol.build () - mf = scf.RHF (mol).density_fit ().run () - mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 5, (2,0), - grids_level=1).run (mo_coeff=mo_coeff) - self.assertTrue (mc.converged) - self.assertAlmostEqual (mc.e_tot, e_ref, 6) - mc_grad = mc.nuc_grad_method () - self.assertAlmostEqual (lib.fp (mc_grad.kernel ()), lib.fp (de_ref), 6) - self.assertTrue (mc_grad.converged) - -if __name__ == "__main__": - print("Full Tests for MC-PDFT gradients API") - unittest.main() - - diff --git a/pyscf/grad/test/test_grad_metagga_mcpdft.py b/pyscf/grad/test/test_grad_metagga_mcpdft.py deleted file mode 100644 index 945ab5eb3..000000000 --- a/pyscf/grad/test/test_grad_metagga_mcpdft.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2025 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: Matthew Hennefarth - -import unittest - -from pyscf import scf, gto, df, dft -from pyscf.data.nist import BOHR -from pyscf import mcpdft - -def diatomic( - atom1, - atom2, - r, - fnal, - basis, - ncas, - nelecas, - nstates, - charge=None, - spin=None, - symmetry=False, - cas_irrep=None, - density_fit=False, - grids_level=9, -): - """Used for checking diatomic systems to see if the Lagrange Multipliers are working properly.""" - 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) - if density_fit: - mf = mf.density_fit(auxbasis=df.aug_etb(mol)) - - mc = mcpdft.CASSCF(mf.run(), fnal, ncas, nelecas, grids_level=grids_level) - if spin is None: - spin = mol.nelectron % 2 - - ss = spin * (spin + 2) * 0.25 - mc.fix_spin_(ss=ss, shift=2) - - if nstates > 1: - mc = mc.state_average( - [ - 1.0 / float(nstates), - ] - * nstates, - ) - - mc.conv_tol = 1e-12 - mc.conv_grad_tol = 1e-6 - mo = None - if symmetry and (cas_irrep is not None): - mo = mc.sort_mo_by_irrep(cas_irrep) - - mc_grad = mc.run(mo).nuc_grad_method() - mc_grad.conv_rtol = 1e-12 - return mc_grad - - -def setUpModule(): - global mols, original_grids - mols = [] - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - - -def tearDownModule(): - global mols, diatomic, original_grids - [m.stdout.close() for m in mols] - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - del mols, diatomic, original_grids - - -class KnownValues(unittest.TestCase): - - def test_grad_lih_sstm06l22_sto3g(self): - mc = diatomic("Li", "H", 0.8, "tM06L", "STO-3G", 2, 2, 1, grids_level=1) - de = mc.kernel()[1, 0] / BOHR - - # Numerical from this software - # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 - # PySCF-forge commit: 4015363355dc691a80bc94d4b2b094318b213e36 - DE_REF = -1.0546009263404388 - - self.assertAlmostEqual(de, DE_REF, 5) - - def test_grad_lih_sa2tm06l22_sto3g(self): - mc = diatomic("Li", "H", 0.8, "tM06L", "STO-3G", 2, 2, 2, grids_level=1) - - # Numerical from this software - # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 - # PySCF-forge commit: 4015363355dc691a80bc94d4b2b094318b213e36 - DE_REF = [-1.0351271000, -0.8919881992] - - for state in range(2): - with self.subTest(state=state): - de = mc.kernel(state=state)[1, 0] / BOHR - self.assertAlmostEqual(de, DE_REF[state], 5) - - def test_grad_lih_ssmc2322_sto3g(self): - mc = diatomic("Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 1, grids_level=1) - de = mc.kernel()[1, 0] / BOHR - - # Numerical from this software - # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 - # PySCF-forge commit: e82ba940654cd0b91f799e889136a316fda34b10 - DE_REF = -1.0641645070 - - self.assertAlmostEqual(de, DE_REF, 5) - - def test_grad_lih_sa2mc2322_sto3g(self): - mc = diatomic("Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 2, grids_level=1) - - # Numerical from this software - # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 - # PySCF-forge commit: e82ba940654cd0b91f799e889136a316fda34b10 - DE_REF = [-1.0510225010, -0.8963063432] - - for state in range(2): - with self.subTest(state=state): - de = mc.kernel(state=state)[1, 0] / BOHR - self.assertAlmostEqual(de, DE_REF[state], 5) - - def test_grad_lih_sa2mc2322_sto3g_df(self): - mc = diatomic( - "Li", "H", 0.8, "MC23", "STO-3G", 2, 2, 2, grids_level=1, density_fit=df - ) - - # Numerical from this software - # PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96 - # PySCF-forge commit: ee6ac742fbc79d170bc4b63ef2b2c4b49478c53a - DE_REF = [-1.0510303416, -0.8963992331] - - for state in range(2): - with self.subTest(state=state): - de = mc.kernel(state=state)[1, 0] / BOHR - self.assertAlmostEqual(de, DE_REF[state], 5) - - -if __name__ == "__main__": - print("Full Tests for MC-PDFT gradients with meta-GGA functionals") - unittest.main() diff --git a/pyscf/grad/test/test_grad_mspdft.py b/pyscf/grad/test/test_grad_mspdft.py deleted file mode 100644 index 941e53a8f..000000000 --- a/pyscf/grad/test/test_grad_mspdft.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, df, mcscf, lib, fci -from pyscf.fci.addons import fix_spin_, initguess_triplet -from pyscf import mcpdft -#from pyscf.fci import csf_solver -from pyscf.grad.mspdft import mspdft_heff_response, mspdft_heff_HellmanFeynman -#from pyscf.df.grad import dfsacasscf -import unittest, math - -h2co_casscf66_631g_xyz = '''C 0.534004 0.000000 0.000000 -O -0.676110 0.000000 0.000000 -H 1.102430 0.000000 0.920125 -H 1.102430 0.000000 -0.920125''' -mol_nosymm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = False, output='/dev/null', verbose = 0) -mol_symm = gto.M (atom = h2co_casscf66_631g_xyz, basis = '6-31g', symmetry = True, output='/dev/null', verbose = 0) -def random_si (): - phi = math.pi * (np.random.rand (1)[0] - 0.5) - cp, sp = math.cos (phi), math.sin (phi) - si = np.array ([[cp,-sp],[sp,cp]]) - return si -si = random_si () -def get_mc_ref (mol, ri=False, sam=False): - mf = scf.RHF (mol) - if ri: mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - mc = mcscf.CASSCF (mf.run (), 6, 6) - mo = None - ci0 = None - if sam: - fcisolvers = [fci.solver (mol), fci.solver (mol)] - if mol.symmetry: - fcisolvers[0].wfnsym = 'A1' - fcisolvers[1].wfnsym = 'A2' - else: - h1, h0 = mc.get_h1cas () - h2 = mc.get_h2cas () - hdiag = mc.fcisolver.make_hdiag (h1, h2, 6, 6) - ci0 = [mc.fcisolver.get_init_guess (6, 6, 1, hdiag), - initguess_triplet (6, 6, '1011')] - mc = mcscf.addons.state_average_mix (mc, fcisolvers, [0.5,0.5]) - else: - if mol.symmetry: - mc.fcisolver.wfnsym = 'A1' - mc = mc.state_average ([0.5,0.5]) - mc.fix_spin_(ss=0) - mc.conv_tol = 1e-12 - #mc.kernel (ci0=ci0) - #print (mc.e_states[1]-mc.e_states[0]) - #return mc - return mc.run (mo, ci0) -#mc_list = [[[get_mc_ref (m, ri=i, sam=j) for i in (0,1)] for j in (0,1)] for m in (mol_nosymm, mol_symm)] -mc_list = [] # Crunch within unittest.main for accurate clock -def get_mc_list (): - if len (mc_list) == 0: - for m in [mol_nosymm, mol_symm]: - mc_list.append ([[get_mc_ref (m, ri=i, sam=j) for i in (0,1)] for j in (0,1)]) - return mc_list - -def tearDownModule(): - global mol_nosymm, mol_symm, mc_list, si - mol_nosymm.stdout.close () - mol_symm.stdout.close () - del mol_nosymm, mol_symm, mc_list, si - -class KnownValues(unittest.TestCase): - - def test_offdiag_response_sanity (self): - for mcs, stype in zip (get_mc_list (), ('nosymm','symm')): - for mca, atype in zip (mcs, ('nomix','mix')): - if 'no' not in atype: - continue - # TODO: redesign this test case. MS-PDFT "_mix" is undefined except - # for L-PDFT and XMS-PDFT, whose gradients aren't implemented yet - for mc, itype in zip (mca, ('conv', 'DF')): - ci_arr = np.asarray (mc.ci) - if itype == 'conv': mc_grad = mc.nuc_grad_method () - else: continue #mc_grad = dfsacasscf.Gradients (mc) - # TODO: proper DF functionality - ngorb = mc_grad.ngorb - dw_ref = np.stack ([mc_grad.get_wfn_response (state=i) for i in (0,1)], axis=0) - dworb_ref, dwci_ref = dw_ref[:,:ngorb], dw_ref[:,ngorb:] - with self.subTest (symm=stype, solver=atype, eri=itype, check='energy convergence'): - self.assertTrue (mc.converged) - with self.subTest (symm=stype, solver=atype, eri=itype, check='ref CI d.f. zero'): - self.assertLessEqual (linalg.norm (dwci_ref), 1e-4) - ham_si = np.diag (mc.e_states) - ham_si = si @ ham_si @ si.T - eris = mc.ao2mo (mc.mo_coeff) - ci = list (np.tensordot (si, ci_arr, axes=1)) - ci_arr = np.asarray (ci) - si_diag = si * si - dw_diag = np.stack ([mc_grad.get_wfn_response (state=i, ci=ci) for i in (0,1)], axis=0) - dworb_diag, dwci_ref = dw_diag[:,:ngorb], dw_diag[:,ngorb:] - dworb_ref -= np.einsum ('sc,sr->rc', dworb_diag, si_diag) - dwci_ref = -np.einsum ('rpab,qab->rpq', dwci_ref.reshape (2,2,20,20), ci_arr) - dwci_ref -= dwci_ref.transpose (0,2,1) - dwci_ref = np.einsum ('spq,sr->rpq', dwci_ref, si_diag) - dwci_ref = dwci_ref[:,1,0] - for r in (0,1): - dworb_test, dwci_test = mspdft_heff_response (mc_grad, ci=ci, state=r, eris=eris, - si_bra=si[:,r], si_ket=si[:,r], heff_mcscf=ham_si) - dworb_test = mc.pack_uniq_var (dworb_test) - with self.subTest (symm=stype, solver=atype, eri=itype, root=r, check='orb'): - self.assertAlmostEqual (lib.fp (dworb_test), lib.fp (dworb_ref[r]), 8) - with self.subTest (symm=stype, solver=atype, eri=itype, root=r, check='CI'): - self.assertAlmostEqual (lib.fp (dwci_test), lib.fp (dwci_ref[r]), 8) - - def test_offdiag_grad_sanity (self): - for mcs, stype in zip (get_mc_list (), ('nosymm','symm')): - for mca, atype in zip (mcs, ('nomix','mix')): - if 'no' not in atype: - continue - # TODO: redesign this test case. MS-PDFT "_mix" is undefined except - # for L-PDFT and XMS-PDFT, whose gradients aren't implemented yet - for mc, itype in zip (mca, ('conv', 'DF')): - ci_arr = np.asarray (mc.ci) - if itype == 'conv': mc_grad = mc.nuc_grad_method () - else: continue #mc_grad = dfsacasscf.Gradients (mc) - # TODO: proper DF functionality - de_ref = np.stack ([mc_grad.get_ham_response (state=i) for i in (0,1)], axis=0) - eris = mc.ao2mo (mc.mo_coeff) - ci = list (np.tensordot (si, ci_arr, axes=1)) - ci_arr = np.asarray (ci) - si_diag = si * si - de_diag = np.stack ([mc_grad.get_ham_response (state=i, ci=ci) for i in (0,1)], axis=0) - de_ref -= np.einsum ('sac,sr->rac', de_diag, si_diag) - mf_grad = mc._scf.nuc_grad_method () - for r in (0,1): - de_test = mspdft_heff_HellmanFeynman (mc_grad, ci=ci, state=r, - si_bra=si[:,r], si_ket=si[:,r], eris=eris, mf_grad=mf_grad) - with self.subTest (symm=stype, solver=atype, eri=itype, root=r): - self.assertAlmostEqual (lib.fp (de_test), lib.fp (de_ref[r]), 8) - - def test_scanner (self): - def get_lih (r): - mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis='sto3g', - output='/dev/null', verbose=0) - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 2, 2, grids_level=1) - mc.fix_spin_(ss=0) - mc = mc.multi_state ([0.5,0.5], 'cms').run (conv_tol=1e-8) - return mol, mc.nuc_grad_method () - mol1, mc_grad1 = get_lih (1.5) - mol2, mc_grad2 = get_lih (1.55) - mc_grad2 = mc_grad2.as_scanner () - for state in 0,1: - de1 = mc_grad1.kernel (state=state) - e1 = mc_grad1.base.e_states[state] - e2, de2 = mc_grad2 (mol1, state=state) - self.assertTrue(mc_grad1.converged) - self.assertTrue(mc_grad2.converged) - self.assertAlmostEqual (e1, e2, 6) - self.assertAlmostEqual (lib.fp (de1), lib.fp (de2), 6) - - -if __name__ == "__main__": - print("Full Tests for MS-PDFT gradient off-diagonal heff fns") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/__init__.py b/pyscf/mcpdft/__init__.py deleted file mode 100644 index 65d446642..000000000 --- a/pyscf/mcpdft/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/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. -# -# Lahh dee dah -''' -Multi-configuration pair-density functional theory -================================================== - -Simple usage:: - - >>> from pyscf import gto, scf, mcpdft - >>> mol = gto.M(atom='N 0 0 0; N 0 0 1', basis='def2-tzvp') - >>> mf = scf.RHF(mol).run () - >>> mc = mcpdft.CASSCF (mf, 'tPBE', 6, 6) - >>> mc.run() -''' - - -import copy -from pyscf.mcpdft.mcpdft import get_mcpdft_child_class -from pyscf.mcpdft.otfnal import make_hybrid_fnal as hyb -from pyscf import mcscf, gto -from pyscf.lib import logger -from pyscf.mcscf import mc1step, casci - -def _sanity_check_of_mol(mc_or_mf_or_mol): - ''' - Sanity check to ensure input is a mol object, not a cell object. - Raises an error for cell objects. - ''' - from pyscf.pbc import gto as pbcgto - if isinstance(mc_or_mf_or_mol, (mc1step.CASSCF, casci.CASCI)): - mol = mc_or_mf_or_mol._scf.mol - elif isinstance(mc_or_mf_or_mol, gto.Mole): - mol = mc_or_mf_or_mol - else: - mol = mc_or_mf_or_mol.mol - - if isinstance(mol, pbcgto.cell.Cell): - raise NotImplementedError("MCPDFT not implemented for PBC") - -# NOTE: As of 02/06/2022, initializing PySCF mcscf classes with a symmetry-enabled molecule -# doesn't work. - -def _MCPDFT (mc_class, mc_or_mf_or_mol, ot, ncas, nelecas, ncore=None, frozen=None, - **kwargs): - # Raise an error if mol is a cell object. - _sanity_check_of_mol(mc_or_mf_or_mol) - if isinstance (mc_or_mf_or_mol, (mc1step.CASSCF, casci.CASCI)): - mc0 = mc_or_mf_or_mol - mf_or_mol = mc_or_mf_or_mol._scf - else: - mc0 = None - mf_or_mol = mc_or_mf_or_mol - if isinstance (mf_or_mol, gto.Mole) and mf_or_mol.symmetry: - logger.warn (mf_or_mol, - 'Initializing MC-SCF with a symmetry-adapted Mole object may not work!') - if frozen is not None: mc1 = mc_class (mf_or_mol, ncas, nelecas, ncore=ncore, frozen=frozen) - else: mc1 = mc_class (mf_or_mol, ncas, nelecas, ncore=ncore) - mc2 = get_mcpdft_child_class (mc1, ot, **kwargs) - if mc0 is not None: - mc2.verbose = mc0.verbose - mc2.stdout = mc0.stdout - mc2.mo_coeff = mc_or_mf_or_mol.mo_coeff.copy () - mc2.ci = copy.deepcopy (mc_or_mf_or_mol.ci) - mc2.converged = mc0.converged - return mc2 - -def CASSCFPDFT (mc_or_mf_or_mol, ot, ncas, nelecas, ncore=None, frozen=None, - **kwargs): - return _MCPDFT (mcscf.CASSCF, mc_or_mf_or_mol, ot, ncas, nelecas, ncore=ncore, frozen=frozen, - **kwargs) - -def CASCIPDFT (mc_or_mf_or_mol, ot, ncas, nelecas, ncore=None, **kwargs): - return _MCPDFT (mcscf.CASCI, mc_or_mf_or_mol, ot, ncas, nelecas, ncore=ncore, - **kwargs) - -CASSCF=CASSCFPDFT -CASCI=CASCIPDFT - -class MultiStateMCPDFTSolver : - pass - # tag - - -# Monkeypatch for double grad folders -# TODO: more elegant solution -import os -mypath = os.path.dirname (os.path.dirname (os.path.abspath (__file__))) -mygradpath = os.path.join (mypath, 'grad') -from pyscf import grad -grad.__path__.append (mygradpath) -grad.__path__=list(set(grad.__path__)) -mydfgradpath = os.path.join (os.path.join (mypath, 'df'), 'grad') -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/_dms.py b/pyscf/mcpdft/_dms.py deleted file mode 100644 index ef7b584a3..000000000 --- a/pyscf/mcpdft/_dms.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/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. -# -# Common density-matrix manipulations - -import numpy as np -from pyscf import lib -from pyscf.mcscf.addons import StateAverageFCISolver -from pyscf.mcscf.addons import StateAverageMixFCISolver -from scipy import linalg - -# DMRG solvers require special handling but dmrgscf is not always installed -try: - from pyscf import dmrgscf - DMRGCI = dmrgscf.DMRGCI -except ImportError: - class DMRGCI : - pass - -def _get_fcisolver (mc, ci, state=0): - '''Find the appropriate FCI solver, CI vector, and nelecas tuple to - build single-state reduced density matrices. If state_average or - state_average_mix is involved this takes a bit of work. - - The better solution, of course, is to edit StateAverage*FCI classes - to have quick density-matrices-of-one-state API... - ''' - nelecas = mc.nelecas - nroots = getattr (mc.fcisolver, 'nroots', 1) - fcisolver = mc.fcisolver - solver_state_index = state - if nroots>1: ci = ci[state] - if isinstance (mc.fcisolver, StateAverageMixFCISolver): - p0 = 0 - fcisolver = None - for s in mc.fcisolver.fcisolvers: - p1 = p0 + s.nroots - if p0 <= state and state < p1: - fcisolver = s - nelecas = mc.fcisolver._get_nelec (s, nelecas) - solver_state_index = state - p0 - break - p0 = p1 - if fcisolver is None: - raise RuntimeError ("Can't find FCI solver for state", state) - elif isinstance (mc.fcisolver, StateAverageFCISolver): - fcisolver = fcisolver.undo_state_average () - if isinstance (fcisolver, DMRGCI): - ci = solver_state_index # DMRGCI takes state index in place of ci vector - return fcisolver, ci, nelecas - -def make_one_casdm1s (mc, ci, state=0): - ''' - Construct the spin-separated active-space one-body reduced density - matrix for a single state. This API is not consistently available - in the StateAverageFCISolver functions without wasted effort (i.e., - without constructing the dms for all states and then discarding most - of them - ''' - ncas = mc.ncas - fcisolver, ci, nelecas = _get_fcisolver (mc, ci, state=state) - return fcisolver.make_rdm1s (ci, ncas, nelecas) - -def make_one_casdm2 (mc, ci, state=0): - ''' - Construct the spin-summed active-space two-body reduced density - matrix for a single state. This API is not consistently available - in the StateAverageFCISolver functions without wasted effort (i.e., - without constructing the dms for all states and then discarding most - of them - ''' - ncas = mc.ncas - fcisolver, ci, nelecas = _get_fcisolver (mc, ci, state=state) - try: - casdm2 = fcisolver.make_rdm2 (ci, ncas, nelecas) - except AttributeError: - # Hail Mary: maybe the fcisolver class only has make_rdm12 - # but not make_rdm2 implemented? - _, casdm2 = fcisolver.make_rdm12 (ci, ncas, nelecas) - return casdm2 - - -def dm2_cumulant (dm2, dm1s): - ''' - Evaluate the spin-summed two-body cumulant reduced density - matrix: - - cm2[p,q,r,s] = (dm2[p,q,r,s] - dm1[p,q]*dm1[r,s] - + dm1s[0][p,s]*dm1s[0][r,q] - + dm1s[1][p,s]*dm1s[1][r,q]) - - Args: - dm2 : ndarray of shape [norb,]*4 - Contains spin-summed 2-RDMs - dm1s : ndarray (or compatible) of overall shape [2,norb,norb] - Contains spin-separated 1-RDMs - - Returns: - cm2 : ndarray of shape [norb,]*4 - ''' - - dm1s = np.asarray (dm1s) - if len (dm1s.shape) < 3: - dm1 = dm1s.copy () - dm1s = dm1 / 2 - dm1s = np.stack ((dm1s, dm1s), axis=0) - else: - dm1 = dm1s[0] + dm1s[1] - cm2 = dm2.copy () - cm2 -= np.multiply.outer (dm1, dm1) - cm2 += np.multiply.outer (dm1s[0], dm1s[0]).transpose (0, 3, 2, 1) - cm2 += np.multiply.outer (dm1s[1], dm1s[1]).transpose (0, 3, 2, 1) - return cm2 - -def dm2s_cumulant (dm2s, dm1s): - '''Evaluate the spin-summed two-body cumulant reduced density - matrix: - - cm2s[0][p,q,r,s] = (dm2s[0][p,q,r,s] - dm1s[0][p,q]*dm1s[0][r,s] - + dm1s[0][p,s]*dm1s[0][r,q]) - cm2s[1][p,q,r,s] = (dm2s[1][p,q,r,s] - dm1s[0][p,q]*dm1s[1][r,s]) - cm2s[2][p,q,r,s] = (dm2s[2][p,q,r,s] - dm1s[1][p,q]*dm1s[1][r,s] - + dm1s[1][p,s]*dm1s[1][r,q]) - - Args: - dm2s : ndarray of shape [norb,]*4 - Contains spin-separated 2-RDMs - dm1s : ndarray (or compatible) of overall shape [2,norb,norb] - Contains spin-separated 1-RDMs - - Returns: - cm2s : (cm2s[0], cms2[1], cm2s[2]) - ndarrays of shape [norb,]*4; contain spin components - aa, ab, bb respectively - ''' - dm1s = np.asarray (dm1s) - if len (dm1s.shape) < 3: - dm1 = dm1s.copy () - dm1s = dm1 / 2 - dm1s = np.stack ((dm1s, dm1s), axis=0) - #cm2 = dm2 - np.einsum ('pq,rs->pqrs', dm1, dm1) - #cm2 += 0.5 * np.einsum ('ps,rq->pqrs', dm1, dm1) - cm2s = [i.copy () for i in dm2s] - cm2s[0] -= np.multiply.outer (dm1s[0], dm1s[0]) - cm2s[1] -= np.multiply.outer (dm1s[0], dm1s[1]) - cm2s[2] -= np.multiply.outer (dm1s[1], dm1s[1]) - cm2s[0] += np.multiply.outer (dm1s[0], dm1s[0]).transpose (0, 3, 2, 1) - cm2s[2] += np.multiply.outer (dm1s[1], dm1s[1]).transpose (0, 3, 2, 1) - return tuple (cm2s) - -def casdm1s_to_dm1s (mc, casdm1s, mo_coeff=None, ncore=None, ncas=None): - '''Generate AO-basis spin-separated 1-RDM from active space part. - This is necessary because the StateAverageMCSCFSolver class doesn't - have API for getting the AO-basis density matrix of a single state. - - Args: - mc : object of CASCI or CASSCF class - casdm1s : ndarray or compatible of shape (2,ncas,ncas) - Active-space spin-separated 1-RDM - - Kwargs: - ncore : integer - Number of occupied inactive orbitals - ncas : integer - Number of active orbitals - - Returns: - dm1s : ndarray of shape (2,nao,nao) - ''' - if mo_coeff is None: mo_coeff=mc.mo_coeff - if ncore is None: ncore=mc.ncore - if ncas is None: ncas=mc.ncas - mo_core = mo_coeff[:,:ncore] - mo_cas = mo_coeff[:,ncore:][:,:ncas] - moH_core = mo_core.conj ().T - moH_cas = mo_cas.conj ().T - - casdm1s = np.asarray (casdm1s) - dm1s_cas = np.dot (casdm1s, moH_cas) - dm1s_cas = np.dot (mo_cas, dm1s_cas).transpose (1,0,2) - dm1s_core = np.dot (mo_core, moH_core) - dm1s = dm1s_cas + dm1s_core[None,:,:] - - # Tags for speeding up rho generators and DF fns - no_coeff = mo_coeff[:,:ncore+ncas] - no_coeff = np.stack ([no_coeff, no_coeff], axis=0) - no_occ = np.zeros ((2,ncore+ncas), dtype=no_coeff.dtype) - no_occ[:,:ncore] = 1.0 - no_cas = no_coeff[:,:,ncore:] - for i in range (2): - no_occ[i,ncore:], umat = linalg.eigh (-casdm1s[i]) - no_cas[i,:,:] = np.dot (no_cas[i,:,:], umat) - no_occ[:,ncore:] *= -1 - dm1s = lib.tag_array (dm1s, mo_coeff=no_coeff, mo_occ=no_occ) - - return dm1s - - -def make_weighted_casdm1s(mc, ci=None, weights=None): - '''Compute the weighted average 1-electron spin-separated CAS density. - - Args: - mc : instance of class _PDFT - - ci : list of ndarrays of length nroots - CI vectors should be from a converged CASSCF/CASCI calculation - - weights : ndarray of length nroots - Weight for each state. If none, uses weights from SA-CASSCF - calculation - - Returns: - Weighted average of casdm1s - ''' - if ci is None: ci = mc.ci - if weights is None: weights = mc.weights - - # There might be a better way to construct all of them, but this should be - # more cost-effective than what is currently in the _dms file. - casdm1s_all = [make_one_casdm1s(mc, ci, state) for state in range(len(ci))] - casdm1s_0 = np.tensordot(weights, casdm1s_all, axes=1) - return tuple(casdm1s_0) - -def make_weighted_casdm2(mc, ci=None, weights=None): - '''Compute the weighted average 2-electron spin-summed CAS density. - - Args: - mc : instance of class _PDFT - - ci : list of ndarrays of length nroots - CI vectors should be from a converged CASSCF/CASCI calculation - - weights : ndarray of length nroots - Weight for each state. If none, uses weights from SA-CASSCF - calculation - - Returns: - Weighted average of casdm2 - ''' - if ci is None: ci = mc.ci - if weights is None: weights = mc.weights - - # There might be a better way to construct all of them, but this should be - # more cost-effective than what is currently in the _dms file. - casdm2_all = [make_one_casdm2(mc, ci, state) for state in range(len(ci))] - return np.tensordot(weights, casdm2_all, axes=1) diff --git a/pyscf/mcpdft/_libxc.py b/pyscf/mcpdft/_libxc.py deleted file mode 100644 index 7d9510096..000000000 --- a/pyscf/mcpdft/_libxc.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/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. -# -from pyscf.dft2.libxc import XC_ALIAS, XC_CODES, XC_KEYS -from pyscf.dft2.libxc import hybrid_coeff, rsh_coeff -from pyscf import lib - -XC_ALIAS_KEYS = set (XC_ALIAS.keys ()) -XC_TYPE_HDR = tuple (['LDA_','GGA_','MGGA_']) -INTCODES_TYPES = {} -INTCODES_HYB = [] -for key, val in XC_CODES.items (): - mykey = key - if key.startswith ('HYB_'): - INTCODES_HYB.append (val) - mykey = key[4:] - if mykey.startswith (XC_TYPE_HDR): - words = mykey.split ('_') - INTCODES_TYPES[val] = words[1] -INTCODES_HYB = set (INTCODES_HYB) - -class XCSplitError (RuntimeError): - def __init__(self, xc): - super().__init__('') - self.path = '{}->?'.format (xc) - def __str__(self): - return self.message + '\npath = ' + self.path - def extend (self, xc): - self.path = self.path[:-1] + '{}->?'.format (xc) - def __call__(self, message): - self.message = message - return self - -def split_x_c_comma (xc): - '''Split an xc code string into two separate strings, one for - exchange and one for correlation, by finding a comma in the string - or in some alias''' - if ',' in xc: return xc.split (',') - if not len (xc): return '','' - xc = xc.upper () - myerr = XCSplitError (xc) - max_recurse = 5 - for _ in range (max_recurse): - if ',' in xc: - break - elif xc in XC_ALIAS_KEYS: - xc = XC_ALIAS[xc] - elif lib.isinteger (XC_CODES.get (xc, None)): - xc_int = XC_CODES[xc] - if xc_int in INTCODES_HYB: - raise myerr ('LibXC built-in hybrid') - xc_type = INTCODES_TYPES[xc_int] - if xc_type == 'X': - xc = xc + ',' - elif xc_type == 'C': - xc = ',' + xc - elif xc_type == 'XC': - raise myerr ('LibXC built-in X+C functional') - elif xc_type == 'K': - raise myerr ('Kinetic energy functional') - else: - raise myerr ('Unknown functional type {} for code {}'.format ( - xc_type, xc_int)) - elif xc in XC_KEYS: - xc = XC_CODES[xc] - else: - raise myerr (xc) - myerr.extend (xc) - if ',' not in xc: - raise myerr ('Maximum XC alias recursion depth') - return xc.split (',') - -def is_hybrid_or_rsh (xc_code): - hyb = hybrid_coeff (xc_code) - omega = rsh_coeff (xc_code)[0] - non0 = [abs (x)>1e-10 for x in (hyb, omega)] - return any (non0) - -def is_hybrid_xc (xc_code): - hyb = hybrid_coeff (xc_code) - return abs (hyb)>1e-10 - -def parse_xc_formula (xc_code): - if ',' in xc_code: - x_code, c_code = xc_code.split (',') - x_facs, x_fnals = _parse_xc_formula (x_code) - c_facs, c_fnals = _parse_xc_formula (c_code) - return x_facs+c_facs, x_fnals+c_fnals - return _parse_xc_formula (xc_code) - -def _parse_xc_formula (xc_code): - facs = [] - fnals = [] - for token in xc_code.replace('-','+-').replace(';+',';').split('+'): - sign = 1 - if not len (token): continue - if token[0] == '-': - sign = -1 - token = token[1:] - if '*' in token: - fac, fnal = token.split ('*') - if fac[0].isalpha (): - fac, fnal = fnal, fac - fac = sign * float (fac) - else: - fac = sign - fnal = token - facs.append (fac) - fnals.append (fnal) - return facs, fnals - -def assemble_xc_formula (facs, terms): - code = [] - for fac, term in zip (facs, terms): - if fac==1.0: code.append ('{:s}'.format (term)) - elif fac==-1.0: code.append ('-{:s}'.format (term)) - elif fac==0.0: continue - else: - fac = '{:.16f}'.format (round (fac,14)) - fac = fac.rstrip ('0').rstrip ('.') - code.append ('{:s}*{:s}'.format (fac, term)) - code = '+'.join (code).replace ('+-','-') - return code diff --git a/pyscf/mcpdft/chkfile.py b/pyscf/mcpdft/chkfile.py deleted file mode 100644 index 14e0cf761..000000000 --- a/pyscf/mcpdft/chkfile.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/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. -# -# Author: Matthew Hennefath - -import h5py -from pyscf.lib.chkfile import load_mol, load, dump -from pyscf.lib import H5FileWrap -from pyscf.mcscf.addons import StateAverageMixFCISolver - - -def load_pdft(chkfile): - return load_mol(chkfile), load(chkfile, "pdft") - - -def dump_mcpdft( - mc, - chkfile=None, - key="pdft", - e_tot=None, - e_ot=None, - e_states=None, - e_mcscf=None, - mcscf_key="mcscf", -): - """Save MC-PDFT calculation results in chkfile""" - if chkfile is None: - chkfile = mc.chkfile - if e_tot is None: - e_tot = mc.e_tot - if e_ot is None: - e_ot = mc.e_ot - - if h5py.is_hdf5(chkfile): - mode = "a" - else: - mode = "w" - - with H5FileWrap(chkfile, mode) as fh5: - if mode == "a" and key in fh5: - del fh5[key] - - def store(subkey, val): - if val is not None: - - fh5[key + "/" + subkey] = val - - store("e_tot", e_tot) - store("e_ot", e_ot) - store("e_states", e_states) - store("e_mcscf", e_mcscf) - - # Now lets link - if mcscf_key in fh5: - hard_links = { - "mo_coeff": "mo_coeff", - "ci__from_list__": "ci__from_list__", - "ci": "ci", - } - - for s, d in hard_links.items(): - if s in fh5[mcscf_key]: - fh5[key + "/" + d] = fh5[mcscf_key + "/" + s] - - -def dump_lpdft( - mc, - chkfile=None, - key="pdft", - e_tot=None, - e_states=None, - e_mcscf=None, - ci=None, - mcscf_key="mcscf", -): - """Save L-PDFT calculation results in chkfile""" - if chkfile is None: - chkfile = mc.chkfile - - if e_tot is None: - e_tot = mc.e_tot - - if e_states is None: - e_states = mc.e_states - - if e_mcscf is None: - e_mcscf = mc.e_mcscf - - if h5py.is_hdf5(chkfile): - mode = "a" - - else: - mode = "w" - - mixed_ci = isinstance(mc.fcisolver, StateAverageMixFCISolver) and ci is not None - - with H5FileWrap(chkfile, mode) as fh5: - if mode == "a" and key in fh5: - del fh5[key] - - def store(subkey, val): - if val is not None: - fh5[key + "/" + subkey] = val - - store("e_tot", e_tot) - store("e_states", e_states) - store("e_mcscf", e_mcscf) - if not mixed_ci: - store("ci", ci) - - if mcscf_key in fh5: - hard_links = {"mo_coeff": "mo_coeff"} - for s, d in hard_links.items(): - if s in fh5[mcscf_key]: - fh5[key + "/" + d] = fh5[mcscf_key + "/" + s] - - if mixed_ci: - dump(chkfile, "pdft/ci", ci) diff --git a/pyscf/mcpdft/cmspdft.py b/pyscf/mcpdft/cmspdft.py deleted file mode 100644 index b728bc692..000000000 --- a/pyscf/mcpdft/cmspdft.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/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 itertools import product -from scipy import linalg -from pyscf import gto, dft, ao2mo, fci, mcscf, lib -from pyscf.lib import logger, temporary_env -from pyscf.mcscf.addons import StateAverageMCSCFSolver, state_average_mix, state_average_mix_, state_average -from pyscf.fci import direct_spin1 -from pyscf import mcpdft - -def coulomb_tensor (mc, mo_coeff=None, ci=None, h2eff=None, eris=None): - '''Compute w_IJKL = (tu|vx) D^IJ_tu D^KL_vx - - Args: - mc : mcscf method instance - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbital coefficients - ci : list of ndarrays of shape (ndeta,ndetb) - Contains CI vectors - h2eff : ndarray of shape [ncas,]*4 - Contains active-space ERIs - eris : mc_ao2mo.ERI object - Contains active-space ERIs. Ignored if h2eff is passed; if - h2eff is not passed then it is constructed from eris.ppaa - - Returns: - w : ndarray of shape [nroots,]*4 - ''' - if mo_coeff is None: mo_coeff=mc.mo_coeff - if ci is None: ci = mc.ci - # TODO: state-average mix extension - ci = np.asarray (ci) - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nroots, nocc = mc.fcisolver.nroots, ncore + ncas - if h2eff is None: - if eris is None: h2eff = mc.get_h2eff (mo_coeff=mo_coeff) - else: h2eff = np.asarray (eris.ppaa[ncore:nocc,ncore:nocc,:,:]) - h2eff = ao2mo.restore (1, h2eff, ncas) - - row, col = np.tril_indices (nroots) - tdm1 = np.stack (mc.fcisolver.states_trans_rdm12(ci[col], ci[row], ncas, - nelecas)[0], axis=0) - - w = np.tensordot (tdm1, h2eff, axes=2) - w = np.tensordot (w, tdm1, axes=((1,2),(1,2))) - return ao2mo.restore (1, w, nroots) - -def e_coul (mc, mo_coeff=None, ci=None, h2eff=None, eris=None): - '''Compute the sum of active-space Coulomb energies (the diabatizer - function for CMS-PDFT) and its first and second derivatives - - Args: - mc : mcscf method instance - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbital coefficients - ci : list of ndarrays of shape (ndeta,ndetb) - Contains CI vectors - h2eff : ndarray of shape [ncas,]*4 - Contains active-space ERIs - eris : mc_ao2mo.ERI object - Contains active-space ERIs. Ignored if h2eff is passed; if - h2eff is not passed then it is constructed from eris.ppaa - - Returns: - Qaa : float - sum of Coulomb energies - dQaa : ndarray of shape npair = nroots*(nroots-1)/2 - first derivatives of J wrt interstate rotation - d2Qaa : ndarray of shape (npair,npair) - Hessian of J wrt interstate rotation - Qaa_update : callable - Takes a unitary matrix of shape (nroots, nroots) and returns - Qaa, dQaa, and d2Qaa as above using the stored Coulomb - tensor intermediate from this function. - ''' - nroots = mc.fcisolver.nroots - - w0 = coulomb_tensor (mc, mo_coeff=mo_coeff, ci=ci, h2eff=h2eff, - eris=eris) - Qaa0, dQaa0, d2Qaa0 = _e_coul (w0, nroots) - def Qaa_update (u=1): - w1 = ao2mo.incore.full (w0, u, compact=False) - return _e_coul (w1, nroots) - return Qaa0, dQaa0, d2Qaa0, Qaa_update - -def _e_coul (w_IJKL, nroots): - npair = nroots * (nroots - 1) // 2 - w_IJKK = np.diagonal (w_IJKL, axis1=2, axis2=3) - w_IKJK = np.diagonal (w_IJKL, axis1=1, axis2=3) - w_IJJJ = np.diagonal (w_IJKK, axis1=1, axis2=2) - - Qaa = np.trace (w_IJJJ) / 2.0 - - tril_mask = np.zeros ([nroots,nroots], dtype=np.bool_) - tril_mask[np.tril_indices (nroots,k=-1)] = True - dQaa = 2*(w_IJJJ.T-w_IJJJ)[tril_mask] - # My sign convention is row idx = source state; col idx = dest - # state, lower-triangular positive. The Newton iteration is designed - # with this in mind and breaks if I flip it. However, regardless of - # sign convention, the unitary operator parameterized this way - # always comes out with destination on the rows and source on the - # columns, because that's just what the word "operator" means: - # |f> = U|i>. So when I exponentiate later, I transpose. Don't let - # the fact that that transpose is mathematically the same as - # flipping this sign confuse you: the sign here is CORRECT. - - v_IJ_K = -4*w_IKJK - 2*w_IJKK - v_IJ_K += (w_IJJJ+w_IJJJ.T)[:,:,None] - d2Qaa = np.zeros_like (w_IJKL) - for k in range (nroots): - d2Qaa[:,k,k,:] = v_IJ_K[:,:,k] - d2Qaa -= d2Qaa.transpose (0,1,3,2) - d2Qaa -= d2Qaa.transpose (1,0,2,3) - tril_mask2 = np.logical_and.outer (tril_mask, tril_mask) - d2Qaa = d2Qaa[tril_mask2].reshape (npair, npair) - - return Qaa, dQaa, d2Qaa - -def e_coul_o0 (mc,ci): - # Old implementation - nroots = mc.fcisolver.nroots - ncas, ncore = mc.ncas,mc.ncore - nocc = ncas + ncore - rows,col = np.tril_indices(nroots,k=-1) - pairs = len(rows) - mo_cas = mc.mo_coeff[:,ncore:nocc] - ci_array = np.array(ci) - casdm1 = mc.fcisolver.states_make_rdm1 (ci,ncas,mc.nelecas) - dm1 = np.dot(casdm1,mo_cas.T) - dm1 = np.dot(mo_cas,dm1).transpose(1,0,2) - j = mc._scf.get_j (dm=dm1) - e_coul = (j*dm1).sum((1,2)) / 2 - - trans12_tdm1, trans12_tdm2 = mc.fcisolver.states_trans_rdm12(ci_array[col], - ci_array[rows],ncas,mc.nelecas) - trans12_tdm1_array = np.array(trans12_tdm1) - tdm1 = np.dot(trans12_tdm1_array,mo_cas.T) - tdm1 = np.dot(mo_cas,tdm1).transpose(1,0,2) - rowscol2ind = np.zeros ((nroots, nroots), dtype=int) - rowscol2ind[(rows,col)] = list (range (pairs)) - rowscol2ind += rowscol2ind.T - rowscol2ind[np.diag_indices(nroots)] = -1 - - def w_klmn(k,l,m,n,dm,tdm): - d = dm[k] if k==l else tdm[rowscol2ind[k,l]] - dm1_g = mc._scf.get_j (dm=d) - d = dm[m] if m==n else tdm[rowscol2ind[m,n]] - w = (dm1_g*d).sum ((0,1)) - return w - - def v_klmn(k,l,m,n,dm,tdm): - if l==m: - v = (w_klmn(k,n,k,k,dm,tdm)-w_klmn(k,n,l,l,dm,tdm) - +w_klmn(n,k,n,n,dm,tdm)-w_klmn(k,n,m,m,dm,tdm) - -4*w_klmn(k,l,m,n,dm,tdm)) - else: - v = 0 - return v - - dg = mc._scf.get_j (dm=tdm1) - grad1 = (dg*dm1[rows]).sum((1,2)) - grad2 = (dg*dm1[col]).sum((1,2)) - e_grad = np.zeros(pairs) - e_grad = 2*(grad1 - grad2) - - e_hess = np.zeros((pairs,pairs)) - for (i, (k,l)), (j, (m,n)) in product (enumerate (zip (rows, col)), - repeat=2): - e_hess[i,j] = (v_klmn(k,l,m,n,dm1,tdm1)+v_klmn(l,k,n,m,dm1,tdm1) - -v_klmn(k,l,n,m,dm1,tdm1)-v_klmn(l,k,m,n,dm1,tdm1)) - - return sum (e_coul), e_grad, e_hess - diff --git a/pyscf/mcpdft/lpdft.py b/pyscf/mcpdft/lpdft.py deleted file mode 100644 index 34db9432e..000000000 --- a/pyscf/mcpdft/lpdft.py +++ /dev/null @@ -1,768 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -import numpy as np -from scipy import linalg - -from pyscf.lib import logger -from pyscf.fci import direct_spin1 -from pyscf import __config__ - -from pyscf import mcpdft -from pyscf.mcpdft import _dms -from pyscf.mcpdft import chkfile - - -def weighted_average_densities(mc, ci=None, weights=None): - """Compute the weighted average 1- and 2-electron CAS densities. - 1-electron CAS is returned as spin-separated. - - Args: - mc : instance of class _PDFT - - ci : list of ndarrays of length nroots - CI vectors should be from a converged CASSCF/CASCI calculation - - weights : ndarray of length nroots - Weight for each state. If none, uses weights from SA-CASSCF - calculation - - Returns: - A tuple, the first is casdm1s and the second is casdm2 where they are - weighted averages where the weights are given. - """ - - return _dms.make_weighted_casdm1s( - mc, ci=ci, weights=weights - ), _dms.make_weighted_casdm2(mc, ci=ci, weights=weights) - - -def get_lpdft_hconst( - mc, - E_ot, - casdm1s_0, - casdm2_0, - hyb=1.0, - ncas=None, - ncore=None, - veff1=None, - veff2=None, - mo_coeff=None, -): - """Compute h_const for the L-PDFT Hamiltonian - - Args: - mc : instance of class _PDFT - - E_ot : float - On-top energy - - casdm1s_0 : ndarray of shape (2, ncas, ncas) - Spin-separated 1-RDM in the active space generated from expansion - density. - - casdm2_0 : ndarray of shape (ncas, ncas, ncas, ncas) - Spin-summed 2-RDM in the active space generated from expansion - density. - - Kwargs: - hyb : float - Hybridization constant (lambda term) - - ncas : float - Number of active space MOs - - ncore: float - Number of core MOs - - veff1 : ndarray of shape (nao, nao) - 1-body effective potential in the AO basis computed using the - zeroth-order densities. - - veff2 : pyscf.mcscf.mc_ao2mo._ERIS instance - Relevant 2-body effective potential in the MO basis. - - Returns: - Constant term h_const for the expansion term. - """ - - if ncas is None: - ncas = mc.ncas - if ncore is None: - ncore = mc.ncore - if veff1 is None: - veff1 = mc.veff1 - if veff2 is None: - veff2 = mc.veff2 - if mo_coeff is None: - mo_coeff = mc.mo_coeff - - nocc = ncore + ncas - - # Get the 1-RDM matrices - casdm1_0 = casdm1s_0[0] + casdm1s_0[1] - dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s=casdm1s_0, mo_coeff=mo_coeff) - dm1 = dm1s[0] + dm1s[1] - - # Coulomb interaction - vj = mc._scf.get_j(dm=dm1) - e_veff1_j = np.tensordot(veff1 + hyb * 0.5 * vj, dm1) - - # Deal with 2-electron on-top potential energy - e_veff2 = veff2.energy_core - e_veff2 += np.tensordot(veff2.vhf_c[ncore:nocc, ncore:nocc], casdm1_0) - e_veff2 += 0.5 * np.tensordot( - veff2.papa[ncore:nocc, :, ncore:nocc, :], casdm2_0, axes=4 - ) - - # h_nuc + E_ot - 1/2 g_pqrs D_pq D_rs - V_pq D_pq - 1/2 v_pqrs d_pqrs - energy_core = hyb * mc.energy_nuc() + E_ot - e_veff1_j - e_veff2 - return energy_core - - -def transformed_h1e_for_cas( - mc, E_ot, casdm1s_0, casdm2_0, hyb=1.0, mo_coeff=None, ncas=None, ncore=None -): - """Compute the CAS one-particle L-PDFT Hamiltonian - - Args: - mc : instance of a _PDFT object - - E_ot : float - On-top energy - - casdm1s_0 : ndarray of shape (2,ncas,ncas) - Spin-separated 1-RDM in the active space generated from expansion - density - - casdm2_0 : ndarray of shape (ncas,ncas,ncas,ncas) - Spin-summed 2-RDM in the active space generated from expansion - density - - hyb : float - Hybridization constant (lambda term) - - mo_coeff : ndarray of shape (nao,nmo) - A full set of molecular orbital coefficients. Taken from self if - not provided. - - ncas : int - Number of active space molecular orbitals - - ncore : int - Number of core molecular orbitals - - Returns: - A tuple, the first is the effective one-electron linear PDFT - Hamiltonian defined in CAS space, the second is the modified core - energy. - """ - if mo_coeff is None: - mo_coeff = mc.mo_coeff - if ncas is None: - ncas = mc.ncas - if ncore is None: - ncore = mc.ncore - - nocc = ncore + ncas - mo_core = mo_coeff[:, :ncore] - mo_cas = mo_coeff[:, ncore:nocc] - - # h_pq + V_pq + J_pq all in AO integrals - hcore_eff = mc.get_lpdft_hcore_only(casdm1s_0, hyb=hyb, mo_coeff=mo_coeff, - ncore=ncore, ncas=ncas) - energy_core = mc.get_lpdft_hconst(E_ot, casdm1s_0, casdm2_0, hyb, - mo_coeff=mo_coeff, ncore=ncore, - ncas=ncas) - - if mo_core.size != 0: - core_dm = np.dot(mo_core, mo_core.conj().T) * 2 - # This is precomputed in MRH's ERIS object - energy_core += mc.veff2.energy_core - energy_core += np.tensordot(core_dm, hcore_eff).real - - h1eff = mo_cas.conj().T @ hcore_eff @ mo_cas - # Add in the 2-electron portion that acts as a 1-electron operator - h1eff += mc.veff2.vhf_c[ncore:nocc, ncore:nocc] - - return h1eff, energy_core - - -def get_transformed_h2eff_for_cas(mc, ncore=None, ncas=None): - """Compute the CAS two-particle linear PDFT Hamiltonian - - Args: - ncore : int - Number of core MOs - - ncas : int - Number of active space MOs - - Returns: - ndarray of shape (ncas,ncas,ncas,ncas) which contain v_vwxy - """ - if ncore is None: - ncore = mc.ncore - if ncas is None: - ncas = mc.ncas - nocc = ncore + ncas - return mc.veff2.papa[ncore:nocc, :, ncore:nocc, :] - - -def make_lpdft_ham_(mc, mo_coeff=None, ci=None, ot=None): - """Compute the L-PDFT Hamiltonian - - Args: - mo_coeff : ndarray of shape (nao, nmo) - A full set of molecular orbital coefficients. Taken from self if - not provided. - - ci : list of ndarrays of length nroots - CI vectors should be from a converged CASSCF/CASCI calculation - - ot : an instance of on-top functional class - see otfnal.py - - Returns: - lpdft_ham : ndarray of shape (nroots, nroots) or (nirreps, nroots, nroots) - Linear approximation to the MC-PDFT energy expressed as a - hamiltonian in the basis provided by the CI vectors. If - StateAverageMix, then returns the block diagonal of the lpdft - hamiltonian for each irrep. - """ - - if mo_coeff is None: - mo_coeff = mc.mo_coeff - if ci is None: - ci = mc.ci - if ot is None: - ot = mc.otfnal - - ot.reset(mol=mc.mol) - - spin = abs(mc.nelecas[0] - mc.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 functionals") - if abs(hyb[0] - hyb[1]) > 1e-11: - raise NotImplementedError( - "hybrid functionals with different exchange, correlations components" - ) - - cas_hyb = hyb[0] - - ncas = mc.ncas - casdm1s_0, casdm2_0 = mc.get_casdm12_0(ci=ci) - - mc.veff1, mc.veff2, E_ot = mc.get_pdft_veff( - mo=mo_coeff, - ci=ci, - casdm1s=casdm1s_0, - casdm2=casdm2_0, - drop_mcwfn=True, - incl_energy=True, - ot=ot - ) - - # This is all standard procedure for generating the hamiltonian in PySCF - h1, h0 = mc.get_h1lpdft(E_ot, casdm1s_0, casdm2_0, hyb=1.0 - cas_hyb, mo_coeff=mo_coeff) - h2 = mc.get_h2lpdft() - h2eff = direct_spin1.absorb_h1e(h1, h2, ncas, mc.nelecas, 0.5) - - def construct_ham_slice(solver, slice, nelecas): - ci_irrep = ci[slice] - if hasattr(solver, "orbsym"): - solver.orbsym = mc.fcisolver.orbsym - - hc_all_irrep = [solver.contract_2e(h2eff, c, ncas, nelecas) for c in ci_irrep] - lpdft_irrep = np.tensordot(ci_irrep, hc_all_irrep, axes=((1, 2), (1, 2))) - diag_idx = np.diag_indices_from(lpdft_irrep) - lpdft_irrep[diag_idx] += h0 + cas_hyb * mc.e_mcscf[slice] - return lpdft_irrep - - if not isinstance(mc, _LPDFTMix): - return construct_ham_slice(direct_spin1, slice(0, len(ci)), mc.nelecas) - - # We have a StateAverageMix Solver - mc._irrep_slices = [] - start = 0 - for solver in mc.fcisolver.fcisolvers: - end = start + solver.nroots - mc._irrep_slices.append(slice(start, end)) - start = end - - return [ - construct_ham_slice(s, irrep, mc.fcisolver._get_nelec(s, mc.nelecas)) - for s, irrep in zip(mc.fcisolver.fcisolvers, mc._irrep_slices) - ] - - -def kernel(mc, mo_coeff=None, ci0=None, ot=None, dump_chk=True, **kwargs): - if ot is None: - ot = mc.otfnal - if mo_coeff is None: - mo_coeff = mc.mo_coeff - - mc.optimize_mcscf_(mo_coeff=mo_coeff, ci0=ci0) - ci_mcscf = mc.ci - mc.lpdft_ham = mc.make_lpdft_ham_(ot=ot) - logger.debug(mc, f"L-PDFT Hamiltonian in MC-SCF Basis:\n{mc.get_lpdft_ham()}") - - if hasattr(mc, "_irrep_slices"): - e_states, si_pdft = zip(*map(mc._eig_si, mc.lpdft_ham)) - e_states = np.concatenate(e_states) - si_pdft = linalg.block_diag(*si_pdft) - - else: - e_states, si_pdft = mc._eig_si(mc.lpdft_ham) - - mc.e_states = e_states - mc.si_pdft = si_pdft - - logger.debug(mc, f"L-PDFT SI:\n{mc.si_pdft}") - - e_tot = np.dot(e_states, mc.weights) - ci = mc._get_ci_adiabats(ci_mcscf) - - mc.e_tot = e_tot - mc.ci = ci - - if dump_chk: - mc.dump_chk(locals()) - - return (mc.e_tot, mc.e_mcscf, mc.e_cas, mc.ci, mc.mo_coeff, mc.mo_energy) - - -class _LPDFT(mcpdft.MultiStateMCPDFTSolver): - """Linerized PDFT - - Saved Results - - e_tot : float - Weighted-average L-PDFT final energy - e_states : ndarray of shape (nroots) - L-PDFT final energies of the adiabatic states - ci : list of length (nroots) of ndarrays - CI vectors in the optimized adiabatic basis of L-PDFT - si_pdft : ndarray of shape (nroots, nroots) - Expansion coefficients of the L-PDFT adiabats in terms of the - optimized MC-SCF adiabats - e_mcscf : ndarray of shape (nroots) - Energies of the MC-SCF adiabatic states - lpdft_ham : ndarray of shape (nroots, nroots) - L-PDFT Hamiltonian in the MC-SCF adiabatic basis - veff1 : ndarray of shape (nao, nao) - 1-body effective potential in the AO basis computed using the - zeroth-order densities. - veff2 : pyscf.mcscf.mc_ao2mo._ERIS instance - Relevant 2-body effective potential in the MO basis. - """ - - # chk_veff = getattr(__config__, 'mcpdft_lpdft_chk_veff', False) - - def __init__(self, mc): - self.__dict__.update(mc.__dict__) - keys = set(("lpdft_ham", "si_pdft", "veff1", "veff2")) - self.lpdft_ham = None - self.si_pdft = None - self.veff1 = None - self.veff2 = None - self._e_states = None - self._keys = set(self.__dict__.keys()).union(keys) - - @property - def e_states(self): - if self._in_mcscf_env: - return self.fcisolver.e_states - - else: - return self._e_states - - @e_states.setter - def e_states(self, x): - self._e_states = x - - make_lpdft_ham_ = make_lpdft_ham_ - make_lpdft_ham_.__doc__ = make_lpdft_ham_.__doc__ - - get_lpdft_hconst = get_lpdft_hconst - get_lpdft_hconst.__doc__ = get_lpdft_hconst.__doc__ - - get_h1lpdft = transformed_h1e_for_cas - get_h1lpdft.__doc__ = transformed_h1e_for_cas.__doc__ - - get_h2lpdft = get_transformed_h2eff_for_cas - get_h2lpdft.__doc__ = get_transformed_h2eff_for_cas.__doc__ - - get_casdm12_0 = weighted_average_densities - get_casdm12_0.__doc__ = weighted_average_densities.__doc__ - - def get_lpdft_diag(self): - """Diagonal elements of the L-PDFT Hamiltonian matrix - (H_00^L-PDFT, H_11^L-PDFT, H_22^L-PDFT, ...) - - Returns: - lpdft_diag : ndarray of shape (nroots) - Contains the linear approximation to the MC-PDFT energy. These - are also the diagonal elements of the L-PDFT Hamiltonian - matrix. - """ - return np.diagonal(self.lpdft_ham).copy() - - def get_lpdft_ham(self): - """The L-PDFT effective Hamiltonian matrix - - Returns: - lpdft_ham : ndarray of shape (nroots, nroots) - Contains L-PDFT Hamiltonian elements on the off-diagonals - and PDFT approx energies on the diagonals - """ - return self.lpdft_ham - - def kernel(self, mo_coeff=None, ci0=None, ot=None, verbose=None, dump_chk=True): - """ - Returns: - 6 elements, they are - total energy, - the MCSCF energies, - the active space CI energy, - the active space FCI wave function coefficients, - the MCSCF canonical orbital coefficients, - the MCSCF canonical orbital energies - - They are attributes of the QLPDFT object, which can be accessed by - .e_tot, .e_mcscf, .e_cas, .ci, .mo_coeff, .mo_energy - """ - if ot is None: - ot = self.otfnal - ot.reset(mol=self.mol) # scanner mode safety - - if mo_coeff is None: - mo_coeff = self.mo_coeff - else: - self.mo_coeff = mo_coeff - - log = logger.new_logger(self, verbose) - - if ci0 is None and isinstance(getattr(self, "ci", None), list): - ci0 = [c.copy() for c in self.ci] - - kernel(self, mo_coeff, ci0, ot=ot, verbose=log, dump_chk=dump_chk) - self._finalize_lin() - return ( - self.e_tot, - self.e_mcscf, - self.e_cas, - self.ci, - self.mo_coeff, - self.mo_energy, - ) - - def _finalize_lin(self): - log = logger.Logger(self.stdout, self.verbose) - nroots = len(self.e_states) - log.note("%s (final) states:", self.__class__.__name__) - if log.verbose >= logger.NOTE and getattr(self.fcisolver, "spin_square", None): - ss = self.fcisolver.states_spin_square(self.ci, self.ncas, self.nelecas)[0] - - for i in range(nroots): - log.note( - " State %d weight %g ELPDFT = %.15g S^2 = %.7f", - i, - self.weights[i], - self.e_states[i], - ss[i], - ) - - else: - for i in range(nroots): - log.note( - " State %d weight %g ELPDFT = %.15g", - i, - self.weights[i], - self.e_states[i], - ) - - def _get_ci_adiabats(self, ci_mcscf): - """Get the CI vertors in eigenbasis of L-PDFT Hamiltonian - - Kwargs: - ci : list of length nroots - MC-SCF ci vectors; defaults to self.ci_mcscf - - Returns: - ci : list of length nroots - CI vectors in basis of L-PDFT Hamiltonian eigenvectors - """ - return list(np.tensordot(self.si_pdft.T, np.asarray(ci_mcscf), axes=1)) - - def _eig_si(self, ham): - return linalg.eigh(ham) - - def get_lpdft_hcore_only(self, casdm1s_0, hyb=1.0, mo_coeff=None, ncore=None, ncas=None): - """ - Returns the lpdft hcore AO integrals weighted by the - hybridization factor. Excludes the MC-SCF (wfn) component. - """ - - dm1s = _dms.casdm1s_to_dm1s(self, casdm1s=casdm1s_0, mo_coeff=mo_coeff, - ncore=ncore, ncas=ncas) - dm1 = dm1s[0] + dm1s[1] - v_j = self._scf.get_j(dm=dm1) - return hyb * self.get_hcore() + self.veff1 + hyb * v_j - - def get_lpdft_hcore(self, casdm1s_0=None, mo_coeff=None, ncore=None, ncas=None): - """ - Returns the full lpdft hcore AO integrals. Includes the MC-SCF - (wfn) component for hybrid functionals. - """ - if casdm1s_0 is None: - casdm1s_0 = self.get_casdm12_0()[0] - - spin = abs(self.nelecas[0] - self.nelecas[1]) - cas_hyb = self.otfnal._numint.rsh_and_hybrid_coeff(self.otfnal.otxc, spin=spin)[ - 2 - ] - hyb = 1.0 - cas_hyb[0] - - return cas_hyb[0] * self.get_hcore() + self.get_lpdft_hcore_only( - casdm1s_0, hyb=hyb, mo_coeff=mo_coeff, ncore=ncore, ncas=ncas - ) - - def nuc_grad_method(self, state=None): - from pyscf.mcscf import mc1step - from pyscf.mcscf.df import _DFCASSCF - - if not isinstance(self, mc1step.CASSCF): - raise NotImplementedError("CASCI-based LPDFT nuclear gradients") - elif getattr(self, "frozen", None) is not None: - raise NotImplementedError("LPDFT nuclear gradients with frozen orbitals") - elif isinstance(self, _DFCASSCF): - from pyscf.df.grad.lpdft import Gradients - else: - from pyscf.grad.lpdft import Gradients - - return Gradients(self, state=state) - - def dump_chk(self, envs): - if self.chkfile is None: - return self - - if self._in_mcscf_env: - self._mc_class.dump_chk(self, envs) - - else: - ci = None - if self.chk_ci: - ci = envs["ci"] - - # if self.chk_veff: - # veff1 = self.veff1 - # veff2 = self.veff2 - - chkfile.dump_lpdft( - self, - chkfile=self.chkfile, - key="pdft", - e_tot=envs["e_tot"], - e_states=envs["e_states"], - e_mcscf=self.e_mcscf, - ci=ci, - ) - - return self - - -class _LPDFTMix(_LPDFT): - """State Averaged Mixed Linerized PDFT - - Saved Results - - e_tot : float - Weighted-average L-PDFT final energy - e_states : ndarray of shape (nroots) - L-PDFT final energies of the adiabatic states - ci : list of length (nroots) of ndarrays - CI vectors in the optimized adiabatic basis of MC-SCF. Related to the - L-PDFT adiabat CI vectors by the expansion coefficients ``si_pdft''. - si_pdft : ndarray of shape (nroots, nroots) - Expansion coefficients of the L-PDFT adiabats in terms of the optimized - MC-SCF adiabats - e_mcscf : ndarray of shape (nroots) - Energies of the MC-SCF adiabatic states - lpdft_ham : list of ndarray of shape (nirreps, nroots, nroots) - L-PDFT Hamiltonian in the MC-SCF adiabatic basis within each irrep - """ - - def __init__(self, mc): - super().__init__(mc) - # Holds the irrep slices for when we need to index into various quantities - self._irrep_slices = None - - def get_lpdft_diag(self): - """Diagonal elements of the L-PDFT Hamiltonian matrix - (H_00^L-PDFT, H_11^L-PDFT, H_22^L-PDFT, ...) - - Returns: - lpdft_diag : ndarray of shape (nroots) - Contains the linear approximation to the MC-PDFT energy. These - are also the diagonal elements of the L-PDFT Hamiltonian - matrix. - """ - return np.concatenate( - [np.diagonal(irrep_ham).copy() for irrep_ham in self.lpdft_ham] - ) - - def get_lpdft_ham(self): - """The L-PDFT effective Hamiltonian matrix - - Returns: - lpdft_ham : ndarray of shape (nroots, nroots) - Contains L-PDFT Hamiltonian elements on the off-diagonals - and PDFT approx energies on the diagonals - """ - return linalg.block_diag(*self.lpdft_ham) - - def _get_ci_adiabats(self, ci_mcscf): - """Get the CI vertors in eigenbasis of L-PDFT Hamiltonian - - ci : list of length nroots - MC-SCF ci vectors - - Returns: - ci : list of length nroots - CI vectors in basis of L-PDFT Hamiltonian eigenvectors - """ - adiabat_ci = [ - np.tensordot( - self.si_pdft[irrep_slice, irrep_slice], - np.asarray(ci_mcscf[irrep_slice]), - axes=1, - ) - for irrep_slice in self._irrep_slices - ] - # Flattens it - return [c for ci_irrep in adiabat_ci for c in ci_irrep] - - def nuc_grad_method(self, state=None): - raise NotImplementedError("MultiState Mix LPDFT nuclear gradients") - - -def linear_multi_state(mc, weights=(0.5, 0.5), **kwargs): - """Build linearized multi-state MC-PDFT method object - - Args: - mc : instance of class _PDFT - - Kwargs: - weights : sequence of floats - - Returns: - si : instance of class _LPDFT - """ - from pyscf.mcscf.addons import StateAverageMCSCFSolver, StateAverageMixFCISolver - - if isinstance(mc, mcpdft.MultiStateMCPDFTSolver): - raise RuntimeError("already a multi-state PDFT solver") - - if isinstance(mc.fcisolver, StateAverageMixFCISolver): - raise RuntimeError("state-average mix type") - - if not isinstance(mc, StateAverageMCSCFSolver): - base_name = mc.__class__.__name__ - mc = mc.state_average(weights=weights, **kwargs) - - else: - base_name = mc.__class__.bases__[0].__name__ - - mcbase_class = mc.__class__ - - class LPDFT(_LPDFT, mcbase_class): - pass - - LPDFT.__name__ = "LIN" + base_name - return LPDFT(mc) - - -def linear_multi_state_mix(mc, fcisolvers, weights=(0.5, 0.5), **kwargs): - """Build SA Mix linearized multi-state MC-PDFT method object - - Args: - mc : instance of class _PDFT - - fcisolvers : fcisolvers to construct StateAverageMixSolver with - - Kwargs: - weights : sequence of floats - - Returns: - si : instance of class _LPDFT - """ - from pyscf.mcscf.addons import StateAverageMCSCFSolver, StateAverageMixFCISolver - - if isinstance(mc, mcpdft.MultiStateMCPDFTSolver): - raise RuntimeError("already a multi-state PDFT solver") - - if not isinstance(mc, StateAverageMCSCFSolver): - base_name = mc.__class__.__name__ - mc = mc.state_average_mix(fcisolvers, weights=weights, **kwargs) - - elif not isinstance(mc.fcisolver, StateAverageMixFCISolver): - raise RuntimeError("already a StateAverageMCSCF solver") - - else: - base_name = mc.__class__.bases__[0].__name__ - - mcbase_class = mc.__class__ - - class LPDFT(_LPDFTMix, mcbase_class): - pass - - LPDFT.__name__ = "LIN" + base_name - return LPDFT(mc) - - -if __name__ == "__main__": - from pyscf import gto, scf - from pyscf import mcpdft - from pyscf.csf_fci import csf_solver - - mol = gto.M( - atom="""H 0 0 0 - H 1.5 0 0""", - basis="sto-3g", - verbose=5, - spin=0, - unit="AU", - symmetry=True, - ) - - mf = scf.RHF(mol).run() - - mc = mcpdft.CASSCF(mf, "tPBE", 2, 2, grids_level=6) - mc.fcisolver = csf_solver(mol, smult=1) - - N_STATES = 2 - - mc = mc.state_average( - [ - 1.0 / float(N_STATES), - ] - * N_STATES - ) - - sc = linear_multi_state(mc) - sc.kernel() diff --git a/pyscf/mcpdft/mcpdft.py b/pyscf/mcpdft/mcpdft.py deleted file mode 100644 index 28d0aa8c7..000000000 --- a/pyscf/mcpdft/mcpdft.py +++ /dev/null @@ -1,885 +0,0 @@ -#!/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 - -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 - - -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 - - -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 - - -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) - - -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 = - 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 = - 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 - -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 diff --git a/pyscf/mcpdft/mspdft.py b/pyscf/mcpdft/mspdft.py deleted file mode 100644 index 1b18329b3..000000000 --- a/pyscf/mcpdft/mspdft.py +++ /dev/null @@ -1,685 +0,0 @@ -#!/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 scipy import linalg -from pyscf import lib -from pyscf.mcscf.addons import StateAverageMCSCFSolver -from pyscf.mcscf.addons import StateAverageMixFCISolver -from pyscf.mcscf.df import _DFCASSCF -from pyscf.mcscf import mc1step -from pyscf.fci import direct_spin1 -from pyscf import mcpdft -from pyscf import __config__ - -# API for general multi-state MC-PDFT method object -# In principle, various forms can be implemented: CMS, XMS, etc. - -# API cleanup desiderata: -# 1. "sipdft", "state_interaction" -> "mspdft", "multi_state" -# 2. Canonicalize function to quickly generate mo_coeff, ci, mo_occ, mo_energy -# for different choices of intermediate, reference, final states. -# 3. Probably "_finalize" stuff? -# 4. checkpoint stuff - -MAX_CYC_DIABATIZE = getattr(__config__, 'mcpdft_mspdft_max_cyc_diabatize', 50) -CONV_TOL_DIABATIZE = getattr(__config__, 'mcpdft_mspdft_conv_tol_diabatize', 1e-8) -SING_TOL_DIABATIZE = getattr(__config__, 'mcpdft_mspdft_sing_tol_diabatize', 1e-8) -NUDGE_TOL_DIABATIZE = getattr(__config__, 'mcpdft_mspdft_nudge_tol_diabatize', 1e-3) - -def make_heff_mcscf (mc, mo_coeff=None, ci=None): - '''Build Hamiltonian matrix in basis of ci vector - - Args: - mc : an instance of MCPDFT class - - Kwargs: - mo_coeff : ndarray of shape (nao, nmo) - MO coefficients - ci : ndarray or list of len (nroots) - CI vectors describing the model space, presumed to be in the - optimized intermediate-state basis - - Returns: - heff_mcscf : ndarray of shape (nroots, nroots) - Effective MC-SCF hamiltonian matrix in the basis of the - provided CI vectors - ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ci is None: ci = mc.ci - - h1, h0 = mc.get_h1eff (mo_coeff) - h2 = mc.get_h2eff (mo_coeff) - h2eff = direct_spin1.absorb_h1e (h1, h2, mc.ncas, mc.nelecas, 0.5) - - def construct_ham_slice(solver, slice, nelecas): - ci_irrep = ci[slice] - if hasattr(solver, "orbsym"): - solver.orbsym = mc.fcisolver.orbsym - - hc_all_irrep = [solver.contract_2e(h2eff, c, mc.ncas, nelecas) for c in ci_irrep] - heff_irrep = np.tensordot(ci_irrep, hc_all_irrep, axes=((1, 2), (1, 2))) - diag_idx = np.diag_indices_from(heff_irrep) - heff_irrep[diag_idx] += h0 - return heff_irrep - - if not isinstance(mc.fcisolver, StateAverageMixFCISolver): - return construct_ham_slice(direct_spin1, slice(0, len(ci)), mc.nelecas) - - irrep_slices = [] - start = 0 - for solver in mc.fcisolver.fcisolvers: - end = start+solver.nroots - irrep_slices.append(slice(start, end)) - start = end - - return [construct_ham_slice(s, irrep, mc.fcisolver._get_nelec(s, mc.nelecas)) - for s, irrep in zip(mc.fcisolver.fcisolvers, irrep_slices)] - - -def si_newton (mc, ci=None, objfn=None, max_cyc=None, conv_tol=None, - sing_tol=None, nudge_tol=None): - '''Optimize the intermediate states describing the model space of - an MS-PDFT calculation by maximizing the provided objective function - using a gradient-ascent algorithm - - Args: - mc : an instance of MSPDFT class - - Kwargs: - ci : ndarray or list of len (nroots) - CI vectors spanning the model space - objfn : callable - Takes CI vectors as a kwarg and returns the value, gradient, - and Hessian of a chosen objective function wrt rotation - between pairs of CI vectors - max_cyc : integer - Maximum number of cycles of the gradient-ascent algorithm - conv_tol : float - Maximum value of both gradient and step vectors at - convergence - sing_tol : float - Tolerance for determining when normal coordinate belongs to - the null space (df = d2f = 0) or when the Hessian is - singular (df != 0, d2f = 0). - nudge_tol : float - Minimum step size along a normal coordinate when the surface - is locally concave. - - Returns: - conv : logical - True if the optimization is converged - ci : list of len (nroots) - Optimized CI vectors describing intermediate states - ''' - - if ci is None: ci = mc.ci - if objfn is None: objfn = mc.diabatizer - if max_cyc is None: max_cyc = getattr (mc, 'max_cyc_diabatize', MAX_CYC_DIABATIZE) - if conv_tol is None: conv_tol = getattr (mc, 'conv_tol_diabatize', CONV_TOL_DIABATIZE) - if sing_tol is None: sing_tol = getattr (mc, 'sing_tol_diabatize', SING_TOL_DIABATIZE) - if nudge_tol is None: nudge_tol = getattr (mc, 'nudge_tol_diabatize', NUDGE_TOL_DIABATIZE) - ci = np.array (ci) # copy - log = lib.logger.new_logger (mc, mc.verbose) - nroots = mc.fcisolver.nroots - rows,col = np.tril_indices(nroots,k=-1) - u = np.eye (nroots) - t = np.zeros((nroots,nroots)) - conv = False - hdr = '{} intermediate-state'.format (mc.__class__.__name__) - f, df, d2f, f_update = objfn (ci=ci) - for it in range(max_cyc): - log.info ("****iter {} ***********".format (it)) - log.info ("{} objective function value = {}".format (hdr, f)) - - # Analyze Hessian - d2f, evecs = linalg.eigh (d2f) - evecs = np.array(evecs) - df = np.dot (df, evecs) - d2f_zero = np.abs (d2f) < sing_tol - df_zero = np.abs (df) < sing_tol - if np.any (d2f_zero & (~df_zero)): - log.warn ("{} Hess is singular!".format (hdr)) - idx_null = d2f_zero & df_zero - df[idx_null] = 0.0 - d2f[idx_null] = -1e-16 - pos_idx = d2f > 0 - neg_def = np.all (d2f < 0) - log.info ("{} Hessian is negative-definite? {}".format (hdr, neg_def)) - - # Analyze gradient - grad_norm = np.linalg.norm(df) - log.info ("{} grad norm = %f".format (hdr), grad_norm) - log.info ("{} grad (normal modes) = {}".format (hdr, df)) - - # Take step - df[pos_idx & (np.abs (df/d2f) < nudge_tol)] = nudge_tol - Dt = df/np.abs (d2f) - step_norm = np.linalg.norm (Dt) - log.info ("{} Hessian eigenvalues: {}".format (hdr, d2f)) - log.info ("{} step vector (normal modes): {}".format (hdr, Dt)) - t[:] = 0 - t[np.tril_indices(t.shape[0], k = -1)] = np.dot (Dt, evecs.T) - t = t - t.T - - if grad_norm < conv_tol and step_norm < conv_tol and neg_def: - conv = True - break - - # I want the states we come from on the rows and the states we - # are going to on the columns: |f> = |i>.Umat. However, the - # antihermitian parameterization of a unitary operator always - # puts it the other way around: |f> = Uop|i>, ~no matter how you - # choose the generator indices~. So I have to transpose here. - # Flipping the sign of t does the same thing, but don't get - # confused: this isn't related to the choice of variables! - u = np.dot (u, linalg.expm (t).T) - f, df, d2f = f_update (u) - - try: - ci = np.tensordot(u.T, ci, 1) - except ValueError as e: - print (u.shape, ci.shape) - raise (e) - if mc.verbose >= lib.logger.DEBUG: - fmt_str = ' ' + ' '.join (['{:5.2f}',]*nroots) - log.debug ("{} final overlap matrix:".format (hdr)) - for row in u: log.debug (fmt_str.format (*row)) - if conv: - log.note ("{} optimization CONVERGED".format (hdr)) - else: - log.note ("{} optimization did not converge after {} " - "cycles".format (hdr, it)) - - return conv, list (ci) - - -class _MSPDFT (mcpdft.MultiStateMCPDFTSolver): - '''MS-PDFT - - Extra attributes for MS-PDFT: - - diabatization: string - The name describing the type of diabatization for I/O. - Currently, only ``CMS'' is available. - max_cyc_diabatize : integer - Maximum cycles of the diabatization iteration. Default is 50. - conv_tol_diabatize : float - Convergence threshold of the diabatization algorithm. Default - is 1e-8. - sing_tol_diabatize : float - Numerical tolerance for null state-rotation modes and - singularities within the diabatization algorithm. Null modes - (e.g., rotation between E1x and E1y states in a linear - molecule) are ignored. Singularities (zero Hessian and - non-zero gradient in the same mode) print a warning. Default - is 1e-8. - nudge_tol_diabatize : float - Minimum step size along modes with positive curvature during - the diabatization algorithm, so as to push away from saddle - points and minima. Default is 1e-3. - - Saved results - - e_tot : float - Weighted-average MS-PDFT final energy - e_states : ndarray of shape (nroots) - MS-PDFT final energies of the adiabatic states - ci : list of length (nroots) of ndarrays - CI vectors in the optimized diabatic basis. Related to the - MC-SCF and MS-PDFT adiabat CI vectors by the expansion - coefficients ``si_mcscf'' and ``si_pdft''. Either set of - adiabat CI vectors can be obtained quickly via - ``get_ci_adiabats'' - si : ndarray of shape (nroots, nroots) - Expansion coefficients for the MS-PDFT adiabats in terms of - the optimized diabatic states - si_pdft : ndarray of shape (nroots, nroots) - Synonym of si - e_mcscf : ndarray of shape (nroots) - Energies of the MC-SCF adiabatic states - si_mcscf : ndarray of shape (nroots, nroots) - Expansion coefficients for the MC-SCF adiabats in terms of - the optimized diabatic states - heff_mcscf : ndarray of shape (nroots, nroots) - Molecular Hamiltonian in the diabatic basis - hdiag_pdft : ndarray of shape (nroots) - MC-PDFT total energies of the optimized diabatic states - ''' - - # Metaclass parent - - def __init__(self, mc, diabatizer, diabatize, diabatization): - self.__dict__.update (mc.__dict__) - keys = set (('diabatizer', 'diabatize', 'diabatization', - 'heff_mcscf', 'hdiag_pdft', - 'get_heff_offdiag', 'get_heff_pdft', - 'si', 'si_mcscf', 'si_pdft', - 'max_cyc_diabatize', 'conv_tol_diabatize', - 'sing_tol_diabatize', 'nudge_tol_diabatize')) - self._diabatizer = diabatizer - self._diabatize = diabatize - self._e_states = None - self.max_cyc_diabatize = MAX_CYC_DIABATIZE - self.conv_tol_diabatize = CONV_TOL_DIABATIZE - self.sing_tol_diabatize = SING_TOL_DIABATIZE - self.nudge_tol_diabatize = CONV_TOL_DIABATIZE - self.diabatization = diabatization - self.si_mcscf = None - self.si_pdft = None - self._keys = set (self.__dict__.keys ()).union (keys) - - @property - def e_states (self): - if self._in_mcscf_env: - return self.fcisolver.e_states - else: - return self._e_states - @e_states.setter - def e_states (self, x): - self._e_states = x - # Unfixed to FCIsolver since MS-PDFT state energies are no longer - # CI solutions - - @property - def si (self): - return self.si_pdft - @si.setter - def si (self, x): - self.si_pdft = x - - def get_heff_offdiag (self): - '''The off-diagonal elements of the effective Hamiltonian matrix - - = heff_mcscf - np.diag (heff_mcscf.diagonal ()) - = ( 0 H_10^* ... ) - ( H_10 0 ... ) - ( ... ... ... ) - - Returns: - heff_offdiag : ndarray of shape (nroots, nroots) - Contains molecular Hamiltonian elements on the off-diagonals - and zero on the diagonals - ''' - idx = np.diag_indices_from (self.heff_mcscf) - heff_offdiag = self.heff_mcscf.copy () - heff_offdiag[idx] = 0.0 - return heff_offdiag - - def get_heff_pdft (self): - '''The MS-PDFT effective Hamiltonian matrix - - = get_heff_offdiag () + np.diag (hdiag_pdft) - = ( EPDFT_0 H_10^* ... ) - ( H_10 EPDFT_1 ... ) - ( ... ... ... ) - - Returns: - heff_pdft : ndarray of shape (nroots, nroots) - Contains molecular Hamiltonian elements on the off-diagonals - and PDFT energies on the diagonals - ''' - idx = np.diag_indices_from (self.heff_mcscf) - heff_pdft = self.heff_mcscf.copy () - heff_pdft[idx] = self.hdiag_pdft - return heff_pdft - - def get_ci_adiabats (self, ci=None, uci='MSPDFT'): - ''' Get the CI vectors in an alternate basis (usually one of the - two adiabatic bases: MCSCF or MSPDFT) - - Kwargs: - ci : list of length nroots - Diabatic ci vectors; defaults to self.ci - uci : 'MSPDFT', 'MCSCF', or square array of length nroots - (String indicating) unitary matrix for transforming - ci vectors - - Returns: - ci : list of length nroots - CI vectors for adiabats - ''' - si_dict = {'MCSCF': self.si_mcscf, - 'MSPDFT': self.si_pdft} - if isinstance (uci, (str,np.bytes_)): - if uci.upper () in si_dict: - uci = si_dict[uci.upper ()] - else: - raise RuntimeError ("valid uci : 'MCSCF', 'MSPDFT', or ndarray") - if ci is None: ci = self.ci - return list (np.tensordot (uci.T, np.asarray (ci), axes=1)) - get_ci_basis=get_ci_adiabats - - def kernel (self, mo_coeff=None, ci0=None, otxc=None, grids_level=None, - grids_attr=None, **kwargs): - self.otfnal.reset (mol=self.mol) # scanner mode safety - if ci0 is None and isinstance (getattr (self, 'ci', None), list): - ci0 = [c.copy () for c in self.ci] - self.optimize_mcscf_(mo_coeff=mo_coeff, ci0=ci0) - diab_conv, self.ci = self.diabatize (ci=self.ci, ci0=ci0, **kwargs) - self.converged = self.converged and diab_conv - self.heff_mcscf = self.make_heff_mcscf () - e_mcscf, self.si_mcscf = self._eig_si (self.heff_mcscf) - if abs (linalg.norm (self.e_mcscf-e_mcscf)) > 1e-9: - raise RuntimeError (("Sanity fault: e_mcscf ({}) != " - "self.e_mcscf ({})").format (e_mcscf, - self.e_mcscf)) - self.hdiag_pdft = self.compute_pdft_energy_( - otxc=otxc, grids_level=grids_level, grids_attr=grids_attr)[-1] - self.e_states, self.si_pdft = self._eig_si (self.get_heff_pdft ()) - self.e_tot = np.dot (self.e_states, self.weights) - self._log_diabats () - self._log_adiabats () - return (self.e_tot, self.e_ot, self.e_mcscf, self.e_cas, self.ci, - self.mo_coeff, self.mo_energy) - - def optimize_mcscf_(self, mo_coeff=None, ci0=None, **kwargs): - # Initialize in an adiabatic basis - if ci0 is not None: - if mo_coeff is None: mo_coeff = self.mo_coeff - heff_mcscf = self.make_heff_mcscf (mo_coeff, ci0) - e, self.si_mcscf = self._eig_si (heff_mcscf) - ci1 = self.get_ci_adiabats (ci=ci0, uci='MCSCF') - else: - ci1 = None - return super().optimize_mcscf_(mo_coeff=mo_coeff, ci0=ci1, **kwargs) - - # All of the below probably need to be wrapped over solvers in - # multi-state-mix metaclass - - def diabatize (self, ci=None, ci0=None, **kwargs): - '''Optimize the ``intermediate'' diabatic states of an MS-PDFT - calculation in the space defined by the MC-SCF ``reference'' - adiabatic states. - - Kwargs - ci : list of ndarrays of length nroots - CI vectors defining the model space, usually from a - or CASSCF kernel call - ci0 : list of ndarrays of length nroots - Initial guess for optimized diabatic CI vectors, - possibly spanning a slightly different space, usually at - a different geometry (i.e., during a geometry - optimization or dynamics trajectory) - - Returns - conv : logical - Reports whether the diabatization algorithm converged - successfully - ci : list of ndarrays of length = nroots - CI vectors of the optimized diabatic states - ''' - if ci is None: ci = self.ci - if ci0 is not None: - ovlp = np.tensordot (np.asarray (ci).conj (), np.asarray (ci0), - axes=((1,2),(1,2))) - u, svals, vh = linalg.svd (ovlp) - ci = self.get_ci_basis (ci=ci, uci=np.dot (u,vh)) - return self._diabatize (self, ci=ci, **kwargs) - - def diabatizer (self, mo_coeff=None, ci=None): - '''Computes the value, gradient vector, and Hessian matrix with - respect to pairwise state rotations of the objective function - maximized by the optimized diabatic (``intermediate'') states. - - Kwargs - mo_coeff : ndarray of shape (nao,nmo) - MO coefficients - ci : list of ndarrays of length nroots - CI vectors - - Returns: - f : float - Objective function value for states - df : ndarray of shape (npairs = nroots*(nroots-1)/2) - Gradient vector of objective function with respect to - pairwise rotations between states - d2f : ndarray of shape (npairs,npairs) - Hessian matrix of objective function with respect to - pairwise rotations between states - f_update : callable - Takes a unitary matrix and returns f, df, and d2f as - above. Some kinds of MS-PDFT can be sped up using - intermediates. If so, the _diabatizer function can - provide f_update. Otherwise, it defaults to running - _diabatizer itself on a rotated set of CI vectors. - ''' - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - rets = self._diabatizer (self, mo_coeff=mo_coeff, ci=ci) - f, df, d2f = rets[:3] - if len (rets) > 3 and callable (rets[3]): - f_update = rets[3] - else: - def f_update (u=1): - ci1 = self.get_ci_basis(ci=ci, uci=u) - f1, df1, d2f1 = self._diabatizer ( - self, mo_coeff=mo_coeff, ci=ci1) - return f1, df1, d2f1 - return f, df, d2f, f_update - - def _eig_si (self, heff): - return linalg.eigh (heff) - - make_heff_mcscf = make_heff_mcscf - - def _log_diabats (self): - # Information about the intermediate states - hdiag_mcscf = self.heff_mcscf.diagonal () - hdiag_pdft = self.hdiag_pdft - nroots = len (hdiag_pdft) - log = lib.logger.new_logger (self, self.verbose) - f, df, d2f = self.diabatizer ()[:3] - hdr = '{} diabatic (intermediate)'.format (self.__class__.__name__) - log.note ('%s objective function value = %.15g |grad| = %.7g', hdr, f, linalg.norm (df)) - log.note ('%s average energy EPDFT = %.15g EMCSCF = %.15g', hdr, - np.dot (self.weights, hdiag_pdft), np.dot (self.weights, hdiag_mcscf)) - log.note ('%s states:', hdr) - if getattr (self.fcisolver, 'spin_square', None): - ss = self.fcisolver.states_spin_square (self.ci, self.ncas, - self.nelecas)[0] - for i in range (nroots): - log.note (' State %d EPDFT = %.15g EMCSCF = %.15g' - ' S^2 = %.7f', i, hdiag_pdft[i], - hdiag_mcscf[i], ss[i]) - else: - for i in range (nroots): - log.note (' State %d EPDFT = %.15g EMCSCF = ' - '%.15g', i, hdiag_pdft[i], hdiag_mcscf[i]) - log.info ('MS-PDFT effective Hamiltonian matrix in diabatic basis:') - fmt_str = ' '.join (['{:9.5f}',]*nroots) - for row in self.get_heff_pdft(): log.info (fmt_str.format (*row)) - log.info ('Diabatic states (columns) in terms of reference states ' - '(rows):') - for row in self.si_mcscf.T: log.info (fmt_str.format (*row)) - - def _log_adiabats (self): - # Information about the final states - log = lib.logger.new_logger (self, self.verbose) - nroots = len (self.e_states) - log.note ('%s adiabatic (final) states:', self.__class__.__name__) - if getattr (self.fcisolver, 'spin_square', None): - ci = np.tensordot (self.si.T, np.asarray (self.ci), axes=1) - ss = self.fcisolver.states_spin_square (ci, self.ncas, - self.nelecas)[0] - for i in range (nroots): - log.note (' State %d weight %g EMSPDFT = %.15g S^2 = %.7f', - i, self.weights[i], self.e_states[i], ss[i]) - else: - for i in range (nroots): - log.note (' State %d weight %g EMSPDFT = %.15g', i, - self.weights[i], self.e_states[i]) - - def nuc_grad_method (self): - 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.mspdft import Gradients - else: - 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") - 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.mspdft import ElectricDipole - if not lib.isinteger (state): - raise RuntimeError ('Permanent dipole requires a single state') - dip_obj = ElectricDipole(self) - mol_dipole = dip_obj.kernel (state=state, unit=unit, origin=origin) - return mol_dipole - - def trans_moment (self, unit='Debye', origin='Coord_Center', state=None): - 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.trans_dip_moment.mspdft import TransitionDipole - if not hasattr(state, '__len__') or len(state) !=2: - raise RuntimeError ('Transition dipole requires two states') - tran_dip_obj = TransitionDipole(self) - mol_trans_dipole = tran_dip_obj.kernel (state=state, unit=unit, origin=origin) - return mol_trans_dipole - -def get_diabfns (obj): - '''Interpret the name of the MS-PDFT method as a pair of functions - which optimize the intermediate states and calculate the power - series in the corresponding objective function to second order. - - Args: - obj : string - Specify particular MS-PDFT method. Currently, only "CMS" is - supported. Not case-sensitive. - - Returns: - diabatizer : callable - Takes model-space CI vectors in a trial intermediate-state - basis and returns the value and first and second derivatives - of the objective function specified by obj - diabatize : callable - Takes model-space CI vectors and returns CI vectors in the - optimized intermediate-state basis - ''' - - if obj.upper () == 'CMS': - from pyscf.mcpdft.cmspdft import e_coul as diabatizer - diabatize = si_newton - - elif obj.upper() == "XMS": - from pyscf.mcpdft.xmspdft import safock_energy as diabatizer - from pyscf.mcpdft.xmspdft import solve_safock as diabatize - - else: - raise RuntimeError ('MS-PDFT type not supported') - - return diabatizer, diabatize - -def multi_state (mc, weights=(0.5,0.5), diabatization='CMS', **kwargs): - ''' Build multi-state MC-PDFT method object - - Args: - mc : instance of class _PDFT - - Kwargs: - weights : sequence of floats - diabatization : objective-function type - Currently supports only 'cms' - - Returns: - si : instance of class _MSPDFT - ''' - - if isinstance (mc, mcpdft.MultiStateMCPDFTSolver): - raise RuntimeError ('already a multi-state PDFT solver') - if isinstance (mc.fcisolver, StateAverageMixFCISolver): - raise RuntimeError ('state-average mix type') - if not isinstance (mc, StateAverageMCSCFSolver): - base_name = mc.__class__.__name__ - mc = mc.state_average (weights=weights, **kwargs) - else: - base_name = mc.__class__.__bases__[0].__name__ - mcbase_class = mc.__class__ - diabatizer, diabatize = get_diabfns (diabatization) - - class MSPDFT (_MSPDFT, mcbase_class): - pass - MSPDFT.__name__ = diabatization.upper () + base_name - return MSPDFT (mc, diabatizer, diabatize, diabatization) - - -if __name__ == '__main__': - # This ^ is a convenient way to debug code that you are working on. The - # code in this block will only execute if you run this python script as the - # input directly: "python mspdft.py". - - from pyscf import scf, gto - from pyscf.tools import molden # My version is better for MC-SCF - from pyscf.fci import csf_solver - xyz = '''O 0.00000000 0.08111156 0.00000000 - H 0.78620605 0.66349738 0.00000000 - H -0.78620605 0.66349738 0.00000000''' - mol = gto.M (atom=xyz, basis='sto-3g', symmetry=False, output='mspdft.log', - verbose=lib.logger.DEBUG) - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'tPBE', 4, 4).set (fcisolver = csf_solver (mol, 1)) - mc = mc.multi_state ([1.0/3,]*3, 'cms').run () - - diff --git a/pyscf/mcpdft/nr_numint.c b/pyscf/mcpdft/nr_numint.c deleted file mode 100644 index 6a14771b1..000000000 --- a/pyscf/mcpdft/nr_numint.c +++ /dev/null @@ -1,123 +0,0 @@ -/* Copyright 2014-2020 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 - */ - -/* Copied from pyscf/pyscf/lib/dft/nr_numint.c on 05/18/2020 - MRH - VXC -> VOT; start with dot_ao_ao and ao_empty_blocks and throw - away everything else */ - -#include -#include -#include -#include - -// -------------------------------------------------------------- -// -------------------- begin PySCF includes -------------------- -// -------------------------------------------------------------- - -#include "config.h" -#include "gto/grid_ao_drv.h" -#include "np_helper/np_helper.h" -#include "vhf/fblas.h" - -// MR Hennefarth 05/26/2023: Removes implicit function warning. Then will give -// warnings about calling parameters. This function defined in -// pyscf/lib/libdft.so. No header file to include, hence the need for the -// extern. -extern int VXCao_empty_blocks(int8_t*, uint8_t*, int*, int*); - -// -------------------------------------------------------------- -// --------------------- end PySCF includes --------------------- -// -------------------------------------------------------------- - -#define BOXSIZE 56 - -/* vv[n,m] = ao[n,ngrids] * mo[m,ngrids] */ -/* MRH 05/18/2020: dot_ao_ao -> dot_ao_mo requires - 1. New variable nmo - 2. nbox -> nboxi, nboxj - 3. Never hermitian: I can handle mo, mo case using dot_ao_ao and null mask - 4. Second degree of freedom has nothing to do with mask: remove conditional -Notice that the linear algebra in column-major order is -mo(ngrids,nmo).T @ ao(ngrids, nao) = vv(nmo,nao) */ -static void dot_ao_mo(double *vv, double *ao, double *mo, - int nao, int nmo, int ngrids, int bgrids, - uint8_t *non0table, int *shls_slice, int *ao_loc) -{ - int nboxi = (nao+BOXSIZE-1) / BOXSIZE; - int nboxj = (nmo+BOXSIZE-1) / BOXSIZE; - int8_t empty[nboxi]; - int has0 = VXCao_empty_blocks(empty, non0table, shls_slice, ao_loc); - - const char TRANS_T = 'T'; - const char TRANS_N = 'N'; - const double D1 = 1; - if (has0) { - int ib, jb, leni, lenj; - size_t b0i, b0j; - - for (ib = 0; ib < nboxi; ib++) { - if (!empty[ib]) { - b0i = ib * BOXSIZE; - leni = MIN(nao-b0i, BOXSIZE); - for (jb = 0; jb < nboxj; jb++) { - b0j = jb * BOXSIZE; - lenj = MIN(nmo-b0j, BOXSIZE); - dgemm_(&TRANS_T, &TRANS_N, &lenj, &leni, &bgrids, &D1, - mo+b0j*ngrids, &ngrids, ao+b0i*ngrids, &ngrids, - &D1, vv+b0i*nao+b0j, &nmo); - } - } } - } else { - dgemm_(&TRANS_T, &TRANS_N, &nmo, &nao, &bgrids, - &D1, mo, &ngrids, ao, &ngrids, &D1, vv, &nmo); - } -} - - -/* vv[nao,nmo] = ao[i,nao] * mo[i,nmo] */ -/* MRH 05/18/2020: dot_ao_ao -> dot_ao_mo straightforward because it's - multithreaded over grid blocks. Just make sure the variable names - and allocations all get changed and hermi is taken out. */ -void VOTdot_ao_mo(double *vv, double *ao, double *mo, - int nao, int nmo, int ngrids, int nbas, - unsigned char *non0table, int *shls_slice, int *ao_loc) -{ - const int nblk = (ngrids+BLKSIZE-1) / BLKSIZE; - memset(vv, 0, sizeof(double) * nao * nmo); - -#pragma omp parallel -{ - int ip, ib; - double *v_priv = calloc(nao*nmo+2, sizeof(double)); -#pragma omp for nowait schedule(static) - for (ib = 0; ib < nblk; ib++) { - ip = ib * BLKSIZE; - dot_ao_mo(v_priv, ao+ip, mo+ip, - nao, nmo, ngrids, MIN(ngrids-ip, BLKSIZE), - non0table+ib*nbas, shls_slice, ao_loc); - } -#pragma omp critical - { - for (ip = 0; ip < nao*nmo; ip++) { - vv[ip] += v_priv[ip]; - } - } - free(v_priv); -} -} - diff --git a/pyscf/mcpdft/otfnal.py b/pyscf/mcpdft/otfnal.py deleted file mode 100644 index d3e64780a..000000000 --- a/pyscf/mcpdft/otfnal.py +++ /dev/null @@ -1,1207 +0,0 @@ -#!/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 -import copy -from scipy import linalg -from pyscf import lib, dft -from pyscf.lib import logger -from pyscf.dft2 import libxc -from pyscf.dft.gen_grid import Grids -from pyscf.dft.numint import _NumInt, NumInt -from pyscf.mcpdft import pdft_veff, tfnal_derivs, _libxc, _dms, pdft_feff, pdft_eff -from pyscf.mcpdft.otpd import get_ontop_pair_density -from pyscf import __config__ - -FT_R0 = getattr(__config__, 'mcpdft_otfnal_ftransfnal_R0', 0.9) -FT_R1 = getattr(__config__, 'mcpdft_otfnal_ftransfnal_R1', 1.15) -FT_A = getattr(__config__, 'mcpdft_otfnal_ftransfnal_A', -475.60656009) -FT_B = getattr(__config__, 'mcpdft_otfnal_ftransfnal_B', -379.47331922) -FT_C = getattr(__config__, 'mcpdft_otfnal_ftransfnal_C', -85.38149682) - -OT_ALIAS = {'MC23': 'tMC23'} -OT_HYB_ALIAS = {'PBE0' : '0.25*HF + 0.75*PBE, 0.25*HF + 0.75*PBE', - } - -REG_OT_FUNCTIONALS={} - -# ALIAS for the preset on-top functional -OT_PRESET={ - # Reparametrized-M06L: rep-M06L - # MC23 = { '0.2952*HF + (1-0.2952)*rep-M06L, 0.2952*HF + (1-0.2952)*rep-M06L'}} - # XC_ID_MGGA_C_M06_L = 233 - # XC_ID_MGGA_X_M06_L = 203 - 'MC23':{ - 'xc_base':'M06L', - 'ext_params':{203: np.array([3.352197, 6.332929e-01, -9.469553e-01, 2.030835e-01, - 2.503819, 8.085354e-01, -3.619144, -5.572321e-01, - -4.506606, 9.614774e-01, 6.977048, -1.309337, -2.426371, - -7.896540e-03, 1.364510e-02, -1.714252e-06, -4.698672e-05, 0.0]), - 233: np.array([0.06, 0.0031, 0.00515088, 0.00304966, 2.427648, 3.707473, - -7.943377, -2.521466, 2.658691, 2.932276, -8.832841e-01, - -1.895247, -2.899644, -5.068570e-01, -2.712838, 9.416102e-02, - -3.485860e-03, -5.811240e-04, 6.668814e-04, 0.0, 2.669169e-01, - -7.563289e-02, 7.036292e-02, 3.493904e-04, 6.360837e-04, 0.0, 1e-10])}, - 'hyb':(0.2952,0.2952,0), - 'facs':(0.7048,0.7048)} - } - -def register_otfnal(xc_code, preset): - ''' - This function registers the new on-top functional if it hasn't been - registered previously. - Args: - xc_code: str - The name of the on-top functional to be registered. - preset: dict - The dictionary containing the information about the on-top functional - to be registered. - xc_base: str - The name of the underylying KS-functional in the libxc library. - ext_params: dict, with LibXC exchange and correlation functional integer ID as key, and - an array-like object containing the functional parameters as value. - hyb: tuple - The hybrid functional parameters. - facs: tuple - The mixing factors. - kwargs: dict - The additional keyword arguments. - ''' - libxc_register_code = xc_code.upper () - libxc_base_code = preset['xc_base'] - ext_params = preset['ext_params'] - hyb = preset.get('hyb', None) - facs = preset.get('facs', None) - libxc.register_custom_functional_(libxc_register_code, libxc_base_code, - ext_params=ext_params, hyb=hyb, facs=facs) - REG_OT_FUNCTIONALS[xc_code.upper()] = {'hyb_x':preset.get('hyb',[0])[0], - 'hyb_c':preset.get('hyb',[0])[0]} - -def unregister_otfnal(xc_code): - ''' - This function unregisters the on-top functional if it has been registered - previously. - Args: - xc_code: str - The name of the on-top functional to be unregistered. - ''' - try: - if xc_code.upper() in REG_OT_FUNCTIONALS: - libxc_unregister_code = xc_code.upper() - libxc.unregister_custom_functional_(libxc_unregister_code) - del REG_OT_FUNCTIONALS[xc_code.upper()] - - except Exception as e: - raise RuntimeError(f"Failed to unregister functional '{xc_code}': {e}") from e - -def _get_registered_ot_functional(xc_code, mol): - ''' - This function returns the on-top functional if it has been registered - previously. - Args: - xc_code: str - The name of the on-top functional to be registered. - ''' - if (xc_code.upper() not in REG_OT_FUNCTIONALS) and (xc_code.upper() in OT_PRESET): - preset = OT_PRESET[xc_code.upper()] - register_otfnal(xc_code, preset) - logger.info(mol, 'Registered the on-top functional: %s', xc_code) - return xc_code.upper() - -def energy_ot (ot, casdm1s, casdm2, mo_coeff, ncore, max_memory=2000, hermi=1): - '''Compute the on-top energy - the last term in - - E_MCPDFT = h_pq l_pq + 1/2 v_pqrs l_pq l_rs + E_ot[rho,Pi] - - Args: - ot : an instance of otfnal class - casdm1s : ndarray of shape (2, ncas, ncas) - Contains spin-separated one-body density matrices in an - active-orbital basis - casdm2 : ndarray of shape (ncas, ncas, ncas, ncas) - Contains spin-summed two-body density matrix in an active- - orbital basis - mo_coeff : ndarray of shape (nao, nmo) - Contains molecular orbital coefficients for active-space - orbitals. Columns ncore through ncore+ncas give the basis - in which casdm1s and casdm2 are expressed. - ncore : integer - Number of doubly occupied inactive "core" orbitals not - explicitly included in casdm1s and casdm2 - - Kwargs: - max_memory : int or float - maximum cache size in MB - default is 2000 - hermi : int - 1 if 1rdms are assumed hermitian, 0 otherwise - - Returns : float - The MC-PDFT on-top (nonclassical) energy - ''' - E_ot = 0.0 - ni, xctype = ot._numint, ot.xctype - if xctype=='HF': return E_ot - dens_deriv = ot.dens_deriv - Pi_deriv = ot.Pi_deriv - - nao = mo_coeff.shape[0] - ncas = casdm2.shape[0] - cascm2 = _dms.dm2_cumulant (casdm2, casdm1s) - dm1s = _dms.casdm1s_to_dm1s (ot, casdm1s, mo_coeff=mo_coeff, ncore=ncore, - ncas=ncas) - mo_cas = mo_coeff[:,ncore:][:,:ncas] - - t0 = (logger.process_clock (), logger.perf_counter ()) - make_rho = tuple (ni._gen_rho_evaluator (ot.mol, dm1s[i,:,:], hermi=hermi, with_lapl=False) for - i in range(2)) - for ao, mask, weight, _ in ni.block_loop (ot.mol, ot.grids, nao, - dens_deriv, max_memory): - rho = np.asarray ([m[0] (0, ao, mask, xctype) for m in make_rho]) - t0 = logger.timer (ot, 'untransformed density', *t0) - Pi = get_ontop_pair_density (ot, rho, ao, cascm2, mo_cas, - Pi_deriv, mask) - t0 = logger.timer (ot, 'on-top pair density calculation', *t0) - if rho.ndim == 2: - rho = np.expand_dims (rho, 1) - Pi = np.expand_dims (Pi, 0) - E_ot += ot.eval_ot (rho, Pi, dderiv=0, weights=weight)[0].dot (weight) - t0 = logger.timer (ot, 'on-top energy calculation', *t0) - - return E_ot - -class otfnal: - r''' Parent class of on-top pair-density functional. The main - callable is ``eval_ot,'' which is comparable to pyscf.dft.libxc - ``eval_xc.'' A true ``kernel'' method, which would take arbitrary - 1- and 2-RDMs and return the total PDFT energy, awaits design - decisions on how far I'm willing/able to generalize the otpd - functions. For instance, in MP2 or CCSD, the 2-RDM spans the - whole orbital space and it may not be possible to hold it in - memory. At present, it's all designed around MC-SCF, which is - why the ``kernel'' function that actually calculates the energy - is in mcpdft.py instead of here. - - Attributes: - mol : object of class pyscf.gto.mole - grids : object of class pyscf.dft.gen_grid.Grids - eval_ot : function with calling signature shown below - _numint : object of class pyscf.dft.NumInt - member functions "hybrid_coeff", "nlc_coeff, "rsh_coeff", - and "_xc_type" (at least) must be overloaded; see below - otxc : string - name of on-top pair-density exchange-correlation functional - ''' - - def __init__ (self, mol): - self.mol = mol - self.verbose = mol.verbose - self.stdout = mol.stdout - - Pi_deriv = 0 - - def _init_info (self): - logger.info (self, 'Building %s functional', self.otxc) - hyb = self._numint.rsh_and_hybrid_coeff(self.otxc, spin=self.mol.spin)[2] - if hyb[0] > 0: - logger.info (self, 'Hybrid functional with %s CASSCF exchange', - hyb) - - @property - def xctype (self): - return self._numint._xc_type (self.otxc) - - @property - def dens_deriv (self): - return ['LDA', 'GGA', 'MGGA'].index (self.xctype) - - def eval_ot (self, rho, Pi, dderiv=0, **kwargs): - r''' Evaluate the on-dop energy and its functional derivatives - on a grid - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Kwargs: - dderiv : integer - Order of derivatives to return - - Returns: - eot : ndarray of shape (ngrids) - integrand of the on-top exchange-correlation energy - vot : (array_like (rho), array_like (Pi)) or None - first functional derivative of Eot wrt (density, pair- - density) and their derivatives - fot : ndarray of shape (*,ngrids) or None - second functional derivative of Eot wrt density, pair- - density, and derivatives; first dimension is lower- - triangular matrix elements corresponding to the basis - (rho, Pi, |drho|^2, drho'.dPi, |dPi|) stopping at Pi (3 - elements) for t-LDA and |drho|^2 (6 elements) for t-GGA. - ''' - raise NotImplementedError("on-top xc functional not defined") - - energy_ot = energy_ot - get_eff_1body = pdft_eff.get_eff_1body - get_eff_2body = pdft_eff.get_eff_2body - get_eff_2body_kl = pdft_eff.get_eff_2body_kl - - get_veff_1body = pdft_veff.get_veff_1body - get_veff_2body = pdft_veff.get_veff_2body - get_veff_2body_kl = pdft_veff.get_veff_2body_kl - - get_feff_1body = pdft_feff.get_feff_1body - get_feff_2body = pdft_feff.get_feff_2body - - def reset (self, mol=None): - ''' Discard cached grid data and optionally update the mol ''' - if mol is not None: - self.mol = mol - self.grids.reset (mol=mol) - -class transfnal (otfnal): - __doc__ = otfnal.__doc__ + r''' - - ``translated functional'' of Li Manni et al., JCTC 10, 3669 (2014). - The extra attributes are all callables; see their docstrings for - more information. - - Args: - ks : object of :class:`dft.RKS` - ks.xc is the Kohn-Sham functional being ``translated'' - - ''' - - transl_prefix='t' - - def __init__ (self, ks, **kwargs): - otfnal.__init__(self, ks.mol, **kwargs) - self.otxc = 't' + ks.xc - self._numint = copy.copy (ks._numint) - self._numint.libxc = libxc - self.grids = copy.copy (ks.grids) - self._numint.hybrid_coeff = t_hybrid_coeff.__get__(self._numint) - self._numint.nlc_coeff = t_nlc_coeff.__get__(self._numint) - self._numint.rsh_coeff = t_rsh_coeff.__get__(self._numint) - self._numint.eval_xc = t_eval_xc.__get__(self._numint) - self._numint._xc_type = t_xc_type.__get__(self._numint) - self._init_info () - - def get_ratio (self, Pi, rho_avg): - r''' R = Pi / [rho/2]^2 = Pi / rho_avg^2 - An intermediate quantity when computing the translated spin - densities - - Note this function returns 1 for values and 0 for - derivatives for every point where the charge density is - close to zero (i.e., convention: 0/0 = 1) - - Args: - Pi : ndarray of shape (*,ngrids) - Contains on-top pair density on a grid - rho_avg : ndarray of shape (*,ngrids) - Contains the average of the spin-up and spin-down - charge densities on a grid, (rho[0]+rho[1])/2 - - Returns: - R : ndarray of shape (*,ngrids) - on-top ratio - ''' - nderiv = min (rho_avg.shape[0], Pi.shape[0]) - ngrids = rho_avg.shape[1] - assert (Pi.shape[1] == ngrids) - if nderiv > 4: - raise NotImplementedError("derivatives above order 1") - - R = np.zeros ((nderiv,ngrids), dtype=Pi.dtype) - R[0,:] = 1 - R[0, Pi[0] == 0] = 0.0 - idx = rho_avg[0] >= (1e-15 / 2) - # Chain rule! - for ideriv in range(nderiv): - R[ideriv,idx] = Pi[ideriv,idx] / rho_avg[0,idx] / rho_avg[0,idx] - - # Product rule! - for ideriv in range (1,nderiv): - R[ideriv,idx] -= (2 * rho_avg[ideriv,idx] * R[0,idx] - / rho_avg[0,idx]) - return R - - def get_rho_translated (self, Pi, rho, _fn_deriv=0): - r''' Compute the "translated" alpha and beta densities: - For the unrestricted case, - rho = [rho^a, rho^b] - Here: - rho^a will have dim of 1,4 or 6 depends on the functional. For MGGA, - rho^a = [rho_u,grad_xu, grad_yu, grad_zu, laplacian_u, tau_u] - Similar for rho_b. - - The translation is done as follows: - - rho_t^a = (rho/2) * (1 + zeta) - rho_t^b = (rho/2) * (1 - zeta) - rho'_t^a = (rho'/2) * (1 + zeta) - rho'_t^b = (rho'/2) * (1 - zeta) - tau_t^a = (tau/2) * (1 + zeta) - tau_t^b = (tau/2) * (1 - zeta) - - See "get_zeta" for the meaning of "zeta" - - Args: - Pi : ndarray of shape (*, ngrids) - containing on-top pair density [and derivatives] - rho : ndarray of shape (2, *, ngrids) - containing spin density [and derivatives] - - Kwargs: - _fn_deriv : integer - Order of functional derivatives of zeta to compute. - In "translated" functionals, no functional derivatives - of zeta are used. This kwarg is used for convenience - when calling from children classes. It changes the - return signature and should not normally be touched by - users. - - Returns: - rho_t : ndarray of shape (2,*,ngrids) - Translated spin density (and derivatives) in case of LDA or GGAs - Translated spin density, derivatives, and kinetic energy density in case of MGGA - ''' - - # For nonzero charge & pair density, set alpha dens = beta dens - # = 1/2 charge dens - rho_avg = (rho[0,:,:] + rho[1,:,:]) / 2 - rho_t = rho.copy () - rho_t[0] = rho_t[1] = rho_avg - - # For 0 <= ratio < 1 and 0 <= rho, correct spin density using on-top - # density - nderiv_R = Pi.shape[0] if _fn_deriv else 1 - R = self.get_ratio (Pi[0:nderiv_R,:], rho_avg[0:nderiv_R,:]) - zeta = self.get_zeta (R, fn_deriv=_fn_deriv) - - # Chain rule! - w = rho_avg * zeta[0:1] - rho_t[0] += w - rho_t[1] -= w - - if _fn_deriv > 0: return rho_t, R, zeta - return rho_t - - def get_zeta (self, R, fn_deriv=0, _Rmax=1): - r''' Compute the intermediate zeta used to compute the - translated spin densities and its functional derivatives - - From the original translation [Li Manni et al., JCTC 10, 3669 - (2014)]: - - zeta = (1-ratio)^(1/2) ; ratio < 1 - = 0 ; otherwise - - Args: - R : ndarray of shape (*,ngrids) - Ratio (4Pi/rho^2) and possibly its spatial derivatives - Only the first row is used in this function - - Kwargs: - fn_deriv : integer - order of functional derivative (d^n z / dR^n) to return - along with the value of zeta - _Rmax : float - maximum value of R for which to compute zeta or its - derivatives; columns of zeta with R[0]>_Rmax are zero. - This is a hook for the ``fully-translated'' child class - and should not be touched normally. - - Returns: - zeta : ndarray of shape (fn_deriv+1, ngrids) - ''' - if R.ndim == 2: R = R[0] - ngrids = R.size - zeta = np.zeros ((fn_deriv+1, ngrids), dtype=R.dtype) - idx = R < _Rmax - zeta[0,idx] = np.sqrt (1.0 - R[idx]) - if fn_deriv: - zeta[1,idx] = -0.5 / zeta[0,idx] - if fn_deriv > 1: fac = 0.5 / (1.0-R[idx]) - for n in range (1,fn_deriv): - zeta[n+1,idx] = zeta[n,idx] * (2*n-1) * fac - return zeta - - def split_x_c (self): - ''' Get one translated functional for just the exchange and one - for just the correlation part of the energy. - - Returns: - xfnal : object of :class:`transfnal` - this functional, but only the exchange part - cfnal : object of :class:`transfnal` - this functional, but only the correlation part - ''' - xc_base = self.otxc[len (self.transl_prefix):] - x_code, c_code = _libxc.split_x_c_comma (xc_base) - x_code = self.transl_prefix + x_code + ',' - c_code = self.transl_prefix + ',' + c_code - xfnal = copy.copy (self) - xfnal._numint = copy.copy (self._numint) - xfnal.grids = copy.copy (self.grids) - xfnal.verbose = self.verbose - xfnal.stdout = self.stdout - xfnal.otxc = x_code - cfnal = copy.copy (self) - cfnal._numint = copy.copy (self._numint) - cfnal.grids = copy.copy (self.grids) - cfnal.verbose = self.verbose - cfnal.stdout = self.stdout - cfnal.otxc = c_code - return xfnal, cfnal - - def jT_op (self, x, rho, Pi): - r''' Evaluate jTx = (x.j)T where j is the Jacobian of the - translated densities in terms of the untranslated density and - pair density - - Args: - x : ndarray of shape (2,*,ngrids) - Usually, a functional derivative of the on-top xc energy - wrt translated densities - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Returns: ndarray of shape (*,ngrids) - Usually, a functional derivative of the on-top pair density - exchange-correlation energy wrt to total density and its - derivatives. The potential must be spin-symmetric in - pair-density functional theory. - 2 rows for tLDA, 3 rows for tGGA, and 4 rows for meta-GGA - ''' - # ordering: rho, Pi, |rho'|^2, tau - ncol = (2, 3, 4)[self.dens_deriv] - ngrid = rho.shape[-1] - jTx = np.zeros ((ncol,ngrid), dtype=x[0].dtype) - rho = rho.sum (0) - R = self.get_ratio (Pi, rho/2) - zeta = self.get_zeta (R, fn_deriv=1) - jTx[:2] = tfnal_derivs._gentLDA_jT_op (x, rho, Pi, R, zeta) - if self.dens_deriv > 0: - jTx[:3] += tfnal_derivs._tGGA_jT_op (x, rho, Pi, R, zeta) - if self.dens_deriv > 1: - jTx[:4] += tfnal_derivs._tmetaGGA_jT_op(x, rho, Pi, R, zeta) - - return jTx - - def d_jT_op (self, x, rho, Pi): - r''' Evaluate the x.(nabla j) contribution to the second density - derivatives of the on-top energy in terms of the untranslated - density and pair density - - Args: - x : ndarray of shape (2,*,ngrids) - Usually, a functional derivative of the on-top xc energy - wrt translated densities - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Returns: ndarray of shape (*,ngrids) - second derivative of the translation dotted with x - 3 rows for tLDA and 5 rows for tGGA - ''' - nrow = 3 + 2*int(self.dens_deriv>0) - f = np.zeros ((nrow, x[0].shape[-1]), dtype=x[0].dtype) - - rho = rho.sum (0) - R = self.get_ratio (Pi, rho/2) - zeta = self.get_zeta (R, fn_deriv=2) - - f[:3] = tfnal_derivs._gentLDA_d_jT_op (x, rho, Pi, R, zeta) - if self.dens_deriv: - f[:] += tfnal_derivs._tGGA_d_jT_op (x, rho, Pi, R, zeta) - - if self.verbose >= logger.DEBUG: - idx = zeta[0] == 0 - logger.debug (self, 'MC-PDFT fot zeta check: %d zeta=0 columns', - np.count_nonzero (idx)) - if np.count_nonzero (idx): - for ix, frow in enumerate (f): - logger.debug (self, 'MC-PDFT fot zeta check: f[%d] norm ' - 'over zeta=0 columns: %e', ix, linalg.norm (frow[idx])) - - return f - - def eval_ot (self, rho, Pi, dderiv=1, weights=None, _unpack_vot=True): - - eot, vot, fot = tfnal_derivs.eval_ot (self, rho, Pi, dderiv=dderiv, - weights=weights, _unpack_vot=_unpack_vot) - if (self.verbose <= logger.DEBUG) or (dderiv<1) or (weights is None): - return eot, vot, fot - if rho.ndim == 2: rho = rho[:,None,:] - if Pi.ndim == 1: Pi = Pi[None,:] - rho_tot = rho.sum (0) - nvr = rho_tot.shape[0] - ngrids = rho_tot.shape[-1] - - r0 = 2*(np.random.rand (ngrids)-1) - - for p in range (20): - # ~~~ eval_xc reference ~~~ - rho_t0 = self.get_rho_translated (Pi, rho) - exc, vxc_p, fxc = self._numint.eval_xc (self.otxc, (rho_t0[0,:,:], - rho_t0[1,:,:]), spin=1, relativity=0, deriv=dderiv, - verbose=self.verbose)[:3] - exc *= rho_t0[:,0,:].sum (0) - vxc_p = tfnal_derivs._reshape_vxc_sigma (vxc_p, self.dens_deriv) - vxc = tfnal_derivs._unpack_sigma_vector (vxc_p, rho_t0[0,1:4], rho_t0[1,1:4]) - if dderiv>1: fxc = tfnal_derivs._pack_fxc_ltri (fxc, self.dens_deriv) - # ~~~ shift translated rho directly ~~~ - r = rho_t0 * r0 / 2**p - drho_t = np.zeros_like (rho_t0, dtype=rho_t0.dtype) - ndf = 2 * (1 + int (nvr>1)) - drho_t[0,0,0::ndf] = r[0,0,0::ndf] - drho_t[1,0,1::ndf] = r[1,0,1::ndf] - if ndf > 2: - drho_t[0,1:4,2::ndf] = r[0,1:4,2::ndf] - drho_t[1,1:4,3::ndf] = r[1,1:4,3::ndf] - # ~~~ eval_xc @ rho_t1 = rho_t0 + drho_t ~~~ - rho_t1 = rho_t0 + drho_t - exc1, vxc1 = self._numint.eval_xc (self.otxc, (rho_t1[0,:,:], - rho_t1[1,:,:]), spin=1, relativity=0, deriv=dderiv, - verbose=self.verbose)[:2] - exc1 *= rho_t1[:,0,:].sum (0) - vxc1 = tfnal_derivs._unpack_vxc_sigma (vxc1, rho_t1, self.dens_deriv) - df_lbl = ('rhoa', 'rhob', "rhoa'", "rhob'")[:2*(1+int(nvr>1))] - _v_err_report (self, 'eval_xc {}'.format (p), df_lbl, rho_t0[0], rho_t0[1], exc, vxc, - vxc_p, fxc, exc1, vxc1, drho_t, weights) - - # ~~~ eval_ot compare ~~~ - nvP = vot[1].shape[0] - d1 = rho_tot[1:4] if nvr > 1 else None - d2 = Pi[1:4] if nvP > 1 else None - if _unpack_vot: - vot_u = vot - vot_p = tfnal_derivs.eval_ot (self, rho, Pi, dderiv=dderiv, - weights=weights, _unpack_vot=False)[1] - else: - vot_p = vot - vot_u = tfnal_derivs._unpack_sigma_vector (vot, d1, d2) - drho = rho_tot * r0 / 2**p - dPi = Pi * r0 / 2**p - r, P = drho.copy (), dPi.copy () - drho[:] = dPi[:] = 0.0 - ndf = 2 + int(nvr>1) + int(nvP>1) - drho[0,0::ndf] = r[0,0::ndf] - dPi[0,1::ndf] = P[0,1::ndf] - if ndf > 2: drho[1:4,2::ndf] = r[1:4,2::ndf] - if ndf > 3: dPi[1:4,3::ndf] = P[1:4,3::ndf] - rho1 = rho+(drho/2) # /2 because rho has one more dimension of size = 2 - # that gets summed later - Pi1 = Pi + dPi - # ~~~ ignore numerical instability of unfully-translated fnals ~~~ - if self.otxc[0].lower () == 't': - z0 = self.get_zeta (self.get_ratio (Pi, rho_tot/2)[0], - fn_deriv=0)[0] - z1 = self.get_zeta (self.get_ratio (Pi1, rho1.sum(0)/2)[0], - fn_deriv=0)[0] - idx = (z0==0) |(z1==0) - drho[:,idx] = dPi[:,idx] = 0 - rho1[:,:,idx] = rho[:,:,idx] - Pi1[:,idx] = Pi[:,idx] - # ~~~ eval_ot @ rho1 = rho + drho ~~~ - eot1, vot1 = tfnal_derivs.eval_ot (self, rho1, Pi1, - dderiv=dderiv, weights=weights, _unpack_vot=True)[:2] - #vot1 = tfnal_derivs._unpack_sigma_vector (vot1, d1, d2) - df_lbl = ('rho', 'Pi', "rho'", "Pi'")[:ndf] - _v_err_report (self, 'eval_ot {}'.format (p), df_lbl, rho_tot, Pi, eot, vot_u, vot_p, fot, - eot1, vot1, (drho, dPi), weights) - - return eot, vot, fot - - eval_ot.__doc__ = otfnal.eval_ot.__doc__ - -# TODO: test continuity of smoothing function and warn at initialization? -class ftransfnal (transfnal): - __doc__ = transfnal.__doc__ + r''' - - Extra attributes for ``fully-translated'' extension of Carlson - et al., JCTC 11, 4077 (2015): - - R0 : float - connecting point to polynomial smoothing function; - R0 <= 1.0. Default is 0.9. - R1 : float - endpoint of polynomial smoothing function, zeta(R1) = - zeta'(R1) = zeta''(R1) = 0.0; R1 >= 1.0. Default is 1.15. - A : float - Quintic coefficient of polynomial smoothing function. - Default = -475.60656009 is chosen to make zeta continuous - through its second derivative at given the default R0 and R1. - B : float - Quartic coefficient of polynomial smoothing function. - Default = -379.47331922 is chosen to make zeta continuous - through its second derivative given the default R0 and R1. - C : float - Cubic coefficient of polynomial smoothing function. - Default = -85.38149682 chosen to make zeta continuous - through its second derivative given the default R0 and R1. - ''' - - transl_prefix='ft' - - def __init__ (self, ks, **kwargs): - otfnal.__init__(self, ks.mol, **kwargs) - self.R0=FT_R0 - self.R1=FT_R1 - self.A=FT_A - self.B=FT_B - self.C=FT_C - self.otxc = 'ft' + ks.xc - self._numint = copy.copy (ks._numint) - self._numint.libxc = libxc - self.grids = copy.copy (ks.grids) - self._numint.hybrid_coeff = ft_hybrid_coeff.__get__(self._numint) - self._numint.nlc_coeff = ft_nlc_coeff.__get__(self._numint) - self._numint.rsh_coeff = ft_rsh_coeff.__get__(self._numint) - self._numint.eval_xc = ft_eval_xc.__get__(self._numint) - self._numint._xc_type = ft_xc_type.__get__(self._numint) - self._init_info () - - Pi_deriv = transfnal.dens_deriv - - def get_rho_translated (self, Pi, rho): - r''' Compute the "fully-translated" alpha and beta densities - and their derivatives. This is the same as "translated" except - - rho'_t^a += zeta' * rho / 2 - rho'_t^b -= zeta' * rho / 2 - - And the functional form of "zeta" is changed (see "get_zeta") - - Args: - Pi : ndarray of shape (*, ngrids) - containing on-top pair density [and derivatives] - rho : ndarray of shape (2, *, ngrids) - containing spin density [and derivatives] - - Returns: - rho_ft : ndarray of shape (2,*,ngrids) - Fully-translated spin density (and derivatives) - ''' - nderiv_R = max (rho.shape[1], Pi.shape[0]) - if nderiv_R == 1: return transfnal.get_rho_translated (self, Pi, rho) - - # Spin density and first term of spin gradient in common with transfnal - rho_avg = (rho[0,:,:] + rho[1,:,:]) / 2 - rho_ft, R, zeta = transfnal.get_rho_translated (self, Pi, rho, - _fn_deriv=1) - - # Add propagation of chain rule through zeta - w = (rho_avg[0] * zeta[1])[None,:] * R[1:4] - rho_ft[0][1:4] += w - rho_ft[1][1:4] -= w - - return rho_ft - - def get_zeta (self, R, fn_deriv=1): - r''' Compute the intermediate zeta used to compute the translated spin - densities and its functional derivatives - - From the "full" translation [Carlson et al., JCTC 11, 4077 (2015)]: - zeta = (1-R)^(1/2) ; R < R0 - = A*(R-R1)^5 + B*(R-R1)^4 + C*(R-R1)^3 ; R0 <= R < R1 - = 0 ; otherwise - - Args: - R : ndarray of shape (*,ngrids) - Ratio (4Pi/rho^2) and possibly its spatial derivatives - Only the first row is used in this function - - Kwargs: - fn_deriv : integer - order of functional derivative (d^n z / dR^n) to return - along with the value of zeta - - Returns: - zeta : ndarray of shape (fn_deriv+1, ngrids) - ''' - # Rmax unused here. It only needs to be passed in the transfnal version - if R.ndim == 2: R = R[0] - R0, R1, A, B, C = self.R0, self.R1, self.A, self.B, self.C - zeta = transfnal.get_zeta (self, R, fn_deriv=fn_deriv, _Rmax=R0) - idx = (R >= R0) & (R < R1) - if not np.count_nonzero (idx): return zeta - zeta[:,idx] = 0.0 - dR = np.stack ([np.power (R[idx] - R1, n) - for n in range (1,6)], axis=0) - def _derivs (): - yield A*dR[4] + B*dR[3] + C*dR[2] - yield 5*A*dR[3] + 4*B*dR[2] + 3*C*dR[1] - yield 20*A*dR[2] + 12*B*dR[1] + 6*C*dR[0] - yield 60*A*dR[1] + 24*B*dR[0] + 6*C - yield 120*A*dR[0] + 24*B - yield 120*A - for n, row in enumerate (_derivs ()): - zeta[n,idx] = row - if n == fn_deriv: break - - return zeta - - def jT_op (self, x, rho, Pi, **kwargs): - r''' Evaluate jTx = (x.j)T where j is the Jacobian of the - translated densities in terms of the untranslated density and - pair density - - Args: - x : ndarray of shape (2,*,ngrids) - Usually, a functional derivative of the on-top xc energy - wrt translated densities - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Returns: ndarray of shape (*,ngrids) - Usually, a functional derivative of the on-top pair density - exchange-correlation energy wrt to total density and its - derivatives. The potential must be spin-symmetric in - pair-density functional theory. - ''' - ntc = 2 + int(self.dens_deriv>0) - ncol = 2 + 3*int(self.dens_deriv>0) - ngrid = rho.shape[-1] - jTx = np.zeros ((ncol,ngrid), dtype=x[0].dtype) - jTx[:ntc,:] = transfnal.jT_op (self, x, rho, Pi, **kwargs) - rho = rho.sum (0) - R = self.get_ratio (Pi[0:4,:], rho[0:4,:]/2) - zeta = self.get_zeta (R[0], fn_deriv=2) - if self.dens_deriv > 0: - jTx[:] += tfnal_derivs._ftGGA_jT_op (x, rho, Pi, R, zeta) - return jTx - - def d_jT_op (self, x, rho, Pi, **kwargs): - r''' Evaluate the x.(nabla j) contribution to the second density - derivatives of the on-top energy in terms of the untranslated - density and pair density - - Args: - x : ndarray of shape (2,*,ngrids) - Usually, a functional derivative of the on-top xc energy - wrt translated densities - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Returns: ndarray of shape (*,ngrids) - second derivative of the translation dotted with x - 3 rows for tLDA and 5 rows for tGGA - ''' - nrow_t = 3 + 2*int(self.dens_deriv>0) - nrow = 3 + 12*int(self.dens_deriv>0) - f = np.zeros ((nrow, x[0].shape[-1]), dtype=x[0].dtype) - f[:nrow_t] = transfnal.d_jT_op (self, x, rho, Pi, **kwargs) - if self.dens_deriv: - rho = rho.sum (0) - R = self.get_ratio (Pi[0:4,:], rho[0:4,:]/2) - zeta = self.get_zeta (R[0], fn_deriv=3) - f[:] += tfnal_derivs._ftGGA_d_jT_op (x, rho, Pi, R, zeta) - return f - - -_CS_a_DEFAULT = 0.04918 -_CS_b_DEFAULT = 0.132 -_CS_c_DEFAULT = 0.2533 -_CS_d_DEFAULT = 0.349 - -def _sanity_check_ftot(xc_code): - ''' - This function will check the functional type and will - raise the warning for fully-translated MGGAs or custom functionals. - ''' - xc_type = libxc.xc_type(xc_code) - if xc_type not in ['LDA', 'GGA']: - msg = f"fully-translated {xc_type} on-top functionals are not defined" - raise NotImplementedError(msg) - -def get_transfnal (mol, otxc): - if otxc.upper () in OT_ALIAS: - otxc = OT_ALIAS[otxc.upper ()] - if otxc.upper ().startswith ('T'): - xc_base = otxc[1:] - fnal_class = transfnal - elif otxc.upper ().startswith ('FT'): - xc_base = otxc[2:] - _sanity_check_ftot(xc_base) - fnal_class = ftransfnal - else: - raise NotImplementedError ( - 'On-top pair-density functional names other than "translated" (t) or ' - '"fully-translated (ft).' - ) - # Try to register the functional with libxc, if not already done - xc_base = _get_registered_ot_functional (xc_base, mol) - - xc_base = OT_HYB_ALIAS.get (xc_base.upper (), xc_base) - - if ',' not in xc_base and \ - (xc_base.upper() not in REG_OT_FUNCTIONALS) and \ - (_libxc.is_hybrid_or_rsh (xc_base)): - raise NotImplementedError ( - 'Aliased or built-in translated hybrid or range-separated ' - 'functionals\nother than those listed in otfnal.OT_HYB_ALIAS. ' - 'Build a compound functional\nstring with a comma separating the ' - 'exchange and correlation parts, or use\notfnal.make_hybrid_fnal ' - 'instead.' - ) - ks = dft.RKS (mol) - ks.xc = xc_base - return fnal_class (ks) - -class colle_salvetti_corr (otfnal): - - - def __init__(self, mol, **kwargs): - super().__init__(mol, **kwargs) - self.otxc = 'Colle_Salvetti' - self._numint = NumInt () - self.grids = Grids (mol) - self._numint.hybrid_coeff = lambda * args : 0 - self._numint.nlc_coeff = lambda * args : [0, 0] - self._numint.rsh_coeff = lambda * args : [0, 0, 0] - self._numint._xc_type = lambda * args : 'MGGA' - self.CS_a =_CS_a_DEFAULT - self.CS_b =_CS_b_DEFAULT - self.CS_c =_CS_c_DEFAULT - self.CS_d =_CS_d_DEFAULT - self._init_info () - - def get_E_ot (self, rho, Pi, weights): - r''' Colle & Salvetti, Theor. Chim. Acta 37, 329 (1975) - see also Lee, Yang, Parr, Phys. Rev. B 37, 785 (1988) - [Eq. (3)]''' - - a, b, c, d = self.CS_a, self.CS_b, self.CS_c, self.CS_d - rho_tot = rho[0,0] + rho[1,0] - idx = rho_tot > 1e-15 - - num = -c * np.power (rho_tot[idx], -1/3) - num = np.exp (num, num) - num *= Pi[4,idx] - num *= b * np.power (rho_tot[idx], -8/3) - num += 1 - - denom = d * np.power (rho_tot[idx], -1/3) - denom += 1 - - num /= denom - num *= Pi[0,idx] - num /= rho_tot[idx] - num *= weights[idx] - - E_ot = np.sum (num) - E_ot *= -4 * a - return E_ot - -def _hybrid_2c_coeff (ni, xc_code, spin=0): - ''' Wrapper to the xc_code hybrid coefficient parser to return the - exchange and correlation components of the hybrid coefficent - separately ''' - - if xc_code.upper() in REG_OT_FUNCTIONALS: - hyb_x = REG_OT_FUNCTIONALS[xc_code.upper ()].get('hyb_x', 0) - hyb_c = REG_OT_FUNCTIONALS[xc_code.upper ()].get('hyb_c', 0) - return [hyb_x, hyb_c] - else: - hyb_tot = _NumInt.hybrid_coeff (ni, xc_code, spin=spin) - if hyb_tot == 0: return [0, 0] - - # For exchange-only functionals, hyb_c = hyb_x - x_code, c_code = _libxc.split_x_c_comma (xc_code) - x_code = x_code + ',' - c_code = ',' + c_code - - # All factors of 'HF' are summed by default. Therefore just run the same - # code for the exchange and correlation parts of the string separately - hyb_x = _NumInt.hybrid_coeff(ni, x_code, spin=spin) if len (x_code) else 0 - hyb_c = _NumInt.hybrid_coeff(ni, c_code, spin=spin) if len (c_code) else 0 - return [hyb_x, hyb_c] - -def make_scaled_fnal (xc_code, hyb_x = 0, hyb_c = 0, fnal_x = None, - fnal_c = None): - ''' Convenience function to write the xc_code corresponding to a - functional of the type - - Exc = hyb_x*E_x[Psi] + fnal_x*E_x[rho] + hyb_c*E_c[Psi] - + fnal_c*E_c[rho] - - where E[Psi] is an energy from a wave function, and E[rho] is a - density functional from libxc. The decomposition of E[Psi] into - exchange (E_x) and correlation (E_c) components is arbitrary. - - Args: - xc_code : string - As used in pyscf.dft.libxc. An exception is raised if it - is already a hybrid or contains a kinetic-energy - functional component. - - Kwargs: - hyb_x : float - fraction of wave function exchange to be included - hyb_c : float - fraction of wave function correlation to be included - fnal_x : float - fraction of density functional exchange to be included. - Defaults to 1 - hyb_x. - fnal_c : float - fraction of density functional correlation to be - included. Defaults to 1 - hyb_c. - - returns: - xc_code : string - If xc_code has exchange part x_code and correlation part - c_code, the return value is - 'fnal_x * x_code + hyb_x * HF, - fnal_c * c_code + hyb_c * HF' - You STILL HAVE TO PREPEND 't' OR 'ft'!!! - ''' - if fnal_x is None: fnal_x = 1 - hyb_x - if fnal_c is None: fnal_c = 1 - hyb_c - - if _libxc.is_hybrid_xc (xc_code): - raise RuntimeError ('Functional {} is already a hybrid!'.format ( - xc_code)) - x_code, c_code = _libxc.split_x_c_comma (xc_code) - - x_facs, x_terms = _libxc.parse_xc_formula (x_code) - if fnal_x != 1: x_facs = list (np.asarray (x_facs)*fnal_x) - if hyb_x != 0: - x_facs.append (hyb_x) - x_terms.append ('HF') - x_code = _libxc.assemble_xc_formula (x_facs, x_terms) - - c_facs, c_terms = _libxc.parse_xc_formula (c_code) - if fnal_c != 1: c_facs = list (np.asarray (c_facs)*fnal_c) - if hyb_c != 0: - c_facs.append (hyb_c) - c_terms.append ('HF') - c_code = _libxc.assemble_xc_formula (c_facs, c_terms) - - return x_code + ',' + c_code - -def make_hybrid_fnal (xc_code, hyb, hyb_type = 1): - ''' Convenience function to write "hybrid" xc functional in terms of - only one parameter - - Args: - xc_code : string - As used in pyscf.dft.libxc. An exception is raised if it - is already a hybrid or contains a kinetic-energy - functional component. - hyb : float - Parameter(s) defining the "hybridization" which is - handled in various ways according to hyb_type - - Kwargs: - hyb_type : int or string - The type of hybrid functional. Current options are: - - 0 or 'translation': Hybrid fnal is - 'hyb*HF + (1-hyb)*x_code, hyb*HF + c_code'. - Based on the idea that 'exact exchange' of the - translated functional corresponds to exchange plus - correlation energy of the underlying wave function. - Requires len (hyb) == 1. - - 1 or 'average': Hybrid fnal is - 'hyb*HF + (1-hyb)*x_code, hyb*HF + (1-hyb)*c_code'. - Based on the idea that hyb = 1 recovers the wave - function energy itself. Requires len (hyb) == 1. - - 2 or 'diagram': Hybrid fnal is - 'hyb*HF + (1-hyb)*x_code, c_code'. - Based on the idea that the exchange energy of the - wave function somehow can be meaningfully separated - from the correlation energy. Requires len (hyb) == 1. - - 3 or 'lambda': as in arXiv:1911.11162v1. Based on - existing 'double-hybrid' functionals. Requires - len (hyb) == 1. - - 4 or 'scaling': Hybrid fnal is - 'a*HF + (1-a)*x_code, a*HF + (1-a**b)*c_code' - where a = hyb[0] and b = 1 + hyb[1]. Based on the - scaling inequalities proven by Levy and Perdew in - PRA 32, 2010 (1985): - E_c[rho_a] < a*E_c[rho] if a < 1 and - E_c[rho_a] > a*E_c[rho] if a > 1; - BUT - E_c[rho_a] ~/~ a^2 E_c[rho], implying that - E_c[rho_a] ~ a^b E_c[rho] with b > 1 unknown. - Requires len (hyb) == 2. - ''' - - if not hasattr (hyb, '__len__'): hyb = [hyb] - HYB_TYPE_CODE = {'translation': 0, - 'average': 1, - 'diagram': 2, - 'lambda': 3, - 'scaling': 4} - if isinstance (hyb_type, str): hyb_type = HYB_TYPE_CODE[hyb_type] - - if hyb_type == 0: - assert (len (hyb) == 1) - return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0], - fnal_x=(1-hyb[0]), fnal_c=1) - elif hyb_type == 1: - assert (len (hyb) == 1) - return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0], - fnal_x=(1-hyb[0]), fnal_c=(1-hyb[0])) - elif hyb_type == 2: - assert (len (hyb) == 1) - return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=0, - fnal_x=(1-hyb[0]), fnal_c=1) - elif hyb_type == 3: - assert (len (hyb) == 1) - return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0], - fnal_x=(1-hyb[0]), fnal_c=(1-(hyb[0]*hyb[0]))) - elif hyb_type == 4: - assert (len (hyb) == 2) - a = hyb[0] - b = hyb[0]**(1+hyb[1]) - return make_scaled_fnal (xc_code, hyb_x=a, hyb_c=a, fnal_x=(1-a), - fnal_c=(1-b)) - else: - raise RuntimeError ('hybrid type undefined') - - -# TODO: reconsider this goofy API... -__t_doc__="For 'translated' functionals, otxc string = 't'+xc string\n" -__ft_doc__="For 'fully translated' functionals, otxc string = 'ft'+xc string\n" - -def t_hybrid_coeff(ni, xc_code, spin=0): - #return _NumInt.hybrid_coeff(ni, xc_code[1:], spin=0) - return _hybrid_2c_coeff (ni, xc_code[1:], spin=0) -t_hybrid_coeff.__doc__ = __t_doc__ + str(_NumInt.hybrid_coeff.__doc__) - -def t_nlc_coeff(ni, xc_code): - return _NumInt.nlc_coeff(ni, xc_code[1:]) -t_nlc_coeff.__doc__ = __t_doc__ + str(_NumInt.nlc_coeff.__doc__) - -def t_rsh_coeff(ni, xc_code): - return _NumInt.rsh_coeff(ni, xc_code[1:]) -t_rsh_coeff.__doc__ = __t_doc__ + str(_NumInt.rsh_coeff.__doc__) - -def t_eval_xc(ni, xc_code, rho, spin=0, relativity=0, deriv=1, verbose=None): - return _NumInt.eval_xc(ni, xc_code[1:], rho, spin=spin, - relativity=relativity, deriv=deriv, verbose=verbose) -t_eval_xc.__doc__ = __t_doc__ + str(_NumInt.eval_xc.__doc__) - -def t_xc_type(ni, xc_code): - return _NumInt._xc_type(ni, xc_code[1:]) -t_xc_type.__doc__ = __t_doc__ + str(_NumInt._xc_type.__doc__) - -def t_rsh_and_hybrid_coeff(ni, xc_code, spin=0): - return _NumInt.rsh_and_hybrid_coeff (ni, xc_code[1:], spin=spin) -t_rsh_and_hybrid_coeff.__doc__ = (__t_doc__ - + str(_NumInt.rsh_and_hybrid_coeff.__doc__)) - -def ft_hybrid_coeff(ni, xc_code, spin=0): - #return _NumInt.hybrid_coeff(ni, xc_code[2:], spin=0) - return _hybrid_2c_coeff(ni, xc_code[2:], spin=0) -ft_hybrid_coeff.__doc__ = __ft_doc__ + str(_NumInt.hybrid_coeff.__doc__) - -def ft_nlc_coeff(ni, xc_code): - return _NumInt.nlc_coeff(ni, xc_code[2:]) -ft_nlc_coeff.__doc__ = __ft_doc__ + str(_NumInt.nlc_coeff.__doc__) - -def ft_rsh_coeff(ni, xc_code): - return _NumInt.rsh_coeff(ni, xc_code[2:]) -ft_rsh_coeff.__doc__ = __ft_doc__ + str(_NumInt.rsh_coeff.__doc__) - -def ft_eval_xc(ni, xc_code, rho, spin=0, relativity=0, deriv=1, verbose=None): - return _NumInt.eval_xc(ni, xc_code[2:], rho, spin=spin, - relativity=relativity, deriv=deriv, verbose=verbose) -ft_eval_xc.__doc__ = __ft_doc__ + str(_NumInt.eval_xc.__doc__) - -def ft_xc_type(ni, xc_code): - return _NumInt._xc_type(ni, xc_code[2:]) -ft_xc_type.__doc__ = __ft_doc__ + str(_NumInt._xc_type.__doc__) - -def ft_rsh_and_hybrid_coeff(ni, xc_code, spin=0): - return _NumInt.rsh_and_hybrid_coeff (ni, xc_code[2:], spin=spin) -ft_rsh_and_hybrid_coeff.__doc__ = (__ft_doc__ - + str(_NumInt.rsh_and_hybrid_coeff.__doc__)) - -def _v_err_report (otfnal, tag, lbls, rho_tot, Pi, e0, v0, v0_packed, f, e1, v1, x, w): - # Examine the error of the first and second functional derivatives in the - # debugging block under transfnal.eval_ot below - logger.debug (otfnal, '--- v_err_report (%s) ---', tag) - ndf = len (lbls) - nvP = v0[1].shape[0] - de = (e1-e0) * w - vx = ((v0[0]*x[0]).sum (0) + (v0[1]*x[1][:nvP]).sum (0)) * w - if f is None: - xfx = np.zeros_like (de) - else: - xf = tfnal_derivs.contract_fot (otfnal, f, rho_tot, Pi, x[0], x[1], vot_packed=v0_packed) - for row in xf: row[:] *= w - xfx = ((xf[0]*x[0]).sum (0) + (xf[1]*x[1][:nvP]).sum (0)) / 2 - xf_df = [xf[0][0], xf[1][0]] - dv_df = [(v1[0][0]-v0[0][0])*w, (v1[1][0]-v0[1][0])*w] - # The lesson of the debug experience from the commented-out block below is: - # the largest errors (fractional or absolute) in the ftLDA fnal gradient - # appear to be for R just under 1.0! - #if 'LDA' in otfnal.otxc: - # print ("bigtab", otfnal.otxc, (np.sum (vx) - np.sum (de))/np.sum (de)) - # tab = np.empty ((xf[0][0].size, 6), dtype=xf[0].dtype) - # tab[:,0] = otfnal.get_ratio (Pi, rho_tot/2)[0] - 1.0 - # tab[:,1] = rho_tot - # tab[:,2] = Pi - # tab[:,3] = vx - # tab[:,4] = tab[:,3] - de - # tab[:,5] = tab[:,4] / de - # tab[(de==0)&(vx==0),3] = 0.0 - # tab[(de==0)&(vx!=0),3] = 1.0 - # tab = tab[np.argsort (-np.abs (tab[:,4])),:] - # for row in tab: - # print ("{:20.12e} {:9.2e} {:9.2e} {:9.2e} {:9.2e} {:9.2e}".format - # (*row)) - if ndf > 2: - xf_df += [xf[0][1:4].T,] - dv_df += [((v1[0][1:4]-v0[0][1:4])*w).T,] - if ndf > 3: - xf_df += [xf[1][1:4].T,] - dv_df += [((v1[1][1:4]-v0[1][1:4])*w).T,] - de_err1 = de - vx - de_err2 = de_err1 - xfx - for ix, lbl in enumerate (lbls): - lib.logger.debug (otfnal, "%s gradient debug %s: %e - %e (- %e) -> %e " - "(%e)", tag, lbl, np.sum (de[ix::ndf]), np.sum (vx[ix::ndf]), - np.sum (xfx[ix::ndf]), np.sum (de_err1[ix::ndf]), - np.sum (de_err2[ix::ndf])) - if f is not None: - for lbl_row, xf_row, dv_row in zip (lbls, xf_df, dv_df): - err_row = dv_row-xf_row - for ix_col, lbl_col in enumerate (lbls): - lib.logger.debug (otfnal, ("%s Hessian debug (H.x_%s)_%s: " - "%e - %e -> %e"), tag, lbl_col, lbl_row, - linalg.norm (dv_row[ix_col::ndf]), - linalg.norm (xf_row[ix_col::ndf]), - linalg.norm (err_row[ix_col::ndf])) - # I am not doing rho'.rho'->sigma right for x.f.x/2 - # However I am somehow doing it right for f.x vs. delta v? - lib.logger.debug (otfnal, "%s dE - v.x - x.f.x: %e - %e - %e = %e", - tag, de.sum (), vx.sum (), xfx.sum (), de_err2.sum ()) diff --git a/pyscf/mcpdft/otpd.py b/pyscf/mcpdft/otpd.py deleted file mode 100644 index 603a3f727..000000000 --- a/pyscf/mcpdft/otpd.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/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 pyscf.lib import logger -from pyscf.dft.numint import _dot_ao_dm - -def _grid_ao2mo (mol, ao, mo_coeff, non0tab=None, shls_slice=None, - ao_loc=None): - '''ao[deriv,grid,AO].mo_coeff[AO,MO]->mo[deriv,grid,MO] - ASSUMES that ao is in data layout (deriv,AO,grid) in row-major order! - mo is returned in data layout (deriv,MO,grid) in row-major order ''' - nderiv, ngrid, _ = ao.shape - nmo = mo_coeff.shape[-1] - mo = np.empty ((nderiv,nmo,ngrid), dtype=mo_coeff.dtype, order='C') - mo = mo.transpose (0,2,1) - if shls_slice is None: shls_slice = (0, mol.nbas) - if ao_loc is None: ao_loc = mol.ao_loc_nr () - for ideriv in range (nderiv): - ao_i = ao[ideriv,:,:] - mo[ideriv] = _dot_ao_dm (mol, ao_i, mo_coeff, non0tab, shls_slice, - ao_loc, out=mo[ideriv]) - return mo - - -def get_ontop_pair_density (ot, rho, ao, cascm2, mo_cas, deriv=0, - non0tab=None): - r'''Compute the on-top pair density and its derivatives on a grid: - - Pi(r) = i(r)*j(r)*k(r)*l(r)*d_ijkl / 2 - = rho[0](r)*rho[1](r) + i(r)*j(r)*k(r)*l(r)*l_ijkl / 2 - - Args: - ot : on-top pair density functional object - rho : ndarray of shape (2,*,ngrids) - Contains spin-separated density [and derivatives]. The dm1s - underlying these densities must correspond to the dm1s/dm1 - in the expression for cascm2 below. - ao : ndarray of shape (*, ngrids, nao) - contains values of aos [and derivatives] - cascm2 : ndarray of shape [ncas,]*4 - contains spin-summed two-body cumulant density matrix in an - active-orbital basis given by mo_cas: - cm2[u,v,x,y] = dm2[u,v,x,y] - dm1[u,v]*dm1[x,y] - + dm1s[0][u,y]*dm1s[0][x,v] - + dm1s[1][u,y]*dm1s[1][x,v] - where dm1 = dm1s[0] + dm1s[1]. The cumulant (cm2) has no - nonzero elements for any index outside the active space, - unlike the density matrix (dm2), which formally has elements - involving uncorrelated, doubly-occupied ``core'' orbitals - which are not usually computed explicitly: - dm2[i,i,u,v] = dm2[u,v,i,i] = 2*dm1[u,v] - dm2[u,i,i,v] = dm2[i,v,u,i] = -dm1[u,v] - mo_cas : ndarray of shape (nao, ncas) - molecular-orbital coefficients for active-space orbitals - - Kwargs: - deriv : derivative order through which to calculate. - deriv > 1 not implemented - non0tab : as in pyscf.dft.gen_grid and pyscf.dft.numint - - Returns : ndarray of shape (*,ngrids) - The on-top pair density and its derivatives if requested - deriv = 0 : value (1d array) - deriv = 1 : value, d/dx, d/dy, d/dz - deriv = 2 : value, d/dx, d/dy, d/dz, d^2/d|r1-r2|^2_(r1=r2) - ''' - # Fix dimensionality of rho and ao - rho_reshape = False - ao_reshape = False - if rho.ndim == 2: - rho_reshape = True - rho = rho.reshape(rho.shape[0], 1, rho.shape[1]) - if ao.ndim == 2: - ao_reshape = True - ao = ao.reshape(1, ao.shape[0], ao.shape[1]) - - # First cumulant and derivatives (chain rule! product rule!) - t0 = (logger.process_clock (), logger.perf_counter ()) - Pi_shape = ((1,4,5)[deriv], rho.shape[-1]) - Pi = np.zeros(Pi_shape, dtype=rho.dtype) - Pi[0] = rho[0,0] * rho[1,0] - if deriv > 0: - assert (rho.shape[1] >= 4), rho.shape - assert (ao.shape[0] >= 4), ao.shape - for ideriv in range(1,4): - Pi[ideriv] = rho[0,ideriv]*rho[1,0] + rho[0,0]*rho[1,ideriv] - if deriv > 1: - assert (rho.shape[1] >= 6), rho.shape - assert (ao.shape[0] >= 10), ao.shape - Pi[4] = -(rho[:,1:4].sum (0).conjugate () * rho[:,1:4].sum (0)).sum (0) - Pi[4] /= 4.0 - Pi[4] += rho[0,0]*(rho[1,4]/4 + rho[0,5]*2) - Pi[4] += rho[1,0]*(rho[0,4]/4 + rho[1,5]*2) - t0 = logger.timer_debug1 (ot, 'otpd first cumulant', *t0) - - # Second cumulant and derivatives (chain rule! product rule!) - # dot, tensordot, and sum are hugely faster than np.einsum - # but whether or when they actually multithread is unclear - # Update 05/11/2020: ao is actually stored in row-major order - # = (deriv,AOs,grids). - grid2amo = _grid_ao2mo (ot.mol, ao, mo_cas, non0tab=non0tab) - t0 = logger.timer (ot, 'otpd ao2mo', *t0) - gridkern = np.zeros (grid2amo.shape + (grid2amo.shape[2],), - dtype=grid2amo.dtype) - gridkern[0] = grid2amo[0,:,:,np.newaxis] * grid2amo[0,:,np.newaxis,:] - # r_0ai, r_0aj -> r_0aij - wrk0 = np.tensordot (gridkern[0], cascm2, axes=2) - # r_0aij, P_ijkl -> P_0akl - Pi[0] += (gridkern[0] * wrk0).sum ((1,2)) / 2 - # r_0aij, P_0aij -> P_0a - t0 = logger.timer_debug1 (ot, 'otpd second cumulant 0th derivative', *t0) - if deriv > 0: - for ideriv in range (1, 4): - # Fourfold tensor symmetry ijkl = klij = jilk = lkji - # & product rule -> factor of 4 - gridkern[ideriv] = (grid2amo[ideriv,:,:,np.newaxis] - * grid2amo[0,:,np.newaxis,:]) - # r_1ai, r_0aj -> r_1aij - Pi[ideriv] += (gridkern[ideriv] * wrk0).sum ((1,2)) * 2 - # r_1aij, P_0aij -> P_1a - t0 = logger.timer_debug1 (ot, 'otpd second cumulant 1st derivative' - ' ({})'.format (ideriv), *t0) - if deriv > 1: # The fifth slot is allocated to the "off-top Laplacian," - # i.e., nabla_(r1-r2)^2 Pi(r1,r2)|(r1=r2) - # nabla_off^2 Pi = 1/2 d^ik_jl * ([nabla_r^2 phi_i] phi_j phi_k phi_l - # + {1 - p_jk - p_jl}[nabla_r phi_i . nabla_r phi_j] phi_k phi_l) - # using four-fold symmetry a lot! be careful! - XX, YY, ZZ = 4, 7, 9 - gridkern[4] = (grid2amo[[XX,YY,ZZ],:,:,np.newaxis].sum (0) - * grid2amo[0,:,np.newaxis,:]) - # r_2ai, r_0aj -> r_2aij - gridkern[4] += (grid2amo[1:4,:,:,np.newaxis] - * grid2amo[1:4,:,np.newaxis,:]).sum (0) - # r_1ai, r_1aj -> r_2aij - wrk1 = np.tensordot (gridkern[1:4], cascm2, axes=2) - # r_1aij, P_ijkl -> P_1akl - Pi[4] += (gridkern[4] * wrk0).sum ((1,2)) / 2 - # r_2aij, P_0aij -> P_2a - Pi[4] -= ((gridkern[1:4] + gridkern[1:4].transpose (0, 1, 3, 2)) - * wrk1).sum ((0,2,3)) / 2 - # r_1aij, P_1aij -> P_2a - t0 = logger.timer (ot, 'otpd second cumulant off-top Laplacian', *t0) - - # Unfix dimensionality of rho, ao, and Pi - # if Pi.shape[0] == 1: - if rho_reshape: - Pi = Pi.reshape (Pi.shape[1]) - rho = rho.reshape (rho.shape[0], rho.shape[2]) - if ao_reshape: - ao = ao.reshape (ao.shape[1], ao.shape[2]) - - return Pi - -def density_orbital_derivative (ot, ncore, ncas, casdm1s, cascm2, rho, mo, - deriv=0, non0tab=None): - '''Compute the half-transformed density and 3/4-transformed pair- - density matrix: - - D_i(r) = sum_j D_ij phi_j(r) - d_i(r) = sum_jkl d_ijkl phi_j(r) phi_k(r) phi_l(r) - - so that, for instance, the derivative with respect to orbital - rotations of the density and pair density are - - d/dk_ij rho(r) = phi_i(r) D_j(r) - phi_j(r) D_k(r) - d/dk_ij Pi(r) = i(r) d_j(r) - j(r) d_i(r) - - and the derivatives with respect to nuclear displacement are - - drho/dRA|(r not in A) = -sum_(mu in A) phi'_mu(r) D_mu(r) - drho/dRA|(r in A) = +sum_(mu not in A) phi'_mu(r) D_mu(r) - dPi/dRA|(r not in A) = -sum_(mu in A) phi'_mu(r) d_mu(r) - dPi/dRA|(r in A) = +sum_(mu not in A) phi'_mu(r) d_mu(r) - - There is a mismatch between the ndarray shape and the data layout in - the arg `mo` and the two return arrays. For performance reasons, the - grid index should be contiguous in memory for every array with a - a grid dimension. However, by convention, in PySCF ndarrays with - grid and orbital indices place the latter index in the last - position, the former in the second-to-last position, and any other - before that in row-major order. That is, for a four-index array of - this type, transpose (2,3,1,0) gives a contiguous column-major array, - and transpose (0,1,3,2) results in a contiguous row-major array. - - Args: - ot : object of :class:`otfnal` - The on-top density functional containing grid information - ncore : integer - Number of doubly-occupied core orbitals - ncas : integer - Number of active orbitals - casdm1s : ndarray of shape (2,ncas,ncas) - Spin-separated one-body reduced density matrix - cascm2 : ndarray of shape [ncas,]*4 - Spin-summed cumulant of two-body reduced density matrix - rho : ndarray of shape (2,*,ngrids) - Spin-separated density (and derivatives) on a grid - mo : ndarray of shape (*,ngrids,nmo) - Molecular orbitals (and derivatives) evaluated on a grid. - Data stride must be 0>2>1; i.e., mo.transpose (0,2,1) must - be a contiguous row-major array. - - Kwargs: - deriv : integer - Order of derivatives of half-transformed density to compute; - i.e., 0 for LDA and 1 for GGA - non0tab : 2D boolean array - Mask array to determine whether densities are considered to - be numerically zero - - Returns: - drho : ndarray of shape (2,*,ngrids,nmo) - Half-transformed density matrix on a grid and its - derivatives. Data stride is 0>1>3>2; i.e., - drho.transpose (0,1,3,2) is a contiguous row-major array. - dPi : ndarray of shape (*,ngrids,nmo) - 3/4-transformed pair density matrix on a grid and its - derivatives. Data stride is 0>2>1; i.e., - dPi.transpose (0,2,1) is a contiguous row-major array. - ''' - nocc = ncore + ncas - nderiv_Pi = (1,4)[int (deriv)] - nmo = mo.shape[-1] - ngrids = rho.shape[-1] - - # Fix dimensionality of rho and mo - if rho.ndim == 2: - rho = rho.reshape (rho.shape[0], 1, rho.shape[1]) - if mo.ndim == 2: - mo = mo.reshape (1, mo.shape[0], mo.shape[1]) - - # First cumulant and derivatives - dm1s_mo = np.stack ([np.eye (nmo, dtype=casdm1s[0].dtype),]*2, axis=0) - dm1s_mo[:,ncore:nocc,ncore:nocc] = casdm1s - dm1s_mo[:,nocc:,:] = 0 - dm1s_mo[:,:,nocc:] = 0 - - drho = np.stack ([_grid_ao2mo (ot.mol, mo, dm1, non0tab=non0tab) - for dm1 in dm1s_mo], axis=0).transpose (0,1,3,2) - dPi = np.zeros ((nderiv_Pi, nmo, ngrids), dtype=rho.dtype) - dPi[0] = ((drho[0][0] * rho[1,0,None,:]) - + (rho[0,0,None,:] * drho[1][0])) - if deriv > 0: - for ideriv in range(1,4): - dPi[ideriv] = ((drho[0][ideriv]*rho[1,0,None,:]) - + (drho[0][0]*rho[1,ideriv,None,:]) - + (rho[0,ideriv,None,:]*drho[1][0]) - + (rho[0,0,None,:]*drho[1][ideriv])) - if deriv > 1: - raise NotImplementedError ("Colle-Salvetti type orbital+grid " - "derivatives") - - # Second cumulant and derivatives - mo_cas = mo[:,:,ncore:nocc] - gridkern = np.zeros (mo_cas.shape + (mo_cas.shape[2],), dtype=mo_cas.dtype) - gridkern[0] = mo_cas[0,:,:,np.newaxis] * mo_cas[0,:,np.newaxis,:] - # r_0ai, r_0aj -> r_0aij - wrk0 = np.tensordot (gridkern[0], cascm2, axes=2) - # r_0aij, P_ijkl -> P_0akl - dPi[0,ncore:nocc] += (mo_cas[0][:,None,:] * wrk0).sum (2).T - # r_0aj, P_0aij -> P_0ai - if deriv > 0: - for ideriv in range (1, 4): - dPi[ideriv,ncore:nocc] += (mo_cas[ideriv][:,None,:] - * wrk0).sum (2).T - # r_1aj, P_0aij -> P_1ai - gridkern[ideriv] = (mo_cas[ideriv,:,:,np.newaxis] - * mo_cas[0,:,np.newaxis,:]) - gridkern[ideriv] += gridkern[ideriv].transpose (0,2,1) - # r_1ai, r_0aj -> r_1aij - for ideriv in range (1, 4): - wrk0 = np.tensordot (gridkern[ideriv], cascm2, axes=2) - # r_1aij, P_ijkl -> P_1akl - dPi[ideriv,ncore:nocc] += (mo_cas[0][:,None,:] * wrk0).sum (2).T - # r_0aj, P_1aij -> P_1ai - if deriv > 1: - raise NotImplementedError ("Colle-Salvetti type orbital+grid " - "derivatives") - - # Unfix dimensionality of rho, ao, and Pi - dPi = dPi.transpose (0,2,1) - drho = drho.transpose (0,1,3,2) - - return drho, dPi - - diff --git a/pyscf/mcpdft/pdft_eff.py b/pyscf/mcpdft/pdft_eff.py deleted file mode 100644 index 710db81ae..000000000 --- a/pyscf/mcpdft/pdft_eff.py +++ /dev/null @@ -1,497 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hermes -# Author: Matthew Hennefarth - -import numpy as np -from pyscf import __config__, lib -from pyscf.lib import logger -from pyscf.dft import numint -from pyscf.mcpdft.otpd import _grid_ao2mo -import ctypes - -SWITCH_SIZE = getattr(__config__, 'dft_numint_SWITCH_SIZE', 800) -libpdft = lib.load_library('libpdft') - - -# TODO: outcore implementation; can I use properties instead of copying? -class _ERIS: - '''Stores two-body PDFT on-top effective integral arrays in a form - compatible with existing MC-SCF kernel and derivative functions. - Unlike actual eris, PDFT 2-electron effective integrals have 24-fold - permutation symmetry, so j_pc = k_pc and ppaa = papa.transpose - (0,2,1,3). The mcscf _ERIS is currently undocumented so I won't - spend more time documenting this for now. - ''' - - def __init__(self, mol, mo_coeff, ncore, ncas, method='incore', - paaa_only=False, aaaa_only=False, jk_pc=False, verbose=0, stdout=None): - self.mol = mol - self.mo_coeff = mo_coeff - self.nao, self.nmo = mo_coeff.shape - self.ncore = ncore - self.ncas = ncas - self.vhf_c = np.zeros((self.nmo, self.nmo), dtype=mo_coeff.dtype) - self.method = method - self.paaa_only = paaa_only - self.aaaa_only = aaaa_only - self.jk_pc = jk_pc - self.verbose = verbose - self.stdout = stdout - if method == 'incore': - self.papa = np.zeros((self.nmo, ncas, self.nmo, ncas), - dtype=mo_coeff.dtype) - self.j_pc = np.zeros((self.nmo, ncore), dtype=mo_coeff.dtype) - else: - raise NotImplementedError("method={} for pdft_eff2".format( - self.method)) - - def _accumulate(self, ot, ao, weight, rho_c, rho_a, eff_Pi, - non0tab=None, shls_slice=None, ao_loc=None): - args = [ot, ao, weight, rho_c, rho_a, eff_Pi, non0tab, shls_slice, ao_loc] - self._accumulate_vhf_c(*args) - if self.method.lower() == 'incore': - self._accumulate_ppaa_incore(*args) - else: - raise NotImplementedError("method={} for pdft_eff2".format( - self.method)) - self._accumulate_j_pc(*args) - - def _accumulate_vhf_c(self, ot, ao, weight, rho_c, rho_a, eff_Pi, - non0tab, shls_slice, ao_loc): - mo_coeff = self.mo_coeff - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - - vrho_c = _contract_eff_rho(eff_Pi, rho_c) - self.vhf_c += mo_coeff.conjugate().T @ ot.get_eff_1body(ao, - weight, vrho_c, non0tab=non0tab, shls_slice=shls_slice, - ao_loc=ao_loc, - hermi=1) @ mo_coeff - self.energy_core = np.trace(self.vhf_c[:ncore, :ncore]) #/ 2 - if self.paaa_only: - # 1/2 v_aiuv D_ii D_uv = v^ai_uv D_uv -> F_ai, F_ia - # needs to be in here since it would otherwise be calculated using - # ppaa and papa. This is harmless to the CI problem because the - # elements in the active space and core-core sector are ignored - # below. - eff_rho_a = _contract_eff_rho(eff_Pi, rho_a) - vhf_a = get_eff_1body(ot, ao, weight, eff_rho_a, non0tab=non0tab, - shls_slice=shls_slice, ao_loc=ao_loc, hermi=1) - vhf_a = mo_coeff.conjugate().T @ vhf_a @ mo_coeff - vhf_a[ncore:nocc, :] = vhf_a[:, ncore:nocc] = 0.0 - self.vhf_c += vhf_a - - def _ftpt_vhf_c(self): - return self.nao + 1 - - def _accumulate_ppaa_incore(self, ot, ao, weight, rho_c, rho_a, - eff_Pi, non0tab, shls_slice, ao_loc): - # ao is here stored in row-major order = deriv,AOs,grids regardless of - # what the ndarray object thinks - mo_coeff = self.mo_coeff - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - nderiv = eff_Pi.shape[0] - mo_cas = _grid_ao2mo(self.mol, ao[:nderiv], mo_coeff[:, ncore:nocc], - non0tab) - if self.aaaa_only: - aaaa = ot.get_eff_2body([mo_cas, mo_cas, mo_cas, - mo_cas], weight, eff_Pi, aosym='s1') - self.papa[ncore:nocc, :, ncore:nocc, :] += aaaa - elif self.paaa_only: - paaa = ot.get_eff_2body([ao, mo_cas, mo_cas, mo_cas], - weight, eff_Pi, aosym='s1') - paaa = np.tensordot(mo_coeff.T, paaa, axes=1) - self.papa[:, :, ncore:nocc, :] += paaa - self.papa[ncore:nocc, :, :, :] += paaa.transpose(2, 3, 0, 1) - self.papa[ncore:nocc, :, ncore:nocc, :] -= paaa[ncore:nocc, :, :, :] - else: - papa = ot.get_eff_2body([ao, mo_cas, ao, mo_cas], - weight, eff_Pi, aosym='s1') - papa = np.tensordot(mo_coeff.T, papa, axes=1) - self.papa += np.tensordot(mo_coeff.T, papa, - axes=((1), (2))).transpose(1, 2, 0, 3) - - def _ftpt_ppaa_incore(self): - nao, ncas = self.nao, self.ncas - ncol = 1 + 2 * ncas - ij_aa = int(self.aaaa_only) - kl_aa = int(ij_aa or self.paaa_only) - ncol += (ij_aa + kl_aa) * (nao - ncas) - return ncol * ncas - - def _accumulate_j_pc(self, ot, ao, weight, rho_c, rho_a, vPi, - non0tab, shls_slice, ao_loc): - mo_coeff = self.mo_coeff - ncore = self.ncore - nderiv = vPi.shape[0] - if self.jk_pc: - mo = _square_ao(_grid_ao2mo(self.mol, ao[:nderiv], mo_coeff, - non0tab)) - mo_core = mo[:, :, :ncore] - self.j_pc += ot.get_eff_1body([mo, mo_core], weight, vPi) - - def _ftpt_j_pc(self): - return self.nao + self.ncore + self.ncas + 1 - - def _accumulate_ftpt(self): - """ memory footprint of _accumulate, divided by nderiv_Pi*ngrids """ - ftpt_fns = [self._ftpt_vhf_c] - if self.method.lower() == 'incore': - ftpt_fns.append(self._ftpt_ppaa_incore) - else: - raise NotImplementedError("method={} for pdft_eff2".format( - self.method)) - if self.verbose > logger.DEBUG: - ftpt_fns.append(self._ftpt_j_pc) - ncol = 0 - for fn in ftpt_fns: ncol = max(ncol, fn()) - return ncol - - def _finalize(self): - if self.method == 'incore': - self.ppaa = np.ascontiguousarray(self.papa.transpose(0, 2, 1, 3)) - self.k_pc = self.j_pc.copy() - else: - raise NotImplementedError("method={} for pdft_eff2".format( - self.method)) - self.k_pc = self.j_pc.copy() - - -def _contract_eff_rho(eff, rho, add_eff_rho=None): - """ Make a jk-like eff_rho from eff and a density. k = j so it's just - eff * eff_rho / 2 , but the product rule needs to be followed """ - if rho.ndim == 1: - rho = rho[None, :] - - nderiv = eff.shape[0] - eff_rho = eff * rho[0] - - if nderiv > 1: - eff_rho[0] += (eff[1:4] * rho[1:4]).sum(0) - - eff_rho /= 2 - # eff involves lower derivatives than eff_rho in original translation - # make sure vot * rho gets added to only the proper component(s) - if add_eff_rho is not None: - add_eff_rho[:nderiv] += eff_rho - eff_rho = add_eff_rho - return eff_rho - - -def _square_ao(ao): - # On a grid, square each element of an AO or MO array, but preserve the - # chain rule so that columns 1 to 4 are still the first derivative of - # the squared AO value, etc. - nderiv = ao.shape[0] - ao_sq = ao * ao[0] - if nderiv > 1: - ao_sq[1:4] *= 2 - if nderiv > 4: - ao_sq[4:10] += ao[1:4] ** 2 - ao_sq[4:10] *= 2 - return ao_sq - -def get_eff_1body(otfnal, ao, weight, kern, non0tab=None, - shls_slice=None, ao_loc=None, hermi=0): - r''' Contract the kern with d vrho/ dDpq. - - Args: - ao : ndarray or 2 ndarrays of shape (*,ngrids,nao) - contains values and derivatives of nao. - 2 different ndarrays can have different nao but not - different ngrids - weight : ndarray of shape (ngrids) - containing numerical integration weights - kern : ndarray of shape (*,ngrids) - the derivative of the on-top potential with respect to - density (vrho)/ If not provided, it is calculated. - - Kwargs: - non0tab : ndarray of shape (nblk, nbas) - Identifies blocks of grid points which are nonzero on - each AO shell so as to exploit sparsity. - If you want the "ao" array to be in the MO basis, just - leave this as None. If hermi == 0, it only applies - to the bra index ao array, even if the ket index ao - array is the same (so probably always pass hermi = 1 - in that case) - shls_slice : sequence of integers of len 2 - Identifies starting and stopping indices of AO shells - ao_loc : ndarray of length nbas - Offset to first AO of each shell - hermi : integer or logical - Toggle whether veff is supposed to be a Hermitian matrix - You can still pass two different ao arrays for the bra and - the ket indices, for instance if one of them is supposed to - be a higher derivative. They just have to have the same nao - in that case. - - Returns : ndarray of shape (nao[0],nao[1]) - The 1-body effective term corresponding to kernel times the AO's, - in the atomic-orbital basis. In PDFT this functional is always - spin-symmetric. - ''' - if isinstance(ao, np.ndarray) and ao.ndim == 3: - ao = [ao, ao] - elif len(ao) != 2: - raise NotImplementedError("uninterpretable aos!") - elif ao[0].size < ao[1].size: - # Life pro-tip: do more operations with smaller arrays and fewer - # operations with bigger arrays - ao = [ao[1], ao[0]] - - kern = kern.copy() - kern *= weight[None, :] - - # Zeroth and first derivatives - nderiv = kern.shape[0] - - first_pass = min(nderiv, 4) - eff_ao = _contract_kern_ao(kern[:first_pass], ao[1][:first_pass]) - nterm = eff_ao.shape[0] - eff = sum([_dot_ao_mo(otfnal.mol, a, v, non0tab=non0tab, - shls_slice=shls_slice, ao_loc=ao_loc, hermi=hermi) - for a, v in zip(ao[0][0:nterm], eff_ao)]) - - if nderiv > 4: - # check if we have laplacian... - vtau = kern[4] - - vtau *= 0.5 - eff += _tau_dot_ao_mo( - otfnal.mol, - ao[1], - ao[0], - vtau, - non0tab=non0tab, - shls_slice=shls_slice, - ao_loc=ao_loc, - hermi=hermi, - ) - - if nderiv > 5: - raise NotImplementedError("laplacian translated meta-GGA functional") - - return eff - -def get_eff_2body(otfnal, ao, weight, kern, aosym='s4', eff_ao=None): - if isinstance(ao, np.ndarray) and ao.ndim == 3: - ao = [ao, ao, ao, ao] - elif len(ao) != 4: - raise NotImplementedError('fancy orbital subsets and fast evaluation ' - 'in get_eff_2body') - - if isinstance(aosym, int): - aosym = str(aosym) - ij_symm = "4" in aosym or "2ij" in aosym - kl_symm = "4" in aosym or "2kl" in aosym - - if eff_ao is None: - eff_ao = otfnal.get_eff_2body_kl(ao[2], ao[3], weight, kern, symm=kl_symm) - - nderiv = eff_ao.shape[0] - ao2 = _contract_ao1_ao2(ao[0], ao[1], nderiv, symm=ij_symm) - try: - ao2 = ao2.transpose(0, 3, 2, 1) - - except ValueError as e: - print(ao[0].shape, ao[1].shape, ao2.shape) - raise(e) - - eff_ao = eff_ao.transpose(0, 3, 2, 1) - ijkl_shape = list(ao2.shape[1:-1]) + list(eff_ao.shape[1:-1]) - ao2 = ao2.reshape(ao2.shape[0], -1, ao2.shape[-1]).transpose(0, 2, 1) - eff_ao = eff_ao.reshape(eff_ao.shape[0], -1, eff_ao.shape[-1]).transpose(0, 2, 1) - eff = sum([_dot_ao_mo(otfnal.mol, a, v) for a, v in zip(ao2, eff_ao)]) - eff = eff.reshape(*ijkl_shape) - - return eff - - -def get_eff_2body_kl(ot, ao_k, ao_l, weight, kern, symm=False): - kern = kern.copy() - kern *= weight[None, :] - - # Flatten deriv and grid so I can tensordot it all at once - # Index symmetry can be built into _contract_ao1_ao2 - kern_ao = _contract_kern_ao(kern, ao_l) - eff_ao = _contract_ao_eff_ao(ao_k, kern_ao, symm=symm) - return eff_ao - -def _dot_ao_mo(mol, ao, mo, non0tab=None, shls_slice=None, ao_loc=None, - hermi=0): - # f_ij = int g_i(r) * h_j(r) dr - # Like numint._dot_ao_ao, but allows for two different bases on the - # rows and columns - if hermi: - return numint._dot_ao_ao(mol, ao, mo, non0tab=non0tab, - shls_slice=shls_slice, ao_loc=ao_loc, hermi=hermi) - ngrids, nao = ao.shape - nmo = mo.shape[-1] - - if nao < SWITCH_SIZE: - return lib.dot(ao.T.conj(), mo) - - if not ao.flags.f_contiguous: - ao = lib.transpose(ao) - if not mo.flags.f_contiguous: - mo = lib.transpose(mo) - if ao.dtype == mo.dtype == np.double: - fn = libpdft.VOTdot_ao_mo - else: - raise NotImplementedError("Complex-orbital PDFT") - - if non0tab is None or shls_slice is None or ao_loc is None: - pnon0tab = pshls_slice = pao_loc = lib.c_null_ptr() - else: - pnon0tab = non0tab.ctypes.data_as(ctypes.c_void_p) - pshls_slice = (ctypes.c_int * 2)(*shls_slice) - pao_loc = ao_loc.ctypes.data_as(ctypes.c_void_p) - - vv = np.empty((nao, nmo), dtype=ao.dtype) - fn(vv.ctypes.data_as(ctypes.c_void_p), - ao.ctypes.data_as(ctypes.c_void_p), - mo.ctypes.data_as(ctypes.c_void_p), - ctypes.c_int(nao), ctypes.c_int(nmo), - ctypes.c_int(ngrids), ctypes.c_int(mol.nbas), - pnon0tab, pshls_slice, pao_loc) - return vv - - -# TODO: unittest? -def _contract_kern_ao(vot, ao, out=None): - # Evaluate v_i(r) = v(r) * AO_i(r) and its derivatives on a grid - # Note that the chain rule means that v' * AO' -> v, v' * AO -> v' - ''' REQUIRES array in shape = (nderiv,nao,ngrids) and data layout - = (nderiv,ngrids,nao)/row-major ''' - nderiv = vot.shape[0] - ao = np.ascontiguousarray(ao.transpose(0, 2, 1)) - nao, ngrids = ao.shape[1:] - vao = np.ndarray((nderiv, nao, ngrids), dtype=ao.dtype, - buffer=out).transpose(0, 2, 1) - ao = ao.transpose(0, 2, 1) - vao[0] = numint._scale_ao(ao[:nderiv], vot, out=vao[0]) - if nderiv > 1: - for i in range(1, 4): - vao[i] = numint._scale_ao(ao[0:1, :, :], vot[i:i + 1, :], out=vao[i]) - - return vao - - -def _contract_ao_eff_ao(ao, vao, symm=False): - r''' Outer-product of ao grid and eff * ao grid - Can be used with two-orb-dimensional vao if the last two dimensions - are flattened into "nao" - - Args: - ao : ndarray of shape (*,ngrids,nao1) - vao : ndarray of shape (nderiv,ngrids,nao2) - - Kwargs: - symm : logical - If true, nao1 == nao2 must be true - - Returns: ndarray of shape (nderiv,ngrids,nao1,nao2) - or (nderiv,ngrids,nao1*(nao1+1)//2) - ''' - ao = ao.transpose(0, 2, 1) - vao = vao.transpose(0, 2, 1) - assert ao.flags.c_contiguous, 'shape = {} ; strides = {}'.format( - ao.shape, ao.strides) - assert vao.flags.c_contiguous, 'shape = {} ; strides = {}'.format( - vao.shape, vao.strides) - - nderiv = vao.shape[0] - if symm: - ix_p, ix_q = np.tril_indices(ao.shape[1]) - ao = ao[:, ix_p] - vao = vao[:, ix_q] - else: - ao = np.expand_dims(ao, -2) - vao = np.expand_dims(vao, -3) - prod = ao[0] * vao - if nderiv > 1: - prod[0] += (ao[1:4] * vao[1:4]).sum(0) - if symm: - prod = prod.transpose(0, 2, 1) - else: - prod = prod.transpose(0, 3, 2, 1) - return prod - - -# TODO: unittest? -def _contract_ao1_ao2(ao1, ao2, nderiv, symm=False): - # Evaluate P_ij(r) = AO_i(r) * AO_j(r) and its derivatives on a grid - ao1 = ao1.transpose(0, 2, 1) - ao2 = ao2.transpose(0, 2, 1) - assert (ao1.flags.c_contiguous), 'shape = {} ; strides = {}'.format( - ao1.shape, ao1.strides) - assert (ao2.flags.c_contiguous), 'shape = {} ; strides = {}'.format( - ao2.shape, ao2.strides) - if symm: # TODO: C implementation of this slow indexing - ix_p, ix_q = np.tril_indices(ao1.shape[1]) - ao1 = ao1[:nderiv, ix_p] - ao2 = ao2[:nderiv, ix_q] - else: - ao1 = np.expand_dims(ao1, -2)[:nderiv] - ao2 = np.expand_dims(ao2, -3)[:nderiv] - prod = ao1[:nderiv] * ao2[0] - if nderiv > 1: - prod[1:4] += ao1[0] * ao2[1:4] # Product rule - ao2 = None - if symm: - prod = prod.transpose(0, 2, 1) - else: - prod = prod.transpose(0, 3, 2, 1) - return prod - - -def _tau_dot_ao_mo( - mol, ao, mo, vtau, non0tab=None, shls_slice=None, ao_loc=None, hermi=0 -): - vao = numint._scale_ao(ao[1], vtau) - eff = _dot_ao_mo( - mol, - mo[1], - vao, - non0tab=non0tab, - shls_slice=shls_slice, - ao_loc=ao_loc, - hermi=hermi, - ) - vao = numint._scale_ao(ao[2], vtau) - eff += _dot_ao_mo( - mol, - mo[2], - vao, - non0tab=non0tab, - shls_slice=shls_slice, - ao_loc=ao_loc, - hermi=hermi, - ) - vao = numint._scale_ao(ao[3], vtau) - eff += _dot_ao_mo( - mol, - mo[3], - vao, - non0tab=non0tab, - shls_slice=shls_slice, - ao_loc=ao_loc, - hermi=hermi, - ) - return eff diff --git a/pyscf/mcpdft/pdft_feff.py b/pyscf/mcpdft/pdft_feff.py deleted file mode 100644 index f65b03907..000000000 --- a/pyscf/mcpdft/pdft_feff.py +++ /dev/null @@ -1,415 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -import numpy as np -from pyscf.lib import logger, tag_array, current_memory -from pyscf.dft.gen_grid import BLKSIZE -from pyscf.mcpdft.otpd import get_ontop_pair_density -from pyscf.mcpdft.pdft_eff import _ERIS -from pyscf.mcpdft.tfnal_derivs import contract_fot -import gc - - -def kernel(ot, dm1s, cascm2, c_dm1s, c_cascm2, mo_coeff, ncore, ncas, max_memory=2000, hermi=1, paaa_only=False, - aaaa_only=False, jk_pc=False, delta=False): - r'''Get the 1- and 2-body effective gradient responses from MC-PDFT. The - $\rho \cdot \mathbf{F}$ terms, or Hessian vector products. - - Args: - ot : an instance of otfnal class - dm1s : ndarray of shape (2, nao, nao) - Contains the spin-separated one-body density matrices to evaluate - the kernel at - cascm2 : ndarray of shape (ncas, ncas, ncas, ncas) - Spin-summed two-body cumulant density matrix in the active space to - evaluate the kernel at - c_dm1s : ndarray of shape (2, nao, nao) - Contains the spin-separated one-body density matrices to contract - the kernel with. - c_cascm2 : ndarray of shape (ncas, ncas, ncas, ncas) - Spin-summed two-body cumulant density matrix in the active space to - contract the kernel with. - mo_coeff : ndarray of shape (nao, nmo) - containing molecular orbital coefficients - ncore : integer - number of inactive orbitals - ncas : integer - number of active orbitals - - Kwargs: - max_memory : int or float - maximum cache size in MB - default is 2000 - hermi : int - 1 if 1rdms are assumed hermitian, 0 otherwise - paaa_only : logical - If true, only compute the paaa range of papa and ppaa (all other - elements set to zero) - aaaa_only : logical - If true, only compute the aaaa range of papa and ppaa (all other - elements set to zero; overrides paaa_only) - jk_pc : logical - If true, compute the ppii=pipi elements of veff2 (otherwise, these - are set to zero) - delta : logical - If true, then contract with the delta density. The delta density is the c_dm1s-dm1s and similarly for the - 2rdm element (though care is taken since 2rdm elements are expressed in cumulant form). - - Returns: - feff1 : ndarray of shape (nao, nao) - 1-body effective gradient response - feff2 : object of class pdft_eff._ERIS - 2-body effective gradient response - ''' - nocc = ncore + ncas - ni, xctype, dens_deriv = ot._numint, ot.xctype, ot.dens_deriv - nao = mo_coeff.shape[0] - mo_core = mo_coeff[:, :ncore] - mo_cas = mo_coeff[:, ncore:nocc] - shls_slice = (0, ot.mol.nbas) - ao_loc = ot.mol.ao_loc_nr() - - omega, alpha, hyb = ot._numint.rsh_and_hybrid_coeff(ot.otxc) - hyb_x, hyb_c = hyb - if abs(omega) > 1e-11: - raise NotImplementedError("range-separated on-top functionals") - - if abs(hyb_x) > 1e-11 or abs(hyb_c) > 1e-11: - raise NotImplementedError("effective potential for hybrid functionals") - - feff1 = np.zeros((nao, nao), dtype=dm1s.dtype) - feff2 = _ERIS(ot.mol, mo_coeff, ncore, ncas, paaa_only=paaa_only, - aaaa_only=aaaa_only, jk_pc=jk_pc, verbose=ot.verbose, - stdout=ot.stdout) - - t0 = (logger.process_clock(), logger.perf_counter()) - - # Density matrices - dm_core = mo_core @ mo_core.T - dm_cas = dm1s - dm_core[None, :, :] - dm_core *= 2 - - # Propagate speedup tags - if hasattr(dm1s, 'mo_coeff') and hasattr(dm1s, 'mo_occ'): - dm_core = tag_array(dm_core, mo_coeff=dm1s.mo_coeff[0, :, :ncore], - mo_occ=dm1s.mo_occ[:, :ncore].sum(0)) - dm_cas = tag_array(dm_cas, mo_coeff=dm1s.mo_coeff[:, :, ncore:nocc], - mo_occ=dm1s.mo_occ[:, ncore:nocc]) - - # rho generators - make_rho_c = ni._gen_rho_evaluator(ot.mol, dm_core, hermi)[0] - make_rho_a = ni._gen_rho_evaluator(ot.mol, dm_cas, hermi)[0] - make_rho = ni._gen_rho_evaluator(ot.mol, dm1s, hermi)[0] - make_crho = ni._gen_rho_evaluator(ot.mol, c_dm1s, hermi)[0] - - # memory block size - gc.collect() - remaining_floats = (max_memory - current_memory()[0]) * 1e6 / 8 - nderiv_rho = (1, 4, 10)[dens_deriv] # ?? for meta-GGA - nderiv_Pi = (1, 4)[ot.Pi_deriv] - ncols = 4 + nderiv_rho * nao # ao, weight, coords - ncols += nderiv_rho * 4 + nderiv_Pi # rho, rho_a, rho_c, Pi - ncols += 1 + nderiv_rho + nderiv_Pi # eot, vot - ncols += (nderiv_rho*nderiv_Pi*(nderiv_rho*nderiv_Pi + 1))/2 # fot - - # Asynchronous part - nfeff1 = nderiv_rho * (nao + 1) # footprint of get_eff_1body - nfeff2 = feff2._accumulate_ftpt() * nderiv_Pi - ncols += np.amax([nfeff1, nfeff2]) # asynchronous fns - pdft_blksize = int(remaining_floats / (ncols * BLKSIZE)) * BLKSIZE - if ot.grids.coords is None: - ot.grids.build(with_non0tab=True) - ngrids = ot.grids.coords.shape[0] - ngrids_blk = int(ngrids / BLKSIZE) * BLKSIZE - pdft_blksize = max(BLKSIZE, min(pdft_blksize, ngrids_blk, BLKSIZE * 1200)) - logger.debug(ot, ('{} MB used of {} available; block size of {} chosen' - 'for grid with {} points').format(current_memory()[0], max_memory, - pdft_blksize, ngrids)) - - for ao, mask, weight, coords in ni.block_loop(ot.mol, ot.grids, nao, - dens_deriv, max_memory, - blksize=pdft_blksize): - rho = np.asarray([make_rho(i, ao, mask, xctype) for i in range(2)]) - crho = np.asarray([make_crho(i, ao, mask, xctype) for i in range(2)]) - rho_a = sum([make_rho_a(i, ao, mask, xctype) for i in range(2)]) - rho_c = make_rho_c(0, ao, mask, xctype) - t0 = logger.timer(ot, 'untransformed densities (core and total)', *t0) - - Pi = get_ontop_pair_density(ot, rho, ao, cascm2, mo_cas, - dens_deriv, mask) - cPi = get_ontop_pair_density(ot, crho, ao, c_cascm2, mo_cas, - dens_deriv, mask) - t0 = logger.timer(ot, 'on-top pair density calculation', *t0) - - if delta: - crho -= rho - cPi -= Pi - - vot, fot = ot.eval_ot(rho, Pi, weights=weight, dderiv=2, - _unpack_vot=False)[1:] - frho, fPi = contract_fot(ot, fot, rho, Pi, crho, cPi, unpack=True, - vot_packed=vot) - - t0 = logger.timer(ot, 'effective gradient response kernel calculation', - *t0) - - if ao.ndim == 2: - ao = ao[None, :, :] - - feff1 += ot.get_eff_1body(ao, weight, frho, non0tab=mask, - shls_slice=shls_slice, ao_loc=ao_loc, - hermi=1) - t0 = logger.timer(ot, '1-body effective gradient response calculation', - *t0) - - feff2._accumulate(ot, ao, weight, rho_c, rho_a, fPi, mask, shls_slice, - ao_loc) - t0 = logger.timer(ot, '2-body effective gradient response calculation', - *t0) - - feff2._finalize() - t0 = logger.timer(ot, 'Finalizing 2-body gradient response calculation', - *t0) - - return feff1, feff2 - - -def lazy_kernel(ot, dm1s, cascm2, c_dm1s, c_cascm2, mo_cas, hermi=1, max_memory=2000, delta=False): - '''1- and 2-body gradient response (hessian-vector products) from MC-PDFT. - This is the lazy way and doesn't care about memory.''' - ni, xctype, dens_deriv = ot._numint, ot.xctype, ot.dens_deriv - nao = mo_cas.shape[0] - - feff1 = np.zeros_like(dm1s[0]) - feff2 = np.zeros((nao, nao, nao, nao), dtype=feff1.dtype) - - t0 = (logger.process_clock(), logger.perf_counter()) - - make_rho = tuple(ni._gen_rho_evaluator(ot.mol, dm1s[i, :, :], hermi) for i - in range(2)) - make_crho = tuple(ni._gen_rho_evaluator(ot.mol, c_dm1s[i, :, :], hermi) for - i in range(2)) - - for ao, mask, weight, coords in ni.block_loop(ot.mol, ot.grids, nao, - dens_deriv, max_memory): - rho = np.asarray([m[0](0, ao, mask, xctype) for m in make_rho]) - crho = np.asarray([m[0](0, ao, mask, xctype) for m in make_crho]) - t0 = logger.timer(ot, 'untransformed density', *t0) - Pi = get_ontop_pair_density(ot, rho, ao, cascm2, mo_cas, dens_deriv, - mask) - cPi = get_ontop_pair_density(ot, crho, ao, c_cascm2, mo_cas, - dens_deriv, mask) - t0 = logger.timer(ot, 'on-top pair density calculation', *t0) - - if delta: - crho -= rho - cPi -= Pi - - vot, fot = ot.eval_ot(rho, Pi, weights=weight, dderiv=2, - _unpack_vot=False)[1:] - frho, fPi = contract_fot(ot, fot, rho, Pi, crho, cPi, unpack=True, - vot_packed=vot) - - t0 = logger.timer(ot, 'effective gradient response kernel calculation', - *t0) - if ao.ndim == 2: - ao = ao[None, :, :] - - feff1 += ot.get_eff_1body(ao, weight, frho) - t0 = logger.timer(ot, '1-body effective gradient response calculation', - *t0) - - feff2 += ot.get_eff_2body(ao, weight, fPi, aosym=1) - t0 = logger.timer(ot, '2-body effective gradient response calculation', - *t0) - - return feff1, feff2 - - -def get_feff_1body(otfnal, rho, Pi, crho, cPi, ao, weight, kern=None, non0tab=None, - shls_slice=None, ao_loc=None, hermi=0, **kwargs): - r"""Get the terms [\Delta F]_{pq} - - Args: - rho : ndarray of shape (2,*,ngrids) - Spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - On-top pair density [and derivatives] - crho : ndarray of shape (2,*,ngrids) - Spin-density [and derivatives] to contract the hessian with - cPi : ndarray with shape (*,ngrids) - On-top pair density [and derivatives] to contract Hessian with - ao : ndarray or 2 ndarrays of shape (*,ngrids,nao) - contains values and derivatives of nao. 2 different ndarrays can - have different nao but not different ngrids - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - kern : ndarray of shape (*,ngrids) - the hessian-vector product. If not provided, it is calculated. - non0tab : ndarray of shape (nblk, nbas) - Identifies blocks of grid points which are nonzero on each AO shell - so as to exploit sparsity. If you want the "ao" array to be in the - MO basis, just leave this as None. If hermi == 0, it only applies - to the bra index ao array, even if the ket index ao array is the - same (so probably always pass hermi = 1 in that case) - shls_slice : sequence of integers of len 2 - Identifies starting and stopping indices of AO shells - ao_loc : ndarray of length nbas - Offset to first AO of each shell - hermi : integer or logical - Toggle whether feff is supposed to be a Hermitian matrix You can - still pass two different ao arrays for the bra and the ket indices, - for instance if one of them is supposed to be a higher derivative. - They just have to have the same nao in that case. - - Returns : ndarray of shape (nao[0],nao[1]) - The 1-body effective gradient response corresponding to this on-top - pair density exchange-correlation functional, in the atomic-orbital - basis. In PDFT this functional is always spin-symmetric. - """ - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - if crho.ndim == 2: - crho = np.expand_dims(crho, 1) - cPi = np.expand_dims(cPi, 0) - - fot = otfnal.eval_ot(rho, Pi, dderiv=2, **kwargs)[2] - kern = contract_fot(otfnal, fot, rho, Pi, crho, cPi)[0] - rho = np.squeeze(rho) - Pi = np.squeeze(Pi) - crho = np.squeeze(crho) - cPi = np.squeeze(cPi) - - return otfnal.get_eff_1body(ao, weight, kern=kern, non0tab=non0tab, - shls_slice=shls_slice, ao_loc=ao_loc, - hermi=hermi) - - -def get_feff_2body(otfnal, rho, Pi, crho, cPi, ao, weight, aosym='s4', - kern=None, fao=None, **kwargs): - r'''Get the terms [\Delta F]_{pqrs} - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - crho : ndarray of shape (2,*,ngrids) - Spin-density [and derivatives] to contract the hessian with - cPi : ndarray with shape (*,ngrids) - On-top pair density [and derivatives] to contract Hessian with - ao : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) values and - derivatives of atomic or molecular orbitals in which space to - calculate the 2-body veff If a list of length 4, the - corresponding set of eri-like elements are returned - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - aosym : int or str - Index permutation symmetry of the desired integrals. Valid - options are 1 (or '1' or 's1'), 4 (or '4' or 's4'), '2ij' (or - 's2ij'), and '2kl' (or 's2kl'). These have the same meaning as - in PySCF's ao2mo module. Currently all symmetry exploitation is - extremely slow and unparallelizable for some reason so trying - to use this is not recommended until I come up with a C - routine. - kern : ndarray of shape (*,ngrids) - the hessian-vector product. If not provided, it is calculated. - fao : ndarray of shape (*,ngrids,nao,nao) or - (*,ngrids,nao*(nao+1)//2). An intermediate in which the kernel - and the k,l orbital indices have been contracted. Overrides - kl_symm - - Returns : eri-like ndarray - The two-body effective gradient response corresponding to this on-top - pair density exchange-correlation functional or elements - thereof, in the provided basis. - ''' - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - if crho.ndim == 2: - crho = np.expand_dims(crho, 1) - cPi = np.expand_dims(cPi, 0) - - fot = otfnal.eval_ot(rho, Pi, dderiv=2, **kwargs)[2] - kern = contract_fot(otfnal, fot, rho, Pi, crho, cPi)[1] - rho = np.squeeze(rho) - Pi = np.squeeze(Pi) - crho = np.squeeze(crho) - cPi = np.squeeze(cPi) - - return otfnal.get_eff_2body(ao, weight, kern, aosym=aosym, eff_ao=fao) - - -def get_feff_2body_kl(otfnal, rho, Pi, crho, cPi, ao_k, ao_l, weight, - symm=False, kern=None, **kwargs): - r''' get the two-index intermediate Mkl of [\Delta \cdot F]_{pqrs} - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - crho : ndarray of shape (2,*,ngrids) - Spin-density [and derivatives] to contract the hessian with - cPi : ndarray with shape (*,ngrids) - On-top pair density [and derivatives] to contract Hessian with - ao_k : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) values and - derivatives of atomic or molecular orbitals corresponding to - index k - ao_l : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) values and - derivatives of atomic or molecular orbitals corresponding to - index l - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - symm : logical - Index permutation symmetry of the desired integral wrt k,l - kern : ndarray of shape (*,ngrids) - the hessian-vector product. If not provided, it is calculated. - - Returns : ndarray of shape (*,ngrids,nao,nao) - or (*,ngrids,nao*(nao+1)//2). An intermediate for calculating the - two-body effective gradient response corresponding to this on-top - pair density exchange-correlation functional in the provided basis. - ''' - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - if crho.ndim == 2: - crho = np.expand_dims(crho, 1) - cPi = np.expand_dims(cPi, 0) - - fot = otfnal.eval_ot(rho, Pi, dderiv=2, **kwargs)[2] - kern = contract_fot(otfnal, fot, rho, Pi, crho, cPi)[1] - - return otfnal.get_eff_2body_kl(ao_k, ao_l, weight, kern=kern, symm=symm) diff --git a/pyscf/mcpdft/pdft_veff.py b/pyscf/mcpdft/pdft_veff.py deleted file mode 100644 index 829da1780..000000000 --- a/pyscf/mcpdft/pdft_veff.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/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. -# - -from pyscf.lib import logger, current_memory, tag_array -from pyscf.dft.gen_grid import BLKSIZE -from pyscf.mcpdft.otpd import get_ontop_pair_density -from pyscf.mcpdft.pdft_eff import _ERIS -import numpy as np -import gc - - -# MRH 05/18/2020: An annoying convention in pyscf.dft.numint that I have to -# comply with is that the AO grid-value arrays and all their derivatives are in -# NEITHER column-major NOR row-major order; they all have ndim = 3 and strides -# of (8*ngrids*nao, 8, 8*ngrids). Here, for my ndim > 3 objects, I choose to -# generalize this so that a cyclic transpose to the left, -# ao.transpose (1,2,3,...,0), places the array in column-major (FORTRAN-style) -# order. I have to comply with this awkward convention in order to take full -# advantage of the code in pyscf.dft.numint and libdft.so. The ngrids -# dimension, which is by far the largest, almost always benefits from having -# the smallest stride. - -# MRH 05/19/2020: Actually, I really should just turn the deriv component part -# of all of these arrays into lists and keep everything in col-major order -# otherwise, because ndarrays have to have regular strides, but lists can just -# be references and less copying is involved. The copying is more expensive and -# less transparently parallel-scalable than the actual math! (The -# parallel-scaling part of that is stupid but it is what it is.) - -def kernel(ot, dm1s, cascm2, mo_coeff, ncore, ncas, - max_memory=2000, hermi=1, paaa_only=False, aaaa_only=False, - jk_pc=False): - '''Get the 1- and 2-body effective potential from MC-PDFT. - - Args: - ot : an instance of otfnal class - dm1s : ndarray of shape (2, nao, nao) - containing spin-separated one-body density matrices - cascm2 : ndarray of shape (ncas, ncas, ncas, ncas) - containing spin-summed two-body cumulant density matrix in - an active space - mo_coeff : ndarray of shape (nao, nmo) - containing molecular orbital coefficients - ncore : integer - number of inactive orbitals - ncas : integer - number of active orbitals - - Kwargs: - max_memory : int or float - maximum cache size in MB - default is 2000 - hermi : int - 1 if 1rdms are assumed hermitian, 0 otherwise - paaa_only : logical - If true, only compute the paaa range of papa and ppaa - (all other elements set to zero) - aaaa_only : logical - If true, only compute the aaaa range of papa and ppaa - (all other elements set to zero; overrides paaa_only) - jk_pc : logical - If true, compute the ppii=pipi elements of veff2 - (otherwise, these are set to zero) - - Returns: - veff1 : ndarray of shape (nao, nao) - 1-body effective potential - veff2 : object of class pdft_eff._ERIS - 2-body effective potential and related quantities - ''' - nocc = ncore + ncas - ni, xctype, dens_deriv = ot._numint, ot.xctype, ot.dens_deriv - nao = mo_coeff.shape[0] - mo_core = mo_coeff[:, :ncore] - mo_cas = mo_coeff[:, ncore:nocc] - shls_slice = (0, ot.mol.nbas) - ao_loc = ot.mol.ao_loc_nr() - - omega, alpha, hyb = ot._numint.rsh_and_hybrid_coeff(ot.otxc) - hyb_x, hyb_c = hyb - if abs(omega) > 1e-11: - raise NotImplementedError("range-separated on-top functionals") - - if abs(hyb_x - hyb_c) > 1e-11: - raise NotImplementedError( - "effective potential for hybrid functionals with different exchange, correlations components") - - - E_ot = 0.0 - veff1 = np.zeros((nao, nao), dtype=dm1s.dtype) - veff2 = _ERIS(ot.mol, mo_coeff, ncore, ncas, paaa_only=paaa_only, - aaaa_only=aaaa_only, jk_pc=jk_pc, verbose=ot.verbose, - stdout=ot.stdout) - - t0 = (logger.process_clock(), logger.perf_counter()) - - # Density matrices - dm_core = mo_core @ mo_core.T - dm_cas = dm1s - dm_core[None, :, :] - dm_core *= 2 - - # Propagate speedup tags - if hasattr(dm1s, 'mo_coeff') and hasattr(dm1s, 'mo_occ'): - dm_core = tag_array(dm_core, mo_coeff=dm1s.mo_coeff[0, :, :ncore], - mo_occ=dm1s.mo_occ[:, :ncore].sum(0)) - dm_cas = tag_array(dm_cas, mo_coeff=dm1s.mo_coeff[:, :, ncore:nocc], - mo_occ=dm1s.mo_occ[:, ncore:nocc]) - - # rho generators - make_rho_c = ni._gen_rho_evaluator(ot.mol, dm_core, hermi=hermi, with_lapl=False)[0] - make_rho_a = ni._gen_rho_evaluator(ot.mol, dm_cas, hermi=hermi, with_lapl=False)[0] - make_rho = ni._gen_rho_evaluator(ot.mol, dm1s, hermi=hermi, with_lapl=False)[0] - - # memory block size - gc.collect() - remaining_floats = (max_memory - current_memory()[0]) * 1e6 / 8 - nderiv_rho = (1, 4, 5)[dens_deriv] - nderiv_Pi = (1, 4)[ot.Pi_deriv] - nderiv_ao = (1,4,10)[dens_deriv] - ncols = 4 + nderiv_rho * nao # ao, weight, coords - ncols += nderiv_rho * 4 + nderiv_Pi # rho, rho_a, rho_c, Pi - ncols += 1 + nderiv_rho + nderiv_Pi # eot, vot - - # Asynchronous part - nveff1 = nderiv_ao * (nao + 1) # footprint of get_veff_1body - nveff2 = veff2._accumulate_ftpt() * nderiv_Pi - ncols += np.amax([nveff1, nveff2]) # asynchronous fns - pdft_blksize = int(remaining_floats / (ncols * BLKSIZE)) * BLKSIZE - if ot.grids.coords is None: - ot.grids.build(with_non0tab=True) - ngrids = ot.grids.coords.shape[0] - ngrids_blk = int(ngrids / BLKSIZE) * BLKSIZE - pdft_blksize = max(BLKSIZE, min(pdft_blksize, ngrids_blk, BLKSIZE * 1200)) - logger.debug(ot, ('{} MB used of {} available; block size of {} chosen' - 'for grid with {} points').format(current_memory()[0], max_memory, - pdft_blksize, ngrids)) - - # The actual loop - for ao, mask, weight, coords in ni.block_loop(ot.mol, ot.grids, nao, - dens_deriv, max_memory, blksize=pdft_blksize): - rho = np.asarray([make_rho(i, ao, mask, xctype) for i in range(2)]) - rho_a = sum([make_rho_a(i, ao, mask, xctype) for i in range(2)]) - rho_c = make_rho_c(0, ao, mask, xctype) - t0 = logger.timer(ot, 'untransformed densities (core and total)', *t0) - Pi = get_ontop_pair_density(ot, rho, ao, cascm2, mo_cas, - deriv=ot.Pi_deriv, non0tab=mask) - t0 = logger.timer(ot, 'on-top pair density calculation', *t0) - eot, vot = ot.eval_ot(rho, Pi, weights=weight)[:2] - E_ot += eot.dot(weight) - vrho, vPi = vot - t0 = logger.timer(ot, 'effective potential kernel calculation', *t0) - if ao.ndim == 2: ao = ao[None, :, :] - # TODO: consistent format req's ao LDA case - veff1 += ot.get_eff_1body(ao, weight, kern=vrho, non0tab=mask, - shls_slice=shls_slice, ao_loc=ao_loc, hermi=1) - t0 = logger.timer(ot, '1-body effective potential calculation', *t0) - veff2._accumulate(ot, ao, weight, rho_c, rho_a, vPi, mask, - shls_slice, ao_loc) - t0 = logger.timer(ot, '2-body effective potential calculation', *t0) - veff2._finalize() - t0 = logger.timer(ot, 'Finalizing 2-body effective potential calculation', - *t0) - return E_ot, veff1, veff2 - - -def lazy_kernel(ot, dm1s, cascm2, mo_cas, max_memory=2000, hermi=1, - veff2_mo=None): - '''Get the 1- and 2-body effective potential from MC-PDFT. - Eventually I'll be able to specify mo slices for the 2-body part - - Args: - ot : an instance of otfnal class - dm1s : ndarray of shape (2, nao, nao) - containing spin-separated one-body density matrices - cascm2 : ndarray of shape (ncas, ncas, ncas, ncas) - containing spin-summed two-body cumulant density matrix - in an active space - mo_cas : ndarray of shape (nao, ncas) - containing molecular orbital coefficients for - active-space orbitals - - Kwargs: - max_memory : int or float - maximum cache size in MB - default is 2000 - hermi : int - 1 if 1rdms are assumed hermitian, 0 otherwise - - Returns : float - The MC-PDFT on-top exchange-correlation energy - ''' - if veff2_mo is not None: - raise NotImplementedError('Molecular orbital slices for 2-body part') - ni, xctype = ot._numint, ot.xctype - dens_deriv = ot.dens_deriv - Pi_deriv = ot.Pi_deriv - nao = mo_cas.shape[0] - - veff1 = np.zeros_like(dm1s[0]) - veff2 = np.zeros((nao, nao, nao, nao), dtype=veff1.dtype) - - t0 = (logger.process_clock(), logger.perf_counter()) - make_rho = tuple(ni._gen_rho_evaluator(ot.mol, dm1s[i, :, :], hermi=hermi, with_lapl=False) - for i in range(2)) - for ao, mask, weight, coords in ni.block_loop(ot.mol, ot.grids, nao, - dens_deriv, max_memory): - rho = np.asarray([m[0](0, ao, mask, xctype) for m in make_rho]) - t0 = logger.timer(ot, 'untransformed density', *t0) - Pi = get_ontop_pair_density(ot, rho, ao, cascm2, mo_cas, - deriv=Pi_deriv, non0tab=mask) - t0 = logger.timer(ot, 'on-top pair density calculation', *t0) - _, vot = ot.eval_ot(rho, Pi, weights=weight)[:2] - vrho, vPi = vot - t0 = logger.timer(ot, "effective potential kernel calculation", *t0) - if ao.ndim == 2: - ao = ao[None, :, :] - # TODO: consistent format req's ao LDA case - veff1 += ot.get_eff_1body(ao, weight, kern=vrho, non0tab=mask) - t0 = logger.timer(ot, '1-body effective potential calculation', *t0) - veff2 += ot.get_eff_2body(ao, weight, kern=vPi, aosym=1) - t0 = logger.timer(ot, '2-body effective potential calculation', *t0) - return veff1, veff2 - - -def get_veff_1body(otfnal, rho, Pi, ao, weight, kern=None, non0tab=None, - shls_slice=None, ao_loc=None, hermi=0, **kwargs): - r''' get the derivatives dEot / dDpq - Can also be abused to get semidiagonal dEot / dPppqq if you pass the - right kern and squared aos/mos - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - ao : ndarray or 2 ndarrays of shape (*,ngrids,nao) - contains values and derivatives of nao. - 2 different ndarrays can have different nao but not - different ngrids - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - kern : ndarray of shape (*,ngrids) - the derivative of the on-top potential with respect to - density (vrho)/ If not provided, it is calculated. - non0tab : ndarray of shape (nblk, nbas) - Identifies blocks of grid points which are nonzero on - each AO shell so as to exploit sparsity. - If you want the "ao" array to be in the MO basis, just - leave this as None. If hermi == 0, it only applies - to the bra index ao array, even if the ket index ao - array is the same (so probably always pass hermi = 1 - in that case) - shls_slice : sequence of integers of len 2 - Identifies starting and stopping indices of AO shells - ao_loc : ndarray of length nbas - Offset to first AO of each shell - hermi : integer or logical - Toggle whether veff is supposed to be a Hermitian matrix - You can still pass two different ao arrays for the bra and - the ket indices, for instance if one of them is supposed to - be a higher derivative. They just have to have the same nao - in that case. - - Returns : ndarray of shape (nao[0],nao[1]) - The 1-body effective potential corresponding to this on-top pair - density exchange-correlation functional, in the atomic-orbital - basis. In PDFT this functional is always spin-symmetric. - ''' - - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - kern = otfnal.eval_ot(rho, Pi, dderiv=1, **kwargs)[1][1] - rho = np.squeeze(rho) - Pi = np.squeeze(Pi) - - return otfnal.get_eff_1body(ao, weight, kern=kern, non0tab=non0tab, - shls_slice=shls_slice, ao_loc=ao_loc, - hermi=hermi) - - -def get_veff_2body(otfnal, rho, Pi, ao, weight, aosym='s4', kern=None, - vao=None, **kwargs): - r''' get the derivatives dEot / dPijkl - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - ao : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) - values and derivatives of atomic or molecular orbitals in - which space to calculate the 2-body veff - If a list of length 4, the corresponding set of eri-like - elements are returned - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - aosym : int or str - Index permutation symmetry of the desired integrals. Valid - options are 1 (or '1' or 's1'), 4 (or '4' or 's4'), '2ij' - (or 's2ij'), and '2kl' (or 's2kl'). These have the same - meaning as in PySCF's ao2mo module. Currently all symmetry - exploitation is extremely slow and unparallelizable for some - reason so trying to use this is not recommended until I come - up with a C routine. - kern : ndarray of shape (*,ngrids) - the derivative of the on-top potential with respect to pair - density (vot). If not provided, it is calculated. - vao : ndarray of shape (*,ngrids,nao,nao) or - (*,ngrids,nao*(nao+1)//2). An intermediate in which the - kernel and the k,l orbital indices have been contracted. - Overrides kl_symm - - Returns : eri-like ndarray - The two-body effective potential corresponding to this on-top - pair density exchange-correlation functional or elements - thereof, in the provided basis. - ''' - - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - kern = otfnal.eval_ot(rho, Pi, dderiv=1, **kwargs)[1][2] - - return otfnal.get_eff_2body(ao, weight, kern, aosym=aosym, eff_ao=vao) - - -def get_veff_2body_kl(otfnal, rho, Pi, ao_k, ao_l, weight, symm=False, - kern=None, **kwargs): - r''' get the two-index intermediate Mkl of dEot/dPijkl - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - ao_k : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) - values and derivatives of atomic or molecular orbitals - corresponding to index k - ao_l : ndarray of shape (*,ngrids,nao) - OR list of ndarrays of shape (*,ngrids,*) - values and derivatives of atomic or molecular orbitals - corresponding to index l - weight : ndarray of shape (ngrids) - containing numerical integration weights - - Kwargs: - symm : logical - Index permutation symmetry of the desired integral wrt k,l - kern : ndarray of shape (*,ngrids) - the derivative of the on-top potential with respect to pair - density (vot). If not provided, it is calculated. - - Returns : ndarray of shape (*,ngrids,nao,nao) - or (*,ngrids,nao*(nao+1)//2). An intermediate for calculating - the two-body effective potential corresponding to this on-top - pair density exchange-correlation functional in the provided - basis. - ''' - - if kern is None: - if rho.ndim == 2: - rho = np.expand_dims(rho, 1) - Pi = np.expand_dims(Pi, 0) - - kern = otfnal.eval_ot(rho, Pi, dderiv=1, **kwargs)[1][2] - return otfnal.get_eff_2body_kl(ao_k, ao_l, weight, kern=kern, symm=symm) diff --git a/pyscf/mcpdft/test/test_cmspdft.py b/pyscf/mcpdft/test/test_cmspdft.py deleted file mode 100644 index ec077e0b7..000000000 --- a/pyscf/mcpdft/test/test_cmspdft.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2025 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 gto, mcpdft, lib -import unittest - -degree = np.pi / 180.0 -THETA0 = 30 - - -def u_theta(theta=THETA0): - # The sign here is consistent with my desired variable convention: - # lower-triangular positive w/ row idx = initial state and col idx = - # final state - ct = np.cos(theta * degree) - st = np.sin(theta * degree) - return np.array([[ct, st], [-st, ct]]) - - -def numerical_Q(mc): - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - mo_cas = mc.mo_coeff[:, ncore:][:, :ncas] - - def num_Q(theta): - ci_theta = mc.get_ci_basis(uci=u_theta(theta)) - states_casdm1 = mc.fcisolver.states_make_rdm1(ci_theta, ncas, nelecas) - Q = 0 - for casdm1 in states_casdm1: - dm_cas = np.dot(mo_cas, casdm1) - dm_cas = np.dot(dm_cas, mo_cas.conj().T) - vj = mc._scf.get_j(dm=dm_cas) - Q += np.dot(vj.ravel(), dm_cas.ravel()) / 2 - return Q - - return num_Q - - -def get_lih(r, fnal="ftLDA,VWN3"): - global mols - mol = gto.M( - atom="Li 0 0 0\nH {} 0 0".format(r), - basis="sto3g", - output="/dev/null", - verbose=0, - ) - mols.append(mol) - mf = mol.RHF().run() - mc = mcpdft.CASSCF(mf, fnal, 2, 2, grids_level=1) - mc.fix_spin_(ss=0) - mc = mc.multi_state([0.5, 0.5], "cms").run(conv_tol=1e-8) - return mc - - -def setUpModule(): - global mols - mols = [] - - -def tearDownModule(): - global mols - [m.stdout.close() for m in mols] - del mols - - -class KnownValues(unittest.TestCase): - - def test_lih_cms2ftlda22(self): - # Reference values from OpenMolcas v22.02, tag 177-gc48a1862b - # Ignoring the PDFT energies and final states because of grid nonsense - mc = get_lih(1.5) - e_mcscf_avg = np.dot(mc.e_mcscf, mc.weights) - hcoup = abs(mc.heff_mcscf[1, 0]) - ct_mcscf = abs(mc.si_mcscf[0, 0]) - q_max = mc.diabatizer()[0] - - E_MCSCF_AVG_EXPECTED = -7.78902185 - Q_MAX_EXPECTED = 1.76394711 - HCOUP_EXPECTED = 0.0350876533212476 - CT_MCSCF_EXPECTED = 0.96259815333407572 - - with self.subTest("diabats"): - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertAlmostEqual(q_max, Q_MAX_EXPECTED, 5) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 5) - self.assertAlmostEqual(ct_mcscf, CT_MCSCF_EXPECTED, 5) - - with self.subTest("e coul"): - ci_theta0 = mc.get_ci_basis(uci=u_theta(THETA0)) - q_test, dQ_test, d2Q_test = mc.diabatizer(ci=ci_theta0)[:3] - num_Q = numerical_Q(mc) - delta = 0.01 - qm = num_Q(THETA0 - delta) - q0 = num_Q(THETA0) - qp = num_Q(THETA0 + delta) - dQ_ref = (qp - qm) / 2 / delta / degree - d2Q_ref = (qp + qm - 2 * q0) / degree / degree / delta / delta - with self.subTest(deriv=0): - self.assertLess(q_test, q_max) - self.assertAlmostEqual(q_test, q0, 9) - with self.subTest(deriv=1): - self.assertAlmostEqual(dQ_test[0], dQ_ref, 6) - with self.subTest(deriv=2): - self.assertAlmostEqual(d2Q_test[0, 0], d2Q_ref, 6) - - with self.subTest("e coul old"): - ci_theta0 = mc.get_ci_basis(uci=u_theta(THETA0)) - q_test, dQ_test, d2Q_test = mc.diabatizer(ci=ci_theta0)[:3] - from pyscf.mcpdft.cmspdft import e_coul_o0 - - q_ref, dQ_ref, d2Q_ref = e_coul_o0(mc, ci_theta0) - with self.subTest(deriv=0): - self.assertAlmostEqual(q_test, q_ref, 9) - with self.subTest(deriv=1): - self.assertAlmostEqual(dQ_test[0], dQ_ref[0], 9) - with self.subTest(deriv=2): - self.assertAlmostEqual(d2Q_test[0, 0], d2Q_ref[0, 0], 9) - - with self.subTest("e coul update"): - theta_rand = 360 * np.random.rand() - 180 - u_rand = u_theta(theta_rand) - ci_rand = mc.get_ci_basis(uci=u_rand) - q, _, _, q_update = mc.diabatizer() - q_ref, dQ_ref, d2Q_ref = mc.diabatizer(ci=ci_rand)[:3] - q_test, dQ_test, d2Q_test = q_update(u_rand) - with self.subTest(deriv=0): - self.assertLessEqual(q_test, q_max) - self.assertAlmostEqual(q_test, q_ref, 9) - with self.subTest(deriv=1): - self.assertAlmostEqual(dQ_test[0], dQ_ref[0], 9) - with self.subTest(deriv=2): - self.assertAlmostEqual(d2Q_test[0, 0], d2Q_ref[0, 0], 9) - - def test_scanner(self): - mc1 = get_lih(1.5) - mc2 = get_lih(1.55).as_scanner() - - mc2(mc1.mol) - self.assertTrue(mc1.converged) - self.assertTrue(mc2.converged) - self.assertAlmostEqual(lib.fp(mc1.e_states), lib.fp(mc2.e_states), 6) - - def test_lih_cms2mc2322(self): - mc = get_lih(1.5, fnal="MC23") - e_mcscf_avg = np.dot(mc.e_mcscf, mc.weights) - hcoup = abs(mc.heff_mcscf[0, 1]) - ct_mcscf = abs(mc.si_mcscf[0, 0]) - ct_pdft = abs(mc.si_pdft[0, 0]) - - HCOUP_EXPECTED = 0.03508667 - E_MCSCF_AVG_EXPECTED = -7.789021830554006 - E_STATES_EXPECTED = [-7.93513351, -7.77927879] - CT_MCSCF_EXPECTED = 0.9626004825617019 - CT_PDFT = 0.9728574801328089 - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 8) - self.assertAlmostEqual(lib.fp(mc.e_states), lib.fp(E_STATES_EXPECTED), 8) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 8) - self.assertAlmostEqual(ct_mcscf, CT_MCSCF_EXPECTED, 8) - self.assertAlmostEqual(ct_pdft, CT_PDFT, 8) - - -if __name__ == "__main__": - print("Full Tests for CMS-PDFT function") - unittest.main() diff --git a/pyscf/mcpdft/test/test_diatomic_energies.py b/pyscf/mcpdft/test/test_diatomic_energies.py deleted file mode 100644 index 883cd05be..000000000 --- a/pyscf/mcpdft/test/test_diatomic_energies.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/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 pyscf import gto, scf, df, fci -from pyscf.fci.addons import fix_spin_ -from pyscf import mcpdft -#from pyscf.dft.openmolcas_grids import quasi_ultrafine -#from pyscf.fci import csf_solver -import unittest - -# Need to use custom grids to get consistent agreement w/ the other program -# particularly for ftPBE test below. -# TODO: type in orbital initialization from OpenMolcas converged orbitals, -# which should result in agreement to 1e-8. - -############################################################################## -# Inline definition of quadrature grid that can be implemented in OpenMolcas # -# v21.10. The corresponding input to the SEWARD module in OpenMolcas is # -# grid input # -# nr=100 # -# lmax=41 # -# rquad=ta # -# nopr # -# noro # -# end of grid input # -############################################################################## - -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] - -my_grids = {'atom_grid': (99,590), - 'radi_method': om_treutler_ahlrichs, - 'prune': False, - 'radii_adjust': None} - -def diatomic (atom1, atom2, r, fnal, basis, ncas, nelecas, nstates, charge=None, spin=None, - symmetry=False, cas_irrep=None, density_fit=False): - 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') - mf = scf.RHF (mol) - if density_fit: mf = mf.density_fit (auxbasis = df.aug_etb (mol)) - mf.kernel () - mc = mcpdft.CASSCF (mf, fnal, ncas, nelecas, grids_attr=my_grids) - #if spin is not None: smult = spin+1 - #else: smult = (mol.nelectron % 2) + 1 - #mc.fcisolver = csf_solver (mol, smult=smult) - if spin is None: spin = mol.nelectron%2 - ss = spin*(spin+2)*0.25 - mc = mc.multi_state ([1.0/float(nstates),]*nstates, 'cms') - mc.fix_spin_(ss=ss, shift=1) - mc.conv_tol = mc.conv_tol_sarot = 1e-12 - mo = None - if symmetry and (cas_irrep is not None): - mo = mc.sort_mo_by_irrep (cas_irrep) - mc.kernel (mo) - return mc.e_states - -def tearDownModule(): - global diatomic - del diatomic - -class KnownValues(unittest.TestCase): - - def test_h2_cms3ftlda22_sto3g (self): - e = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', 'STO-3G', 2, 2, 3) - e_ref = [-1.02544144, -0.44985771, -0.23390995] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (3): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 6) - - def test_h2_cms2ftlda22_sto3g (self): - e = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2) - e_ref = [-1.11342858, -0.50064433] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 6) - - def test_h2_cms3ftlda22_631g (self): - e = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', '6-31G', 2, 2, 3) - e_ref = [-1.08553117, -0.69136123, -0.49602992] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (3): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 4) - - def test_h2_cms2ftlda22_631g (self): - e = diatomic ('H', 'H', 1.3, 'ftLDA,VWN3', '6-31G', 2, 2, 2) - e_ref = [-1.13120015, -0.71600911] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 5) - - def test_lih_cms2ftlda44_sto3g (self): - e = diatomic ('Li', 'H', 1.8, 'ftLDA,VWN3', 'STO-3G', 4, 4, 2, symmetry=True, cas_irrep={'A1': 4}) - e_ref = [-7.86001566, -7.71804507] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 5) - - def test_lih_cms2ftlda22_sto3g (self): - e = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2) - e_ref = [-7.77572652, -7.68950326] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 6) - - def test_lih_cms2ftpbe22_sto3g (self): - e = diatomic ('Li', 'H', 2.5, 'ftPBE', 'STO-3G', 2, 2, 2) - e_ref = [-7.83953187, -7.75506453] - # Reference values obtained with OpenMolcas - # version: 22.02 - # tag: 277-gd1f6a7392 - # commit: c3bdc83f9213a511233096e94715be3bbc73fb94 - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 6) - - def test_lih_cms2ftlda22_sto3g_df (self): - e = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 2, density_fit=True) - e_ref = [-7.776307, -7.689764] - # Reference values from this program - for i in range (2): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 5) - - def test_lih_cms3ftlda22_sto3g (self): - e = diatomic ('Li', 'H', 2.5, 'ftLDA,VWN3', 'STO-3G', 2, 2, 3) - e_ref = [-7.79692534, -7.64435032, -7.35033371] - # Reference values obtained with OpenMolcas - # version: 21.06 - # tag: 109-gbd596f6ca-dirty - # commit: bd596f6cabd6da0301f3623af2de6a14082b34b5 - for i in range (3): - with self.subTest (state=i): - self.assertAlmostEqual (e[i], e_ref[i], 5) - - -if __name__ == "__main__": - print("Full Tests for CMS-PDFT energies of diatomic molecules") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/test/test_lpdft.py b/pyscf/mcpdft/test/test_lpdft.py deleted file mode 100644 index 8795844c5..000000000 --- a/pyscf/mcpdft/test/test_lpdft.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -import tempfile, h5py -import numpy as np -from pyscf import gto, scf, dft, fci, lib -from pyscf import mcpdft -import unittest - - -def get_lih (r, n_states=2, functional='ftLDA,VWN3', basis='sto3g'): - mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis=basis, - output='/dev/null', verbose=0) - mf = scf.RHF (mol).run () - if n_states == 2: - mc = mcpdft.CASSCF (mf, functional, 2, 2, grids_level=1) - - else: - mc = mcpdft.CASSCF(mf, functional, 5, 2, grids_level=1) - - mc.fix_spin_(ss=0) - weights = [1.0/float(n_states), ] * n_states - - mc = mc.multi_state(weights, "lin") - mc = mc.run() - return mc - -def get_water(functional='tpbe', basis='6-31g'): - mol = gto.M(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 - ''',symmetry=True, basis=basis, output='/dev/null', verbose=0) - - mf = scf.RHF(mol).run() - - weights = [0.5, 0.5] - solver1 = fci.direct_spin1_symm.FCI(mol) - solver1.wfnsym = 'A1' - solver1.spin = 0 - solver2 = fci.direct_spin1_symm.FCI(mol) - solver2.wfnsym = 'A2' - solver2.spin = 2 - - mc = mcpdft.CASSCF(mf, functional, 4, 4, grids_level=1) - mc.chkfile = tempfile.NamedTemporaryFile().name - # mc.chk_ci = True - mc = mc.multi_state_mix([solver1, solver2], weights, "lin") - mc.run() - return mc - -def get_water_triplet(functional='tPBE', basis="6-31G"): - mol = gto.M(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 - ''', symmetry=True, basis=basis, output='/dev/null', verbose=0) - - mf = scf.RHF(mol).run() - - weights = np.ones(3) / 3 - solver1 = fci.direct_spin1_symm.FCI(mol) - solver1.spin = 2 - solver1 = fci.addons.fix_spin(solver1, shift=.2, ss=2) - solver1.nroots = 1 - solver2 = fci.direct_spin0_symm.FCI(mol) - solver2.spin = 0 - solver2.nroots = 2 - - mc = mcpdft.CASSCF(mf, functional, 4, 4, grids_level=1) - mc.chkfile = tempfile.NamedTemporaryFile().name - # mc.chk_ci = True - mc = mc.multi_state_mix([solver1, solver2], weights, "lin") - mc.run() - return mc - - -def setUpModule(): - global lih, lih_4, lih_tpbe, lih_tpbe0, lih_mc23, water, t_water, original_grids - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - lih = get_lih(1.5) - lih_4 = get_lih(1.5, n_states=4, basis="6-31G") - lih_tpbe = get_lih(1.5, functional="tPBE") - lih_tpbe0 = get_lih(1.5, functional="tPBE0") - lih_mc23 = get_lih(1.5, functional="MC23") - water = get_water() - t_water = get_water_triplet() - -def tearDownModule(): - global lih, lih_4, lih_tpbe0, lih_tpbe, t_water, water, original_grids, lih_mc23 - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - lih.mol.stdout.close() - lih_4.mol.stdout.close() - lih_tpbe0.mol.stdout.close() - lih_tpbe.mol.stdout.close() - water.mol.stdout.close() - t_water.mol.stdout.close() - del lih, lih_4, lih_tpbe0, lih_tpbe, t_water, water, original_grids, lih_mc23 - -class KnownValues(unittest.TestCase): - - def assertListAlmostEqual(self, first_list, second_list, expected): - self.assertTrue(len(first_list) == len(second_list)) - for first, second in zip(first_list, second_list): - self.assertAlmostEqual(first, second, expected) - - def test_lih_2_states_adiabat(self): - e_mcscf_avg = np.dot (lih.e_mcscf, lih.weights) - hcoup = abs(lih.lpdft_ham[1,0]) - hdiag = lih.get_lpdft_diag() - - e_states = lih.e_states - - # Reference values from OpenMolcas v22.02, tag 177-gc48a1862b - E_MCSCF_AVG_EXPECTED = -7.78902185 - - # Below reference values from - # - PySCF commit 71fc2a41e697fec76f7f9a5d4d10fd2f2476302c - # - mrh commit c5fc02f1972c1c8793061f20ed6989e73638fc5e - HCOUP_EXPECTED = 0.01663680 - HDIAG_EXPECTED = [-7.87848993, -7.72984482] - - E_STATES_EXPECTED = [-7.88032921, -7.72800554] - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertAlmostEqual(abs(hcoup), HCOUP_EXPECTED, 7) - self.assertListAlmostEqual(hdiag, HDIAG_EXPECTED, 7) - self.assertListAlmostEqual(e_states, E_STATES_EXPECTED, 7) - - def test_lih_4_states_adiabat(self): - e_mcscf_avg = np.dot(lih_4.e_mcscf, lih_4.weights) - hdiag = lih_4.get_lpdft_diag() - hcoup = lih_4.lpdft_ham[np.triu_indices(4, k=1)] - e_states = lih_4.e_states - - # References values from - # - PySCF commit 71fc2a41e697fec76f7f9a5d4d10fd2f2476302c - # - PySCF-forge commit 00183c314ebbf541f8461e7b7e5ee9e346fd6ff5 - E_MCSCF_AVG_EXPECTED = -7.88112386 - HDIAG_EXPECTED = [-7.99784259, -7.84720560, -7.80476518, -7.80476521] - HCOUP_EXPECTED = [0.01479405,0,0,0,0,0] - E_STATES_EXPECTED = [-7.99928176, -7.84576642, -7.80476519, -7.80476519] - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertListAlmostEqual(hdiag, HDIAG_EXPECTED, 7) - self.assertListAlmostEqual(list(map(abs, hcoup)), HCOUP_EXPECTED, 7) - self.assertListAlmostEqual(e_states, E_STATES_EXPECTED, 7) - - - def test_lih_hybrid_tPBE_adiabat(self): - e_mcscf_tpbe_avg = np.dot(lih_tpbe.e_mcscf, lih_tpbe.weights) - e_mcscf_tpbe0_avg = np.dot(lih_tpbe0.e_mcscf, lih_tpbe0.weights) - - hlpdft_ham = 0.75 * lih_tpbe.lpdft_ham - idx = np.diag_indices_from(hlpdft_ham) - hlpdft_ham[idx] += 0.25 * lih_tpbe.e_mcscf - e_hlpdft, si_hlpdft = lih_tpbe._eig_si(hlpdft_ham) - - # References values from - # - PySCF commit 8ae2bb2eefcd342c52639097517b1eda7ca5d1cd - # - PySCF-forge commit a7b8b3bb291e528088f9cefab007438d9e0f4701 - E_MCSCF_AVG_EXPECTED = -7.78902182 - E_TPBE_STATES_EXPECTED = [-7.93389909, -7.78171959] - - - self.assertAlmostEqual(e_mcscf_tpbe_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertAlmostEqual(e_mcscf_tpbe_avg, e_mcscf_tpbe0_avg, 9) - self.assertListAlmostEqual(lih_tpbe.e_states, E_TPBE_STATES_EXPECTED, 7) - self.assertListAlmostEqual(lih_tpbe0.e_states, e_hlpdft, 9) - self.assertListAlmostEqual(hlpdft_ham.flatten(), lih_tpbe0.lpdft_ham.flatten(), 9) - - def test_lih_mc23_adiabat(self): - e_mcscf_mc23_avg = np.dot(lih_mc23.e_mcscf, lih_mc23.weights) - hcoup = abs(lih_mc23.lpdft_ham[1,0]) - hdiag = lih_mc23.get_lpdft_diag() - - # Reference values from - # - PySCF commit 9a0bb6ddded7049bdacdaf4cfe422f7ce826c2c7 - # - PySCF-forge commit eb0ad96f632994d2d1846009ecce047193682526 - E_MCSCF_AVG_EXPECTED = -7.78902182 - E_MC23_EXPECTED = [-7.94539408, -7.80094952] - HCOUP_EXPECTED = 0.01285147 - HDIAG_EXPECTED = [-7.94424147, -7.80210214] - - self.assertAlmostEqual(e_mcscf_mc23_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 7) - self.assertAlmostEqual(lib.fp(hdiag), lib.fp(HDIAG_EXPECTED), 7) - self.assertAlmostEqual(lib.fp(lih_mc23.e_states), lib.fp(E_MC23_EXPECTED), 7) - - def test_water_spatial_samix(self): - e_mcscf_avg = np.dot(water.e_mcscf, water.weights) - hdiag = water.get_lpdft_diag() - e_states = water.e_states - - # References values from - # - PySCF commit 8ae2bb2eefcd342c52639097517b1eda7ca5d1cd - # - PySCF-forge commit 2c75a59604c458069ebda550e84a866ec1be45dc - E_MCSCF_AVG_EXPECTED = -75.81489195169507 - HDIAG_EXPECTED = [-76.29913074162732, -75.93502437481517] - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertListAlmostEqual(hdiag, HDIAG_EXPECTED, 7) - # The off-diagonal should be identical to zero because of symmetry - self.assertListAlmostEqual(e_states, hdiag, 10) - - def test_water_spin_samix(self): - e_mcscf_avg = np.dot(t_water.e_mcscf, t_water.weights) - hdiag = t_water.get_lpdft_diag() - e_states = t_water.e_states - hcoup = abs(t_water.get_lpdft_ham()[1,2]) - - # References values from - # - PySCF commit 8ae2bb2eefcd342c52639097517b1eda7ca5d1cd - # - PySCF-forge commit 2c75a59604c458069ebda550e84a866ec1be45dc - E_MCSCF_AVG_EXPECTED = -75.75552048294597 - HDIAG_EXPECTED = [-76.01218048502902, -76.31379141689696, -75.92134410312458] - E_STATES_EXPECTED = [-76.01218048502898, -76.3168078608912, -75.91832765913041] - HCOUP_EXPECTED = 0.03453830159471619 - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 7) - self.assertListAlmostEqual(e_states, E_STATES_EXPECTED, 6) - self.assertListAlmostEqual(hdiag, HDIAG_EXPECTED, 6) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 6) - - def test_chkfile(self): - for mc, case in zip([water, t_water], ["SA", "SA Mix"]): - with self.subTest(case=case): - self.assertTrue(h5py.is_hdf5(mc.chkfile)) - self.assertEqual(lib.fp(mc.mo_coeff), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/mo_coeff"))) - self.assertEqual(mc.e_tot, lib.chkfile.load(mc.chkfile, "pdft/e_tot")) - self.assertEqual(lib.fp(mc.e_mcscf), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/e_mcscf"))) - self.assertEqual(lib.fp(mc.e_states), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/e_states"))) - - # Requires PySCF version > 2.6.2 which is not available on pip currently - # for state, (c_ref, c) in enumerate(zip(mc.ci, lib.chkfile.load(mc.chkfile, "pdft/ci"))): - # with self.subTest(state=state): - # self.assertEqual(lib.fp(c_ref), lib.fp(c)) - -if __name__ == "__main__": - print("Full Tests for Linearized-PDFT") - unittest.main() diff --git a/pyscf/mcpdft/test/test_mcpdft.py b/pyscf/mcpdft/test/test_mcpdft.py deleted file mode 100644 index 325a108dd..000000000 --- a/pyscf/mcpdft/test/test_mcpdft.py +++ /dev/null @@ -1,771 +0,0 @@ -#!/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. -# -# Test API: -# 0. Initialize from mol, mf, and mc (done) -# 1. kernel (done) -# 2. optimize_mcscf_ (done) -# 3. compute_pdft_ (done) -# 4. energy_tot (done) -# 5. get_energy_decomposition (done) -# 6. checkpoint stuff -# 7. get_pdft_veff (maybe this elsewhere?) -# In the context of: -# 1. CASSCF, CASCI -# 2. Symmetry, with and without -# 3. State average, state average mix w/ different spin states - -# Some assertAlmostTrue thresholds are loose because we are only -# trying to test the API here; we need tight convergence and grids -# to reproduce well when OMP is on. -import tempfile, h5py -import numpy as np -from pyscf import gto, scf, mcscf, lib, fci, dft -from pyscf import mcpdft -import unittest - - -mol_nosym = mol_sym = mf_nosym = mf_sym = mc_nosym = mc_sym = mcp = mc_chk = None - - -def auto_setup(xyz="Li 0 0 0\nH 1.5 0 0", fnal="tPBE"): - mol_nosym = gto.M(atom=xyz, basis="sto3g", verbose=0, output="/dev/null") - mol_sym = gto.M( - atom=xyz, basis="sto3g", symmetry=True, verbose=0, output="/dev/null" - ) - mf_nosym = scf.RHF(mol_nosym).run() - mc_nosym = mcscf.CASSCF(mf_nosym, 5, 2).run(conv_tol=1e-8) - mf_sym = scf.RHF(mol_sym).run() - mc_sym = mcscf.CASSCF(mf_sym, 5, 2).run(conv_tol=1e-8) - mcp_ss_nosym = mcpdft.CASSCF(mc_nosym, fnal, 5, 2).run(conv_tol=1e-8) - mcp_ss_sym = ( - mcpdft.CASSCF(mc_sym, fnal, 5, 2) - .set(chkfile=tempfile.NamedTemporaryFile().name)#, chk_ci=True) - .run(conv_tol=1e-8) - ) - mcp_sa_0 = mcp_ss_nosym.state_average( - [ - 1.0 / 5, - ] - * 5 - ).run(conv_tol=1e-8) - solver_S = fci.solver(mol_nosym, singlet=True).set(spin=0, nroots=2) - solver_T = fci.solver(mol_nosym, singlet=False).set(spin=2, nroots=3) - mcp_sa_1 = ( - mcp_ss_nosym.state_average_mix( - [solver_S, solver_T], - [ - 1.0 / 5, - ] - * 5, - ) - .set(ci=None) - .run(conv_tol=1e-8) - ) - solver_A1 = fci.solver(mol_sym).set(wfnsym="A1", nroots=3) - solver_E1x = fci.solver(mol_sym).set(wfnsym="E1x", nroots=1, spin=2) - solver_E1y = fci.solver(mol_sym).set(wfnsym="E1y", nroots=1, spin=2) - mcp_sa_2 = ( - mcp_ss_sym.state_average_mix( - [solver_A1, solver_E1x, solver_E1y], - [ - 1.0 / 5, - ] - * 5, - ) - .set(ci=None, chkfile=tempfile.NamedTemporaryFile().name)#, chk_ci=True) - .run(conv_tol=1e-8) - ) - mcp = [[mcp_ss_nosym, mcp_ss_sym], [mcp_sa_0, mcp_sa_1, mcp_sa_2]] - nosym = [mol_nosym, mf_nosym, mc_nosym] - sym = [mol_sym, mf_sym, mc_sym] - mc_chk = [mcp_ss_sym, mcp_sa_2] - return nosym, sym, mcp, mc_chk - - -def setUpModule(): - global mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, mc_chk, original_grids - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - nosym, sym, mcp, mc_chk = auto_setup() - mol_nosym, mf_nosym, mc_nosym = nosym - mol_sym, mf_sym, mc_sym = sym - - -def tearDownModule(): - global mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, mc_chk, original_grids - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - mol_nosym.stdout.close() - mol_sym.stdout.close() - del mol_nosym, mf_nosym, mc_nosym, mol_sym, mf_sym, mc_sym, mcp, mc_chk, original_grids - - -class KnownValues(unittest.TestCase): - - def test_init(self): - ref_e = -7.924089707 - for symm in False, True: - mol = (mol_nosym, mol_sym)[int(symm)] - mf = (mf_nosym, mf_sym)[int(symm)] - mc0 = (mc_nosym, mc_sym)[int(symm)] - for i, cls in enumerate((mcpdft.CASCI, mcpdft.CASSCF)): - scf = bool(i) - for my_init in (mol, mf, mc0): - init_name = my_init.__class__.__name__ - if init_name == "Mole" and symm: - continue - # ^ The underlying PySCF modules can't do this as of 02/06/2022 - my_kwargs = {} - if isinstance(my_init, gto.Mole) or (not scf): - my_kwargs["mo_coeff"] = mc0.mo_coeff - with self.subTest(symm=symm, scf=scf, init=init_name): - mc = cls(my_init, "tPBE", 5, 2).run(**my_kwargs) - self.assertAlmostEqual(mc.e_tot, ref_e, delta=1e-6) - self.assertTrue(mc.converged) - - def test_df(self): - ref_e = -7.924259 - ref_e0_sa = -7.923959 - for mf, symm in zip((mf_nosym, mf_sym), (False, True)): - mf_df = mf.density_fit() - mo = (mc_nosym, mc_sym)[int(symm)].mo_coeff - for i, cls in enumerate((mcpdft.CASCI, mcpdft.CASSCF)): - scf = bool(i) - mc = cls(mf_df, "tPBE", 5, 2).run(mo_coeff=mo) - with self.subTest(symm=symm, scf=scf, nroots=1): - self.assertAlmostEqual(mc.e_tot, ref_e, delta=1e-6) - self.assertTrue(mc.converged) - nroots = 3 - if scf: - mc = mc.state_average( - [ - 1.0 / nroots, - ] - * nroots - ).run() - e_states = mc.e_states - ref = ref_e0_sa - else: - mc.fcisolver.nroots = nroots - mc.kernel() - e_states = mc.e_tot - ref = ref_e - with self.subTest(symm=symm, scf=scf, nroots=nroots): - self.assertAlmostEqual(e_states[0], ref, delta=1e-6) - self.assertTrue(mc.converged) - - def test_state_average(self): - # grids_level = 6 - # ref = np.array ([-7.9238958646710085,-7.7887395616498125,-7.7761692676370355, - # -7.754856419853813,-7.754856419853812,]) - # grids_level = 5 - # ref = np.array ([-7.923895345983219,-7.788739501036741,-7.776168040902887, - # -7.75485647715595,-7.7548564771559505]) - # grids_level = 4 - # ref = np.array ([-7.923894841822498,-7.788739444709943,-7.776169108993544, - # -7.754856321482755,-7.754856321482756]) - # grids_level = 3 - ref = np.array( - [ - -7.923894179700609, - -7.7887396628199, - -7.776172495309403, - -7.754856085624646, - -7.754856085624647, - ] - ) - # TODO: figure out why SA always needs more precision than SS to get - # the same repeatability. Fix if possible? In the mean time, loose - # deltas below for the sake of speed. - for ix, mc in enumerate(mcp[1]): - with self.subTest(symmetry=bool(ix // 2), triplet_ms=(0, 1, "mixed")[ix]): - self.assertTrue(mc.converged) - self.assertAlmostEqual( - lib.fp(np.sort(mc.e_states)), lib.fp(ref), delta=1e-5 - ) - self.assertAlmostEqual(mc.e_tot, np.average(ref), delta=1e-5) - - def test_casci_multistate(self): - # grids_level = 3 - ref = np.array( - [ - -7.923894179700609, - -7.7887396628199, - -7.776172495309403, - -7.754856085624646, - -7.754856085624647, - ] - ) - mc = mcpdft.CASCI(mcp[1][0], "tPBE", 5, 2) - mc.fcisolver.nroots = 5 - mc.kernel() - with self.subTest(symmetry=False): - self.assertTrue(mc.converged) - self.assertAlmostEqual(lib.fp(np.sort(mc.e_tot)), lib.fp(ref), delta=1e-5) - self.assertAlmostEqual(np.average(mc.e_tot), np.average(ref), delta=1e-5) - e_tot = [] - mc = mcpdft.CASCI(mcp[1][2], "tPBE", 5, 2) - ci_ref = mcp[1][2].ci - mc.ci, mc.fcisolver.nroots, mc.fcisolver.wfnsym = ci_ref[:3], 3, "A1" - mc.kernel() - self.assertTrue(mc.converged) - e_tot.extend(mc.e_tot) - mc.ci, mc.fcisolver.nroots, mc.fcisolver.wfnsym = ci_ref[3], 1, "E1x" - mc.fcisolver.spin = 2 - mc.kernel() - self.assertTrue(mc.converged) - e_tot.append(mc.e_tot) - mc.ci, mc.fcisolver.nroots, mc.fcisolver.wfnsym = ci_ref[4], 1, "E1y" - mc.kernel() - self.assertTrue(mc.converged) - e_tot.append(mc.e_tot) - with self.subTest(symmetry=True): - self.assertTrue(mc.converged) - self.assertAlmostEqual(lib.fp(np.sort(e_tot)), lib.fp(ref), delta=1e-5) - self.assertAlmostEqual(np.average(e_tot), np.average(ref), delta=1e-5) - - def test_decomposition_ss(self): # TODO - ref = [ - 1.0583544218, - -12.5375911135, - 5.8093938665, - -2.1716353580, - -0.0826115115, - -2.2123063329, - ] - terms = ["nuc", "core", "Coulomb", "OT(X)", "OT(C)", "WFN(XC)"] - for ix, mc in enumerate(mcp[0]): - casci = mcpdft.CASCI(mc, "tPBE", 5, 2).run() - for obj, objtype in zip((mc, casci), ("CASSCF", "CASCI")): - test = obj.get_energy_decomposition() - for t, r, term in zip(test, ref, terms): - with self.subTest(objtype=objtype, symmetry=bool(ix), term=term): - self.assertAlmostEqual(t, r, delta=1e-5) - with self.subTest(objtype=objtype, symmetry=bool(ix), term="sanity"): - self.assertAlmostEqual(np.sum(test[:-1]), obj.e_tot, 9) - - def test_decomposition_hybrid(self): - ref = [ - 1.0583544218, - -12.5375911135, - 5.8093938665, - -1.6287258807, - -0.0619586538, - -0.5530763650, - ] - terms = ["nuc", "core", "Coulomb", "OT(X)", "OT(C)", "WFN(XC)"] - for ix, mc in enumerate(mcp[0]): - mc_scf = mcpdft.CASSCF(mc, "tPBE0", 5, 2).run() - mc_ci = mcpdft.CASCI(mc, "tPBE0", 5, 2).run() - for obj, objtype in zip((mc_scf, mc_ci), ("CASSCF", "CASCI")): - test = obj.get_energy_decomposition() - for t, r, term in zip(test, ref, terms): - with self.subTest(objtype=objtype, symmetry=bool(ix), term=term): - self.assertAlmostEqual(t, r, delta=1e-5) - with self.subTest(objtype=objtype, symmetry=bool(ix), term="sanity"): - self.assertAlmostEqual(np.sum(test), obj.e_tot, 9) - - - def test_decomposition_sa(self): - ref_nuc = 1.0583544218 - ref_states = np.array( - [ - [ - -12.5385413915, - 5.8109724796, - -2.1720331222, - -0.0826465641, - -2.2127964255, - ], - [ - -12.1706553996, - 5.5463231972, - -2.1601539256, - -0.0626079593, - -2.1943087132, - ], - [ - -12.1768195314, - 5.5632261670, - -2.1552571900, - -0.0656763663, - -2.1887042769, - ], - [ - -12.1874226655, - 5.5856701424, - -2.1481995107, - -0.0632609608, - -2.1690856659, - ], - [ - -12.1874226655, - 5.5856701424, - -2.1481995107, - -0.0632609608, - -2.1690856659, - ], - ] - ) - terms = ["core", "Coulomb", "OT(X)", "OT(C)", "WFN(XC)"] - for ix, (mc, ms) in enumerate(zip(mcp[1], [0, 1, "mixed"])): - s = bool(ix // 2) - objs = [ - mc, - ] - objtypes = [ - "CASSCF", - ] - if ix != 1: # There is no CASCI equivalent to mcp[1][1] - casci = mcpdft.CASCI(mc, "tPBE", 5, 2) - casci.fcisolver.nroots = ( - 5 - ix - ) # Just check the A1 roots when symmetry is enabled - casci.ci = mc.ci[: 5 - ix] - casci.kernel() - objs.append(casci) - objtypes.append("CASCI") - for obj, objtype in zip(objs, objtypes): - test = obj.get_energy_decomposition() - test_nuc, test_states = test[0], np.array(test[1:]).T - # Arrange states in ascending energy order - e_states = getattr(obj, "e_states", obj.e_tot) - idx = np.argsort(e_states) - test_states = test_states[idx, :] - e_ref = np.array(e_states)[idx] - with self.subTest( - objtype=objtype, symmetry=s, triplet_ms=ms, term="nuc" - ): - self.assertAlmostEqual(test_nuc, ref_nuc, 9) - for state, (test, ref) in enumerate(zip(test_states, ref_states)): - for t, r, term in zip(test, ref, terms): - with self.subTest( - objtype=objtype, - symmetry=s, - triplet_ms=ms, - term=term, - state=state, - ): - self.assertAlmostEqual(t, r, delta=1e-5) - with self.subTest( - objtype=objtype, - symmetry=s, - triplet_ms=ms, - term="sanity", - state=state, - ): - self.assertAlmostEqual( - np.sum(test[:-1]) + test_nuc, e_ref[state], 9 - ) - - def test_decomposition_hybrid_sa(self): - ref_nuc = 1.0583544218 - ref_states = np.array( - [ - [ - -12.5385413915, - 5.8109724796, - -1.6290249990, - -0.0619850920, - -0.5531991067, - ], - [ - -12.1706553996, - 5.5463231972, - -1.6201152933, - -0.0469559736, - -0.5485771470, - ], - [ - -12.1768195314, - 5.5632261670, - -1.6164436229, - -0.0492571730, - -0.5471763843, - ], - [ - -12.1874226655, - 5.5856701424, - -1.6111471613, - -0.0474456546, - -0.5422714244, - ], - [ - -12.1874226655, - 5.5856701424, - -1.6111480360, - -0.0474456745, - -0.5422714244, - ], - ] - ) - terms = ["core", "Coulomb", "OT(X)", "OT(C)", "WFN(XC)"] - for ix, (mc, ms) in enumerate(zip(mcp[1], [0, 1, "mixed"])): - s = bool(ix // 2) - mc_scf = mcpdft.CASSCF(mc, "tPBE0", 5, 2) - if ix == 0: - mc_scf = mc_scf.state_average(mc.weights) - else: - mc_scf = mc_scf.state_average_mix(mc.fcisolver.fcisolvers, mc.weights) - mc_scf.run(ci=mc.ci, mo_coeff=mc.mo_coeff) - objs = [ - mc_scf, - ] - objtypes = [ - "CASSCF", - ] - if ix != 1: # There is no CASCI equivalent to mcp[1][1] - mc_ci = mcpdft.CASCI(mc, "tPBE0", 5, 2) - mc_ci.fcisolver.nroots = ( - 5 - ix - ) # Just check the A1 roots when symmetry is enabled - mc_ci.ci = mc.ci[: 5 - ix] - mc_ci.kernel() - objs.append(mc_ci) - objtypes.append("CASCI") - for obj, objtype in zip(objs, objtypes): - test = obj.get_energy_decomposition() - test_nuc, test_states = test[0], np.array(test[1:]).T - # Arrange states in ascending energy order - e_states = getattr(obj, "e_states", obj.e_tot) - idx = np.argsort(e_states) - test_states = test_states[idx, :] - e_ref = np.array(e_states)[idx] - with self.subTest( - objtype=objtype, symmetry=s, triplet_ms=ms, term="nuc" - ): - self.assertAlmostEqual(test_nuc, ref_nuc, 9) - for state, (test, ref) in enumerate(zip(test_states, ref_states)): - for t, r, term in zip(test, ref, terms): - with self.subTest( - objtype=objtype, - symmetry=s, - triplet_ms=ms, - term=term, - state=state, - ): - self.assertAlmostEqual(t, r, delta=1e-5) - with self.subTest( - objtype=objtype, - symmetry=s, - triplet_ms=ms, - term="sanity", - state=state, - ): - self.assertAlmostEqual( - np.sum(test) + test_nuc, e_ref[state], 9 - ) - - def test_energy_tot(self): - # Test both correctness and energy_tot function purity - def get_attr(mc): - mo_ref = lib.fp(mc.mo_coeff) - ci_ref = lib.fp(np.concatenate(mc.ci, axis=None)) - e_states_ref = lib.fp(getattr(mc, "e_states", 0)) - return mo_ref, ci_ref, mc.e_tot, e_states_ref, mc.grids.level, mc.otxc - - def test_energy_tot_crunch(test_list, ref_list, casestr): - for ix, (t, r) in enumerate(zip(test_list, ref_list)): - with self.subTest(case=casestr, item=ix): - if isinstance(t, (float, np.floating)): - self.assertAlmostEqual(t, r, delta=1e-5) - else: - self.assertEqual(t, r) - - def test_energy_tot_loop_ss(e_ref_ss, diff, **kwargs): - for ix, mc in enumerate(mcp[0]): - ref_list = [e_ref_ss] + list(get_attr(mc)) - e_test = mc.energy_tot(**kwargs)[0] - test_list = [e_test] + list(get_attr(mc)) - casestr = "diff={}; SS; symmetry={}".format(diff, bool(ix)) - test_energy_tot_crunch(test_list, ref_list, casestr) - - def test_energy_tot_loop_sa(e_ref_sa, diff, **kwargs): - for ix, mc in enumerate(mcp[1]): - ref_list = [e_ref_sa] + list(get_attr(mc)) - e_s0_test = mc.energy_tot(**kwargs)[0] - test_list = [e_s0_test] + list(get_attr(mc)) - sym = bool(ix // 2) - tms = (0, 1, "mixed")[ix] - casestr = "diff={}; SA; symmetry={}; triplet_ms={}".format( - diff, sym, tms - ) - test_energy_tot_crunch(test_list, ref_list, casestr) - - def test_energy_tot_loop(e_ref_ss, e_ref_sa, diff, **kwargs): - test_energy_tot_loop_ss(e_ref_ss, diff, **kwargs) - test_energy_tot_loop_sa(e_ref_sa, diff, **kwargs) - - # tBLYP - e_ref_ss = mcpdft.CASSCF(mcp[0][0], "tBLYP", 5, 2).kernel()[0] - mc_ref = ( - mcpdft.CASSCF(mcp[1][0], "tBLYP", 5, 2) - .state_average( - [ - 1.0 / 5, - ] - * 5 - ) - .run() - ) - e_ref_sa = mc_ref.e_states[0] - test_energy_tot_loop(e_ref_ss, e_ref_sa, "fnal", otxc="tBLYP") - # grids_level = 2 - e_ref_ss = mcpdft.CASSCF(mcp[0][0], "tPBE", 5, 2, grids_level=2).kernel()[0] - mc_ref = ( - mcpdft.CASSCF(mcp[1][0], "tPBE", 5, 2, grids_level=2) - .state_average( - [ - 1.0 / 5, - ] - * 5 - ) - .run() - ) - e_ref_sa = mc_ref.e_states[0] - test_energy_tot_loop(e_ref_ss, e_ref_sa, "grids", grids_level=2) - # CASCI wfn - mc_ref = mcpdft.CASCI(mf_nosym, "tPBE", 5, 2).run() - test_energy_tot_loop_ss( - mc_ref.e_tot, "wfn", mo_coeff=mc_ref.mo_coeff, ci=mc_ref.ci - ) - fake_ci = [c.copy() for c in mcp[1][0].ci] - fake_ci[0] = mc_ref.ci.copy() - test_energy_tot_loop_sa( - mc_ref.e_tot, "wfn", mo_coeff=mc_ref.mo_coeff, ci=fake_ci - ) - - def test_kernel_steps_casscf(self): - ref_tot = -7.919939037859329 - ref_ot = -2.2384273324895165 - mo_ref = 0.9925428665189101 - ci_ref = 0.9886507094634355 - for ix, mc in enumerate(mcp[0]): - e_tot = mc.e_tot - e_ot = mc.e_ot - e_mcscf = mc.e_mcscf - mo = (mf_nosym, mf_sym)[ix].mo_coeff.copy() - ci = np.zeros_like(mc.ci) - ci[0, 0] = 1.0 - mc.compute_pdft_energy_(mo_coeff=mo, ci=ci) - with self.subTest(case="SS", part="pdft1", symmetry=bool(ix)): - self.assertEqual(lib.fp(mc.mo_coeff), lib.fp(mo)) - self.assertEqual(lib.fp(mc.ci), lib.fp(ci)) - self.assertEqual(mc.e_mcscf, e_mcscf) - self.assertAlmostEqual(mc.e_tot, ref_tot, 9) - self.assertAlmostEqual(mc.e_ot, ref_ot, 7) - mc.e_tot = 0.0 - mc.e_ot = 0.0 - mc.optimize_mcscf_() - with self.subTest(case="SS", part="mcscf", symmetry=bool(ix)): - self.assertEqual(mc.e_tot, 0.0) - self.assertEqual(mc.e_ot, 0.0) - self.assertAlmostEqual(abs(mc.mo_coeff[0, 0]), mo_ref, delta=1e-5) - self.assertAlmostEqual(abs(mc.ci[0, 0]), ci_ref, delta=1e-5) - self.assertAlmostEqual(mc.e_mcscf, e_mcscf, 9) - mc.compute_pdft_energy_() - with self.subTest(case="SS", part="pdft2", symmetry=bool(ix)): - self.assertAlmostEqual(mc.e_tot, e_tot, delta=1e-6) - self.assertAlmostEqual(mc.e_ot, e_ot, delta=1e-6) - mo_ref = 0.9908324004974881 - ci_ref = 0.988599145861302 - ref_avg = -7.7986453 - for ix, mc in enumerate(mcp[1]): - sym = bool(ix // 2) - tms = (0, 1, "mixed")[ix] - e_tot = mc.e_tot - e_ot = np.array(mc.e_ot) - e_mcscf = np.array(mc.e_mcscf) - e_states = np.array(mc.e_states) - nroots = len(e_mcscf) - mo = (mf_nosym, mf_sym)[int(sym)].mo_coeff.copy() - ci = [c.copy() for c in mc.ci] - ci[0][:, :] = 0.0 - ci[0][0, 0] = 1.0 - fp_fake_ci = lib.fp(np.concatenate(ci, axis=None)) - mc.compute_pdft_energy_(mo_coeff=mo, ci=ci) - with self.subTest(case="SA", part="pdft1", symmetry=sym, triplet_ms=tms): - self.assertEqual(lib.fp(mc.mo_coeff), lib.fp(mo)) - self.assertEqual(lib.fp(np.concatenate(mc.ci, axis=None)), fp_fake_ci) - self.assertEqual(lib.fp(mc.e_mcscf), lib.fp(e_mcscf)) - self.assertAlmostEqual(mc.e_tot, ref_avg, delta=1e-5) - self.assertAlmostEqual(mc.e_states[0], ref_tot, 9) - self.assertAlmostEqual(mc.e_ot[0], ref_ot, 7) - mc.e_tot = 0.0 - mc.e_ot = [ - 0.0, - ] * len(mc.e_ot) - mc.fcisolver.e_states = [ - 0.0, - ] * len(mc.e_states) - mc.optimize_mcscf_() - with self.subTest(case="SA", part="mcscf", symmetry=sym, triplet_ms=tms): - self.assertEqual(mc.e_tot, 0.0) - self.assertEqual(lib.fp(mc.e_ot), 0.0) - self.assertEqual(lib.fp(mc.e_states), 0.0) - self.assertAlmostEqual(abs(mc.mo_coeff[0, 0]), mo_ref, delta=1e-5) - self.assertAlmostEqual(abs(mc.ci[0][0, 0]), ci_ref, delta=1e-5) - self.assertAlmostEqual(lib.fp(mc.e_mcscf), lib.fp(e_mcscf), 7) - mc.compute_pdft_energy_() - with self.subTest(case="SA", part="pdft2", symmetry=sym, triplet_ms=tms): - self.assertAlmostEqual(mc.e_tot, e_tot, delta=1e-5) - self.assertAlmostEqual(lib.fp(mc.e_ot), lib.fp(e_ot), delta=1e-5) - self.assertAlmostEqual( - lib.fp(mc.e_states), lib.fp(e_states), delta=1e-5 - ) - - def test_kernel_steps_casci(self): - ref_tot = -7.919924747436255 - ref_ot = -2.238538447576774 - ci_ref = 0.9886507094634355 - for nroots in range(1, 3): - for init, symmetry in zip(mcp[0], (False, True)): - mc = mcpdft.CASCI(init, "tPBE", 5, 2) - mc.fcisolver.nroots = nroots - if nroots > 1: - mc.ci = None - mc.kernel() - e_tot = np.atleast_1d(mc.e_tot)[0] - e_ot = np.atleast_1d(mc.e_ot)[0] - e_mcscf = np.atleast_1d(mc.e_mcscf)[0] - ci = np.zeros_like(mc.ci) - ci.flat[0] = 1.0 - if nroots > 1: - ci = list(ci) - # pyscf PR #1623 made direct_spin1_symm unable to interpret - # a 3D fci vector array - mc.compute_pdft_energy_(ci=ci) - with self.subTest(part="pdft1", symmetry=symmetry, nroots=nroots): - self.assertEqual(lib.fp(np.array(mc.ci)), lib.fp(ci), 9) - self.assertEqual(np.atleast_1d(mc.e_mcscf)[0], e_mcscf) - self.assertAlmostEqual(np.atleast_1d(mc.e_tot)[0], ref_tot, 7) - self.assertAlmostEqual(np.atleast_1d(mc.e_ot)[0], ref_ot, 7) - mc.e_tot = 0.0 - mc.e_ot = 0.0 - mc.optimize_mcscf_() - with self.subTest(part="mcscf", symmetry=symmetry, nroots=nroots): - self.assertEqual(np.atleast_1d(mc.e_tot)[0], 0.0) - self.assertEqual(np.atleast_1d(mc.e_ot)[0], 0.0) - self.assertAlmostEqual( - abs(np.asarray(mc.ci).flat[0]), ci_ref, delta=1e-5 - ) - self.assertAlmostEqual(np.atleast_1d(mc.e_mcscf)[0], e_mcscf, 9) - mc.compute_pdft_energy_() - with self.subTest(part="pdft2", symmetry=symmetry, nroots=nroots): - self.assertAlmostEqual( - np.atleast_1d(mc.e_tot)[0], e_tot, delta=1e-5 - ) - self.assertAlmostEqual(np.atleast_1d(mc.e_ot)[0], e_ot, delta=1e-5) - - def test_scanner(self): - # Putting more energy into CASSCF than CASCI scanner because this is - # necessary for geometry optimization, which isn't available for CASCI - mcp1 = auto_setup(xyz="Li 0 0 0\nH 1.55 0 0")[-2] - for mol0, mc0, mc1 in zip([mol_nosym, mol_sym], mcp[0], mcp1[0]): - mc_scan = mc1.as_scanner() - with self.subTest(case="SS CASSCF", symm=mol0.symmetry): - self.assertAlmostEqual(mc_scan(mol0), mc0.e_tot, delta=1e-6) - mc2 = mcpdft.CASCI(mc1, "tPBE", 5, 2).run(mo_coeff=mc1.mo_coeff) - mc_scan = mc2.as_scanner() - mc_scan._scf(mol0) # TODO: fix this in CASCI as_scanner - # when you pass mo_coeff on call, it skips updating the _scf - # object geometry. This breaks things like CASCI.energy_nuc (), - # CASCI.get_hcore (), etc. which refer to the corresponding - # _scf fns but don't default to CASCI self.mol - e_tot = mc_scan(mol0, mo_coeff=mc0.mo_coeff, ci0=mc0.ci) - with self.subTest(case="SS CASCI", symm=mol0.symmetry): - self.assertAlmostEqual(e_tot, mc0.e_tot, delta=1e-6) - for ix, (mc0, mc1) in enumerate(zip(mcp[1], mcp1[1])): - tms = (0, 1, "mixed")[ix] - sym = bool(ix // 2) - mol0 = [mol_nosym, mol_sym][int(sym)] - with self.subTest(case="SA CASSCF", symm=mol0.symmetry, triplet_ms=tms): - mc_scan = mc1.as_scanner() - e_tot = mc_scan(mol0) - e_states_fp = lib.fp(np.sort(mc_scan.e_states)) - e_states_fp_ref = lib.fp(np.sort(mc0.e_states)) - self.assertAlmostEqual(e_tot, mc0.e_tot, delta=2e-6) - self.assertAlmostEqual(e_states_fp, e_states_fp_ref, delta=2e-5) - mc2 = mcpdft.CASCI(mcp1[1][0], "tPBE", 5, 2) - mc2.fcisolver.nroots = 5 - mc2.run(mo_coeff=mcp[1][0].mo_coeff) - mc_scan = mc2.as_scanner() - mc_scan._scf(mol_nosym) # TODO: fix this in CASCI as_scanner - # when you pass mo_coeff on call, it skips updating the _scf - # object geometry. This breaks things like CASCI.energy_nuc (), - # CASCI.get_hcore (), etc. which refer to the corresponding - # _scf fns but don't default to CASCI self.mol - e_tot = mc_scan(mol_nosym, mo_coeff=mcp[1][0].mo_coeff, ci0=mcp[1][0].ci) - e_states_fp = lib.fp(np.sort(e_tot)) - e_states_fp_ref = lib.fp(np.sort(mcp[1][0].e_states)) - with self.subTest(case="nroots=5 CASCI"): - self.assertAlmostEqual(e_states_fp, e_states_fp_ref, delta=5e-5) - - def test_tpbe0(self): - # The most common hybrid functional - for mc in mcp[0]: - e_ref = 0.75 * mc.e_tot + 0.25 * mc.e_mcscf - e_test = mc.energy_tot(otxc="tPBE0")[0] - with self.subTest(case="SS", symm=mc.mol.symmetry): - self.assertAlmostEqual(e_test, e_ref, 10) - for ix, mc in enumerate(mcp[1]): - tms = (0, 1, "mixed")[ix] - sym = bool(ix // 2) - e_states = np.array(mc.e_states) - e_mcscf = np.array(mc.e_mcscf) - e_ref = 0.75 * e_states + 0.25 * e_mcscf - e_test = [ - mc.energy_tot(otxc="tPBE0", state=i)[0] for i in range(len(mc.e_states)) - ] - with self.subTest(case="SA CASSCF", symm=sym, triplet_ms=tms): - e_ref_fp = lib.fp(np.sort(e_ref)) - e_test_fp = lib.fp(np.sort(e_test)) - self.assertAlmostEqual(e_test_fp, e_ref_fp, 10) - - def test_chkfile(self): - for mc in mc_chk: - if hasattr(mc, "e_states"): - case = "SA CASSCF" - else: - case = "SS" - - with self.subTest(case=case): - self.assertTrue(h5py.is_hdf5(mc.chkfile)) - self.assertEqual(lib.fp(mc.mo_coeff), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/mo_coeff"))) - self.assertEqual(mc.e_tot, lib.chkfile.load(mc.chkfile, "pdft/e_tot")) - self.assertEqual(lib.fp(mc.e_ot), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/e_ot"))) - self.assertEqual(lib.fp(mc.e_mcscf), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/e_mcscf"))) - - # Requires PySCF version > 2.6.2 which is not currently available on pip - # for state, (c_ref, c) in enumerate(zip(mc.ci, lib.chkfile.load(mc.chkfile, "pdft/ci"))): - # with self.subTest(state=state): - # self.assertEqual(lib.fp(c_ref), lib.fp(c)) - - if case=="SA CASSCF": - self.assertEqual(lib.fp(mc.e_states), lib.fp(lib.chkfile.load(mc.chkfile, "pdft/e_states"))) - - def test_h2_triplet(self): - mol = gto.M (atom='H 0 0 0; H 1 0 0', basis='sto-3g', spin=2) - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'tPBE', 2, 2).run () - # Reference from OpenMolcas v24.10 - e_ref = -0.74702903 - self.assertAlmostEqual (mc.e_tot, e_ref, 6) - - - -if __name__ == "__main__": - print("Full Tests for MC-PDFT energy API") - unittest.main() diff --git a/pyscf/mcpdft/test/test_mgga.py b/pyscf/mcpdft/test/test_mgga.py deleted file mode 100644 index 974077fe9..000000000 --- a/pyscf/mcpdft/test/test_mgga.py +++ /dev/null @@ -1,269 +0,0 @@ -#!/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. -# -# Author: Bhavnesh Jangid - -import numpy as np -from pyscf import gto, scf, dft, fci -from pyscf import mcpdft -import unittest - -''' -In this unit-test, test the MCPDFT energies calculated for the LiH -molecule at the state-specific and state-average (2-states) using -1. Meta-GGA functional (tM06L) -2. Hybrid-meta-GGA functional tM06L0 -3. MC23 Functional - -Test the MCPDFT energies calculated for the triplet water molecule at the -4. Meta-GGA functional (M06L) -5. MC23 Functional - -Note: The reference values are generated from -OpenMolcas v24.10, tag 682-gf74be507d - -The OpenMolcas results were obtained with this grid settings -&SEWARD -Grid Input -RQuad=TA -NR=100 -LMAX=41 -NOPrun -NOSCreening -''' - -# To be consistent with OpenMolcas Grid Settings. Grids_att is defined as below -# Source: pyscf/mcpdft/test/test_diatomic_energies.py - -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] - -my_grids = {'atom_grid': (99,590), - 'radi_method': om_treutler_ahlrichs, - 'prune': False, - 'radii_adjust': None} - -def get_lih (r, stateaverage=False, functional='tM06L', basis='sto3g'): - mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis=basis, - output='/dev/null', verbose=0) - mf = scf.RHF (mol).run () - if functional == 'tM06L0': - tM06L0 = 't' + mcpdft.hyb('M06L',0.25, hyb_type='average') - mc = mcpdft.CASSCF(mf, tM06L0, 5, 2, grids_attr=my_grids) - else: - mc = mcpdft.CASSCF(mf, functional, 5, 2, grids_attr=my_grids) - - if stateaverage: - mc = mc.state_average_([0.5, 0.5]) - - mc.fix_spin_(ss=0) - mc = mc.run() - return mc - -def get_water_triplet(functional='tM06L', basis='6-31G'): - mol = gto.M(atom=''' - O 0. 0.000 0.1174 - H 0. 0.757 -0.4696 - H 0. -0.757 -0.4696 - ''',basis=basis, spin=2,output='/dev/null', verbose=0) - - mf = scf.RHF(mol).run() - - mc = mcpdft.CASSCF(mf, functional, 2, 2, grids_attr=my_grids) - solver1 = fci.direct_spin1.FCI(mol) - solver1 = fci.addons.fix_spin(solver1, ss=2) - mc.fcisolver = solver1 - mc = mc.run() - return mc - -def setUpModule(): - global get_lih, lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2,lih_tm06l0 - global get_water_triplet, water_tm06l, water_tmc23 - global lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 - - # register otfnal tMC23_2 which is identical to MC23 - mc232_preset = mcpdft.otfnal.OT_PRESET['MC23'] - mcpdft.otfnal.register_otfnal('MC23_2', mc232_preset) - - lih_tm06l = get_lih(1.5, functional='tM06L') - lih_tmc23 = get_lih(1.5, functional='MC23') - lih_tmc23_2 = get_lih(1.5, functional='tMC23_2') - lih_tm06l_sa2 = get_lih(1.5, stateaverage=True, functional='tM06L') - lih_tmc23_sa2 = get_lih(1.5, stateaverage=True, functional='MC23') - lih_tmc23_sa2_2 = get_lih(1.5, stateaverage=True, functional='tmc23_2') - lih_tm06l0 = get_lih(1.5, functional='tM06L0') - water_tm06l = get_water_triplet() - water_tmc23 = get_water_triplet(functional='MC23') - water_tmc23_2 = get_water_triplet(functional='TMc23_2') - -def tearDownModule(): - global lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2 - global lih_tm06l0, water_tm06l, water_tmc23 - global lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 - - lih_tm06l.mol.stdout.close() - lih_tmc23.mol.stdout.close() - lih_tmc23_2.mol.stdout.close() - lih_tm06l_sa2.mol.stdout.close() - lih_tmc23_sa2.mol.stdout.close() - lih_tmc23_sa2_2.mol.stdout.close() - lih_tm06l0.mol.stdout.close() - water_tm06l.mol.stdout.close() - water_tmc23.mol.stdout.close() - water_tmc23_2.mol.stdout.close() - - mcpdft.otfnal.unregister_otfnal('tMC23_2') - - del lih_tm06l, lih_tmc23, lih_tm06l_sa2, lih_tmc23_sa2 - del lih_tm06l0, water_tm06l, water_tmc23 - del lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 - -class KnownValues(unittest.TestCase): - - def assertListAlmostEqual(self, first_list, second_list, expected): - self.assertTrue(len(first_list) == len(second_list)) - for first, second in zip(first_list, second_list): - self.assertAlmostEqual(first, second, expected) - - def test_tmgga(self): - e_mcscf = lih_tm06l.e_mcscf - epdft = lih_tm06l.e_tot - - sa_e_mcscf = lih_tm06l_sa2.e_mcscf - sa_epdft = lih_tm06l_sa2.e_states - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -7.88214917 - E_MCPDFT_EXPECTED = -7.95814186 - SA_E_CASSCF_EXPECTED = [-7.88205449, -7.74391704] - SA_E_MCPDFT_EXPECTED = [-7.95807682, -7.79920022] - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - self.assertListAlmostEqual(sa_e_mcscf, SA_E_CASSCF_EXPECTED, 6) - self.assertListAlmostEqual(sa_epdft, SA_E_MCPDFT_EXPECTED, 6) - - def test_t_hyb_mgga(self): - e_mcscf = lih_tm06l0.e_mcscf - epdft = lih_tm06l0.e_tot - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -7.88214917 - E_MCPDFT_EXPECTED = -7.93914369 - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - - def test_tmc23(self): - e_mcscf = lih_tmc23.e_mcscf - epdft = lih_tmc23.e_tot - - sa_e_mcscf = lih_tmc23_sa2.e_mcscf - sa_epdft = lih_tmc23_sa2.e_states - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -7.88214917 - E_MCPDFT_EXPECTED = -7.95098727 - SA_E_CASSCF_EXPECTED = [-7.88205449, -7.74391704] - SA_E_MCPDFT_EXPECTED = [-7.95093826, -7.80604012] - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - self.assertListAlmostEqual(sa_e_mcscf, SA_E_CASSCF_EXPECTED, 6) - self.assertListAlmostEqual(sa_epdft, SA_E_MCPDFT_EXPECTED, 6) - - def test_tmc23_2(self): - e_mcscf = lih_tmc23_2.e_mcscf - epdft = lih_tmc23_2.e_tot - - sa_e_mcscf = lih_tmc23_sa2_2.e_mcscf - sa_epdft = lih_tmc23_sa2_2.e_states - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -7.88214917 - E_MCPDFT_EXPECTED = -7.95098727 - SA_E_CASSCF_EXPECTED = [-7.88205449, -7.74391704] - SA_E_MCPDFT_EXPECTED = [-7.95093826, -7.80604012] - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - self.assertListAlmostEqual(sa_e_mcscf, SA_E_CASSCF_EXPECTED, 6) - self.assertListAlmostEqual(sa_epdft, SA_E_MCPDFT_EXPECTED, 6) - - def test_water_triplet_tm06l(self): - e_mcscf = water_tm06l.e_mcscf - epdft = water_tm06l.e_tot - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -75.72365496 - E_MCPDFT_EXPECTED = -76.07686505 - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - - def test_water_triplet_tmc23(self): - e_mcscf = water_tmc23.e_mcscf - epdft = water_tmc23.e_tot - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -75.72365496 - E_MCPDFT_EXPECTED = -76.02630019 - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - - def test_water_triplet_tmc23_2(self): - e_mcscf = water_tmc23_2.e_mcscf - epdft = water_tmc23_2.e_tot - - # The CAS and MCPDFT reference values are generated using - # OpenMolcas v24.10, tag 682-gf74be507d - E_CASSCF_EXPECTED = -75.72365496 - E_MCPDFT_EXPECTED = -76.02630019 - - self.assertAlmostEqual(e_mcscf, E_CASSCF_EXPECTED, 6) - self.assertAlmostEqual(epdft, E_MCPDFT_EXPECTED, 6) - -if __name__ == "__main__": - print("Full Tests for MGGAs, Hybrid-MGGAs, and MC23") - unittest.main() diff --git a/pyscf/mcpdft/test/test_mspdft.py b/pyscf/mcpdft/test/test_mspdft.py deleted file mode 100644 index e55a8d0c9..000000000 --- a/pyscf/mcpdft/test/test_mspdft.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, mcscf -from pyscf import mcpdft -import unittest, math - - -def get_lih (r): - mol = gto.M (atom='Li 0 0 0\nH {} 0 0'.format (r), basis='sto3g', - output='/dev/null', verbose=0) - mf = scf.RHF (mol).run () - mc = mcpdft.CASSCF (mf, 'ftLDA,VWN3', 2, 2, grids_level=1) - mc.fix_spin_(ss=0) - mc = mc.multi_state ([0.5,0.5], 'cms').run (conv_tol=1e-8) - return mol, mf, mc - -def setUpModule(): - global mol, mf, mc - mol, mf, mc = get_lih (1.5) - -def tearDownModule(): - global mol, mf, mc - mol.stdout.close () - del mol, mf, mc - -class KnownValues(unittest.TestCase): - - def test_reference_adiabats (self): - # Recover SA-CASSCF properly - mc_ref = mcscf.CASCI (mf, 2, 2) - mc_ref.fix_spin_(ss=0) - mc_ref.fcisolver.nroots = 2 - mc_ref.kernel (mo_coeff=mc.mo_coeff) - ci_test = mc.get_ci_adiabats (uci='MCSCF') - for state in 0,1: - with self.subTest (state=state): - e1 = mc.e_mcscf[state] - e2 = mc_ref.e_tot[state] - self.assertAlmostEqual (e1, e2, 8) - ovlp = np.dot (mc_ref.ci[state].ravel (), - ci_test[state].ravel ()) - self.assertAlmostEqual (abs(ovlp), 1.0, 8) - - def test_intermediate_diabats (self): - # Diabat optimization and energy expression - mc_ref = mcpdft.CASCI (mf, 'ftLDA,VWN3', 2, 2, grids_level=1) - mc_ref.fix_spin_(ss=0) - mc_ref.fcisolver.nroots = 2 - mc_ref.kernel (mo_coeff=mc.mo_coeff) - mc_ref.compute_pdft_energy_(ci=mc.ci) - for state in 0,1: - with self.subTest (state=state): - e1 = mc.hdiag_pdft[state] - e2 = mc_ref.e_tot[state] - self.assertAlmostEqual (e1, e2, 8) - Qaa_max = mc.diabatizer ()[0] - rand_gen = np.zeros ((2,2)) - rand_gen[1,0] = math.pi * ((2*np.random.rand())-1) - rand_gen -= rand_gen.T - rand_u = linalg.expm (rand_gen) - rand_ci = mc.get_ci_basis (uci=rand_u) - Qaa_rand = mc.diabatizer (ci=rand_ci)[0] - self.assertLessEqual (Qaa_rand, Qaa_max) - - def test_final_adiabats (self): - # Indirect energy calculation - dh = mc.get_heff_pdft () - mc.heff_mcscf - dh_offdiag = dh - np.diag (dh.diagonal ()) - self.assertAlmostEqual (np.amax (np.abs (dh_offdiag)), 0, 9) - dh = np.dot (dh, mc.si_pdft) - dh = np.dot (mc.si_pdft.conj ().T, dh) - mc_ref = mcscf.CASCI (mf, 2, 2) - mc_ref.mo_coeff = mc.mo_coeff - cisolver = mc_ref.fcisolver - h1, h0 = mc_ref.get_h1eff () - h2 = mc_ref.get_h2eff () - h2eff = cisolver.absorb_h1e (h1, h2, 2, (1,1), 0.5) - ci = mc.get_ci_adiabats (uci='MSPDFT') - hci = [cisolver.contract_2e (h2eff, c, 2, (1,1)) for c in ci] - chc = np.tensordot (ci, hci, axes=((1,2),(1,2))) + dh - e_ref = chc.diagonal () - chc_offdiag = chc - np.diag (e_ref) - self.assertAlmostEqual (np.amax (np.abs (chc_offdiag)), 0, 9) - for state in 0,1: - with self.subTest (state=state): - e1 = mc.e_states[state] - e2 = e_ref[state] + h0 - self.assertAlmostEqual (e1, e2, 8) - - def test_diabatize (self): - f_ref = mc.diabatizer ()[0] - theta_rand = 360 * np.random.rand () - 180 - ct = math.cos (theta_rand) - st = math.sin (theta_rand) - u_rand = np.array ([[ct,st],[-st,ct]]) - ci_rand = mc.get_ci_basis (uci=u_rand) - f_test = mc.diabatizer (ci=ci_rand)[0] - self.assertLessEqual (f_test, f_ref) - conv, ci_test = mc.diabatize (ci=ci_rand) - f_test = mc.diabatizer (ci=ci_test)[0] - self.assertTrue (conv) - self.assertAlmostEqual (f_test, f_ref, 9) - -if __name__ == "__main__": - print("Full Tests for MS-PDFT energy API") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/test/test_otfnal.py b/pyscf/mcpdft/test/test_otfnal.py deleted file mode 100644 index 29f8a40a4..000000000 --- a/pyscf/mcpdft/test/test_otfnal.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/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. -# -from pyscf import gto, scf -from pyscf.dft2.libxc import XC_KEYS, XC_ALIAS, hybrid_coeff, rsh_coeff -from pyscf.dft2.libxc import parse_xc, is_nlc, needs_laplacian -from pyscf import mcpdft -from pyscf.mcpdft.otfnal import make_hybrid_fnal -import unittest -from itertools import product - -h2 = scf.RHF (gto.M (atom = 'H 0 0 0; H 1.2 0 0', basis = 'sto-3g', - output='/dev/null', verbose=0)).run () -mc = mcpdft.CASSCF (h2, 'tPBE', 2, 2, grids_level=1).run () -LIBXC_KILLS = ['GGA_X_LB','GGA_X_LBM','LDA_XC_TIH'] - -def _test_hybrid_and_decomp (kv, xc): - txc = 't'+xc - e_pdft, e_ot = mc.energy_tot (otxc=txc) - decomp = mc.get_energy_decomposition (otxc=txc) - e_nuc, e_1e, e_coul, e_otx, e_otc, e_mcwfn = decomp - kv.assertAlmostEqual (e_otx+e_otc, e_ot, 10) - kv.assertAlmostEqual (e_nuc+e_1e+e_coul, e_pdft-e_ot, 10) - htxc = 't'+make_hybrid_fnal (xc, 0.25) - e_hyb = mc.energy_tot (otxc=htxc)[0] - kv.assertAlmostEqual (e_hyb, 0.75*e_pdft+0.25*mc.e_mcscf, 10) - -# _itrf.LIBXC_is_meta_gga returns an error code (-1) if the functional isn't found -# Abuse this to screen broken or unavailable KS-DFT functionals -# Cf. PySCF issue #1692 -# TODO: revisit after _itrf or libxc.py is improved to handle error codes -def is_laplacian_meta_gga_or_broken(xc_code): - if xc_code is None: - return None - elif isinstance(xc_code, str): - if is_nlc(xc_code): - return 'NLC' - hyb, fn_facs = parse_xc(xc_code) - else: - fn_facs = [(xc_code, 1)] # mimic fn_facs - if not fn_facs: - return False - - else: - return any(needs_laplacian(xid) for xid, fac in fn_facs) - -def _skip_key (key): - xc = str (key) - return (xc in LIBXC_KILLS - or '_XC_' in xc - or '_K_' in xc - or xc.startswith ('HYB_') - or is_laplacian_meta_gga_or_broken (xc) - or hybrid_coeff (xc) - or rsh_coeff (xc)[0]) - -def setUpModule (): - global h2, mc, XC_TEST_KEYS - XC_KEYS1 = {key for key in XC_KEYS if not _skip_key (key)} - XC_KEYS2 = {key for key, val in XC_ALIAS.items () if not _skip_key (val)} - XC_TEST_KEYS = XC_KEYS1.union (XC_KEYS2) - -def tearDownModule(): - global h2, mc, XC_TEST_KEYS - h2.mol.stdout.close () - del h2, mc, XC_TEST_KEYS - -class KnownValues(unittest.TestCase): - - def test_combo_fnals (self): - # just a sanity test for the string parsing - x_list = ["", "LDA", "0.4*LDA+0.6*B88", "0.5*LDA+0.7*B88-0.2*MPW91"] - c_list = ["", "VWN3", "0.33*VWN3+0.67*LYP", "0.43*VWN3-0.77*LYP-0.2*P86"] - for x, c in product(x_list, c_list): - xc = x + "," + c - if xc == ",": - continue - with self.subTest(x=x, c=c): - _test_hybrid_and_decomp(self, xc) - - def test_many_fnals (self): - # sanity test for built-in functionals - # many functionals are expected to fail and must be skipped - for xc in XC_TEST_KEYS: - with self.subTest (xc=xc): - _test_hybrid_and_decomp (self, xc) - - def test_null_fnal (self): - _test_hybrid_and_decomp (self, '') - -if __name__ == "__main__": - print("Full Tests for MC-PDFT on-top functional class API") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/test/test_otpd.py b/pyscf/mcpdft/test/test_otpd.py deleted file mode 100644 index cc635057e..000000000 --- a/pyscf/mcpdft/test/test_otpd.py +++ /dev/null @@ -1,235 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, lib, mcscf -from pyscf import mcpdft -from pyscf.mcpdft.otpd import get_ontop_pair_density, _grid_ao2mo -from pyscf.mcpdft.otpd import density_orbital_derivative -import unittest - -def vector_error (test, ref): - err = test - ref - norm_test = linalg.norm (test) - norm_ref = linalg.norm (ref) - norm_err = linalg.norm (err) - if norm_ref > 0: err = norm_err / norm_ref - elif norm_test > 0: err = norm_err / norm_test - else: err = norm_err - return err - -h2 = scf.RHF (gto.M (atom = 'H 0 0 0; H 1.2 0 0', basis = '6-31g', - output='/dev/null', verbose=0)).run () -lih = scf.RHF (gto.M (atom = 'Li 0 0 0; H 1.2 0 0', basis = 'sto-3g', - output='/dev/null', verbose=0)).run () - -def get_dm2_ao (mc, mo_coeff, casdm1, casdm2): - i, ncas = mc.ncore, mc.ncas - j = i + ncas - mo_occ = mo_coeff[:,:j] - dm1 = 2*np.eye (j) - dm1[i:j,i:j] = casdm1 - dm2 = np.multiply.outer (dm1, dm1) - dm2 -= 0.5*np.multiply.outer (dm1, dm1).transpose (0,3,2,1) - dm2[i:j,i:j,i:j,i:j] = casdm2 - return np.einsum ('pqrs,ip,jq,kr,ls->ijkl', dm2, mo_occ, mo_occ, - mo_occ, mo_occ) - -def get_rho_ref (dm1s, ao): - rho = np.einsum ('sjk,caj,ak->sca', dm1s, ao[:4], ao[0]) - rho[:,1:4] += np.einsum ('sjk,cak,aj->sca', dm1s, ao[1:4], ao[0]) - return rho - -def get_Pi_ref (dm2, ao): - nderiv, ngrid, nao = ao.shape - Pi = np.zeros ((5,ngrid)) - Pi[:4] = np.einsum ('ijkl,cai,aj,ak,al->ca', dm2, - ao[:4], ao[0], ao[0], ao[0]) / 2 - Pi[1:4] += np.einsum ('ijkl,caj,ai,ak,al->ca', dm2, - ao[1:4], ao[0], ao[0], ao[0]) / 2 - Pi[1:4] += np.einsum ('ijkl,cak,ai,aj,al->ca', dm2, - ao[1:4], ao[0], ao[0], ao[0]) / 2 - Pi[1:4] += np.einsum ('ijkl,cal,ai,aj,ak->ca', dm2, - ao[1:4], ao[0], ao[0], ao[0]) / 2 - X, Y, Z, XX, YY, ZZ = 1,2,3,4,7,9 - Pi[4] = np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[XX], ao[0], ao[0], ao[0]) / 2 - Pi[4] += np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[YY], ao[0], ao[0], ao[0]) / 2 - Pi[4] += np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[ZZ], ao[0], ao[0], ao[0]) / 2 - Pi[4] += np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[X], ao[X], ao[0], ao[0]) / 2 - Pi[4] += np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Y], ao[Y], ao[0], ao[0]) / 2 - Pi[4] += np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Z], ao[Z], ao[0], ao[0]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[X], ao[0], ao[X], ao[0]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Y], ao[0], ao[Y], ao[0]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Z], ao[0], ao[Z], ao[0]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[X], ao[0], ao[0], ao[X]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Y], ao[0], ao[0], ao[Y]) / 2 - Pi[4] -= np.einsum ('ijkl,ai,aj,ak,al->a', dm2, - ao[Z], ao[0], ao[0], ao[Z]) / 2 - return Pi - -def num_Drho_DPi (mc, x, dm1s_mo, cascm2, ao, mask): - ot = mc.otfnal - ni = ot._numint - ncore, ncas = mc.ncore, mc.ncas - nocc = ncore+ncas - mo = mc.mo_coeff.dot (mc.update_rotate_matrix (x)) - dm1s = np.dot (mo, dm1s_mo).transpose (1,0,2) - dm1s = np.dot (dm1s, mo.conj ().T) - make_rho = tuple (ni._gen_rho_evaluator (ot.mol, dm1s[i], 1) for i in range (2)) - Drho = np.array ([m[0] (0, ao, mask, 'MGGA') for m in make_rho]) - DPi = get_ontop_pair_density (ot, Drho, ao, cascm2, mo[:,ncore:nocc], - deriv=2, non0tab=mask) - return Drho, DPi - -def an_Drho_DPi (mc, x, drho, dPi, mo): - mo1 = mo.dot (mc.unpack_uniq_var (x))*2 - Drho = np.einsum ('sdgi,gi->sdg', drho[:,:4], mo1[0]) - Drho[:,1:4] += np.einsum ('sgi,dgi->sdg', drho[:,0], mo1[1:4]) - DPi = np.einsum ('dgi,gi->dg', dPi[:4], mo1[0]) - DPi[1:4] += np.einsum ('gi,dgi->dg', dPi[0], mo1[1:4]) - return Drho, DPi - -def convergence_table_Drho_DPi (mc, x, make_rho, casdm1s, cascm2, ao, mask): - ot = mc.otfnal - ni = ot._numint - nao, nmo = mc.mo_coeff.shape - ncore, ncas = mc.ncore, mc.ncas - nocc = ncore+ncas - dm1s_mo = np.stack ([np.eye (nmo),]*2, axis=0) - dm1s_mo[:,nocc:,:] = 0 - dm1s_mo[:,:,nocc:] = 0 - dm1s_mo[:,ncore:nocc,ncore:nocc] = casdm1s - mo_cas = mc.mo_coeff[:,ncore:nocc] - rho = np.array ([m[0] (0, ao, mask, 'MGGA') for m in make_rho]) - Pi = get_ontop_pair_density (ot, rho, ao, cascm2, mo_cas, deriv=2, non0tab=mask) - mo = _grid_ao2mo (ot.mol, ao, mc.mo_coeff, non0tab=mask) - drho, dPi = density_orbital_derivative ( - ot, ncore, ncas, casdm1s, cascm2, rho, mo, deriv=1, - non0tab=mask) - err_tab = np.zeros ((4, 5)) - for ix, p in enumerate (range (16,20)): - x1 = x / 2**p - Drho_an, DPi_an = an_Drho_DPi (mc, x1, drho, dPi, mo) - Drho_num, DPi_num = num_Drho_DPi (mc, x1, dm1s_mo, cascm2, ao, mask) - Drho_num -= rho - DPi_num -= Pi - err_tab[ix,0] = 1/2**p - err_tab[ix,1] = vector_error (Drho_an[:,0], Drho_num[:,0]) - err_tab[ix,2] = vector_error (Drho_an[:,1:4], Drho_num[:,1:4]) - err_tab[ix,3] = vector_error (DPi_an[0], DPi_num[0]) - err_tab[ix,4] = vector_error (DPi_an[1:4], DPi_num[1:4]) - denom_tab = err_tab[:-1].copy () - err_tab[1:][denom_tab==0] = 0.5 - denom_tab[denom_tab==0] = 1 - conv_tab = err_tab[1:] / denom_tab - return conv_tab - -def tearDownModule(): - global h2, lih - h2.mol.stdout.close () - lih.mol.stdout.close () - del h2, lih - -class KnownValues(unittest.TestCase): - - def test_otpd (self): - for mol, mf in zip (('H2', 'LiH'), (h2, lih)): - for state, nel in zip (('Singlet', 'Triplet'), (2, (2,0))): - mc = mcpdft.CASSCF (mf, 'tLDA,VWN3', 2, nel, grids_attr={'atom_grid':(2,14)}).run () - ncore, ncas = mc.ncore, mc.ncas - nocc = ncore+ncas - dm1s = np.array (mc.make_rdm1s ()) - casdm1s, casdm2s = mc.fcisolver.make_rdm12s (mc.ci, mc.ncas, mc.nelecas) - casdm1 = casdm1s[0] + casdm1s[1] - casdm2 = casdm2s[0] + casdm2s[1] + casdm2s[1].transpose (2,3,0,1) + casdm2s[2] - cascm2 = casdm2 - np.multiply.outer (casdm1, casdm1) - cascm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - cascm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - mo_cas = mc.mo_coeff[:,ncore:nocc] - nao, ncas = mo_cas.shape - with self.subTest (mol=mol, state=state): - ot, ni = mc.otfnal, mc.otfnal._numint - make_rho = tuple (ni._gen_rho_evaluator (ot.mol, dm1s[i], 1) for i in range (2)) - dm2_ao = get_dm2_ao (mc, mc.mo_coeff, casdm1, casdm2) - for ao, mask, weight, coords in ni.block_loop (ot.mol, ot.grids, nao, 2, 2000): - rho = np.array ([m[0] (0, ao, mask, 'MGGA') for m in make_rho]) - Pi_test = get_ontop_pair_density ( - ot, rho, ao, cascm2, mo_cas, deriv=2, - non0tab=mask) - Pi_ref = get_Pi_ref (dm2_ao, ao) - self.assertAlmostEqual (lib.fp (Pi_test), lib.fp (Pi_ref), 10) - mo = _grid_ao2mo (ot.mol, ao, mc.mo_coeff, non0tab=mask) - drho, dPi = density_orbital_derivative ( - ot, ncore, ncas, casdm1s, cascm2, rho, mo, deriv=1, - non0tab=mask) - rho_test = np.einsum ('sdgi,gi->sdg', drho[:,:4], mo[0]) - rho_test[:,1:4] += np.einsum ('sgi,dgi->sdg', drho[:,0], mo[1:4]) - self.assertAlmostEqual (lib.fp (rho_test), lib.fp (rho[:,:4]), 10) - Pi_test = np.einsum ('dgi,gi->dg', dPi[:4], mo[0]) / 2 - Pi_test[1:4] += np.einsum ('gi,dgi->dg', dPi[0], mo[1:4]) / 2 - self.assertAlmostEqual (lib.fp (Pi_test), lib.fp (Pi_ref[:4]), 10) - - def test_otpd_orbital_deriv (self): - for mol, mf in zip (('H2', 'LiH'), (h2, lih)): - for state, nel in zip (('Singlet', 'Triplet'), (2, (2,0))): - mc = mcpdft.CASSCF (mf, 'tLDA,VWN3', 2, nel, grids_attr={'atom_grid':(2,14)}).run () - ncore, ncas = mc.ncore, mc.ncas - nocc = ncore+ncas - dm1s = np.array (mc.make_rdm1s ()) - casdm1s, casdm2s = mc.fcisolver.make_rdm12s (mc.ci, mc.ncas, mc.nelecas) - casdm1 = casdm1s[0] + casdm1s[1] - casdm2 = casdm2s[0] + casdm2s[1] + casdm2s[1].transpose (2,3,0,1) + casdm2s[2] - cascm2 = casdm2 - np.multiply.outer (casdm1, casdm1) - cascm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - cascm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - mo_cas = mc.mo_coeff[:,ncore:nocc] - nao, nmo = mc.mo_coeff.shape - x = 2*(1-np.random.rand (nmo, nmo)) - x = mc.pack_uniq_var (x-x.T) - ot, ni = mc.otfnal, mc.otfnal._numint - make_rho = tuple (ni._gen_rho_evaluator (ot.mol, dm1s[i], 1) for i in range (2)) - for ao, mask, weight, coords in ni.block_loop (ot.mol, ot.grids, nao, 2, 2000): - conv_tab=convergence_table_Drho_DPi (mc, x, make_rho, casdm1s, cascm2, ao, mask) - conv_tab=conv_tab[-3:].sum (0)/3 - with self.subTest (mol=mol, state=state, quantity="rho"): - self.assertAlmostEqual (conv_tab[1], .5, 3) - with self.subTest (mol=mol, state=state, quantity="rho'"): - self.assertAlmostEqual (conv_tab[2], .5, 3) - with self.subTest (mol=mol, state=state, quantity="Pi"): - self.assertAlmostEqual (conv_tab[3], .5, 3) - with self.subTest (mol=mol, state=state, quantity="Pi'"): - self.assertAlmostEqual (conv_tab[4], .5, 3) - -if __name__ == "__main__": - print("Full Tests for MC-PDFT on-top pair density construction") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/test/test_pdft_feff.py b/pyscf/mcpdft/test/test_pdft_feff.py deleted file mode 100644 index 78416e06c..000000000 --- a/pyscf/mcpdft/test/test_pdft_feff.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -import numpy as np -from pyscf import gto, scf, ao2mo, lib, mcscf -from pyscf.lib import temporary_env -from pyscf.mcscf import mc_ao2mo, newton_casscf -from pyscf.mcpdft import pdft_feff, _dms -from pyscf import mcpdft -import unittest - - -def setUpModule(): - global h2, lih - h2 = scf.RHF(gto.M(atom='H 0 0 0; H 1.2 0 0', basis='6-31g', - output='/dev/null', verbose=0)).run() - lih = scf.RHF(gto.M(atom='Li 0 0 0; H 1.2 0 0', basis='sto-3g', - output='/dev/null', verbose=0)).run() - - -def tearDownModule(): - global h2, lih - h2.mol.stdout.close() - lih.mol.stdout.close() - del h2, lih - - -def get_feff_ref(mc, state=0, dm1s=None, cascm2=None, c_dm1s=None, c_cascm2=None): - nao = mc.mo_coeff.shape[0] - if dm1s is None or cascm2 is None: - t_casdm1s = mc.make_one_casdm1s(mc.ci, state=state) - t_dm1s = _dms.casdm1s_to_dm1s(mc, t_casdm1s) - t_casdm2 = mc.make_one_casdm2(mc.ci, state=state) - t_cascm2 = _dms.dm2_cumulant(t_casdm2, t_casdm1s) - if dm1s is None: - dm1s = t_dm1s - - if cascm2 is None: - cascm2 = t_cascm2 - - mo_cas = mc.mo_coeff[:, mc.ncore:][:, :mc.ncas] - if c_dm1s is None: - c_dm1s = dm1s - - if c_cascm2 is None: - c_cascm2 = cascm2 - - v1, v2_ao = pdft_feff.lazy_kernel(mc.otfnal, dm1s, cascm2, c_dm1s, - c_cascm2, mo_cas) - with temporary_env(mc._scf, _eri=ao2mo.restore(4, v2_ao, nao)): - with temporary_env(mc.mol, incore_anyway=True): - v2 = mc_ao2mo._ERIS(mc, mc.mo_coeff, method='incore') - - return v1, v2 - - -def contract_veff(mc, mo_coeff, ci, veff1, veff2, ncore=None, ncas=None): - if ncore is None: - ncore = mc.ncore - if ncas is None: - ncas = mc.ncas - - nocc = ncore + ncas - - casdm1s = mc.make_one_casdm1s(ci) - casdm1 = casdm1s[0] + casdm1s[1] - casdm2 = mc.make_one_casdm2(ci) - - dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s, mo_coeff=mo_coeff) - dm1 = dm1s[0] + dm1s[1] - - ref_e = np.tensordot(veff1, dm1) - ref_e += veff2.energy_core - ref_e += np.tensordot(veff2.vhf_c[ncore:nocc, ncore:nocc], casdm1) - ref_e += 0.5 * np.tensordot(veff2.papa[ncore:nocc, :, ncore:nocc, :], - casdm2, axes=4) - return ref_e - - -def case(kv, mc): - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nmo = mc.mo_coeff.shape[1] - nocc, nvir = ncore + ncas, nmo - ncore - ncas - ngorb = ncore * ncas + nocc * nvir - fcasscf = mcscf.CASSCF(mc._scf, ncas, nelecas) - fcasscf.__dict__.update(mc.__dict__) - - # What we will do is numerically check the differentiation of V_pq D_pq + - # 1/2 v_pqrs d_pqrs with respect to MO and CI rotations. This manifests - # itself as the generalized Fock matrix elements generated from V_pq and - # v_pqrs as well as then the generalized fock matrix elements from [rho - # F]_pq and [rho F]_pqrs. We are then numerically check how the contract is - # by evaluating V_pq D_pq + ... at the reference and the slightly modified - # CI/MO parameters - - feff1, feff2 = mc.get_pdft_feff(mc.mo_coeff, mc.ci, incl_coul=False, paaa_only=True, jk_pc=True) - veff1, veff2 = mc.get_pdft_veff(mc.mo_coeff, mc.ci, incl_coul=False, paaa_only=False) - ref_c_veff = contract_veff(mc, mc.mo_coeff, mc.ci, veff1, veff2) - - with lib.temporary_env(fcasscf, get_hcore=lambda: feff1): - g_feff, _, _, hdiag_feff = newton_casscf.gen_g_hop(fcasscf, - mc.mo_coeff, mc.ci, - feff2) - - with lib.temporary_env(fcasscf, get_hcore=lambda: veff1): - g_veff, _, _, hdiag_veff = newton_casscf.gen_g_hop(fcasscf, - mc.mo_coeff, mc.ci, - veff2) - - g_all = g_feff + g_veff - hdiag_all = hdiag_feff + hdiag_veff - g_numzero = np.abs(g_all) < 1e-8 - hdiag_all[g_numzero] = 1 - x0 = -g_all / hdiag_all - xorb_norm = np.linalg.norm(x0[:ngorb]) - xci_norm = np.linalg.norm(x0[ngorb:]) - x0 = g_all * np.random.rand(*x0.shape) - 0.5 - x0[g_numzero] = 0 - x0[:ngorb] *= xorb_norm / np.linalg.norm(x0[:ngorb]) - x0[ngorb:] *= xci_norm / (np.linalg.norm(x0[ngorb:]) or 1) - err_tab = np.zeros((0, 2)) - - def seminum(x): - uorb, ci1 = newton_casscf.extract_rotation(fcasscf, x, 1, mc.ci) - mo1 = mc.rotate_mo(mc.mo_coeff, uorb) - veff1_1, veff2_1 = mc.get_pdft_veff(mo=mo1, ci=ci1, incl_coul=False) - semi_num_c_veff = contract_veff(mc, mo1, ci1, veff1_1, veff2_1) - return semi_num_c_veff - ref_c_veff - - for ix, p in enumerate(range(30)): - x1 = x0 / (2 ** p) - x1_norm = np.linalg.norm(x1) - dg_test = np.dot(g_all, x1) - dg_ref = seminum(x1) - dg_err = abs((dg_test - dg_ref) / dg_ref) - err_tab = np.append(err_tab, [[x1_norm, dg_err]], axis=0) - if ix > 0: - conv_tab = err_tab[1:ix + 1, :] / err_tab[:ix, :] - - if ix > 1 and np.all(np.abs(conv_tab[-3:, -1] - 0.5) < 0.01) and abs(err_tab[-1, 1]) < 1e-3: - break - - with kv.subTest(q='x'): - kv.assertAlmostEqual(conv_tab[-1, 0], 0.5, 9) - - with kv.subTest(q='de'): - kv.assertLess(abs(err_tab[-1, 1]), 1e-3) - kv.assertAlmostEqual(conv_tab[-1, 1], 0.5, delta=0.05) - - -class KnownValues(unittest.TestCase): - - def test_dvot(self): - np.random.seed(1) - for mol, mf in zip(("H2", "LiH"), (h2, lih)): - for state, nel in zip(('Singlet', 'Triplet'), (2, (2, 0))): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, grids_level=1).run() - with self.subTest(mol=mol, state=state, fnal=fnal): - case(self, mc) - - def test_feff_ao2mo(self): - for mol, mf in zip(("H2", "LiH"), (h2, lih)): - for state, nel in zip(('Singlet', 'Triplet'), (2, (2, 0))): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, grids_level=1).run() - f1_test, f2_test = mc.get_pdft_feff(jk_pc=True) - f1_ref, f2_ref = get_feff_ref(mc) - f_test = [f1_test, f2_test.vhf_c, f2_test.papa, - f2_test.ppaa, f2_test.j_pc, f2_test.k_pc] - f_ref = [f1_ref, f2_ref.vhf_c, f2_ref.papa, f2_ref.ppaa, - f2_ref.j_pc, f2_ref.k_pc] - terms = ['f1', 'f2.vhf_c', 'f2.papa', 'f2.ppaa', 'f2.j_pc', - 'f2.k_pc'] - for test, ref, term in zip(f_test, f_ref, terms): - with self.subTest(mol=mol, state=state, fnal=fnal, - term=term): - self.assertAlmostEqual(lib.fp(test), - lib.fp(ref), delta=1e-4) - - def test_sa_contract_feff_ao2mo(self): - for mol, mf in zip(("H2", "LiH"), (h2, lih)): - for state, nel in zip(['Singlet'], [2]): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, - grids_level=1).state_average_([0.5, - 0.5]).run() - - sa_casdm1s = _dms.make_weighted_casdm1s(mc) - sa_casdm2 = _dms.make_weighted_casdm2(mc) - sa_dm1s = _dms.casdm1s_to_dm1s(mc, sa_casdm1s) - sa_cascm2 = _dms.dm2_cumulant(sa_casdm2, sa_casdm1s) - - f1_test, f2_test = mc.get_pdft_feff(jk_pc=True, - c_dm1s=sa_dm1s, - c_cascm2=sa_cascm2) - f1_ref, f2_ref = get_feff_ref(mc, c_dm1s=sa_dm1s, - c_cascm2=sa_cascm2) - f_test = [f1_test, f2_test.vhf_c, f2_test.papa, - f2_test.ppaa, f2_test.j_pc, f2_test.k_pc] - f_ref = [f1_ref, f2_ref.vhf_c, f2_ref.papa, f2_ref.ppaa, - f2_ref.j_pc, f2_ref.k_pc] - terms = ['f1', 'f2.vhf_c', 'f2.papa', 'f2.ppaa', 'f2.j_pc', - 'f2.k_pc'] - for test, ref, term in zip(f_test, f_ref, terms): - with self.subTest(mol=mol, state=state, fnal=fnal, - term=term): - self.assertAlmostEqual(lib.fp(test), lib.fp(ref), - delta=1e-6) - - def test_delta_contract_feff_ao2mo(self): - for mol, mf in zip(("H2", "LiH"), (h2, lih)): - for state, nel in zip(['Singlet'], [2]): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, grids_level=1).state_average_([0.5, 0.5]).run() - - sa_casdm1s = _dms.make_weighted_casdm1s(mc) - sa_casdm2 = _dms.make_weighted_casdm2(mc) - - casdm1s = mc.make_one_casdm1s(ci=mc.ci) - casdm2 = mc.make_one_casdm2(ci=mc.ci) - dm1s = _dms.casdm1s_to_dm1s(mc, casdm1s) - cascm2 = _dms.dm2_cumulant(casdm2, casdm1s) - - f1_test, f2_test = mc.get_pdft_feff(jk_pc=True, casdm1s=sa_casdm1s, casdm2=sa_casdm2, c_dm1s=dm1s, - c_cascm2=cascm2, delta=True) - - f1_ref, f2_ref = mc.get_pdft_feff(jk_pc=True, casdm1s=sa_casdm1s, casdm2=sa_casdm2, c_dm1s=dm1s, - c_cascm2=cascm2) - f1_sa, f2_sa = mc.get_pdft_feff(jk_pc=True, casdm1s=sa_casdm1s, casdm2=sa_casdm2) - - f1_ref -= f1_sa - f2_ref.vhf_c -= f2_sa.vhf_c - f2_ref.papa -= f2_sa.papa - f2_ref.ppaa -= f2_sa.ppaa - f2_ref.j_pc -= f2_sa.j_pc - f2_ref.k_pc -= f2_sa.k_pc - - f_test = [f1_test, f2_test.vhf_c, f2_test.papa, - f2_test.ppaa, f2_test.j_pc, f2_test.k_pc] - f_ref = [f1_ref, f2_ref.vhf_c, f2_ref.papa, f2_ref.ppaa, - f2_ref.j_pc, f2_ref.k_pc] - terms = ['f1', 'f2.vhf_c', 'f2.papa', 'f2.ppaa', 'f2.j_pc', - 'f2.k_pc'] - for test, ref, term in zip(f_test, f_ref, terms): - with self.subTest(mol=mol, state=state, fnal=fnal, - term=term): - self.assertAlmostEqual(lib.fp(test), lib.fp(ref), - delta=1e-6) - - -if __name__ == "__main__": - print("Full Tests for pdft_feff") - unittest.main() diff --git a/pyscf/mcpdft/test/test_pdft_veff.py b/pyscf/mcpdft/test/test_pdft_veff.py deleted file mode 100644 index bf69cb8a0..000000000 --- a/pyscf/mcpdft/test/test_pdft_veff.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, lib, mcscf, ao2mo -from pyscf.lib import temporary_env -from pyscf.mcscf import newton_casscf, mc_ao2mo -from pyscf import mcpdft -from pyscf.mcpdft import pdft_veff -import unittest - - -def get_veff_ref(mc): - nao, nmo = mc.mo_coeff.shape - dm1s = np.asarray(mc.make_rdm1s()) - casdm1s = np.asarray(mc.fcisolver.make_rdm1s(mc.ci, mc.ncas, mc.nelecas)) - casdm1 = casdm1s.sum(0) - casdm1s = mc.fcisolver.make_rdm1s(mc.ci, mc.ncas, mc.nelecas) - casdm2 = mc.fcisolver.make_rdm2(mc.ci, mc.ncas, mc.nelecas) - cascm2 = casdm2 - np.multiply.outer(casdm1, casdm1) - cascm2 += np.einsum('sij,skl->ilkj', casdm1s, casdm1s) - mo_cas = mc.mo_coeff[:, mc.ncore:][:, :mc.ncas] - v1, v2_ao = pdft_veff.lazy_kernel(mc.otfnal, dm1s, cascm2, mo_cas) - with temporary_env(mc._scf, _eri=ao2mo.restore(4, v2_ao, nao)): - with temporary_env(mc.mol, incore_anyway=True): - v2 = mc_ao2mo._ERIS(mc, mc.mo_coeff, method='incore') - return v1, v2 - - -def case(kv, mc): - ncore, ncas, nelecas = mc.ncore, mc.ncas, mc.nelecas - nao, nmo = mc.mo_coeff.shape - nocc, nvir = ncore + ncas, nmo - ncore - ncas - ngorb = ncore * ncas + nocc * nvir - fcasscf = mcscf.CASSCF(mc._scf, ncas, nelecas) - fcasscf.__dict__.update(mc.__dict__) - veff1, veff2 = mc.get_pdft_veff(mc.mo_coeff, mc.ci, incl_coul=True, paaa_only=True) - with lib.temporary_env(fcasscf, get_hcore=lambda: mc.get_hcore() + veff1): - g_all, _, _, hdiag_all = newton_casscf.gen_g_hop( - fcasscf, mc.mo_coeff, mc.ci, veff2) - g_numzero = np.abs(g_all) < 1e-8 - hdiag_all[g_numzero] = 1 - x0 = -g_all / hdiag_all - xorb_norm = linalg.norm(x0[:ngorb]) - xci_norm = linalg.norm(x0[ngorb:]) - x0 = g_all * np.random.rand(*x0.shape) - 0.5 - x0[g_numzero] = 0 - x0[:ngorb] *= xorb_norm / linalg.norm(x0[:ngorb]) - x0[ngorb:] *= xci_norm / (linalg.norm(x0[ngorb:]) or 1) - err_tab = np.zeros((0, 2)) - - def seminum(x): - uorb, ci1 = newton_casscf.extract_rotation(fcasscf, x, 1, mc.ci) - mo1 = mc.rotate_mo(mc.mo_coeff, uorb) - e1 = mc.energy_tot(mo_coeff=mo1, ci=ci1)[0] - return e1 - mc.e_tot - - for ix, p in enumerate(range(20)): - # For numerically unstable (i.e., translated) fnals, - # it is somewhat difficult to find the convergence plateau - # However, repeated calculations should show that - # failure is rare and due only to numerical instability - # and chance. - x1 = x0 / (2 ** p) - x1_norm = linalg.norm(x1) - de_test = np.dot(g_all, x1) - de_ref = seminum(x1) - de_err = abs((de_test - de_ref) / de_ref) - err_tab = np.append(err_tab, [[x1_norm, de_err]], axis=0) - if ix > 0: - conv_tab = err_tab[1:ix + 1, :] / err_tab[:ix, :] - if ix > 1 and np.all(np.abs(conv_tab[-3:, -1] - 0.5) < 0.01) and abs(err_tab[-1, 1]) < 1e-3: - break - - with kv.subTest(q='x'): - kv.assertAlmostEqual(conv_tab[-1, 0], 0.5, 9) - with kv.subTest(q='de'): - kv.assertLess(abs(err_tab[-1, 1]), 1e-3) - kv.assertAlmostEqual(conv_tab[-1, 1], 0.5, delta=0.01) - - -def setUpModule(): - global h2, lih - h2 = scf.RHF(gto.M(atom='H 0 0 0; H 1.2 0 0', basis='6-31g', - output='/dev/null', verbose=0)).run() - lih = scf.RHF(gto.M(atom='Li 0 0 0; H 1.2 0 0', basis='sto-3g', - output='/dev/null', verbose=0)).run() - - -def tearDownModule(): - global h2, lih - h2.mol.stdout.close() - lih.mol.stdout.close() - del h2, lih - - -class KnownValues(unittest.TestCase): - - def test_de(self): - np.random.seed(1) - for mol, mf in zip(('H2', 'LiH'), (h2, lih)): - for state, nel in zip(('Singlet', 'Triplet'), (2, (2, 0))): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE', 'tN12', 'ftN12', 'tM06L'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, grids_level=1).run() - with self.subTest(mol=mol, state=state, fnal=fnal): - case(self, mc) - - - def test_veff_ao2mo(self): - for mol, mf in zip(('H2', 'LiH'), (h2, lih)): - for state, nel in zip(('Singlet', 'Triplet'), (2, (2, 0))): - for fnal in ('tLDA,VWN3', 'ftLDA,VWN3', 'tPBE', 'ftPBE', 'tN12', 'ftN12', 'tM06L'): - mc = mcpdft.CASSCF(mf, fnal, 2, nel, grids_level=1).run() - v1_test, v2_test = mc.get_pdft_veff(jk_pc=True) - v1_ref, v2_ref = get_veff_ref(mc) - v_test = [v1_test, v2_test.vhf_c, v2_test.papa, - v2_test.ppaa, v2_test.j_pc, v2_test.k_pc] - v_ref = [v1_ref, v2_ref.vhf_c, v2_ref.papa, v2_ref.ppaa, - v2_ref.j_pc, v2_ref.k_pc] - terms = ['v1', 'v2.vhf_c', 'v2.papa', 'v2.ppaa', 'v2.j_pc', - 'v2.k_pc'] - for test, ref, term in zip(v_test, v_ref, terms): - with self.subTest(mol=mol, state=state, fnal=fnal, - term=term): - self.assertAlmostEqual(lib.fp(test), - lib.fp(ref), delta=1e-4) - - -if __name__ == "__main__": - print("Full Tests for MC-PDFT first fnal derivatives") - unittest.main() diff --git a/pyscf/mcpdft/test/test_spingaps_atoms.py b/pyscf/mcpdft/test/test_spingaps_atoms.py deleted file mode 100644 index e6476c553..000000000 --- a/pyscf/mcpdft/test/test_spingaps_atoms.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/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 scipy import linalg -from pyscf import gto, scf, lib, mcscf -from pyscf.data.nist import HARTREE2EV -from pyscf.fci.direct_spin1 import _unpack_nelec -from pyscf import mcpdft -import unittest - - -############################################################################## -# Inline CC-PVTZ basis set for Be as implemented in OpenMolcas v21.10 # -############################################################################## - -Bebasis = gto.parse(''' -BASIS "ao basis" SPHERICAL PRINT -#BASIS SET: (11s,5p,2d,1f) -> [4s,3p,2d,1f] -Be S - 6.863000E+03 2.360000E-04 0.000000E+00 -4.300000E-05 0.000000E+00 - 1.030000E+03 1.826000E-03 0.000000E+00 -3.330000E-04 0.000000E+00 - 2.347000E+02 9.452000E-03 0.000000E+00 -1.736000E-03 0.000000E+00 - 6.656000E+01 3.795700E-02 0.000000E+00 -7.012000E-03 0.000000E+00 - 2.169000E+01 1.199650E-01 0.000000E+00 -2.312600E-02 0.000000E+00 - 7.734000E+00 2.821620E-01 0.000000E+00 -5.813800E-02 0.000000E+00 - 2.916000E+00 4.274040E-01 0.000000E+00 -1.145560E-01 0.000000E+00 - 1.130000E+00 2.662780E-01 0.000000E+00 -1.359080E-01 0.000000E+00 - 2.577000E-01 1.819300E-02 1.000000E+00 2.280260E-01 0.000000E+00 - 1.101000E-01 -7.275000E-03 0.000000E+00 5.774410E-01 0.000000E+00 - 4.409000E-02 1.903000E-03 0.000000E+00 3.178730E-01 1.000000E+00 -Be P - 7.436000E+00 0.000000E+00 1.073600E-02 0.000000E+00 - 1.577000E+00 0.000000E+00 6.285400E-02 0.000000E+00 - 4.352000E-01 0.000000E+00 2.481800E-01 0.000000E+00 - 1.438000E-01 1.000000E+00 5.236990E-01 0.000000E+00 - 4.994000E-02 0.000000E+00 3.534250E-01 1.000000E+00 -Be D - 3.493000E-01 1.000000E+00 0.000000E+00 - 1.724000E-01 0.000000E+00 1.000000E+00 -Be F - 3.423000E-01 1.0000000 -END - ''') - -############################################################################## -# Inline definition of quadrature grid that can be implemented in OpenMolcas # -# v21.10. The corresponding input to the SEWARD module in OpenMolcas is # -# grid input # -# nr=100 # -# lmax=41 # -# rquad=ta # -# nopr # -# noro # -# end of grid input # -############################################################################## - -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] - -my_grids = {'atom_grid': (99,590), - 'radi_method': om_treutler_ahlrichs, - 'prune': False, - 'radii_adjust': None} - -############################################################################## -# Reference from OpenMolcas v21.10. tPBE and tBLYP values are consistent w/ # -# JCTC 10, 3669 (2014) (after erratum!) # -############################################################################## - -ref = {'N': {'tPBE': 2.05911249, - 'tBLYP': 1.97701243, - 'ftPBE': 1.50034408, - 'ftBLYP': 1.31520533}, - 'Be': {'tPBE': -2.56743750, - 'tBLYP': -2.56821329, - 'ftPBE': -2.59816134, - 'ftBLYP': -2.58502831}} - -Natom = scf.RHF (gto.M (atom = 'N 0 0 0', basis='cc-pvtz', spin=3, symmetry='D2h', output='/dev/null')) -Beatom = scf.RHF (gto.M (atom = 'Be 0 0 0', basis=Bebasis, spin=2, symmetry='D2h', output='/dev/null')) -Natom_hs = [Natom, (4,1), 'Au', None] -Natom_ls = [Natom, (3,2), 'B3u', None] -Beatom_ls = [Beatom, (1,1), 'Ag', None] -Beatom_hs = [Beatom, (2,0), 'B3u', None] - -calcs = {'N': (Natom_hs, Natom_ls), - 'Be': (Beatom_hs, Beatom_ls)} - -def check_calc (calc): - if not calc[0].converged: calc[0].kernel () - if calc[-1] is None: - mf = calc[0] - mol = mf.mol - nelecas = calc[1] - s = (nelecas[1]-nelecas[0])*0.5 - ss = s*(s+1) - mc = mcscf.CASSCF (mf, 4, nelecas).set (conv_tol=1e-10) - mc.fix_spin_(ss=ss) - mc.fcisolver.wfnsym = calc[2] - calc[-1] = mc - if not calc[-1].converged: - calc[-1].kernel () - return calc[-1] - -def get_gap (gs, es, fnal): - gs = check_calc (gs) - es = check_calc (es) - e0 = mcpdft.CASSCF (gs._scf, fnal, gs.ncas, gs.nelecas, grids_attr=my_grids).set ( - fcisolver = gs.fcisolver, conv_tol=1e-10).kernel ( - gs.mo_coeff, gs.ci)[0] - e1 = mcpdft.CASSCF (es._scf, fnal, es.ncas, es.nelecas, grids_attr=my_grids).set ( - fcisolver = es.fcisolver, conv_tol=1e-10).kernel ( - es.mo_coeff, es.ci)[0] - return (e1-e0)*HARTREE2EV - -def tearDownModule(): - global Natom, Natom_hs, Natom_ls, Beatom, Beatom_ls, Beatom_hs - Natom.mol.stdout.close () - Beatom.mol.stdout.close () - del Natom, Natom_hs, Natom_ls, Beatom, Beatom_ls, Beatom_hs - -class KnownValues(unittest.TestCase): - - def test_gaps (self): - for atom in 'N', 'Be': - for fnal in 'tPBE','tBLYP','ftPBE','ftBLYP': - my_ref = ref[atom][fnal] - my_test = get_gap (*calcs[atom], fnal) - with self.subTest (atom=atom, fnal=fnal): - self.assertAlmostEqual (my_test, my_ref, 5) - -if __name__ == "__main__": - print("Full Tests for MC-PDFT energies of N and Be atom spin states") - unittest.main() - - - - - - diff --git a/pyscf/mcpdft/test/test_xmspdft.py b/pyscf/mcpdft/test/test_xmspdft.py deleted file mode 100644 index 3372ba66d..000000000 --- a/pyscf/mcpdft/test/test_xmspdft.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2025 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: Matthew Hennefarth - - -import numpy as np -from pyscf import gto, scf, dft, mcpdft, lib -from pyscf.mcpdft import xmspdft - -import unittest - - -def get_lih(r, weights=None, fnal="ftLDA,VWN3"): - global mols - mol = gto.M( - atom="Li 0 0 0\nH {} 0 0".format(r), - basis="sto3g", - output="/dev/null", - verbose=0, - ) - mols.append(mol) - mf = scf.RHF(mol).run() - mc = mcpdft.CASSCF(mf, fnal, 2, 2, grids_level=1) - mc.fix_spin_(ss=0) - if weights is None: - weights = [0.5, 0.5] - mc = mc.multi_state(weights, "xms").run(conv_tol=1e-8) - return mc - - -def setUpModule(): - global mols, original_grids - mols = [] - original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False - - -def tearDownModule(): - global mols, original_grids - dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids - [m.stdout.close() for m in mols] - del mols, original_grids - - -class KnownValues(unittest.TestCase): - - def test_lih_xms2ftlda(self): - mc = get_lih(1.5) - e_mcscf_avg = np.dot(mc.e_mcscf, mc.weights) - hcoup = abs(mc.heff_mcscf[1, 0]) - ct_mcscf = abs(mc.si_mcscf[0, 0]) - - # Reference values from - # - PySCF hash 8ae2bb2eefcd342c52639097517b1eda7ca5d1cd - # - PySCF-forge hash 5b8ab86a31917ca1a6b414f7a590c4046b9a8994 - # - # Implementation with those hashes verified with OpenMolcas - # (tag 462-g00b34a15f) - - HCOUP_EXPECTED = 0.01996004651860848 - CT_MCSCF_EXPECTED = 0.9886771524332543 - E_MCSCF_AVG_EXPECTED = -7.789021830554006 - E_STATES_EXPECTED = [-7.858628517291297, -7.69980510010583] - - with self.subTest("diabats"): - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 9) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 9) - self.assertAlmostEqual(ct_mcscf, CT_MCSCF_EXPECTED, 9) - - with self.subTest("adiabats"): - self.assertAlmostEqual(lib.fp(mc.e_states), lib.fp(E_STATES_EXPECTED), 9) - - with self.subTest("safock"): - safock = xmspdft.make_fock_mcscf(mc, ci=mc.get_ci_adiabats(uci="MCSCF")) - EXPECTED_SA_FOCK_DIAG = [-4.207598506457942, -3.88169762424571] - EXPECTED_SA_FOCK_OFFDIAG = 0.05063053788053997 - - self.assertAlmostEqual( - lib.fp(safock.diagonal()), lib.fp(EXPECTED_SA_FOCK_DIAG), 9 - ) - self.assertAlmostEqual(abs(safock[0, 1]), EXPECTED_SA_FOCK_OFFDIAG, 9) - - with self.subTest("safock unequal"): - safock = xmspdft.make_fock_mcscf( - mc, ci=mc.get_ci_adiabats(uci="MCSCF"), weights=[1, 0] - ) - EXPECTED_SA_FOCK_DIAG = [-4.194714957289011, -3.8317682977263754] - EXPECTED_SA_FOCK_OFFDIAG = 0.006987847963283834 - - self.assertAlmostEqual( - lib.fp(safock.diagonal()), lib.fp(EXPECTED_SA_FOCK_DIAG), 9 - ) - self.assertAlmostEqual(abs(safock[0, 1]), EXPECTED_SA_FOCK_OFFDIAG, 9) - - def test_lih_xms2ftlda_unequal_weights(self): - mc = get_lih(1.5, weights=[0.9, 0.1]) - e_mcscf_avg = np.dot(mc.e_mcscf, mc.weights) - hcoup = abs(mc.heff_mcscf[1, 0]) - ct_mcscf = abs(mc.si_mcscf[0, 0]) - - HCOUP_EXPECTED = 0.006844540922301437 - CT_MCSCF_EXPECTED = 0.9990626861718451 - E_MCSCF_AVG_EXPECTED = -7.8479729055935685 - E_STATES_EXPECTED = [-7.869843475893866, -7.696820527732333] - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 9) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 9) - self.assertAlmostEqual(ct_mcscf, CT_MCSCF_EXPECTED, 9) - self.assertAlmostEqual(lib.fp(mc.e_states), lib.fp(E_STATES_EXPECTED), 9) - - def test_lih_xms2mc23(self): - mc = get_lih(1.5) - e_mcscf_avg = np.dot(mc.e_mcscf, mc.weights) - hcoup = abs(mc.heff_mcscf[0,1]) - ct_mcscf = abs(mc.si_mcscf[0,0]) - ct_pdft = abs(mc.si_pdft[0,0]) - - # Reference values from - # - PySCF hash 9a0bb6ddded7049bdacdaf4cfe422f7ce826c2c7 - # - PySCF-forge hash 40bfc1eb8b8a662c1c57bff5f7ffa7316e5d043d - - E_MCSCF_AVG_EXPECTED = -7.7890218306 - E_STATES_EXPECTED = [-7.85862852, -7.6998051] - HCOUP_EXPECTED = 0.01996005 - CT_MCSCF_EXPECTED = 0.98867715 - CT_PDFT_EXPECTED = 0.9919416682619435 - - self.assertAlmostEqual(e_mcscf_avg, E_MCSCF_AVG_EXPECTED, 8) - self.assertAlmostEqual(lib.fp(mc.e_states), lib.fp(E_STATES_EXPECTED), 8) - self.assertAlmostEqual(hcoup, HCOUP_EXPECTED, 8) - self.assertAlmostEqual(ct_mcscf, CT_MCSCF_EXPECTED, 8) - self.assertAlmostEqual(ct_pdft, CT_PDFT_EXPECTED, 8) - - -if __name__ == "__main__": - print("Full Tests for XMS-PDFT") - unittest.main() diff --git a/pyscf/mcpdft/tfnal_derivs.py b/pyscf/mcpdft/tfnal_derivs.py deleted file mode 100644 index 74feb0a1f..000000000 --- a/pyscf/mcpdft/tfnal_derivs.py +++ /dev/null @@ -1,830 +0,0 @@ -#!/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 scipy import linalg -from pyscf.lib import logger - - -def _reshape_vxc_sigma(vxc0, dens_deriv): - # d/drho, d/dsigma -> d/drho, d/drho' - vrho = vxc0[0] - vxc1 = list(vrho.T) - if dens_deriv: - vsigma = vxc0[1] - vxc1 = vxc1 + list(vsigma.T) - else: - vxc1 = [vxc1[0][None, :], vxc1[1][None, :]] - return vxc1 - - -def _unpack_vxc_sigma(vxc0, rho, dens_deriv): - # d/drho, d/dsigma -> d/drho, d/drho' - vrho = vxc0[0] - vxc1 = list(vrho.T) - if dens_deriv: - vsigma = vxc0[1] - vxc1 = vxc1 + list(vsigma.T) - vxc1 = _unpack_sigma_vector(vxc1, rho[0][1:4], rho[1][1:4]) - else: - vxc1 = [vxc1[0][None, :], vxc1[1][None, :]] - return vxc1 - - -def _pack_fxc_ltri(fxc0, dens_deriv): - # d2/drho2, d2/drhodsigma, d2/dsigma2 - # -> lower-triangular Hessian matrix - frho = fxc0[0].T - fxc1 = [frho[0], ] - fxc1 += [frho[1], frho[2], ] - if dens_deriv: - frhosigma, fsigma = fxc0[1].T, fxc0[2].T - fxc1 += [frhosigma[0], frhosigma[3], fsigma[0], ] - fxc1 += [frhosigma[1], frhosigma[4], fsigma[1], fsigma[3], ] - fxc1 += [frhosigma[2], frhosigma[5], fsigma[2], fsigma[4], fsigma[5]] - return fxc1 - - -def eval_ot(otfnal, rho, Pi, dderiv=1, weights=None, _unpack_vot=True): - r'''get the integrand of the on-top xc energy and its functional - derivatives wrt rho and Pi - - Args: - rho : ndarray of shape (2,*,ngrids) - containing spin-density [and derivatives] - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - - Kwargs: - dderiv : integer - Order of derivatives to return - weights : ndarray of shape (ngrids) - used ONLY for debugging the total number of ``translated'' - electrons in the calculation of rho_t - Not multiplied into anything! - _unpack_vot : logical - If True, derivatives with respect to density gradients are - reported as de/drho' and de/dPi'; otherwise, they are - reported as de/d|rho'|^2, de/d(rho'.Pi'), and de/d|Pi'|^2 - - Returns: - eot : ndarray of shape (ngrids) - integrand of the on-top exchange-correlation energy - vot : ndarrays of shape (*,ngrids) or None - first functional derivative of Eot wrt (density, pair - density) and their derivatives. If _unpack_vot = True, shape - and format is ([a, ngrids], [b, ngrids]) : (vrho, vPi); - otherwise, [c, ngrids] : [rho,Pi,|rho'|^2,tau,rho'.Pi',|Pi'|^2] - ftGGA: a=4, b=4, c=5 (drop tau) - tmGGA: a=5, b=1, c=4 (drop Pi') - tGGA: a=4, b=1, c=3 (drop Pi', tau) - *tLDA: a=1, b=1, c=2 (drop rho', tau) - fot : ndarray of shape (*,ngrids) or None - second functional derivative of Eot wrt density, pair - density, and derivatives; first dimension is lower- - triangular matrix elements corresponding to the basis - (rho, Pi, |rho'|^2, rho'.Pi', |Pi'|^2) - stopping at Pi (3 elements) for *tLDA and |rho'|^2 (6 - elements) for tGGA. - ''' - if dderiv > 2: - raise NotImplementedError("Translation of density derivatives of " - "higher order than 2") - if rho.ndim == 2: rho = rho[:, None, :] - if Pi.ndim == 1: Pi = Pi[None, :] - assert (rho.shape[0] == 2) - assert (rho.shape[1] <= 6), "Undefined behavior for this function" - - nderiv = rho.shape[1] - nderiv_Pi = Pi.shape[0] - - rho_t = otfnal.get_rho_translated(Pi, rho) - # LDA in libxc has a special numerical problem with zero-valued densities - # in one spin - if nderiv == 0: - idx = (rho_t[0, 0] > 1e-15) & (rho_t[1, 0] < 1e-15) - rho_t[1, 0, idx] = 1e-15 - idx = (rho_t[0, 0] < 1e-15) & (rho_t[1, 0] > 1e-15) - rho_t[0, 0, idx] = 1e-15 - - # mGGA in libxc has special numerical problem with zero-valued densities in - # one spin! - if nderiv == 5: - idx = (rho_t[0, 4] > 1e-15) & (rho_t[1, 4] < 1e-15) - rho_t[1, 4, idx] = 1e-15 - idx = (rho_t[0, 4] < 1e-15) & (rho_t[1, 4] > 1e-15) - rho_t[0, 4, idx] = 1e-15 - - rho_tot = rho.sum(0) - - if nderiv > 4 and dderiv > 1: - raise NotImplementedError("Meta-GGA functional Hessians") - - if 1 < nderiv <= 4: - rho_deriv = rho_tot[1:4, :] - elif 4 < nderiv <= 5: - rho_deriv = rho_tot[1:5, :] - else: - rho_deriv = None - - Pi_deriv = Pi[1:4, :] if nderiv_Pi > 1 else None - xc_grid = otfnal._numint.eval_xc(otfnal.otxc, (rho_t[0, :, :], - rho_t[1, :, :]), spin=1, relativity=0, deriv=dderiv, - verbose=otfnal.verbose)[:dderiv + 1] - eot = xc_grid[0] * rho_t[:, 0, :].sum(0) - if (weights is not None) and otfnal.verbose >= logger.DEBUG: - nelec = rho_t[0, 0].dot(weights) + rho_t[1, 0].dot(weights) - logger.debug(otfnal, ('MC-PDFT: Total number of electrons in (this ' - 'chunk of) the total density = %s'), nelec) - ms = (rho_t[0, 0].dot(weights) - rho_t[1, 0].dot(weights)) / 2.0 - logger.debug(otfnal, ('MC-PDFT: Total ms = (neleca - nelecb) / 2 in ' - '(this chunk of) the translated density = %s'), ms) - vot = fot = None - if dderiv > 0: - # vrho, vsigma = xc_grid[1][:2] - vxc = list(xc_grid[1][0].T) - if otfnal.dens_deriv > 0: - vxc = vxc + list(xc_grid[1][1].T) - - # vrho, vsigma, vlapl, vtau = xc_grid[1][:4] - if otfnal.dens_deriv > 1: - # we might get a None for one of the derivatives.. - # we get None for the laplacian derivative - if xc_grid[1][2] is not None: - raise NotImplementedError("laplacian translated meta-GGA functionals") - - # Here is the tau term - vxc = vxc + list(xc_grid[1][3].T) - - vot = otfnal.jT_op(vxc, rho, Pi) - if _unpack_vot: vot = _unpack_sigma_vector(vot, - deriv1=rho_deriv, deriv2=Pi_deriv) - if dderiv > 1: - # I should implement this entirely in terms of the gradient norm, since - # that reduces the number of grid columns from 25 to 9 for t-GGA and - # from 64 to 25 for ft-GGA (and steps around the need to "unpack" - # fsigma and frhosigma entirely). - fxc = _pack_fxc_ltri(xc_grid[2], otfnal.dens_deriv) - # First pass: fxc - fot = _jT_f_j(fxc, otfnal.jT_op, rho, Pi, rec=otfnal) - # Second pass: translation derivatives - fot_d_jT = otfnal.d_jT_op(vxc, rho, Pi) - fot[:fot_d_jT.shape[0]] += fot_d_jT - return eot, vot, fot - - -def unpack_vot(packed, rho, Pi): - if rho.ndim == 2: rho = rho[:, None, :] - if Pi.ndim == 1: Pi = Pi[None, :] - assert (rho.shape[0] == 2) - - nderiv = rho.shape[1] - nderiv_Pi = Pi.shape[0] - - rho_tot = rho.sum(0) - rho_deriv = rho_tot[1:4, :] if nderiv > 1 else None - Pi_deriv = Pi[1:4, :] if nderiv_Pi > 1 else None - return _unpack_sigma_vector(packed, deriv1=rho_deriv, deriv2=Pi_deriv) - - -def _unpack_sigma_vector(packed, deriv1=None, deriv2=None): - # For GGAs, libxc differentiates with respect to - # sigma[0] = nabla^2 rhoa - # sigma[1] = nabla rhoa . nabla rhob - # sigma[2] = nabla^2 rhob - # So we have to multiply the Jacobian to obtain the requested derivatives: - # J[0,nabla rhoa] = 2 * nabla rhoa - # J[0,nabla rhob] = 0 - # J[1,nabla rhoa] = nabla rhob - # J[1,nabla rhob] = nabla rhoa - # J[2,nabla rhoa] = 0 - # J[2,nabla rhob] = 2 * nabla rhob - if len(packed) > 5: - raise RuntimeError("{} {}".format(len(packed), [p.shape for p in packed[:5]])) - ncol1 = 1 - if deriv1 is not None and len(packed) > 2: - ncol1 += deriv1.shape[0] - ncol2 = 1 + 3 * int((deriv2 is not None) and len(packed) > 3) - ngrid = packed[0].shape[-1] # Don't assume it's an ndarray - unp1 = np.empty((ncol1, ngrid), dtype=packed[0].dtype) - unp2 = np.empty((ncol2, ngrid), dtype=packed[0].dtype) - unp1[0] = packed[0] - unp2[0] = packed[1] - if ncol1 > 1: - unp1[1:4] = 2 * deriv1[:3] * packed[2] - if ncol1 > 4: - # Deal with the tau term - unp1[4:5] = packed[3:4] - if ncol2 > 1: - unp1[1:4] += deriv2 * packed[-2] - unp2[1:4] = (2 * deriv2 * packed[-1]) + (deriv1[:3] * packed[-2]) - return unp1, unp2 - - -def contract_vot(vot, rho, Pi): - '''Evalute the product of unpacked vot with perturbed density, pair density, and derivatives. - - Args: - vot : (ndarray of shape (*,ngrids), ndarray of shape (*, ngrids)) - format is ([a, ngrids], [b, ngrids]) : (vrho, vPi) - ftGGA: a=4, b=4 - tGGA: a=4, b=1 - *tLDA: a=1, b=1 - rho : ndarray of shape (*,ngrids) - containing density [and derivatives] - the density contracted with vot - Pi : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - the density contracted with vot - - Returns: - cvot : ndarray of shape (ngrids) - product of vot wrt (density, pair density) and their derivatives - ''' - vrho, vPi = vot - if rho.shape[0] == 2: rho = rho.sum(0) - if rho.ndim == 1: rho = rho[None, :] - if Pi.ndim == 1: Pi = Pi[None, :] - - cvot = vrho[0] * rho[0] + vPi[0] * Pi[0] - if len(vrho) > 1: - cvot += (vrho[1:4,:] * rho[1:4, :]).sum(0) - - if len(vPi) > 1: - cvot += (vPi[1:4, :] * Pi[1:4, :]).sum(0) - - return cvot - - -def contract_fot(otfnal, fot, rho0, Pi0, rho1, Pi1, unpack=True, - vot_packed=None): - r''' Evaluate the product of a packed lower-triangular matrix - with perturbed density, pair density, and derivatives. - - Args: - fot : ndarray of shape (*,ngrids) - Lower-triangular matrix elements corresponding to the basis - (rho, Pi, |drho|^2, drho'.dPi, |dPi|^2) stopping at Pi (3 - elements) for *tLDA and |drho|^2 (6 elements) for tGGA. - rho0 : ndarray of shape (2,*,ngrids) - containing density [and derivatives] - the density at which fot was evaluated - Pi0 : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - the density at which fot was evaluated - rho1 : ndarray of shape (2,*,ngrids) - containing density [and derivatives] - the density contracted with fot - Pi1 : ndarray with shape (*,ngrids) - containing on-top pair density [and derivatives] - the density contracted with fot - - Kwargs: - unpack : logical - If True, returns vot1 in unpacked shape: - (ndarray of shape (*,ngrids), - ndarray of shape (*,ngrids)) - corresponding to (density, pair density) and their - derivatives. This requires vot_packed for *tGGA functionals - Otherwise, returns vot1 in packed shape: - (rho, Pi, |rho'|^2, rho'.Pi', |Pi'|^2) - stopping at Pi (3 elements) for *tLDA and |rho'|^2 (6 - elements) for tGGA. - vot_packed : ndarray of shape (*,ngrids) - Vector elements corresponding to the basis - (rho, Pi, |drho|^2, drho'.dPi, |dPi|^2) stopping at Pi (2 - elements) for *tLDA and |drho|^2 (3 elements) for tGGA. - Required if unpack == True for *tGGA functionals - (because vot_|drho|^2 contributes to fot_rho',rho', etc.) - - Returns: - vot1 : (ndarray of shape (*,ngrids), - ndarray of shape (*,ngrids)) - product of fot wrt (density, pair density) - and their derivatives - ''' - if rho0.shape[0] == 2: rho0 = rho0.sum(0) # Never has exactly 1 derivative - if rho0.ndim == 1: rho0 = rho0[None, :] - if Pi0.ndim == 1: Pi0 = Pi0[None, :] - if rho1.shape[0] == 2: rho1 = rho1.sum(0) # Never has exactly 1 derivative - if rho1.ndim == 1: rho1 = rho1[None, :] - if Pi1.ndim == 1: Pi1 = Pi1[None, :] - - ngrids = fot[0].shape[-1] - vrho1 = np.zeros(ngrids, dtype=fot[0].dtype) - vPi1 = np.zeros(ngrids, dtype=fot[2].dtype) - vrho1, vPi1 = np.zeros_like(rho1), np.zeros_like(Pi1) - - # TODO: dspmv implementation - vrho1 = fot[0] * rho1[0] + fot[1] * Pi1[0] - vPi1 = fot[2] * Pi1[0] + fot[1] * rho1[0] - - rho0p = Pi0p = rho1p = Pi1p = None - v1 = [vrho1, vPi1] - if len(fot) > 3: - srr = 2 * (rho0[1:4, :] * rho1[1:4, :]).sum(0) - vrho1 += fot[3] * srr - vPi1 += fot[4] * srr - vrr = fot[3] * rho1[0] + fot[4] * Pi1[0] + fot[5] * srr - rho0p = rho0[1:4] - rho1p = rho1[1:4] - v1 = [vrho1, vPi1, vrr] - if len(fot) > 6: - srP = ((rho0[1:4, :] * Pi1[1:4, :]).sum(0) - + (rho1[1:4, :] * Pi0[1:4, :]).sum(0)) - sPP = 2 * (Pi0[1:4, :] * Pi1[1:4, :]).sum(0) - vrho1 += fot[6] * srP + fot[10] * sPP - vPi1 += fot[7] * srP + fot[11] * sPP - vrr += fot[8] * srP + fot[12] * sPP - vrP = (fot[6] * rho1[0] + fot[7] * Pi1[0] - + fot[8] * srr + fot[9] * srP + fot[13] * sPP) - vPP = (fot[10] * rho1[0] + fot[11] * Pi1[0] - + fot[12] * srr + fot[13] * srP + fot[14] * sPP) - Pi0p = Pi0[1:4] - Pi1p = Pi1[1:4] - v1 = [vrho1, vPi1, vrr, vrP, vPP] - - ggrad = (rho0p is not None) or (Pi0p is not None) - if unpack: - vrho1, vPi1 = _unpack_sigma_vector(v1, rho0p, Pi0p) - if ggrad: - if vot_packed is None: - raise RuntimeError("Cannot evaluate fot.x in terms of " - "unpacked density gradients without vot_packed") - vrho2, vPi2 = _unpack_sigma_vector(vot_packed, rho1p, Pi1p) - if vrho1.shape[0] > 1: vrho1[1:4, :] += vrho2[1:4, :] - if vPi1.shape[0] > 1: vPi1[1:4, :] += vPi2[1:4, :] - v1 = (vrho1, vPi1) - - return v1 - - -def _jT_f_j(frr, jT_op, *args, **kwargs): - r''' Apply a jacobian function taking *args to the lower-triangular - second-derivative array frr''' - nel = len(frr) - nr = int(round(np.sqrt(1 + 8 * nel) - 1)) // 2 - rec = kwargs.get('rec', None) - ngrids = frr[0].shape[-1] - - # build square-matrix index array to address packed matrix frr w/o copying - ltri_ix = np.tril_indices(nr) - idx_arr = np.zeros((nr, nr), dtype=np.int32) - idx_arr[ltri_ix] = range(nel) - idx_arr += idx_arr.T - diag_ix = np.diag_indices(nr) - idx_arr[diag_ix] = idx_arr[diag_ix] // 2 - - # first pass: jT . frr -> fcr - fcr = np.stack([jT_op([frr[i] for i in ix_row], *args) - for ix_row in idx_arr], axis=1) - - # second pass. fcr is a rectangular matrix (unavoidably) - nc = fcr.shape[0] - if getattr(rec, 'verbose', 0) < logger.DEBUG: - fcc = np.empty((nc * (nc + 1) // 2, ngrids), dtype=fcr.dtype) - i = 0 - for ix_row, fc_row in enumerate(fcr): - di = ix_row + 1 - j = i + di - fcc[i:j] = jT_op(fc_row, *args)[:di] - i = j - else: - fcc = np.empty((nc, nc, ngrids), dtype=fcr.dtype) - for fcc_row, fcr_row in zip(fcc, fcr): - fcc_row[:] = jT_op(fcr_row, *args) - for i in range(1, nc): - for j in range(i): - scale = (fcc[i, j] + fcc[j, i]) / 2 - scale[scale == 0] = 1 - logger.debug(rec, 'MC-PDFT jT_f_j symmetry check %d,%d: %e', - i, j, linalg.norm((fcc[i, j] - fcc[j, i]) / scale)) - ltri_ix = np.tril_indices(nc) - fcc = fcc[ltri_ix] - - return fcc - - -def _gentLDA_jT_op(x, rho, Pi, R, zeta): - # On a grid, multiply the transpose of the Jacobian - # d(trhoa,trhob) [fictitous densities] - # J = ______________ - # d(rho,Pi) [real densities] - # by a vector x_(trhoa,trhob) - ngrid = rho.shape[-1] - if R.ndim > 1: R = R[0] - - # ab -> cs coordinate transformation - xc = (x[0] + x[1]) / 2.0 - xm = (x[0] - x[1]) / 2.0 - - # Charge sector has no explicit rho denominator - # and so does not require indexing to avoid - # division by zero - jTx = np.zeros((2, ngrid), dtype=x[0].dtype) - jTx[0] = xc + xm * (zeta[0] - (2 * R * zeta[1])) - - # Spin sector has a rho denominator - idx = (rho[0] > 1e-15) - zeta = zeta[1, idx] - rho = rho[0, idx] - xm = xm[idx] - jTx[1, idx] = 4 * xm * zeta / rho - - return jTx - - -def _tGGA_jT_op(x, rho, Pi, R, zeta): - # On a grid, multiply the transpose of the Jacobian - # d(trho?'.trho?') [fictitous density gradients] - # J = ______________ - # d(rho,Pi,|rho'|) [real densities and gradients] - # by a vector x_(|trho*'|^2) in the context of tGGAs - ngrid = rho.shape[-1] - jTx = np.zeros((3, ngrid), dtype=x[0].dtype) - if R.ndim > 1: R = R[0] - - # ab -> cs coordinate transformation - xcc = (x[2] + x[4] + x[3]) / 4.0 - xcm = (x[2] - x[4]) / 2.0 - xmm = (x[2] + x[4] - x[3]) / 4.0 - - # Gradient-gradient sector - idx = (zeta[0]!=1) - jTx[2] = x[2] - jTx[2,idx] = (xcc + xcm * zeta[0] + xmm * zeta[0] * zeta[0])[idx] - - # Finite-precision safety - - # Density-gradient sector - idx = (rho[0] > 1e-15) - sigma_fac = ((rho[1:4].conj() * rho[1:4]).sum(0) * zeta[1]) - sigma_fac = ((xcm + 2 * zeta[0] * xmm) * sigma_fac)[idx] - rho = rho[0, idx] - R = R[idx] - sigma_fac = -2 * sigma_fac / rho - jTx[0, idx] = R * sigma_fac - jTx[1, idx] = -2 * sigma_fac / rho - - return jTx - - -def _tmetaGGA_jT_op(x, rho, Pi, R, zeta): - # output ordering is - # ordering: rho, Pi, |rho'|^2, tau - ngrid = rho.shape[-1] - jTx = np.zeros((4, ngrid), dtype=x[0].dtype) - if R.ndim > 1: - R = R[0] - - # ab -> cs coordinate transformation - xc = (x[5] + x[6]) / 2.0 - xm = (x[5] - x[6]) / 2.0 - - # easy part - jTx[3] = xc + zeta[0] * xm - - tau_lapl_factor = zeta[1] * rho[4] * xm - idx = rho[0] > 1e-15 - rho = rho[0, idx] - R = R[idx] - - tau_lapl_factor = 2 * tau_lapl_factor[idx] / rho - - jTx[0, idx] = -R * tau_lapl_factor - jTx[1, idx] = 2 * tau_lapl_factor / rho - - return jTx - - -def _tGGA_jT_op_m2z(x, rho, zeta, srr): - # cs -> rho,zeta step of _tGGA_jT_op above - # unused; for contemplative purposes only - jTx = np.empty_like(x) - jTx[0] = x[0] + zeta[0] * x[1] - jTx[1] = x[1] * rho + (x[3] + 2 * zeta[0] * x[4]) * srr - jTx[2] = x[2] + x[3] * zeta[0] + x[4] * zeta[0] * zeta[0] - jTx[3] = 0 - jTx[4] = 0 - return jTx - - -def _ftGGA_jT_op_m2z(x, rho, zeta, srz, szz): - # cs -> rho,zeta step of _ftGGA_jT_op below - jTx = np.empty_like(x) - jTx[0] = 2 * x[4] * (zeta[0] * srz + rho * szz) + x[3] * srz - jTx[1] = 2 * x[4] * rho * srz - jTx[2] = 0 - jTx[3] = (x[3] + 2 * x[4] * zeta[0]) * rho - jTx[4] = x[4] * rho * rho - return jTx - - -def _ftGGA_jT_op_z2R(x, zeta, srP, sPP): - # rho,zeta -> rho,R step of _ftGGA_jT_op below - jTx = np.empty_like(x) - jTx[0] = x[0] - jTx[1] = (x[1] * zeta[1] + x[3] * srP * zeta[2] + - 2 * x[4] * sPP * zeta[1] * zeta[2]) - jTx[2] = x[2] - jTx[3] = x[3] * zeta[1] - jTx[4] = x[4] * zeta[1] * zeta[1] - return jTx - - -def _ftGGA_jT_op_R2Pi(x, rho, R, srr, srP, sPP): - # rho,R -> rho,Pi step of _ftGGA_jT_op below - if rho.ndim > 1: rho = rho[0] - if R.ndim > 1: R = R[0] - jTx = np.empty_like(x) - ri = np.empty_like(x) - ri[0, :] = 0.0 - idx = rho > 1e-15 - ri[0, idx] = 1.0 / rho[idx] - for i in range(4): - ri[i + 1] = ri[i] * ri[0] - - jTx[0] = (x[0] - 2 * R * x[1] * ri[0] - + x[3] * (6 * R * ri[1] * srr - 8 * srP * ri[2]) - + x[4] * (-24 * R * R * ri[2] * srr + 80 * R * ri[3] * srP - - 64 * ri[4] * sPP)) - jTx[1] = (4 * x[1] * ri[1] - 8 * x[3] * ri[2] * srr - + x[4] * (32 * R * ri[3] * srr - 64 * ri[4] * srP)) - jTx[2] = x[2] - 2 * R * x[3] * ri[0] + 4 * x[4] * R * R * ri[1] - jTx[3] = 4 * x[3] * ri[1] - 16 * x[4] * R * ri[2] - jTx[4] = 16 * x[4] * ri[3] - return jTx - - -def _ftGGA_jT_op(x, rho, Pi, R, zeta): - # On a grid, evaluate the contribution to the matrix-vector product - # of the transpose of the Jacobian - # d(trho?'.trho?') [fictitous density gradients] - # J = ______________ - # d(rho,Pi,|rho'|) [real densities and gradients] - # with a vector x_(|trho*'|^2), which is present in ftGGAs and - # missing in tGGAs - ngrid = rho.shape[-1] - jTx = np.zeros((5, ngrid), dtype=x[0].dtype) - - # ab -> cs step - jTx[2] = (x[2] + x[4] + x[3]) / 4.0 - jTx[3] = (x[2] - x[4]) / 2.0 - jTx[4] = (x[2] + x[4] - x[3]) / 4.0 - x = jTx - - # Intermediates - srr = (rho[1:4, :] * rho[1:4, :]).sum(0) - srP = (rho[1:4, :] * R[1:4, :]).sum(0) - sPP = (R[1:4, :] * R[1:4, :]).sum(0) - srz = srP * zeta[1] - szz = sPP * zeta[1] * zeta[1] - - # cs -> rho,zeta step - x = _ftGGA_jT_op_m2z(x, rho[0], zeta, srz, szz) - - # rho,zeta -> rho,R step - x = _ftGGA_jT_op_z2R(x, zeta, srP, sPP) - - # rho,R -> rho,Pi step - srP = (rho[1:4, :] * Pi[1:4, :]).sum(0) - sPP = (Pi[1:4, :] * Pi[1:4, :]).sum(0) - jTx = _ftGGA_jT_op_R2Pi(x, rho, R, srr, srP, sPP) - - return jTx - - -def _gentLDA_d_jT_op(x, rho, Pi, R, zeta): - # On a grid, differentiate once the Jacobian - # d(trhoa,trhob) [fictitous densities] - # J = ______________ - # d(rho,Pi) [real densities] - # and multiply by x_(trhoa,trhob) so as to compute the nonlinear- - # translation contribution to the second functional derivatives of - # the on-top energy in tLDA and ftLDA. - rho = rho[0] - Pi = Pi[0] - R = R[0] - ngrid = rho.shape[-1] - f = np.zeros((3, ngrid), dtype=x[0].dtype) - - # ab -> cs - xm = (x[0] - x[1]) / 2.0 - - # Indexing - idx = rho > 1e-15 - rho = rho[idx] - Pi = Pi[idx] - xm = xm[idx] - R = R[idx] - zeta = zeta[:, idx] - - # Intermediates - # R = otfnal.get_ratio (Pi, rho/2) - # zeta = otfnal.get_zeta (R, fn_deriv=2)[1:] - xmw = 2 * xm / rho - z1 = xmw * (zeta[1] + 2 * R * zeta[2]) - - # without further ceremony - f[0, idx] = R * z1 - f[1, idx] = -2 * z1 / rho - f[2, idx] = xmw * 8 * zeta[2] / rho / rho - - return f - - -def _tGGA_d_jT_op(x, rho, Pi, R, zeta): - # On a grid, differentiate once the Jacobian - # d(trho?'.trho?') [fictitous density gradients] - # J = ______________ - # d(rho,Pi,|rho'|) [real densities and gradients] - # and multiply by x_(trho?'.trho?') so as to compute the nonlinear- - # translation contribution to the second functional derivatives of - # the on-top energy in tGGAs. - - # Generates contributions to the first five elements - # of the lower-triangular packed Hessian - ngrid = rho.shape[-1] - f = np.zeros((5, ngrid), dtype=x[0].dtype) - - # Indexing - idx = rho[0] > 1e-15 - rho = rho[0:4, idx] - Pi = Pi[0:1, idx] - x = [xi[idx] for xi in x] - R = R[0, idx] - zeta = zeta[:, idx] - - # ab -> cs - xcm = (x[2] - x[4]) / 2.0 - xmm = (x[2] + x[4] - x[3]) / 4.0 - - # Intermediates - sigma = (rho[1:4] * rho[1:4]).sum(0) - rho = rho[0] - rho2 = rho * rho - rho3 = rho2 * rho - rho4 = rho3 * rho - - # coefficient of dsigma dz - xcm += 2 * zeta[0] * xmm - f[3, idx] = -2 * xcm * R * zeta[1] / rho - f[4, idx] = 4 * xcm * zeta[1] / rho2 - - # coefficient of d^2 z - xcm *= sigma - f[0, idx] = 2 * xcm * R * (3 * zeta[1] + 2 * R * zeta[2]) / rho2 - f[1, idx] = -8 * xcm * (zeta[1] + R * zeta[2]) / rho3 - f[2, idx] = 16 * xcm * zeta[2] / rho4 - - # coefficient of dz dz - xmm *= 8 * sigma * zeta[1] * zeta[1] / rho2 - f[0, idx] += xmm * R * R - f[1, idx] -= 2 * xmm * R / rho - f[2, idx] += 4 * xmm / rho2 - - return f - - -# r,r -# 1,r 1,1 -# srr,r srr,1 srr,srr -# sr1,r sr1,1 sr1,srr sr1,sr1 -# s11,r s11,1 s11,srr s11,sr1 s11,s11 - -def _ftGGA_d_jT_op_m2z(v, rho, zeta, srz, szz): - # srm += srz*r - # smm += 2srz*r*z + szz*r*r - # 0 : r, r - # 1 : r, z - # 6 : srz, r - # 7 : srz, z - # 10 : szz, r - ngrids = v.shape[1] - f = np.zeros((15, ngrids), dtype=v.dtype) - f[0] = 2 * v[4] * szz - f[1] = 2 * v[4] * srz - f[6] = v[3] + 2 * v[4] * zeta[0] - f[7] = 2 * v[4] * rho - f[10] = 2 * v[4] * rho - assert (tuple(f[0].shape) == tuple(f[1].shape)) - assert (tuple(f[0].shape) == tuple(f[6].shape)) - assert (tuple(f[0].shape) == tuple(f[10].shape)) - return f - - -def _ftGGA_d_jT_op_z2R(v, zeta, srP, sPP): - # z = z[0] - # srz = srP*z[1] - # szz = sPP*z[1]**2 - # 2 : P, P - # 7 : srP, P - # 11 : sPP, P - ngrids = v.shape[1] - f = np.zeros((15, ngrids), dtype=v.dtype) - f[2] = 2 * v[4] * sPP * (zeta[3] * zeta[1] + zeta[2] * zeta[2]) - f[2] += v[1] * zeta[2] + v[3] * srP * zeta[3] - f[7] = v[3] * zeta[2] - f[11] = 2 * v[4] * zeta[1] * zeta[2] - assert (tuple(f[2].shape) == tuple(f[7].shape)) - assert (tuple(f[2].shape) == tuple(f[11].shape)) - return f - - -def _ftGGA_d_jT_op_R2Pi(v, rho, Pi, srr, srP, sPP): - # R = 4Pi/(r**2) = Pi*d[0] - # srR = srP*d[0] + srr*Pi*d[1] - # sRR = sPP*d[0]**2 + 2*Pi*d[1]*srP*d[0] + srr*(Pi*d[1])**2 - # d^n(d[0]) - # d[n] = --------- - # dr^n - ngrids = v.shape[-1] - f = np.zeros((15, ngrids), dtype=v.dtype) - d = np.zeros((4, ngrids), dtype=v.dtype) - idx = np.abs(rho) > 1e-15 - d[0, idx] = 4 / rho[idx] / rho[idx] - d[1, idx] = -2 * d[0, idx] / rho[idx] - d[2, idx] = -3 * d[1, idx] / rho[idx] - d[3, idx] = -4 * d[2, idx] / rho[idx] - # rho, rho - f[0] = v[1] * Pi * d[2] - f[0] += v[3] * (srP * d[2] + srr * Pi * d[3]) - f[0] += 2 * v[4] * sPP * (d[2] * d[0] + d[1] * d[1]) - f[0] += 2 * v[4] * srP * Pi * (3 * d[2] * d[1] + d[3] * d[0]) - f[0] += 2 * v[4] * srr * Pi * Pi * (d[3] * d[1] + d[2] * d[2]) - # rho, Pi - f[1] = v[1] * d[1] + v[3] * srr * d[2] - f[1] += 2 * v[4] * srP * (d[2] * d[0] + d[1] * d[1]) - f[1] += 4 * v[4] * srr * Pi * d[2] * d[1] - # Pi, Pi - f[2] = 2 * v[4] * srr * d[1] * d[1] - # rho, rr - f[3] = v[3] * Pi * d[2] + 2 * v[4] * Pi * Pi * d[2] * d[1] - # Pi, rr - f[4] = v[3] * d[1] + 2 * v[4] * Pi * d[1] * d[1] - # rho, rP - f[6] = v[3] * d[1] + 2 * v[4] * Pi * (d[2] * d[0] + d[1] * d[1]) - # Pi, rP - f[7] = 2 * v[4] * d[0] * d[1] - # rho, PP - f[10] = 2 * v[4] * d[0] * d[1] - for row in f[1:]: - if hasattr(row, 'shape'): - assert (tuple(row.shape) == tuple(f[0].shape)) - return f - - -def _ftGGA_d_jT_op(v, rho, Pi, R, zeta): - # raise NotImplementedError ("Second density derivatives for fully-" - # "translated GGA functionals") - # Generates contributions to the first five elements, - # then 6,7, then 10,11 - # of the lower-triangular packed Hessian - # (I.E., no double gradient derivatives) - # for the terms added in the fully-translated extension of tGGA - - # ab -> cs - vcm = (v[2] - v[4]) / 2.0 - vmm = (v[2] + v[4] - v[3]) / 4.0 - v[3] = vcm - v[4] = vmm - v = np.asarray(v) - - # Intermediates - srr = (rho[1:4, :] * rho[1:4, :]).sum(0) - srP = (rho[1:4, :] * R[1:4, :]).sum(0) - sPP = (R[1:4, :] * R[1:4, :]).sum(0) - srz = srP * zeta[1] - szz = sPP * zeta[1] * zeta[1] - - # cs -> rho, zeta - f = _ftGGA_d_jT_op_m2z(v, rho[0], zeta, srz, szz) - v = _ftGGA_jT_op_m2z(v, rho[0], zeta, srz, szz) - - # rho, zeta -> rho, R - # The for loops here are because I'm guessing that repeated - # initialization of large arrays to zero is slower than a short, - # shallow Python loop - f = _jT_f_j(f, _ftGGA_jT_op_z2R, zeta, srP, sPP) - f += _ftGGA_d_jT_op_z2R(v, zeta, srP, sPP) - v = _ftGGA_jT_op_z2R(v, zeta, srP, sPP) - - # rho, R -> rho, Pi - srP = (rho[1:4, :] * Pi[1:4, :]).sum(0) - sPP = (Pi[1:4, :] * Pi[1:4, :]).sum(0) - f = _jT_f_j(f, _ftGGA_jT_op_R2Pi, rho, R, srr, srP, sPP) - f += _ftGGA_d_jT_op_R2Pi(v, rho[0], Pi[0], srr, srP, sPP) - - return f diff --git a/pyscf/mcpdft/xmspdft.py b/pyscf/mcpdft/xmspdft.py deleted file mode 100644 index 4145a24b9..000000000 --- a/pyscf/mcpdft/xmspdft.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2023 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: Matthew Hennefarth - -from functools import reduce -import numpy as np -from scipy import linalg - -from pyscf.mcpdft import _dms -from pyscf.fci import direct_spin1 - - -def fock_h1e_for_cas(mc, sa_casdm1, mo_coeff=None, ncas=None, ncore=None): - '''Compute the CAS SA Fock Matrix 1-electron integrals. - - Args: - mc : instance of class _PDFT - - sa_casdm1 : ndarray of shape (ncas,ncas) - 1-RDM in the active space generated from the state-average - density. - - mo_coeff : ndarray of shape (nao,nmo) - A full set of molecular orbital coefficients. Taken from - self if not provided. - - ncas : int - Number of active space molecular orbitals - - ncore : int - Number of core molecular orbitals - - Returns: - A tuple, the first is the one-electron integrals defined in the CAS - space and the second is the constant, core part. - ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ncas is None: ncas = mc.ncas - if ncore is None: ncore = mc.ncore - - nocc = ncore + ncas - mo_core = mo_coeff[:, :ncore] - mo_cas = mo_coeff[:, ncore:nocc] - - hcore_eff = mc.get_fock(casdm1=sa_casdm1) - energy_core = mc._scf.energy_nuc() - - if mo_core.size != 0: - core_dm = np.dot(mo_core, mo_core.conj().T) * 2 - energy_core += np.tensordot(core_dm, hcore_eff).real - - h1eff = reduce(np.dot, (mo_cas.conj().T, hcore_eff, mo_cas)) - - return h1eff, energy_core - - -def make_fock_mcscf(mc, mo_coeff=None, ci=None, weights=None): - '''Compute the SA-Fock Hamiltonian/Matrix - - Args: - mc : instance of class _PDFT - - mo_coeff : ndarray of shape (nao,nmo) - A full set of molecular orbital coefficients. Taken from - self if not provided. - - ci : ndarray of shape (nroots) - CI vectors should be from a converged CASSCF/CASCI calculation - - weights : ndarray of length nroots - Weight for each state. If none, uses weights from SA-CASSCF - calculation - - Returns: - safock_ham : ndarray of shape (nroots,nroots) - State-average Fock matrix expressed in the basis provided by the CI - vectors. - - ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ci is None: ci = mc.ci - - ncas = mc.ncas - - sa_casdm1 = sum(_dms.make_weighted_casdm1s(mc, ci=ci, weights=weights)) - h1, h0 = fock_h1e_for_cas(mc, sa_casdm1, mo_coeff=mo_coeff) - hc_all = [direct_spin1.contract_1e(h1, c, ncas, mc.nelecas) for c in ci] - safock_ham = np.tensordot(ci, hc_all, axes=((1, 2), (1, 2))) - idx = np.diag_indices_from(safock_ham) - safock_ham[idx] += h0 - - return safock_ham - - -def diagonalize_safock(mc, mo_coeff=None, ci=None): - '''Diagonalizes the SA-Fock matrix. Returns the eigenvalues and - eigenvectors. ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ci is None: ci = mc.ci - - fock = make_fock_mcscf(mc, mo_coeff=mo_coeff, ci=ci) - return linalg.eigh(fock) - - -def safock_energy(mc, **kwargs): - '''Diabatizer Function - - The "objective" function we are optimizing when solving for the - SA-Fock eigenstates is that the SA-Fock energy (average) is - minimized with the constraint to the final states being orthonormal. - Its the whole saddle point thing similar to in SA-MC-PDFT gradients. For now - I just omit this whole issue since the first derivative is by default 0 and - don't worry about the Hessian (or second derivatives) since I don't care. - It should fail if we try and do gradients but I can put a hard - RaiseImplementation error - - Returns: - SA-Fock Energy : float - weighted sum of SA-Fock energies - dSA-Fock Energy : ndarray of shape npair = nroots*(nroots - 1)/2 - first derivative of the SA-Fock energy wrt interstate rotation - This is zero by default since we diagonalize a matrix - d2SA-Fock Energy : ndarray of shape (npair,npair) - Should be the Lagrange multiplier terms. Currently returning None - since we cannot do gradients yet. - ''' - dsa_fock = np.zeros( - int(mc.fcisolver.nroots * (mc.fcisolver.nroots - 1) / 2)) - # TODO fix, this redundancy...no need to compute fock matrix twice.. - e_states, _ = diagonalize_safock(mc) - - return np.dot(e_states, mc.weights), dsa_fock, None - - -def solve_safock(mc, mo_coeff=None, ci=None, ): - '''Diabatize Function. Finds the SA-Fock Eigenstates within the model space - spanned by the CI vectors. - - Args: - mc : instance of class _PDFT - - mo_coeff : ndarray of shape (nao,nmo) - A full set of molecular orbital coefficients. Taken from - self if not provided. - - ci : ndarray of shape (nroots) - CI vectors should be from a converged CASSCF/CASCI calculation - - Returns: - conv : bool - Always true. If there is a convergence issue, scipy will raise an - exception. - - ci : list of ndarrays of length = nroots - CI vectors of the optimized diabatic states - ''' - if mo_coeff is None: mo_coeff = mc.mo_coeff - if ci is None: ci = mc.ci - - e_states, si_pdft = diagonalize_safock(mc, mo_coeff=mo_coeff, ci=ci) - - ci = np.tensordot(si_pdft.T, ci, 1) - conv = True - - return conv, ci - - -if __name__ == "__main__": - from pyscf import scf, gto - from pyscf import mcpdft - - xyz = '''O 0.00000000 0.08111156 0.00000000 - H 0.78620605 0.66349738 0.00000000 - H -0.78620605 0.66349738 0.00000000''' - mol = gto.M(atom=xyz, basis='sto-3g', symmetry=False, - verbose=5) - mf = scf.RHF(mol).run() - mc = mcpdft.CASSCF(mf, 'tPBE', 4, 4) - mc.fix_spin_(ss=0) - mc = mc.multi_state([1.0 / 3, ] * 3, 'xms').run() diff --git a/pyscf/nac/mspdft.py b/pyscf/nac/mspdft.py deleted file mode 100644 index b87dcd12a..000000000 --- a/pyscf/nac/mspdft.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/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.get_rhf_base ().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 deleted file mode 100644 index 5b499c49b..000000000 --- a/pyscf/nac/test/test_nac_cmspdft.py +++ /dev/null @@ -1,210 +0,0 @@ -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() From a85a52e56583038c5720c95b0bb6ce14f4fa721e Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 17 Jul 2025 11:38:19 -0500 Subject: [PATCH 3/5] patch dft2.libxc -> dft.libxc in tests & example --- examples/mcpdft/03-metaGGA_functionals.py | 4 ++++ pyscf/dft2/test/test_grad_metagga_mcpdft.py | 5 +++++ pyscf/dft2/test/test_lpdft.py | 7 +++++++ pyscf/dft2/test/test_mgga.py | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/examples/mcpdft/03-metaGGA_functionals.py b/examples/mcpdft/03-metaGGA_functionals.py index 8b5fac5e2..574ef0c94 100644 --- a/examples/mcpdft/03-metaGGA_functionals.py +++ b/examples/mcpdft/03-metaGGA_functionals.py @@ -1,3 +1,7 @@ +# patch dft2.libxc (must be done before loading mpcdft module) +from pyscf import dft +from pyscf import dft2 +dft.libxc = dft2.libxc #!/usr/bin/env/python from pyscf import gto, scf, mcpdft diff --git a/pyscf/dft2/test/test_grad_metagga_mcpdft.py b/pyscf/dft2/test/test_grad_metagga_mcpdft.py index a555b3025..6406f3c64 100644 --- a/pyscf/dft2/test/test_grad_metagga_mcpdft.py +++ b/pyscf/dft2/test/test_grad_metagga_mcpdft.py @@ -82,6 +82,11 @@ def diatomic( def setUpModule(): global mols, original_grids + from importlib import reload + from pyscf import dft2 + dft.libxc = dft2.libxc + reload (mcpdft) + reload (mcpdft.otfnal) mols = [] original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False diff --git a/pyscf/dft2/test/test_lpdft.py b/pyscf/dft2/test/test_lpdft.py index 9cf12d667..b9b4544e8 100644 --- a/pyscf/dft2/test/test_lpdft.py +++ b/pyscf/dft2/test/test_lpdft.py @@ -91,6 +91,13 @@ def get_water_triplet(functional='tPBE', basis="6-31G"): def setUpModule(): global lih, lih_4, lih_tpbe, lih_tpbe0, lih_mc23, water, t_water, original_grids + + from importlib import reload + from pyscf import dft2 + dft.libxc = dft2.libxc + reload (mcpdft) + reload (mcpdft.otfnal) + original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False lih = get_lih(1.5) diff --git a/pyscf/dft2/test/test_mgga.py b/pyscf/dft2/test/test_mgga.py index 6df6322e8..26cbad5a1 100644 --- a/pyscf/dft2/test/test_mgga.py +++ b/pyscf/dft2/test/test_mgga.py @@ -115,6 +115,12 @@ def setUpModule(): global get_water_triplet, water_tm06l, water_tmc23 global lih_tmc23_2, lih_tmc23_sa2_2, water_tmc23_2 + from importlib import reload + from pyscf import dft2 + dft.libxc = dft2.libxc + reload (mcpdft) + reload (mcpdft.otfnal) + # register otfnal tMC23_2 which is identical to MC23 mc232_preset = mcpdft.otfnal.OT_PRESET['MC23'] mcpdft.otfnal.register_otfnal('MC23_2', mc232_preset) From 668a1534fc65c6aa0be148ef0194dc652550983b Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 21 Jul 2025 10:02:48 -0500 Subject: [PATCH 4/5] Remove libpdft from CMakeFiles.txt --- pyscf/lib/CMakeLists.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pyscf/lib/CMakeLists.txt b/pyscf/lib/CMakeLists.txt index 0ad10308f..0e73867c1 100644 --- a/pyscf/lib/CMakeLists.txt +++ b/pyscf/lib/CMakeLists.txt @@ -112,16 +112,6 @@ else () set(CMAKE_INSTALL_RPATH "\$ORIGIN:\$ORIGIN/deps/lib:\$ORIGIN/deps/lib64") endif () -# Build the PDFT library -set (PDFT_SOURCE_FILES "../mcpdft/nr_numint.c") -add_library (clib_pdft SHARED ${PDFT_SOURCE_FILES}) -#FIXME: LD_LIBRARY_PATH -target_link_libraries (clib_pdft dft ${BLAS_LIBRARIES} ${OPENMP_C_PROPERTIES}) -set_target_properties (clib_pdft PROPERTIES - CLEAN_DIRECT_OUTPUT 1 - LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_NAME "pdft") - # Build the LibXC interface v2 library set (LIBXC_ITRF2_SOURCE_FILES "dft/libxc_itrf2.c") add_library (clib_libxc_itrf2 SHARED ${LIBXC_ITRF2_SOURCE_FILES}) From 777098c645c7033be6baa0e323a708db9e181d19 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 4 Aug 2025 10:32:29 -0500 Subject: [PATCH 5/5] Comment in dft2 --- pyscf/dft2/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyscf/dft2/__init__.py b/pyscf/dft2/__init__.py index 183fd8b78..e4d222da9 100644 --- a/pyscf/dft2/__init__.py +++ b/pyscf/dft2/__init__.py @@ -13,4 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +# NOTE 08/04/2025 +# This module should function with PySCF v2.10.0 specifically +# The next release will include this module, which should then be removed +# from PySCF-Forge from pyscf.dft2 import libxc