Source code for pyscf.pbc.scf.kuhf_ksymm

#!/usr/bin/env python
# Copyright 2020-2023 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: Xing Zhang <zhangxing.nju@gmail.com>
#

import numpy as np
from pyscf import __config__
from pyscf import lib
from pyscf.lib import logger
from pyscf.scf import hf as mol_hf
from pyscf.pbc.lib import kpts as libkpts
from pyscf.pbc.scf import khf_ksymm, khf, kuhf
from pyscf.pbc.lib.kpts import KPoints

[docs] @lib.with_doc(kuhf.get_occ.__doc__) def get_occ(mf, mo_energy_kpts=None, mo_coeff_kpts=None): if mo_energy_kpts is None: mo_energy_kpts = mf.mo_energy kpts = mf.kpts assert isinstance(kpts, KPoints) nocc_a, nocc_b = mf.nelec mo_energy_kpts = kpts.transform_mo_energy(mo_energy_kpts) mo_energy = np.sort(np.hstack(mo_energy_kpts[0])) fermi_a = mo_energy[nocc_a-1] mo_occ_kpts = [[], []] for mo_e in mo_energy_kpts[0]: mo_occ_kpts[0].append((mo_e <= fermi_a).astype(np.double)) if nocc_a < len(mo_energy): logger.info(mf, 'alpha HOMO = %.12g LUMO = %.12g', fermi_a, mo_energy[nocc_a]) else: logger.info(mf, 'alpha HOMO = %.12g (no LUMO because of small basis) ', fermi_a) if nocc_b > 0: mo_energy = np.sort(np.hstack(mo_energy_kpts[1])) fermi_b = mo_energy[nocc_b-1] for mo_e in mo_energy_kpts[1]: mo_occ_kpts[1].append((mo_e <= fermi_b).astype(np.double)) if nocc_b < len(mo_energy): logger.info(mf, 'beta HOMO = %.12g LUMO = %.12g', fermi_b, mo_energy[nocc_b]) else: logger.info(mf, 'beta HOMO = %.12g (no LUMO because of small basis) ', fermi_b) else: for mo_e in mo_energy_kpts[1]: mo_occ_kpts[1].append(np.zeros_like(mo_e)) if mf.verbose >= logger.DEBUG: np.set_printoptions(threshold=len(mo_energy)) logger.debug(mf, ' k-point alpha mo_energy') for k,kpt in enumerate(mf.cell.get_scaled_kpts(kpts, kpts_in_ibz=False)): if (np.count_nonzero(mo_occ_kpts[0][k]) > 0 and np.count_nonzero(mo_occ_kpts[0][k] == 0) > 0): logger.debug(mf, ' %2d (%6.3f %6.3f %6.3f) %s %s', k, kpt[0], kpt[1], kpt[2], np.sort(mo_energy_kpts[0][k][mo_occ_kpts[0][k]> 0]), np.sort(mo_energy_kpts[0][k][mo_occ_kpts[0][k]==0])) else: logger.debug(mf, ' %2d (%6.3f %6.3f %6.3f) %s', k, kpt[0], kpt[1], kpt[2], mo_energy_kpts[0][k]) logger.debug(mf, ' k-point beta mo_energy') for k,kpt in enumerate(mf.cell.get_scaled_kpts(kpts, kpts_in_ibz=False)): if (np.count_nonzero(mo_occ_kpts[1][k]) > 0 and np.count_nonzero(mo_occ_kpts[1][k] == 0) > 0): logger.debug(mf, ' %2d (%6.3f %6.3f %6.3f) %s %s', k, kpt[0], kpt[1], kpt[2], np.sort(mo_energy_kpts[1][k][mo_occ_kpts[1][k]> 0]), np.sort(mo_energy_kpts[1][k][mo_occ_kpts[1][k]==0])) else: logger.debug(mf, ' %2d (%6.3f %6.3f %6.3f) %s', k, kpt[0], kpt[1], kpt[2], mo_energy_kpts[1][k]) np.set_printoptions(threshold=1000) if isinstance(kpts, KPoints): mo_occ_kpts[0] = kpts.check_mo_occ_symmetry(mo_occ_kpts[0], tol=1e-4) mo_occ_kpts[1] = kpts.check_mo_occ_symmetry(mo_occ_kpts[1], tol=1e-4) return mo_occ_kpts
[docs] @lib.with_doc(kuhf.energy_elec.__doc__) def energy_elec(mf, dm_kpts=None, h1e_kpts=None, vhf_kpts=None): if dm_kpts is None: dm_kpts = mf.make_rdm1() if h1e_kpts is None: h1e_kpts = mf.get_hcore() if vhf_kpts is None: vhf_kpts = mf.get_veff(mf.cell, dm_kpts) wtk = mf.kpts.weights_ibz e1 = np.einsum('k,kij,kji', wtk, dm_kpts[0], h1e_kpts) e1+= np.einsum('k,kij,kji', wtk, dm_kpts[1], h1e_kpts) e_coul = np.einsum('k,kij,kji', wtk, dm_kpts[0], vhf_kpts[0]) * 0.5 e_coul+= np.einsum('k,kij,kji', wtk, dm_kpts[1], vhf_kpts[1]) * 0.5 mf.scf_summary['e1'] = e1.real mf.scf_summary['e2'] = e_coul.real logger.debug(mf, 'E1 = %s E_coul = %s', e1, e_coul) if kuhf.CHECK_COULOMB_IMAG and abs(e_coul.imag > mf.cell.precision*10): logger.warn(mf, "Coulomb energy has imaginary part %s. " "Coulomb integrals (e-e, e-N) may not converge !", e_coul.imag) return (e1+e_coul).real, e_coul.real
get_rho = khf_ksymm.get_rho
[docs] class KsymAdaptedKUHF(khf_ksymm.KsymAdaptedKSCF, kuhf.KUHF): """ KUHF with k-point symmetry """ get_occ = get_occ energy_elec = energy_elec get_rho = get_rho to_ks = kuhf.KUHF.to_ks convert_from_ = kuhf.KUHF.convert_from_ def __init__(self, cell, kpts=libkpts.KPoints(), exxdiv=getattr(__config__, 'pbc_scf_SCF_exxdiv', 'ewald'), use_ao_symmetry=True): khf_ksymm.ksymm_scf_common_init(self, cell, kpts, use_ao_symmetry) kuhf.KUHF.__init__(self, cell, kpts, exxdiv) @property def nelec(self): if self._nelec is not None: return self._nelec else: cell = self.cell nkpts = self.kpts.nkpts ne = cell.tot_electrons(nkpts) nalpha = (ne + cell.spin) // 2 nbeta = nalpha - cell.spin if nalpha + nbeta != ne: raise RuntimeError('Electron number %d and spin %d are not consistent\n' 'Note cell.spin = 2S = Nalpha - Nbeta, not 2S+1' % (ne, cell.spin)) return nalpha, nbeta @nelec.setter def nelec(self, x): self._nelec = x
[docs] def dump_flags(self, verbose=None): khf_ksymm.KsymAdaptedKSCF.dump_flags(self, verbose) logger.info(self, 'number of electrons per unit cell ' 'alpha = %d beta = %d', *self.nelec) return self
[docs] def get_init_guess(self, cell=None, key='minao', s1e=None): if s1e is None: s1e = self.get_ovlp(cell) dm_kpts = mol_hf.SCF.get_init_guess(self, cell, key) assert dm_kpts.shape[0]==2 if dm_kpts.ndim != 4: nkpts = self.kpts.nkpts_ibz # dm[spin,nao,nao] at gamma point -> dm_kpts[spin,nkpts,nao,nao] dm_kpts = np.repeat(dm_kpts[:,None,:,:], nkpts, axis=1) elif dm_kpts.shape[1] != self.kpts.nkpts_ibz: dm_kpts = dm_kpts[:,self.kpts.ibz2bz] ne = lib.einsum('k,xkij,kji->x', self.kpts.weights_ibz, dm_kpts, s1e).real nkpts = self.kpts.nkpts ne *= nkpts nelec = np.asarray(self.nelec) if np.any(abs(ne - nelec) > 0.01*nkpts): logger.debug(self, 'Big error detected in the electron number ' 'of initial guess density matrix (Ne/cell = %g)!\n' ' This can cause huge error in Fock matrix and ' 'lead to instability in SCF for low-dimensional ' 'systems.\n DM is normalized wrt the number ' 'of electrons %s', ne.mean()/nkpts, nelec/nkpts) dm_kpts *= (nelec / ne).reshape(2,-1,1,1) return dm_kpts
[docs] def eig(self, h_kpts, s_kpts): e_a, c_a = khf_ksymm.KsymAdaptedKSCF.eig(self, h_kpts[0], s_kpts) e_b, c_b = khf_ksymm.KsymAdaptedKSCF.eig(self, h_kpts[1], s_kpts) return (e_a,e_b), (c_a,c_b)
[docs] def get_orbsym(self, mo_coeff=None, s=None): if mo_coeff is None: mo_coeff = self.mo_coeff if s is None: s = self.get_ovlp() orbsym_a = khf_ksymm.KsymAdaptedKSCF.get_orbsym(self, mo_coeff[0], s) orbsym_b = khf_ksymm.KsymAdaptedKSCF.get_orbsym(self, mo_coeff[1], s) return (orbsym_a, orbsym_b)
orbsym = property(get_orbsym) def _finalize(self): from pyscf.scf import chkfile as mol_chkfile kuhf.KUHF._finalize(self) if not self.use_ao_symmetry: return orbsym = self.get_orbsym() for s in range(2): for k, mo_e in enumerate(self.mo_energy[s]): idx = np.argsort(mo_e.round(9), kind='stable') self.mo_energy[s][k] = self.mo_energy[s][k][idx] self.mo_occ[s][k] = self.mo_occ[s][k][idx] self.mo_coeff[s][k] = lib.tag_array(self.mo_coeff[s][k][:,idx], orbsym=orbsym[s][k][idx]) self.dump_chk({'e_tot': self.e_tot, 'mo_energy': self.mo_energy, 'mo_coeff': self.mo_coeff, 'mo_occ': self.mo_occ}) return self
KUHF = KsymAdaptedKUHF