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, 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, *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