Esempio n. 1
0
    def lowdin_populations(self, atom_weights=None):
        r"""Return the Lowdin populations of the molecular orbitals in atomic orbital basis set.

        Lowdin population analysis is simply the Mulliken population analysis where the basis
        functions are symmeterically orthogonalized.

        Parameters
        ----------
        atom_weights : np.ndarray(A, K, K)
            Weights of the atomic orbital pairs for the atoms. In other words, this weight controls
            the amount of electrons associated with an atomic orbital pair that will be attributed
            to an atom.
            `A` is the number of atoms and `K` is the number of atomic orbitals.
            Default is the Mulliken partitioning scheme where two orbitals that belong to the given
            atom is 1, only one orbital that belong to the given atoms is 0.5, and no orbitals is 0.

        Returns
        -------
        population : np.ndarray(M,)
            Number of electrons associated with each atom.
            `M` is the number of atoms, which will be assumed to be the maximum index in
            `ab_atom_indices`.

        """
        coeff_ab_oab = power_symmetric(self.olp_ab_ab, -0.5)
        new_orbpart = self.transform_orbitals(coeff_ab_oab,
                                              self.ab_atom_indices)
        return new_orbpart.mulliken_populations(atom_weights=atom_weights)
Esempio n. 2
0
def project(olp_one_one, olp_one_two):
    r"""Project one basis set onto another basis set.

    .. math::

        \mathrm{proj}_A \ket{b_i} &=  \sum_{kl} \ket{a_k} (S_{A}^{-1})_{kl} \braket{a_l | b_i}\\
        &= \sum_{kl} \ket{a_k} (S_{A}^{-1})_{kl} (S_{A,B})_{li}\\
        &= \sum_k \ket{a_k} C_{ki}

    where :math:`(S_A)_{kl} = \braket{a_k | a_l}`, :math:`(S_{A,B})_{li} = \braket{a_l | b_i}`, and
    :math:`C_{ki} = \sum_l (S_{A}^{-1})_{kl} (S_{A,B})_{li}`.

    Parameters
    ----------
    olp_one_one : np.ndarray(N, N)
        Overlap of the basis functions in set 1 with basis functions from set 1.
    olp_one_two : np.ndarray(N, M)
        Overlap of the basis functions in set 1 with basis functions from set 2.

    Returns
    -------
    coeff : np.ndarray(N, M)
        Transformation matrix from basis functions in set 1 to the projection of baiss set 2 onto
        basis set 1.

    """
    if not (isinstance(olp_one_one, np.ndarray) and olp_one_one.ndim == 2
            and olp_one_one.shape[0] == olp_one_one.shape[1]):
        raise TypeError(
            "`olp_one_one` must be a two-dimensional square numpy array.")
    if not (isinstance(olp_one_two, np.ndarray) and olp_one_two.ndim == 2):
        raise TypeError("`olp_one_two` must be a two-dimensional numpy array.")
    if olp_one_one.shape[0] != olp_one_two.shape[0]:
        raise ValueError(
            "Number of rows/columns of `olp_one_one` must be equal to the number of rows in "
            "`olp_one_two`.")
    olp_one_one_inv = orth.power_symmetric(olp_one_one, -1)
    coeff_one_proj = olp_one_one_inv.dot(olp_one_two)
    # Remove zero columns
    coeff_one_proj = coeff_one_proj[:, np.any(coeff_one_proj, axis=0)]
    # Normalize
    olp_proj_proj = coeff_one_proj.T.dot(olp_one_one).dot(coeff_one_proj)
    normalizer = np.diag(olp_proj_proj)**(-0.5)
    coeff_one_proj *= normalizer
    # Check linear dependence
    rank = np.linalg.matrix_rank(coeff_one_proj)
    if rank < coeff_one_proj.shape[1]:
        print((
            "Warning: There are {0} linearly dependent projections. The transformation matrix has a"
            " shape of {1} and rank of {2}".format(
                coeff_one_proj.shape[1] - rank, coeff_one_proj.shape, rank)))
    return coeff_one_proj
Esempio n. 3
0
def test_lowdin_populations():
    """Test orbstools.mulliken.lowdin_populations."""
    with path("chemtools.data", "naclo4_coeff_ab_mo.npy") as fname:
        coeff_ab_mo = np.load(str(fname))
    with path("chemtools.data", "naclo4_olp_ab_ab.npy") as fname:
        olp_ab_ab = np.load(str(fname))
    with path("chemtools.data", "naclo4_occupations.npy") as fname:
        occupations = np.load(str(fname))
    with path("chemtools.data", "naclo4_ab_atom_indices.npy") as fname:
        ab_atom_indices = np.load(str(fname))

    coeff_ab_oab = power_symmetric(olp_ab_ab, -0.5)
    assert np.allclose(
        coeff_ab_oab.T.dot(olp_ab_ab).dot(coeff_ab_oab), np.identity(124))
    assert np.allclose(
        mulliken_populations_newbasis(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                      coeff_ab_oab, ab_atom_indices),
        lowdin_populations(coeff_ab_mo, occupations, olp_ab_ab, 6,
                           ab_atom_indices),
    )
Esempio n. 4
0
def test_lowdin_populations():
    """Test orbstools.partition.OrbitalPartitionTools.lowdin_populations."""
    with path("chemtools.data", "naclo4_coeff_ab_mo.npy") as fname:
        coeff_ab_mo = np.load(str(fname))
    with path("chemtools.data", "naclo4_olp_ab_ab.npy") as fname:
        olp_ab_ab = np.load(str(fname))
    with path("chemtools.data", "naclo4_occupations.npy") as fname:
        occupations = np.load(str(fname))
    with path("chemtools.data", "naclo4_ab_atom_indices.npy") as fname:
        ab_atom_indices = np.load(str(fname))

    coeff_ab_oab = power_symmetric(olp_ab_ab, -0.5)
    orbpart = OrbitalPartitionTools(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                    ab_atom_indices)
    assert np.allclose(
        coeff_ab_oab.T.dot(olp_ab_ab).dot(coeff_ab_oab), np.identity(124))
    assert np.allclose(
        orbpart.transform_orbitals(coeff_ab_oab,
                                   ab_atom_indices).mulliken_populations(),
        orbpart.lowdin_populations(),
    )
Esempio n. 5
0
def test_power_symmetric():
    """Test orbstools.orthogonalization.power_symmetric."""
    matrix = np.random.rand(5, 5)
    # positive semidefinite
    matrix = matrix.dot(matrix.T)
    assert np.allclose(orth.power_symmetric(matrix, 2), matrix.dot(matrix))
    assert np.allclose(orth.power_symmetric(matrix, -1).dot(matrix), np.identity(5))
    power_matrix = orth.power_symmetric(matrix, 0.5)
    assert np.allclose(power_matrix.dot(power_matrix), matrix)
    power_matrix = orth.power_symmetric(matrix, 1.0 / 3.0)
    assert np.allclose(power_matrix.dot(power_matrix).dot(power_matrix), matrix)
    # not positive semidifinite (probably)
    matrix = matrix + matrix.T
    assert np.allclose(orth.power_symmetric(matrix, 2), matrix.dot(matrix))
    assert np.allclose(orth.power_symmetric(matrix, -1).dot(matrix), np.identity(5))
    # following crashes because it has negative eigenvalues
    matrix = np.random.rand(100, 100)
    matrix = matrix + matrix.T
    assert_raises(ValueError, orth.power_symmetric, matrix, 0.5)
Esempio n. 6
0
def quao(olp_ab_ab,
         olp_aao_ab,
         olp_aao_aao,
         coeff_ab_mo,
         indices_span,
         dim=None):
    r"""Return transformation matrix from atomic basis functions to QUAO's.

    Parameters
    ----------
    olp_ab_ab : np.ndarray(K, K)
        Overlaps of the atomic basis functions.
        :math:`K` is the number of atomic basis functions.
    olp_ab_aao : np.ndarray(K, L)
        Overlaps of the atomic basis functions with the reference basis functions (aao).
        Rows correspond to the atomic basis functions. :math:`K` is the number of atomic basis
        functions.
        Columns correspond to the reference basis functions. :math:`L` is the number of reference
        basis functions.
    olp_aao_aao : np.ndarray(L, L)
        Overlaps of the atomic basis functions with the reference basis functions (aao).
        Rows correspond to the atomic basis functions. :math:`K` is the number of atomic basis
        functions.
        Columns correspond to the reference basis functions. :math:`L` is the number of reference
        basis functions.
    coeff_ab_mo : np.ndarray(K, M)
        Transformation matrix from the atomic basis functions to molecular orbitals. The matrix is
        applied onto the right side.
        Rows correspond to the atomic basis functions. :math:`K` is the number of atomic basis
        functions.
        Columns correspond to the molecular basis functions. :math:`L` is the number of molecular
        basis functions.
    indices_span : np.ndarray(M)
        Molecular orbitals that will be spanned exactly by the QUAMBO's.
        Each entry is a boolean, where molecular orbitals that are exactly described have value
        `True`.
    dim : {int, None}
        Number of QUAMBO basis functions.
        Default is the number of reference basis functions.

    Returns
    -------
    coeff_ab_quao
        Transformation matrix from atomic basis functions to QUAO's

    Notes
    -----
    .. math::

        \ket{\mathrm{QUAO}_k} &= \mathrm{proj}_{\mathrm{mMO}^\mathrm{QUAO}}\\
        &= \sum_i \ket{ \mathrm{mMO}^\mathrm{QUAO}_i }
            (olp_{{\mathrm{mMO}^\mathrm{QUAO}}}^{-1})_{ij}
            \braket{ \mathrm{mMO}^\mathrm{QUAO}_j | \mathrm{AAO}_k }\\
        &= \sum_j \ket{AB_j} (C^{\mathrm{AB},\mathrm{mMO}^\mathrm{QUAO}}
            olp_{{\mathrm{mMO}^\mathrm{QUAO}}}^{-1}
            olp_{\mathrm{mMO}^\mathrm{QUAO}, \mathrm{AAO}})_{ji}\\

    References
    ----------
    .. [1] West, A.C.; Schmidt, M.W. Gordon, M.S; Ruedenberg, K. A comprehensive analysis of
        molecule-intrinsic quasiatomic, bonding, and correlating orbitals. I. Hartree-Fock wave
        functions. J. Chem. Phys. 2013, 139, 234107.

    """
    _check_input(
        olp_ab_ab=olp_ab_ab,
        olp_aao_ab=olp_aao_ab,
        olp_aao_aao=olp_aao_aao,
        coeff_ab_mo=coeff_ab_mo,
        indices_span=indices_span,
    )
    # Orthogonalize AAOs
    olp_oaao_ab = orth.power_symmetric(olp_aao_aao, -0.5).dot(olp_aao_ab)

    # Get MMOs using the orthogonalized AAOs (MMO for QUAOs)
    coeff_ab_mmo = make_mmo(olp_oaao_ab,
                            coeff_ab_mo,
                            indices_span,
                            dim_mmo=dim)

    # Find transformation for QUAOs
    olp_mmo_mmo = coeff_ab_mmo.T.dot(olp_ab_ab).dot(coeff_ab_mmo)
    olp_mmo_aao = (olp_aao_ab.dot(coeff_ab_mmo)).T
    coeff_mmo_proj = project(olp_mmo_mmo, olp_mmo_aao)

    # Normalize
    olp_proj_proj = coeff_mmo_proj.T.dot(olp_mmo_mmo).dot(coeff_mmo_proj)
    coeff_mmo_proj *= np.diag(olp_proj_proj)**(-0.5)

    return coeff_ab_mmo.dot(coeff_mmo_proj)
Esempio n. 7
0
def lowdin_populations(coeff_ab_mo,
                       occupations,
                       olp_ab_ab,
                       num_atoms,
                       ab_atom_indices,
                       atom_weights=None):
    r"""Return the Lowdin populations of the given molecular orbitals in atomic orbital basis set.

    Lowdin population analysis is simply the Mulliken population analysis where the basis functions
    are symmeterically orthogonalized.

    Parameters
    ----------
    coeff_ab_mo : np.ndarray(K, M)
        Transformation matrix from the atomic basis to molecular orbitals.
        Rows correspond to the atomic basis.
        Columns correspond to the molecular orbitals.
        The transformation matrix is applied to the right:
        .. math::

            \ket{\psi_i} = \sum_j \phi_i C_{ij}

        Data type must be float.
        `K` is the number of atomic orbitals and `M` is the number of molecular orbitals.
    occupations : np.ndarray(M,)
        Occupation numbers of each molecular orbital.
        Data type must be integers or floats.
        `M` is the number of molecular orbitals.
    olp_ab_ab : np.ndarray(K, K)
        Overlap between atomic basis functions.
        Data type must be floats.
        `K` is the number of atomic orbitals.
    num_atoms : int
        Number of atoms.
        Must be an integer.
    ab_atom_indices : np.ndarray(K,)
        Index of the atom to which each atomic basis function belongs.
        Data type must be integers.
        `K` is the number of atomic orbitals.
    atom_weights : np.ndarray(A, K, K)
        Weights of the atomic orbital pairs for the atoms. In other words, this weight controls the
        amount of electrons associated with an atomic orbital pair that will be attributed to an
        atom.
        `A` is the number of atoms and `K` is the number of atomic orbitals.
        Default is the Mulliken partitioning scheme where two orbitals that belong to the given atom
        is 1, only one orbital that belong to the given atoms is 0.5, and no orbitals is 0.

    Returns
    -------
    population : np.ndarray(M,)
        Number of electrons associated with each atom.
        `M` is the number of atoms, which will be assumed to be the maximum index in
        `ab_atom_indices`.

    """
    coeff_ab_oab = power_symmetric(olp_ab_ab, -0.5)
    return mulliken_populations_newbasis(
        coeff_ab_mo,
        occupations,
        olp_ab_ab,
        num_atoms,
        coeff_ab_oab,
        ab_atom_indices,
        new_atom_weights=atom_weights,
    )