#!/usr/bin/env python
# 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.
'''
Analytical nuclear hessian for 1-electron spin-free x2c method
Ref.
JCP 135, 244104 (2011); DOI:10.1063/1.3667202
JCTC 8, 2617 (2012); DOI:10.1021/ct300127e
'''
from functools import reduce
import numpy
import scipy.linalg
from pyscf import lib
from pyscf import gto
from pyscf.x2c import x2c
from pyscf.x2c import sfx2c1e_grad
[docs]
def hcore_hess_generator(x2cobj, mol=None):
'''nuclear gradients of 1-component X2c hcore Hamiltonian (spin-free part only)
'''
if mol is None: mol = x2cobj.mol
xmol, contr_coeff = x2cobj.get_xmol(mol)
if x2cobj.basis is not None:
s22 = xmol.intor_symmetric('int1e_ovlp')
s21 = gto.intor_cross('int1e_ovlp', xmol, mol)
contr_coeff = lib.cho_solve(s22, s21)
get_h1_xmol = gen_sf_hfw(xmol, x2cobj.approx)
def hcore_deriv(ia, ja):
h1 = get_h1_xmol(ia, ja)
if contr_coeff is not None:
h1 = lib.einsum('pi,xypq,qj->xyij', contr_coeff, h1, contr_coeff)
return numpy.asarray(h1)
return hcore_deriv
[docs]
def gen_sf_hfw(mol, approx='1E'):
approx = approx.upper()
c = lib.param.LIGHT_SPEED
h0, s0 = sfx2c1e_grad._get_h0_s0(mol)
e0, c0 = scipy.linalg.eigh(h0, s0)
c0[:,c0[1]<0] *= -1
aoslices = mol.aoslice_by_atom()
nao = mol.nao_nr()
if 'ATOM' in approx:
x0 = numpy.zeros((nao,nao))
for ia in range(mol.natm):
ish0, ish1, p0, p1 = aoslices[ia]
shls_slice = (ish0, ish1, ish0, ish1)
t1 = mol.intor('int1e_kin', shls_slice=shls_slice)
s1 = mol.intor('int1e_ovlp', shls_slice=shls_slice)
with mol.with_rinv_at_nucleus(ia):
z = -mol.atom_charge(ia)
v1 = z * mol.intor('int1e_rinv', shls_slice=shls_slice)
w1 = z * mol.intor('int1e_prinvp', shls_slice=shls_slice)
x0[p0:p1,p0:p1] = x2c._x2c1e_xmatrix(t1, v1, w1, s1, c)
else:
cl0 = c0[:nao,nao:]
cs0 = c0[nao:,nao:]
x0 = scipy.linalg.solve(cl0.T, cs0.T).T
t0x0 = numpy.dot(s0[nao:,nao:], x0)
s_nesc0 = s0[:nao,:nao] + numpy.dot(x0.T, t0x0)
w_s, v_s = scipy.linalg.eigh(s0[:nao,:nao])
w_sqrt = numpy.sqrt(w_s)
s_nesc0_vbas = reduce(numpy.dot, (v_s.T, s_nesc0, v_s))
R0_mid = numpy.einsum('i,ij,j->ij', 1./w_sqrt, s_nesc0_vbas, 1./w_sqrt)
wr0, vr0 = scipy.linalg.eigh(R0_mid)
wr0_sqrt = numpy.sqrt(wr0)
# R0 in v_s basis
R0 = numpy.dot(vr0/wr0_sqrt, vr0.T)
R0 *= w_sqrt
R0 /= w_sqrt[:,None]
# Transform R0 back
R0 = reduce(numpy.dot, (v_s, R0, v_s.T))
R0 = x2c._get_r(s0[:nao,:nao], s_nesc0)
c_fw0 = numpy.vstack((R0, numpy.dot(x0, R0)))
h0_fw_half = numpy.dot(h0, c_fw0)
epq = e0[:,None] - e0
degen_mask = abs(epq) < 1e-7
epq[degen_mask] = 1e200
s2aa = mol.intor('int1e_ipipovlp', comp=9).reshape(3,3,nao,nao)
t2aa = mol.intor('int1e_ipipkin', comp=9).reshape(3,3,nao,nao)
v2aa = mol.intor('int1e_ipipnuc', comp=9).reshape(3,3,nao,nao)
w2aa = mol.intor('int1e_ipippnucp', comp=9).reshape(3,3,nao,nao)
s2ab = mol.intor('int1e_ipovlpip', comp=9).reshape(3,3,nao,nao)
t2ab = mol.intor('int1e_ipkinip', comp=9).reshape(3,3,nao,nao)
v2ab = mol.intor('int1e_ipnucip', comp=9).reshape(3,3,nao,nao)
w2ab = mol.intor('int1e_ippnucpip', comp=9).reshape(3,3,nao,nao)
n2 = nao * 2
h2ao = numpy.zeros((3,3,n2,n2), dtype=v2aa.dtype)
s2ao = numpy.zeros((3,3,n2,n2), dtype=v2aa.dtype)
get_h1_etc = sfx2c1e_grad._gen_first_order_quantities(mol, e0, c0, x0, approx)
def hcore_deriv(ia, ja):
ish0, ish1, i0, i1 = aoslices[ia]
jsh0, jsh1, j0, j1 = aoslices[ja]
s2cc = numpy.zeros_like(s2aa)
t2cc = numpy.zeros_like(s2aa)
v2cc = numpy.zeros_like(s2aa)
w2cc = numpy.zeros_like(s2aa)
if ia == ja:
with mol.with_rinv_origin(mol.atom_coord(ia)):
z = mol.atom_charge(ia)
rinv2aa = z*mol.intor('int1e_ipiprinv', comp=9).reshape(3,3,nao,nao)
rinv2ab = z*mol.intor('int1e_iprinvip', comp=9).reshape(3,3,nao,nao)
prinvp2aa = z*mol.intor('int1e_ipipprinvp', comp=9).reshape(3,3,nao,nao)
prinvp2ab = z*mol.intor('int1e_ipprinvpip', comp=9).reshape(3,3,nao,nao)
s2cc[:,:,i0:i1 ] = s2aa[:,:,i0:i1 ]
s2cc[:,:,i0:i1,j0:j1]+= s2ab[:,:,i0:i1,j0:j1]
t2cc[:,:,i0:i1 ] = t2aa[:,:,i0:i1 ]
t2cc[:,:,i0:i1,j0:j1]+= t2ab[:,:,i0:i1,j0:j1]
v2cc -= rinv2aa + rinv2ab
v2cc[:,:,i0:i1 ]+= v2aa[:,:,i0:i1 ]
v2cc[:,:,i0:i1,j0:j1]+= v2ab[:,:,i0:i1,j0:j1]
v2cc[:,:,i0:i1 ]+= rinv2aa[:,:,i0:i1]
v2cc[:,:,i0:i1 ]+= rinv2ab[:,:,i0:i1]
v2cc[:,:,: ,i0:i1]+= rinv2aa[:,:,i0:i1].transpose(0,1,3,2)
v2cc[:,:,: ,i0:i1]+= rinv2ab[:,:,:,i0:i1]
w2cc -= prinvp2aa + prinvp2ab
w2cc[:,:,i0:i1 ]+= w2aa[:,:,i0:i1 ]
w2cc[:,:,i0:i1,j0:j1]+= w2ab[:,:,i0:i1,j0:j1]
w2cc[:,:,i0:i1 ]+= prinvp2aa[:,:,i0:i1]
w2cc[:,:,i0:i1 ]+= prinvp2ab[:,:,i0:i1]
w2cc[:,:,: ,i0:i1]+= prinvp2aa[:,:,i0:i1].transpose(0,1,3,2)
w2cc[:,:,: ,i0:i1]+= prinvp2ab[:,:,:,i0:i1]
else:
s2cc[:,:,i0:i1,j0:j1] = s2ab[:,:,i0:i1,j0:j1]
t2cc[:,:,i0:i1,j0:j1] = t2ab[:,:,i0:i1,j0:j1]
v2cc[:,:,i0:i1,j0:j1] = v2ab[:,:,i0:i1,j0:j1]
w2cc[:,:,i0:i1,j0:j1] = w2ab[:,:,i0:i1,j0:j1]
zi = mol.atom_charge(ia)
zj = mol.atom_charge(ja)
with mol.with_rinv_at_nucleus(ia):
shls_slice = (jsh0, jsh1, 0, mol.nbas)
rinv2aa = mol.intor('int1e_ipiprinv', comp=9, shls_slice=shls_slice)
rinv2ab = mol.intor('int1e_iprinvip', comp=9, shls_slice=shls_slice)
prinvp2aa = mol.intor('int1e_ipipprinvp', comp=9, shls_slice=shls_slice)
prinvp2ab = mol.intor('int1e_ipprinvpip', comp=9, shls_slice=shls_slice)
rinv2aa = zi * rinv2aa.reshape(3,3,j1-j0,nao)
rinv2ab = zi * rinv2ab.reshape(3,3,j1-j0,nao)
prinvp2aa = zi * prinvp2aa.reshape(3,3,j1-j0,nao)
prinvp2ab = zi * prinvp2ab.reshape(3,3,j1-j0,nao)
v2cc[:,:,j0:j1] += rinv2aa
v2cc[:,:,j0:j1] += rinv2ab.transpose(1,0,2,3)
w2cc[:,:,j0:j1] += prinvp2aa
w2cc[:,:,j0:j1] += prinvp2ab.transpose(1,0,2,3)
with mol.with_rinv_at_nucleus(ja):
shls_slice = (ish0, ish1, 0, mol.nbas)
rinv2aa = mol.intor('int1e_ipiprinv', comp=9, shls_slice=shls_slice)
rinv2ab = mol.intor('int1e_iprinvip', comp=9, shls_slice=shls_slice)
prinvp2aa = mol.intor('int1e_ipipprinvp', comp=9, shls_slice=shls_slice)
prinvp2ab = mol.intor('int1e_ipprinvpip', comp=9, shls_slice=shls_slice)
rinv2aa = zj * rinv2aa.reshape(3,3,i1-i0,nao)
rinv2ab = zj * rinv2ab.reshape(3,3,i1-i0,nao)
prinvp2aa = zj * prinvp2aa.reshape(3,3,i1-i0,nao)
prinvp2ab = zj * prinvp2ab.reshape(3,3,i1-i0,nao)
v2cc[:,:,i0:i1] += rinv2aa
v2cc[:,:,i0:i1] += rinv2ab
w2cc[:,:,i0:i1] += prinvp2aa
w2cc[:,:,i0:i1] += prinvp2ab
s2cc = s2cc + s2cc.transpose(0,1,3,2)
t2cc = t2cc + t2cc.transpose(0,1,3,2)
v2cc = v2cc + v2cc.transpose(0,1,3,2)
w2cc = w2cc + w2cc.transpose(0,1,3,2)
h2ao[:,:,:nao,:nao] = v2cc
h2ao[:,:,:nao,nao:] = t2cc
h2ao[:,:,nao:,:nao] = t2cc
h2ao[:,:,nao:,nao:] = w2cc * (.25/c**2) - t2cc
s2ao[:,:,:nao,:nao] = s2cc
s2ao[:,:,nao:,nao:] = t2cc * (.5/c**2)
h1i, s1i, e1i, c1i, x1i, s_nesc1i, R1i, c_fw1i = get_h1_etc(ia)
h1j, s1j, e1j, c1j, x1j, s_nesc1j, R1j, c_fw1j = get_h1_etc(ja)
if 'ATOM' not in approx:
f2 = lib.einsum('xypq,qj->xypj', h2ao, c0[:,nao:])
f2+= lib.einsum('xpq,yqj->xypj', h1i, c1j)
f2+= lib.einsum('ypq,xqj->xypj', h1j, c1i)
sc2 = lib.einsum('xypq,qj->xypj', s2ao, c0[:,nao:])
sc2+= lib.einsum('xpq,yqj->xypj', s1i, c1j)
sc2+= lib.einsum('ypq,xqj->xypj', s1j, c1i)
f2-= sc2 * e0[nao:]
sc1i = lib.einsum('xpq,qj->xpj', s1i, c0[:,nao:])
sc1j = lib.einsum('xpq,qj->xpj', s1j, c0[:,nao:])
sc1i+= lib.einsum('pq,xqj->xpj', s0, c1i)
sc1j+= lib.einsum('pq,xqj->xpj', s0, c1j)
f2-= lib.einsum('xpq,yqj->xypj', sc1i, e1j)
f2-= lib.einsum('ypq,xqj->xypj', sc1j, e1i)
c2 = lib.einsum('pi,xypj->xyij', c0.conj(), f2) / -epq[:,nao:]
c2_ao = lib.einsum('pq,xyqi->xypi', c0, c2)
cl2 = c2_ao[:,:,:nao]
cs2 = c2_ao[:,:,nao:]
tmp = cs2 - lib.einsum('pq,xyqi->xypi', x0, cl2)
tmp-= lib.einsum('xpq,yqi->xypi', x1i, c1j[:,:nao])
tmp-= lib.einsum('ypq,xqi->xypi', x1j, c1i[:,:nao])
x2 = scipy.linalg.solve(cl0.T, tmp.reshape(-1,nao).T).T.reshape(3,3,nao,nao)
hfw2 = numpy.empty((3,3,nao,nao))
for i in range(3):
for j in range(3):
if 'ATOM' in approx:
s_nesc2 = reduce(numpy.dot, (x0.T, s2ao[i,j,nao:,nao:], x0))
s_nesc2 += s2ao[i,j,:nao,:nao]
R2 = _get_r2((w_sqrt,v_s), s_nesc0,
s1i[i,:nao,:nao], s_nesc1i[i],
s1j[j,:nao,:nao], s_nesc1j[j],
s2ao[i,j,:nao,:nao], s_nesc2, (wr0_sqrt,vr0))
c_fw2 = numpy.vstack((R2, numpy.dot(x0, R2)))
else:
s_nesc2 = numpy.dot(x2[i,j].T, t0x0)
s_nesc2 += reduce(numpy.dot, (x1i[i].T, s1j[j,nao:,nao:], x0))
s_nesc2 += reduce(numpy.dot, (x0.T, s1i[i,nao:,nao:], x1j[j]))
s_nesc2 += reduce(numpy.dot, (x1i[i].T, s0[nao:,nao:], x1j[j]))
s_nesc2 = s_nesc2 + s_nesc2.T
s_nesc2 += reduce(numpy.dot, (x0.T, s2ao[i,j,nao:,nao:], x0))
s_nesc2 += s2ao[i,j,:nao,:nao]
R2 = _get_r2((w_sqrt,v_s), s_nesc0,
s1i[i,:nao,:nao], s_nesc1i[i],
s1j[j,:nao,:nao], s_nesc1j[j],
s2ao[i,j,:nao,:nao], s_nesc2, (wr0_sqrt,vr0))
c_fw_s = (numpy.dot(x0, R2) + numpy.dot(x1i[i], R1j[j]) +
numpy.dot(x1j[j], R1i[i]) + numpy.dot(x2[i,j], R0))
c_fw2 = numpy.vstack((R2, c_fw_s))
tmp = numpy.dot(c_fw2.T, h0_fw_half)
tmp += reduce(numpy.dot, (c_fw1i[i].T, h1j[j], c_fw0))
tmp += reduce(numpy.dot, (c_fw0.T, h1i[i], c_fw1j[j]))
tmp += reduce(numpy.dot, (c_fw1i[i].T, h0, c_fw1j[j]))
hfw2[i,j] = tmp + tmp.T
hfw2[i,j]+= reduce(numpy.dot, (c_fw0.T, h2ao[i,j], c_fw0))
return hfw2
return hcore_deriv
def _get_r2(s0_roots, sa0, s1i, sa1i, s1j, sa1j, s2, sa2, r0_roots):
w_sqrt, v_s = s0_roots
w_invsqrt = 1. / w_sqrt
wr0_sqrt, vr0 = r0_roots
wr0_invsqrt = 1. / wr0_sqrt
sa0 = lib.einsum('pi,pq,qj->ij', v_s, sa0 , v_s)
s1i = lib.einsum('pi,pq,qj->ij', v_s, s1i , v_s)
s1j = lib.einsum('pi,pq,qj->ij', v_s, s1j , v_s)
s2 = lib.einsum('pi,pq,qj->ij', v_s, s2 , v_s)
sa1i = lib.einsum('pi,pq,qj->ij', v_s, sa1i, v_s)
sa1j = lib.einsum('pi,pq,qj->ij', v_s, sa1j, v_s)
sa2 = lib.einsum('pi,pq,qj->ij', v_s, sa2 , v_s)
s1i_sqrt = s1i / (w_sqrt[:,None] + w_sqrt)
s1i_invsqrt = (numpy.einsum('i,ij,j->ij', w_invsqrt**2, s1i, w_invsqrt**2)
/ -(w_invsqrt[:,None] + w_invsqrt))
s1j_sqrt = s1j / (w_sqrt[:,None] + w_sqrt)
s1j_invsqrt = (numpy.einsum('i,ij,j->ij', w_invsqrt**2, s1j, w_invsqrt**2)
/ -(w_invsqrt[:,None] + w_invsqrt))
tmp = numpy.dot(s1i_sqrt, s1j_sqrt)
s2_sqrt = (s2 - tmp - tmp.T) / (w_sqrt[:,None] + w_sqrt)
tmp = numpy.dot(s1i*w_invsqrt**2, s1j)
tmp = s2 - tmp - tmp.T
tmp = -numpy.einsum('i,ij,j->ij', w_invsqrt**2, tmp, w_invsqrt**2)
tmp1 = numpy.dot(s1i_invsqrt, s1j_invsqrt)
s2_invsqrt = (tmp - tmp1 - tmp1.T) / (w_invsqrt[:,None] + w_invsqrt)
R1i_mid = lib.einsum('ip,pj,j->ij', s1i_invsqrt, sa0, w_invsqrt)
R1i_mid = R1i_mid + R1i_mid.T
R1i_mid+= numpy.einsum('i,ij,j->ij', w_invsqrt, sa1i, w_invsqrt)
R1i_mid = tmpi = lib.einsum('pi,pq,qj->ij', vr0, R1i_mid, vr0)
R1i_mid = (numpy.einsum('i,ij,j->ij', wr0_invsqrt**2, R1i_mid, wr0_invsqrt**2)
/ -(wr0_invsqrt[:,None] + wr0_invsqrt))
R1j_mid = lib.einsum('ip,pj,j->ij', s1j_invsqrt, sa0, w_invsqrt)
R1j_mid = R1j_mid + R1j_mid.T
R1j_mid+= numpy.einsum('i,ij,j->ij', w_invsqrt, sa1j, w_invsqrt)
R1j_mid = tmpj = lib.einsum('pi,pq,qj->ij', vr0, R1j_mid, vr0)
R1j_mid = (numpy.einsum('i,ij,j->ij', wr0_invsqrt**2, R1j_mid, wr0_invsqrt**2)
/ -(wr0_invsqrt[:,None] + wr0_invsqrt))
# second derivative of (s_invsqrt * sa * s_invsqrt), 9 terms
R2_mid = lib.einsum('ip,pj,j->ij', s2_invsqrt , sa0 , w_invsqrt)
R2_mid+= lib.einsum('ip,pj,j->ij', s1i_invsqrt, sa1j, w_invsqrt)
R2_mid+= lib.einsum('i,ip,pj->ij', w_invsqrt , sa1i, s1j_invsqrt)
R2_mid+= lib.einsum('ip,pq,qj->ij', s1i_invsqrt, sa0 , s1j_invsqrt)
R2_mid = R2_mid + R2_mid.T
R2_mid+= numpy.einsum('i,ij,j->ij', w_invsqrt, sa2, w_invsqrt)
R2_mid = lib.einsum('pi,pq,qj->ij', vr0, R2_mid, vr0)
tmp = numpy.dot(tmpi*wr0_invsqrt**2, tmpj)
tmp = R2_mid - tmp - tmp.T
tmp = -numpy.einsum('i,ij,j->ij', wr0_invsqrt**2, tmp, wr0_invsqrt**2)
tmp1 = numpy.dot(R1i_mid, R1j_mid)
R2_mid = (tmp - tmp1 - tmp1.T) / (wr0_invsqrt[:,None] + wr0_invsqrt)
R0_mid = numpy.dot(vr0*wr0_invsqrt, vr0.T)
R1i_mid = reduce(numpy.dot, (vr0, R1i_mid, vr0.T))
R1j_mid = reduce(numpy.dot, (vr0, R1j_mid, vr0.T))
R2_mid = reduce(numpy.dot, (vr0, R2_mid, vr0.T))
R2 = lib.einsum('ip,pj,j->ij' , s2_invsqrt , R0_mid , w_sqrt)
R2 += lib.einsum('ip,pj,j->ij' , s1i_invsqrt, R1j_mid, w_sqrt)
R2 += lib.einsum('ip,pq,qj->ij', s1i_invsqrt, R0_mid , s1j_sqrt)
R2 += lib.einsum('ip,pj,j->ij' , s1j_invsqrt, R1i_mid, w_sqrt)
R2 += numpy.einsum('i,ij,j->ij', w_invsqrt , R2_mid , w_sqrt)
R2 += lib.einsum('i,iq,qj->ij' , w_invsqrt , R1i_mid, s1j_sqrt)
R2 += lib.einsum('ip,pq,qj->ij', s1j_invsqrt, R0_mid , s1i_sqrt)
R2 += lib.einsum('i,iq,qj->ij' , w_invsqrt , R1j_mid, s1i_sqrt)
R2 += lib.einsum('i,iq,qj->ij' , w_invsqrt , R0_mid , s2_sqrt)
R2 = reduce(numpy.dot, (v_s, R2, v_s.T))
return R2
if __name__ == '__main__':
bak = lib.param.LIGHT_SPEED
lib.param.LIGHT_SPEED = 10
mol = gto.M(
verbose = 0,
atom = [["O" , (0. , 0. , 0.0001)],
[1 , (0. , -0.757 , 0.587)],
[1 , (0. , 0.757 , 0.587)]],
basis = '3-21g',
)
h1_deriv_1 = sfx2c1e_grad.gen_sf_hfw(mol, approx='1E')
mol = gto.M(
verbose = 0,
atom = [["O" , (0. , 0. ,-0.0001)],
[1 , (0. , -0.757 , 0.587)],
[1 , (0. , 0.757 , 0.587)]],
basis = '3-21g',
)
h1_deriv_2 = sfx2c1e_grad.gen_sf_hfw(mol, approx='1E')
mol = gto.M(
verbose = 0,
atom = [["O" , (0. , 0. , 0. )],
[1 , (0. , -0.757 , 0.587)],
[1 , (0. , 0.757 , 0.587)]],
basis = '3-21g',
)
h2_deriv = gen_sf_hfw(mol)
h2 = h2_deriv(0,0)
h2_ref = (h1_deriv_1(0)[2] - h1_deriv_2(0)[2]) / 0.0002 * lib.param.BOHR
print(abs(h2[2,2]-h2_ref).max())
print(lib.finger(h2) - 33.71188112440316)
h2 = h2_deriv(1,0)
h2_ref = (h1_deriv_1(1)[2] - h1_deriv_2(1)[2]) / 0.0002 * lib.param.BOHR
print(abs(h2[2,2]-h2_ref).max())
print(lib.finger(h2) - -23.609411428378138)
lib.param.LIGHT_SPEED = bak