Exemple #1
0
    def test_a(self):
        """Test whether SimPhony gives the same result *at* grid points.

        This test should always pass, since the BZGridQcomplex object is filled
        by calculating eigen-energies and eigen-vectors using SimPhony.
        Unfortunately, due to rounding errors, this test can fail to run if an
        out-of-bounds interpolation point is required by brille.

        The moveinto keyword is what makes it possible to verify the
        "interpolation" result against the SimPhony result. This same keyword
        also opens up the possibility of rounding-errors leading to a runtime
        error of brille.
        With the moveinto keyword omitted (or set to True), out-of-zone grid
        points *will* likely have different polarization vectors compared to
        the SimPhony result as one interpolates at q=Q-τ and the other
        calculates at Q. It's unclear if this difference is important for
        intensity calculations.
        """
        i_data = load_interpolation_data('nb')
        symsim = BrEu(i_data, halfN=(2, 2, 2))

        q_rlu = symsim.grid.rlu
        int_freq, int_vecs = symsim.frqs_vecs(q_rlu,
                                              interpolate=True,
                                              moveinto=False)
        sim_freq, sim_vecs = symsim.frqs_vecs(q_rlu, interpolate=False)

        ad_freq = np.abs(int_freq - sim_freq).magnitude
        as_freq = np.abs(int_freq + sim_freq).magnitude
        # Check if |interpolated-simulated|/|interpolated+simulated|>1e-14
        # AND if |interpolated-simulated|>1e-14
        unequal_freq = (ad_freq > 1e-14 * as_freq) * (ad_freq > 1e-14)
        self.assertFalse(unequal_freq.any())

        # The vectors only need to be equal up to an arbitrary phase
        # which is equivalent to saying that the inner product between
        # equal ion eigenvectors for each branch should have ||ϵ⋅ϵ||²≡1
        n_pt, n_br, n_io, n_d = int_vecs.shape
        int_vecs = int_vecs.reshape(n_pt * n_br * n_io, n_d)
        sim_vecs = sim_vecs.reshape(n_pt * n_br * n_io, n_d)
        product = [hermitian_product(x, y) for x, y in zip(int_vecs, sim_vecs)]

        self.assertTrue(np.isclose(np.abs(product), 1).all())
    def test_NaCl(self):
        # fetch and load the NaCl.castep_bin file from the brille repository
        idata = get_InterpolationData_object('NaCl')
        # Do not sort the modes on neighbouring trellis vertices to make comparison with Euphonic easier
        breu = BrEu(idata, sort=False, trellis=True, max_volume=0.1, parallel=False)
        # pick a trellis Q vertex inside the irreducible polyhedron, away from the boundaries:
        q_ir = breu.grid.rlu[6:7] # shape = (1,3)
        # pull together the pointgroup operations
        ptgr = b.PointSymmetry(breu.grid.BrillouinZone.lattice.hall)
        # and apply each to q_ir to find q_nu = R^T_nu q_ir
        q_nu = np.einsum('xji,aj->xi', ptgr.W, q_ir)

        # use brille to find eigenenergies and eigenvectors at each q_nu
        # this *should* be only an application of the rotation, so
        #   all omega_nu are expected to be identical
        #   and all epsilon_nu will be permuted as Gamma(q|nu) dictates
        br_omega_nu, br_epsilon_nu = breu.frqs_vecs(q_nu, interpolate=True)
        # use Euphonic to diagonalise the dynamical matrix at each q_nu
        eu_omega_nu, eu_epsilon_nu = breu.frqs_vecs(q_nu, interpolate=False)

        # verify that all brille eigenvalues are identical for each q_nu
        self.assertTrue(np.allclose(np.diff(br_omega_nu.magnitude, axis=0), 0.))
        # and that these results match the Euphonic results
        self.assertTrue(np.allclose(br_omega_nu.magnitude, eu_omega_nu.magnitude))
        # Now that we're sure the permutation is the same, we can verify the eigenvectors
        # brille stores and returns eigenvectors expressed in units of the conventional direct lattice
        # while euphonic calculates, returns, and stores them in a cartesian coordinate system defined
        # relative to the lattice by its lat_vec matrix.
        # A brille.spglib method handles conversion between these descriptions:
        br_epsilon_nu_xyz = breu.brspgl.conventional_to_orthogonal_eigenvectors(br_epsilon_nu)

        # The eigenvectors returned by brille and those from Euphonic should be the same up to an overall complex phase factor
        # the eigenvectors are normalised, so the phase is calculated directly
        antiphase_per_q_per_mode = np.exp(-1J*np.angle(np.einsum('qmij,qmij->qm', np.conj(eu_epsilon_nu), br_epsilon_nu_xyz)))
        # and removed from the cartesian coordinate eigenvectors
        br_epsilon_nu_xyz_phased = np.array([[x0*y0 for x0,y0 in zip(x,y)] for x,y in zip(antiphase_per_q_per_mode, br_epsilon_nu_xyz)])
        # now all eigenvectors returned from brille should be the same as
        # calculated by Euphonic directly
        self.assertTrue(np.allclose(br_epsilon_nu_xyz_phased, eu_epsilon_nu))