#!/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.
#
# Author: Qiming Sun <osirpt.sun@gmail.com>
#
import numpy
from pyscf import lib
from pyscf import scf
from pyscf import symm
from pyscf import ao2mo
from pyscf.lib import logger
from pyscf.scf import uhf_symm
from pyscf.tdscf import rhf
from pyscf.tdscf._lr_eig import eigh as lr_eigh, eig as lr_eig, real_eig
from pyscf.data import nist
from pyscf import __config__
OUTPUT_THRESHOLD = getattr(__config__, 'tdscf_rhf_get_nto_threshold', 0.3)
REAL_EIG_THRESHOLD = getattr(__config__, 'tdscf_rhf_TDDFT_pick_eig_threshold', 1e-4)
MO_BASE = getattr(__config__, 'MO_BASE', 1)
[docs]
def gen_tda_operation(mf, fock_ao=None, wfnsym=None, with_nlc=True):
'''A x
Kwargs:
wfnsym : int or str
Point group symmetry irrep symbol or ID for excited CIS wavefunction.
'''
td = TDA(mf)
td.exclude_nlc = not with_nlc
return _gen_tda_operation(td, fock_ao, wfnsym)
gen_tda_hop = gen_tda_operation
def _gen_tda_operation(td, fock_ao=None, wfnsym=None):
assert fock_ao is None
mf = td._scf
mol = mf.mol
maska, maskb = td.get_frozen_mask()
mo_coeff = (mf.mo_coeff[0][:, maska], mf.mo_coeff[1][:, maskb])
assert (mo_coeff[0].dtype == numpy.double)
mo_energy = (mf.mo_energy[0][maska], mf.mo_energy[1][maskb])
mo_occ = (mf.mo_occ[0][maska], mf.mo_occ[1][maskb])
nao, nmo = mo_coeff[0].shape
occidxa = numpy.where(mo_occ[0]>0)[0]
occidxb = numpy.where(mo_occ[1]>0)[0]
viridxa = numpy.where(mo_occ[0]==0)[0]
viridxb = numpy.where(mo_occ[1]==0)[0]
nocca = len(occidxa)
noccb = len(occidxb)
nvira = len(viridxa)
nvirb = len(viridxb)
orboa = mo_coeff[0][:,occidxa]
orbob = mo_coeff[1][:,occidxb]
orbva = mo_coeff[0][:,viridxa]
orbvb = mo_coeff[1][:,viridxb]
if wfnsym is not None and mol.symmetry:
if isinstance(wfnsym, str):
wfnsym = symm.irrep_name2id(mol.groupname, wfnsym)
wfnsym = wfnsym % 10 # convert to D2h subgroup
x_sym_a, x_sym_b = _get_x_sym_table(td)
sym_forbid = numpy.append(x_sym_a.ravel(), x_sym_b.ravel()) != wfnsym
e_ia_a = mo_energy[0][viridxa] - mo_energy[0][occidxa,None]
e_ia_b = mo_energy[1][viridxb] - mo_energy[1][occidxb,None]
e_ia = numpy.hstack((e_ia_a.reshape(-1), e_ia_b.reshape(-1)))
hdiag = e_ia
if wfnsym is not None and mol.symmetry:
hdiag[sym_forbid] = 0
mem_now = lib.current_memory()[0]
max_memory = max(2000, mf.max_memory*.8-mem_now)
vresp = td.gen_response(hermi=0, max_memory=max_memory)
def vind(zs):
nz = len(zs)
zs = numpy.asarray(zs)
if wfnsym is not None and mol.symmetry:
zs = numpy.copy(zs)
zs[:,sym_forbid] = 0
za = zs[:,:nocca*nvira].reshape(nz,nocca,nvira)
zb = zs[:,nocca*nvira:].reshape(nz,noccb,nvirb)
dmsa = lib.einsum('xov,pv,qo->xpq', za, orbva, orboa.conj())
dmsb = lib.einsum('xov,pv,qo->xpq', zb, orbvb, orbob.conj())
v1ao = vresp(numpy.asarray((dmsa,dmsb)))
v1a = lib.einsum('xpq,qo,pv->xov', v1ao[0], orboa, orbva.conj())
v1b = lib.einsum('xpq,qo,pv->xov', v1ao[1], orbob, orbvb.conj())
v1a += numpy.einsum('xia,ia->xia', za, e_ia_a)
v1b += numpy.einsum('xia,ia->xia', zb, e_ia_b)
hx = numpy.hstack((v1a.reshape(nz,-1), v1b.reshape(nz,-1)))
if wfnsym is not None and mol.symmetry:
hx[:,sym_forbid] = 0
return hx
return vind, hdiag
[docs]
def get_frozen_mask(td):
'''Get boolean mask for the unrestricted reference orbitals.
In the returned boolean (mask) array of frozen orbital indices, the
element is False if it corresponds to the frozen orbital.
'''
moidxa = numpy.ones(td._scf.mo_occ[0].size, dtype=bool)
moidxb = numpy.ones(td._scf.mo_occ[1].size, dtype=bool)
frozen = td.frozen
if frozen is None:
pass
elif isinstance(frozen, (int, numpy.integer)):
moidxa[:frozen] = False
moidxb[:frozen] = False
elif hasattr(frozen, '__len__'):
if len(frozen) > 0:
if isinstance(frozen[0], (int, numpy.integer)):
frozen = (frozen, frozen)
moidxa[list(frozen[0])] = False
moidxb[list(frozen[1])] = False
else:
raise NotImplementedError
return moidxa,moidxb
def _get_x_sym_table(td):
'''Irrep (up to D2h symmetry) of each coefficient in X amplitude'''
mf = td._scf
mol = mf.mol
maska, maskb = td.get_frozen_mask()
mo_coeff = (mf.mo_coeff[0][:, maska], mf.mo_coeff[1][:, maskb])
mo_occ = (mf.mo_occ[0][maska], mf.mo_occ[1][maskb])
mo_occa, mo_occb = mo_occ
orbsyma, orbsymb = uhf_symm.get_orbsym(mol, mo_coeff)
orbsyma = orbsyma % 10
orbsymb = orbsymb % 10
x_sym_a = orbsyma[mo_occa>0,None] ^ orbsyma[mo_occa==0]
x_sym_b = orbsymb[mo_occb>0,None] ^ orbsymb[mo_occb==0]
return x_sym_a, x_sym_b
[docs]
def get_ab(mf, frozen=None, mo_energy=None, mo_coeff=None, mo_occ=None):
r'''A and B matrices for TDDFT response function.
A[i,a,j,b] = \delta_{ab}\delta_{ij}(E_a - E_i) + (ai||jb)
B[i,a,j,b] = (ai||bj)
Spin symmetry is considered in the returned A, B lists. List A has three
items: (A_aaaa, A_aabb, A_bbbb). A_bbaa = A_aabb.transpose(2,3,0,1).
B has three items: (B_aaaa, B_aabb, B_bbbb).
B_bbaa = B_aabb.transpose(2,3,0,1).
'''
if mo_energy is None: mo_energy = mf.mo_energy
if mo_coeff is None: mo_coeff = mf.mo_coeff
if mo_occ is None: mo_occ = mf.mo_occ
mo_coeff0 = numpy.copy(mo_coeff)
mo_occ0 = numpy.copy(mo_occ)
if frozen is not None:
# see get_frozen_mask()
moidxa = numpy.ones(mf.mo_occ[0].size, dtype=bool)
moidxb = numpy.ones(mf.mo_occ[1].size, dtype=bool)
if isinstance(frozen, (int, numpy.integer)):
moidxa[:frozen] = False
moidxb[:frozen] = False
elif hasattr(frozen, '__len__'):
if len(frozen) > 0:
if isinstance(frozen[0], (int, numpy.integer)):
frozen = (frozen, frozen)
moidxa[list(frozen[0])] = False
moidxb[list(frozen[1])] = False
else:
raise NotImplementedError
mo_energy = (mo_energy[0][moidxa], mo_energy[1][moidxb])
mo_coeff = (mo_coeff[0][:, moidxa], mo_coeff[1][:, moidxb])
mo_occ = (mo_occ[0][moidxa], mo_occ[1][moidxb])
assert mo_coeff[0].dtype == numpy.float64
mol = mf.mol
nao = mol.nao_nr()
occidx_a = numpy.where(mo_occ[0]==1)[0]
viridx_a = numpy.where(mo_occ[0]==0)[0]
occidx_b = numpy.where(mo_occ[1]==1)[0]
viridx_b = numpy.where(mo_occ[1]==0)[0]
orbo_a = mo_coeff[0][:,occidx_a]
orbv_a = mo_coeff[0][:,viridx_a]
orbo_b = mo_coeff[1][:,occidx_b]
orbv_b = mo_coeff[1][:,viridx_b]
nocc_a = orbo_a.shape[1]
nvir_a = orbv_a.shape[1]
nocc_b = orbo_b.shape[1]
nvir_b = orbv_b.shape[1]
mo_a = numpy.hstack((orbo_a,orbv_a))
mo_b = numpy.hstack((orbo_b,orbv_b))
nmo_a = nocc_a + nvir_a
nmo_b = nocc_b + nvir_b
e_ia_a = (mo_energy[0][viridx_a,None] - mo_energy[0][occidx_a]).T
e_ia_b = (mo_energy[1][viridx_b,None] - mo_energy[1][occidx_b]).T
a_aa = numpy.diag(e_ia_a.ravel()).reshape(nocc_a,nvir_a,nocc_a,nvir_a)
a_bb = numpy.diag(e_ia_b.ravel()).reshape(nocc_b,nvir_b,nocc_b,nvir_b)
a_ab = numpy.zeros((nocc_a,nvir_a,nocc_b,nvir_b))
b_aa = numpy.zeros_like(a_aa)
b_ab = numpy.zeros_like(a_ab)
b_bb = numpy.zeros_like(a_bb)
a = (a_aa, a_ab, a_bb)
b = (b_aa, b_ab, b_bb)
def add_hf_(a, b, hyb=1):
eri_aa = ao2mo.general(mol, [orbo_a,mo_a,mo_a,mo_a], compact=False)
eri_ab = ao2mo.general(mol, [orbo_a,mo_a,mo_b,mo_b], compact=False)
eri_bb = ao2mo.general(mol, [orbo_b,mo_b,mo_b,mo_b], compact=False)
eri_aa = eri_aa.reshape(nocc_a,nmo_a,nmo_a,nmo_a)
eri_ab = eri_ab.reshape(nocc_a,nmo_a,nmo_b,nmo_b)
eri_bb = eri_bb.reshape(nocc_b,nmo_b,nmo_b,nmo_b)
a_aa, a_ab, a_bb = a
b_aa, b_ab, b_bb = b
a_aa += numpy.einsum('iabj->iajb', eri_aa[:nocc_a,nocc_a:,nocc_a:,:nocc_a])
a_aa -= numpy.einsum('ijba->iajb', eri_aa[:nocc_a,:nocc_a,nocc_a:,nocc_a:]) * hyb
b_aa += numpy.einsum('iajb->iajb', eri_aa[:nocc_a,nocc_a:,:nocc_a,nocc_a:])
b_aa -= numpy.einsum('jaib->iajb', eri_aa[:nocc_a,nocc_a:,:nocc_a,nocc_a:]) * hyb
a_bb += numpy.einsum('iabj->iajb', eri_bb[:nocc_b,nocc_b:,nocc_b:,:nocc_b])
a_bb -= numpy.einsum('ijba->iajb', eri_bb[:nocc_b,:nocc_b,nocc_b:,nocc_b:]) * hyb
b_bb += numpy.einsum('iajb->iajb', eri_bb[:nocc_b,nocc_b:,:nocc_b,nocc_b:])
b_bb -= numpy.einsum('jaib->iajb', eri_bb[:nocc_b,nocc_b:,:nocc_b,nocc_b:]) * hyb
a_ab += numpy.einsum('iabj->iajb', eri_ab[:nocc_a,nocc_a:,nocc_b:,:nocc_b])
b_ab += numpy.einsum('iajb->iajb', eri_ab[:nocc_a,nocc_a:,:nocc_b,nocc_b:])
if isinstance(mf, scf.hf.KohnShamDFT):
from pyscf.dft import xc_deriv
ni = mf._numint
ni.libxc.test_deriv_order(mf.xc, 2, raise_error=True)
omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
add_hf_(a, b, hyb)
if omega != 0: # For RSH
with mol.with_range_coulomb(omega):
eri_aa = ao2mo.general(mol, [orbo_a,mo_a,mo_a,mo_a], compact=False)
eri_bb = ao2mo.general(mol, [orbo_b,mo_b,mo_b,mo_b], compact=False)
eri_aa = eri_aa.reshape(nocc_a,nmo_a,nmo_a,nmo_a)
eri_bb = eri_bb.reshape(nocc_b,nmo_b,nmo_b,nmo_b)
a_aa, a_ab, a_bb = a
b_aa, b_ab, b_bb = b
k_fac = alpha - hyb
a_aa -= numpy.einsum('ijba->iajb', eri_aa[:nocc_a,:nocc_a,nocc_a:,nocc_a:]) * k_fac
b_aa -= numpy.einsum('jaib->iajb', eri_aa[:nocc_a,nocc_a:,:nocc_a,nocc_a:]) * k_fac
a_bb -= numpy.einsum('ijba->iajb', eri_bb[:nocc_b,:nocc_b,nocc_b:,nocc_b:]) * k_fac
b_bb -= numpy.einsum('jaib->iajb', eri_bb[:nocc_b,nocc_b:,:nocc_b,nocc_b:]) * k_fac
xctype = ni._xc_type(mf.xc)
dm0 = mf.make_rdm1(mo_coeff0, mo_occ0)
make_rho = ni._gen_rho_evaluator(mol, dm0, hermi=1, with_lapl=False)[0]
mem_now = lib.current_memory()[0]
max_memory = max(2000, mf.max_memory*.8-mem_now)
if xctype == 'LDA':
ao_deriv = 0
for ao, mask, weight, coords \
in ni.block_loop(mol, mf.grids, nao, ao_deriv, max_memory):
rho0a = make_rho(0, ao, mask, xctype)
rho0b = make_rho(1, ao, mask, xctype)
rho = (rho0a, rho0b)
fxc = ni.eval_xc_eff(mf.xc, rho, deriv=2, xctype=xctype)[2]
wfxc = fxc[:,0,:,0] * weight
rho_o_a = lib.einsum('rp,pi->ri', ao, orbo_a)
rho_v_a = lib.einsum('rp,pi->ri', ao, orbv_a)
rho_o_b = lib.einsum('rp,pi->ri', ao, orbo_b)
rho_v_b = lib.einsum('rp,pi->ri', ao, orbv_b)
rho_ov_a = numpy.einsum('ri,ra->ria', rho_o_a, rho_v_a)
rho_ov_b = numpy.einsum('ri,ra->ria', rho_o_b, rho_v_b)
w_ov = numpy.einsum('ria,r->ria', rho_ov_a, wfxc[0,0])
iajb = lib.einsum('ria,rjb->iajb', rho_ov_a, w_ov)
a_aa += iajb
b_aa += iajb
w_ov = numpy.einsum('ria,r->ria', rho_ov_b, wfxc[0,1])
iajb = lib.einsum('ria,rjb->iajb', rho_ov_a, w_ov)
a_ab += iajb
b_ab += iajb
w_ov = numpy.einsum('ria,r->ria', rho_ov_b, wfxc[1,1])
iajb = lib.einsum('ria,rjb->iajb', rho_ov_b, w_ov)
a_bb += iajb
b_bb += iajb
elif xctype == 'GGA':
ao_deriv = 1
for ao, mask, weight, coords \
in ni.block_loop(mol, mf.grids, nao, ao_deriv, max_memory):
rho0a = make_rho(0, ao, mask, xctype)
rho0b = make_rho(1, ao, mask, xctype)
rho = (rho0a, rho0b)
fxc = ni.eval_xc_eff(mf.xc, rho, deriv=2, xctype=xctype)[2]
wfxc = fxc * weight
rho_o_a = lib.einsum('xrp,pi->xri', ao, orbo_a)
rho_v_a = lib.einsum('xrp,pi->xri', ao, orbv_a)
rho_o_b = lib.einsum('xrp,pi->xri', ao, orbo_b)
rho_v_b = lib.einsum('xrp,pi->xri', ao, orbv_b)
rho_ov_a = numpy.einsum('xri,ra->xria', rho_o_a, rho_v_a[0])
rho_ov_b = numpy.einsum('xri,ra->xria', rho_o_b, rho_v_b[0])
rho_ov_a[1:4] += numpy.einsum('ri,xra->xria', rho_o_a[0], rho_v_a[1:4])
rho_ov_b[1:4] += numpy.einsum('ri,xra->xria', rho_o_b[0], rho_v_b[1:4])
w_ov_aa = numpy.einsum('xyr,xria->yria', wfxc[0,:,0], rho_ov_a)
w_ov_ab = numpy.einsum('xyr,xria->yria', wfxc[0,:,1], rho_ov_a)
w_ov_bb = numpy.einsum('xyr,xria->yria', wfxc[1,:,1], rho_ov_b)
iajb = lib.einsum('xria,xrjb->iajb', w_ov_aa, rho_ov_a)
a_aa += iajb
b_aa += iajb
iajb = lib.einsum('xria,xrjb->iajb', w_ov_bb, rho_ov_b)
a_bb += iajb
b_bb += iajb
iajb = lib.einsum('xria,xrjb->iajb', w_ov_ab, rho_ov_b)
a_ab += iajb
b_ab += iajb
elif xctype == 'HF':
pass
elif xctype == 'NLC':
pass # Processed later
elif xctype == 'MGGA':
ao_deriv = 1
for ao, mask, weight, coords \
in ni.block_loop(mol, mf.grids, nao, ao_deriv, max_memory):
rho0a = make_rho(0, ao, mask, xctype)
rho0b = make_rho(1, ao, mask, xctype)
rho = (rho0a, rho0b)
fxc = ni.eval_xc_eff(mf.xc, rho, deriv=2, xctype=xctype)[2]
wfxc = fxc * weight
rho_oa = lib.einsum('xrp,pi->xri', ao, orbo_a)
rho_ob = lib.einsum('xrp,pi->xri', ao, orbo_b)
rho_va = lib.einsum('xrp,pi->xri', ao, orbv_a)
rho_vb = lib.einsum('xrp,pi->xri', ao, orbv_b)
rho_ov_a = numpy.einsum('xri,ra->xria', rho_oa, rho_va[0])
rho_ov_b = numpy.einsum('xri,ra->xria', rho_ob, rho_vb[0])
rho_ov_a[1:4] += numpy.einsum('ri,xra->xria', rho_oa[0], rho_va[1:4])
rho_ov_b[1:4] += numpy.einsum('ri,xra->xria', rho_ob[0], rho_vb[1:4])
tau_ov_a = numpy.einsum('xri,xra->ria', rho_oa[1:4], rho_va[1:4]) * .5
tau_ov_b = numpy.einsum('xri,xra->ria', rho_ob[1:4], rho_vb[1:4]) * .5
rho_ov_a = numpy.vstack([rho_ov_a, tau_ov_a[numpy.newaxis]])
rho_ov_b = numpy.vstack([rho_ov_b, tau_ov_b[numpy.newaxis]])
w_ov_aa = numpy.einsum('xyr,xria->yria', wfxc[0,:,0], rho_ov_a)
w_ov_ab = numpy.einsum('xyr,xria->yria', wfxc[0,:,1], rho_ov_a)
w_ov_bb = numpy.einsum('xyr,xria->yria', wfxc[1,:,1], rho_ov_b)
iajb = lib.einsum('xria,xrjb->iajb', w_ov_aa, rho_ov_a)
a_aa += iajb
b_aa += iajb
iajb = lib.einsum('xria,xrjb->iajb', w_ov_bb, rho_ov_b)
a_bb += iajb
b_bb += iajb
iajb = lib.einsum('xria,xrjb->iajb', w_ov_ab, rho_ov_b)
a_ab += iajb
b_ab += iajb
if mf.do_nlc():
raise NotImplementedError('vv10 nlc not implemented in get_ab(). '
'However the nlc contribution is small in TDDFT, '
'so feel free to take the risk and comment out this line.')
else:
add_hf_(a, b)
return a, b
[docs]
def get_nto(tdobj, state=1, threshold=OUTPUT_THRESHOLD, verbose=None):
r'''
Natural transition orbital analysis.
The natural transition density matrix between ground state and excited
state :math:`Tia = \langle \Psi_{ex} | i a^\dagger | \Psi_0 \rangle` can
be transformed to diagonal form through SVD
:math:`T = O \sqrt{\lambda} V^\dagger`. O and V are occupied and virtual
natural transition orbitals. The diagonal elements :math:`\lambda` are the
weights of the occupied-virtual orbital pair in the excitation.
Ref: Martin, R. L., JCP, 118, 4775-4777
Note in the TDHF/TDDFT calculations, the excitation part (X) is
interpreted as the CIS coefficients and normalized to 1. The de-excitation
part (Y) is ignored.
Args:
state : int
Excited state ID. state = 1 means the first excited state.
If state < 0, state ID is counted from the last excited state.
Kwargs:
threshold : float
Above which the NTO coefficients will be printed in the output.
Returns:
A list (weights, NTOs). NTOs are natural orbitals represented in AO
basis. The first N_occ NTOs are occupied NTOs and the rest are virtual
NTOs.
'''
if state == 0:
logger.warn(tdobj, 'Excited state starts from 1. '
'Set state=1 for first excited state.')
state_id = state
elif state < 0:
state_id = state
else:
state_id = state - 1
mol = tdobj.mol
maska, maskb = tdobj.get_frozen_mask()
mo_coeff = (tdobj._scf.mo_coeff[0][:, maska], tdobj._scf.mo_coeff[1][:, maskb])
mo_occ = (tdobj._scf.mo_occ[0][maska], tdobj._scf.mo_occ[1][maskb])
orbo_a = mo_coeff[0][:,mo_occ[0]==1]
orbv_a = mo_coeff[0][:,mo_occ[0]==0]
orbo_b = mo_coeff[1][:,mo_occ[1]==1]
orbv_b = mo_coeff[1][:,mo_occ[1]==0]
nocc_a = orbo_a.shape[1]
nvir_a = orbv_a.shape[1]
nocc_b = orbo_b.shape[1]
nvir_b = orbv_b.shape[1]
cis_t1a, cis_t1b = tdobj.xy[state_id][0]
norm = numpy.linalg.norm(cis_t1a)**2 + numpy.linalg.norm(cis_t1b)**2
cis_t1a *= 1. / norm
cis_t1b *= 1. / norm
if mol.symmetry:
orbsyma, orbsymb = uhf_symm.get_orbsym(mol, mo_coeff)
o_sym_a = orbsyma[mo_occ[0]==1]
v_sym_a = orbsyma[mo_occ[0]==0]
o_sym_b = orbsymb[mo_occ[1]==1]
v_sym_b = orbsymb[mo_occ[1]==0]
nto_o_a = numpy.eye(nocc_a)
nto_v_a = numpy.eye(nvir_a)
nto_o_b = numpy.eye(nocc_b)
nto_v_b = numpy.eye(nvir_b)
weights_o_a = numpy.zeros(nocc_a)
weights_v_a = numpy.zeros(nvir_a)
weights_o_b = numpy.zeros(nocc_b)
weights_v_b = numpy.zeros(nvir_b)
for ir in set(orbsyma):
idx = numpy.where(o_sym_a == ir)[0]
if idx.size > 0:
dm_oo = numpy.dot(cis_t1a[idx], cis_t1a[idx].T)
weights_o_a[idx], nto_o_a[idx[:,None],idx] = numpy.linalg.eigh(dm_oo)
idx = numpy.where(v_sym_a == ir)[0]
if idx.size > 0:
dm_vv = numpy.dot(cis_t1a[:,idx].T, cis_t1a[:,idx])
weights_v_a[idx], nto_v_a[idx[:,None],idx] = numpy.linalg.eigh(dm_vv)
for ir in set(orbsymb):
idx = numpy.where(o_sym_b == ir)[0]
if idx.size > 0:
dm_oo = numpy.dot(cis_t1b[idx], cis_t1b[idx].T)
weights_o_b[idx], nto_o_b[idx[:,None],idx] = numpy.linalg.eigh(dm_oo)
idx = numpy.where(v_sym_b == ir)[0]
if idx.size > 0:
dm_vv = numpy.dot(cis_t1b[:,idx].T, cis_t1b[:,idx])
weights_v_b[idx], nto_v_b[idx[:,None],idx] = numpy.linalg.eigh(dm_vv)
def sort(weights, nto, sym):
# weights in descending order
idx = numpy.argsort(-weights)
weights = weights[idx]
nto = nto[:,idx]
sym = sym[idx]
return weights, nto, sym
weights_o_a, nto_o_a, o_sym_a = sort(weights_o_a, nto_o_a, o_sym_a)
weights_v_a, nto_v_a, v_sym_a = sort(weights_v_a, nto_v_a, v_sym_a)
weights_o_b, nto_o_b, o_sym_b = sort(weights_o_b, nto_o_b, o_sym_b)
weights_v_b, nto_v_b, v_sym_b = sort(weights_v_b, nto_v_b, v_sym_b)
nto_orbsyma = numpy.hstack((o_sym_a, v_sym_a))
nto_orbsymb = numpy.hstack((o_sym_b, v_sym_b))
if nocc_a < nvir_a:
weights_a = weights_o_a
else:
weights_a = weights_v_a
if nocc_b < nvir_b:
weights_b = weights_o_b
else:
weights_b = weights_v_b
else:
nto_o_a, w_a, nto_v_aT = numpy.linalg.svd(cis_t1a)
nto_o_b, w_b, nto_v_bT = numpy.linalg.svd(cis_t1b)
nto_v_a = nto_v_aT.conj().T
nto_v_b = nto_v_bT.conj().T
weights_a = w_a**2
weights_b = w_b**2
nto_orbsyma = nto_orbsymb = None
def _set_phase_(c):
idx = numpy.argmax(abs(c.real), axis=0)
c[:,c[idx,numpy.arange(c.shape[1])].real<0] *= -1
_set_phase_(nto_o_a)
_set_phase_(nto_o_b)
_set_phase_(nto_v_a)
_set_phase_(nto_v_b)
occupied_nto_a = numpy.dot(orbo_a, nto_o_a)
occupied_nto_b = numpy.dot(orbo_b, nto_o_b)
virtual_nto_a = numpy.dot(orbv_a, nto_v_a)
virtual_nto_b = numpy.dot(orbv_b, nto_v_b)
nto_coeff = (numpy.hstack((occupied_nto_a, virtual_nto_a)),
numpy.hstack((occupied_nto_b, virtual_nto_b)))
if mol.symmetry:
nto_coeff = (lib.tag_array(nto_coeff[0], orbsym=nto_orbsyma),
lib.tag_array(nto_coeff[1], orbsym=nto_orbsymb))
log = logger.new_logger(tdobj, verbose)
if log.verbose >= logger.INFO:
log.info('State %d: %g eV NTO largest component %s',
state_id+1, tdobj.e[state_id]*nist.HARTREE2EV,
weights_a[0]+weights_b[0])
fmt = '%' + str(lib.param.OUTPUT_DIGITS) + 'f (MO #%d)'
o_idx_a = numpy.where(abs(nto_o_a[:,0]) > threshold)[0]
v_idx_a = numpy.where(abs(nto_v_a[:,0]) > threshold)[0]
o_idx_b = numpy.where(abs(nto_o_b[:,0]) > threshold)[0]
v_idx_b = numpy.where(abs(nto_v_b[:,0]) > threshold)[0]
log.info(' alpha occ-NTO: ' +
' + '.join([(fmt % (nto_o_a[i,0], i+MO_BASE))
for i in o_idx_a]))
log.info(' alpha vir-NTO: ' +
' + '.join([(fmt % (nto_v_a[i,0], i+MO_BASE+nocc_a))
for i in v_idx_a]))
log.info(' beta occ-NTO: ' +
' + '.join([(fmt % (nto_o_b[i,0], i+MO_BASE))
for i in o_idx_b]))
log.info(' beta vir-NTO: ' +
' + '.join([(fmt % (nto_v_b[i,0], i+MO_BASE+nocc_b))
for i in v_idx_b]))
return (weights_a, weights_b), nto_coeff
[docs]
def analyze(tdobj, verbose=None):
log = logger.new_logger(tdobj, verbose)
mol = tdobj.mol
maska, maskb = tdobj.get_frozen_mask()
mo_coeff = (tdobj._scf.mo_coeff[0][:, maska], tdobj._scf.mo_coeff[1][:, maskb])
mo_occ = (tdobj._scf.mo_occ[0][maska], tdobj._scf.mo_occ[1][maskb])
nocc_a = numpy.count_nonzero(mo_occ[0] == 1)
nocc_b = numpy.count_nonzero(mo_occ[1] == 1)
e_ev = numpy.asarray(tdobj.e) * nist.HARTREE2EV
e_wn = numpy.asarray(tdobj.e) * nist.HARTREE2WAVENUMBER
wave_length = 1e7/e_wn
log.note('\n** Excitation energies and oscillator strengths **')
if mol.symmetry:
orbsyma, orbsymb = uhf_symm.get_orbsym(mol, mo_coeff)
x_syma = symm.direct_prod(orbsyma[mo_occ[0]==1], orbsyma[mo_occ[0]==0], mol.groupname)
x_symb = symm.direct_prod(orbsymb[mo_occ[1]==1], orbsymb[mo_occ[1]==0], mol.groupname)
else:
x_syma = None
f_oscillator = tdobj.oscillator_strength()
for i, ei in enumerate(tdobj.e):
x, y = tdobj.xy[i]
if x_syma is None:
log.note('Excited State %3d: %12.5f eV %9.2f nm f=%.4f',
i+1, e_ev[i], wave_length[i], f_oscillator[i])
else:
wfnsyma = rhf._analyze_wfnsym(tdobj, x_syma, x[0])
wfnsymb = rhf._analyze_wfnsym(tdobj, x_symb, x[1])
if wfnsyma == wfnsymb:
wfnsym = wfnsyma
else:
wfnsym = '???'
log.note('Excited State %3d: %4s %12.5f eV %9.2f nm f=%.4f',
i+1, wfnsym, e_ev[i], wave_length[i], f_oscillator[i])
if log.verbose >= logger.INFO:
for o, v in zip(* numpy.where(abs(x[0]) > 0.1)):
log.info(' %4da -> %4da %12.5f',
o+MO_BASE, v+MO_BASE+nocc_a, x[0][o,v])
for o, v in zip(* numpy.where(abs(x[1]) > 0.1)):
log.info(' %4db -> %4db %12.5f',
o+MO_BASE, v+MO_BASE+nocc_b, x[1][o,v])
if log.verbose >= logger.INFO:
log.info('\n** Transition electric dipole moments (AU) **')
log.info('state X Y Z Dip. S. Osc.')
trans_dip = tdobj.transition_dipole()
for i, ei in enumerate(tdobj.e):
dip = trans_dip[i]
log.info('%3d %11.4f %11.4f %11.4f %11.4f %11.4f',
i+1, dip[0], dip[1], dip[2], numpy.dot(dip, dip),
f_oscillator[i])
log.info('\n** Transition velocity dipole moments (imaginary part, AU) **')
log.info('state X Y Z Dip. S. Osc.')
trans_v = tdobj.transition_velocity_dipole()
f_v = tdobj.oscillator_strength(gauge='velocity', order=0)
for i, ei in enumerate(tdobj.e):
v = trans_v[i]
log.info('%3d %11.4f %11.4f %11.4f %11.4f %11.4f',
i+1, v[0], v[1], v[2], numpy.dot(v, v), f_v[i])
log.info('\n** Transition magnetic dipole moments (imaginary part, AU) **')
log.info('state X Y Z')
trans_m = tdobj.transition_magnetic_dipole()
for i, ei in enumerate(tdobj.e):
m = trans_m[i]
log.info('%3d %11.4f %11.4f %11.4f',
i+1, m[0], m[1], m[2])
return tdobj
def _contract_multipole(tdobj, ints, hermi=True, xy=None):
if xy is None: xy = tdobj.xy
maska, maskb = tdobj.get_frozen_mask()
mo_coeff = (tdobj._scf.mo_coeff[0][:, maska], tdobj._scf.mo_coeff[1][:, maskb])
mo_occ = (tdobj._scf.mo_occ[0][maska], tdobj._scf.mo_occ[1][maskb])
orbo_a = mo_coeff[0][:,mo_occ[0]==1]
orbv_a = mo_coeff[0][:,mo_occ[0]==0]
orbo_b = mo_coeff[1][:,mo_occ[1]==1]
orbv_b = mo_coeff[1][:,mo_occ[1]==0]
ints_a = numpy.einsum('...pq,pi,qj->...ij', ints, orbo_a, orbv_a.conj())
ints_b = numpy.einsum('...pq,pi,qj->...ij', ints, orbo_b, orbv_b.conj())
pol = [(numpy.einsum('...ij,ij->...', ints_a, x[0]) +
numpy.einsum('...ij,ij->...', ints_b, x[1])) for x,y in xy]
pol = numpy.array(pol)
y = xy[0][1]
if isinstance(y[0], numpy.ndarray):
pol_y = [(numpy.einsum('...ij,ij->...', ints_a, y[0]) +
numpy.einsum('...ij,ij->...', ints_b, y[1])) for x,y in xy]
if hermi:
pol += pol_y
else: # anti-Hermitian
pol -= pol_y
return pol
[docs]
class TDBase(rhf.TDBase):
[docs]
@lib.with_doc(get_ab.__doc__)
def get_ab(self, mf=None, frozen=None):
if mf is None: mf = self._scf
if frozen is None: frozen = self.frozen
return get_ab(mf, frozen=frozen)
get_frozen_mask = get_frozen_mask
analyze = analyze
get_nto = get_nto
_contract_multipole = _contract_multipole # needed by transition dipoles
[docs]
@lib.with_doc(rhf.TDA.__doc__)
class TDA(TDBase):
singlet = None
[docs]
def gen_vind(self, mf=None):
'''Generate function to compute Ax'''
assert mf is None or mf is self._scf
return _gen_tda_operation(self, wfnsym=self.wfnsym)
[docs]
def get_init_guess(self, mf, nstates=None, wfnsym=None, return_symmetry=False):
if nstates is None: nstates = self.nstates
if wfnsym is None: wfnsym = self.wfnsym
mol = mf.mol
maska, maskb = self.get_frozen_mask()
mo_energy = (mf.mo_energy[0][maska], mf.mo_energy[1][maskb])
mo_occ = (mf.mo_occ[0][maska], mf.mo_occ[1][maskb])
occidxa = numpy.where(mo_occ[0]>0)[0]
occidxb = numpy.where(mo_occ[1]>0)[0]
viridxa = numpy.where(mo_occ[0]==0)[0]
viridxb = numpy.where(mo_occ[1]==0)[0]
e_ia_a = mo_energy[0][viridxa] - mo_energy[0][occidxa,None]
e_ia_b = mo_energy[1][viridxb] - mo_energy[1][occidxb,None]
nov = e_ia_a.size + e_ia_b.size
nstates = min(nstates, nov)
if (wfnsym is not None or return_symmetry) and mf.mol.symmetry:
x_sym_a, x_sym_b = _get_x_sym_table(self)
if wfnsym is not None:
if isinstance(wfnsym, str):
wfnsym = symm.irrep_name2id(mol.groupname, wfnsym)
wfnsym = wfnsym % 10 # convert to D2h subgroup
e_ia_a[x_sym_a != wfnsym] = 1e99
e_ia_b[x_sym_b != wfnsym] = 1e99
nov_allowed = (numpy.count_nonzero(x_sym_a == wfnsym) +
numpy.count_nonzero(x_sym_b == wfnsym))
nstates = min(nstates, nov_allowed)
e_ia = numpy.append(e_ia_a.ravel(), e_ia_b.ravel())
# Find the nstates-th lowest energy gap
e_threshold = numpy.partition(e_ia, nstates-1)[nstates-1]
e_threshold += self.deg_eia_thresh
idx = numpy.where(e_ia <= e_threshold)[0]
x0 = numpy.zeros((idx.size, nov))
for i, j in enumerate(idx):
x0[i, j] = 1 # Koopmans' excitations
if return_symmetry:
if mf.mol.symmetry:
x_sym = numpy.append(x_sym_a.ravel(), x_sym_b.ravel())
x0sym = x_sym[idx]
else:
x0sym = None
return x0, x0sym
else:
return x0
[docs]
def kernel(self, x0=None, nstates=None):
'''TDA diagonalization solver
'''
cpu0 = (logger.process_clock(), logger.perf_counter())
self.check_sanity()
self.dump_flags()
if nstates is None:
nstates = self.nstates
else:
self.nstates = nstates
log = logger.Logger(self.stdout, self.verbose)
mol = self.mol
vind, hdiag = self.gen_vind(self._scf)
precond = self.get_precond(hdiag)
def pickeig(w, v, nroots, envs):
idx = numpy.where(w > self.positive_eig_threshold)[0]
return w[idx], v[:,idx], idx
x0sym = None
if x0 is None:
x0, x0sym = self.get_init_guess(
self._scf, self.nstates, return_symmetry=True)
elif mol.symmetry:
x_sym_a, x_sym_b = _get_x_sym_table(self)
x_sym = numpy.append(x_sym_a.ravel(), x_sym_b.ravel())
x0sym = [rhf._guess_wfnsym_id(self, x_sym, x) for x in x0]
self.converged, self.e, x1 = lr_eigh(
vind, x0, precond, tol_residual=self.conv_tol, lindep=self.lindep,
nroots=nstates, x0sym=x0sym, pick=pickeig, max_cycle=self.max_cycle,
max_memory=self.max_memory, verbose=log)
maska, maskb = self.get_frozen_mask()
nmo = self._scf.mo_occ[0][maska].size
nocca = (self._scf.mo_occ[0][maska]>0).sum()
noccb = (self._scf.mo_occ[1][maskb]>0).sum()
nvira = nmo - nocca
nvirb = nmo - noccb
self.xy = [((xi[:nocca*nvira].reshape(nocca,nvira), # X_alpha
xi[nocca*nvira:].reshape(noccb,nvirb)), # X_beta
(0, 0)) # (Y_alpha, Y_beta)
for xi in x1]
if self.chkfile:
lib.chkfile.save(self.chkfile, 'tddft/e', self.e)
lib.chkfile.save(self.chkfile, 'tddft/xy', self.xy)
log.timer('TDA', *cpu0)
self._finalize()
return self.e, self.xy
[docs]
def Gradients(self):
if getattr(self._scf, 'with_df', None):
logger.warn(self, 'TDDFT Gradients with DF approximation is not available. '
'TDDFT Gradients are computed using exact integrals')
from pyscf.grad import tduhf
return tduhf.Gradients(self)
to_gpu = lib.to_gpu
CIS = TDA
[docs]
def gen_tdhf_operation(mf, fock_ao=None, wfnsym=None, with_nlc=True):
'''Generate function to compute
[ A B ][X]
[-B* -A*][Y]
'''
td = TDHF(mf)
td.exclude_nlc = not with_nlc
return _gen_tdhf_operation(td, fock_ao, wfnsym)
def _gen_tdhf_operation(td, fock_ao=None, wfnsym=None):
mf = td._scf
mol = mf.mol
maska, maskb = td.get_frozen_mask()
mo_coeff = (mf.mo_coeff[0][:, maska], mf.mo_coeff[1][:, maskb])
mo_energy = (mf.mo_energy[0][maska], mf.mo_energy[1][maskb])
mo_occ = (mf.mo_occ[0][maska], mf.mo_occ[1][maskb])
nao, nmo = mo_coeff[0].shape
occidxa = numpy.where(mo_occ[0]>0)[0]
occidxb = numpy.where(mo_occ[1]>0)[0]
viridxa = numpy.where(mo_occ[0]==0)[0]
viridxb = numpy.where(mo_occ[1]==0)[0]
nocca = len(occidxa)
noccb = len(occidxb)
nvira = len(viridxa)
nvirb = len(viridxb)
orboa = mo_coeff[0][:,occidxa]
orbob = mo_coeff[1][:,occidxb]
orbva = mo_coeff[0][:,viridxa]
orbvb = mo_coeff[1][:,viridxb]
if wfnsym is not None and mol.symmetry:
if isinstance(wfnsym, str):
wfnsym = symm.irrep_name2id(mol.groupname, wfnsym)
wfnsym = wfnsym % 10 # convert to D2h subgroup
x_sym_a, x_sym_b = _get_x_sym_table(td)
sym_forbid = numpy.append(x_sym_a.ravel(), x_sym_b.ravel()) != wfnsym
e_ia_a = mo_energy[0][viridxa] - mo_energy[0][occidxa,None]
e_ia_b = mo_energy[1][viridxb] - mo_energy[1][occidxb,None]
e_ia = hdiag = numpy.hstack((e_ia_a.ravel(), e_ia_b.ravel()))
if wfnsym is not None and mol.symmetry:
hdiag[sym_forbid] = 0
mem_now = lib.current_memory()[0]
max_memory = max(2000, mf.max_memory*.8-mem_now)
vresp = td.gen_response(hermi=0, max_memory=max_memory)
def vind(xys):
nz = len(xys)
xys = numpy.asarray(xys).reshape(nz,2,-1)
if wfnsym is not None and mol.symmetry:
# shape(nz,2,-1): 2 ~ X,Y
xys = numpy.copy(xys)
xys[:,:,sym_forbid] = 0
xs, ys = xys.transpose(1,0,2)
xa = xs[:,:nocca*nvira].reshape(nz,nocca,nvira)
xb = xs[:,nocca*nvira:].reshape(nz,noccb,nvirb)
ya = ys[:,:nocca*nvira].reshape(nz,nocca,nvira)
yb = ys[:,nocca*nvira:].reshape(nz,noccb,nvirb)
dmsa = lib.einsum('xov,pv,qo->xpq', xa, orbva, orboa.conj())
dmsb = lib.einsum('xov,pv,qo->xpq', xb, orbvb, orbob.conj())
dmsa += lib.einsum('xov,qv,po->xpq', ya, orbva.conj(), orboa)
dmsb += lib.einsum('xov,qv,po->xpq', yb, orbvb.conj(), orbob)
v1ao = vresp(numpy.asarray((dmsa,dmsb)))
v1a_top = lib.einsum('xpq,qo,pv->xov', v1ao[0], orboa, orbva.conj())
v1b_top = lib.einsum('xpq,qo,pv->xov', v1ao[1], orbob, orbvb.conj())
v1a_bot = lib.einsum('xpq,po,qv->xov', v1ao[0], orboa.conj(), orbva)
v1b_bot = lib.einsum('xpq,po,qv->xov', v1ao[1], orbob.conj(), orbvb)
v1_top = xs * e_ia # AX
v1_bot = ys * e_ia # AY
v1_top[:,:nocca*nvira] += v1a_top.reshape(nz,-1)
v1_bot[:,:nocca*nvira] += v1a_bot.reshape(nz,-1)
v1_top[:,nocca*nvira:] += v1b_top.reshape(nz,-1)
v1_bot[:,nocca*nvira:] += v1b_bot.reshape(nz,-1)
if wfnsym is not None and mol.symmetry:
v1_top[:,sym_forbid] = 0
v1_bot[:,sym_forbid] = 0
hx = numpy.hstack((v1_top, -v1_bot))
return hx
hdiag = numpy.hstack((hdiag, -hdiag))
return vind, hdiag
[docs]
class TDHF(TDBase):
singlet = None
[docs]
@lib.with_doc(gen_tdhf_operation.__doc__)
def gen_vind(self, mf=None):
assert mf is None or mf is self._scf
return _gen_tdhf_operation(self, None, self.wfnsym)
[docs]
def get_init_guess(self, mf, nstates=None, wfnsym=None, return_symmetry=False):
if return_symmetry:
x0, x0sym = TDA.get_init_guess(self, mf, nstates, wfnsym, return_symmetry)
y0 = numpy.zeros_like(x0)
return numpy.hstack([x0, y0]), x0sym
else:
x0 = TDA.get_init_guess(self, mf, nstates, wfnsym, return_symmetry)
y0 = numpy.zeros_like(x0)
return numpy.hstack([x0, y0])
[docs]
def kernel(self, x0=None, nstates=None):
'''TDHF diagonalization with non-Hermitian eigenvalue solver
'''
log = logger.new_logger(self)
cpu0 = (logger.process_clock(), logger.perf_counter())
self.check_sanity()
self.dump_flags()
if nstates is None:
nstates = self.nstates
else:
self.nstates = nstates
mol = self.mol
real_system = True
# handle single kpt PBC SCF
if getattr(self._scf, 'kpt', None) is not None:
from pyscf.pbc.lib.kpts_helper import gamma_point
real_system = (gamma_point(self._scf.kpt) and
self._scf.mo_coeff[0].dtype == numpy.double)
real_eig_solver = real_system
vind, hdiag = self.gen_vind(self._scf)
precond = self.get_precond(hdiag)
if real_eig_solver:
eig = real_eig
pickeig = None
else:
eig = lr_eig
# We only need positive eigenvalues
def pickeig(w, v, nroots, envs):
realidx = numpy.where((abs(w.imag) < REAL_EIG_THRESHOLD) &
(w.real > self.positive_eig_threshold))[0]
return lib.linalg_helper._eigs_cmplx2real(w, v, realidx, real_system)
x0sym = None
if x0 is None:
x0, x0sym = self.get_init_guess(
self._scf, self.nstates, return_symmetry=True)
elif mol.symmetry:
x_sym_a, x_sym_b = _get_x_sym_table(self)
x_sym = y_sym = numpy.append(x_sym_a.ravel(), x_sym_b.ravel())
x_sym = numpy.append(x_sym, y_sym)
x0sym = [rhf._guess_wfnsym_id(self, x_sym, x) for x in x0]
self.converged, self.e, x1 = eig(
vind, x0, precond, tol_residual=self.conv_tol, lindep=self.lindep,
nroots=nstates, x0sym=x0sym, pick=pickeig, max_cycle=self.max_cycle,
max_memory=self.max_memory, verbose=log)
maska, maskb = self.get_frozen_mask()
nmo = self._scf.mo_occ[0][maska].size
nocca = (self._scf.mo_occ[0][maska]>0).sum()
noccb = (self._scf.mo_occ[1][maskb]>0).sum()
nvira = nmo - nocca
nvirb = nmo - noccb
xy = []
for i, z in enumerate(x1):
x, y = z.reshape(2, -1)
norm = lib.norm(x)**2 - lib.norm(y)**2
if norm < 0:
log.warn('TDDFT amplitudes |X| smaller than |Y|')
norm = abs(norm)**-.5
xy.append(((x[:nocca*nvira].reshape(nocca,nvira) * norm, # X_alpha
x[nocca*nvira:].reshape(noccb,nvirb) * norm), # X_beta
(y[:nocca*nvira].reshape(nocca,nvira) * norm, # Y_alpha
y[nocca*nvira:].reshape(noccb,nvirb) * norm)))# Y_beta
self.xy = xy
if self.chkfile:
lib.chkfile.save(self.chkfile, 'tddft/e', self.e)
lib.chkfile.save(self.chkfile, 'tddft/xy', self.xy)
log.timer('TDHF/TDDFT', *cpu0)
self._finalize()
return self.e, self.xy
Gradients = TDA.Gradients
to_gpu = lib.to_gpu
RPA = TDUHF = TDHF
scf.uhf.UHF.TDA = lib.class_as_method(TDA)
scf.uhf.UHF.TDHF = lib.class_as_method(TDHF)
del (OUTPUT_THRESHOLD)