Source code for pyscf.eph.eph_fd

#!/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: Yang Gao <younggao1994@gmail.com>

#
'''
electron-phonon matrix from finite difference
'''

import numpy as np
from pyscf import scf, dft, gto, hessian
from pyscf.eph import rhf as rhf_eph
from pyscf.lib import logger
from pyscf.data.nist import MP_ME

CUTOFF_FREQUENCY = rhf_eph.CUTOFF_FREQUENCY
KEEP_IMAG_FREQUENCY = rhf_eph.KEEP_IMAG_FREQUENCY

[docs] def run_mfs(mf, mols_a, mols_b): nconfigs = len(mols_a) dm0 = mf.make_rdm1() mflist = [] for i in range(nconfigs): mf1 = mf.copy() mf1.reset(mols_a[i]) mf2 = mf.copy() mf2.reset(mols_b[i]) mf1.kernel(dm0=dm0) mf2.kernel(dm0=dm0) if not (mf1.converged): logger.warn(mf, "%ith config mf1 not converged", i) if not (mf2.converged): logger.warn(mf, "%ith config mf2 not converged", i) mflist.append((mf1, mf2)) return mflist
[docs] def get_mode(mf, cutoff_frequency=CUTOFF_FREQUENCY, keep_imag_frequency=KEEP_IMAG_FREQUENCY): hmat = mf.Hessian().kernel() w_new, c_new = rhf_eph.solve_hmat(mf.mol, hmat, cutoff_frequency, keep_imag_frequency) return w_new, c_new
[docs] def gen_moles(mol, disp): """From the given equilibrium molecule, generate 3N molecules with a shift on + displacement(mol_a) and - displacement(mol_s) on each Cartesian coordinates """ coords = mol.atom_coords() natoms = len(coords) mol_a, mol_s = [],[] for i in range(natoms): for x in range(3): new_coords_a, new_coords_s = coords.copy(), coords.copy() new_coords_a[i][x] += disp new_coords_s[i][x] -= disp atoma = [[mol.atom_symbol(j), coord] for (j, coord) in zip(range(natoms), new_coords_a)] atoms = [[mol.atom_symbol(j), coord] for (j, coord) in zip(range(natoms), new_coords_s)] mol_a.append(mol.set_geom_(atoma, inplace=False, unit='B')) mol_s.append(mol.set_geom_(atoms, inplace=False, unit='B')) return mol_a, mol_s
[docs] def get_vmat(mf, mfset, disp): vmat=[] mygrad = mf.nuc_grad_method() ve = mygrad.get_veff() + mygrad.get_hcore() + mf.mol.intor("int1e_ipkin") RESTRICTED = (ve.ndim==3) aoslice = mf.mol.aoslice_by_atom() for ki, (mf1, mf2) in enumerate(mfset): atmid, axis = np.divmod(ki, 3) p0, p1 = aoslice[atmid][2:] vfull1 = mf1.get_veff() + mf1.get_hcore() - mf1.mol.intor_symmetric('int1e_kin') # <u+|V+|v+> vfull2 = mf2.get_veff() + mf2.get_hcore() - mf2.mol.intor_symmetric('int1e_kin') # <u-|V-|v-> vfull = (vfull1 - vfull2)/disp # (<p+|V+|q+>-<p-|V-|q->)/dR if RESTRICTED: vfull[p0:p1] -= ve[axis,p0:p1] vfull[:,p0:p1] -= ve[axis,p0:p1].T else: vfull[:,p0:p1] -= ve[:,axis,p0:p1] vfull[:,:,p0:p1] -= ve[:,axis,p0:p1].transpose(0,2,1) vmat.append(vfull) return np.asarray(vmat)
[docs] def kernel(mf, disp=1e-4, mo_rep=False, cutoff_frequency=CUTOFF_FREQUENCY, keep_imag_frequency=KEEP_IMAG_FREQUENCY): if isinstance(mf, scf.hf.KohnShamDFT): mf.grids.build() if not mf.converged: mf.kernel() RESTRICTED = (mf.mo_coeff.ndim==2) mol = mf.mol omega, vec = get_mode(mf, cutoff_frequency, keep_imag_frequency) mass = mol.atom_mass_list() * MP_ME vec = rhf_eph._freq_mass_weighted_vec(vec, omega, mass) mols_a, mols_b = gen_moles(mol, disp/2.0) # generate a bunch of molecules with disp/2 on each cartesian coord mfset = run_mfs(mf, mols_a, mols_b) # run mean field calculations on all these molecules vmat = get_vmat(mf, mfset, disp) # extracting <p|dV|q>/dR if mo_rep: if RESTRICTED: vmat = np.einsum('xuv,up,vq->xpq', vmat, mf.mo_coeff.conj(), mf.mo_coeff) else: vmat = np.einsum('xsuv,sup,svq->xspq', vmat, mf.mo_coeff.conj(), mf.mo_coeff) if RESTRICTED: mat = np.einsum('xJ,xpq->Jpq', vec, vmat) else: mat = np.einsum('xJ,xspq->sJpq', vec, vmat) return mat, omega
if __name__ == '__main__': mol = gto.M() mol.atom = '''O 0.000000000000 0.00000000136 0.459620634131 H 0.000000000000 -0.77050867841 1.139170094494 H 0.000000000000 0.77050867841 1.139170094494''' mol.unit = 'angstrom' mol.basis = 'sto3g' mol.verbose=4 mol.build() # this is a pre-computed relaxed geometry mf = dft.RKS(mol) mf.grids.level=4 mf.grids.build() mf.xc = 'b3lyp' mf.conv_tol = 1e-14 mf.conv_tol_grad = 1e-8 mf.kernel() grad = mf.nuc_grad_method().kernel() print("Force on the atoms/au:") print(grad) assert (abs(grad).max()<1e-5) mat, omega = kernel(mf) matmo, _ = kernel(mf, mo_rep=True) from pyscf.eph.rks import EPH myeph = EPH(mf) eph, _ = myeph.kernel() ephmo, _ = myeph.kernel(mo_rep=True) print("***Testing on RKS***") for i in range(len(mat)): print("AO",min(np.linalg.norm(eph[i]-mat[i]), np.linalg.norm(eph[i]+mat[i]))) print("AO", min(abs(eph[i]-mat[i]).max(), abs(eph[i]+mat[i]).max())) print("MO",min(np.linalg.norm(ephmo[i]-matmo[i]), np.linalg.norm(ephmo[i]+matmo[i]))) print("MO", min(abs(ephmo[i]-matmo[i]).max(), abs(ephmo[i]+matmo[i]).max()))