Skip to content

Commit

Permalink
Fix smearning-ROHF
Browse files Browse the repository at this point in the history
  • Loading branch information
sunqm committed Jul 8, 2024
1 parent 6d39f69 commit 0f4962e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 9 deletions.
38 changes: 29 additions & 9 deletions pyscf/scf/addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ def smearing(mf, sigma=None, method=SMEARING_METHOD, mu0=None, fix_spin=False):
return mf

assert not mf.istype('KSCF')
if mf.istype('ROHF'):
# Roothaan Fock matrix does not make much sense for smearing.
# Restore the conventional RHF treatment.
from pyscf import scf
from pyscf import dft
known_class = {
dft.rks_symm.ROKS: dft.rks_symm.RKS,
dft.roks.ROKS : dft.rks.RKS ,
scf.hf_symm.ROHF : scf.hf_symm.RHF ,
scf.rohf.ROHF : scf.hf.RHF ,
}
mf = _object_without_soscf(mf, known_class)
return lib.set_class(_SmearingSCF(mf, sigma, method, mu0, fix_spin),
(_SmearingSCF, mf.__class__))

Expand All @@ -69,21 +81,24 @@ def _gaussian_smearing_occ(mu, mo_energy, sigma):
return 0.5 * scipy.special.erfc((mo_energy - mu) / sigma)

def _smearing_optimize(f_occ, mo_es, nocc, sigma):
def nelec_cost_fn(m, mo_es, nocc):
def nelec_cost_fn(m):
mo_occ = f_occ(m, mo_es, sigma)
return (mo_occ.sum() - nocc)**2

fermi = _get_fermi(mo_es, nocc)
res = scipy.optimize.minimize(
nelec_cost_fn, fermi, args=(mo_es, nocc), method='Powell',
nelec_cost_fn, fermi, method='Powell',
options={'xtol': 1e-5, 'ftol': 1e-5, 'maxiter': 10000})
mu = res.x
mo_occs = f_occ(mu, mo_es, sigma)
return mu, mo_occs

def _get_fermi(mo_energy, nocc):
mo_e_sorted = numpy.sort(mo_energy)
return mo_e_sorted[nocc-1]
if isinstance(nocc, int):
return mo_e_sorted[nocc-1]
else: # nocc = ?.5 or nocc = ?.0
return mo_e_sorted[int(nocc+.5) - 1]

class _SmearingSCF:

Expand Down Expand Up @@ -123,7 +138,7 @@ def get_occ(self, mo_energy=None, mo_coeff=None):

is_uhf = self.istype('UHF')
is_rhf = self.istype('RHF')
is_rohf = self.istype('ROHF')
is_rohf = is_rhf and self.mol.spin != 0

sigma = self.sigma
if self.smearing_method.lower() == 'fermi':
Expand All @@ -134,9 +149,14 @@ def get_occ(self, mo_energy=None, mo_coeff=None):
if self.fix_spin and (is_uhf or is_rohf): # spin separated fermi level
if is_rohf: # treat rohf as uhf
mo_es = (mo_energy, mo_energy)
nocc = self.mol.nelec
# ROHF with different occs for alpha and beta electrons lead to
# two Fock matrices, it's not clear how to define a single
# Fock matrix from the two Fock.
raise RuntimeError('Ill defined smearing-ROHF with fix_spin=True')
else:
mo_es = mo_energy
nocc = self.nelec
nocc = self.nelec
if self.mu0 is None:
mu_a, occa = _smearing_optimize(f_occ, mo_es[0], nocc[0], sigma)
mu_b, occb = _smearing_optimize(f_occ, mo_es[1], nocc[1], sigma)
Expand Down Expand Up @@ -168,16 +188,16 @@ def get_occ(self, mo_energy=None, mo_coeff=None):
if is_rohf:
mo_occs = mo_occs[0] + mo_occs[1]
else: # all orbitals treated with the same fermi level
nocc = nelectron = self.mol.nelectron
nelectron = self.mol.nelectron
if is_uhf:
mo_es = numpy.hstack(mo_energy)
else:
mo_es = mo_energy
if is_rhf:
nocc = (nelectron + 1) // 2
nelectron = nelectron * .5

if self.mu0 is None:
mu, mo_occs = _smearing_optimize(f_occ, mo_es, nocc, sigma)
mu, mo_occs = _smearing_optimize(f_occ, mo_es, nelectron, sigma)
else:
# If mu0 is given, fix mu instead of electron number. XXX -Chong Sun
mu = self.mu0
Expand All @@ -188,7 +208,7 @@ def get_occ(self, mo_energy=None, mo_coeff=None):
mo_occs *= 2
self.entropy *= 2

fermi = _get_fermi(mo_es, nocc)
fermi = _get_fermi(mo_es, nelectron)
logger.debug(self, ' Fermi level %g Sum mo_occ = %s should equal nelec = %s',
fermi, mo_occs.sum(), nelectron)
logger.info(self, ' sigma = %g Optimized mu = %.12g entropy = %.12g',
Expand Down
21 changes: 21 additions & 0 deletions pyscf/scf/test/test_addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ def test_remove_lindep(self):
lindep=1e-9).run()
self.assertAlmostEqual(mf.e_tot, -1.6291001503057689, 7)

@unittest.skip('Smearing ROHF with fix_spin does not work')
def test_rohf_smearing(self):
# Fe2 from https://doi.org/10.1021/acs.jpca.1c05769
mol = gto.M(
Expand All @@ -438,6 +439,25 @@ def test_rohf_smearing(self):
self.assertAlmostEqual(myhf_s.e_tot, -244.200255453, 6)
self.assertAlmostEqual(myhf_s.entropy, 3.585155, 4)

def test_rohf_smearing1(self):
mol = gto.M(atom = '''
7 0. 0 -0.7
7 0. 0 0.7''',
charge = -1,
spin = 1)
mf = mol.RHF()
mf = addons.smearing(mf, sigma=0.1)
mf.kernel()
self.assertAlmostEqual(mf.mo_occ.sum(), 15, 8)
self.assertAlmostEqual(mf.e_tot, -106.9310800402, 8)

def test_uhf_smearing(self):
mol = gto.M(
atom='''
Fe 0. 0. 0.
Fe 2.01 0. 0.
''', basis="lanl2dz", ecp="lanl2dz", symmetry=False, unit='Angstrom',
spin=6, charge=0, verbose=0)
myhf_s = scf.UHF(mol)
myhf_s = addons.smearing_(myhf_s, sigma=0.01, method='fermi', fix_spin=True)
myhf_s.kernel()
Expand Down Expand Up @@ -487,6 +507,7 @@ def _hubbard_hamilts_pbc(L, U):
self.assertAlmostEqual(mf_ft.e_tot, -2.93405853397115, 5)
self.assertAlmostEqual(mf_ft.entropy, 0.11867520273160392, 5)


if __name__ == "__main__":
print("Full Tests for addons")
unittest.main()

0 comments on commit 0f4962e

Please sign in to comment.