Skip to content

Commit 8245113

Browse files
committed
format
1 parent 391b939 commit 8245113

2 files changed

Lines changed: 137 additions & 86 deletions

File tree

‎pyscf/scf/chebyshev_scf.py‎

Lines changed: 104 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
# Author: Naoki Kitamura
1717
#
1818

19-
'''
19+
"""
2020
Diagonalization-free Hartree-Fock density construction with Chebyshev filters.
2121
2222
This module provides an experimental SCF driver that approximates the
2323
finite-temperature occupation projector by a Chebyshev polynomial expansion.
2424
The density matrix is built from filtered basis vectors in an orthogonalized AO
2525
representation, avoiding an explicit Fock diagonalization in each SCF cycle.
26-
'''
26+
"""
2727

2828

2929
import numpy
@@ -81,6 +81,7 @@ def block_inv_sqrt(s, thresh=1e-12):
8181
# ブロックを対角に並べる
8282
return s_inv_sqrt
8383

84+
8485
def inv_sqrt(s, thresh=1e-12, full_inv=False):
8586
"""
8687
Compute an inverse square root of a symmetric positive-definite matrix.
@@ -107,6 +108,7 @@ def inv_sqrt(s, thresh=1e-12, full_inv=False):
107108
else:
108109
return block_inv_sqrt(s, thresh=thresh)
109110

111+
110112
def to_csr_matrix(matrix, threshold=1e-5):
111113
"""
112114
Convert a dense array to CSR format after dropping small elements.
@@ -126,8 +128,9 @@ def to_csr_matrix(matrix, threshold=1e-5):
126128
matrix = numpy.where(numpy.abs(matrix) < threshold, 0.0, matrix)
127129
return csr_matrix(matrix)
128130

131+
129132
class ChebyshevSCF(hf.SCF):
130-
'''
133+
"""
131134
Restricted Hartree-Fock SCF driver using Chebyshev density filtering.
132135
133136
The class follows the :class:`pyscf.scf.hf.SCF` interface but replaces the
@@ -156,7 +159,8 @@ class ChebyshevSCF(hf.SCF):
156159
an accepted, documented scope limitation rather than a defect to fix
157160
in this driver -- benchmarking and validation of this class should
158161
likewise be restricted to well-gapped closed-shell systems.
159-
'''
162+
"""
163+
160164
def __init__(self, mol):
161165
"""
162166
Initialize the Chebyshev SCF object for a PySCF molecule.
@@ -200,11 +204,15 @@ def build(self, mol=None):
200204
The built SCF object.
201205
"""
202206
hf.SCF.build(self, mol)
203-
self._keys = self._keys.union(['chebyshev_degree',
204-
'chebyshev_filter_interval',
205-
'auto_degree',
206-
'degree_safety_factor',
207-
'rng_seed'])
207+
self._keys = self._keys.union(
208+
[
209+
"chebyshev_degree",
210+
"chebyshev_filter_interval",
211+
"auto_degree",
212+
"degree_safety_factor",
213+
"rng_seed",
214+
]
215+
)
208216
return self
209217

210218
def get_ovlp_inv_sqrt(self, mol=None, thresh=1e-14, full_inv=False):
@@ -225,8 +233,9 @@ def get_ovlp_inv_sqrt(self, mol=None, thresh=1e-14, full_inv=False):
225233
ndarray
226234
AO overlap inverse square-root matrix.
227235
"""
228-
if mol is None: mol = self.mol
229-
ovlp = mol.intor_symmetric('int1e_ovlp')
236+
if mol is None:
237+
mol = self.mol
238+
ovlp = mol.intor_symmetric("int1e_ovlp")
230239
ovlp_inv_sqrt = inv_sqrt(ovlp, thresh=thresh, full_inv=full_inv)
231240
return ovlp_inv_sqrt
232241

@@ -259,10 +268,11 @@ def _power_method(self, fock, x0, max_cycle=50, tol=1e-6):
259268
if err < tol:
260269
break
261270
e_max = numpy.dot(x1.T, numpy.dot(fock, x1)) / numpy.dot(x1.T, x1)
262-
return e_max[0,0]
271+
return e_max[0, 0]
263272

264-
def _chebyshev_poly_vec(self, v, fock_ortho, mu, degree, beta, a,
265-
vec_threshold=None):
273+
def _chebyshev_poly_vec(
274+
self, v, fock_ortho, mu, degree, beta, a, vec_threshold=None
275+
):
266276
"""
267277
Apply the Chebyshev occupation polynomial to a basis vector.
268278
@@ -305,7 +315,7 @@ def _chebyshev_poly_vec(self, v, fock_ortho, mu, degree, beta, a,
305315
Filtered vector contribution to the density matrix.
306316
"""
307317
if vec_threshold is None:
308-
vec_threshold = getattr(self, 'vec_threshold', 1e-6)
318+
vec_threshold = getattr(self, "vec_threshold", 1e-6)
309319

310320
coeffs = self._calc_coeff(degree, beta=beta, mu=mu, a=a)
311321
# T0 v_
@@ -316,7 +326,7 @@ def _chebyshev_poly_vec(self, v, fock_ortho, mu, degree, beta, a,
316326
if vec_threshold:
317327
v1 = to_csr_matrix(v1.toarray(), threshold=vec_threshold)
318328
y = coeffs[0] * v0 + coeffs[1] * v1
319-
for k in range(2, degree+1):
329+
for k in range(2, degree + 1):
320330
v2 = 2 * fock_ortho @ v1 - v0
321331
if vec_threshold:
322332
v2 = to_csr_matrix(v2.toarray(), threshold=vec_threshold)
@@ -349,7 +359,7 @@ def _calc_coeff(self, n, N=8192, beta=100.0, mu=0.0, a=1.0):
349359
theta = numpy.pi * numpy.arange(N) / (N - 1)
350360
beta_tilde = beta * a
351361
f = 1.0 / (1.0 + numpy.exp(beta_tilde * (numpy.cos(theta) - mu)))
352-
coeffs = numpy.fft.fft(numpy.r_[f, f[-2:0:-1]]).real[:n+1]
362+
coeffs = numpy.fft.fft(numpy.r_[f, f[-2:0:-1]]).real[: n + 1]
353363
coeffs *= 2.0 / (N - 1)
354364
coeffs[0] *= 0.5
355365
return coeffs
@@ -391,12 +401,14 @@ def _spectral_bounds(self, fock):
391401
# filtered density matrix, and the final energy are exactly
392402
# reproducible for a given (mol, beta, rng_seed), not just
393403
# statistically similar run to run.
394-
rng = numpy.random.RandomState(getattr(self, 'rng_seed', 0))
404+
rng = numpy.random.RandomState(getattr(self, "rng_seed", 0))
395405
random_vec = rng.rand(nmo)
396406
random_vec /= numpy.linalg.norm(random_vec)
397407
from pyscf.lib import davidson
408+
398409
def fock_ortho_mv(v):
399410
return fock_ortho @ v
411+
400412
emin, _ = davidson(fock_ortho_mv, random_vec, precond=precond)
401413
emin -= 0.1
402414

@@ -409,7 +421,9 @@ def fock_ortho_mv(v):
409421
b = (emax + emin) / 2.0
410422
return a, b, emin, emax, fock_ortho
411423

412-
def _degree_for_beta_a(self, beta, a, safety_factor=2.0, min_degree=10, max_degree=2000):
424+
def _degree_for_beta_a(
425+
self, beta, a, safety_factor=2.0, min_degree=10, max_degree=2000
426+
):
413427
"""
414428
Pure formula: suggested Chebyshev degree for a given ``beta`` and
415429
spectral half-width ``a``, without re-estimating ``a`` from a Fock
@@ -436,7 +450,9 @@ def _degree_for_beta_a(self, beta, a, safety_factor=2.0, min_degree=10, max_degr
436450
degree = int(numpy.ceil(safety_factor * beta_tilde))
437451
return max(min_degree, min(max_degree, degree))
438452

439-
def _auto_degree(self, fock, beta=None, safety_factor=2.5, min_degree=10, max_degree=2000):
453+
def _auto_degree(
454+
self, fock, beta=None, safety_factor=2.5, min_degree=10, max_degree=2000
455+
):
440456
"""
441457
Estimate a Chebyshev expansion degree sufficient to resolve the
442458
Fermi-Dirac occupation step for the given Fock matrix and ``beta``.
@@ -477,15 +493,28 @@ def _auto_degree(self, fock, beta=None, safety_factor=2.5, min_degree=10, max_de
477493
if beta is None:
478494
beta = self.beta
479495
a, b, emin, emax, _ = self._spectral_bounds(fock)
480-
degree = self._degree_for_beta_a(beta, a, safety_factor=safety_factor,
481-
min_degree=min_degree, max_degree=max_degree)
482-
logger.info(self, 'Auto-selected chebyshev_degree = %d '
483-
'(beta=%.3g, a=%.4f, beta*a=%.3g, safety_factor=%.2f)',
484-
degree, beta, a, beta * a, safety_factor)
496+
degree = self._degree_for_beta_a(
497+
beta,
498+
a,
499+
safety_factor=safety_factor,
500+
min_degree=min_degree,
501+
max_degree=max_degree,
502+
)
503+
logger.info(
504+
self,
505+
"Auto-selected chebyshev_degree = %d "
506+
"(beta=%.3g, a=%.4f, beta*a=%.3g, safety_factor=%.2f)",
507+
degree,
508+
beta,
509+
a,
510+
beta * a,
511+
safety_factor,
512+
)
485513
return degree
486514

487-
488-
def make_rdm1_chebyshev(self, fock, mu, ovlp=None, nmo=None, degree=None, beta=None):
515+
def make_rdm1_chebyshev(
516+
self, fock, mu, ovlp=None, nmo=None, degree=None, beta=None
517+
):
489518
"""
490519
Construct an AO-basis one-particle density matrix by Chebyshev filtering.
491520
@@ -527,17 +556,18 @@ def make_rdm1_chebyshev(self, fock, mu, ovlp=None, nmo=None, degree=None, beta=N
527556

528557
dm = numpy.zeros((fock.shape[0], fock.shape[0]))
529558

530-
identity = csr_matrix((numpy.ones(fock.shape[0]),
531-
(numpy.arange(fock.shape[0]), numpy.arange(fock.shape[0]))),
532-
shape=(fock.shape[0], fock.shape[0]))
559+
identity = csr_matrix(
560+
(
561+
numpy.ones(fock.shape[0]),
562+
(numpy.arange(fock.shape[0]), numpy.arange(fock.shape[0])),
563+
),
564+
shape=(fock.shape[0], fock.shape[0]),
565+
)
533566
normalized_fock = (fock_ortho - b * identity) / a
534567
normalized_fock = to_csr_matrix(normalized_fock.toarray(), threshold=1e-5)
535568

536569
for i in range(nmo):
537-
v = coo_matrix(
538-
([1.0], ([i], [0])),
539-
shape=(nmo, 1)
540-
).tocsr()
570+
v = coo_matrix(([1.0], ([i], [0])), shape=(nmo, 1)).tocsr()
541571
y = self._chebyshev_poly_vec(v, normalized_fock, mu, degree, beta, a)
542572
dm[:, i] = y.toarray().flatten()
543573

@@ -568,10 +598,11 @@ class never occupies orbitals directly, so the converged density
568598
ndarray
569599
AO-basis one-particle density matrix.
570600
"""
571-
if getattr(self, '_dm1', None) is None:
601+
if getattr(self, "_dm1", None) is None:
572602
raise RuntimeError(
573-
'ChebyshevSCF.make_rdm1() called before kernel() has run; '
574-
'no density matrix is available yet.')
603+
"ChebyshevSCF.make_rdm1() called before kernel() has run; "
604+
"no density matrix is available yet."
605+
)
575606
return self._dm1
576607

577608
def _diff_ne(self, nelec, mu, fock):
@@ -619,13 +650,13 @@ def bisect_search_mu(self, nelectrons, fock):
619650
if fock is None:
620651
fock = self.get_fock(self.get_init_guess(self.mol))
621652
mu = bisect(
622-
lambda mu: self._diff_ne(nelectrons, mu, fock),
623-
mu_lo, mu_hi,
624-
xtol=1e-14
653+
lambda mu: self._diff_ne(nelectrons, mu, fock), mu_lo, mu_hi, xtol=1e-14
625654
)
626655
return mu
627656

628-
def kernel(self, conv_dm=1e-3, conv_e=1e-6, max_cycle=100, mixing_beta=0.3, dm1=None):
657+
def kernel(
658+
self, conv_dm=1e-3, conv_e=1e-6, max_cycle=100, mixing_beta=0.3, dm1=None
659+
):
629660
"""
630661
Run the Chebyshev-filtered SCF iteration.
631662
@@ -662,11 +693,17 @@ def kernel(self, conv_dm=1e-3, conv_e=1e-6, max_cycle=100, mixing_beta=0.3, dm1=
662693
ovlp = self.get_ovlp()
663694

664695
if self.auto_degree:
665-
auto_deg = self._auto_degree(fock, beta=self.beta,
666-
safety_factor=self.degree_safety_factor)
696+
auto_deg = self._auto_degree(
697+
fock, beta=self.beta, safety_factor=self.degree_safety_factor
698+
)
667699
if auto_deg > self.chebyshev_degree:
668-
logger.info(self, 'auto_degree: raising chebyshev_degree '
669-
'%d -> %d for this geometry/beta', self.chebyshev_degree, auto_deg)
700+
logger.info(
701+
self,
702+
"auto_degree: raising chebyshev_degree "
703+
"%d -> %d for this geometry/beta",
704+
self.chebyshev_degree,
705+
auto_deg,
706+
)
670707
self.chebyshev_degree = auto_deg
671708

672709
mu = self.bisect_search_mu(self.mol.nelectron, fock)
@@ -689,19 +726,32 @@ def kernel(self, conv_dm=1e-3, conv_e=1e-6, max_cycle=100, mixing_beta=0.3, dm1=
689726
diff = numpy.linalg.norm(dm_new - dm1)
690727
## energy_elec_dm
691728
e = self.energy_elec(dm_new, h1e, veff)[0] + self.mol.energy_nuc()
692-
logger.info(self, 'Cycle %d mu = %.12f diff = %.8f e = %.8f ne = %.8f',
693-
cycle, self.mu, diff, e, ne)
729+
logger.info(
730+
self,
731+
"Cycle %d mu = %.12f diff = %.8f e = %.8f ne = %.8f",
732+
cycle,
733+
self.mu,
734+
diff,
735+
e,
736+
ne,
737+
)
694738
if diff < conv_dm or abs(e - e_save) < conv_e:
695739
converged = True
696740
break
697741
dm1 = dm_new
698742
e_save = e
699743

700744
if not converged:
701-
logger.warn(self, 'ChebyshevSCF did not converge after %d cycles '
702-
'(final |ddm|/|dE| still above conv_dm=%.3g / '
703-
'conv_e=%.3g). Try increasing max_cycle, '
704-
'chebyshev_degree, or beta.', max_cycle, conv_dm, conv_e)
745+
logger.warn(
746+
self,
747+
"ChebyshevSCF did not converge after %d cycles "
748+
"(final |ddm|/|dE| still above conv_dm=%.3g / "
749+
"conv_e=%.3g). Try increasing max_cycle, "
750+
"chebyshev_degree, or beta.",
751+
max_cycle,
752+
conv_dm,
753+
conv_e,
754+
)
705755

706756
## energy_elec_dm
707757
e_tot = self.energy_elec(dm_new, h1e, veff)[0] + self.mol.energy_nuc()
@@ -727,6 +777,6 @@ def kernel(self, conv_dm=1e-3, conv_e=1e-6, max_cycle=100, mixing_beta=0.3, dm1=
727777
self.mo_coeff = mo_coeff
728778
self.mo_occ = mo_occ
729779

730-
logger.info(self, 'ChebyshevSCF converged = %s', self.converged)
731-
logger.info(self, 'Converged SCF energy = %.12f', e_tot)
732-
return e_tot
780+
logger.info(self, "ChebyshevSCF converged = %s", self.converged)
781+
logger.info(self, "Converged SCF energy = %.12f", e_tot)
782+
return e_tot

0 commit comments

Comments
 (0)