Skip to content

Commit 2e13995

Browse files
authored
Add PCM support for derivative coupling calculations (#550)
* finish add the g-e tdrhf with LR-PCM * finish tdrhf * finish rks * finish tests * fix some bugs * review the codes
1 parent 951c0cd commit 2e13995

File tree

6 files changed

+1220
-17
lines changed

6 files changed

+1220
-17
lines changed

gpu4pyscf/nac/finite_diff.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, nJ, delta=0.001, with_ris=False, singlet=Tru
214214
s = cp.asarray(s)
215215
for iatm in range(natm):
216216
for icart in range(3):
217-
print(f"iatm, icart, {iatm} {icart}")
218217
mol_add = get_new_mol(mol, coords, delta, iatm, icart)
219218
mf_add, xy_diag_add = get_mf_td(mol_add, mf, s, mo_coeff, with_ris)
220219
mol_minus = get_new_mol(mol, coords, -delta, iatm, icart)

gpu4pyscf/nac/tdrhf.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ def get_nacv_ge(td_nac, x_yI, EI, singlet=True, atmlst=None, verbose=logger.INFO
108108
nvir = nmo - nocc
109109
orbv = mo_coeff[:, nocc:]
110110
orbo = mo_coeff[:, :nocc]
111-
if getattr(mf, 'with_solvent', None) is not None:
112-
raise NotImplementedError('With solvent is not supported yet')
113111

114112
xI, yI = x_yI
115113
xI = cp.asarray(xI).reshape(nocc, nvir).T
@@ -118,7 +116,7 @@ def get_nacv_ge(td_nac, x_yI, EI, singlet=True, atmlst=None, verbose=logger.INFO
118116
yI = cp.asarray(yI).reshape(nocc, nvir).T
119117
LI = xI-yI # eq.(83) in Ref. [1]
120118

121-
vresp = mf.gen_response(singlet=None, hermi=1)
119+
vresp = td_nac.base.gen_response(singlet=None, hermi=1)
122120

123121
def fvind(x):
124122
dm = reduce(cp.dot, (orbv, x.reshape(nvir, nocc) * 2, orbo.T)) # double occupency
@@ -152,6 +150,7 @@ def fvind(x):
152150

153151
mf_grad = mf.nuc_grad_method()
154152
dmz1doo = z1aoS
153+
td_nac.dmz1doo = dmz1doo
155154
oo0 = reduce(cp.dot, (orbo, orbo.T)) * 2.0
156155

157156
if atmlst is None:
@@ -239,8 +238,6 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
239238
nvir = nmo - nocc
240239
orbv = mo_coeff[:, nocc:]
241240
orbo = mo_coeff[:, :nocc]
242-
if getattr(mf, 'with_solvent', None) is not None:
243-
raise NotImplementedError('With solvent is not supported yet')
244241

245242
xI, yI = x_yI
246243
xJ, yJ = x_yJ
@@ -262,6 +259,8 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
262259
xmyJ = (xJ - yJ)
263260
dmxpyJ = reduce(cp.dot, (orbv, xpyJ, orbo.T))
264261
dmxmyJ = reduce(cp.dot, (orbv, xmyJ, orbo.T))
262+
td_nac.dmxpyI = dmxpyI
263+
td_nac.dmxpyJ = dmxpyJ
265264

266265
rIJoo =-contract('ai,aj->ij', xJ, xI) - contract('ai,aj->ij', yI, yJ)
267266
rIJvv = contract('ai,bi->ab', xI, xJ) + contract('ai,bi->ab', yJ, yI)
@@ -297,13 +296,16 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
297296
vk2J = cp.asarray(vk2J)
298297

299298
veff0doo = vj0IJ * 2 - vk0IJ
299+
veff0doo += td_nac.solvent_response(dmzooIJ)
300300
wvo = reduce(cp.dot, (orbv.T, veff0doo, orbo)) * 2
301301
veffI = vj1I * 2 - vk1I
302+
veffI += td_nac.solvent_response(dmxpyI + dmxpyI.T)
302303
veffI *= 0.5
303304
veff0mopI = reduce(cp.dot, (mo_coeff.T, veffI, mo_coeff))
304305
wvo -= contract("ki,ai->ak", veff0mopI[:nocc, :nocc], xpyJ) * 2
305306
wvo += contract("ac,ai->ci", veff0mopI[nocc:, nocc:], xpyJ) * 2
306307
veffJ = vj1J * 2 - vk1J
308+
veffJ += td_nac.solvent_response(dmxpyJ + dmxpyJ.T)
307309
veffJ *= 0.5
308310
veff0mopJ = reduce(cp.dot, (mo_coeff.T, veffJ, mo_coeff))
309311
wvo -= contract("ki,ai->ak", veff0mopJ[:nocc, :nocc], xpyI) * 2
@@ -320,7 +322,7 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
320322
wvo += contract("ac,ai->ci", veff0momJ[nocc:, nocc:], xmyI) * 2
321323
# The up parts are according to eq. (86) and (86) in Ref. [1]
322324

323-
vresp = mf.gen_response(singlet=None, hermi=1)
325+
vresp = td_nac.base.gen_response(singlet=None, hermi=1)
324326

325327
def fvind(x):
326328
dm = reduce(cp.dot, (orbv, x.reshape(nvir, nocc) * 2, orbo.T)) # double occupency
@@ -390,6 +392,7 @@ def fvind(x):
390392
s1 = mf_grad.get_ovlp(mol)
391393
z1aoS = (z1ao + z1ao.T)*0.5* (EJ - EI)
392394
dmz1doo = z1aoS + dmzooIJ # P
395+
td_nac.dmz1doo = dmz1doo
393396
oo0 = reduce(cp.dot, (orbo, orbo.T))*2 # D
394397

395398
if atmlst is None:
@@ -492,7 +495,10 @@ class NAC(lib.StreamObject):
492495
"de",
493496
"de_scaled",
494497
"de_etf",
495-
"de_etf_scaled"
498+
"de_etf_scaled",
499+
"dmz1doo",
500+
"dmxpyI",
501+
"dmxpyJ"
496502
}
497503

498504
def __init__(self, td):
@@ -506,6 +512,9 @@ def __init__(self, td):
506512
self.de_scaled = None # CIS derivative coupling without ETF
507513
self.de_etf = None # CIS Force Matrix Element with ETF
508514
self.de_etf_scaled = None # Knwon as CIS derivative coupling with ETF
515+
self.dmz1doo = None
516+
self.dmxpyI = None
517+
self.dmxpyJ = None
509518

510519
_write = rhf_grad_cpu.GradientsBase._write
511520

@@ -683,7 +692,6 @@ def check_phase_modified(mol0, mo_coeff0, mo1_reordered, xy0, xy1, nocc, s):
683692
* xy0[idxo_l, idxv_l] * xy1[idxo_r, idxv_r] * 2
684693

685694
total_s_state += s_state_contribution
686-
print(total_s_state)
687695

688696
return total_s_state
689697

gpu4pyscf/nac/tdrks.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ def get_nacv_ge(td_nac, x_yI, EI, singlet=True, atmlst=None, verbose=logger.INFO
7171
nvir = nmo - nocc
7272
orbv = mo_coeff[:, nocc:]
7373
orbo = mo_coeff[:, :nocc]
74-
if getattr(mf, 'with_solvent', None) is not None:
75-
raise NotImplementedError('With solvent is not supported yet')
7674

7775
xI, yI = x_yI
7876
xI = cp.asarray(xI).reshape(nocc, nvir).T
@@ -85,8 +83,10 @@ def get_nacv_ge(td_nac, x_yI, EI, singlet=True, atmlst=None, verbose=logger.INFO
8583
ni.libxc.test_deriv_order(mf.xc, 3, raise_error=True)
8684
omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
8785
with_k = ni.libxc.is_hybrid_xc(mf.xc)
88-
89-
vresp = mf.gen_response(singlet=None, hermi=1)
86+
if isinstance(td_nac.base, tdscf.ris.TDDFT) or isinstance(td_nac.base, tdscf.ris.TDA):
87+
vresp = td_nac.base._scf.gen_response(singlet=None, hermi=1)
88+
else:
89+
vresp = td_nac.base.gen_response(singlet=None, hermi=1)
9090

9191
def fvind(x):
9292
dm = reduce(cp.dot, (orbv, x.reshape(nvir, nocc) * 2, orbo.T)) # double occupency
@@ -121,6 +121,7 @@ def fvind(x):
121121
mf_grad = mf.nuc_grad_method()
122122
s1 = mf_grad.get_ovlp(mol)
123123
dmz1doo = z1aoS
124+
td_nac.dmz1doo = dmz1doo
124125
oo0 = reduce(cp.dot, (orbo, orbo.T)) * 2.0
125126

126127
if atmlst is None:
@@ -242,8 +243,6 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
242243
nvir = nmo - nocc
243244
orbv = mo_coeff[:, nocc:]
244245
orbo = mo_coeff[:, :nocc]
245-
if getattr(mf, 'with_solvent', None) is not None:
246-
raise NotImplementedError('With solvent is not supported yet')
247246

248247
xI, yI = x_yI
249248
xJ, yJ = x_yJ
@@ -265,6 +264,8 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
265264
xmyJ = (xJ - yJ)
266265
dmxpyJ = reduce(cp.dot, (orbv, xpyJ, orbo.T))
267266
dmxmyJ = reduce(cp.dot, (orbv, xmyJ, orbo.T))
267+
td_nac.dmxpyI = dmxpyI
268+
td_nac.dmxpyJ = dmxpyJ
268269

269270
rIJoo =-contract('ai,aj->ij', xJ, xI) - contract('ai,aj->ij', yI, yJ)
270271
rIJvv = contract('ai,bi->ab', xI, xJ) + contract('ai,bi->ab', yJ, yI)
@@ -335,13 +336,16 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
335336
vk2J += vk2J_omega * (alpha - hyb)
336337

337338
veff0doo = vj0IJ * 2 - vk0IJ + f1ooIJ[0] + k1aoIJ[0] * 2
339+
veff0doo += td_nac.solvent_response(dmzooIJ)
338340
wvo = reduce(cp.dot, (orbv.T, veff0doo, orbo)) * 2
339341
veffI = vj1I * 2 - vk1I + f1voI[0] * 2
342+
veffI += td_nac.solvent_response(dmxpyI + dmxpyI.T)
340343
veffI *= 0.5
341344
veff0mopI = reduce(cp.dot, (mo_coeff.T, veffI, mo_coeff))
342345
wvo -= contract("ki,ai->ak", veff0mopI[:nocc, :nocc], xpyJ) * 2
343346
wvo += contract("ac,ai->ci", veff0mopI[nocc:, nocc:], xpyJ) * 2
344347
veffJ = vj1J * 2 - vk1J + f1voJ[0] * 2
348+
veffJ += td_nac.solvent_response(dmxpyJ + dmxpyJ.T)
345349
veffJ *= 0.5
346350
veff0mopJ = reduce(cp.dot, (mo_coeff.T, veffJ, mo_coeff))
347351
wvo -= contract("ki,ai->ak", veff0mopJ[:nocc, :nocc], xpyI) * 2
@@ -369,21 +373,24 @@ def get_nacv_ee(td_nac, x_yI, x_yJ, EI, EJ, singlet=True, atmlst=None, verbose=l
369373
vj1J = cp.asarray(vj1J)
370374

371375
veff0doo = vj0IJ * 2 + f1ooIJ[0] + k1aoIJ[0] * 2
376+
veff0doo += td_nac.solvent_response(dmzooIJ)
372377
wvo = reduce(cp.dot, (orbv.T, veff0doo, orbo)) * 2
373378
veffI = vj1I * 2 + f1voI[0] * 2
379+
veffI += td_nac.solvent_response(dmxpyI + dmxpyI.T)
374380
veffI *= 0.5
375381
veff0mopI = reduce(cp.dot, (mo_coeff.T, veffI, mo_coeff))
376382
wvo -= contract("ki,ai->ak", veff0mopI[:nocc, :nocc], xpyJ) * 2
377383
wvo += contract("ac,ai->ci", veff0mopI[nocc:, nocc:], xpyJ) * 2
378384
veffJ = vj1J * 2 + f1voJ[0] * 2
385+
veffJ += td_nac.solvent_response(dmxpyJ + dmxpyJ.T)
379386
veffJ *= 0.5
380387
veff0mopJ = reduce(cp.dot, (mo_coeff.T, veffJ, mo_coeff))
381388
wvo -= contract("ki,ai->ak", veff0mopJ[:nocc, :nocc], xpyI) * 2
382389
wvo += contract("ac,ai->ci", veff0mopJ[nocc:, nocc:], xpyI) * 2
383390
veff0momI = cp.zeros((nmo, nmo))
384391
veff0momJ = cp.zeros((nmo, nmo))
385392

386-
vresp = mf.gen_response(singlet=None, hermi=1)
393+
vresp = td_nac.base.gen_response(singlet=None, hermi=1)
387394

388395
def fvind(x):
389396
dm = reduce(cp.dot, (orbv, x.reshape(nvir, nocc) * 2, orbo.T)) # double occupency
@@ -453,6 +460,7 @@ def fvind(x):
453460
s1 = mf_grad.get_ovlp(mol)
454461
z1aoS = (z1ao + z1ao.T)*0.5* (EJ - EI)
455462
dmz1doo = z1aoS + dmzooIJ # P
463+
td_nac.dmz1doo = dmz1doo
456464
oo0 = reduce(cp.dot, (orbo, orbo.T))*2 # D
457465

458466
if atmlst is None:

gpu4pyscf/solvent/tdscf/pcm.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ def make_tdscf_gradient_object(td_base_method):
6262
(WithSolventTDSCFGradient, td_grad.__class__), name)
6363

6464

65+
def make_tdscf_nac_object(td_base_method):
66+
'''For td_method in vacuum, add td of solvent pcmobj'''
67+
# The nuclear gradients of stable exited states should correspond to a
68+
# fully relaxed solvent. Strictly, the TDDFT exited states should be
69+
# solved using state-specific solvent model. Even if running LR-PCM for
70+
# the zeroth order TDDFT, the wavefunction should be comptued using the
71+
# same dielectric constant as the ground state (the zero-frequency eps).
72+
with_solvent = td_base_method.with_solvent
73+
if not with_solvent.equilibrium_solvation:
74+
raise RuntimeError(
75+
'When computing derivative couplings of PCM-TDDFT, equilibrium solvation should '
76+
'be employed. The PCM TDDFT should be initialized as\n'
77+
' mf.TDDFT(equilibrium_solvation=True)')
78+
td_nac = td_base_method.undo_solvent().nac_method()
79+
td_nac.base = td_base_method
80+
name = with_solvent.__class__.__name__ + td_nac.__class__.__name__
81+
return lib.set_class(WithSolventTDSCFNacMethod(td_nac),
82+
(WithSolventTDSCFNacMethod, td_nac.__class__), name)
83+
84+
6585
class WithSolventTDSCF:
6686
from gpu4pyscf.lib.utils import to_gpu, device
6787

@@ -134,6 +154,7 @@ def undo_solvent(self):
134154

135155
nuc_grad_method = make_tdscf_gradient_object
136156
Gradients = nuc_grad_method
157+
nac_method = make_tdscf_nac_object
137158

138159

139160
class WithSolventTDSCFGradient:
@@ -180,3 +201,94 @@ def grad_elec(self, xy, singlet=None, atmlst=None, verbose=logger.INFO):
180201
de += grad_solver(pcmobj, dmxpy, v_grids=v_grids, v_grids_l=v_grids, q=q) * 2.0
181202

182203
return de
204+
205+
206+
class WithSolventTDSCFNacMethod:
207+
from gpu4pyscf.lib.utils import to_gpu, device
208+
209+
def __init__(self, tda_grad_method):
210+
self.__dict__.update(tda_grad_method.__dict__)
211+
212+
def undo_solvent(self):
213+
cls = self.__class__
214+
name_mixin = self.base.with_solvent.__class__.__name__
215+
obj = lib.view(self, lib.drop_class(cls, WithSolventTDSCFNacMethod, name_mixin))
216+
del obj.with_solvent
217+
return obj
218+
219+
def solvent_response(self, dm):
220+
return self.base.with_solvent._B_dot_x(dm)*2.0
221+
222+
def get_nacv_ge(self, xy, EI, singlet=None, atmlst=None, verbose=logger.INFO):
223+
if self.base.with_solvent.frozen:
224+
raise RuntimeError('Frozen solvent model is not supported')
225+
226+
de_tuple = super().get_nacv_ge(xy, EI, singlet, atmlst, verbose)
227+
de, de_scaled, de_etf, de_etf_scaled = de_tuple
228+
229+
dm = self.base._scf.make_rdm1(ao_repr=True)
230+
if dm.ndim == 3:
231+
dm = dm[0] + dm[1]
232+
dmP = self.dmz1doo #1.0 * (self.dmz1doo + self.dmz1doo.T)
233+
pcmobj = self.base.with_solvent
234+
assert pcmobj.equilibrium_solvation
235+
236+
de_modify = 0
237+
q_sym_dm = pcmobj._get_qsym(dm, with_nuc = True)[0]
238+
qE_sym_dmP = pcmobj._get_qsym(dmP)[0]
239+
de_modify += grad_qv(pcmobj, dm, q_sym = qE_sym_dmP)
240+
de_modify += grad_nuc(pcmobj, dm, q_sym = qE_sym_dmP.get())
241+
de_modify += grad_qv(pcmobj, dmP, q_sym = q_sym_dm)
242+
v_grids_l = pcmobj._get_vgrids(dmP, with_nuc = False)[0]
243+
de_modify += grad_solver(pcmobj, dm, v_grids_l = v_grids_l) * 2.0
244+
245+
de += de_modify
246+
de_scaled = de/EI
247+
de_etf += de_modify
248+
de_etf_scaled = de_etf/EI
249+
250+
return de, de_scaled, de_etf, de_etf_scaled
251+
252+
def get_nacv_ee(self, x_yI, x_yJ, EI, EJ, singlet=None, atmlst=None, verbose=logger.INFO):
253+
if self.base.with_solvent.frozen:
254+
raise RuntimeError('Frozen solvent model is not supported')
255+
256+
de_tuple = super().get_nacv_ee(x_yI, x_yJ, EI, EJ, singlet, atmlst, verbose)
257+
de, de_scaled, de_etf, de_etf_scaled = de_tuple
258+
259+
dm = self.base._scf.make_rdm1(ao_repr=True)
260+
if dm.ndim == 3:
261+
dm = dm[0] + dm[1]
262+
dmP = 0.5 * (self.dmz1doo + self.dmz1doo.T)
263+
dmxpyI = self.dmxpyI + self.dmxpyI.T
264+
dmxpyJ = self.dmxpyJ + self.dmxpyJ.T
265+
pcmobj = self.base.with_solvent
266+
assert pcmobj.equilibrium_solvation
267+
268+
de_modify = 0.0
269+
q_sym_dm = pcmobj._get_qsym(dm, with_nuc = True)[0]
270+
qE_sym_dmP = pcmobj._get_qsym(dmP)[0]
271+
qE_sym_dmxpyI = pcmobj._get_qsym(dmxpyI)[0]
272+
qE_sym_dmxpyJ = pcmobj._get_qsym(dmxpyJ)[0]
273+
de_modify += grad_qv(pcmobj, dm, q_sym = qE_sym_dmP)
274+
de_modify += grad_nuc(pcmobj, dm, q_sym = qE_sym_dmP.get())
275+
de_modify += grad_qv(pcmobj, dmP, q_sym = q_sym_dm)
276+
v_grids_l = pcmobj._get_vgrids(dmP, with_nuc = False)[0]
277+
de_modify += grad_solver(pcmobj, dm, v_grids_l = v_grids_l) * 2.0
278+
279+
de_modify += grad_qv(pcmobj, dmxpyJ, q_sym = qE_sym_dmxpyI)
280+
de_modify += grad_qv(pcmobj, dmxpyI, q_sym = qE_sym_dmxpyJ)
281+
282+
v_gridsJ = pcmobj._get_vgrids(dmxpyJ, with_nuc = False)[0]
283+
v_gridsI = pcmobj._get_vgrids(dmxpyI, with_nuc = False)[0]
284+
qI = pcmobj._get_qsym(dmxpyI, with_nuc = False)[1]
285+
qJ = pcmobj._get_qsym(dmxpyJ, with_nuc = False)[1]
286+
de_modify += grad_solver(pcmobj, dmxpyJ, v_grids=v_gridsI, v_grids_l=v_gridsJ, q=qI)
287+
de_modify += grad_solver(pcmobj, dmxpyI, v_grids=v_gridsJ, v_grids_l=v_gridsI, q=qJ)
288+
289+
de = de + de_modify
290+
de_scaled = de/(EJ-EI)
291+
de_etf = de_etf + de_modify
292+
de_etf_scaled = de_etf/(EJ-EI)
293+
294+
return de, de_scaled, de_etf, de_etf_scaled

0 commit comments

Comments
 (0)