def cholesky_mos(mo_coeff): ''' Calculates localized orbitals through a pivoted Cholesky factorization of the density matrix. Args: mo_coeff: block of MO coefficients to be localized Returns: the localized MOs ''' assert(mo_coeff.ndim == 2) nao, nmo = mo_coeff.shape # Factorization of a density matrix-like quantity. D = np.dot(mo_coeff, mo_coeff.T) L, piv, rank = pivoted_cholesky(D, lower=True) if rank < nmo: raise RuntimeError('rank of matrix lower than the number of orbitals') # Permute L back to the original order of the AOs. # Superfluous columns are cropped out. P = np.zeros((nao, nao)) P[piv, np.arange(nao)] = 1 mo_loc = np.dot(P, L[:, :nmo]) return mo_loc
def partial_cholesky_orth_(S, canthr=1e-7, cholthr=1e-9): '''Partial Cholesky orthogonalization for curing overcompleteness. References: Susi Lehtola, Curing basis set overcompleteness with pivoted Cholesky decompositions, J. Chem. Phys. 151, 241102 (2019), doi:10.1063/1.5139948. Susi Lehtola, Accurate reproduction of strongly repulsive interatomic potentials, Phys. Rev. A 101, 032504 (2020), doi:10.1103/PhysRevA.101.032504. ''' # Ensure the basis functions are normalized normlz = numpy.power(numpy.diag(S), -0.5) Snorm = numpy.dot(numpy.diag(normlz), numpy.dot(S, numpy.diag(normlz))) # Sort the basis functions according to the Gershgorin circle # theorem so that the Cholesky routine is well-initialized odS = numpy.abs(Snorm) numpy.fill_diagonal(odS, 0.0) odSs = numpy.sum(odS, axis=0) sortidx = numpy.argsort(odSs) # Run the pivoted Cholesky decomposition Ssort = Snorm[numpy.ix_(sortidx, sortidx)].copy() c, piv, r_c = pivoted_cholesky(Ssort, tol=cholthr) # The functions we're going to use are given by the pivot as idx = sortidx[piv[:r_c]] # Get the (un-normalized) sub-basis Ssub = S[numpy.ix_(idx, idx)].copy() # Orthogonalize sub-basis Xsub = canonical_orth_(Ssub, thr=canthr) # Full X X = numpy.zeros((S.shape[0], Xsub.shape[1])) X[idx, :] = Xsub return X