Skip to content

Commit bf4795c

Browse files
authored
Improve the RSH treatment in DFT (pyscf#2525)
* Compute LR or SR K matrices directly for certain XC functionals * Fix libxc.rsh_coeff parser for custom RSH such as "hse06+HF" * Better SR/LR treatments for RSH in TDDFT and scf/_response_fuctions.py * Remove TDDFT code that were never used * Fix various issues for previous commits and update tests * Fix tests * Drop invalid test * Fix pbc.tdscf tests and update docstrings * Improve TDDFT eigen solver * Update _lr_eig * error introduced in previous commit * Bugfix and optimization for lr_eig * Restore compatibility between lr_eig and real_eig * Fix tdscf symmetric_orth function for complex vectors * Reduce numerical errors in TDHF diagonalization * Dimension check
1 parent 9a0bb6d commit bf4795c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+821
-5582
lines changed

examples/pbc/11-gamma_point_all_electron_scf.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
'''
44
Gamma point Hartree-Fock/DFT for all-electron calculation
55
6-
The default FFT-based 2-electron integrals may not be accurate enough for
7-
all-electron calculation. It's recommended to use MDF (mixed density fitting)
8-
technique to improve the accuracy.
9-
106
See also
117
examples/df/00-with_df.py
128
examples/df/01-auxbasis.py
@@ -33,11 +29,6 @@
3329
mf = scf.RHF(cell).density_fit()
3430
mf.kernel()
3531

36-
# Mixed density fitting is another option for all-electron calculations
37-
mf = scf.RHF(cell).mix_density_fit()
38-
mf.with_df.mesh = [10]*3 # Tune #PWs in MDF for performance/accuracy balance
39-
mf.kernel()
40-
4132
# Or use even-tempered Gaussian basis as auxiliary fitting functions.
4233
# The following auxbasis is generated based on the expression
4334
# alpha = a * 1.7^i i = 0..N

examples/pbc/12-gamma_point_post_hf.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
verbose = 4,
2424
)
2525

26-
mf = scf.RHF(cell).density_fit()
27-
mf.with_df.mesh = [10]*3
28-
mf.kernel()
26+
mf = scf.RHF(cell).density_fit().run()
2927

3028
#
3129
# Import CC, TDDFT module from the molecular implementations

pyscf/dft/gks.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,36 +89,40 @@ def get_veff(ks, mol=None, dm=None, dm_last=0, vhf_last=0, hermi=1):
8989
logger.debug(ks, 'nelec with nlc grids = %s', n)
9090
t0 = logger.timer(ks, 'vxc', *t0)
9191

92+
incremental_jk = (ks._eri is None and ks.direct_scf and
93+
getattr(vhf_last, 'vj', None) is not None)
94+
if incremental_jk:
95+
_dm = numpy.asarray(dm) - numpy.asarray(dm_last)
96+
else:
97+
_dm = dm
9298
if not ni.libxc.is_hybrid_xc(ks.xc):
9399
vk = None
94-
if (ks._eri is None and ks.direct_scf and
95-
getattr(vhf_last, 'vj', None) is not None):
96-
ddm = numpy.asarray(dm) - numpy.asarray(dm_last)
97-
vj = ks.get_j(mol, ddm, hermi)
100+
vj = ks.get_j(mol, _dm, hermi)
101+
if incremental_jk:
98102
vj += vhf_last.vj
99-
else:
100-
vj = ks.get_j(mol, dm, hermi)
101103
vxc += vj
102104
else:
103105
omega, alpha, hyb = ni.rsh_and_hybrid_coeff(ks.xc, spin=mol.spin)
104-
if (ks._eri is None and ks.direct_scf and
105-
getattr(vhf_last, 'vk', None) is not None):
106-
ddm = numpy.asarray(dm) - numpy.asarray(dm_last)
107-
vj, vk = ks.get_jk(mol, ddm, hermi)
106+
if omega == 0:
107+
vj, vk = ks.get_jk(mol, _dm, hermi)
108+
vk *= hyb
109+
elif alpha == 0: # LR=0, only SR exchange
110+
vj = ks.get_j(mol, _dm, hermi)
111+
vk = ks.get_k(mol, _dm, hermi, omega=-omega)
108112
vk *= hyb
109-
if omega != 0:
110-
vklr = ks.get_k(mol, ddm, hermi, omega=omega)
111-
vklr *= (alpha - hyb)
112-
vk += vklr
113+
elif hyb == 0: # SR=0, only LR exchange
114+
vj = ks.get_j(mol, _dm, hermi)
115+
vk = ks.get_k(mol, _dm, hermi, omega=omega)
116+
vk *= alpha
117+
else: # SR and LR exchange with different ratios
118+
vj, vk = ks.get_jk(mol, _dm, hermi)
119+
vk *= hyb
120+
vklr = ks.get_k(mol, _dm, hermi, omega=omega)
121+
vklr *= (alpha - hyb)
122+
vk += vklr
123+
if incremental_jk:
113124
vj += vhf_last.vj
114125
vk += vhf_last.vk
115-
else:
116-
vj, vk = ks.get_jk(mol, dm, hermi)
117-
vk *= hyb
118-
if omega != 0:
119-
vklr = ks.get_k(mol, dm, hermi, omega=omega)
120-
vklr *= (alpha - hyb)
121-
vk += vklr
122126
vxc += vj - vk
123127

124128
if ground_state:

pyscf/dft/libxc.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,14 @@ def rsh_coeff(xc_code):
410410
if 'SR_HF' in xc_code or 'LR_HF' in xc_code or 'RSH(' in xc_code:
411411
check_omega = False
412412

413-
hyb, fn_facs = parse_xc(xc_code)
414-
hyb, alpha, omega = hyb
415-
beta = hyb - alpha
413+
(hyb, alpha, omega), fn_facs = parse_xc(xc_code)
414+
if omega == 0:
415+
# SR and LR Coulomb share the same coefficients
416+
# Note: this change breaks compatibility with pyscf-2.7
417+
assert hyb == alpha
418+
beta = 0.
419+
else:
420+
beta = hyb - alpha
416421
rsh_pars = [omega, alpha, beta]
417422
rsh_tmp = (ctypes.c_double*3)()
418423
for xid, fac in fn_facs:
@@ -639,8 +644,6 @@ def possible_c_for(key):
639644
# dftd3 cannot be used in a custom xc description
640645
assert '-d3' not in token
641646
parse_token(token, 'compound XC', search_xc_alias=True)
642-
if hyb[2] == 0: # No omega is assigned. LR_HF is 0 for normal Coulomb operator
643-
hyb[1] = 0
644647
return tuple(hyb), tuple(remove_dup(fn_facs))
645648

646649
_NAME_WITH_DASH = {'SR-HF' : 'SR_HF',

pyscf/dft/numint.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2717,9 +2717,12 @@ def rsh_and_hybrid_coeff(self, xc_code, spin=0):
27172717
'''
27182718
omega, alpha, beta = self.rsh_coeff(xc_code)
27192719
if self.omega is not None:
2720+
if omega == 0 and self.omega != 0:
2721+
raise RuntimeError(f'Not support assigning omega={self.omega}. '
2722+
f'{xc_code} is not a RSH functional')
27202723
omega = self.omega
27212724

2722-
if abs(omega) > 1e-10:
2725+
if omega != 0:
27232726
hyb = alpha + beta
27242727
else:
27252728
hyb = self.hybrid_coeff(xc_code, spin)

pyscf/dft/rks.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,36 +92,40 @@ def get_veff(ks, mol=None, dm=None, dm_last=0, vhf_last=0, hermi=1):
9292
logger.debug(ks, 'nelec with nlc grids = %s', n)
9393
t0 = logger.timer(ks, 'vxc', *t0)
9494

95+
incremental_jk = (ks._eri is None and ks.direct_scf and
96+
getattr(vhf_last, 'vj', None) is not None)
97+
if incremental_jk:
98+
_dm = numpy.asarray(dm) - numpy.asarray(dm_last)
99+
else:
100+
_dm = dm
95101
if not ni.libxc.is_hybrid_xc(ks.xc):
96102
vk = None
97-
if (ks._eri is None and ks.direct_scf and
98-
getattr(vhf_last, 'vj', None) is not None):
99-
ddm = numpy.asarray(dm) - numpy.asarray(dm_last)
100-
vj = ks.get_j(mol, ddm, hermi)
103+
vj = ks.get_j(mol, _dm, hermi)
104+
if incremental_jk:
101105
vj += vhf_last.vj
102-
else:
103-
vj = ks.get_j(mol, dm, hermi)
104106
vxc += vj
105107
else:
106108
omega, alpha, hyb = ni.rsh_and_hybrid_coeff(ks.xc, spin=mol.spin)
107-
if (ks._eri is None and ks.direct_scf and
108-
getattr(vhf_last, 'vk', None) is not None):
109-
ddm = numpy.asarray(dm) - numpy.asarray(dm_last)
110-
vj, vk = ks.get_jk(mol, ddm, hermi)
109+
if omega == 0:
110+
vj, vk = ks.get_jk(mol, _dm, hermi)
111+
vk *= hyb
112+
elif alpha == 0: # LR=0, only SR exchange
113+
vj = ks.get_j(mol, _dm, hermi)
114+
vk = ks.get_k(mol, _dm, hermi, omega=-omega)
111115
vk *= hyb
112-
if omega != 0: # For range separated Coulomb
113-
vklr = ks.get_k(mol, ddm, hermi, omega=omega)
114-
vklr *= (alpha - hyb)
115-
vk += vklr
116+
elif hyb == 0: # SR=0, only LR exchange
117+
vj = ks.get_j(mol, _dm, hermi)
118+
vk = ks.get_k(mol, _dm, hermi, omega=omega)
119+
vk *= alpha
120+
else: # SR and LR exchange with different ratios
121+
vj, vk = ks.get_jk(mol, _dm, hermi)
122+
vk *= hyb
123+
vklr = ks.get_k(mol, _dm, hermi, omega=omega)
124+
vklr *= (alpha - hyb)
125+
vk += vklr
126+
if incremental_jk:
116127
vj += vhf_last.vj
117128
vk += vhf_last.vk
118-
else:
119-
vj, vk = ks.get_jk(mol, dm, hermi)
120-
vk *= hyb
121-
if omega != 0:
122-
vklr = ks.get_k(mol, dm, hermi, omega=omega)
123-
vklr *= (alpha - hyb)
124-
vk += vklr
125129
vxc += vj - vk * .5
126130

127131
if ground_state:

pyscf/dft/test/test_gks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_ncol_gks_lda_omega(self):
6060
self.assertAlmostEqual(eks4, -75.883375491657, 6)
6161

6262
mf = mol.GKS()
63-
mf.xc = 'lda + .2*HF'
63+
mf.xc = 'lda + .2*SR_HF(0.3)'
6464
mf.collinear = 'ncol'
6565
mf.omega = .5
6666
eks4 = mf.kernel()

pyscf/dft/test/test_grids.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ def tearDownModule():
4444
class KnownValues(unittest.TestCase):
4545
@classmethod
4646
def setUpClass(cls):
47-
radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False
47+
cls.original_grids = dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS
48+
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = False
49+
50+
@classmethod
51+
def tearDownClass(cls):
52+
dft.radi.ATOM_SPECIFIC_TREUTLER_GRIDS = cls.original_grids
4853

4954
def test_gen_grid(self):
5055
grid = gen_grid.Grids(h2o)

pyscf/dft/test/test_he.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ def test_nr_b3lypg(self):
8585
m.xc = 'b3lyp5'
8686
self.assertAlmostEqual(m.scf(), -2.89992555753, 9)
8787

88+
def test_camb3lyp(self):
89+
self.assertAlmostEqual(mol.RKS(xc='camb3lyp').kernel(), -2.89299475730048, 9)
90+
self.assertAlmostEqual(mol.GKS(xc='camb3lyp').kernel(), -2.89299475730048, 9)
91+
self.assertAlmostEqual(mol.UKS(xc='camb3lyp').kernel(), -2.89299475730048, 9)
92+
93+
def test_wb97(self):
94+
self.assertAlmostEqual(mol.RKS(xc='wb97').kernel(), -2.89430888240579, 9)
95+
self.assertAlmostEqual(mol.GKS(xc='wb97').kernel(), -2.89430888240579, 9)
96+
self.assertAlmostEqual(mol.UKS(xc='wb97').kernel(), -2.89430888240579, 9)
97+
# The old way to compute RSH, short-range = full-range - long-range
98+
xc = 'wb97 + 1e-9*HF'
99+
self.assertAlmostEqual(mol.RKS(xc=xc).kernel(), -2.89430888240579, 8)
100+
101+
def test_hse(self):
102+
self.assertAlmostEqual(mol.RKS(xc='hse06').kernel(), -2.88908568982727, 9)
103+
self.assertAlmostEqual(mol.GKS(xc='hse06').kernel(), -2.88908568982727, 9)
104+
self.assertAlmostEqual(mol.UKS(xc='hse06').kernel(), -2.88908568982727, 9)
105+
# The old way to compute RSH, short-range = full-range - long-range
106+
xc = 'hse06 + 1e-9*HF'
107+
self.assertAlmostEqual(mol.RKS(xc=xc).kernel(), -2.88908568982727, 8)
108+
88109
def test_nr_lda_1e(self):
89110
mf = dft.RKS(mol1).run()
90111
self.assertAlmostEqual(mf.e_tot, -1.936332393935281, 9)

pyscf/dft/test/test_libxc.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,22 @@ def test_parse_xc(self):
7373
self.assertAlmostEqual(hyb, .15, 12)
7474

7575
hyb, fn_facs = dft.libxc.parse_xc('0.6*CAM_B3LYP+0.4*B3P86V5')
76-
self.assertTrue(numpy.allclose(hyb, (.08, 0, 0)))
76+
self.assertTrue(numpy.allclose(hyb, (.08, .08, 0)))
7777
self.assertTrue(numpy.allclose(fn_facs,
7878
((433, 0.6), (1, 0.032), (106, 0.288), (132, 0.324), (7, 0.076))))
7979
rsh = dft.libxc.rsh_coeff('0.6*CAM_B3LYP+0.4*B3P86V5')
80-
self.assertTrue(numpy.allclose(rsh, (0.33, 0.39, -0.196)))
80+
rsh1 = dft.libxc.rsh_coeff('CAM_B3LYP')
81+
hyb = dft.libxc.hybrid_coeff('B3P86V5')
82+
self.assertTrue(numpy.allclose(rsh, (0.33, 0.6*rsh1[1]+.4*hyb, 0.6*rsh1[2])))
8183

8284
hyb, fn_facs = dft.libxc.parse_xc('0.4*B3P86V5+0.6*CAM_B3LYP')
83-
self.assertTrue(numpy.allclose(hyb, (.08, 0, 0)))
85+
self.assertTrue(numpy.allclose(hyb, (.08, .08, 0)))
8486
self.assertTrue(numpy.allclose(fn_facs,
8587
((1, 0.032), (106, 0.288), (132, 0.324), (7, 0.076), (433, 0.6))))
8688
rsh = dft.libxc.rsh_coeff('0.4*B3P86V5+0.6*CAM_B3LYP')
87-
self.assertTrue(numpy.allclose(rsh, (0.33, 0.39, -0.196)))
89+
rsh1 = dft.libxc.rsh_coeff('CAM_B3LYP')
90+
hyb = dft.libxc.hybrid_coeff('B3P86V5')
91+
self.assertTrue(numpy.allclose(rsh, (0.33, 0.6*rsh1[1]+.4*hyb, 0.6*rsh1[2])))
8892

8993
hyb, fn_facs = dft.libxc.parse_xc('0.5*SR-HF(0.3) + .8*HF + .22*LR_HF')
9094
self.assertEqual(hyb, (1.3, 1.02, 0.3))
@@ -103,9 +107,18 @@ def test_parse_xc(self):
103107
self.assertEqual(hyb, (1.3, 1.02, 0.3))
104108
self.assertEqual(fn_facs, ((106, 0.5), (132, 0.5)))
105109

110+
rsh = dft.libxc.rsh_coeff('0.5*HSE06+.001*HF')
111+
self.assertTrue(numpy.allclose(rsh, (0.11, .001, 0.125)))
112+
113+
rsh = dft.libxc.rsh_coeff('0.5*wb97+0.001*HF')
114+
self.assertTrue(numpy.allclose(rsh, (0.4, 0.501, -.5)))
115+
106116
self.assertRaises(ValueError, dft.libxc.parse_xc, 'SR_HF(0.3) + LR_HF(.5)')
107117
self.assertRaises(ValueError, dft.libxc.parse_xc, 'LR-HF(0.3) + SR-HF(.5)')
108118

119+
hyb = dft.libxc.hybrid_coeff('0.5*B3LYP+0.2*HF')
120+
self.assertAlmostEqual(hyb, .3, 12)
121+
109122
hyb = dft.libxc.hybrid_coeff('M05')
110123
self.assertAlmostEqual(hyb, 0.28, 9)
111124

@@ -119,7 +132,7 @@ def test_parse_xc(self):
119132
#self.assertEqual(fn_facs, [(50, 1)])
120133

121134
hyb, fn_facs = dft.libxc.parse_xc("9.999e-5*HF,")
122-
self.assertEqual(hyb, (9.999e-5, 0, 0))
135+
self.assertEqual(hyb, (9.999e-5, 9.999e-5, 0))
123136

124137
ref = ((1, 1), (7, 1))
125138
self.assertEqual(dft.libxc.parse_xc_name('LDA,VWN'), (1,7))
@@ -218,6 +231,26 @@ def test_lyp(self):
218231
# rho_b = numpy.array([[4.53272893e-06, 4.18968775e-06,-2.83034672e-06, 2.61832978e-06, 5.63360737e-06, 8.97541777e-07]]).T
219232
# e, v = dft.libxc.eval_xc('tpss,', (rho_a, rho_b), spin=1, deriv=1)[:2]
220233

234+
#TDOO: enable this test when https://gitlab.com/libxc/libxc/-/issues/561 is solved
235+
@unittest.skip('hse03 and hse06 fxc have large numerical errors in Libxc')
236+
def test_hse06(self):
237+
ni = dft.numint.NumInt()
238+
rho = numpy.array([.235, 1.5e-9, 2e-9, 1e-9])[:,None]
239+
xc = 'hse06'
240+
fxc1 = ni.eval_xc_eff(xc, rho, deriv=2, xctype='GGA')[2]
241+
rho = numpy.array([rho*.5]*2)
242+
fxc2 = ni.eval_xc_eff(xc, rho, deriv=2, xctype='GGA')[2]
243+
fxc2 = (fxc2[0,:,0] + fxc2[0,:,1])/2
244+
self.assertAlmostEqual(abs(fxc2 - fxc1).max(), 0, 12)
245+
246+
rho = numpy.array([.235, 0, 0, 0])[:,None]
247+
xc = 'hse06'
248+
fxc1 = ni.eval_xc_eff(xc, rho, deriv=2, xctype='GGA')[2]
249+
rho = numpy.array([rho*.5]*2)
250+
fxc2 = ni.eval_xc_eff(xc, rho, deriv=2, xctype='GGA')[2]
251+
fxc2 = (fxc2[0,:,0] + fxc2[0,:,1])/2
252+
self.assertAlmostEqual(abs(fxc2 - fxc1).max(), 0, 12)
253+
221254
def test_define_xc_gga(self):
222255
def eval_xc(xc_code, rho, spin=0, relativity=0, deriv=1, omega=None, verbose=None):
223256
# A fictitious XC functional to demonstrate the usage

0 commit comments

Comments
 (0)