#!/usr/bin/env python
# Copyright 2014-2019 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.
'''
1-electron Spin-free X2C approximation
'''
from functools import reduce
import numpy
import scipy.linalg
from pyscf import lib
from pyscf import gto
from pyscf.lib import logger
from pyscf.scf import hf
from pyscf.scf import ghf
from pyscf.x2c import x2c
from pyscf.data import nist
[docs]
def sfx2c1e(mf):
'''Spin-free X2C.
For the given SCF object, it updates the hcore constructor. All integrals
are computed in the real spherical GTO basis.
Args:
mf : an SCF object
Returns:
An SCF object
Examples:
>>> mol = gto.M(atom='H 0 0 0; F 0 0 1', basis='ccpvdz', verbose=0)
>>> mf = scf.RHF(mol).sfx2c1e()
>>> mf.scf()
>>> import pyscf.x2c.sfx2c1e
>>> mol.symmetry = 1
>>> mol.build(0, 0)
>>> mf = pyscf.x2c.sfx2c1e.sfx2c1e(scf.UHF(mol))
>>> mf.scf()
'''
assert isinstance(mf, hf.SCF)
if isinstance(mf, x2c._X2C_SCF):
if mf.with_x2c is None:
mf.with_x2c = SpinFreeX2CHelper(mf.mol)
return mf
elif not isinstance(mf.with_x2c, SpinFreeX2CHelper):
# An object associated to x2c.SpinOrbitalX2CHelper
raise NotImplementedError
else:
return mf
return lib.set_class(SFX2C1E_SCF(mf), (SFX2C1E_SCF, mf.__class__))
sfx2c = sfx2c1e
[docs]
class SFX2C1E_SCF(x2c._X2C_SCF):
'''
Attributes for spin-free X2C:
with_x2c : X2C object
'''
__name_mixin__ = 'sfX2C1e'
_keys = {'with_x2c'}
def __init__(self, mf):
self.__dict__.update(mf.__dict__)
self.with_x2c = SpinFreeX2CHelper(mf.mol)
[docs]
def undo_x2c(self):
'''Remove the X2C Mixin'''
obj = lib.view(self, lib.drop_class(self.__class__, SFX2C1E_SCF))
del obj.with_x2c
return obj
[docs]
def get_hcore(self, mol=None):
if self.with_x2c:
hcore = self.with_x2c.get_hcore(mol)
if isinstance(self, ghf.GHF):
hcore = scipy.linalg.block_diag(hcore, hcore)
return hcore
else:
return super(x2c._X2C_SCF, self).get_hcore(mol)
[docs]
def dip_moment(self, mol=None, dm=None, unit='Debye', verbose=logger.NOTE,
picture_change=True, **kwargs):
r''' Dipole moment calculation with picture change correction
Args:
mol: an instance of :class:`Mole`
dm : a 2D ndarrays density matrices
Kwarg:
picture_chang (bool) : Whether to compute the dipole moment with
picture change correction.
Return:
A list: the dipole moment on x, y and z component
'''
if mol is None: mol = self.mol
if dm is None: dm = self.make_rdm1()
log = logger.new_logger(mol, verbose)
if not (isinstance(dm, numpy.ndarray) and dm.ndim == 2):
# UHF density matrices
dm = dm[0] + dm[1]
if isinstance(self, ghf.GHF):
nao = mol.nao_nr()
dm = dm[:nao,:nao] + dm[nao:,nao:]
with mol.with_common_orig((0,0,0)):
if picture_change:
xmol = self.with_x2c.get_xmol()[0]
nao = xmol.nao
prp = xmol.intor_symmetric('int1e_sprsp').reshape(3,4,nao,nao)[:,3]
c1 = 0.5/lib.param.LIGHT_SPEED
ao_dip = self.with_x2c.picture_change(('int1e_r', prp*c1**2))
else:
ao_dip = mol.intor_symmetric('int1e_r')
el_dip = numpy.einsum('xij,ji->x', ao_dip, dm).real
charges = mol.atom_charges()
coords = mol.atom_coords()
nucl_dip = numpy.einsum('i,ix->x', charges, coords)
mol_dip = nucl_dip - el_dip
if unit.upper() == 'DEBYE':
mol_dip *= nist.AU2DEBYE
log.note('Dipole moment(X, Y, Z, Debye): %8.5f, %8.5f, %8.5f', *mol_dip)
else:
log.note('Dipole moment(X, Y, Z, A.U.): %8.5f, %8.5f, %8.5f', *mol_dip)
return mol_dip
def _transfer_attrs_(self, dst):
if self.with_x2c and not hasattr(dst, 'with_x2c'):
logger.warn(self, 'Destination object of to_hf/to_ks method is not '
'an X2C object. Convert dst to X2C object.')
dst = dst.sfx2c()
return hf.SCF._transfer_attrs_(self, dst)
[docs]
def to_gpu(self):
obj = self.undo_x2c().to_gpu().sfx2c1e()
return lib.to_gpu(self, obj)
[docs]
class SpinFreeX2CHelper(x2c.X2CHelperBase):
'''1-component X2c (spin-free part only)
'''
[docs]
def get_hcore(self, mol=None):
'''1-component X2c Foldy-Wouthuysen (FW Hamiltonian (spin-free part only)
'''
if mol is None: mol = self.mol
if mol.has_ecp():
raise NotImplementedError
xmol, contr_coeff = self.get_xmol(mol)
c = lib.param.LIGHT_SPEED
assert ('1E' in self.approx.upper())
t = xmol.intor_symmetric('int1e_kin')
v = xmol.intor_symmetric('int1e_nuc')
s = xmol.intor_symmetric('int1e_ovlp')
w = xmol.intor_symmetric('int1e_pnucp')
if 'get_xmat' in self.__dict__:
# If the get_xmat method is overwritten by user, build the X
# matrix with the external get_xmat method
x = self.get_xmat(xmol)
h1 = x2c._get_hcore_fw(t, v, w, s, x, c)
elif 'ATOM' in self.approx.upper():
atom_slices = xmol.offset_nr_by_atom()
nao = xmol.nao_nr()
x = numpy.zeros((nao,nao))
for ia in range(xmol.natm):
ish0, ish1, p0, p1 = atom_slices[ia]
shls_slice = (ish0, ish1, ish0, ish1)
t1 = xmol.intor('int1e_kin', shls_slice=shls_slice)
s1 = xmol.intor('int1e_ovlp', shls_slice=shls_slice)
with xmol.with_rinv_at_nucleus(ia):
z = -xmol.atom_charge(ia)
v1 = z * xmol.intor('int1e_rinv', shls_slice=shls_slice)
w1 = z * xmol.intor('int1e_prinvp', shls_slice=shls_slice)
x[p0:p1,p0:p1] = x2c._x2c1e_xmatrix(t1, v1, w1, s1, c)
h1 = x2c._get_hcore_fw(t, v, w, s, x, c)
else:
h1 = x2c._x2c1e_get_hcore(t, v, w, s, c)
if self.basis is not None:
s22 = xmol.intor_symmetric('int1e_ovlp')
s21 = gto.intor_cross('int1e_ovlp', xmol, mol)
c = lib.cho_solve(s22, s21)
h1 = reduce(numpy.dot, (c.T, h1, c))
if self.xuncontract and contr_coeff is not None:
h1 = reduce(numpy.dot, (contr_coeff.T, h1, contr_coeff))
return h1
[docs]
def picture_change(self, even_operator=(None, None), odd_operator=None):
'''Picture change for even_operator + odd_operator
even_operator has two terms at diagonal blocks
[ v 0 ]
[ 0 w ]
odd_operator has the term at off-diagonal blocks
[ 0 p ]
[ p^T 0 ]
v, w, and p can be strings (integral name) or matrices.
'''
mol = self.mol
xmol, c = self.get_xmol(mol)
pc_mat = self._picture_change(xmol, even_operator, odd_operator)
if self.basis is not None:
s22 = xmol.intor_symmetric('int1e_ovlp')
s21 = gto.mole.intor_cross('int1e_ovlp', xmol, mol)
c = lib.cho_solve(s22, s21)
elif self.xuncontract:
pass
else:
return pc_mat
if pc_mat.ndim == 2:
return lib.einsum('pi,pq,qj->ij', c, pc_mat, c)
else:
return lib.einsum('pi,xpq,qj->xij', c, pc_mat, c)
[docs]
def get_xmat(self, mol=None):
if mol is None:
xmol = self.get_xmol(mol)[0]
else:
xmol = mol
c = lib.param.LIGHT_SPEED
assert ('1E' in self.approx.upper())
if 'ATOM' in self.approx.upper():
atom_slices = xmol.offset_nr_by_atom()
nao = xmol.nao_nr()
x = numpy.zeros((nao,nao))
for ia in range(xmol.natm):
ish0, ish1, p0, p1 = atom_slices[ia]
shls_slice = (ish0, ish1, ish0, ish1)
t1 = xmol.intor('int1e_kin', shls_slice=shls_slice)
s1 = xmol.intor('int1e_ovlp', shls_slice=shls_slice)
with xmol.with_rinv_at_nucleus(ia):
z = -xmol.atom_charge(ia)
v1 = z * xmol.intor('int1e_rinv', shls_slice=shls_slice)
w1 = z * xmol.intor('int1e_prinvp', shls_slice=shls_slice)
x[p0:p1,p0:p1] = x2c._x2c1e_xmatrix(t1, v1, w1, s1, c)
else:
t = xmol.intor_symmetric('int1e_kin')
v = xmol.intor_symmetric('int1e_nuc')
s = xmol.intor_symmetric('int1e_ovlp')
w = xmol.intor_symmetric('int1e_pnucp')
x = x2c._x2c1e_xmatrix(t, v, w, s, c)
return x
def _get_rmat(self, x=None):
'''The matrix (in AO basis) that changes metric from NESC metric to NR metric'''
xmol = self.get_xmol()[0]
if x is None:
x = self.get_xmat(xmol)
c = lib.param.LIGHT_SPEED
s = xmol.intor_symmetric('int1e_ovlp')
t = xmol.intor_symmetric('int1e_kin')
s1 = s + reduce(numpy.dot, (x.conj().T, t, x)) * (.5/c**2)
return x2c._get_r(s, s1)
[docs]
def hcore_deriv_generator(self, mol=None, deriv=1):
from pyscf.x2c import sfx2c1e_grad
from pyscf.x2c import sfx2c1e_hess
if deriv == 1:
return sfx2c1e_grad.hcore_grad_generator(self, mol)
elif deriv == 2:
return sfx2c1e_hess.hcore_hess_generator(self, mol)
else:
raise NotImplementedError
SpinFreeX2C = SpinFreeX2CHelper
if __name__ == '__main__':
mol = gto.Mole()
mol.build(
verbose = 0,
atom = [["O" , (0. , 0. , 0.)],
[1 , (0. , -0.757 , 0.587)],
[1 , (0. , 0.757 , 0.587)] ],
basis = 'ccpvdz-dk',
)
method = hf.RHF(mol)
enr = method.kernel()
print('E(NR) = %.12g' % enr)
method = sfx2c1e(hf.RHF(mol))
esfx2c = method.kernel()
print('E(SFX2C1E) = %.12g' % esfx2c)
method.with_x2c.basis = 'unc-ccpvqz-dk'
print('E(SFX2C1E) = %.12g' % method.kernel())
method.with_x2c.approx = 'atom1e'
print('E(SFX2C1E) = %.12g' % method.kernel())
mf = method.density_fit().undo_x2c().run()
print('E(DF-NR) = %.12g' % mf.e_tot)