def get_ovlp(mf, cell=None, kpts=None): '''Get the overlap AO matrices at sampled k-points. Args: kpts : (nkpts, 3) ndarray Returns: ovlp_kpts : (nkpts, nao, nao) ndarray ''' if cell is None: cell = mf.cell if kpts is None: kpts = mf.kpts # Avoid pbcopt's prescreening in the lattice sum, for better accuracy s = cell.pbc_intor('int1e_ovlp', hermi=1, kpts=kpts, pbcopt=lib.c_null_ptr()) cond = np.max(lib.cond(s)) if cond * cell.precision > 1e2: prec = 1e2 / cond rmin = max([cell.bas_rcut(ib, prec) for ib in range(cell.nbas)]) if cell.rcut < rmin: logger.warn( cell, 'Singularity detected in overlap matrix. ' 'Integral accuracy may be not enough.\n ' 'You can adjust cell.precision or cell.rcut to ' 'improve accuracy. Recommended values are\n ' 'cell.precision = %.2g or smaller.\n ' 'cell.rcut = %.4g or larger.', prec, rmin) return lib.asarray(s)
def remove_linear_dep_(mf, threshold=LINEAR_DEP_THRESHOLD, lindep=LINEAR_DEP_TRIGGER): ''' Args: threshold : float The threshold under which the eigenvalues of the overlap matrix are discarded to avoid numerical instability. lindep : float The threshold that triggers the special treatment of the linear dependence issue. ''' s = mf.get_ovlp() cond = numpy.max(lib.cond(s)) if cond < 1. / lindep: return mf logger.info(mf, 'Applying remove_linear_dep_ on SCF obejct.') logger.debug(mf, 'Overlap condition number %g', cond) def eigh(h, s): d, t = numpy.linalg.eigh(s) x = t[:, d > threshold] / numpy.sqrt(d[d > threshold]) xhx = reduce(numpy.dot, (x.T.conj(), h, x)) e, c = numpy.linalg.eigh(xhx) c = numpy.dot(x, c) return e, c mf._eigh = eigh return mf
def get_ovlp(cell, kpt=np.zeros(3)): '''Get the overlap AO matrix. ''' # Avoid pbcopt's prescreening in the lattice sum, for better accuracy s = cell.pbc_intor('int1e_ovlp', hermi=0, kpts=kpt, pbcopt=lib.c_null_ptr()) s = lib.asarray(s) hermi_error = abs(s - np.rollaxis(s.conj(), -1, -2)).max() if hermi_error > cell.precision and hermi_error > 1e-12: logger.warn( cell, '%.4g error found in overlap integrals. ' 'cell.precision or cell.rcut can be adjusted to ' 'improve accuracy.') cond = np.max(lib.cond(s)) if cond * cell.precision > 1e2: prec = 1e2 / cond rmin = max([cell.bas_rcut(ib, prec) for ib in range(cell.nbas)]) if cell.rcut < rmin: logger.warn( cell, 'Singularity detected in overlap matrix. ' 'Integral accuracy may be not enough.\n ' 'You can adjust cell.precision or cell.rcut to ' 'improve accuracy. Recommended values are\n ' 'cell.precision = %.2g or smaller.\n ' 'cell.rcut = %.4g or larger.', prec, rmin) return s
def remove_linear_dep_(mf, threshold=LINEAR_DEP_THRESHOLD, lindep=LINEAR_DEP_TRIGGER): ''' Args: threshold : float The threshold under which the eigenvalues of the overlap matrix are discarded to avoid numerical instability. lindep : float The threshold that triggers the special treatment of the linear dependence issue. ''' s = mf.get_ovlp() cond = numpy.max(lib.cond(s)) if cond < 1./lindep: return mf logger.info(mf, 'Applying remove_linear_dep_ on SCF obejct.') logger.debug(mf, 'Overlap condition number %g', cond) def eigh(h, s): d, t = numpy.linalg.eigh(s) x = t[:,d>threshold] / numpy.sqrt(d[d>threshold]) xhx = reduce(numpy.dot, (x.T.conj(), h, x)) e, c = numpy.linalg.eigh(xhx) c = numpy.dot(x, c) return e, c mf._eigh = eigh return mf
def remove_linear_dep_(mf, threshold=LINEAR_DEP_THRESHOLD, lindep=LINEAR_DEP_TRIGGER, cholesky_threshold=CHOLESKY_THRESHOLD, force_pivoted_cholesky=FORCE_PIVOTED_CHOLESKY): ''' Args: threshold : float The threshold under which the eigenvalues of the overlap matrix are discarded to avoid numerical instability. lindep : float The threshold that triggers the special treatment of the linear dependence issue. ''' s = mf.get_ovlp() cond = numpy.max(lib.cond(s)) if cond < 1. / lindep and not force_pivoted_cholesky: return mf logger.info(mf, 'Applying remove_linear_dep_ on SCF object.') logger.debug(mf, 'Overlap condition number %g', cond) if (cond < 1. / numpy.finfo(s.dtype).eps and not force_pivoted_cholesky): logger.info( mf, 'Using canonical orthogonalization with threshold {}'.format( threshold)) def eigh(h, s): x = canonical_orth_(s, threshold) xhx = reduce(numpy.dot, (x.T.conj(), h, x)) e, c = numpy.linalg.eigh(xhx) c = numpy.dot(x, c) return e, c mf._eigh = eigh else: logger.info( mf, 'Using partial Cholesky orthogonalization ' '(doi:10.1063/1.5139948, doi:10.1103/PhysRevA.101.032504)') logger.info( mf, 'Using threshold {} for pivoted Cholesky'.format( cholesky_threshold)) logger.info( mf, 'Using threshold {} to orthogonalize the subbasis'.format( threshold)) def eigh(h, s): x = partial_cholesky_orth_(s, canthr=threshold, cholthr=cholesky_threshold) xhx = reduce(numpy.dot, (x.T.conj(), h, x)) e, c = numpy.linalg.eigh(xhx) c = numpy.dot(x, c) return e, c mf._eigh = eigh return mf
def get_ovlp(cell, kpt=np.zeros(3)): '''Get the overlap AO matrix. ''' s = cell.pbc_intor('int1e_ovlp_sph', hermi=1, kpts=kpt) cond = np.max(lib.cond(s)) if cond * cell.precision > 1e2: prec = 1e2 / cond rmin = max([cell.bas_rcut(ib, prec) for ib in range(cell.nbas)]) if cell.rcut < rmin: logger.warn( cell, 'Singularity detected in overlap matrix. ' 'Integral accuracy may be not enough.\n ' 'You can adjust cell.precision or cell.rcut to ' 'improve accuracy. Recommended values are\n ' 'cell.precision = %.2g or smaller.\n ' 'cell.rcut = %.4g or larger.', prec, rmin) return s
def get_ovlp(cell, kpt=np.zeros(3)): '''Get the overlap AO matrix. ''' # Avoid pbcopt's prescreening in the lattice sum, for better accuracy s = cell.pbc_intor('int1e_ovlp', hermi=1, kpts=kpt, pbcopt=lib.c_null_ptr()) cond = np.max(lib.cond(s)) if cond * cell.precision > 1e2: prec = 1e2 / cond rmin = max([cell.bas_rcut(ib, prec) for ib in range(cell.nbas)]) if cell.rcut < rmin: logger.warn(cell, 'Singularity detected in overlap matrix. ' 'Integral accuracy may be not enough.\n ' 'You can adjust cell.precision or cell.rcut to ' 'improve accuracy. Recommended values are\n ' 'cell.precision = %.2g or smaller.\n ' 'cell.rcut = %.4g or larger.', prec, rmin) return s
def kernel(mf, conv_tol=1e-10, conv_tol_grad=None, dump_chk=False, dm0e=None, dm0n=[], callback=None, conv_check=True, **kwargs): cput0 = (logger.process_clock(), logger.perf_counter()) if conv_tol_grad is None: conv_tol_grad = numpy.sqrt(conv_tol) logger.info(mf, 'Set gradient conv threshold to %g', conv_tol_grad) mol = mf.mol if dm0e is None: mf.dm_elec = mf.get_init_guess_elec(mol, mf.init_guess) else: mf.dm_elec = dm0e if len(dm0n) < mol.nuc_num: mf.dm_nuc = mf.get_init_guess_nuc(mol, mf.init_guess) # if mf.init_guess is not 'chkfile', then it only affects the electronic part else: mf.dm_nuc = dm0n h1e = mf.mf_elec.get_hcore(mol.elec) vhf_e = mf.mf_elec.get_veff(mol.elec, mf.dm_elec) h1n = [] veff_n = [] for i in range(mol.nuc_num): h1n.append(mf.mf_nuc[i].get_hcore(mol.nuc[i])) veff_n.append(mf.mf_nuc[i].get_veff(mol.nuc[i], mf.dm_nuc[i])) e_tot = mf.energy_tot(mf.dm_elec, mf.dm_nuc, h1e, vhf_e, h1n, veff_n) logger.info(mf, 'init E= %.15g', e_tot) scf_conv = False mo_energy_e = mo_coeff_e = mo_occ_e = None mo_energy_n = [None] * mol.nuc_num mo_coeff_n = [None] * mol.nuc_num mo_occ_n = [None] * mol.nuc_num fock_n = [None] * mol.nuc_num s1e = mf.mf_elec.get_ovlp(mol.elec) cond = lib.cond(s1e) logger.debug(mf, 'cond(S) = %s', cond) if numpy.max(cond) * 1e-17 > conv_tol: logger.warn( mf, 'Singularity detected in overlap matrix (condition number = %4.3g). ' 'SCF may be inaccurate and hard to converge.', numpy.max(cond)) s1n = [] for i in range(mol.nuc_num): s1n.append(mf.mf_nuc[i].get_ovlp(mol.nuc[i])) # Skip SCF iterations. Compute only the total energy of the initial density if mf.max_cycle <= 0: fock_e = mf.mf_elec.get_fock(h1e, s1e, vhf_e, mf.dm_elec) # = h1e + vhf, no DIIS mo_energy_e, mo_coeff_e = mf.mf_elec.eig(fock_e, s1e) mo_occ_e = mf.mf_elec.get_occ(mo_energy_e, mo_coeff_e) mf.mf_elec.mo_energy = mo_energy_e mf.mf_elec.mo_coeff = mo_coeff_e mf.mf_elec.mo_occ = mo_occ_e for i in range(mol.nuc_num): fock_n[i] = mf.mf_nuc[i].get_fock(h1n[i], s1n[i], veff_n[i], mf.dm_nuc[i]) mo_energy_n[i], mo_coeff_n[i] = mf.mf_nuc[i].eig(fock_n[i], s1n[i]) mo_occ_n[i] = mf.mf_nuc[i].get_occ(mo_energy_n[i], mo_coeff_e[i]) mf.mf_nuc[i].mo_energy = mo_energy_n[i] mf.mf_nuc[i].mo_coeff = mo_coeff_n[i] mf.mf_nuc[i].mo_occ = mo_occ_n[i] if mf.dm_elec.ndim > 2: mf.dm_elec = mf.dm_elec[0] + mf.dm_elec[1] return scf_conv, e_tot, mo_energy_e, mo_coeff_e, mo_occ_e, \ mo_energy_n, mo_coeff_n, mo_occ_n if isinstance(mf.mf_elec.diis, lib.diis.DIIS): mf_diis = mf.mf_elec.diis elif mf.mf_elec.diis: assert issubclass(mf.mf_elec.DIIS, lib.diis.DIIS) mf_diis = mf.mf_elec.DIIS(mf.mf_elec, mf.mf_elec.diis_file) mf_diis.space = mf.mf_elec.diis_space mf_diis.rollback = mf.mf_elec.diis_space_rollback else: mf_diis = None # Nuclei need DIIS when there is epc mf_nuc_diis = [None] * mol.nuc_num if hasattr(mf, 'epc') and mf.epc is not None: for i in range(mol.nuc_num): mf_nuc = mf.mf_nuc[i] if isinstance(mf_nuc.diis, lib.diis.DIIS): mf_nuc_diis[i] = mf_nuc.diis elif mf_nuc.diis: assert issubclass(mf_nuc.DIIS, lib.diis.DIIS) mf_nuc_diis[i] = mf_nuc.DIIS(mf_nuc, mf_nuc.diis_file) mf_nuc_diis[i].space = mf_nuc.diis_space mf_nuc_diis[i].rollback = mf_nuc.diis_space_rollback else: mf_nuc_diis[i] = None if dump_chk and mf.chkfile: # Explicit overwrite the mol object in chkfile # Note in pbc.scf, mf.mol == mf.cell, cell is saved under key "mol" chkfile.save_mol(mol, mf.chkfile) # A preprocessing hook before the SCF iteration mf.pre_kernel(locals()) if isinstance(mf, neo.CDFT): int1e_r = [] for i in range(mol.nuc_num): int1e_r.append(mf.mf_nuc[i].mol.intor_symmetric('int1e_r', comp=3)) cput1 = logger.timer(mf, 'initialize scf', *cput0) for cycle in range(mf.max_cycle): dm_elec_last = numpy.copy( mf.dm_elec) # why didn't pyscf.scf.hf use copy? dm_nuc_last = numpy.copy(mf.dm_nuc) last_e = e_tot # set up the electronic Hamiltonian and diagonalize it fock_e = mf.mf_elec.get_fock(h1e, s1e, vhf_e, mf.dm_elec, cycle, mf_diis) mo_energy_e, mo_coeff_e = mf.mf_elec.eig(fock_e, s1e) mo_occ_e = mf.mf_elec.get_occ(mo_energy_e, mo_coeff_e) mf.dm_elec = mf.mf_elec.make_rdm1(mo_coeff_e, mo_occ_e) # attach mo_coeff and mo_occ to dm to improve DFT get_veff efficiency mf.dm_elec = lib.tag_array(mf.dm_elec, mo_coeff=mo_coeff_e, mo_occ=mo_occ_e) # set up the nuclear Hamiltonian and diagonalize it for i in range(mol.nuc_num): # update nuclear core Hamiltonian after the electron density is updated h1n[i] = mf.mf_nuc[i].get_hcore(mf.mf_nuc[i].mol) # optimize f in cNEO skip = False if isinstance(mf, neo.CDFT): ia = mf.mf_nuc[i].mol.atom_index fx = numpy.einsum('xij,x->ij', int1e_r[i], mf.f[ia]) opt = scipy.optimize.root(mf.first_order_de, mf.f[ia], args=(mf.mf_nuc[i], h1n[i] - fx, veff_n[i], s1n[i], int1e_r[i]), method='hybr') logger.debug( mf, 'f of %s(%i) atom: %s' % (mf.mf_nuc[i].mol.atom_symbol(ia), ia, mf.f[ia])) logger.debug(mf, '1st de of L: %s', opt.fun) if mf_nuc_diis[i] is None: # skip the extra diagonalization if epc is not present and DIIS is disabled skip = True if not skip: fock_n[i] = mf.mf_nuc[i].get_fock(h1n[i], s1n[i], veff_n[i], mf.dm_nuc[i], cycle, mf_nuc_diis[i]) mo_energy_n[i], mo_coeff_n[i] = mf.mf_nuc[i].eig( fock_n[i], s1n[i]) mf.mf_nuc[i].mo_energy, mf.mf_nuc[i].mo_coeff = mo_energy_n[ i], mo_coeff_n[i] mo_occ_n[i] = mf.mf_nuc[i].get_occ(mo_energy_n[i], mo_coeff_n[i]) mf.mf_nuc[i].mo_occ = mo_occ_n[i] mf.dm_nuc[i] = mf.mf_nuc[i].make_rdm1(mo_coeff_n[i], mo_occ_n[i]) # update nuclear veff and possible ep correlation part after the diagonalization veff_n[i] = mf.mf_nuc[i].get_veff(mf.mf_nuc[i].mol, mf.dm_nuc[i]) norm_ddm_n = numpy.linalg.norm( numpy.concatenate(mf.dm_nuc, axis=None).ravel() - numpy.concatenate(dm_nuc_last, axis=None).ravel()) # update electronic core Hamiltonian after the nuclear density is updated h1e = mf.mf_elec.get_hcore(mol.elec) # also update the veff, along with the possible ep correlation part vhf_e = mf.mf_elec.get_veff(mol.elec, mf.dm_elec, dm_elec_last, vhf_e) # Here Fock matrix is h1e + vhf, without DIIS. Calling get_fock # instead of the statement "fock = h1e + vhf" because Fock matrix may # be modified in some methods. fock_e = mf.mf_elec.get_fock(h1e, s1e, vhf_e, mf.dm_elec) # = h1e + vhf, no DIIS norm_gorb_e = numpy.linalg.norm( mf.mf_elec.get_grad(mo_coeff_e, mo_occ_e, fock_e)) if not TIGHT_GRAD_CONV_TOL: norm_gorb_e = norm_gorb_e / numpy.sqrt(norm_gorb_e.size) norm_ddm_e = numpy.linalg.norm(mf.dm_elec - dm_elec_last) e_tot = mf.energy_tot(mf.dm_elec, mf.dm_nuc, h1e, vhf_e, h1n, veff_n) logger.info( mf, 'cycle= %d E= %.15g delta_E= %4.3g |g_e|= %4.3g |ddm_e|= %4.3g |ddm_n|= %4.3g', cycle + 1, e_tot, e_tot - last_e, norm_gorb_e, norm_ddm_e, norm_ddm_n) if abs(e_tot - last_e) < conv_tol and norm_gorb_e < conv_tol_grad: scf_conv = True if dump_chk: mf.dump_chk(locals()) if callable(callback): callback(locals()) cput1 = logger.timer(mf, 'cycle= %d' % (cycle + 1), *cput1) if scf_conv: break if scf_conv and conv_check: # An extra diagonalization, to remove level shift #fock = mf.get_fock(h1e, s1e, vhf, dm) # = h1e + vhf mo_energy_e, mo_coeff_e = mf.mf_elec.eig(fock_e, s1e) mo_occ_e = mf.mf_elec.get_occ(mo_energy_e, mo_coeff_e) mf.dm_elec, dm_elec_last = mf.mf_elec.make_rdm1(mo_coeff_e, mo_occ_e), mf.dm_elec mf.dm_elec = lib.tag_array(mf.dm_elec, mo_coeff=mo_coeff_e, mo_occ=mo_occ_e) for i in range(mol.nuc_num): h1n[i] = mf.mf_nuc[i].get_hcore(mf.mf_nuc[i].mol) veff_n[i] = mf.mf_nuc[i].get_veff(mf.mf_nuc[i].mol, mf.dm_nuc[i]) fock_n[i] = mf.mf_nuc[i].get_fock(h1n[i], s1n[i], veff_n[i], mf.dm_nuc[i]) mo_energy_n[i], mo_coeff_n[i] = mf.mf_nuc[i].eig(fock_n[i], s1n[i]) mf.mf_nuc[i].mo_energy, mf.mf_nuc[i].mo_coeff = mo_energy_n[ i], mo_coeff_n[i] mo_occ_n[i] = mf.mf_nuc[i].get_occ(mo_energy_n[i], mo_coeff_n[i]) mf.mf_nuc[i].mo_occ = mo_occ_n[i] mf.dm_nuc[i], dm_nuc_last[i] = mf.mf_nuc[i].make_rdm1( mo_coeff_n[i], mo_occ_n[i]), mf.dm_nuc[i] norm_ddm_n = numpy.linalg.norm( numpy.concatenate(mf.dm_nuc, axis=None).ravel() - numpy.concatenate(dm_nuc_last, axis=None).ravel()) h1e = mf.mf_elec.get_hcore(mol.elec) vhf_e = mf.mf_elec.get_veff(mol.elec, mf.dm_elec, dm_elec_last, vhf_e) fock_e = mf.mf_elec.get_fock(h1e, s1e, vhf_e, mf.dm_elec) norm_gorb_e = numpy.linalg.norm( mf.mf_elec.get_grad(mo_coeff_e, mo_occ_e, fock_e)) if not TIGHT_GRAD_CONV_TOL: norm_gorb_e = norm_gorb_e / numpy.sqrt(norm_gorb_e.size) norm_ddm_e = numpy.linalg.norm(mf.dm_elec - dm_elec_last) e_tot, last_e = mf.energy_tot(mf.dm_elec, mf.dm_nuc, h1e, vhf_e, h1n, veff_n), e_tot conv_tol = conv_tol * 10 conv_tol_grad = conv_tol_grad * 3 if abs(e_tot - last_e) < conv_tol or norm_gorb_e < conv_tol_grad: scf_conv = True logger.info( mf, 'Extra cycle E= %.15g delta_E= %4.3g |g_e|= %4.3g |ddm_e|= %4.3g |ddm_n|= %4.3g', e_tot, e_tot - last_e, norm_gorb_e, norm_ddm_e, norm_ddm_n) if dump_chk: mf.dump_chk(locals()) logger.timer(mf, 'scf_cycle', *cput0) # A post-processing hook before return mf.post_kernel(locals()) if mf.dm_elec.ndim > 2: mf.dm_elec = mf.dm_elec[0] + mf.dm_elec[1] return scf_conv, e_tot, mo_energy_e, mo_coeff_e, mo_occ_e, \ mo_energy_n, mo_coeff_n, mo_occ_n
def kernel(mf, conv_tol=1e-10, conv_tol_grad=None, dump_chk=True, dm0=None, callback=None, **kwargs): '''kernel: the SCF driver. Args: mf : an instance of SCF class To hold the flags to control SCF. Besides the control parameters, one can modify its function members to change the behavior of SCF. The member functions which are called in kernel are | mf.get_init_guess | mf.get_hcore | mf.get_ovlp | mf.get_fock | mf.get_grad | mf.eig | mf.get_occ | mf.make_rdm1 | mf.energy_tot | mf.dump_chk Kwargs: conv_tol : float converge threshold. conv_tol_grad : float gradients converge threshold. dump_chk : bool Whether to save SCF intermediate results in the checkpoint file dm0 : ndarray Initial guess density matrix. If not given (the default), the kernel takes the density matrix generated by ``mf.get_init_guess``. callback : function(envs_dict) => None callback function takes one dict as the argument which is generated by the builtin function :func:`locals`, so that the callback function can access all local variables in the current envrionment. Returns: A list : scf_conv, e_tot, mo_energy, mo_coeff, mo_occ scf_conv : bool True means SCF converged e_tot : float Hartree-Fock energy of last iteration mo_energy : 1D float array Orbital energies. Depending the eig function provided by mf object, the orbital energies may NOT be sorted. mo_coeff : 2D array Orbital coefficients. mo_occ : 1D array Orbital occupancies. The occupancies may NOT be sorted from large to small. Examples: >>> from pyscf import gto, scf >>> mol = gto.M(atom='H 0 0 0; H 0 0 1.1', basis='cc-pvdz') >>> conv, e, mo_e, mo, mo_occ = scf.hf.kernel(scf.hf.SCF(mol), dm0=numpy.eye(mol.nao_nr())) >>> print('conv = %s, E(HF) = %.12f' % (conv, e)) conv = True, E(HF) = -1.081170784378 ''' if 'init_dm' in kwargs: raise RuntimeError(''' You see this error message because of the API updates in pyscf v0.11. Keyword argument "init_dm" is replaced by "dm0"''') cput0 = (time.clock(), time.time()) if conv_tol_grad is None: conv_tol_grad = numpy.sqrt(conv_tol) logger.info(mf, 'Set gradient conv threshold to %g', conv_tol_grad) mol = mf.mol if dm0 is None: dm = mf.get_init_guess(mol, mf.init_guess) else: dm = dm0 h1e = mf.get_hcore(mol) s1e = mf.get_ovlp(mol) cond = lib.cond(s1e) logger.debug(mf, 'cond(S) = %s', cond) if numpy.max(cond)*1e-17 > conv_tol: logger.warn(mf, 'Singularity detected in overlap matrix (condition number = %4.3g). ' 'SCF may be inaccurate and hard to converge.', numpy.max(cond)) if mf.diis and mf.DIIS: adiis = mf.DIIS(mf, mf.diis_file) adiis.space = mf.diis_space adiis.rollback = mf.diis_space_rollback else: adiis = None vhf = mf.get_veff(mol, dm) e_tot = mf.energy_tot(dm, h1e, vhf) logger.info(mf, 'init E= %.15g', e_tot) if dump_chk: # Explicit overwrite the mol object in chkfile # Note in pbc.scf, mf.mol == mf.cell, cell is saved under key "mol" pyscf.scf.chkfile.save_mol(mol, mf.chkfile) scf_conv = False cycle = 0 cput1 = logger.timer(mf, 'initialize scf', *cput0) while not scf_conv and cycle < max(1, mf.max_cycle): dm_last = dm last_hf_e = e_tot fock = mf.get_fock(h1e, s1e, vhf, dm, cycle, adiis) mo_energy, mo_coeff = mf.eig(fock, s1e) mo_occ = mf.get_occ(mo_energy, mo_coeff) dm = mf.make_rdm1(mo_coeff, mo_occ) vhf = mf.get_veff(mol, dm, dm_last, vhf) e_tot = mf.energy_tot(dm, h1e, vhf) norm_gorb = numpy.linalg.norm(mf.get_grad(mo_coeff, mo_occ, h1e+vhf)) norm_ddm = numpy.linalg.norm(dm-dm_last) logger.info(mf, 'cycle= %d E= %.15g delta_E= %4.3g |g|= %4.3g |ddm|= %4.3g', cycle+1, e_tot, e_tot-last_hf_e, norm_gorb, norm_ddm) if (abs(e_tot-last_hf_e) < conv_tol and norm_gorb < conv_tol_grad): scf_conv = True if dump_chk: mf.dump_chk(locals()) if callable(callback): callback(locals()) cput1 = logger.timer(mf, 'cycle= %d'%(cycle+1), *cput1) cycle += 1 # An extra diagonalization, to remove level shift fock = mf.get_fock(h1e, s1e, vhf, dm, cycle, None, 0, 0, 0) mo_energy, mo_coeff = mf.eig(fock, s1e) mo_occ = mf.get_occ(mo_energy, mo_coeff) if dump_chk: mf.dump_chk(locals()) logger.timer(mf, 'scf_cycle', *cput0) return scf_conv, e_tot, mo_energy, mo_coeff, mo_occ
def kernel(mf, conv_tol=1e-10, conv_tol_grad=None, dump_chk=True, dm0=None, callback=None, **kwargs): '''kernel: the SCF driver. Args: mf : an instance of SCF class To hold the flags to control SCF. Besides the control parameters, one can modify its function members to change the behavior of SCF. The member functions which are called in kernel are | mf.get_init_guess | mf.get_hcore | mf.get_ovlp | mf.get_fock | mf.get_grad | mf.eig | mf.get_occ | mf.make_rdm1 | mf.energy_tot | mf.dump_chk Kwargs: conv_tol : float converge threshold. conv_tol_grad : float gradients converge threshold. dump_chk : bool Whether to save SCF intermediate results in the checkpoint file dm0 : ndarray Initial guess density matrix. If not given (the default), the kernel takes the density matrix generated by ``mf.get_init_guess``. callback : function(envs_dict) => None callback function takes one dict as the argument which is generated by the builtin function :func:`locals`, so that the callback function can access all local variables in the current envrionment. Returns: A list : scf_conv, e_tot, mo_energy, mo_coeff, mo_occ scf_conv : bool True means SCF converged e_tot : float Hartree-Fock energy of last iteration mo_energy : 1D float array Orbital energies. Depending the eig function provided by mf object, the orbital energies may NOT be sorted. mo_coeff : 2D array Orbital coefficients. mo_occ : 1D array Orbital occupancies. The occupancies may NOT be sorted from large to small. Examples: >>> from pyscf import gto, scf >>> mol = gto.M(atom='H 0 0 0; H 0 0 1.1', basis='cc-pvdz') >>> conv, e, mo_e, mo, mo_occ = scf.hf.kernel(scf.hf.SCF(mol), dm0=numpy.eye(mol.nao_nr())) >>> print('conv = %s, E(HF) = %.12f' % (conv, e)) conv = True, E(HF) = -1.081170784378 ''' if 'init_dm' in kwargs: raise RuntimeError(''' You see this error message because of the API updates in pyscf v0.11. Keyword argument "init_dm" is replaced by "dm0"''') cput0 = (time.clock(), time.time()) if conv_tol_grad is None: conv_tol_grad = numpy.sqrt(conv_tol) logger.info(mf, 'Set gradient conv threshold to %g', conv_tol_grad) mol = mf.mol if dm0 is None: dm = mf.get_init_guess(mol, mf.init_guess) else: dm = dm0 h1e = mf.get_hcore(mol) s1e = mf.get_ovlp(mol) cond = lib.cond(s1e) logger.debug(mf, 'cond(S) = %s', cond) if numpy.max(cond) * 1e-17 > conv_tol: logger.warn( mf, 'Singularity detected in overlap matrix (condition number = %4.3g). ' 'SCF may be inaccurate and hard to converge.', numpy.max(cond)) if isinstance(mf.diis, lib.diis.DIIS): mf_diis = mf.diis elif mf.diis: mf_diis = diis.SCF_DIIS(mf, mf.diis_file) mf_diis.space = mf.diis_space mf_diis.rollback = mf.diis_space_rollback else: mf_diis = None vhf = mf.get_veff(mol, dm) e_tot = mf.energy_tot(dm, h1e, vhf) logger.info(mf, 'init E= %.15g', e_tot) if dump_chk: # Explicit overwrite the mol object in chkfile # Note in pbc.scf, mf.mol == mf.cell, cell is saved under key "mol" chkfile.save_mol(mol, mf.chkfile) scf_conv = False cycle = 0 cput1 = logger.timer(mf, 'initialize scf', *cput0) while not scf_conv and cycle < max(1, mf.max_cycle): dm_last = dm last_hf_e = e_tot fock = mf.get_fock(h1e, s1e, vhf, dm, cycle, mf_diis) mo_energy, mo_coeff = mf.eig(fock, s1e) mo_occ = mf.get_occ(mo_energy, mo_coeff) dm = mf.make_rdm1(mo_coeff, mo_occ) dm = _attach_mo(dm, mo_coeff, mo_occ) # to improve DFT get_veff efficiency vhf = mf.get_veff(mol, dm, dm_last, vhf) e_tot = mf.energy_tot(dm, h1e, vhf) norm_gorb = numpy.linalg.norm(mf.get_grad(mo_coeff, mo_occ, h1e + vhf)) norm_ddm = numpy.linalg.norm(dm - dm_last) logger.info( mf, 'cycle= %d E= %.15g delta_E= %4.3g |g|= %4.3g |ddm|= %4.3g', cycle + 1, e_tot, e_tot - last_hf_e, norm_gorb, norm_ddm) if (abs(e_tot - last_hf_e) < conv_tol and norm_gorb < conv_tol_grad): scf_conv = True if dump_chk: mf.dump_chk(locals()) if callable(callback): callback(locals()) cput1 = logger.timer(mf, 'cycle= %d' % (cycle + 1), *cput1) cycle += 1 # An extra diagonalization, to remove level shift fock = mf.get_fock(h1e, s1e, vhf, dm, cycle, None, 0, 0, 0) norm_gorb = numpy.linalg.norm(mf.get_grad(mo_coeff, mo_occ, h1e + vhf)) mo_energy, mo_coeff = mf.eig(fock, s1e) mo_occ = mf.get_occ(mo_energy, mo_coeff) dm, dm_last = mf.make_rdm1(mo_coeff, mo_occ), dm dm = _attach_mo(dm, mo_coeff, mo_occ) # to improve DFT get_veff efficiency vhf = mf.get_veff(mol, dm, dm_last, vhf) e_tot, last_hf_e = mf.energy_tot(dm, h1e, vhf), e_tot norm_ddm = numpy.linalg.norm(dm - dm_last) logger.info( mf, 'Extra cycle E= %.15g delta_E= %4.3g |g|= %4.3g |ddm|= %4.3g', e_tot, e_tot - last_hf_e, norm_gorb, norm_ddm) if dump_chk: mf.dump_chk(locals()) logger.timer(mf, 'scf_cycle', *cput0) return scf_conv, e_tot, mo_energy, mo_coeff, mo_occ