Пример #1
0
 def kernel(s1, s2, s12, orbocc):
     """Make IAOs for a molecule or single k-point."""
     s21 = s12.conj().T
     # Minimal basis CD is not expected to fail
     s2cd = scipy.linalg.cho_factor(s2)
     ctild = scipy.linalg.cho_solve(s2cd, numpy.dot(s21, orbocc))
     # Try Cholesky of computational basis first
     try:
         s1cd = scipy.linalg.cho_factor(s1)
         p12 = scipy.linalg.cho_solve(s1cd, s12)
         ctild = scipy.linalg.cho_solve(s1cd, numpy.dot(s12, ctild))
     # For overcomplete basis sets, use eigendecomposition + canonical orthogonalization instead
     except numpy.linalg.LinAlgError:
         se, sv = numpy.linalg.eigh(s1)
         logger.debug(
             mol,
             "Cholesky decomp. of overlap S failed; removing %d eigenvectors of S with eigenvalues below %.2e",
             numpy.count_nonzero(se < lindep_threshold), lindep_threshold)
         keep = (se >= lindep_threshold)
         invS = numpy.einsum("ai,i,bi->ab", sv[:, keep], 1 / se[keep],
                             sv[:, keep])
         p12 = numpy.dot(invS, s12)
         ctild = numpy.dot(p12, ctild)
     ctild = vec_lowdin(ctild, s1)
     ccs1 = reduce(numpy.dot, (orbocc, orbocc.conj().T, s1))
     ccs2 = reduce(numpy.dot, (ctild, ctild.conj().T, s1))
     #a is the set of IAOs in the original basis
     a = (p12 + reduce(numpy.dot, (ccs1, ccs2, p12)) * 2 -
          numpy.dot(ccs1, p12) - numpy.dot(ccs2, p12))
     return a
Пример #2
0
Файл: iao.py Проект: pyscf/pyscf
 def make_iaos(s1, s2, s12, mo):
     """Make IAOs for a molecule or single k-point"""
     s21 = s12.conj().T
     # s2 is overlap in minimal reference basis and should never be singular:
     s2cd = scipy.linalg.cho_factor(s2)
     ctild = scipy.linalg.cho_solve(s2cd, numpy.dot(s21, mo))
     try:
         s1cd = scipy.linalg.cho_factor(s1)
         p12 = scipy.linalg.cho_solve(s1cd, s12)
         ctild = scipy.linalg.cho_solve(s1cd, numpy.dot(s12, ctild))
     # s1 can be singular in large basis sets: Use canonical orthogonalization in this case:
     except numpy.linalg.LinAlgError:
         x = scf.addons.canonical_orth_(s1, lindep_threshold)
         p12 = numpy.linalg.multi_dot((x, x.conj().T, s12))
         ctild = numpy.dot(p12, ctild)
     # If there are no occupied orbitals at this k-point, all but the first term will vanish:
     if mo.shape[-1] == 0:
         return p12
     ctild = vec_lowdin(ctild, s1)
     ccs1 = numpy.linalg.multi_dot((mo, mo.conj().T, s1))
     ccs2 = numpy.linalg.multi_dot((ctild, ctild.conj().T, s1))
     #a is the set of IAOs in the original basis
     a = (p12 + 2 * numpy.linalg.multi_dot(
         (ccs1, ccs2, p12)) - numpy.dot(ccs1, p12) - numpy.dot(ccs2, p12))
     return a
Пример #3
0
def vvo(mol, orbocc, orbvirt, iaos=None, s=None, verbose=logger.NOTE):
    '''Valence Virtual Orbitals ref. 10.1021/acs.jctc.7b00493

    Valence virtual orbitals can be formed from the singular value 
    decomposition of the overlap between the canonical molecular orbitals
    and an accurate underlying atomic basis set. This implementation uses
    the intrinsic atomic orbital as this underlying set. VVOs can also be 
    formed from the null space of the overlap of the canonical molecular 
    orbitals and the underlying atomic basis sets (IAOs). This is not 
    implemented here.

    Args:
        mol : the molecule or cell object

        orbocc : occupied molecular orbital coefficients

        orbvirt : virtual molecular orbital coefficients

    Kwargs:
        iaos : 2D array
            the array of IAOs

        s : 2D array
            the overlap array in the ao basis

    Returns:
        VVOs in the basis defined in mol object.
    '''
    log = logger.new_logger(mol, verbose)
    if s is None:
        if getattr(mol, 'pbc_intor', None):  # whether mol object is a cell
            if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2:
                s = mol.pbc_intor('int1e_ovlp', hermi=1)
            else:
                raise NotImplementedError('k-points crystal orbitals')
        else:
            s = mol.intor_symmetric('int1e_ovlp')

    if iaos is None:
        iaos = iao.iao(mol, orbocc)

    nvvo = iaos.shape[1] - orbocc.shape[1]

    # Symmetrically orthogonalization of the IAO orbitals as Knizia's
    # implementation.  The IAO returned by iao.iao function is not orthogonal.
    iaos = orth.vec_lowdin(iaos, s)

    #S = reduce(np.dot, (orbvirt.T, s, iaos))
    S = numpy.einsum('ji,jk,kl->il', orbvirt, s, iaos, optimize=True)
    U, sigma, Vh = scipy.linalg.svd(S)
    U = U[:, 0:nvvo]
    vvo = numpy.einsum('ik,ji->jk', U, orbvirt, optimize=True)
    return vvo
Пример #4
0
 def test_orth(self):
     numpy.random.seed(10)
     n = 100
     a = numpy.random.random((n,n))
     s = numpy.dot(a.T, a)
     c = orth.lowdin(s)
     self.assertTrue(numpy.allclose(reduce(numpy.dot, (c.T, s, c)),
                                    numpy.eye(n)))
     x1 = numpy.dot(a, c)
     x2 = orth.vec_lowdin(a)
     d = numpy.dot(x1.T,x2)
     d[numpy.diag_indices(n)] = 0
     self.assertAlmostEqual(numpy.linalg.norm(d), 0, 9)
     self.assertAlmostEqual(numpy.linalg.norm(c), 36.56738258719514, 9)
     self.assertAlmostEqual(abs(c).sum(), 2655.5580057303964, 7)
Пример #5
0
 def test_orth(self):
     numpy.random.seed(10)
     n = 100
     a = numpy.random.random((n, n))
     s = numpy.dot(a.T, a)
     c = orth.lowdin(s)
     self.assertTrue(
         numpy.allclose(reduce(numpy.dot, (c.T, s, c)), numpy.eye(n)))
     x1 = numpy.dot(a, c)
     x2 = orth.vec_lowdin(a)
     d = numpy.dot(x1.T, x2)
     d[numpy.diag_indices(n)] = 0
     self.assertAlmostEqual(numpy.linalg.norm(d), 0, 9)
     self.assertAlmostEqual(numpy.linalg.norm(c), 36.56738258719514, 9)
     self.assertAlmostEqual(abs(c).sum(), 2655.5580057303964, 7)
Пример #6
0
def iao(mol, orbocc, minao=MINAO, kpts=None):
    '''Intrinsic Atomic Orbitals. [Ref. JCTC, 9, 4834]

    Args:
        mol : the molecule or cell object

        orbocc : 2D array
            occupied orbitals

    Returns:
        non-orthogonal IAO orbitals.  Orthogonalize them as C (C^T S C)^{-1/2},
        eg using :func:`orth.lowdin`

        >>> orbocc = mf.mo_coeff[:,mf.mo_occ>0]
        >>> c = iao(mol, orbocc)
        >>> numpy.dot(c, orth.lowdin(reduce(numpy.dot, (c.T,s,c))))
    '''
    if mol.has_ecp():
        logger.warn(
            mol,
            'ECP/PP is used. MINAO is not a good reference AO basis in IAO.')

    pmol = reference_mol(mol, minao)
    # For PBC, we must use the pbc code for evaluating the integrals lest the
    # pbc conditions be ignored.
    # DO NOT import pbcgto early and check whether mol is a cell object.
    # "from pyscf.pbc import gto as pbcgto and isinstance(mol, pbcgto.Cell)"
    # The code should work even pbc module is not availabe.
    if getattr(mol, 'pbc_intor', None):  # cell object has pbc_intor method
        from pyscf.pbc import gto as pbcgto
        s1 = numpy.asarray(mol.pbc_intor('int1e_ovlp', hermi=1, kpts=kpts))
        s2 = numpy.asarray(pmol.pbc_intor('int1e_ovlp', hermi=1, kpts=kpts))
        s12 = numpy.asarray(
            pbcgto.cell.intor_cross('int1e_ovlp', mol, pmol, kpts=kpts))
    else:
        #s1 is the one electron overlap integrals (coulomb integrals)
        s1 = mol.intor_symmetric('int1e_ovlp')
        #s2 is the same as s1 except in minao
        s2 = pmol.intor_symmetric('int1e_ovlp')
        #overlap integrals of the two molecules
        s12 = gto.mole.intor_cross('int1e_ovlp', mol, pmol)

    if len(s1.shape) == 2:
        s21 = s12.conj().T
        s1cd = scipy.linalg.cho_factor(s1)
        s2cd = scipy.linalg.cho_factor(s2)
        p12 = scipy.linalg.cho_solve(s1cd, s12)
        ctild = scipy.linalg.cho_solve(s2cd, numpy.dot(s21, orbocc))
        ctild = scipy.linalg.cho_solve(s1cd, numpy.dot(s12, ctild))
        ctild = vec_lowdin(ctild, s1)
        ccs1 = reduce(numpy.dot, (orbocc, orbocc.conj().T, s1))
        ccs2 = reduce(numpy.dot, (ctild, ctild.conj().T, s1))
        #a is the set of IAOs in the original basis
        a = (p12 + reduce(numpy.dot, (ccs1, ccs2, p12)) * 2 -
             numpy.dot(ccs1, p12) - numpy.dot(ccs2, p12))
    else:  # k point sampling
        s21 = numpy.swapaxes(s12, -1, -2).conj()
        nkpts = len(kpts)
        a = numpy.zeros((nkpts, s1.shape[-1], s2.shape[-1]),
                        dtype=numpy.complex128)
        for k in range(nkpts):
            # ZHC NOTE check the case, at some kpts, there is no occupied MO.
            s1cd_k = scipy.linalg.cho_factor(s1[k])
            s2cd_k = scipy.linalg.cho_factor(s2[k])
            p12_k = scipy.linalg.cho_solve(s1cd_k, s12[k])
            ctild_k = scipy.linalg.cho_solve(s2cd_k,
                                             numpy.dot(s21[k], orbocc[k]))
            ctild_k = scipy.linalg.cho_solve(s1cd_k,
                                             numpy.dot(s12[k], ctild_k))
            ctild_k = vec_lowdin(ctild_k, s1[k])
            ccs1_k = reduce(numpy.dot, (orbocc[k], orbocc[k].conj().T, s1[k]))
            ccs2_k = reduce(numpy.dot, (ctild_k, ctild_k.conj().T, s1[k]))
            #a is the set of IAOs in the original basis
            a[k] = (p12_k + reduce(numpy.dot, (ccs1_k, ccs2_k, p12_k)) * 2 -
                    numpy.dot(ccs1_k, p12_k) - numpy.dot(ccs2_k, p12_k))
    return a
Пример #7
0
def ibo_loc(mol, orbocc, iaos, s, exponent, grad_tol, max_iter,
            minao=MINAO, verbose=logger.NOTE):
    '''Intrinsic Bonding Orbitals. [Ref. JCTC, 9, 4834]

    This implementation follows Knizia's implementation execept that the
    resultant IBOs are symmetrically orthogonalized.  Note the IBOs of this
    implementation do not strictly maximize the IAO Mulliken charges.

    IBOs can also be generated by another implementation (see function
    pyscf.lo.ibo.PM). In that function, PySCF builtin Pipek-Mezey localization
    module was used to maximize the IAO Mulliken charges.

    Args:
        mol : the molecule or cell object

        orbocc : 2D array or a list of 2D array
            occupied molecular orbitals or crystal orbitals for each k-point

    Kwargs:
        iaos : 2D array
            the array of IAOs
        exponent : integer
            Localization power in PM scheme
        grad_tol : float
            convergence tolerance for norm of gradients

    Returns:
        IBOs in the big basis (the basis defined in mol object).
    '''
    log = logger.new_logger(mol, verbose)
    assert(exponent in (2, 4))

    # Symmetrically orthogonalization of the IAO orbitals as Knizia's
    # implementation.  The IAO returned by iao.iao function is not orthogonal.
    iaos = orth.vec_lowdin(iaos, s)

    #static variables
    StartTime = logger.perf_counter()
    L  = 0 # initialize a value of the localization function for safety
    #max_iter = 20000 #for some reason the convergence of solid is slower
    #fGradConv = 1e-10 #this ought to be pumped up to about 1e-8 but for testing purposes it's fine
    swapGradTolerance = 1e-12

    #dynamic variables
    Converged = False

    # render Atoms list without ghost atoms
    iao_mol = iao.reference_mol(mol, minao=minao)
    Atoms = [iao_mol.atom_pure_symbol(i) for i in range(iao_mol.natm)]

    #generates the parameters we need about the atomic structure
    nAtoms = len(Atoms)
    AtomOffsets = MakeAtomIbOffsets(Atoms)[0]
    iAtSl = [slice(AtomOffsets[A],AtomOffsets[A+1]) for A in range(nAtoms)]
    #converts the occupied MOs to the IAO basis
    CIb = reduce(numpy.dot, (iaos.T, s , orbocc))
    numOccOrbitals = CIb.shape[1]

    log.debug("   {0:^5s} {1:^14s} {2:^11s} {3:^8s}"
              .format("ITER.","LOC(Orbital)","GRADIENT", "TIME"))

    for it in range(max_iter):
        fGrad = 0.00

        #calculate L for convergence checking
        L = 0.
        for A in range(nAtoms):
            for i in range(numOccOrbitals):
                CAi = CIb[iAtSl[A],i]
                L += numpy.dot(CAi,CAi)**exponent

        # loop over the occupied orbitals pairs i,j
        for i in range(numOccOrbitals):
            for j in range(i):
                # I eperimented with exponentially falling off random noise
                Aij  = 0.0 #numpy.random.random() * numpy.exp(-1*it)
                Bij  = 0.0 #numpy.random.random() * numpy.exp(-1*it)
                for k in range(nAtoms):
                    CIbA = CIb[iAtSl[k],:]
                    Cii  = numpy.dot(CIbA[:,i], CIbA[:,i])
                    Cij  = numpy.dot(CIbA[:,i], CIbA[:,j])
                    Cjj  = numpy.dot(CIbA[:,j], CIbA[:,j])
                    #now I calculate Aij and Bij for the gradient search
                    if exponent == 2:
                        Aij += 4.*Cij**2 - (Cii - Cjj)**2
                        Bij += 4.*Cij*(Cii - Cjj)
                    else:
                        Bij += 4.*Cij*(Cii**3-Cjj**3)
                        Aij += -Cii**4 - Cjj**4 + 6*(Cii**2 + Cjj**2)*Cij**2 + Cii**3 * Cjj + Cii*Cjj**3

                if (Aij**2 + Bij**2 < swapGradTolerance) and False:
                    continue
                    #this saves us from replacing already fine orbitals
                else:
                    #THE BELOW IS TAKEN DIRECLTY FROMG KNIZIA's FREE CODE
                    # Calculate 2x2 rotation angle phi.
                    # This correspond to [2] (12)-(15), re-arranged and simplified.
                    phi = .25*numpy.arctan2(Bij,-Aij)
                    fGrad += Bij**2
                    # ^- Bij is the actual gradient. Aij is effectively
                    #    the second derivative at phi=0.

                    # 2x2 rotation form; that's what PM suggest. it works
                    # fine, but I don't like the asymmetry.
                    cs = numpy.cos(phi)
                    ss = numpy.sin(phi)
                    Ci = 1. * CIb[:,i]
                    Cj = 1. * CIb[:,j]
                    CIb[:,i] =  cs * Ci + ss * Cj
                    CIb[:,j] = -ss * Ci + cs * Cj
        fGrad = fGrad**.5

        log.debug(" {0:5d} {1:12.8f} {2:11.2e} {3:8.2f}"
                  .format(it+1, L**(1./exponent), fGrad, logger.perf_counter()-StartTime))
        if fGrad < grad_tol:
            Converged = True
            break
    Note = "IB/P%i/2x2, %i iter; Final gradient %.2e" % (exponent, it+1, fGrad)
    if not Converged:
        log.note("\nWARNING: Iterative localization failed to converge!"
                 "\n         %s", Note)
    else:
        log.note(" Iterative localization: %s", Note)
    log.debug(" Localized orbitals deviation from orthogonality: %8.2e",
              numpy.linalg.norm(numpy.dot(CIb.T, CIb) - numpy.eye(numOccOrbitals)))
    # Note CIb is not unitary matrix (although very close to unitary matrix)
    # because the projection <IAO|OccOrb> does not give unitary matrix.
    return numpy.dot(iaos, (orth.vec_lowdin(CIb)))
Пример #8
0
def atomic_pops(mol, mo_coeff, method='meta_lowdin', mf=None):
    '''
    Kwargs:
        method : string
            The atomic population projection scheme. It can be mulliken,
            lowdin, meta_lowdin, iao, or becke

    Returns:
        A 3-index tensor [A,i,j] indicates the population of any orbital-pair
        density |i><j| for each species (atom in this case).  This tensor is
        used to construct the population and gradients etc.

        You can customize the PM localization wrt other population metric,
        such as the charge of a site, the charge of a fragment (a group of
        atoms) by overwriting this tensor.  See also the example
        pyscf/examples/loc_orb/40-hubbard_model_PM_localization.py for the PM
        localization of site-based population for hubbard model.
    '''
    method = method.lower().replace('_', '-')
    nmo = mo_coeff.shape[1]
    proj = numpy.empty((mol.natm,nmo,nmo))

    if getattr(mol, 'pbc_intor', None):  # whether mol object is a cell
        s = mol.pbc_intor('int1e_ovlp_sph', hermi=1)
    else:
        s = mol.intor_symmetric('int1e_ovlp')

    if method == 'becke':
        from pyscf.dft import gen_grid
        if not (getattr(mf, 'grids', None) and getattr(mf, '_numint', None)):
            # Call DFT to initialize grids and numint objects
            mf = mol.RKS()
        grids = mf.grids
        ni = mf._numint

        if not isinstance(grids, gen_grid.Grids):
            raise NotImplementedError('PM becke scheme for PBC systems')

        # The atom-wise Becke grids (without concatenated to a vector of grids)
        coords, weights = grids.get_partition(mol, concat=False)

        for i in range(mol.natm):
            ao = ni.eval_ao(mol, coords[i], deriv=0)
            aow = numpy.einsum('pi,p->pi', ao, weights[i])
            charge_matrix = lib.dot(aow.conj().T, ao)
            proj[i] = reduce(lib.dot, (mo_coeff.conj().T, charge_matrix, mo_coeff))

    elif method == 'mulliken':
        for i, (b0, b1, p0, p1) in enumerate(mol.offset_nr_by_atom()):
            csc = reduce(numpy.dot, (mo_coeff[p0:p1].conj().T, s[p0:p1], mo_coeff))
            proj[i] = (csc + csc.conj().T) * .5

    elif method in ('lowdin', 'meta-lowdin'):
        csc = reduce(lib.dot, (mo_coeff.conj().T, s, orth.orth_ao(mol, method, 'ANO', s=s)))
        for i, (b0, b1, p0, p1) in enumerate(mol.offset_nr_by_atom()):
            proj[i] = numpy.dot(csc[:,p0:p1], csc[:,p0:p1].conj().T)

    elif method in ('iao', 'ibo'):
        from pyscf.lo import iao
        assert mf is not None
        # FIXME: How to handle UHF/UKS object?
        orb_occ = mf.mo_coeff[:,mf.mo_occ>0]

        iao_coeff = iao.iao(mol, orb_occ)
        #
        # IAO is generally not orthogonalized. For simplicity, we take Lowdin
        # orthogonalization here. Other orthogonalization can be used. Results
        # should be very closed to the Lowdin-orth orbitals
        #
        # PM with Mulliken population of non-orth IAOs can be found in
        # ibo.PipekMezey function
        #
        iao_coeff = orth.vec_lowdin(iao_coeff, s)
        csc = reduce(lib.dot, (mo_coeff.conj().T, s, iao_coeff))

        iao_mol = iao.reference_mol(mol)
        for i, (b0, b1, p0, p1) in enumerate(iao_mol.offset_nr_by_atom()):
            proj[i] = numpy.dot(csc[:,p0:p1], csc[:,p0:p1].conj().T)

    else:
        raise KeyError('method = %s' % method)

    return proj
Пример #9
0
mol.symmetry = 1
mol.build()

mf = dft.RKS(mol)
mf.xc = 'HF*0.2 + .08*LDA + .72*B88, .81*LYP + .19*VWN5'
mf.kernel()

orbocc = mf.mo_coeff[:,0:mol.nelec[0]]
orbvirt = mf.mo_coeff[:,mol.nelec[0]:]
mocoeff = mf.mo_coeff

ovlpS = mol.intor_symmetric('int1e_ovlp')

# plot canonical mos
iaos = iao.iao(mol, orbocc)
iaos = orth.vec_lowdin(iaos, ovlpS)
for i in range(iaos.shape[1]):
    tools.cubegen.orbital(mol, 'h2o_cmo_{:02d}.cube'.format(i+1), mocoeff[:,i])

# plot intrinsic atomic orbitals
for i in range(iaos.shape[1]):
    tools.cubegen.orbital(mol, 'h2o_iao_{:02d}.cube'.format(i+1), iaos[:,i])

# plot intrinsic bonding orbitals
count = 0
ibos = lo.ibo.ibo(mol, orbocc, locmethod='IBO')
for i in range(ibos.shape[1]):
    count += 1
    tools.cubegen.orbital(mol, 'h2o_ibo_{:02d}.cube'.format(count), ibos[:,i])

# plot valence virtual orbitals and localized valence virtual orbitals
Пример #10
0
def ibo(mol, orbocc, iaos=None, exponent=4, grad_tol=1e-8, max_iter=200,
        verbose=logger.NOTE):
    '''Intrinsic Bonding Orbitals. [Ref. JCTC, 9, 4834]

    This implementation follows Knizia's implementation execept that the
    resultant IBOs are symmetrically orthogonalized.  Note the IBOs of this
    implementation do not strictly maximize the IAO Mulliken charges.

    IBOs can also be generated by another implementation (see function
    pyscf.lo.ibo.PM). In that function, PySCF builtin Pipek-Mezey localization
    module was used to maximize the IAO Mulliken charges.

    Args:
        mol : the molecule or cell object

        orbocc : 2D array or a list of 2D array
            occupied molecular orbitals or crystal orbitals for each k-point

    Kwargs:
        iaos : 2D array
            the array of IAOs
        exponent : integer
            Localization power in PM scheme
        grad_tol : float
            convergence tolerance for norm of gradients

    Returns:
        IBOs in the big basis (the basis defined in mol object).
    '''
    log = logger.new_logger(mol, verbose)
    assert(exponent in (2, 4))

    if getattr(mol, 'pbc_intor', None):  # whether mol object is a cell
        if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2:
            ovlpS = mol.pbc_intor('int1e_ovlp', hermi=1)
        else:
            raise NotImplementedError('k-points crystal orbitals')
    else:
        ovlpS = mol.intor_symmetric('int1e_ovlp')

    if iaos is None:
        iaos = iao.iao(mol, orbocc)

    # Symmetrically orthogonalization of the IAO orbitals as Knizia's
    # implementation.  The IAO returned by iao.iao function is not orthogonal.
    iaos = orth.vec_lowdin(iaos, ovlpS)

    #static variables
    StartTime = time()
    L  = 0 # initialize a value of the localization function for safety
    #max_iter = 20000 #for some reason the convergence of solid is slower
    #fGradConv = 1e-10 #this ought to be pumped up to about 1e-8 but for testing purposes it's fine
    swapGradTolerance = 1e-12

    #dynamic variables
    Converged = False

    Atoms  = [mol.atom_symbol(i) for i in range(mol.natm)]

    #generates the parameters we need about the atomic structure
    nAtoms = len(Atoms)
    AtomOffsets = MakeAtomIbOffsets(Atoms)[0]
    iAtSl = [slice(AtomOffsets[A],AtomOffsets[A+1]) for A in range(nAtoms)]
    #converts the occupied MOs to the IAO basis
    CIb = reduce(numpy.dot, (iaos.T, ovlpS , orbocc))
    numOccOrbitals = CIb.shape[1]

    log.debug("   {0:^5s} {1:^14s} {2:^11s} {3:^8s}"
              .format("ITER.","LOC(Orbital)","GRADIENT", "TIME"))

    for it in range(max_iter):
        fGrad = 0.00

        #calculate L for convergence checking
        L = 0.
        for A in range(nAtoms):
            for i in range(numOccOrbitals):
                CAi = CIb[iAtSl[A],i]
                L += numpy.dot(CAi,CAi)**exponent

        # loop over the occupied orbitals pairs i,j
        for i in range(numOccOrbitals):
            for j in range(i):
                # I eperimented with exponentially falling off random noise
                Aij  = 0.0 #numpy.random.random() * numpy.exp(-1*it)
                Bij  = 0.0 #numpy.random.random() * numpy.exp(-1*it)
                for k in range(nAtoms):
                    CIbA = CIb[iAtSl[k],:]
                    Cii  = numpy.dot(CIbA[:,i], CIbA[:,i])
                    Cij  = numpy.dot(CIbA[:,i], CIbA[:,j])
                    Cjj  = numpy.dot(CIbA[:,j], CIbA[:,j])
                    #now I calculate Aij and Bij for the gradient search
                    if exponent == 2:
                        Aij += 4.*Cij**2 - (Cii - Cjj)**2
                        Bij += 4.*Cij*(Cii - Cjj)
                    else:
                        Bij += 4.*Cij*(Cii**3-Cjj**3)
                        Aij += -Cii**4 - Cjj**4 + 6*(Cii**2 + Cjj**2)*Cij**2 + Cii**3 * Cjj + Cii*Cjj**3

                if (Aij**2 + Bij**2 < swapGradTolerance) and False:
                    continue
                    #this saves us from replacing already fine orbitals
                else:
                    #THE BELOW IS TAKEN DIRECLTY FROMG KNIZIA's FREE CODE
                    # Calculate 2x2 rotation angle phi.
                    # This correspond to [2] (12)-(15), re-arranged and simplified.
                    phi = .25*numpy.arctan2(Bij,-Aij)
                    fGrad += Bij**2
                    # ^- Bij is the actual gradient. Aij is effectively
                    #    the second derivative at phi=0.

                    # 2x2 rotation form; that's what PM suggest. it works
                    # fine, but I don't like the asymmetry.
                    cs = numpy.cos(phi)
                    ss = numpy.sin(phi)
                    Ci = 1. * CIb[:,i]
                    Cj = 1. * CIb[:,j]
                    CIb[:,i] =  cs * Ci + ss * Cj
                    CIb[:,j] = -ss * Ci + cs * Cj
        fGrad = fGrad**.5

        log.debug(" {0:5d} {1:12.8f} {2:11.2e} {3:8.2f}"
                  .format(it+1, L**(1./exponent), fGrad, time()-StartTime))
        if fGrad < grad_tol:
            Converged = True
            break
    Note = "IB/P%i/2x2, %i iter; Final gradient %.2e" % (exponent, it+1, fGrad)
    if not Converged:
        log.note("\nWARNING: Iterative localization failed to converge!"
                 "\n         %s", Note)
    else:
        log.note(" Iterative localization: %s", Note)
    log.debug(" Localized orbitals deviation from orthogonality: %8.2e",
              numpy.linalg.norm(numpy.dot(CIb.T, CIb) - numpy.eye(numOccOrbitals)))
    # Note CIb is not unitary matrix (although very close to unitary matrix)
    # because the projection <IAO|OccOrb> does not give unitary matrix.
    return numpy.dot(iaos, (orth.vec_lowdin(CIb)))