示例#1
0
    def transform_orbitals(self, coeff_ab_new, new_atom_indices):
        """Return a new instance of OrbitalPartitionTools with the orbitals transformed.

        Parameters
        ----------
        coeff_ab_new : np.ndarray(K, L)
            Transformation matrix from the atomic basis to new basis functions.
            Rows correspond to the atomic basis.
            Columns correspond to the new basis functions.
            The transformation matrix is applied to the right.
            Data type must be float.
            `K` is the number of atomic orbitals and `L` is the number of new basis functions.
        new_atom_indices : np.ndarray(L,)
            Index of the atom to which each of the new basis function belongs.
            Data type must be integers.
            `L` is the number of atomic orbitals.

        Returns
        -------
        new_orbpart : OrbitalPartitionTools
            New instance of OrbitalPartitionTools with the orbitals transformed.


        """
        olp_new_new = coeff_ab_new.T.dot(self.olp_ab_ab).dot(coeff_ab_new)
        olp_new_mo = coeff_ab_new.T.dot(self.olp_ab_ab).dot(self.coeff_ab_mo)
        coeff_new_mo = project(olp_new_new, olp_new_mo)

        return self.__class__(coeff_new_mo, self.occupations, olp_new_new,
                              self.num_atoms, new_atom_indices)
示例#2
0
def test_quambo():
    """Test orbstools.quasi.quambo against literature value for Mulliken populations.

    References
    ----------
    .. [1] Janowski, T. Near equivalence of intrinsic atomic orbitals and quasiatomic orbitals.
        JCTC, 2014, 10, 3085-3091.

    """
    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_olp_aao_ab.npy") as fname:
        olp_aao_ab = np.load(str(fname))
    with path("chemtools.data", "naclo4_occupations.npy") as fname:
        occupations = np.load(str(fname))
    indices_span = occupations > 0
    with path("chemtools.data", "naclo4_qab_atom_indices.npy") as fname:
        ab_atom_indices = np.load(str(fname))

    olp_ab_omo = olp_ab_ab.dot(coeff_ab_mo[:, indices_span])

    coeff_ab_quambo = quambo(olp_ab_ab, olp_aao_ab, coeff_ab_mo, indices_span)
    olp_quambo_quambo = coeff_ab_quambo.T.dot(olp_ab_ab).dot(coeff_ab_quambo)
    olp_quambo_omo = coeff_ab_quambo.T.dot(olp_ab_omo)
    coeff_quambo_omo = project(olp_quambo_quambo, olp_quambo_omo)
    pop = mulliken_populations(coeff_quambo_omo, occupations[indices_span],
                               olp_quambo_quambo, 6, ab_atom_indices)
    partial_pop = np.array([11, 17, 8, 8, 8, 8]) - pop
    assert np.allclose(partial_pop,
                       np.array([0.968, 2.454, -0.807, -0.904, -0.904,
                                 -0.807]),
                       atol=1e-3)
示例#3
0
def test_mulliken_populations_newbasis():
    """Test orbstools.mulliken.mulliken_populations_newabasis."""
    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))

    assert np.allclose(
        mulliken_populations_newbasis(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                      np.identity(124), ab_atom_indices),
        mulliken_populations(coeff_ab_mo, occupations, olp_ab_ab, 6,
                             ab_atom_indices),
    )

    coeff_ab_rand = np.linalg.svd(np.random.rand(124, 124))[0].T
    rand_atom_indices = (np.random.rand(124) * 6 // 1).astype(int)
    # normalize
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_ab_rand *= np.diag(olp_rand_rand)**(-0.5)
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_rand_mo = project(olp_rand_rand,
                            coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_mo))
    assert np.allclose(coeff_ab_rand.dot(coeff_rand_mo), coeff_ab_mo)
    assert np.allclose(
        mulliken_populations_newbasis(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                      coeff_ab_rand, rand_atom_indices),
        mulliken_populations(coeff_rand_mo, occupations, olp_rand_rand, 6,
                             rand_atom_indices),
    )
示例#4
0
def test_transform_orbitals():
    """Test orbtools.partition.OrbitalPartitionTools.transform_orbitals."""
    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_rand = np.linalg.svd(np.random.rand(124, 124))[0].T
    rand_atom_indices = (np.random.rand(124) * 6 // 1).astype(int)
    # normalize
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_ab_rand *= np.diag(olp_rand_rand)**(-0.5)
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_rand_mo = project(olp_rand_rand,
                            coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_mo))

    orbpart = OrbitalPartitionTools(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                    ab_atom_indices)
    neworbpart = orbpart.transform_orbitals(coeff_ab_rand, rand_atom_indices)
    assert np.allclose(coeff_ab_rand.dot(coeff_rand_mo), orbpart.coeff_ab_mo)
    assert np.allclose(coeff_rand_mo, neworbpart.coeff_ab_mo)
    assert np.allclose(olp_rand_rand, neworbpart.olp_ab_ab)
示例#5
0
def test_make_mmo():
    """Test orbstools.quasi.make_mmo."""
    # olp_aao_ab
    olp_aao_ab = np.random.rand(5, 10)
    # coeff_ab_mo
    olp_ab_ab, _, _ = np.linalg.svd(np.random.rand(10, 10))
    olp_ab_ab = (olp_ab_ab * np.random.rand(10)).dot(olp_ab_ab.T)
    norm_ab = np.diag(olp_ab_ab) ** (-0.5)
    olp_ab_ab *= norm_ab[:, None] * norm_ab[None, :]
    coeff_ab_mo = olp_ab_ab.dot(np.random.rand(10, 10))
    coeff_ab_mo *= np.diag(coeff_ab_mo.T.dot(olp_ab_ab).dot(coeff_ab_mo)) ** (-0.5)
    # indices_span
    indices_span = np.array([True] * 5 + [False] * 5)

    assert_raises(TypeError, make_mmo, olp_aao_ab, coeff_ab_mo, indices_span, dim_mmo=8.0)
    assert_raises(ValueError, make_mmo, olp_aao_ab, coeff_ab_mo, indices_span, dim_mmo=11)
    assert_raises(ValueError, make_mmo, olp_aao_ab, coeff_ab_mo, indices_span, dim_mmo=4)

    coeff_ab_mmo = make_mmo(olp_aao_ab, coeff_ab_mo, indices_span, dim_mmo=6)
    # check that occupied mo's are spanned exactly
    coeff_mmo_newmo = project(
        coeff_ab_mmo.T.dot(olp_ab_ab).dot(coeff_ab_mmo),
        coeff_ab_mmo.T.dot(olp_ab_ab).dot(coeff_ab_mo),
    )
    olp_mo_newmo = coeff_ab_mo.T.dot(olp_ab_ab).dot(coeff_ab_mmo).dot(coeff_mmo_newmo)
    olp_mo_mo = coeff_ab_mo.T.dot(olp_ab_ab).dot(coeff_ab_mo)
    assert np.allclose(np.diag(olp_mo_newmo)[:5], 1)
    assert np.allclose(olp_mo_newmo[:5, :5], olp_mo_mo[:5, :5])
    # check that the virtual mo's are not spanned exactly
    assert not np.allclose(np.diag(olp_mo_newmo)[5:], 1)
    assert not np.allclose(olp_mo_newmo, olp_mo_mo)
示例#6
0
def test_project():
    """Test the orbstools.quasi.project."""
    olp_1 = np.identity(10)
    # (trivial) projecting onto same space
    olp_1_2 = np.identity(10)
    assert np.allclose(project(olp_1, olp_1_2), np.identity(10))
    # (trivial) projecting onto subspace
    olp_1_2 = np.eye(10, 6)
    assert np.allclose(project(olp_1, olp_1_2), np.eye(10, 6))
    # (trivial) projecting onto same space + orthogonal complement
    olp_1_2 = np.eye(10, 20)
    assert np.allclose(project(olp_1, olp_1_2), np.eye(10, 10))
    # projecting onto non normalized functions
    olp_1_2 = np.identity(10) * 2
    assert np.allclose(project(olp_1, olp_1_2), np.identity(10))
    # projecting onto linearly dependent basis functions
    olp_1 = np.identity(20)
    olp_1[:10, 10:] = np.identity(10)
    olp_1[10:, :10] = np.identity(10)
    olp_1_2 = np.vstack([np.identity(10)] * 2)
    coeff_1_proj = project(olp_1, olp_1_2)
    assert np.allclose(olp_1.dot(coeff_1_proj), olp_1_2)
    # projecting linearly dependent basis functions
    olp_1 = np.identity(10)
    olp_1_2 = np.hstack([np.identity(10)] * 2)
    assert np.allclose(project(olp_1, olp_1_2),
                       np.hstack([np.identity(10)] * 2))
    # errors
    assert_raises(TypeError, project, olp_1.tolist(), olp_1_2)
    assert_raises(TypeError, project, olp_1.reshape(10, 10, 1), olp_1_2)
    assert_raises(TypeError, project, olp_1.reshape(4, 25), olp_1_2)
    assert_raises(TypeError, project, olp_1, olp_1_2.tolist())
    assert_raises(TypeError, project, olp_1, olp_1_2.reshape(10, 20, 1))
    assert_raises(ValueError, project, olp_1, olp_1_2.T)
示例#7
0
def test_mulliken_populations_newbasis():
    """Test orbstools.partition.OrbitalPartitionTools.mulliken_populations with a new basis."""
    # pylint: disable=C0103
    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))

    orbpart = OrbitalPartitionTools(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                    ab_atom_indices)
    assert np.allclose(
        orbpart.transform_orbitals(np.identity(124),
                                   ab_atom_indices).mulliken_populations(),
        orbpart.mulliken_populations(),
    )

    coeff_ab_rand = np.linalg.svd(np.random.rand(124, 124))[0].T
    rand_atom_indices = (np.random.rand(124) * 6 // 1).astype(int)
    # normalize
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_ab_rand *= np.diag(olp_rand_rand)**(-0.5)
    olp_rand_rand = coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_rand)
    coeff_rand_mo = project(olp_rand_rand,
                            coeff_ab_rand.T.dot(olp_ab_ab).dot(coeff_ab_mo))

    orbpart = OrbitalPartitionTools(coeff_ab_mo, occupations, olp_ab_ab, 6,
                                    rand_atom_indices)
    assert np.allclose(coeff_ab_rand.dot(coeff_rand_mo), coeff_ab_mo)
    assert np.allclose(
        orbpart.transform_orbitals(coeff_ab_rand,
                                   rand_atom_indices).mulliken_populations(),
        OrbitalPartitionTools(coeff_rand_mo, occupations, olp_rand_rand, 6,
                              rand_atom_indices).mulliken_populations(),
    )
示例#8
0
def mulliken_populations_newbasis(
    coeff_ab_mo,
    occupations,
    olp_ab_ab,
    num_atoms,
    coeff_ab_new,
    new_atom_indices,
    new_atom_weights=None,
):
    r"""Return the Mulliken populations of the given system in a new basis set.

    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.
    coeff_ab_new : np.ndarray(K, L)
        Transformation matrix from the atomic basis to new basis functions.
        Rows correspond to the atomic basis.
        Columns correspond to the new basis functions.
        The transformation matrix is applied to the right.
        Data type must be float.
        `K` is the number of atomic orbitals and `L` is the number of new basis functions.
    new_atom_indices : np.ndarray(L,)
        Index of the atom to which each of the new basis function belongs.
        Data type must be integers.
        `L` is the number of atomic orbitals.
    new_atom_weights : np.ndarray(A, L, L)
        Weights of the pair of new basis functions for the atoms. In other words, this weight
        controls the amount of electrons associated with an new basis function pair that will be
        attributed to an atom.
        `A` is the number of atoms and `K` is the number of new basis functions.
        Default is the Mulliken partitioning scheme where two basis functions that belong to the
        given atom is 1, only one basis function that belong to the given atoms is 0.5, and no basis
        functions 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`.

    See Also
    --------
    orbstools.mulliken.mulliken_populations

    """
    olp_new_new = coeff_ab_new.T.dot(olp_ab_ab).dot(coeff_ab_new)
    olp_new_mo = coeff_ab_new.T.dot(olp_ab_ab).dot(coeff_ab_mo)
    coeff_new_mo = project(olp_new_new, olp_new_mo)
    return mulliken_populations(
        coeff_new_mo,
        occupations,
        olp_new_new,
        num_atoms,
        new_atom_indices,
        atom_weights=new_atom_weights,
    )