def __call__(self): """dumps single-orbital entropy and orbital-pair mututal information see :py:meth:`horton.orbital_entanglement.orbital_entanglement.OrbitalEntanglement.dump_output` for more info """ if log.do_medium: log(' ') log('Calculating orbital entanglement measures') log(' ') # # Compute single-orbital entropy and mutual information # if log.do_medium: log(' Computing s(1) and I_ij') self.compute_single_orbital_entropy() self.compute_two_orbital_entropy() self.compute_mutual_information() # # Dump output to file: # if log.do_medium: log(' Dumping output files') log(' ') log.hline('=') self.dump_output()
def normalize(self): if log.do_medium: log('Normalizing proatoms to integer populations') log(' Z charge before after') log.hline() for number in self.get_numbers(): rgrid = self.get_rgrid(number) for charge in self.get_charges(number): r = self.get_record(number, charge) nel_before = rgrid.integrate(r.rho) nel_integer = r.pseudo_number - charge r.rho[:] *= nel_integer / nel_before nel_after = rgrid.integrate(r.rho) if log.do_medium: log('%4i %+3i %15.8e %15.8e' % (number, charge, nel_before, nel_after))
def normalize(self): if log.do_medium: log('Normalizing proatoms to integer populations') log(' Z charge before after') log.hline() for number in self.get_numbers(): rgrid = self.get_rgrid(number) for charge in self.get_charges(number): r = self.get_record(number, charge) nel_before = rgrid.integrate(r.rho) nel_integer = r.pseudo_number - charge r.rho[:] *= nel_integer/nel_before nel_after = rgrid.integrate(r.rho) if log.do_medium: log('%4i %+3i %15.8e %15.8e' % ( number, charge, nel_before, nel_after ))
def _init_log_memory(self): if log.do_medium: # precompute arrays sizes for certain grids nbyte_global = self.grid.size*8 nbyte_locals = np.array([self.get_grid(i).size*8 for i in xrange(self.natom)]) # compute and report usage estimates = self.get_memory_estimates() nbyte_total = 0 log('Coarse estimate of memory usage for the partitioning:') log(' Label Memory[GB]') log.hline() for label, nlocals, nglobal in estimates: nbyte = np.dot(nlocals, nbyte_locals) + nglobal*nbyte_global log('%30s %10.3f' % (label, nbyte/1024.0**3)) nbyte_total += nbyte log('%30s %10.3f' % ('Total', nbyte_total/1024.0**3)) log.hline() log.blank()
def log(self): log(' Nr Pop Chg Energy Ionization Affinity') log.hline() for number, cases in sorted(self.all.iteritems()): for pop, energy in sorted(cases.iteritems()): energy_prev = cases.get(pop-1) if energy_prev is None: ip_str = '' else: ip_str = '% 18.10f' % (energy_prev - energy) energy_next = cases.get(pop+1) if energy_next is None: ea_str = '' else: ea_str = '% 18.10f' % (energy - energy_next) log('%3i %3i %+3i % 18.10f %18s %18s' % ( number, pop, number-pop, energy, ip_str, ea_str )) log.blank()
def log(self): log(' Nr Pop Chg Energy Ionization Affinity' ) log.hline() for number, cases in sorted(self.all.iteritems()): for pop, energy in sorted(cases.iteritems()): energy_prev = cases.get(pop - 1) if energy_prev is None: ip_str = '' else: ip_str = '% 18.10f' % (energy_prev - energy) energy_next = cases.get(pop + 1) if energy_next is None: ea_str = '' else: ea_str = '% 18.10f' % (energy - energy_next) log('%3i %3i %+3i % 18.10f %18s %18s' % (number, pop, number - pop, energy, ip_str, ea_str)) log.blank()
def do_partitioning(self): # Perform one general check in the beginning to avoid recomputation new = any(('at_weights', i) not in self.cache for i in xrange(self.natom)) new |= 'niter' not in self.cache new |= 'change'not in self.cache if new: propars = self._init_propars() if log.medium: log.hline() log('Iteration Change') log.hline() counter = 0 change = 1e100 while True: counter += 1 # Update the parameters that determine the pro-atoms. old_propars = propars.copy() self._update_propars() # Check for convergence change = self.compute_change(propars, old_propars) if log.medium: log('%9i %10.5e' % (counter, change)) if change < self._threshold or counter >= self._maxiter: break if log.medium: log.hline() self._finalize_propars() self.cache.dump('niter', counter, tags='o') self.cache.dump('change', change, tags='o')
def compact(self, nel_lost): '''Make the pro-atoms more compact **Argument:** nel_lost This parameter controls the part of the tail that gets neglected. This is the (maximym) number of electrons in the part that gets discarded. Note that only 'safe' atoms are considered to determine the cutoff radius. ''' if log.do_medium: log('Reducing extents of the pro-atoms') log(' Z npiont radius') log.hline() for number in self.get_numbers(): rgrid = self.get_rgrid(number) npoint = 0 for charge in self.get_charges(number, safe=True): r = self.get_record(number, charge) nel = r.pseudo_number-charge npoint = max(npoint, r.compute_radii([nel-nel_lost])[0][0]+1) for charge in self.get_charges(number): r = self.get_record(number, charge) r.chop(npoint) new_rgrid = self._rgrid_map[number].chop(npoint) self._rgrid_map[number] = new_rgrid if log.do_medium: log('%4i %5i -> %5i %10.3e -> %10.3e' % ( number, rgrid.size, new_rgrid.size, rgrid.radii[-1], new_rgrid.radii[-1] )) if log.do_medium: log.hline()
def log_energy(self): '''Write an overview of the last energy computation on screen''' log('Contributions to the energy:') log.hline() log(' Energy term Value') log.hline() for term in self.terms: energy = self.system.extra['energy_%s' % term.label] log('%50s %20.12f' % (term.label, energy)) log('%50s %20.12f' % ('nn', self.system.extra['energy_nn'])) log('%50s %20.12f' % ('total', self.system.extra['energy'])) log.hline() log.blank()
def log(self): """Write an overview of the last computation on screen.""" log('Contributions to the energy:') log.hline() log(' term Value') log.hline() for term in self.terms: energy = self.cache['energy_%s' % term.label] log('%50s %20.12f' % (term.label, energy)) for key, energy in self.external.iteritems(): log('%50s %20.12f' % (key, energy)) log('%50s %20.12f' % ('total', self.cache['energy'])) log.hline() log.blank()
def log_energy(self): '''Write an overview of the last energy computation on screen''' log('Contributions to the energy:') log.hline() log(' Energy term Value' ) log.hline() for term in self.terms: energy = self.system.extra['energy_%s' % term.label] log('%50s %20.12f' % (term.label, energy)) log('%50s %20.12f' % ('nn', self.system.extra['energy_nn'])) log('%50s %20.12f' % ('total', self.system.extra['energy'])) log.hline() log.blank()
def __init__(self, numbers, proatomdb): self.numbers = numbers self.proatomdb = proatomdb self.nbasis = 0 self.basis_specs = [] for i in xrange(len(numbers)): number = numbers[i] rgrid = proatomdb.get_rgrid(number) licos = self._init_atom_licos(number, proatomdb) #padb_charges = proatomdb.get_charges(number, safe=True) #complete = proatomdb.get_record(number, padb_charges[0]).pseudo_population == 1 # remove redundant basis functions for j in xrange(len(licos) - 1, -1, -1): # test if this lico is redundant with respect to earlier ones redundant = False rho = self.proatomdb.get_rho(number, licos[j]) for earlier_lico in licos[:j]: earlier_rho = self.proatomdb.get_rho(number, earlier_lico) delta = rho - earlier_rho rmsd = np.sqrt(rgrid.integrate(delta, delta)) if rmsd < 1e-8: redundant = True break # remove if redundant if redundant: if log.do_medium: log('Skipping redundant basis function for atom %i: %s' % (i, licos[j])) licos.pop(j) self.basis_specs.append([self.nbasis, licos]) self.nbasis += len(licos) if log.do_high: log('Hirshfeld-E basis') log.hline() log('Atom Z k label') log.hline() for i in xrange(len(numbers)): for j in xrange(self.get_atom_nbasis(i)): label = self.get_basis_label(i, j) log('%4i %3i %3i %s' % (i, numbers[i], j, label)) log.hline()
def __init__(self, numbers, proatomdb): self.numbers = numbers self.proatomdb = proatomdb self.nbasis = 0 self.basis_specs = [] for i in xrange(len(numbers)): number = numbers[i] rgrid = proatomdb.get_rgrid(number) licos = self._init_atom_licos(number, proatomdb) #padb_charges = proatomdb.get_charges(number, safe=True) #complete = proatomdb.get_record(number, padb_charges[0]).pseudo_population == 1 # remove redundant basis functions for j in xrange(len(licos)-1, -1, -1): # test if this lico is redundant with respect to earlier ones redundant = False rho = self.proatomdb.get_rho(number, licos[j]) for earlier_lico in licos[:j]: earlier_rho = self.proatomdb.get_rho(number, earlier_lico) delta = rho - earlier_rho rmsd = np.sqrt(rgrid.integrate(delta, delta)) if rmsd < 1e-8: redundant = True break # remove if redundant if redundant: if log.do_medium: log('Skipping redundant basis function for atom %i: %s' % (i, licos[j])) licos.pop(j) self.basis_specs.append([self.nbasis, licos]) self.nbasis += len(licos) if log.do_high: log('Hirshfeld-E basis') log.hline() log('Atom Z k label') log.hline() for i in xrange(len(numbers)): for j in xrange(self.get_atom_nbasis(i)): label = self.get_basis_label(i, j) log('%4i %3i %3i %s' % (i, numbers[i], j, label)) log.hline()
def converge_scf_oda_os(ham, maxiter=128, threshold=1e-6, debug=False): '''Minimize the energy of the open-shell wavefunction with optimal damping **Arguments:** ham A Hamiltonian instance. **Optional arguments:** maxiter The maximum number of iterations. When set to None, the SCF loop will go one until convergence is reached. threshold The convergence threshold for the wavefunction. debug Make debug plots with matplotlib of the linear interpolation **Raises:** NoSCFConvergence if the convergence criteria are not met within the specified number of iterations. **Returns:** the number of iterations ''' if log.do_medium: log('Starting restricted closed-shell SCF with optimal damping') log.hline() log(' Iter Energy Error(alpha) Error(beta) Mixing') log.hline() # initialization of variables and datastructures lf = ham.system.lf wfn = ham.system.wfn overlap = ham.system.get_overlap() # suffixes # 0 = current or initial state # 1 = state after conventional SCF step # 2 = state after optimal damping # a = alpha # b = beta fock0a = lf.create_one_body() fock1a = lf.create_one_body() fock0b = lf.create_one_body() fock1b = lf.create_one_body() dm0a = lf.create_one_body() dm2a = lf.create_one_body() dm0b = lf.create_one_body() dm2b = lf.create_one_body() converged = False mixing = None errora = None errorb = None # Get rid of outdated stuff ham.clear() # If an input density matrix is present, check if it sensible. This avoids # redundant testing of a density matrix that is derived here from the # orbitals. if 'dm_alpha' in wfn._cache: check_dm(wfn.dm_alpha, overlap, lf, 'alpha') if 'dm_beta' in wfn._cache: check_dm(wfn.dm_beta, overlap, lf, 'beta') counter = 0 while maxiter is None or counter < maxiter: # A) Construct Fock operator, compute energy and keep dm at current/initial point fock0a.clear() fock0b.clear() ham.compute_fock(fock0a, fock0b) energy0 = ham.compute() dm0a.assign(wfn.dm_alpha) dm0b.assign(wfn.dm_beta) if log.do_medium: if mixing is None: log('%5i %20.13f' % (counter, energy0)) else: log('%5i %20.13f %12.5e %12.5e %10.5f' % (counter, energy0, errora, errorb, mixing)) # B) Diagonalize fock operator and go to state 1 wfn.clear() wfn.update_exp(fock0a, fock0b, overlap) # Let the hamiltonian know that the wavefunction has changed. ham.clear() # C) Compute Fock matrix at state 1 fock1a.clear() fock1b.clear() ham.compute_fock(fock1a, fock1b) # Compute energy at new point energy1 = ham.compute() # take the density matrix dm1a = wfn.dm_alpha dm1b = wfn.dm_beta # D) Find the optimal damping # Compute the derivatives of the energy towards lambda at edges 0 and 1 ev_00 = fock0a.expectation_value(dm0a) + fock0b.expectation_value(dm0b) ev_01 = fock0a.expectation_value(dm1a) + fock0b.expectation_value(dm1b) ev_10 = fock1a.expectation_value(dm0a) + fock1b.expectation_value(dm0b) ev_11 = fock1a.expectation_value(dm1a) + fock1b.expectation_value(dm1b) energy0_deriv = (ev_01-ev_00) energy1_deriv = (ev_11-ev_10) # find the lambda that minimizes the cubic polynomial in the range [0,1] if log.do_high: log(' E0: % 10.5e D0: % 10.5e' % (energy0, energy0_deriv)) log(' E1-E0: % 10.5e D1: % 10.5e' % (energy1-energy0, energy1_deriv)) mixing = find_min_cubic(energy0, energy1, energy0_deriv, energy1_deriv) if log.do_high: log(' mixing: % 10.5f' % mixing) if debug: check_cubic_os(ham, dm0a, dm0b, dm1a, dm1b, energy0, energy1, energy0_deriv, energy1_deriv) # E) Construct the new dm # Put the mixed dm in dm_old, which is local in this routine. dm2a.clear() dm2a.iadd(dm1a, factor=mixing) dm2a.iadd(dm0a, factor=1-mixing) dm2b.clear() dm2b.iadd(dm1b, factor=mixing) dm2b.iadd(dm0b, factor=1-mixing) # Wipe the caches and use the interpolated density matrix wfn.clear() wfn.update_dm('alpha', dm2a) wfn.update_dm('beta', dm2b) ham.clear() errora = dm2a.distance(dm0a) errorb = dm2b.distance(dm0b) if errora < threshold and errorb < threshold: if abs(energy0_deriv) > threshold: raise RuntimeError('The ODA algorithm stopped a point with non-zero gradient.') converged = True break # Write intermediate wfn to checkpoint. ham.system.update_chk('wfn') # counter counter += 1 # compute orbitals, energies and occupation numbers # Note: suffix 0 is used for final state here dm0a.assign(wfn.dm_alpha) dm0b.assign(wfn.dm_beta) fock0a.clear() fock0b.clear() ham.compute_fock(fock0a, fock0b) wfn.clear() wfn.update_exp(fock0a, fock0b, overlap, dm0a, dm0b) energy0 = ham.compute() # Write final wfn to checkpoint. ham.system.update_chk('wfn') if log.do_medium: log('%5i %20.13f %12.5e %12.5e %10.5f' % (counter+1, energy0, errora, errorb, mixing)) log.blank() if log.do_medium: ham.log_energy() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, lf, overlap, occ_model, *exps): '''Find a self-consistent set of orbitals. **Arguments:** ham An effective Hamiltonian. lf The linalg factory to be used. overlap The overlap operator. occ_model Model for the orbital occupations. exp1, exp2, ... The initial orbitals. The number of dms must match ham.ndm. ''' # Some type checking if ham.ndm != len(exps): raise TypeError( 'The number of initial orbital expansions does not match the Hamiltonian.' ) # Impose the requested occupation numbers occ_model.assign(*exps) # Check the orthogonality of the orbitals for exp in exps: exp.check_normalization(overlap) if log.do_medium: log('Starting plain SCF solver. ndm=%i' % ham.ndm) log.hline() log('Iter Error') log.hline() focks = [lf.create_two_index() for i in xrange(ham.ndm)] dms = [lf.create_two_index() for i in xrange(ham.ndm)] converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # convert the orbital expansions to density matrices for i in xrange(ham.ndm): exps[i].to_dm(dms[i]) # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operator ham.compute_fock(*focks) # Check for convergence error = 0.0 for i in xrange(ham.ndm): error += exps[i].error_eigen(focks[i], overlap) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < self.threshold: converged = True break # Diagonalize the fock operators to obtain new orbitals and for i in xrange(ham.ndm): exps[i].from_fock(focks[i], overlap) # Assign new occupation numbers. occ_model.assign(*exps) # counter counter += 1 if log.do_medium: log.blank() if not self.skip_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, overlap, occ_model, *dm0s): '''Find a self-consistent set of density matrices. **Arguments:** ham An effective Hamiltonian. overlap The overlap operator. occ_model Model for the orbital occupations. dm1, dm2, ... The initial density matrices. The number of dms must match ham.ndm. ''' # Some type checking if ham.ndm != len(dm0s): raise TypeError( 'The number of initial density matrices does not match the Hamiltonian.' ) # Check input density matrices. for i in xrange(ham.ndm): check_dm(dm0s[i], overlap) occ_model.check_dms(overlap, *dm0s) if log.do_medium: log('Starting SCF with optimal damping. ham.ndm=%i' % ham.ndm) log.hline() log(' Iter Energy Error Mixing') log.hline() fock0s = [np.zeros(overlap.shape) for i in xrange(ham.ndm)] fock1s = [np.zeros(overlap.shape) for i in xrange(ham.ndm)] dm1s = [np.zeros(overlap.shape) for i in xrange(ham.ndm)] orbs = [Orbitals(overlap.shape[0]) for i in xrange(ham.ndm)] work = np.zeros(dm0s[0].shape) commutator = np.zeros(dm0s[0].shape) converged = False counter = 0 mixing = None error = None while self.maxiter is None or counter < self.maxiter: # feed the latest density matrices in the hamiltonian ham.reset(*dm0s) # Construct the Fock operators in point 0 ham.compute_fock(*fock0s) # Compute the energy in point 0 energy0 = ham.compute_energy() if log.do_medium: if mixing is None: log('%5i %20.13f' % (counter, energy0)) else: log('%5i %20.13f %12.5e %10.5f' % (counter, energy0, error, mixing)) # go to point 1 by diagonalizing the fock matrices for i in xrange(ham.ndm): orbs[i].from_fock(fock0s[i], overlap) # Assign new occupation numbers. occ_model.assign(*orbs) # Construct the density matrices for i in xrange(ham.ndm): dm1s[i][:] = orbs[i].to_dm() # feed the latest density matrices in the hamiltonian ham.reset(*dm1s) # Compute the fock matrices in point 1 ham.compute_fock(*fock1s) # Compute the energy in point 1 energy1 = ham.compute_energy() # Compute the derivatives of the energy at point 0 and 1 for a # linear interpolation of the density matrices deriv0 = 0.0 deriv1 = 0.0 for i in xrange(ham.ndm): deriv0 += np.einsum('ab,ab', fock0s[i], dm1s[i]) deriv0 -= np.einsum('ab,ab', fock0s[i], dm0s[i]) deriv1 += np.einsum('ab,ab', fock1s[i], dm1s[i]) deriv1 -= np.einsum('ab,ab', fock1s[i], dm0s[i]) deriv0 *= ham.deriv_scale deriv1 *= ham.deriv_scale # find the lambda that minimizes the cubic polynomial in the range [0,1] if log.do_high: log(' E0: % 10.5e D0: % 10.5e' % (energy0, deriv0)) log(' E1-E0: % 10.5e D1: % 10.5e' % (energy1 - energy0, deriv1)) mixing = find_min_cubic(energy0, energy1, deriv0, deriv1) if self.debug: check_cubic(ham, dm0s, dm1s, energy0, energy1, deriv0, deriv1) # compute the mixed density and fock matrices (in-place in dm0s and fock0s) for i in xrange(ham.ndm): dm0s[i][:] *= 1 - mixing dm0s[i][:] += dm1s[i] * mixing fock0s[i][:] *= 1 - mixing fock0s[i][:] += fock1s[i] * mixing # Compute the convergence criterion. errorsq = 0.0 for i in xrange(ham.ndm): commutator = compute_commutator(dm0s[i], fock0s[i], overlap) errorsq += np.einsum('ab,ab', commutator, commutator) error = errorsq**0.5 if error < self.threshold: converged = True break elif mixing == 0.0: raise NoSCFConvergence( 'The ODA algorithm made a zero step without reaching convergence.' ) # counter counter += 1 if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, lf, overlap, occ_model, *exps): '''Find a self-consistent set of orbitals. **Arguments:** ham An effective Hamiltonian. lf The linalg factory to be used. overlap The overlap operator. occ_model Model for the orbital occupations. exp1, exp2, ... The initial orbitals. The number of dms must match ham.ndm. ''' # Some type checking if ham.ndm != len(exps): raise TypeError('The number of initial orbital expansions does not match the Hamiltonian.') # Impose the requested occupation numbers occ_model.assign(*exps) # Check the orthogonality of the orbitals for exp in exps: exp.check_normalization(overlap) if log.do_medium: log('Starting plain SCF solver. ndm=%i' % ham.ndm) log.hline() log('Iter Error') log.hline() focks = [lf.create_two_index() for i in xrange(ham.ndm)] dms = [lf.create_two_index() for i in xrange(ham.ndm)] converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # convert the orbital expansions to density matrices for i in xrange(ham.ndm): exps[i].to_dm(dms[i]) # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operator ham.compute_fock(*focks) # Check for convergence error = 0.0 for i in xrange(ham.ndm): error += exps[i].error_eigen(focks[i], overlap) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < self.threshold: converged = True break # Diagonalize the fock operators to obtain new orbitals and for i in xrange(ham.ndm): exps[i].from_fock(focks[i], overlap) # Assign new occupation numbers. occ_model.assign(*exps) # counter counter += 1 if log.do_medium: log.blank() if not self.skip_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def converge_scf_diis_cs(ham, DIISHistoryClass, maxiter=128, threshold=1e-6, nvector=6, prune_old_states=False, skip_energy=False, scf_step='regular'): '''Minimize the energy of the closed-shell wavefunction with EDIIS **Arguments:** ham A Hamiltonian instance. DIISHistoryClass A DIIS history class. **Optional arguments:** maxiter The maximum number of iterations. When set to None, the SCF loop will go one until convergence is reached. threshold The convergence threshold for the wavefunction prune_old_states When set to True, old states are pruned from the history when their coefficient is zero. Pruning starts at the oldest state and stops as soon as a state is encountered with a non-zero coefficient. Even if some newer states have a zero coefficient. skip_energy When set to True, the final energy is not computed. Note that some DIIS variants need to compute the energy anyway. for these methods this option is irrelevant. scf_step The type of SCF step to take after the interpolated states was create from the DIIS history. This can be 'regular', 'oda2' or 'oda3'. **Raises:** NoSCFConvergence if the convergence criteria are not met within the specified number of iterations. **Returns:** the number of iterations ''' # allocated and define some one body operators lf = ham.system.lf wfn = ham.system.wfn overlap = ham.system.get_overlap() history = DIISHistoryClass(lf, nvector, overlap) fock = lf.create_one_body() dm = lf.create_one_body() # The scf step if scf_step == 'regular': cs_scf_step = CSSCFStep(ham) elif scf_step == 'oda2': cs_scf_step = CSQuadraticODAStep(ham) elif scf_step == 'oda3': cs_scf_step = CSCubicODAStep(ham) else: raise ValueError('scf_step argument not recognized: %s' % scf_step) # Get rid of outdated stuff ham.clear() if log.do_medium: log('Starting restricted closed-shell %s-SCF' % history.name) log.hline() log('Iter Error(alpha) CN(alpha) Last(alpha) nv Method Energy Change' ) log.hline() converged = False counter = 0 while maxiter is None or counter < maxiter: # Construct the Fock operator from scratch: if history.nused == 0: # Update the Fock matrix fock.clear() ham.compute_fock(fock, None) # Put this state also in the history energy = ham.compute() if history.need_energy else None # Add the current fock+dm pair to the history error = history.add(energy, wfn.dm_alpha, fock) if log.do_high: log(' DIIS add') if error < threshold: converged = True break if log.do_high: log.blank() if log.do_medium: energy_str = ' ' * 20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % (counter, error, history.nused, energy_str)) if log.do_high: log.blank() fock_interpolated = False else: energy = None fock_interpolated = True # Construct a new density matrix and fock matrix (based on the current # density matrix or fock matrix). The fock matrix is modified in-place # while the density matrix is stored in ham.system.wfn mixing = cs_scf_step(energy, fock, fock_interpolated) if mixing == 0.0: converged = True break energy = ham.compute() if history.need_energy else None # Write intermediate results to checkpoint ham.system.update_chk('wfn') # Add the current (dm, fock) pair to the history if log.do_high: log(' DIIS add') error = history.add(energy, wfn.dm_alpha, fock) # break when converged if error < threshold: converged = True break if log.do_high: log.blank() if log.do_medium: energy_str = ' ' * 20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % (counter, error, history.nused, energy_str)) if log.do_high: log.blank() # get extra/intra-polated Fock matrix while True: energy_approx, coeffs, cn, method = history.solve(dm, fock) #if coeffs[coeffs<0].sum() < -1: # if log.do_high: # log(' DIIS (coeffs too negative) -> drop %i and retry' % history.stack[0].identity) # history.shrink() if history.nused <= 2: break if coeffs[-1] == 0.0: if log.do_high: log(' DIIS (last coeff zero) -> drop %i and retry' % history.stack[0].identity) history.shrink() else: break if False and len(coeffs) == 2: tmp = dm.copy() import matplotlib.pyplot as pt xs = np.linspace(0.0, 1.0, 25) a, b = history._setup_equations() energies1 = [] energies2 = [] for x in xs: x_coeffs = np.array([1 - x, x]) energies1.append( np.dot(x_coeffs, 0.5 * np.dot(a, x_coeffs) - b)) history._build_combinations(x_coeffs, tmp, None) wfn.clear() wfn.update_dm('alpha', tmp) ham.clear() energies2.append(ham.compute()) print x, energies1[-1], energies2[-1] pt.clf() pt.plot(xs, energies1, label='est') pt.plot(xs, energies2, label='ref') pt.axvline(coeffs[1], color='k') pt.legend(loc=0) pt.savefig('ediis2_test_%05i.png' % counter) wfn.clear() wfn.update_dm('alpha', dm) ham.clear() if energy_approx is not None: energy_change = energy_approx - min(state.energy for state in history.stack) else: energy_change = None # log if log.do_high: history.log(coeffs) if log.do_medium: change_str = ' ' * 10 if energy_change is None else '% 12.7f' % energy_change log('%4i %10.3e %12.7f %2i %s %12s' % (counter, cn, coeffs[-1], history.nused, method, change_str)) if log.do_high: log.blank() if prune_old_states: # get rid of old states with zero coeff for i in xrange(history.nused): if coeffs[i] == 0.0: if log.do_high: log(' DIIS insignificant -> drop %i' % history.stack[0].identity) history.shrink() else: break # counter counter += 1 if log.do_medium: if converged: log('%4i %12.5e (converged)' % (counter, error)) log.blank() if not skip_energy or history.need_energy: if not history.need_energy: ham.compute() if log.do_medium: ham.log_energy() if not converged: raise NoSCFConvergence return counter
def converge_scf_cs(ham, maxiter=128, threshold=1e-8, skip_energy=False): '''Minimize the energy of the wavefunction with basic closed-shell SCF **Arguments:** ham A Hamiltonian instance. **Optional arguments:** maxiter The maximum number of iterations. When set to None, the SCF loop will go one until convergence is reached. threshold The convergence threshold for the wavefunction. skip_energy When set to True, the final energy is not computed. **Raises:** NoSCFConvergence if the convergence criteria are not met within the specified number of iterations. **Returns:** the number of iterations ''' if log.do_medium: log('Starting restricted closed-shell SCF') log.hline() log('Iter Error(alpha)') log.hline() # Get rid of outdated stuff ham.clear() lf = ham.system.lf wfn = ham.system.wfn overlap = ham.system.get_overlap() fock = lf.create_one_body() converged = False counter = 0 while maxiter is None or counter < maxiter: # Construct the Fock operator fock.clear() ham.compute_fock(fock, None) # Check for convergence error = lf.error_eigen(fock, overlap, wfn.exp_alpha) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < threshold: converged = True break # Diagonalize the fock operator wfn.clear() # discard previous wfn state wfn.update_exp(fock, overlap) # Let the hamiltonian know that the wavefunction has changed. ham.clear() # Write intermediate results to checkpoint ham.system.update_chk('wfn') # counter counter += 1 if log.do_medium: log.blank() if not skip_energy: ham.compute() if log.do_medium: ham.log_energy() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, lf, overlap, occ_model, *exps): """Find a self-consistent set of orbitals. Parameters ---------- ham : EffHam An effective Hamiltonian. lf : LinalgFactor The linalg factory to be used. overlap : TwoIndex The overlap operator. occ_model : OccModel Model for the orbital occupations. exp1, exp2, ... : Expansion The initial orbitals. The number of dms must match ham.ndm. """ # Some type checking if ham.ndm != len(exps): raise TypeError('The number of initial orbital expansions does not match the Hamiltonian.') # Impose the requested occupation numbers occ_model.assign(*exps) # Check the orthogonality of the orbitals for exp in exps: exp.check_normalization(overlap) if log.do_medium: log('Starting plain SCF solver. ndm=%i' % ham.ndm) log.hline() log('Iter Error') log.hline() focks = [lf.create_two_index() for i in xrange(ham.ndm)] dms = [lf.create_two_index() for i in xrange(ham.ndm)] converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # convert the orbital expansions to density matrices for i in xrange(ham.ndm): exps[i].to_dm(dms[i]) # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operator ham.compute_fock(*focks) # Check for convergence error = 0.0 for i in xrange(ham.ndm): error += exps[i].error_eigen(focks[i], overlap) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < self.threshold: converged = True break # If requested, add the level shift to the Fock operator if self.level_shift > 0: for i in xrange(ham.ndm): lshift = overlap.copy() lshift.idot(dms[i]) lshift.idot(overlap) # The normal behavior is to shift down the occupied levels. lshift.iscale(-self.level_shift) focks[i].iadd(lshift) # Diagonalize the fock operators to obtain new orbitals and for i in xrange(ham.ndm): exps[i].from_fock(focks[i], overlap) # If requested, compensate for level-shift. This compensation # is only correct when the SCF has converged. if self.level_shift > 0: exps[i].energies[:] += self.level_shift*exps[i].occupations # Assign new occupation numbers. occ_model.assign(*exps) # counter counter += 1 if log.do_medium: log.blank() if not self.skip_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, lf, overlap, occ_model, *dm0s): '''Find a self-consistent set of density matrices. **Arguments:** ham An effective Hamiltonian. lf The linalg factory to be used. overlap The overlap operator. occ_model Model for the orbital occupations. dm1, dm2, ... The initial density matrices. The number of dms must match ham.ndm. ''' # Some type checking if ham.ndm != len(dm0s): raise TypeError('The number of initial density matrices does not match the Hamiltonian.') # Check input density matrices. for i in xrange(ham.ndm): check_dm(dm0s[i], overlap, lf) occ_model.check_dms(overlap, *dm0s) if log.do_medium: log('Starting SCF with optimal damping. ham.ndm=%i' % ham.ndm) log.hline() log(' Iter Energy Error Mixing') log.hline() fock0s = [lf.create_two_index() for i in xrange(ham.ndm)] fock1s = [lf.create_two_index() for i in xrange(ham.ndm)] dm1s = [lf.create_two_index() for i in xrange(ham.ndm)] exps = [lf.create_expansion() for i in xrange(ham.ndm)] work = dm0s[0].new() commutator = dm0s[0].new() converged = False counter = 0 mixing = None error = None while self.maxiter is None or counter < self.maxiter: # feed the latest density matrices in the hamiltonian ham.reset(*dm0s) # Construct the Fock operators in point 0 ham.compute_fock(*fock0s) # Compute the energy in point 0 energy0 = ham.compute_energy() if log.do_medium: if mixing is None: log('%5i %20.13f' % (counter, energy0)) else: log('%5i %20.13f %12.5e %10.5f' % (counter, energy0, error, mixing)) # go to point 1 by diagonalizing the fock matrices for i in xrange(ham.ndm): exps[i].from_fock(fock0s[i], overlap) # Assign new occupation numbers. occ_model.assign(*exps) # Construct the density matrices for i in xrange(ham.ndm): exps[i].to_dm(dm1s[i]) # feed the latest density matrices in the hamiltonian ham.reset(*dm1s) # Compute the fock matrices in point 1 ham.compute_fock(*fock1s) # Compute the energy in point 1 energy1 = ham.compute_energy() # Compute the derivatives of the energy at point 0 and 1 for a # linear interpolation of the density matrices deriv0 = 0.0 deriv1 = 0.0 for i in xrange(ham.ndm): deriv0 += fock0s[i].contract_two('ab,ab', dm1s[i]) deriv0 -= fock0s[i].contract_two('ab,ab', dm0s[i]) deriv1 += fock1s[i].contract_two('ab,ab', dm1s[i]) deriv1 -= fock1s[i].contract_two('ab,ab', dm0s[i]) deriv0 *= ham.deriv_scale deriv1 *= ham.deriv_scale # find the lambda that minimizes the cubic polynomial in the range [0,1] if log.do_high: log(' E0: % 10.5e D0: % 10.5e' % (energy0, deriv0)) log(' E1-E0: % 10.5e D1: % 10.5e' % (energy1-energy0, deriv1)) mixing = find_min_cubic(energy0, energy1, deriv0, deriv1) if self.debug: check_cubic(ham, dm0s, dm1s, energy0, energy1, deriv0, deriv1) # compute the mixed density and fock matrices (in-place in dm0s and fock0s) for i in xrange(ham.ndm): dm0s[i].iscale(1-mixing) dm0s[i].iadd(dm1s[i], mixing) fock0s[i].iscale(1-mixing) fock0s[i].iadd(fock1s[i], mixing) # Compute the convergence criterion. errorsq = 0.0 for i in xrange(ham.ndm): compute_commutator(dm0s[i], fock0s[i], overlap, work, commutator) errorsq += commutator.contract_two('ab,ab', commutator) error = errorsq**0.5 if error < self.threshold: converged = True break elif mixing == 0.0: raise NoSCFConvergence('The ODA algorithm made a zero step without reaching convergence.') # counter counter += 1 if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, lf, overlap, occ_model, *dms): '''Find a self-consistent set of density matrices. **Arguments:** ham An effective Hamiltonian. lf The linalg factory to be used. overlap The overlap operator. occ_model Model for the orbital occupations. dm1, dm2, ... The initial density matrices. The number of dms must match ham.ndm. ''' # Some type checking if ham.ndm != len(dms): raise TypeError('The number of initial density matrices does not match the Hamiltonian.') # Check input density matrices. for i in xrange(ham.ndm): check_dm(dms[i], overlap, lf) occ_model.check_dms(overlap, *dms) # keep local variables as attributes for inspection/debugging by caller self._history = self.DIISHistoryClass(lf, self.nvector, ham.ndm, ham.deriv_scale, overlap) self._focks = [lf.create_two_index() for i in xrange(ham.ndm)] self._exps = [lf.create_expansion() for i in xrange(ham.ndm)] if log.do_medium: log('Starting restricted closed-shell %s-SCF' % self._history.name) log.hline() log('Iter Error CN Last nv Method Energy Change') log.hline() converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # Construct the Fock operator from scratch if the history is empty: if self._history.nused == 0: # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operators ham.compute_fock(*self._focks) # Compute the energy if needed by the history energy = ham.compute_energy() if self._history.need_energy \ else None # Add the current fock+dm pair to the history error = self._history.add(energy, dms, self._focks) # Screen logging if log.do_high: log(' DIIS add') if error < self.threshold: converged = True break if log.do_high: log.blank() if log.do_medium: energy_str = ' '*20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % ( counter, error, self._history.nused, energy_str )) if log.do_high: log.blank() fock_interpolated = False else: energy = None fock_interpolated = True # Take a regular SCF step using the current fock matrix. Then # construct a new density matrix and fock matrix. for i in xrange(ham.ndm): self._exps[i].from_fock(self._focks[i], overlap) occ_model.assign(*self._exps) for i in xrange(ham.ndm): self._exps[i].to_dm(dms[i]) ham.reset(*dms) energy = ham.compute_energy() if self._history.need_energy else None ham.compute_fock(*self._focks) # Add the current (dm, fock) pair to the history if log.do_high: log(' DIIS add') error = self._history.add(energy, dms, self._focks) # break when converged if error < self.threshold: converged = True break # Screen logging if log.do_high: log.blank() if log.do_medium: energy_str = ' '*20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % ( counter, error, self._history.nused, energy_str )) if log.do_high: log.blank() # get extra/intra-polated Fock matrix while True: # The following method writes the interpolated dms and focks # in-place. energy_approx, coeffs, cn, method, error = self._history.solve(dms, self._focks) # if the error is small on the interpolated state, we have # converged to a solution that may have fractional occupation # numbers. if error < self.threshold: converged = True break #if coeffs[coeffs<0].sum() < -1: # if log.do_high: # log(' DIIS (coeffs too negative) -> drop %i and retry' % self._history.stack[0].identity) # self._history.shrink() if self._history.nused <= 2: break if coeffs[-1] == 0.0: if log.do_high: log(' DIIS (last coeff zero) -> drop %i and retry' % self._history.stack[0].identity) self._history.shrink() else: break if False and len(coeffs) == 2: dms_tmp = [dm.copy() for dm in dms] import matplotlib.pyplot as pt xs = np.linspace(0.0, 1.0, 25) a, b = self._history._setup_equations() energies1 = [] energies2 = [] for x in xs: x_coeffs = np.array([1-x, x]) energies1.append(np.dot(x_coeffs, 0.5*np.dot(a, x_coeffs) - b)) self._history._build_combinations(x_coeffs, dms_tmp, None) ham.reset(*dms_tmp) energies2.append(ham.compute_energy()) print x, energies1[-1], energies2[-1] pt.clf() pt.plot(xs, energies1, label='est') pt.plot(xs, energies2, label='ref') pt.axvline(coeffs[1], color='k') pt.legend(loc=0) pt.savefig('diis_test_%05i.png' % counter) if energy_approx is not None: energy_change = energy_approx - min(state.energy for state in self._history.stack) else: energy_change = None # log if log.do_high: self._history.log(coeffs) if log.do_medium: change_str = ' '*10 if energy_change is None else '% 12.7f' % energy_change log('%4i %10.3e %12.7f %2i %s %12s' % ( counter, cn, coeffs[-1], self._history.nused, method, change_str )) if log.do_high: log.blank() if self.prune_old_states: # get rid of old states with zero coeff for i in xrange(self._history.nused): if coeffs[i] == 0.0: if log.do_high: log(' DIIS insignificant -> drop %i' % self._history.stack[0].identity) self._history.shrink() else: break # counter counter += 1 if log.do_medium: if converged: log('%4i %12.5e (converged)' % (counter, error)) log.blank() if not self.skip_energy or self._history.need_energy: if not self._history.need_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, overlap, occ_model, *orbs): """Find a self-consistent set of orbitals. Parameters ---------- ham : EffHam An effective Hamiltonian. overlap : np.ndarray, shape=(nbasis, nbasis) The overlap operator. occ_model : OccModel Model for the orbital occupations. orb1, orb2, ... : Orbitals The initial orbitals. The number of dms must match ham.ndm. """ # Some type checking if ham.ndm != len(orbs): raise TypeError( 'The number of initial orbital expansions does not match the Hamiltonian.' ) # Impose the requested occupation numbers occ_model.assign(*orbs) # Check the orthogonality of the orbitals for orb in orbs: orb.check_normalization(overlap) if log.do_medium: log('Starting plain SCF solver. ndm=%i' % ham.ndm) log.hline() log('Iter Error') log.hline() focks = [np.zeros(overlap.shape) for i in xrange(ham.ndm)] dms = [None] * ham.ndm converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # convert the orbital expansions to density matrices for i in xrange(ham.ndm): dms[i] = orbs[i].to_dm() # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operator ham.compute_fock(*focks) # Check for convergence error = 0.0 for i in xrange(ham.ndm): error += orbs[i].error_eigen(focks[i], overlap) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < self.threshold: converged = True break # If requested, add the level shift to the Fock operator if self.level_shift > 0: for i in xrange(ham.ndm): # The normal behavior is to shift down the occupied levels. focks[i] += -self.level_shift * get_level_shift( dms[i], overlap) # Diagonalize the fock operators to obtain new orbitals and for i in xrange(ham.ndm): orbs[i].from_fock(focks[i], overlap) # If requested, compensate for level-shift. This compensation # is only correct when the SCF has converged. if self.level_shift > 0: orbs[i].energies[:] += self.level_shift * orbs[ i].occupations # Assign new occupation numbers. occ_model.assign(*orbs) # counter counter += 1 if log.do_medium: log.blank() if not self.skip_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter
def converge_scf_diis_cs(ham, DIISHistoryClass, maxiter=128, threshold=1e-6, nvector=6, prune_old_states=False, skip_energy=False, scf_step='regular'): '''Minimize the energy of the closed-shell wavefunction with EDIIS **Arguments:** ham A Hamiltonian instance. DIISHistoryClass A DIIS history class. **Optional arguments:** maxiter The maximum number of iterations. When set to None, the SCF loop will go one until convergence is reached. threshold The convergence threshold for the wavefunction prune_old_states When set to True, old states are pruned from the history when their coefficient is zero. Pruning starts at the oldest state and stops as soon as a state is encountered with a non-zero coefficient. Even if some newer states have a zero coefficient. skip_energy When set to True, the final energy is not computed. Note that some DIIS variants need to compute the energy anyway. for these methods this option is irrelevant. scf_step The type of SCF step to take after the interpolated states was create from the DIIS history. This can be 'regular', 'oda2' or 'oda3'. **Raises:** NoSCFConvergence if the convergence criteria are not met within the specified number of iterations. **Returns:** the number of iterations ''' # allocated and define some one body operators lf = ham.system.lf wfn = ham.system.wfn overlap = ham.system.get_overlap() history = DIISHistoryClass(lf, nvector, overlap) fock = lf.create_one_body() dm = lf.create_one_body() # The scf step if scf_step == 'regular': cs_scf_step = CSSCFStep(ham) elif scf_step == 'oda2': cs_scf_step = CSQuadraticODAStep(ham) elif scf_step == 'oda3': cs_scf_step = CSCubicODAStep(ham) else: raise ValueError('scf_step argument not recognized: %s' % scf_step) # Get rid of outdated stuff ham.clear() if log.do_medium: log('Starting restricted closed-shell %s-SCF' % history.name) log.hline() log('Iter Error(alpha) CN(alpha) Last(alpha) nv Method Energy Change') log.hline() converged = False counter = 0 while maxiter is None or counter < maxiter: # Construct the Fock operator from scratch: if history.nused == 0: # Update the Fock matrix fock.clear() ham.compute_fock(fock, None) # Put this state also in the history energy = ham.compute() if history.need_energy else None # Add the current fock+dm pair to the history error = history.add(energy, wfn.dm_alpha, fock) if log.do_high: log(' DIIS add') if error < threshold: converged = True break if log.do_high: log.blank() if log.do_medium: energy_str = ' '*20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % ( counter, error, history.nused, energy_str )) if log.do_high: log.blank() fock_interpolated = False else: energy = None fock_interpolated = True # Construct a new density matrix and fock matrix (based on the current # density matrix or fock matrix). The fock matrix is modified in-place # while the density matrix is stored in ham.system.wfn mixing = cs_scf_step(energy, fock, fock_interpolated) if mixing == 0.0: converged = True break energy = ham.compute() if history.need_energy else None # Write intermediate results to checkpoint ham.system.update_chk('wfn') # Add the current (dm, fock) pair to the history if log.do_high: log(' DIIS add') error = history.add(energy, wfn.dm_alpha, fock) # break when converged if error < threshold: converged = True break if log.do_high: log.blank() if log.do_medium: energy_str = ' '*20 if energy is None else '% 20.13f' % energy log('%4i %12.5e %2i %20s' % ( counter, error, history.nused, energy_str )) if log.do_high: log.blank() # get extra/intra-polated Fock matrix while True: energy_approx, coeffs, cn, method = history.solve(dm, fock) #if coeffs[coeffs<0].sum() < -1: # if log.do_high: # log(' DIIS (coeffs too negative) -> drop %i and retry' % history.stack[0].identity) # history.shrink() if history.nused <= 2: break if coeffs[-1] == 0.0: if log.do_high: log(' DIIS (last coeff zero) -> drop %i and retry' % history.stack[0].identity) history.shrink() else: break if False and len(coeffs) == 2: tmp = dm.copy() import matplotlib.pyplot as pt xs = np.linspace(0.0, 1.0, 25) a, b = history._setup_equations() energies1 = [] energies2 = [] for x in xs: x_coeffs = np.array([1-x, x]) energies1.append(np.dot(x_coeffs, 0.5*np.dot(a, x_coeffs) - b)) history._build_combinations(x_coeffs, tmp, None) wfn.clear() wfn.update_dm('alpha', tmp) ham.clear() energies2.append(ham.compute()) print(x, energies1[-1], energies2[-1]) pt.clf() pt.plot(xs, energies1, label='est') pt.plot(xs, energies2, label='ref') pt.axvline(coeffs[1], color='k') pt.legend(loc=0) pt.savefig('ediis2_test_%05i.png' % counter) wfn.clear() wfn.update_dm('alpha', dm) ham.clear() if energy_approx is not None: energy_change = energy_approx - min(state.energy for state in history.stack) else: energy_change = None # log if log.do_high: history.log(coeffs) if log.do_medium: change_str = ' '*10 if energy_change is None else '% 12.7f' % energy_change log('%4i %10.3e %12.7f %2i %s %12s' % ( counter, cn, coeffs[-1], history.nused, method, change_str )) if log.do_high: log.blank() if prune_old_states: # get rid of old states with zero coeff for i in xrange(history.nused): if coeffs[i] == 0.0: if log.do_high: log(' DIIS insignificant -> drop %i' % history.stack[0].identity) history.shrink() else: break # counter counter += 1 if log.do_medium: if converged: log('%4i %12.5e (converged)' % (counter, error)) log.blank() if not skip_energy or history.need_energy: if not history.need_energy: ham.compute() if log.do_medium: ham.log_energy() if not converged: raise NoSCFConvergence return counter
def __call__(self, ham, overlap, occ_model, *orbs): """Find a self-consistent set of orbitals. Parameters ---------- ham : EffHam An effective Hamiltonian. overlap : np.ndarray, shape=(nbasis, nbasis) The overlap operator. occ_model : OccModel Model for the orbital occupations. orb1, orb2, ... : Orbitals The initial orbitals. The number of dms must match ham.ndm. """ # Some type checking if ham.ndm != len(orbs): raise TypeError('The number of initial orbital expansions does not match the Hamiltonian.') # Impose the requested occupation numbers occ_model.assign(*orbs) # Check the orthogonality of the orbitals for orb in orbs: orb.check_normalization(overlap) if log.do_medium: log('Starting plain SCF solver. ndm=%i' % ham.ndm) log.hline() log('Iter Error') log.hline() focks = [np.zeros(overlap.shape) for i in xrange(ham.ndm)] dms = [None] * ham.ndm converged = False counter = 0 while self.maxiter is None or counter < self.maxiter: # convert the orbital expansions to density matrices for i in xrange(ham.ndm): dms[i] = orbs[i].to_dm() # feed the latest density matrices in the hamiltonian ham.reset(*dms) # Construct the Fock operator ham.compute_fock(*focks) # Check for convergence error = 0.0 for i in xrange(ham.ndm): error += orbs[i].error_eigen(focks[i], overlap) if log.do_medium: log('%4i %12.5e' % (counter, error)) if error < self.threshold: converged = True break # If requested, add the level shift to the Fock operator if self.level_shift > 0: for i in xrange(ham.ndm): # The normal behavior is to shift down the occupied levels. focks[i] += -self.level_shift*get_level_shift(dms[i], overlap) # Diagonalize the fock operators to obtain new orbitals and for i in xrange(ham.ndm): orbs[i].from_fock(focks[i], overlap) # If requested, compensate for level-shift. This compensation # is only correct when the SCF has converged. if self.level_shift > 0: orbs[i].energies[:] += self.level_shift*orbs[i].occupations # Assign new occupation numbers. occ_model.assign(*orbs) # counter counter += 1 if log.do_medium: log.blank() if not self.skip_energy: ham.compute_energy() if log.do_medium: ham.log() if not converged: raise NoSCFConvergence return counter