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)
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)
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), )
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)
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)
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)
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(), )
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, )