Example #1
0
    def __call__(self):
        """dumps single-orbital entropy and orbital-pair mututal information

        see :py:meth:`horton.orbital_entanglement.orbital_entanglement.OrbitalEntanglement.dump_output` for more info
        """

        if log.do_medium:
            log(' ')
            log('Calculating orbital entanglement measures')
            log(' ')

        #
        # Compute single-orbital entropy and mutual information
        #
        if log.do_medium:
            log('  Computing s(1) and I_ij')
        self.compute_single_orbital_entropy()
        self.compute_two_orbital_entropy()
        self.compute_mutual_information()

        #
        # Dump output to file:
        #
        if log.do_medium:
            log('  Dumping output files')
            log(' ')
            log.hline('=')
        self.dump_output()
Example #2
0
    def __call__(self):
        """dumps single-orbital entropy and orbital-pair mututal information

        see :py:meth:`horton.orbital_entanglement.orbital_entanglement.OrbitalEntanglement.dump_output` for more info
        """

        if log.do_medium:
            log(' ')
            log('Calculating orbital entanglement measures')
            log(' ')

        #
        # Compute single-orbital entropy and mutual information
        #
        if log.do_medium:
            log('  Computing s(1) and I_ij')
        self.compute_single_orbital_entropy()
        self.compute_two_orbital_entropy()
        self.compute_mutual_information()

        #
        # Dump output to file:
        #
        if log.do_medium:
            log('  Dumping output files')
            log(' ')
            log.hline('=')
        self.dump_output()
Example #3
0
 def normalize(self):
     if log.do_medium:
         log('Normalizing proatoms to integer populations')
         log('   Z  charge             before             after')
         log.hline()
     for number in self.get_numbers():
         rgrid = self.get_rgrid(number)
         for charge in self.get_charges(number):
             r = self.get_record(number, charge)
             nel_before = rgrid.integrate(r.rho)
             nel_integer = r.pseudo_number - charge
             r.rho[:] *= nel_integer / nel_before
             nel_after = rgrid.integrate(r.rho)
             if log.do_medium:
                 log('%4i     %+3i    %15.8e   %15.8e' %
                     (number, charge, nel_before, nel_after))
Example #4
0
 def normalize(self):
     if log.do_medium:
         log('Normalizing proatoms to integer populations')
         log('   Z  charge             before             after')
         log.hline()
     for number in self.get_numbers():
         rgrid = self.get_rgrid(number)
         for charge in self.get_charges(number):
             r = self.get_record(number, charge)
             nel_before = rgrid.integrate(r.rho)
             nel_integer = r.pseudo_number - charge
             r.rho[:] *= nel_integer/nel_before
             nel_after = rgrid.integrate(r.rho)
             if log.do_medium:
                 log('%4i     %+3i    %15.8e   %15.8e' % (
                     number, charge, nel_before, nel_after
                 ))
Example #5
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()
Example #6
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()
Example #7
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()
Example #8
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()
Example #9
0
    def do_partitioning(self):
        # Perform one general check in the beginning to avoid recomputation
        new = any(('at_weights', i) not in self.cache for i in xrange(self.natom))
        new |= 'niter' not in self.cache
        new |= 'change'not in self.cache
        if new:
            propars = self._init_propars()
            if log.medium:
                log.hline()
                log('Iteration       Change')
                log.hline()

            counter = 0
            change = 1e100

            while True:
                counter += 1

                # Update the parameters that determine the pro-atoms.
                old_propars = propars.copy()
                self._update_propars()

                # Check for convergence
                change = self.compute_change(propars, old_propars)
                if log.medium:
                    log('%9i   %10.5e' % (counter, change))
                if change < self._threshold or counter >= self._maxiter:
                    break

            if log.medium:
                log.hline()

            self._finalize_propars()
            self.cache.dump('niter', counter, tags='o')
            self.cache.dump('change', change, tags='o')
Example #10
0
    def compact(self, nel_lost):
        '''Make the pro-atoms more compact

           **Argument:**

           nel_lost
                This parameter controls the part of the tail that gets
                neglected. This is the (maximym) number of electrons in the part
                that gets discarded.

           Note that only 'safe' atoms are considered to determine the cutoff
           radius.
        '''
        if log.do_medium:
            log('Reducing extents of the pro-atoms')
            log('   Z     npiont           radius')
            log.hline()
        for number in self.get_numbers():
            rgrid = self.get_rgrid(number)
            npoint = 0
            for charge in self.get_charges(number, safe=True):
                r = self.get_record(number, charge)
                nel = r.pseudo_number-charge
                npoint = max(npoint, r.compute_radii([nel-nel_lost])[0][0]+1)
            for charge in self.get_charges(number):
                r = self.get_record(number, charge)
                r.chop(npoint)
            new_rgrid = self._rgrid_map[number].chop(npoint)
            self._rgrid_map[number] = new_rgrid
            if log.do_medium:
                log('%4i   %5i -> %5i    %10.3e -> %10.3e' % (
                    number, rgrid.size, new_rgrid.size, rgrid.radii[-1],
                    new_rgrid.radii[-1]
                ))
        if log.do_medium:
            log.hline()
Example #11
0
    def compact(self, nel_lost):
        '''Make the pro-atoms more compact

           **Argument:**

           nel_lost
                This parameter controls the part of the tail that gets
                neglected. This is the (maximym) number of electrons in the part
                that gets discarded.

           Note that only 'safe' atoms are considered to determine the cutoff
           radius.
        '''
        if log.do_medium:
            log('Reducing extents of the pro-atoms')
            log('   Z     npiont           radius')
            log.hline()
        for number in self.get_numbers():
            rgrid = self.get_rgrid(number)
            npoint = 0
            for charge in self.get_charges(number, safe=True):
                r = self.get_record(number, charge)
                nel = r.pseudo_number-charge
                npoint = max(npoint, r.compute_radii([nel-nel_lost])[0][0]+1)
            for charge in self.get_charges(number):
                r = self.get_record(number, charge)
                r.chop(npoint)
            new_rgrid = self._rgrid_map[number].chop(npoint)
            self._rgrid_map[number] = new_rgrid
            if log.do_medium:
                log('%4i   %5i -> %5i    %10.3e -> %10.3e' % (
                    number, rgrid.size, new_rgrid.size, rgrid.radii[-1],
                    new_rgrid.radii[-1]
                ))
        if log.do_medium:
            log.hline()
Example #12
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()
Example #13
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()
Example #14
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()
Example #15
0
    def __init__(self, numbers, proatomdb):
        self.numbers = numbers
        self.proatomdb = proatomdb

        self.nbasis = 0
        self.basis_specs = []

        for i in xrange(len(numbers)):
            number = numbers[i]
            rgrid = proatomdb.get_rgrid(number)
            licos = self._init_atom_licos(number, proatomdb)

            #padb_charges = proatomdb.get_charges(number, safe=True)
            #complete = proatomdb.get_record(number, padb_charges[0]).pseudo_population == 1

            # remove redundant basis functions
            for j in xrange(len(licos) - 1, -1, -1):
                # test if this lico is redundant with respect to earlier ones
                redundant = False
                rho = self.proatomdb.get_rho(number, licos[j])
                for earlier_lico in licos[:j]:
                    earlier_rho = self.proatomdb.get_rho(number, earlier_lico)
                    delta = rho - earlier_rho
                    rmsd = np.sqrt(rgrid.integrate(delta, delta))
                    if rmsd < 1e-8:
                        redundant = True
                        break
                # remove if redundant
                if redundant:
                    if log.do_medium:
                        log('Skipping redundant basis function for atom %i: %s'
                            % (i, licos[j]))
                    licos.pop(j)

            self.basis_specs.append([self.nbasis, licos])
            self.nbasis += len(licos)

        if log.do_high:
            log('Hirshfeld-E basis')
            log.hline()
            log('Atom   Z   k label')
            log.hline()
            for i in xrange(len(numbers)):
                for j in xrange(self.get_atom_nbasis(i)):
                    label = self.get_basis_label(i, j)
                    log('%4i %3i %3i %s' % (i, numbers[i], j, label))
            log.hline()
Example #16
0
    def __init__(self, numbers, proatomdb):
        self.numbers = numbers
        self.proatomdb = proatomdb

        self.nbasis = 0
        self.basis_specs = []

        for i in xrange(len(numbers)):
            number = numbers[i]
            rgrid = proatomdb.get_rgrid(number)
            licos = self._init_atom_licos(number, proatomdb)

            #padb_charges = proatomdb.get_charges(number, safe=True)
            #complete = proatomdb.get_record(number, padb_charges[0]).pseudo_population == 1

            # remove redundant basis functions
            for j in xrange(len(licos)-1, -1, -1):
                # test if this lico is redundant with respect to earlier ones
                redundant = False
                rho = self.proatomdb.get_rho(number, licos[j])
                for earlier_lico in licos[:j]:
                    earlier_rho = self.proatomdb.get_rho(number, earlier_lico)
                    delta = rho - earlier_rho
                    rmsd = np.sqrt(rgrid.integrate(delta, delta))
                    if rmsd < 1e-8:
                        redundant = True
                        break
                # remove if redundant
                if redundant:
                    if log.do_medium:
                        log('Skipping redundant basis function for atom %i: %s' % (i, licos[j]))
                    licos.pop(j)

            self.basis_specs.append([self.nbasis, licos])
            self.nbasis += len(licos)

        if log.do_high:
            log('Hirshfeld-E basis')
            log.hline()
            log('Atom   Z   k label')
            log.hline()
            for i in xrange(len(numbers)):
                for j in xrange(self.get_atom_nbasis(i)):
                    label = self.get_basis_label(i, j)
                    log('%4i %3i %3i %s' % (i, numbers[i], j, label))
            log.hline()
Example #17
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
Example #18
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
Example #19
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
Example #20
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
Example #21
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
Example #22
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
Example #23
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
Example #24
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
Example #25
0
    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
Example #26
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
Example #27
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
Example #28
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
Example #29
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
Example #30
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