Esempio n. 1
0
 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()
Esempio n. 2
0
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
Esempio n. 3
0
 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()
Esempio n. 4
0
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
Esempio n. 5
0
 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()
Esempio n. 6
0
 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()
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
 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')
Esempio n. 10
0
 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')
Esempio n. 11
0
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
Esempio n. 12
0
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
Esempio n. 13
0
 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()
Esempio n. 14
0
 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()
Esempio n. 15
0
 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()
Esempio n. 16
0
 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()
Esempio n. 17
0
 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()
Esempio n. 18
0
    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()
Esempio n. 19
0
    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()
Esempio n. 20
0
    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()
Esempio n. 21
0
    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()
Esempio n. 22
0
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()
Esempio n. 23
0
 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()
Esempio n. 24
0
    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()
Esempio n. 25
0
    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()
Esempio n. 26
0
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()
Esempio n. 27
0
    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
Esempio n. 28
0
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
Esempio n. 29
0
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)
Esempio n. 30
0
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)
Esempio n. 31
0
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
Esempio n. 32
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
Esempio n. 33
0
    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
Esempio n. 34
0
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
Esempio n. 35
0
    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()
Esempio n. 36
0
    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
Esempio n. 37
0
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
Esempio n. 38
0
    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()
Esempio n. 39
0
    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
Esempio n. 40
0
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
Esempio n. 41
0
    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
Esempio n. 42
0
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