예제 #1
0
파일: scf_oda.py 프로젝트: stevenvdb/horton
    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
예제 #2
0
    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
예제 #3
0
    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