コード例 #1
0
ファイル: phonon.py プロジェクト: sofiasanz/sisl
def _velocity(mode, hw, dDk, degenerate, project):
    r""" For modes in an orthogonal basis """
    # Decouple the degenerate modes
    if not degenerate is None:
        for deg in degenerate:
            # Set the average frequency
            hw[deg] = np.average(hw[deg])

            # Now diagonalize to find the contributions from individual modes
            # then re-construct the seperated degenerate modes
            # Since we do this for all directions we should decouple them all
            mode[deg] = _decouple_eigh(mode[deg], *dDk)

    cm = conj(mode)
    if project:
        v = np.empty([mode.shape[0], mode.shape[1], 3], dtype=dtype_complex_to_real(mode.dtype))
        v[:, :, 0] = (cm * dDk[0].dot(mode.T).T).real
        v[:, :, 1] = (cm * dDk[1].dot(mode.T).T).real
        v[:, :, 2] = (cm * dDk[2].dot(mode.T).T).real

    else:
        v = np.empty([mode.shape[0], 3], dtype=dtype_complex_to_real(mode.dtype))
        v[:, 0] = einsum('ij,ji->i', cm, dDk[0].dot(mode.T)).real
        v[:, 1] = einsum('ij,ji->i', cm, dDk[1].dot(mode.T)).real
        v[:, 2] = einsum('ij,ji->i', cm, dDk[2].dot(mode.T)).real

    # Set everything to zero for the negative frequencies
    v[hw < 0, ...] = 0

    if project:
        return v * _velocity_const / (2 * hw.reshape(-1, 1, 1))
    return v * _velocity_const / (2 * hw.reshape(-1, 1))
コード例 #2
0
ファイル: phonon.py プロジェクト: fyalcin/sisl
def _velocity(mode, hw, dDk, degenerate):
    r""" For modes in an orthogonal basis """

    # Along all directions
    v = np.empty([mode.shape[0], 3], dtype=dtype_complex_to_real(mode.dtype))

    # Decouple the degenerate modes
    if not degenerate is None:
        for deg in degenerate:
            # Set the average frequency
            hw[deg] = np.average(hw[deg])

            # Now diagonalize to find the contributions from individual modes
            # then re-construct the seperated degenerate modes
            # Since we do this for all directions we should decouple them all
            vv = conj(mode[deg, :]).dot(dDk[0].dot(mode[deg, :].T))
            S = eigh_destroy(vv)[1].T.dot(mode[deg, :])
            vv = conj(S).dot((dDk[1]).dot(S.T))
            S = eigh_destroy(vv)[1].T.dot(S)
            vv = conj(S).dot((dDk[2]).dot(S.T))
            mode[deg, :] = eigh_destroy(vv)[1].T.dot(S)

    v[:, 0] = (conj(mode.T) * dDk[0].dot(mode.T)).sum(0).real
    v[:, 1] = (conj(mode.T) * dDk[1].dot(mode.T)).sum(0).real
    v[:, 2] = (conj(mode.T) * dDk[2].dot(mode.T)).sum(0).real

    # Set everything to zero for the negative frequencies
    v[hw < 0, :] = 0

    return v * _velocity_const / (2 * hw.reshape(-1, 1))
    def norm2(self, sum=True):
        r""" Return a vector with the norm of each state :math:`\langle\psi|\psi\rangle`

        Parameters
        ----------
        sum : bool, optional
           if true the summed site square is returned (a vector). For false a matrix
           with normalization squared per site is returned.

        Returns
        -------
        numpy.ndarray
            the normalization for each state
        """
        if not sum:
            return (conj(self.state) * self.state.T).real

        dtype = dtype_complex_to_real(self.dtype)

        N = len(self)
        n = np.empty(N, dtype=dtype)

        for i in range(N):
            n[i] = _idot(self.state[i, :]).real

        return n
コード例 #4
0
def test_dtype_complex_to_real():
    for d in [np.int32, np.int64, np.float32, np.float64]:
        assert dtype_complex_to_real(d) == d
    assert dtype_complex_to_real(np.complex64) == np.float32
    assert dtype_complex_to_real(np.complex128) == np.float64
def PDOS(E, eig, eig_v, S=None, distribution='gaussian', spin=None):
    r""" Calculate the projected density of states (PDOS) for a set of energies, `E`, with a distribution function

    The :math:`\mathrm{PDOS}(E)` is calculated as:

    .. math::
       \mathrm{PDOS}_\nu(E) = \sum_i \psi^*_{i,\nu} [\mathbf S | \psi_{i}\rangle]_\nu D(E-\epsilon_i)

    where :math:`D(\Delta E)` is the distribution function used. Note that the distribution function
    used may be a user-defined function. Alternatively a distribution function may
    be aquired from `sisl.physics.distribution`.

    In case of an orthogonal basis set :math:`\mathbf S` is equal to the identity matrix.
    Note that `DOS` is the sum of the orbital projected DOS:

    .. math::
       \mathrm{DOS}(E) = \sum_\nu\mathrm{PDOS}_\nu(E)

    For non-collinear calculations (this includes spin-orbit calculations) the PDOS is additionally
    separated into 4 components (in this order):

    - Total projected DOS
    - Projected spin magnetic moment along :math:`x` direction
    - Projected spin magnetic moment along :math:`y` direction
    - Projected spin magnetic moment along :math:`z` direction

    These are calculated using the Pauli matrices :math:`\boldsymbol\sigma_x`, :math:`\boldsymbol\sigma_y` and :math:`\boldsymbol\sigma_z`:

    .. math::

       \mathrm{PDOS}_\nu^\Sigma(E) &= \sum_i \psi^*_{i,\nu} \boldsymbol\sigma_z \boldsymbol\sigma_z [\mathbf S | \psi_{i}\rangle]_\nu D(E-\epsilon_i)
       \\
       \mathrm{PDOS}_\nu^x(E) &= \sum_i \psi^*_{i,\nu} \boldsymbol\sigma_x [\mathbf S | \psi_{i}\rangle]_\nu D(E-\epsilon_i)
       \\
       \mathrm{PDOS}_\nu^y(E) &= \sum_i \psi^*_{i,\nu} \boldsymbol\sigma_y [\mathbf S | \psi_{i}\rangle]_\nu D(E-\epsilon_i)
       \\
       \mathrm{PDOS}_\nu^z(E) &= \sum_i \psi^*_{i,\nu} \boldsymbol\sigma_z [\mathbf S | \psi_{i}\rangle]_\nu D(E-\epsilon_i)

    Note that the total PDOS may be calculated using :math:`\boldsymbol\sigma_i\boldsymbol\sigma_i` where :math:`i` may be either of :math:`x`,
    :math:`y` or :math:`z`.

    Parameters
    ----------
    E : array_like
       energies to calculate the projected-DOS from
    eig : array_like
       eigenvalues
    eig_v : array_like
       eigenvectors
    S : array_like, optional
       overlap matrix used in the :math:`\langle\psi|\mathbf S|\psi\rangle` calculation. If `None` the identity
       matrix is assumed. For non-collinear calculations this matrix may be halve the size of ``len(eig_v[0, :])`` to
       trigger the non-collinear calculation of PDOS.
    distribution : func or str, optional
       a function that accepts :math:`E-\epsilon` as argument and calculates the
       distribution function.
    spin : str or Spin, optional
       the spin configuration. This is generally only needed when the eigenvectors correspond to a non-collinear
       calculation.

    See Also
    --------
    sisl.physics.distribution : a selected set of implemented distribution functions
    DOS : total DOS (same as summing over orbitals)
    spin_moment: spin moment for states

    Returns
    -------
    numpy.ndarray
        projected DOS calculated at energies, has dimension ``(eig_v.shape[1], len(E))``.
        For non-collinear calculations it will be ``(4, eig_v.shape[1] // 2, len(E))``, ordered as
        indicated in the above list.
    """
    if isinstance(distribution, str):
        distribution = get_distribution(distribution)

    # Figure out whether we are dealing with a non-collinear calculation
    if S is None:

        class S(object):
            __slots__ = []
            shape = (eig_v.shape[1], eig_v.shape[1])

            @staticmethod
            def dot(v):
                return v

    if spin is None:
        if S.shape[1] == eig_v.shape[1] // 2:
            spin = Spin('nc')
            S = S[::2, ::2]
        else:
            spin = Spin()

    # check for non-collinear (or SO)
    if spin.kind > Spin.POLARIZED:
        # Non colinear eigenvectors
        if S.shape[1] == eig_v.shape[1]:
            # Since we are going to reshape the eigen-vectors
            # to more easily get the mixed states, we can reduce the overlap matrix
            S = S[::2, ::2]

        # Initialize data
        PDOS = np.empty(
            [4, eig_v.shape[1] // 2, len(E)],
            dtype=dtype_complex_to_real(eig_v.dtype))

        d = distribution(E - eig[0]).reshape(1, -1)
        v = S.dot(eig_v[0].reshape(-1, 2))
        D = (conj(eig_v[0]) * v.ravel()).real.reshape(-1, 2)  # diagonal PDOS
        PDOS[0, :, :] = D.sum(1).reshape(-1, 1) * d  # total DOS
        PDOS[3, :, :] = (D[:, 0] - D[:, 1]).reshape(-1, 1) * d  # z-dos
        D = (conj(eig_v[0, 1::2]) * 2 * v[:, 0]).reshape(
            -1, 1)  # psi_down * psi_up * 2
        PDOS[1, :, :] = D.real * d  # x-dos
        PDOS[2, :, :] = D.imag * d  # y-dos
        for i in range(1, len(eig)):
            d = distribution(E - eig[i]).reshape(1, -1)
            v = S.dot(eig_v[i].reshape(-1, 2))
            D = (conj(eig_v[i]) * v.ravel()).real.reshape(-1, 2)
            PDOS[0, :, :] += D.sum(1).reshape(-1, 1) * d
            PDOS[3, :, :] += (D[:, 0] - D[:, 1]).reshape(-1, 1) * d
            D = (conj(eig_v[i, 1::2]) * 2 * v[:, 0]).reshape(-1, 1)
            PDOS[1, :, :] += D.real * d
            PDOS[2, :, :] += D.imag * d

    else:
        PDOS = (conj(eig_v[0]) * S.dot(eig_v[0])).real.reshape(-1, 1) \
               * distribution(E - eig[0]).reshape(1, -1)
        for i in range(1, len(eig)):
            PDOS[:, :] += (conj(eig_v[i]) * S.dot(eig_v[i])).real.reshape(-1, 1) \
                          * distribution(E - eig[i]).reshape(1, -1)

    return PDOS
def spin_moment(eig_v, S=None):
    r""" Calculate the spin magnetic moment (also known as spin texture)

    This calculation only makes sense for non-collinear calculations.

    The returned quantities are given in this order:

    - Spin magnetic moment along :math:`x` direction
    - Spin magnetic moment along :math:`y` direction
    - Spin magnetic moment along :math:`z` direction

    These are calculated using the Pauli matrices :math:`\boldsymbol\sigma_x`, :math:`\boldsymbol\sigma_y` and :math:`\boldsymbol\sigma_z`:

    .. math::

       \mathbf{S}_i^x &= \langle \psi_i | \boldsymbol\sigma_x \mathbf S | \psi_i \rangle
       \\
       \mathbf{S}_i^y &= \langle \psi_i | \boldsymbol\sigma_y \mathbf S | \psi_i \rangle
       \\
       \mathbf{S}_i^z &= \langle \psi_i | \boldsymbol\sigma_z \mathbf S | \psi_i \rangle

    Parameters
    ----------
    eig_v : array_like
       vectors describing the electronic states
    S : array_like, optional
       overlap matrix used in the :math:`\langle\psi|\mathbf S|\psi\rangle` calculation. If `None` the identity
       matrix is assumed. The overlap matrix should correspond to the system and :math:`k` point the eigenvectors
       have been evaluated at.

    Notes
    -----
    This routine cannot check whether the input eigenvectors originate from a non-collinear calculation.
    If a non-polarized eigenvector is passed to this routine, the output will have no physical meaning.

    See Also
    --------
    DOS : total DOS
    PDOS : projected DOS

    Returns
    -------
    numpy.ndarray
        spin moments per eigenvector with final dimension ``(eig_v.shape[0], 3)``.
    """
    if eig_v.ndim == 1:
        return spin_moment(eig_v.reshape(1, -1), S).ravel()

    if S is None:

        class S(object):
            __slots__ = []
            shape = (eig_v.shape[1] // 2, eig_v.shape[1] // 2)

            @staticmethod
            def dot(v):
                return v

    if S.shape[1] == eig_v.shape[1]:
        S = S[::2, ::2]

    # Initialize
    s = np.empty([eig_v.shape[0], 3], dtype=dtype_complex_to_real(eig_v.dtype))

    # TODO consider doing this all in a few lines
    # TODO Since there are no energy dependencies here we can actually do all
    # TODO dot products in one go and then use b-casting rules. Should be much faster
    # TODO but also way more memory demanding!
    for i in range(len(eig_v)):
        v = S.dot(eig_v[i].reshape(-1, 2))
        D = (conj(eig_v[i]) * v.ravel()).real.reshape(-1, 2)
        s[i, 2] = (D[:, 0] - D[:, 1]).sum()
        D = 2 * (conj(eig_v[i, 1::2]) * v[:, 0]).sum()
        s[i, 0] = D.real
        s[i, 1] = D.imag

    return s