Skip to content

Commit f993539

Browse files
Merge pull request #120 from matthew-hennefarth/mcpdft-mgga-nucgrad
PDFT meta-GGA nuclear gradients and MS-PDFT meta-GGA tests
2 parents 226601b + 8055f0c commit f993539

File tree

7 files changed

+447
-196
lines changed

7 files changed

+447
-196
lines changed

doc/mcpdft/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ energy calculations for wave functions of various types.
6565
1. Single-state CASSCF wave function: [*JCTC* **2018**, *14*, 126]
6666
1. State-averaged CASSCF wave functions: [*JCP* **2020**, *153*, 014106]
6767
1. CMS-PDFT: [*Mol Phys* **2022**, 120]
68-
1. L-PDFT: [*JCTC* **2024**, *20*, 3637]
68+
1. L-PDFT (no meta-GGA): [*JCTC* **2024**, *20*, 3637]
6969
- Permanent electric dipole moment (non-hybrid functionals only) for:
7070
1. Single-state CASSCF wave function: [*JCTC* **2021**, *17*, 7586]
7171
1. State-averaged CASSCF wave functions

pyscf/grad/mcpdft.py

+41-17
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,19 @@ def gfock_sym(mc, mo_coeff, casdm1, casdm2, h1e, eris):
6868
def xc_response(ot, vot, rho, Pi, weights, moval_occ, aoval, mo_occ, mo_occup, ncore, nocc, casdm2_pack, ndpi, mo_cas):
6969
vrho, vPi = vot
7070

71+
7172
# Vpq + Vpqrs * Drs ; I'm not sure why the list comprehension down
7273
# there doesn't break ao's stride order but I'm not complaining
7374
vrho = _contract_eff_rho(vPi, rho.sum(0), add_eff_rho=vrho)
74-
tmp_dv = np.stack([ot.get_veff_1body(rho, Pi, [ao_i, moval_occ], weights, kern=vrho) for ao_i in aoval], axis=0)
75-
tmp_dv = (tmp_dv * mo_occ[None,:,:] * mo_occup[None, None,:nocc]).sum(2)
75+
76+
tmp_dv = np.stack(
77+
[
78+
ot.get_veff_1body(rho, Pi, [ao_i, moval_occ], weights, kern=vrho)
79+
for ao_i in aoval
80+
],
81+
axis=0,
82+
)
83+
tmp_dv = (tmp_dv * mo_occ[None, :, :] * mo_occup[None, None, :nocc]).sum(2)
7684

7785
# Vpuvx * Lpuvx ; remember the stupid slowest->fastest->medium
7886
# stride order of the ao grid arrays
@@ -202,7 +210,7 @@ def mcpdft_HellmanFeynman_grad (mc, ot, veff1, veff2, mo_coeff=None, ci=None,
202210
casdm1, casdm2 = mc.fcisolver.make_rdm12(ci, ncas, nelecas)
203211
twoCDM = _dms.dm2_cumulant (casdm2, casdm1)
204212
dm1 = tag_array (dm1, mo_coeff=mo_occ, mo_occ=mo_occup[:nocc])
205-
make_rho = ot._numint._gen_rho_evaluator (mol, dm1, 1)[0]
213+
make_rho = ot._numint._gen_rho_evaluator (mol, dm1, hermi=1, with_lapl=False)[0]
206214
dvxc = np.zeros ((3,nao))
207215
idx = np.array ([[1,4,5,6],[2,5,7,8],[3,6,8,9]], dtype=np.int_)
208216
# For addressing particular ao derivatives
@@ -215,9 +223,13 @@ def mcpdft_HellmanFeynman_grad (mc, ot, veff1, veff2, mo_coeff=None, ci=None,
215223
for k, ia in enumerate (atmlst):
216224
full_atmlst[ia] = k
217225

218-
ndao = (1, 4)[ot.dens_deriv]
226+
# for LDA we need 1 deriv, GGA: 2 deriv
227+
# for mGGA with tau, we only need 2 deriv
228+
ao_deriv = (1, 2, 2)[ot.dens_deriv]
229+
ndao = (1, 4, 10)[ao_deriv]
230+
ndrho = (1, 4, 5)[ot.dens_deriv]
219231
ndpi = (1, 4)[ot.Pi_deriv]
220-
ncols = 1.05 * 3 * (ndao * (nao + nocc) + max(ndao * nao, ndpi * ncas * ncas))
232+
ncols = 1.05 * 3 * (ndao * (nao + nocc) + max(ndrho * nao, ndpi * ncas * ncas))
221233

222234
for ia, (coords, w0, w1) in enumerate (rks_grad.grids_response_cc (
223235
ot.grids)):
@@ -235,27 +247,39 @@ def mcpdft_HellmanFeynman_grad (mc, ot, veff1, veff2, mo_coeff=None, ci=None,
235247
blksize = max (BLKSIZE, min (blksize, ngrids, BLKSIZE*1200))
236248
t1 = logger.timer (mc, 'PDFT HlFn quadrature atom {} mask and memory '
237249
'setup'.format (ia), *t1)
238-
for ip0 in range (0, ngrids, blksize):
239-
ip1 = min (ngrids, ip0+blksize)
240-
mask = gen_grid.make_mask (mol, coords[ip0:ip1])
241-
logger.info (mc, ('PDFT gradient atom {} slice {}-{} of {} '
242-
'total').format (ia, ip0, ip1, ngrids))
243-
ao = ot._numint.eval_ao (mol, coords[ip0:ip1],
244-
deriv=ot.dens_deriv+1, non0tab=mask)
250+
251+
for ip0 in range(0, ngrids, blksize):
252+
ip1 = min(ngrids, ip0 + blksize)
253+
mask = gen_grid.make_mask(mol, coords[ip0:ip1])
254+
logger.info(
255+
mc,
256+
("PDFT gradient atom {} slice {}-{} of {} total").format(
257+
ia, ip0, ip1, ngrids
258+
),
259+
)
260+
261+
ao = ot._numint.eval_ao(mol, coords[ip0:ip1], deriv=ao_deriv, non0tab=mask)
262+
245263
# Need 1st derivs for LDA, 2nd for GGA, etc.
246-
t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} ao '
247-
'grids').format (ia), *t1)
264+
t1 = logger.timer(
265+
mc, ("PDFT HlFn quadrature atom {} ao " "grids").format(ia), *t1
266+
)
248267
# Slice down ao so as not to confuse the rho and Pi generators
249-
if ot.xctype == 'LDA':
268+
if ot.xctype == "LDA":
250269
aoval = ao[0]
251-
if ot.xctype == 'GGA':
270+
elif ot.xctype == "GGA":
252271
aoval = ao[:4]
272+
elif ot.xctype == "MGGA":
273+
aoval = ao[:4]
274+
else:
275+
raise ValueError("Unknown xctype: {}".format(ot.xctype))
276+
253277
rho = make_rho (0, aoval, mask, ot.xctype) / 2.0
254278
rho = np.stack ((rho,)*2, axis=0)
255279
t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} rho '
256280
'calc').format (ia), *t1)
257281
Pi = get_ontop_pair_density (ot, rho, aoval, twoCDM, mo_cas,
258-
ot.dens_deriv, mask)
282+
ot.Pi_deriv, mask)
259283
t1 = logger.timer (mc, ('PDFT HlFn quadrature atom {} Pi '
260284
'calc').format (ia), *t1)
261285

pyscf/grad/test/test_diatomic_gradients.py

+29-13
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
import numpy as np
17-
from scipy import linalg
18-
from pyscf import gto, scf, df, dft, mcscf, lib
16+
from pyscf import gto, scf, df, dft
1917
from pyscf.data.nist import BOHR
2018
from pyscf import mcpdft
21-
#from pyscf.fci import csf_solver
22-
from pyscf.grad.cmspdft import diab_response, diab_grad, diab_response_o0, diab_grad_o0
23-
from pyscf.grad import mspdft as mspdft_grad
24-
import unittest, math
19+
import unittest
2520

2621
def diatomic (atom1, atom2, r, fnal, basis, ncas, nelecas, nstates,
2722
charge=None, spin=None, symmetry=False, cas_irrep=None,
28-
density_fit=False):
23+
density_fit=False, grids_level=9):
24+
global mols
2925
xyz = '{:s} 0.0 0.0 0.0; {:s} {:.3f} 0.0 0.0'.format (atom1, atom2, r)
3026
mol = gto.M (atom=xyz, basis=basis, charge=charge, spin=spin, symmetry=symmetry, verbose=0, output='/dev/null')
27+
mols.append(mol)
3128
mf = scf.RHF (mol)
32-
if density_fit: mf = mf.density_fit (auxbasis = df.aug_etb (mol))
33-
mc = mcpdft.CASSCF (mf.run (), fnal, ncas, nelecas, grids_level=9)
29+
30+
if density_fit:
31+
mf = mf.density_fit (auxbasis = df.aug_etb (mol))
32+
33+
mc = mcpdft.CASSCF (mf.run (), fnal, ncas, nelecas, grids_level=grids_level)
3434
#if spin is not None: smult = spin+1
3535
#else: smult = (mol.nelectron % 2) + 1
3636
#mc.fcisolver = csf_solver (mol, smult=smult)
@@ -46,14 +46,16 @@ def diatomic (atom1, atom2, r, fnal, basis, ncas, nelecas, nstates,
4646
return mc.nuc_grad_method ()
4747

4848
def setUpModule():
49-
global diatomic, original_grids
49+
global mols, diatomic, original_grids
50+
mols = []
5051
original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS
5152
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False
5253

5354
def tearDownModule():
54-
global diatomic, original_grids
55+
global mols, diatomic, original_grids
5556
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids
56-
del diatomic, original_grids
57+
[m.stdout.close() for m in mols]
58+
del diatomic, original_grids, mols
5759

5860
# The purpose of these separate test functions is to narrow down an error to specific degrees of
5961
# freedom. But if the lih_cms2ftpbe and _df cases pass, then almost certainly, they all pass.
@@ -155,6 +157,20 @@ def test_grad_lih_cms2ftpbe22_sto3g (self):
155157
with self.subTest (state=i):
156158
de = mc_grad.kernel (state=i) [1,0] / BOHR
157159
self.assertAlmostEqual (de, de_ref[i], 5)
160+
161+
def test_grad_lih_cms2tm06l22_sto3g (self):
162+
# z_orb: yes
163+
# z_ci: yes
164+
# z_is: yes
165+
mc_grad = diatomic ('Li', 'H', 0.8, 'tM06L', 'STO-3G', 2, 2, 2, grids_level=1)
166+
de_ref = [-1.03428938, -0.88278628]
167+
168+
# Numerical from this software
169+
for i in range (2):
170+
with self.subTest (state=i):
171+
de = mc_grad.kernel (state=i) [1,0]/BOHR
172+
173+
self.assertAlmostEqual (de, de_ref[i], 5)
158174

159175
# MRH 05/05/2023: currently, the only other test which uses DF-MC-PDFT features is
160176
# test_grad_h2co, which is slower than this. Therefore I am restoring it.
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python
2+
# Copyright 2014-2025 The PySCF Developers. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
# Author: Matthew Hennefarth <[email protected]>
17+
18+
import unittest
19+
20+
from pyscf import scf, gto, df, dft
21+
from pyscf.data.nist import BOHR
22+
from pyscf import mcpdft
23+
24+
25+
def diatomic(
26+
atom1,
27+
atom2,
28+
r,
29+
fnal,
30+
basis,
31+
ncas,
32+
nelecas,
33+
nstates,
34+
charge=None,
35+
spin=None,
36+
symmetry=False,
37+
cas_irrep=None,
38+
density_fit=False,
39+
grids_level=9,
40+
):
41+
"""Used for checking diatomic systems to see if the Lagrange Multipliers are working properly."""
42+
global mols
43+
xyz = "{:s} 0.0 0.0 0.0; {:s} {:.3f} 0.0 0.0".format(atom1, atom2, r)
44+
mol = gto.M(
45+
atom=xyz,
46+
basis=basis,
47+
charge=charge,
48+
spin=spin,
49+
symmetry=symmetry,
50+
verbose=0,
51+
output="/dev/null",
52+
)
53+
mols.append(mol)
54+
mf = scf.RHF(mol)
55+
if density_fit:
56+
mf = mf.density_fit(auxbasis=df.aug_etb(mol))
57+
58+
mc = mcpdft.CASSCF(mf.run(), fnal, ncas, nelecas, grids_level=grids_level)
59+
if spin is None:
60+
spin = mol.nelectron % 2
61+
62+
ss = spin * (spin + 2) * 0.25
63+
mc.fix_spin_(ss=ss, shift=2)
64+
65+
if nstates > 1:
66+
mc = mc.state_average(
67+
[
68+
1.0 / float(nstates),
69+
]
70+
* nstates,
71+
)
72+
73+
mc.conv_tol = 1e-12
74+
mc.conv_grad_tol = 1e-6
75+
mo = None
76+
if symmetry and (cas_irrep is not None):
77+
mo = mc.sort_mo_by_irrep(cas_irrep)
78+
79+
mc_grad = mc.run(mo).nuc_grad_method()
80+
mc_grad.conv_rtol = 1e-12
81+
return mc_grad
82+
83+
84+
def setUpModule():
85+
global mols, original_grids
86+
mols = []
87+
original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS
88+
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False
89+
90+
91+
def tearDownModule():
92+
global mols, diatomic, original_grids
93+
[m.stdout.close() for m in mols]
94+
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = original_grids
95+
del mols, diatomic, original_grids
96+
97+
98+
class KnownValues(unittest.TestCase):
99+
100+
def test_grad_lih_sstm06l22_sto3g(self):
101+
mc = diatomic("Li", "H", 0.8, "tM06L", "STO-3G", 2, 2, 1, grids_level=1)
102+
de = mc.kernel()[1, 0] / BOHR
103+
104+
# Numerical from this software
105+
# PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96
106+
# PySCF-forge commit: 4015363355dc691a80bc94d4b2b094318b213e36
107+
DE_REF = -1.0546009263404388
108+
109+
self.assertAlmostEqual(de, DE_REF, 5)
110+
111+
def test_grad_lih_sa2tm06l22_sto3g(self):
112+
mc = diatomic("Li", "H", 0.8, "tM06L", "STO-3G", 2, 2, 2, grids_level=1)
113+
114+
# Numerical from this software
115+
# PySCF commit: f2c2d3f963916fb64ae77241f1b44f24fa484d96
116+
# PySCF-forge commit: 4015363355dc691a80bc94d4b2b094318b213e36
117+
DE_REF = [-1.0351271000, -0.8919881992]
118+
119+
for state in range(2):
120+
with self.subTest(state=state):
121+
de = mc.kernel(state=state)[1, 0] / BOHR
122+
self.assertAlmostEqual(de, DE_REF[state], 5)
123+
124+
125+
if __name__ == "__main__":
126+
print("Full Tests for MC-PDFT gradients with meta-GGA functionals")
127+
unittest.main()

0 commit comments

Comments
 (0)