def _log_init(self): '''Write a summary of the wavefunction to the screen logger''' if log.do_medium: log('Initialized: %s' % self) if self.occ_model is not None: self.occ_model.log() log.blank()
def guess_core_hamiltonian(overlap, core, *orbs): '''Guess the orbitals by diagonalizing a core Hamiltonian. Parameters ---------- overlap : np.ndarray, shape=(nbasis, nbasis), dtype=float The overlap operator. core : np.ndarray, shape=(nbasis, nbasis), dtype=float The core Hamiltonian. operator that resembles a Fock operator is fine. Usually, one adds the kinetic energy and nuclear attraction integrals. orb1, orb2, ... : Orbitals A list of Orbitals objects (output arguments) This method only modifies the expansion coefficients and the orbital energies. ''' if log.do_medium: log('Performing a core Hamiltonian guess.') log.blank() if len(orbs) == 0: raise TypeError('At least one set of orbitals.') # Compute orbitals. orbs[0].from_fock(core, overlap) # Copy to other Orbitals objects. for i in xrange(1, len(orbs)): orbs[i].coeffs[:] = orbs[0].coeffs orbs[i].energies[:] = orbs[0].energies
def _log_init(self): if log.do_medium: log('Initialized: %s' % self) log.deflist([ ('Numbers', self._rgrid_map.keys()), ('Records', self._map.keys()), ]) log.blank()
def guess_hamiltonian_core(system): if log.do_medium: log('Performing a hamiltonian core guess.') log.blank() if isinstance(system.wfn, RestrictedWFN): guess_hamiltonian_core_cs(system) elif isinstance(system.wfn, UnrestrictedWFN): guess_hamiltonian_core_os(system) else: raise NotImplementedError
def _log_init(self): if log.do_medium: log('Initialized: %s' % self) log.deflist([ ('Size', self.size), ('Switching function', 'k=%i' % self._k), ]) log.blank() # Cite reference log.cite('becke1988_multicenter', 'the multicenter integration scheme used for the molecular integration grid') log.cite('cordero2008', 'the covalent radii used for the Becke-Lebedev molecular integration grid')
def _log_init(self): if log.do_medium: log('Initialized: %s' % self) log.deflist([ ('Size', self.size), ('Switching function', 'k=%i' % self._k), ]) log.blank() # Cite reference biblio.cite('becke1988_multicenter', 'the multicenter integration scheme used for the molecular integration grid') biblio.cite('cordero2008', 'the covalent radii used for the Becke-Lebedev molecular integration grid')
def guess_core_hamiltonian(overlap, *args, **kwargs): '''Guess the orbitals by diagonalizing a core Hamiltonian **Arguments:** overlap The overlap operator. core1, core2, ... A number of operators that add up to the core Hamiltonian. Any set of operators whose sum resembles a Fock operator is fine. Usually, one passes the kinetic energy and nuclear attraction integrals. exp1, exp2, ... A list of wavefunction expansion objects (output arguments) This method only modifies the expansion coefficients and the orbital energies. ''' if len(kwargs) != 0: raise TypeError('Unknown keyword arguments: %s' % kwargs.keys()) if log.do_medium: log('Performing a core Hamiltonian guess.') log.blank() core = [] exps = [] for arg in args: if isinstance(arg, TwoIndex): core.append(arg) elif isinstance(arg, Expansion): exps.append(arg) else: raise TypeError('argument of unsupported type: %s' % arg) if len(core) == 0: raise TypeError( 'At least one term is needed for the core Hamiltonian.') if len(exps) == 0: raise TypeError('At least one wavefunction expansion is needed.') # Take sum of operators for core hamiltonian hamcore = core[0].copy() for term in core[1:]: hamcore.iadd(term) # Compute orbitals. exps[0].from_fock(hamcore, overlap) # Copy to other expansions. for i in xrange(1, len(exps)): exps[i].coeffs[:] = exps[0].coeffs exps[i].energies[:] = exps[0].energies
def guess_core_hamiltonian(overlap, *args, **kwargs): '''Guess the orbitals by diagonalizing a core Hamiltonian **Arguments:** overlap The overlap operator. core1, core2, ... A number of operators that add up to the core Hamiltonian. Any set of operators whose sum resembles a Fock operator is fine. Usually, one passes the kinetic energy and nuclear attraction integrals. exp1, exp2, ... A list of wavefunction expansion objects (output arguments) This method only modifies the expansion coefficients and the orbital energies. ''' if len(kwargs) != 0: raise TypeError('Unknown keyword arguments: %s' % kwargs.keys()) if log.do_medium: log('Performing a core Hamiltonian guess.') log.blank() core = [] exps = [] for arg in args: if isinstance(arg, TwoIndex): core.append(arg) elif isinstance(arg, Expansion): exps.append(arg) else: raise TypeError('argument of unsupported type: %s' % arg) if len(core) == 0: raise TypeError('At least one term is needed for the core Hamiltonian.') if len(exps) == 0: raise TypeError('At least one wavefunction expansion is needed.') # Take sum of operators for core hamiltonian hamcore = core[0].copy() for term in core[1:]: hamcore.iadd(term) # Compute orbitals. exps[0].from_fock(hamcore, overlap) # Copy to other expansions. for i in xrange(1, len(exps)): exps[i].coeffs[:] = exps[0].coeffs exps[i].energies[:] = exps[0].energies
def log(self, coeffs): eref = min(state.energy for state in self.stack[:self.nused]) if eref is None: log(' DIIS history normsq coeff id') for i in xrange(self.nused): state = self.stack[i] log(' DIIS history %12.5e %12.7f %8i' % (state.normsq, coeffs[i], state.identity)) else: log(' DIIS history normsq energy coeff id') for i in xrange(self.nused): state = self.stack[i] log(' DIIS history %12.5e %12.5e %12.7f %8i' % (state.normsq, state.energy-eref, coeffs[i], state.identity)) log.blank()
def log(self, coeffs): eref = min(state.energy for state in self.stack[:self.nused]) if eref is None: log(' DIIS history norm coeff id') for i in xrange(self.nused): state = self.stack[i] log(' DIIS history %12.5e %12.7f %8i' % (state.norm, coeffs[i], state.identity)) else: log(' DIIS history norm energy coeff id') for i in xrange(self.nused): state = self.stack[i] log(' DIIS history %12.5e %12.5e %12.7f %8i' % (state.norm, state.energy-eref, coeffs[i], state.identity)) 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 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, system, grid, local, slow, lmax, moldens=None): ''' **Arguments:** system The system to be partitioned. grid The integration grid local Whether or not to use local (non-periodic) grids. slow When ``True``, also the AIM properties are computed that use the AIM overlap operators. lmax The maximum angular momentum in multipole expansions. **Optional arguments:** moldens The all-electron density grid data. ''' JustOnceClass.__init__(self) self._system = system self._grid = grid self._local = local self._slow = slow self._lmax = lmax # Caching stuff, to avoid recomputation of earlier results self._cache = Cache() # Caching of work arrays to avoid reallocation if moldens is not None: self._cache.dump('moldens', moldens) # Initialize the subgrids if local: self._init_subgrids() # Some screen logging self._init_log_base() self._init_log_scheme() self._init_log_memory() if log.do_medium: log.blank()
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 project_orbitals_mgs(system, old_wfn, old_obasis, eps=1e-10): '''Project orbitals from the ``old_wfn`` (wrt ``old_basis``) on the wfn of the ``system`` object with the modified Gram-Schmidt algorithm. **Arguments:** system The system with the new orbital basis. It must have a wavefunction object compatible with the old wavefunction (restricted versus unrestricted). old_wfn The old (mean-field) wavefunction object old_obasis The orbital basis for the old wavefunction **Optional arguments:** eps A threshold for the renormalization in the Gram-Schmidt procedure. See ``project_orbitals_mgs_low`` for details. ''' if log.do_medium: log('Projecting the wavefunction on a new basis.') log.blank() if isinstance(old_wfn, RestrictedWFN): assert isinstance(system.wfn, RestrictedWFN) if 'exp_alpha' not in system.wfn._cache: system.wfn.init_exp('alpha') project_orbitals_mgs_low(old_obasis, system.obasis, old_wfn.exp_alpha, system.wfn.exp_alpha, eps) else: assert isinstance(system.wfn, UnrestrictedWFN) if 'exp_alpha' not in system.wfn._cache: system.wfn.init_exp('alpha') system.wfn.init_exp('beta') project_orbitals_mgs_low(old_obasis, system.obasis, old_wfn.exp_alpha, system.wfn.exp_alpha, eps) project_orbitals_mgs_low(old_obasis, system.obasis, old_wfn.exp_beta, system.wfn.exp_beta, eps) system.wfn.clear_dm()
def _log_init(self): '''Write some basic information about the system to the screen logger.''' if log.do_medium: log('Initialized: %s' % self) log.deflist([('Number of atoms', self.natom)] + [('Number of %s' % periodic[n].symbol, (self.numbers == n).sum()) for n in sorted(np.unique(self.numbers))] + [ ('Linalg Factory', self._lf), ('Orbital basis', self._obasis), ('Wavefunction', self._wfn), ('Checkpoint file', self._chk), ]) if len(self._cache) > 0: log('The following cached items are present: %s' % (', '.join(self._cache.iterkeys()))) if len(self._extra) > 0: log('The following extra attributes are present: %s' % (', '.join(self._extra.iterkeys()))) 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 __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 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 setup_mean_field_wfn(system, charge=0, mult=None, restricted=None, temperature=0): '''Initialize a mean-field wavefunction and assign it to the given system. **Arguments:** system A System instance. **Optional Arguments:** charge The total charge of the system. Defaults to zero. mult The spin multiplicity. Defaults to lowest possible. Use 'free' to let the SCF algorithm find the spin multiplicity with the lowest energy. (Beware of local minima.) temperature The electronic temperature used for the Fermi smearing. restricted Set to True or False to enforce a restricted or unrestricted wavefunction. Note that restricted open shell is not yet supported. When not set, restricted is used when mult==1 and unrestricted otherwise. If a wavefunction of the correct type is already present, it will be configured with the requested charge and multiplicity. ''' if system._obasis is None: raise RuntimeError('A wavefunction can only be initialized when a basis is specified.') # Determine charge, spin, mult and restricted if charge is None: charge = 0 nel = system.numbers.sum() - charge if isinstance(nel, int): if mult is None: mult = nel%2+1 elif mult != 'free' and ((nel%2 == 0) ^ (mult%2 != 0)): raise ValueError('Not compatible: number of electrons = %i and spin multiplicity = %i' % (nel, mult)) else: if mult is None: if restricted is True: mult = 1.0 else: raise ValueError('In case of an unrestricted wfn and a fractional number of electrons, mult must be given explicitly or must be set to \'free\'.') if mult != 'free' and mult < 1: raise ValueError('mult must be strictly positive.') if restricted is True and mult != 1: raise ValueError('Restricted==True only works when mult==1. Restricted open shell is not supported yet.') if restricted is None: restricted = mult==1 # Show some thing on screen. if log.do_medium: log('Wavefunction initialization, without initial guess.') log.deflist([ ('Charge', charge), ('Multiplicity', mult), ('Number of e', nel), ('Restricted', restricted), ]) log.blank() # Create a model for the occupation numbers if temperature == 0: if restricted: occ_model = AufbauOccModel(nel/2, nel/2) elif mult=='free': occ_model = AufbauSpinOccModel(nel) else: occ_model = AufbauOccModel((nel + (mult-1))/2, (nel - (mult-1))/2) else: if restricted: occ_model = FermiOccModel(nel/2, nel/2, temperature) elif mult=='free': raise NotImplementedError #occ_model = FermiSpinOccModel(nel) else: occ_model = FermiOccModel((nel + (mult-1))/2, (nel - (mult-1))/2, temperature) if system._wfn is not None: # Check if the existing wfn is consistent with the arguments if not isinstance(system.wfn, MeanFieldWFN): raise ValueError('A wavefunction is already present and it is not a mean-field wavefunction.') elif system.wfn.nbasis != system.obasis.nbasis: raise ValueError('The number of basis functions in the wfn is incorrect.') elif restricted ^ isinstance(system.wfn, RestrictedWFN): raise ValueError('The wfn does not match the restricted argument.') # Assign occ_model system.wfn.occ_model = occ_model # If the wfn contains an expansion, update the occupations and remove # density matrices. Otherwise clean up if 'exp_alpha' in system.wfn._cache: if restricted: system.wfn.occ_model.assign(system.wfn.exp_alpha) else: system.wfn.occ_model.assign(system.wfn.exp_alpha, system.wfn.exp_beta) system.wfn.clear_dm() else: system.wfn.clear() else: # if the wfn does not exist yet, create a proper one. if restricted: system._wfn = RestrictedWFN(system.lf, system.obasis.nbasis, occ_model) else: system._wfn = UnrestrictedWFN(system.lf, system.obasis.nbasis, occ_model)
def setup_mean_field_wfn(system, charge=0, mult=None, restricted=None, temperature=0): '''Initialize a mean-field wavefunction and assign it to the given system. **Arguments:** system A System instance. **Optional Arguments:** charge The total charge of the system. Defaults to zero. mult The spin multiplicity. Defaults to lowest possible. Use 'free' to let the SCF algorithm find the spin multiplicity with the lowest energy. (Beware of local minima.) temperature The electronic temperature used for the Fermi smearing. restricted Set to True or False to enforce a restricted or unrestricted wavefunction. Note that restricted open shell is not yet supported. When not set, restricted is used when mult==1 and unrestricted otherwise. If a wavefunction of the correct type is already present, it will be configured with the requested charge and multiplicity. ''' if system._obasis is None: raise RuntimeError( 'A wavefunction can only be initialized when a basis is specified.' ) # Determine charge, spin, mult and restricted if charge is None: charge = 0 nel = system.numbers.sum() - charge if isinstance(nel, int): if mult is None: mult = nel % 2 + 1 elif mult != 'free' and ((nel % 2 == 0) ^ (mult % 2 != 0)): raise ValueError( 'Not compatible: number of electrons = %i and spin multiplicity = %i' % (nel, mult)) else: if mult is None: if restricted is True: mult = 1.0 else: raise ValueError( 'In case of an unrestricted wfn and a fractional number of electrons, mult must be given explicitly or must be set to \'free\'.' ) if mult != 'free' and mult < 1: raise ValueError('mult must be strictly positive.') if restricted is True and mult != 1: raise ValueError( 'Restricted==True only works when mult==1. Restricted open shell is not supported yet.' ) if restricted is None: restricted = mult == 1 # Show some thing on screen. if log.do_medium: log('Wavefunction initialization, without initial guess.') log.deflist([ ('Charge', charge), ('Multiplicity', mult), ('Number of e', nel), ('Restricted', restricted), ]) log.blank() # Create a model for the occupation numbers if temperature == 0: if restricted: occ_model = AufbauOccModel(nel / 2, nel / 2) elif mult == 'free': occ_model = AufbauSpinOccModel(nel) else: occ_model = AufbauOccModel((nel + (mult - 1)) / 2, (nel - (mult - 1)) / 2) else: if restricted: occ_model = FermiOccModel(nel / 2, nel / 2, temperature) elif mult == 'free': raise NotImplementedError #occ_model = FermiSpinOccModel(nel) else: occ_model = FermiOccModel((nel + (mult - 1)) / 2, (nel - (mult - 1)) / 2, temperature) if system._wfn is not None: # Check if the existing wfn is consistent with the arguments if not isinstance(system.wfn, MeanFieldWFN): raise ValueError( 'A wavefunction is already present and it is not a mean-field wavefunction.' ) elif system.wfn.nbasis != system.obasis.nbasis: raise ValueError( 'The number of basis functions in the wfn is incorrect.') elif restricted ^ isinstance(system.wfn, RestrictedWFN): raise ValueError('The wfn does not match the restricted argument.') # Assign occ_model system.wfn.occ_model = occ_model # If the wfn contains an expansion, update the occupations and remove # density matrices. Otherwise clean up if 'exp_alpha' in system.wfn._cache: if restricted: system.wfn.occ_model.assign(system.wfn.exp_alpha) else: system.wfn.occ_model.assign(system.wfn.exp_alpha, system.wfn.exp_beta) system.wfn.clear_dm() else: system.wfn.clear() else: # if the wfn does not exist yet, create a proper one. if restricted: system._wfn = RestrictedWFN(system.lf, system.obasis.nbasis, occ_model) else: system._wfn = UnrestrictedWFN(system.lf, system.obasis.nbasis, occ_model)
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, 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 __init__(self, coordinates, numbers, pseudo_numbers, grid, moldens, spindens, local, lmax): ''' **Arguments:** coordinates An array (N, 3) with centers for the atom-centered grids. numbers An array (N,) with atomic numbers. pseudo_numbers An array (N,) with effective charges. When set to None, this defaults to``numbers.astype(float)``. grid The integration grid moldens The spin-summed electron density on the grid. spindens The spin difference density on the grid. (Can be None) local Whether or not to use local (non-periodic) subgrids for atomic integrals. lmax The maximum angular momentum in multipole expansions. ''' # Init base class JustOnceClass.__init__(self) # Some type checking for first three arguments natom, coordinates, numbers, pseudo_numbers = typecheck_geo(coordinates, numbers, pseudo_numbers) self._natom = natom self._coordinates = coordinates self._numbers = numbers self._pseudo_numbers = pseudo_numbers # Assign remaining arguments as attributes self._grid = grid self._moldens = moldens self._spindens = spindens self._local = local self._lmax = lmax # Caching stuff, to avoid recomputation of earlier results self._cache = Cache() # Initialize the subgrids if local: self._init_subgrids() # Some screen logging self._init_log_base() self._init_log_scheme() self._init_log_memory() if log.do_medium: log.blank()
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, 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_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, 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