def convergence_error_commutator(ham, lf, overlap, *dms): '''Compute the commutator error **Arguments:** ham A Hamiltonian instance. lf The linalg factory to be used. overlap The overlap operator. dm1, dm2, ... A list of density matrices. The numbers of dms must match ham.ndm. **Returns:** The commutator error. This measure (not this function) is also used in some SCF algorithms to check for convergence. ''' if len(dms) != ham.ndm: raise TypeError('Expecting %i density matrices, got %i.' % (ham.ndm, len(dms))) ham.reset(*dms) focks = [lf.create_two_index() for i in xrange(ham.ndm)] ham.compute_fock(*focks) error = 0.0 work = lf.create_two_index() commutator = lf.create_two_index() errorsq = 0.0 for i in xrange(ham.ndm): compute_commutator(dms[i], focks[i], overlap, work, commutator) errorsq += commutator.contract_two('ab,ab', commutator) return errorsq**0.5
def _build_combinations(self, coeffs, dms_output, focks_output): '''Construct a linear combination of density/fock matrices **Arguments:** coeffs The linear mixing coefficients for the previous SCF states. dms_output A list of output density matrices. (Ignored if None) focks_output A list of output density matrices. (Ignored if None) **Returns:** the commutator error, only when both dms_output and focks_output are given. ''' if dms_output is not None: if len(dms_output) != self.ndm: raise TypeError('The number of density matrices must match the ndm parameter.') for i in xrange(self.ndm): dms_stack = [self.stack[j].dms[i] for j in xrange(self.nused)] self._linear_combination(coeffs, dms_stack, dms_output[i]) if focks_output is not None: if len(focks_output) != self.ndm: raise TypeError('The number of Fock matrices must match the ndm parameter.') for i in xrange(self.ndm): focks_stack = [self.stack[j].focks[i] for j in xrange(self.nused)] self._linear_combination(coeffs, focks_stack, focks_output[i]) if not (dms_output is None or focks_output is None): errorsq = 0.0 for i in xrange(self.ndm): compute_commutator(dms_output[i], focks_output[i], self.overlap, self.work, self.commutator) errorsq += self.commutator.contract_two('ab,ab', self.commutator) return errorsq**0.5
def assign(self, identity, energy, dms, focks): '''Assign a new state. **Arguments:** identity A unique id for the new state. energy The energy of the new state. dm The density matrix of the new state. fock The Fock matrix of the new state. ''' self.identity = identity self.energy = energy self.normsq = 0.0 for i in xrange(self.ndm): self.dms[i].assign(dms[i]) self.focks[i].assign(focks[i]) compute_commutator(dms[i], focks[i], self.overlap, self.work, self.commutators[i]) self.normsq += self.commutators[i].contract_two('ab,ab', self.commutators[i])
def convergence_error_commutator(ham, overlap, *dms): '''Compute the commutator error Parameters ---------- ham A Hamiltonian instance. overlap The overlap operator. dm1, dm2, ... A list of density matrices. The numbers of dms must match ham.ndm. Returns ------- error: float The commutator error. This measure (not this function) is also used in some SCF algorithms to check for convergence. ''' if len(dms) != ham.ndm: raise TypeError('Expecting %i density matrices, got %i.' % (ham.ndm, len(dms))) ham.reset(*dms) focks = [np.zeros(dms[0].shape) for i in xrange(ham.ndm)] ham.compute_fock(*focks) error = 0.0 errorsq = 0.0 for i in xrange(ham.ndm): commutator = compute_commutator(dms[i], focks[i], overlap) errorsq += np.einsum('ab,ab', commutator, commutator) return errorsq**0.5
def assign(self, identity, energy, dms, focks): '''Assign a new state. **Arguments:** identity A unique id for the new state. energy The energy of the new state. dm The density matrix of the new state. fock The Fock matrix of the new state. ''' self.identity = identity self.energy = energy self.normsq = 0.0 for i in xrange(self.ndm): self.dms[i][:] = dms[i] self.focks[i][:] = focks[i] self.commutators[i][:] = compute_commutator( dms[i], focks[i], self.overlap) self.normsq += np.einsum('ab,ab', self.commutators[i], self.commutators[i])
def assign(self, identity, energy, dms, focks): '''Assign a new state. **Arguments:** identity A unique id for the new state. energy The energy of the new state. dm The density matrix of the new state. fock The Fock matrix of the new state. ''' self.identity = identity self.energy = energy self.normsq = 0.0 for i in xrange(self.ndm): self.dms[i][:] = dms[i] self.focks[i][:] = focks[i] self.commutators[i][:] = compute_commutator(dms[i], focks[i], self.overlap) self.normsq += np.einsum('ab,ab', self.commutators[i], self.commutators[i])
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, *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