Exemplo n.º 1
0
    def shift(self, E):
        r""" Shift the electronic structure by a constant energy

        This is equal to performing this operation:

        .. math::
           \mathbf H_\sigma = \mathbf H_\sigma + E \mathbf S

        where :math:`\mathbf H_\sigma` correspond to the spin diagonal components of the
        Hamiltonian.

        Parameters
        ----------
        E : float or (2,)
           the energy (in eV) to shift the electronic structure, if two values are passed
           the two first spin-components get shifted individually.
        """
        E = _a.asarrayd(E)
        if E.size == 1:
            E = np.tile(E, 2)

        if np.abs(E).sum() == 0.:
            # When the energy is zero, there is no shift
            return

        if self.orthogonal:
            for i in range(self.shape[0]):
                for j in range(min(self.spin.spins, 2)):
                    self[i, i, j] = self[i, i, j] + E[j]
        else:
            # For non-collinear and SO only the diagonal (real) components
            # should be shifted.
            for i in range(min(self.spin.spins, 2)):
                self._csr._D[:, i] += self._csr._D[:, self.S_idx] * E[i]
Exemplo n.º 2
0
    def spin_squared(self, k=(0, 0, 0), n_up=None, n_down=None, **kwargs):
        r""" Calculate spin-squared expectation value, see `~sisl.physics.electron.spin_squared` for details

        Parameters
        ----------
        k : array_like, optional
            k-point at which the spin-squared expectation value is
        n_up : int, optional
            number of states for spin up configuration, default to all. All states up to and including
            `n_up`.
        n_down : int, optional
            same as `n_up` but for the spin-down configuration
        **kwargs : optional
            additional parameters passed to the `eigenstate` routine
        """
        if not self.spin.is_polarized:
            raise ValueError(self.__class__.__name__ +
                             '.spin_squared requires as spin-polarized system')
        es_alpha = self.eigenstate(k, spin=0, **kwargs)
        if not n_up is None:
            es_alpha = es_alpha.sub(range(n_up))
        es_beta = self.eigenstate(k, spin=1, **kwargs)
        if not n_down is None:
            es_beta = es_beta.sub(range(n_down))
        # es_alpha.Sk should equal es_beta.Sk, so just pass one of them
        return spin_squared(es_alpha.state, es_beta.state, es_alpha.Sk())
Exemplo n.º 3
0
Arquivo: state.py Projeto: silsgs/sisl
    def outer(self, right=None, align=True):
        r""" Return the outer product by :math:`\sum_i|\psi_i\rangle\langle\psi'_i|`

        Parameters
        ----------
        right : State, optional
           the right object to calculate the outer product of, if not passed it will do the outer
           product with itself. This object will always be the left :math:`|\psi_i\rangle`
        align : bool, optional
           first align `right` with the angles for this state (see `align`)

        Notes
        -----
        This does *not* take into account a possible overlap matrix when non-orthogonal basis sets are used.

        Returns
        -------
        numpy.ndarray
            a matrix with the sum of outer state products
        """
        if right is None:
            m = _outer1(self.state[0, :])
            for i in range(1, len(self)):
                m += _outer1(self.state[i, :])
        else:
            if not np.array_equal(self.shape, right.shape):
                raise ValueError(self.__class__.__name__ + '.outer requires the objects to have the same shape')
            if align:
                # Align the states
                right = self.align_phase(right, copy=False)
            m = _outer(self.state[0, :], right.state[0, :])
            for i in range(1, len(self)):
                m += _outer(self.state[i, :], right.state[i, :])
        return m
Exemplo n.º 4
0
    def fromsp(cls, geom, P, S=None):
        """ Read and return the object with possible overlap """
        # Calculate maximum number of connections per row
        nc = 0

        # Ensure list of csr format (to get dimensions)
        if isspmatrix(P):
            P = [P]

        # Number of dimensions
        dim = len(P)
        # Sort all indices for the passed sparse matrices
        for i in range(dim):
            P[i] = P[i].tocsr()
            P[i].sort_indices()

        # Figure out the maximum connections per
        # row to reduce number of re-allocations to 0
        for i in range(P[0].shape[0]):
            nc = max(nc, P[0][i, :].getnnz())

        # Create the sparse object
        v = cls(geom, dim, P[0].dtype, nc, orthogonal=S is None)

        for i in range(dim):
            for jo, io, vv in ispmatrixd(P[i]):
                v[jo, io, i] = vv

        if not S is None:
            for jo, io, vv in ispmatrixd(S):
                v.S[jo, io] = vv

        return v
Exemplo n.º 5
0
    def fromsp(cls, geometry, P, S=None):
        """ Read and return the object with possible overlap """
        # Calculate maximum number of connections per row
        nc = 0

        # Ensure list of csr format (to get dimensions)
        if isspmatrix(P):
            P = [P]

        # Number of dimensions
        dim = len(P)
        # Sort all indices for the passed sparse matrices
        for i in range(dim):
            P[i] = P[i].tocsr()
            P[i].sort_indices()
            P[i].sum_duplicates()

        # Figure out the maximum connections per
        # row to reduce number of re-allocations to 0
        for i in range(P[0].shape[0]):
            nc = max(nc, P[0][i, :].getnnz())

        # Create the sparse object
        p = cls(geometry, dim, P[0].dtype, nc, orthogonal=S is None)

        if p._size != P[0].shape[0]:
            raise ValueError(cls.__name__ + '.fromsp cannot create a new class, the geometry ' + \
                             'and sparse matrices does not have coinciding dimensions size != sp.shape[0]')

        for i in range(dim):
            ptr = P[i].indptr
            col = P[i].indices
            D = P[i].data

            # loop and add elements
            for r in range(p.shape[0]):
                sl = slice(ptr[r], ptr[r+1], None)
                p[r, col[sl], i] = D[sl]

        if not S is None:
            S = S.tocsr()
            S.sort_indices()
            S.sum_duplicates()
            ptr = S.indptr
            col = S.indices
            D = S.data

            # loop and add elements
            for r in range(p.shape[0]):
                sl = slice(ptr[r], ptr[r+1], None)
                p.S[r, col[sl]] = D[sl]

        return p
    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
Exemplo n.º 7
0
Arquivo: state.py Projeto: silsgs/sisl
    def rotate(self, phi=0., individual=False):
        r""" Rotate all states (in-place) to rotate the largest component to be along the angle `phi`

        The states will be rotated according to:

        .. math::

            S' = S / S^\dagger_{\phi-\mathrm{max}} \exp (i \phi),

        where :math:`S^\dagger_{\phi-\mathrm{max}}` is the phase of the component with the largest amplitude
        and :math:`\phi` is the angle to align on.

        Parameters
        ----------
        phi : float, optional
           angle to align the state at (in radians), 0 is the positive real axis
        individual : bool, optional
           whether the rotation is per state, or a single maximum component is chosen.
        """
        # Convert angle to complex phase
        phi = np.exp(1j * phi)
        s = self.state.view()
        if individual:
            for i in range(len(self)):
                # Find the maximum amplitude index
                idx = _argmax(_abs(s[i, :]))
                s[i, :] *= phi * _conj(s[i, idx] / _abs(s[i, idx]))
        else:
            # Find the maximum amplitude index among all elements
            idx = np.unravel_index(_argmax(_abs(s)), s.shape)
            s *= phi * _conj(s[idx] / _abs(s[idx]))
Exemplo n.º 8
0
Arquivo: delta.py Projeto: lwk205/sisl
    def read_geometry(self, *args, **kwargs):
        """ Returns the `Geometry` object from this file """
        sc = self.read_supercell()

        xyz = _a.arrayd(np.copy(self._value('xa')))
        xyz.shape = (-1, 3)

        # Create list with correct number of orbitals
        lasto = _a.arrayi(np.copy(self._value('lasto')))
        nos = np.append([lasto[0]], np.diff(lasto))
        nos = _a.arrayi(nos)

        if 'atom' in kwargs:
            # The user "knows" which atoms are present
            atms = kwargs['atom']
            # Check that all atoms have the correct number of orbitals.
            # Otherwise we will correct them
            for i in range(len(atms)):
                if atms[i].no != nos[i]:
                    atms[i] = Atom(atms[i].Z, [-1] * nos[i], tag=atms[i].tag)

        else:
            # Default to Hydrogen atom with nos[ia] orbitals
            # This may be counterintuitive but there is no storage of the
            # actual species
            atms = [Atom('H', [-1] * o) for o in nos]

        # Create and return geometry object
        geom = Geometry(xyz, atms, sc=sc)

        return geom
Exemplo n.º 9
0
Arquivo: ham.py Projeto: cationly/sisl
    def read_hamiltonian(self, hermitian=True, dtype=np.float64, **kwargs):
        """ Reads a Hamiltonian (including the geometry)

        Reads the Hamiltonian model
        """
        # Read the geometry in this file
        geom = self.read_geometry()

        # Rewind to ensure we can read the entire matrix structure
        self.fh.seek(0)

        # With the geometry in place we can read in the entire matrix
        # Create a new sparse matrix
        from scipy.sparse import lil_matrix
        H = lil_matrix((geom.no, geom.no_s), dtype=dtype)
        S = lil_matrix((geom.no, geom.no_s), dtype=dtype)

        def i2o(geom, i):
            try:
                # pure orbital
                return int(i)
            except:
                # ia[o]
                # atom ia and the orbital o
                j = i.replace('[', ' ').replace(']', ' ').split()
                return geom.a2o(int(j[0])) + int(j[1])

        # Start reading in the supercell
        while True:
            found, l = self.step_to('matrix', reread=False)
            if not found:
                break

            # Get supercell
            ls = l.split()
            try:
                isc = np.array([int(ls[i]) for i in range(2, 5)], np.int32)
            except:
                isc = np.array([0, 0, 0], np.int32)

            off1 = geom.sc_index(isc) * geom.no
            off2 = geom.sc_index(-isc) * geom.no
            l = self.readline()
            while not l.startswith('end'):
                ls = l.split()
                jo = i2o(geom, ls[0])
                io = i2o(geom, ls[1])
                h = float(ls[2])
                try:
                    s = float(ls[3])
                except:
                    s = 0.
                H[jo, io + off1] = h
                S[jo, io + off1] = s
                if hermitian:
                    S[io, jo + off2] = s
                    H[io, jo + off2] = h
                l = self.readline()

        return Hamiltonian.fromsp(geom, H, S)
Exemplo n.º 10
0
Arquivo: state.py Projeto: silsgs/sisl
    def align_norm(self, other, ret_index=False):
        r""" Align `other.state` with the site-norms for this state, a copy of `other` is returned with re-ordered states

        To determine the new ordering of `other` we first calculate the residual norm of the site-norms.

        .. math::
           \delta N_{\alpha\beta} = \sum_i \big(\langle \psi^\alpha_i | \psi^\alpha_i\rangle - \langle \psi^\beta_i | \psi^\beta_i\rangle\big)^2

        where :math:`\alpha` and :math:`\beta` correspond to state indices in `self` and `other`, respectively.
        The new states (from `other`) returned is then ordered such that the index
        :math:`\alpha \equiv \beta'` where :math:`\delta N_{\alpha\beta}` is smallest.

        Parameters
        ----------
        other : State
           the other state to align onto this state
        ret_index : bool, optional
           also return indices for the swapped indices

        Returns
        -------
        other_swap : State
            A swapped instance of `other`
        index : array of int
            the indices that swaps `other` to be ``other_swap``, i.e. ``other_swap = other.sub(index)``

        Notes
        -----
        The input state and output state have the same states, but their ordering is not necessarily the same.

        See Also
        --------
        align_phase : rotate states such that their phases align
        """
        snorm = self.norm2(False)
        onorm = other.norm2(False)

        # Now find new orderings
        show_warn = False
        idx = _a.fulli(len(other), -1)
        idxr = _a.emptyi(len(other))
        for i in range(len(other)):
            R = ((snorm - onorm[i, :].reshape(1, -1)) ** 2).sum(1)

            # Figure out which band it should correspond to
            # find closest largest one
            for j in np.argsort(R):
                if j not in idx[:i]:
                    idx[i] = j
                    idxr[j] = i
                    break
                show_warn = True

        if show_warn:
            warn(self.__class__.__name__ + '.align_norm found multiple possible candidates with minimal residue, swapping not unique')

        if ret_index:
            return other.sub(idxr), idxr
        return other.sub(idxr)
Exemplo n.º 11
0
    def shift(self, E):
        """ Shift the electronic structure by a constant energy

        Parameters
        ----------
        E : float
           the energy (in eV) to shift the electronic structure
        """
        if not self.orthogonal:
            # For non-colinear and SO only the diagonal components
            # should be shifted.
            for i in range(min(self.spin.spins, 2)):
                self._csr._D[:, i] -= self._csr._D[:, self.S_idx] * E
        else:
            for i in range(self.shape[0]):
                for j in range(min(self.spin.spins, 2)):
                    self[i, i, j] = self[i, i, j] - E
Exemplo n.º 12
0
def iter_shape(shape):
    """ Generator for iterating a shape by returning consecutive slices 

    Parameters
    ----------
    shape : array_like
      the shape of the iterator

    Yields
    ------
    tuple of int
       a tuple of the same length as the input shape. The iterator
       is using the C-indexing.

    Examples
    --------
    >>> for slc in iter_shape([2, 1, 3]):
    >>>    print(slc)
    [0, 0, 0]
    [0, 0, 1]
    [0, 0, 2]
    [1, 0, 0]
    [1, 0, 1]
    [1, 0, 2]
    """
    shape1 = [i - 1 for i in shape]
    ns = len(shape)
    ns1 = ns - 1
    # Create list for iterating
    # we require a list because tuple's are immutable
    slc = [0] * ns

    while slc[0] < shape[0]:
        for i in range(shape[ns1]):
            slc[ns1] = i
            yield slc

        # Increment the previous shape indices
        for i in range(ns1, 0, -1):
            if slc[i] >= shape1[i]:
                slc[i] = 0
                if i > 0:
                    slc[i - 1] += 1
    def _Pk_non_colinear_accummulate(self,
                                     k=(0, 0, 0),
                                     dtype=None,
                                     gauge='R',
                                     format='csr'):
        """ Sparse matrix (``scipy.sparse.csr_matrix``) at `k` for a non-collinear system

        Parameters
        ----------
        k: array_like, optional
           k-point (default is Gamma point)
        dtype : numpy.dtype, optional
           default to `numpy.complex128`
        gauge : {'R', 'r'}
           chosen gauge
        """
        if dtype is None:
            dtype = np.complex128

        if np.dtype(dtype).kind != 'c':
            raise ValueError(
                "Non-colinear quantity setup requires a complex matrix")

        if gauge != 'R':
            raise ValueError('Only the cell vector gauge has been implemented')

        k = np.asarray(k, np.float64)
        k.shape = (-1, )

        no = self.no

        # sparse matrix dimension (2 * self.no)
        V = csr_matrix((len(self), len(self)), dtype=dtype)
        v = [self.tocsr(i) for i in range(len(self.spin))]

        # Calculate all phases
        phases = np.exp(
            -1j * dot(dot(dot(self.rcell, k), self.cell), self.sc.sc_off.T))

        # Now accummulate the matrix
        for si, phase in enumerate(phases):
            sl = slice(si * no, (si + 1) * no, None)

            # diagonal elements
            V[::2, ::2] += v[0][:, sl] * phase
            V[1::2, 1::2] += v[1][:, sl] * phase

            # off-diagonal elements
            vv = v[2][:, sl] - 1j * v[3][:, sl]
            V[1::2, ::2] += vv * phase
            V[::2, 1::2] += vv.conj() * phase

        del v

        return V.asformat(format)
Exemplo n.º 14
0
    def _Pk_spin_orbit_accummulate(self,
                                   k=(0, 0, 0),
                                   dtype=None,
                                   gauge='R',
                                   format='csr'):
        """ Sparse matrix (``scipy.sparse.csr_matrix``) at `k` for a spin-orbit system

        Parameters
        ----------
        k: ``array_like``, `[0,0,0]`
           k-point 
        dtype : ``numpy.dtype``
           default to `numpy.complex128`
        gauge : str, 'R'
           chosen gauge
        """
        if dtype is None:
            dtype = np.complex128

        if np.dtype(dtype).kind != 'c':
            raise ValueError(
                "Spin orbit quantity setup requires a complex matrix")

        if gauge != 'R':
            raise ValueError('Only the cell vector gauge has been implemented')

        k = np.asarray(k, np.float64)
        k.shape = (-1, )

        no = self.no

        # sparse matrix dimension (2 * self.no)
        V = csr_matrix((len(self), len(self)), dtype=dtype)
        v = [self.tocsr(i) for i in range(len(self.spin))]

        # Calculate all phases
        phases = np.exp(
            -1j * dot(dot(dot(self.rcell, k), self.cell), self.sc.sc_off.T))

        # Now accummulate the matrix
        for si, phase in enumerate(phases):
            sl = slice(si * no, (si + 1) * no, None)

            # diagonal elements
            V[::2, ::2] += (v[0][:, sl] + 1j * v[4][:, sl]) * phase
            V[1::2, 1::2] = (v[1][:, sl] + 1j * v[5][:, sl]) * phase

            # off-diagonal elements
            V[1::2, ::2] = (v[2][:, sl] - 1j * v[3][:, sl]) * phase
            V[::2, 1::2] = (v[6][:, sl] + 1j * v[7][:, sl]) * phase

        del v

        return V.asformat(format)
    def shift(self, E):
        """ Shift the electronic structure by a constant energy

        Parameters
        ----------
        E : float or (2,)
           the energy (in eV) to shift the electronic structure, if two values are passed
           the two first spin-components get shifted individually.
        """
        E = _a.asarrayd(E)
        if E.size == 1:
            E = np.tile(_a.asarrayd(E), 2)
        if not self.orthogonal:
            # For non-collinear and SO only the diagonal (real) components
            # should be shifted.
            for i in range(min(self.spin.spins, 2)):
                self._csr._D[:, i] += self._csr._D[:, self.S_idx] * E[i]
        else:
            for i in range(self.shape[0]):
                for j in range(min(self.spin.spins, 2)):
                    self[i, i, j] = self[i, i, j] + E[i]
Exemplo n.º 16
0
    def _mulliken(self):
        # Calculate the Mulliken elements

        # First we re-create the sparse matrix as required for csr_matrix
        ptr = self._csr.ptr
        ncol = self._csr.ncol
        # Indices of non-zero elements
        idx = array_arange(ptr[:-1], n=ncol)
        # Create the new pointer array
        new_ptr = _a.emptyi(len(ptr))
        new_ptr[0] = 0
        col = self._csr.col[idx]
        _a.cumsumi(ncol, out=new_ptr[1:])

        # The shape of the matrices
        shape = self.shape[:2]

        # Create list of charges to be returned
        Q = list()

        if self.orthogonal:
            # We only need the diagonal elements
            S = csr_matrix(shape, dtype=self.dtype)
            S.setdiag(1.)

            for i in range(self.shape[2]):
                DM = csr_matrix((self._csr._D[idx, i], col, new_ptr),
                                shape=shape)
                Q.append(DM.multiply(S))
                Q[-1].eliminate_zeros()
        else:

            # We now what S is and do it element-wise.
            q = self._csr._D[idx, :-1] * self._csr._D[idx, self.S_idx].reshape(
                -1, 1)
            for i in range(q.shape[1]):
                Q.append(csr_matrix((q[:, i], col, new_ptr), shape=shape))
                Q[-1].eliminate_zeros()

        return Q
Exemplo n.º 17
0
Arquivo: state.py Projeto: silsgs/sisl
    def iter(self, asarray=False):
        """ An iterator looping over the coefficients in this system

        Parameters
        ----------
        asarray : bool, optional
           if true the yielded values are the coefficient vectors, i.e. a numpy array.
           Otherwise an equivalent object is yielded.

        Yields
        ------
        coeff : Coefficent
           the current coefficent as an object, only returned if `asarray` is false.
        coeff : numpy.ndarray
           the current the coefficient as an array, only returned if `asarray` is true.
        """
        if asarray:
            for i in range(len(self)):
                yield self.c[i]
        else:
            for i in range(len(self)):
                yield self.sub(i)
Exemplo n.º 18
0
Arquivo: state.py Projeto: silsgs/sisl
    def iter(self, asarray=False):
        """ An iterator looping over the states in this system

        Parameters
        ----------
        asarray : bool, optional
           if true the yielded values are the state vectors, i.e. a numpy array.
           Otherwise an equivalent object is yielded.

        Yields
        ------
        state : State
           a state *only* containing individual elements, if `asarray` is false
        state : numpy.ndarray
           a state *only* containing individual elements, if `asarray` is true
        """
        if asarray:
            for i in range(len(self)):
                yield self.state[i]
        else:
            for i in range(len(self)):
                yield self.sub(i)
Exemplo n.º 19
0
    def read_hessian(self, **kwargs):
        """ Returns a GULP Hessian matrix model for the output of GULP 

        Parameters
        ----------
        cutoff: float (0.001 eV/Ang**2)
           the cutoff of the force-constant matrix for adding to the matrix
        dtype: np.dtype (np.float64)
           default data-type of the matrix
        """
        from scipy.sparse import diags

        dtype = kwargs.get('dtype', np.float64)

        geom = self.read_geometry(**kwargs)

        hessian = kwargs.get('hessian', None)
        if hessian is None:
            dyn = self._read_dyn(geom.no, **kwargs)
        else:
            dyn = get_sile(hessian, 'r').read_hessian(**kwargs)

            if dyn.shape[0] != geom.no:
                raise ValueError(
                    "Inconsistent Hessian file, number of atoms not correct")

            # Perform mass scaling to retrieve the dynamical matrix
            mass = [geom.atom[ia].mass for ia in range(geom.na)]

            # Construct orbital mass
            mass = np.array(mass, np.float64).repeat(3)

            # Scale to get dynamical matrix
            dyn.data[:] /= np.sqrt(mass[dyn.row] * mass[dyn.col])

            # slower, less memory consuming...
            #for I, ijd in enumerate(zip(dyn.row, dyn.col, dyn.data)):
            #    dyn.data[I] = ijd[2] / sqrt(mass[ijd[0]] * mass[ijd[1]])

            # clean-up
            del mass

        return Hessian.fromsp(geom, dyn)
Exemplo n.º 20
0
    def write_geometry(self, geom, fmt='.8f', **kwargs):
        """
        Writes the geometry to the output file

        Parameters
        ----------
        geom: Geometry
              The geometry we wish to write
        """

        # The format of the geometry file is
        # for now, pretty stringent
        # Get cell_fmt
        cell_fmt = fmt
        if 'cell_fmt' in kwargs:
            cell_fmt = kwargs['cell_fmt']
        xyz_fmt = fmt

        self._write('begin cell\n')
        # Write the cell
        fmt_str = '  {{0:{0}}} {{1:{0}}} {{2:{0}}}\n'.format(cell_fmt)
        for i in range(3):
            self._write(fmt_str.format(*geom.cell[i, :]))
        self._write('end cell\n')

        # Write number of super cells in each direction
        self._write('\nsupercell {0:d} {1:d} {2:d}\n'.format(*geom.nsc))

        # Write all atomic positions along with the specie type
        self._write('\nbegin atom\n')
        fmt1_str = '  {{0:d}} {{1:{0}}} {{2:{0}}} {{3:{0}}}\n'.format(xyz_fmt)
        fmt2_str = '  {{0:d}}[{{1:d}}] {{2:{0}}} {{3:{0}}} {{4:{0}}}\n'.format(
            xyz_fmt)

        for ia in geom:
            Z = geom.atoms[ia].Z
            no = geom.atoms[ia].no
            if no == 1:
                self._write(fmt1_str.format(Z, *geom.xyz[ia, :]))
            else:
                self._write(fmt2_str.format(Z, no, *geom.xyz[ia, :]))

        self._write('end atom\n')
def DOS(E, eig, distribution='gaussian'):
    r""" Calculate the density of states (DOS) for a set of energies, `E`, with a distribution function

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

    .. math::
       \mathrm{DOS}(E) = \sum_i D(E-\epsilon_i) \approx\delta(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 retrieved from `sisl.physics.distribution`.

    Parameters
    ----------
    E : array_like
       energies to calculate the DOS at
    eig : array_like
       eigenvalues
    distribution : func or str, optional
       a function that accepts :math:`E-\epsilon` as argument and calculates the
       distribution function.

    See Also
    --------
    sisl.physics.distribution : a selected set of implemented distribution functions
    PDOS : projected DOS (same as this, but projected onto each orbital)
    spin_moment: spin moment for states

    Returns
    -------
    numpy.ndarray : DOS calculated at energies, has same length as `E`
    """
    if isinstance(distribution, str):
        distribution = get_distribution(distribution)

    DOS = distribution(E - eig[0])
    for i in range(1, len(eig)):
        DOS += distribution(E - eig[i])
    return DOS
Exemplo n.º 22
0
Arquivo: state.py Projeto: silsgs/sisl
    def outer(self, idx=None):
        r""" Return the outer product for the indices `idx` (or all if ``None``) by :math:`\sum_i|\psi_i\rangle c_i\langle\psi_i|`

        Parameters
        ----------
        idx : int or array_like, optional
           only perform an outer product of the specified indices, otherwise all states are used

        Returns
        -------
        numpy.ndarray : a matrix with the sum of outer state products
        """
        if idx is None:
            m = _couter1(self.c[0], self.state[0].ravel())
            for i in range(1, len(self)):
                m += _couter1(self.c[i], self.state[i].ravel())
            return m
        idx = _a.asarrayi(idx).ravel()
        m = _couter1(self.c[idx[0]], self.state[idx[0]].ravel())
        for i in idx[1:]:
            m += _couter1(self.c[i], self.state[i].ravel())
        return m
Exemplo n.º 23
0
    def write_hamiltonian(self, H, **kwargs):
        """ Writes Hamiltonian model to file

        Parameters
        ----------
        H : Hamiltonian
           the model to be saved in the NC file
        spin : int, optional
           the spin-index of the Hamiltonian object that is stored. Default is the first index.
        """
        # Ensure finalization
        H.finalize()

        # Ensure that the geometry is written
        self.write_geometry(H.geom)

        self._crt_dim(self, 'spin', len(H.spin))

        # Determine the type of dH we are storing...
        k = kwargs.get('k', None)
        E = kwargs.get('E', None)

        ilvl, ik, iE = self._get_lvl_k_E(**kwargs)
        lvl = self._add_lvl(ilvl)

        # Append the sparsity pattern
        # Create basis group
        if 'n_col' in lvl.variables:
            if len(lvl.dimensions['nnzs']) != H.nnz:
                raise ValueError("The sparsity pattern stored in dH *MUST* be equivalent for "
                                 "all dH entries [nnz].")
            if np.any(lvl.variables['n_col'][:] != H._csr.ncol[:]):
                raise ValueError("The sparsity pattern stored in dH *MUST* be equivalent for "
                                 "all dH entries [n_col].")
            if np.any(lvl.variables['list_col'][:] != H._csr.col[:]+1):
                raise ValueError("The sparsity pattern stored in dH *MUST* be equivalent for "
                                 "all dH entries [list_col].")
            if np.any(lvl.variables['isc_off'][:] != H.geometry.sc.sc_off):
                raise ValueError("The sparsity pattern stored in dH *MUST* be equivalent for "
                                 "all dH entries [sc_off].")
        else:
            self._crt_dim(lvl, 'nnzs', H._csr.col.shape[0])
            v = self._crt_var(lvl, 'n_col', 'i4', ('no_u',))
            v.info = "Number of non-zero elements per row"
            v[:] = H._csr.ncol[:]
            v = self._crt_var(lvl, 'list_col', 'i4', ('nnzs',),
                              chunksizes=(len(H._csr.col),), **self._cmp_args)
            v.info = "Supercell column indices in the sparse format"
            v[:] = H._csr.col[:] + 1  # correct for fortran indices
            v = self._crt_var(lvl, 'isc_off', 'i4', ('n_s', 'xyz'))
            v.info = "Index of supercell coordinates"
            v[:] = H.geometry.sc.sc_off[:, :]

        warn_E = True
        if ilvl in [3, 4]:
            if iE < 0:
                # We need to add the new value
                iE = len(lvl.variables['E'])
                lvl.variables['E'][iE] = E * eV2Ry
                warn_E = False

        warn_k = True
        if ilvl in [2, 4]:
            if ik < 0:
                ik = len(lvl.variables['kpt'])
                lvl.variables['kpt'][ik, :] = k
                warn_k = False

        if ilvl == 4 and warn_k and warn_E and False:
            # As soon as we have put the second k-point and the first energy
            # point, this warning will proceed...
            # I.e. even though the variable has not been set, it will WARN
            # Hence we out-comment this for now...
            warn(SileWarning('Overwriting k-point {0} and energy point {1} correction.'.format(ik, iE)))
        elif ilvl == 3 and warn_E:
            warn(SileWarning('Overwriting energy point {0} correction.'.format(iE)))
        elif ilvl == 2 and warn_k:
            warn(SileWarning('Overwriting k-point {0} correction.'.format(ik)))

        if ilvl == 1:
            dim = ('spin', 'nnzs')
            sl = [slice(None)] * 2
            csize = [1] * 2
        elif ilvl == 2:
            dim = ('nkpt', 'spin', 'nnzs')
            sl = [slice(None)] * 3
            sl[0] = ik
            csize = [1] * 3
        elif ilvl == 3:
            dim = ('ne', 'spin', 'nnzs')
            sl = [slice(None)] * 3
            sl[0] = iE
            csize = [1] * 3
        elif ilvl == 4:
            dim = ('nkpt', 'ne', 'spin', 'nnzs')
            sl = [slice(None)] * 4
            sl[0] = ik
            sl[1] = iE
            csize = [1] * 4

        # Number of non-zero elements
        csize[-1] = H.nnz

        if H.dtype.kind == 'c':
            v1 = self._crt_var(lvl, 'RedH', 'f8', dim,
                               chunksizes=csize,
                               attr = {'info': "Real part of dH",
                                       'unit': "Ry"}, **self._cmp_args)
            for i in range(len(H.spin)):
                sl[-2] = i
                v1[sl] = H._csr._D[:, i].real * eV2Ry

            v2 = self._crt_var(lvl, 'ImdH', 'f8', dim,
                               chunksizes=csize,
                               attr = {'info': "Imaginary part of dH",
                                       'unit': "Ry"}, **self._cmp_args)
            for i in range(len(H.spin)):
                sl[-2] = i
                v2[sl] = H._csr._D[:, i].imag * eV2Ry

        else:
            v = self._crt_var(lvl, 'dH', 'f8', dim,
                              chunksizes=csize,
                              attr = {'info': "dH",
                                      'unit': "Ry"},  **self._cmp_args)
            for i in range(len(H.spin)):
                sl[-2] = i
                v[sl] = H._csr._D[:, i] * eV2Ry
Exemplo n.º 24
0
    def write_hamiltonian(self, ham, hermitian=True, **kwargs):
        """ Writes the Hamiltonian model to the file

        Writes a Hamiltonian model to the intrinsic Hamiltonian file format.
        The file can be constructed by the implict force of Hermiticity,
        or without.

        Utilizing the Hermiticity we reduce the file-size by approximately
        50%.

        Parameters
        ----------
        ham : `Hamiltonian` model
        hermitian : boolean=True
            whether the stored data is halved using the Hermitian property

        """
        ham.finalize()

        # We use the upper-triangular form of the Hamiltonian
        # and the overlap matrix for hermitian problems

        geom = ham.geometry

        # First write the geometry
        self.write_geometry(geom, **kwargs)

        # We default to the advanced layuot if we have more than one
        # orbital on any one atom
        advanced = kwargs.get(
            'advanced',
            np.any(np.array([a.no for a in geom.atom.atom], np.int32) > 1))

        fmt = kwargs.get('fmt', 'g')
        if advanced:
            fmt1_str = ' {{0:d}}[{{1:d}}] {{2:d}}[{{3:d}}] {{4:{0}}}\n'.format(
                fmt)
            fmt2_str = ' {{0:d}}[{{1:d}}] {{2:d}}[{{3:d}}] {{4:{0}}} {{5:{0}}}\n'.format(
                fmt)
        else:
            fmt1_str = ' {{0:d}} {{1:d}} {{2:{0}}}\n'.format(fmt)
            fmt2_str = ' {{0:d}} {{1:d}} {{2:{0}}} {{3:{0}}}\n'.format(fmt)

        # We currently force the model to be finalized
        # before we can write it
        # This should be easily circumvented
        H = ham.tocsr(0)
        if not ham.orthogonal:
            S = ham.tocsr(ham.S_idx)

        # If the model is Hermitian we can
        # do with writing out half the entries
        if hermitian:
            herm_acc = kwargs.get('herm_acc', 1e-6)
            # We check whether it is Hermitian (not S)
            for i, isc in enumerate(geom.sc.sc_off):
                oi = i * geom.no
                oj = geom.sc_index(-isc) * geom.no
                # get the difference between the ^\dagger elements
                diff = H[:, oi:oi + geom.no] - \
                    H[:, oj:oj + geom.no].transpose()
                diff.eliminate_zeros()
                if np.any(np.abs(diff.data) > herm_acc):
                    amax = np.amax(np.abs(diff.data))
                    warn(
                        SileWarning(
                            'The model could not be asserted to be Hermitian '
                            'within the accuracy required ({0}).'.format(
                                amax)))
                    hermitian = False
                del diff

        if hermitian:
            # Remove all double stuff
            for i, isc in enumerate(geom.sc.sc_off):
                if np.any(isc < 0):
                    # We have ^\dagger element, remove it
                    o = i * geom.no
                    # Ensure that we remove all nullified quantities
                    # (setting elements to zero will add them internally
                    #  :(, hence this actually constructs the full matrix
                    # Therefore we do it on a row basis, to limit memory
                    # requirements
                    for j in range(geom.no):
                        H[j, o:o + geom.no] = 0.
                        H.eliminate_zeros()
                        if not ham.orthogonal:
                            S[j, o:o + geom.no] = 0.
                            S.eliminate_zeros()
            o = geom.sc_index(np.zeros([3], np.int32))
            # Get upper-triangular matrix of the unit-cell H and S
            ut = triu(H[:, o:o + geom.no], k=0).tocsr()
            for j in range(geom.no):
                H[j, o:o + geom.no] = 0.
                H[j, o:o + geom.no] = ut[j, :]
                H.eliminate_zeros()
            if not ham.orthogonal:
                ut = triu(S[:, o:o + geom.no], k=0).tocsr()
                for j in range(geom.no):
                    S[j, o:o + geom.no] = 0.
                    S[j, o:o + geom.no] = ut[j, :]
                    S.eliminate_zeros()

                # Ensure that S and H have the same sparsity pattern
                for jo, io in ispmatrix(S):
                    H[jo, io] = H[jo, io]

            del ut

        # Start writing of the model
        # We loop on all super-cells
        for i, isc in enumerate(geom.sc.sc_off):
            # Check that we have any contributions in this
            # sub-section
            Hsub = H[:, i * geom.no:(i + 1) * geom.no]
            if not ham.orthogonal:
                Ssub = S[:, i * geom.no:(i + 1) * geom.no]
            if Hsub.getnnz() == 0:
                continue
            # We have a contribution, write out the information
            self._write('\nbegin matrix {0:d} {1:d} {2:d}\n'.format(*isc))
            if advanced:
                for jo, io, h in ispmatrixd(Hsub):
                    o = np.array([jo, io], np.int32)
                    a = geom.o2a(o)
                    o = o - geom.a2o(a)
                    if not ham.orthogonal:
                        s = Ssub[jo, io]
                    elif jo == io:
                        s = 1.
                    else:
                        s = 0.
                    if s == 0.:
                        self._write(fmt1_str.format(a[0], o[0], a[1], o[1], h))
                    else:
                        self._write(
                            fmt2_str.format(a[0], o[0], a[1], o[1], h, s))
            else:
                for jo, io, h in ispmatrixd(Hsub):
                    if not ham.orthogonal:
                        s = Ssub[jo, io]
                    elif jo == io:
                        s = 1.
                    else:
                        s = 0.
                    if s == 0.:
                        self._write(fmt1_str.format(jo, io, h))
                    else:
                        self._write(fmt2_str.format(jo, io, h, s))
            self._write('end matrix {0:d} {1:d} {2:d}\n'.format(*isc))
Exemplo n.º 25
0
    def read_geometry(self):
        """ Reading a geometry in regular Hamiltonian format """

        cell = np.zeros([3, 3], np.float64)
        Z = []
        xyz = []

        nsc = np.zeros([3], np.int32)

        def Z2no(i, no):
            try:
                # pure atomic number
                return int(i), no
            except Exception:
                # both atomic number and no
                j = i.replace('[', ' ').replace(']', ' ').split()
                return int(j[0]), int(j[1])

        # The format of the geometry file is
        keys = ['atom', 'cell', 'supercell', 'nsc']
        for _ in range(len(keys)):
            _, l = self.step_to(keys, case=False)
            l = l.strip()
            if 'supercell' in l.lower() or 'nsc' in l.lower():
                # We have everything in one line
                l = l.split()[1:]
                for i in range(3):
                    nsc[i] = int(l[i])
            elif 'cell' in l.lower():
                if 'begin' in l.lower():
                    for i in range(3):
                        l = self.readline().split()
                        cell[i, 0] = float(l[0])
                        cell[i, 1] = float(l[1])
                        cell[i, 2] = float(l[2])
                    self.readline()  # step past the block
                else:
                    # We have everything in one line
                    l = l.split()[1:]
                    for i in range(3):
                        cell[i, i] = float(l[i])
                    # TODO incorporate rotations
            elif 'atom' in l.lower():
                l = self.readline()
                while not l.startswith('end'):
                    ls = l.split()
                    try:
                        no = int(ls[4])
                    except Exception:
                        no = 1
                    z, no = Z2no(ls[0], no)
                    Z.append({'Z': z, 'orbs': no})
                    xyz.append([float(f) for f in ls[1:4]])
                    l = self.readline()
                xyz = np.array(xyz, np.float64)
                xyz.shape = (-1, 3)
                self.readline()  # step past the block

        # Return the geometry
        # Create list of atoms
        geom = Geometry(xyz, atom=Atom[Z], sc=SuperCell(cell, nsc))

        return geom
Exemplo n.º 26
0
Arquivo: delta.py Projeto: lwk205/sisl
    def write_delta(self, delta, **kwargs):
        r""" Writes a :math:`\delta` Hamiltonian to the file

        This term may be of

        - level-1: no E or k dependence
        - level-2: k-dependent
        - level-3: E-dependent
        - level-4: k- and E-dependent

        Parameters
        ----------
        delta : SparseOrbitalBZSpin
           the model to be saved in the NC file
        k : array_like, optional
           a specific k-point :math:`\delta` term. I.e. only save the :math:`\delta` term for
           the given k-point. May be combined with `E` for a specific k and energy point.
        E : float, optional
           an energy dependent :math:`\delta` term. I.e. only save the :math:`\delta` term for
           the given energy. May be combined with `k` for a specific k and energy point.
        """
        # Ensure finalization
        delta.finalize()

        # Ensure that the geometry is written
        self.write_geometry(delta.geom)

        self._crt_dim(self, 'spin', len(delta.spin))

        # Determine the type of delta we are storing...
        k = kwargs.get('k', None)
        E = kwargs.get('E', None)

        ilvl, ik, iE = self._get_lvl_k_E(**kwargs)
        lvl = self._add_lvl(ilvl)

        # Append the sparsity pattern
        # Create basis group
        if 'n_col' in lvl.variables:
            if len(lvl.dimensions['nnzs']) != delta.nnz:
                raise ValueError(
                    "The sparsity pattern stored in delta *MUST* be equivalent for "
                    "all delta entries [nnz].")
            if np.any(lvl.variables['n_col'][:] != delta._csr.ncol[:]):
                raise ValueError(
                    "The sparsity pattern stored in delta *MUST* be equivalent for "
                    "all delta entries [n_col].")
            if np.any(lvl.variables['list_col'][:] != delta._csr.col[:] + 1):
                raise ValueError(
                    "The sparsity pattern stored in delta *MUST* be equivalent for "
                    "all delta entries [list_col].")
            if np.any(lvl.variables['isc_off'][:] != delta.geometry.sc.sc_off):
                raise ValueError(
                    "The sparsity pattern stored in delta *MUST* be equivalent for "
                    "all delta entries [sc_off].")
        else:
            self._crt_dim(lvl, 'nnzs', delta.nnz)
            v = self._crt_var(lvl, 'n_col', 'i4', ('no_u', ))
            v.info = "Number of non-zero elements per row"
            v[:] = delta._csr.ncol[:]
            v = self._crt_var(lvl,
                              'list_col',
                              'i4', ('nnzs', ),
                              chunksizes=(delta.nnz, ),
                              **self._cmp_args)
            v.info = "Supercell column indices in the sparse format"
            v[:] = delta._csr.col[:] + 1  # correct for fortran indices
            v = self._crt_var(lvl, 'isc_off', 'i4', ('n_s', 'xyz'))
            v.info = "Index of supercell coordinates"
            v[:] = delta.geometry.sc.sc_off[:, :]

        warn_E = True
        if ilvl in [3, 4]:
            if iE < 0:
                # We need to add the new value
                iE = lvl.variables['E'].shape[0]
                lvl.variables['E'][iE] = E * eV2Ry
                warn_E = False

        warn_k = True
        if ilvl in [2, 4]:
            if ik < 0:
                ik = lvl.variables['kpt'].shape[0]
                lvl.variables['kpt'][ik, :] = k
                warn_k = False

        if ilvl == 4 and warn_k and warn_E and False:
            # As soon as we have put the second k-point and the first energy
            # point, this warning will proceed...
            # I.e. even though the variable has not been set, it will WARN
            # Hence we out-comment this for now...
            warn(
                SileWarning(
                    'Overwriting k-point {0} and energy point {1} correction.'.
                    format(ik, iE)))
        elif ilvl == 3 and warn_E:
            warn(
                SileWarning(
                    'Overwriting energy point {0} correction.'.format(iE)))
        elif ilvl == 2 and warn_k:
            warn(SileWarning('Overwriting k-point {0} correction.'.format(ik)))

        if ilvl == 1:
            dim = ('spin', 'nnzs')
            sl = [slice(None)] * 2
            csize = [1] * 2
        elif ilvl == 2:
            dim = ('nkpt', 'spin', 'nnzs')
            sl = [slice(None)] * 3
            sl[0] = ik
            csize = [1] * 3
        elif ilvl == 3:
            dim = ('ne', 'spin', 'nnzs')
            sl = [slice(None)] * 3
            sl[0] = iE
            csize = [1] * 3
        elif ilvl == 4:
            dim = ('nkpt', 'ne', 'spin', 'nnzs')
            sl = [slice(None)] * 4
            sl[0] = ik
            sl[1] = iE
            csize = [1] * 4

        # Number of non-zero elements
        csize[-1] = delta.nnz

        if delta.dtype.kind == 'c':
            v1 = self._crt_var(lvl,
                               'Redelta',
                               'f8',
                               dim,
                               chunksizes=csize,
                               attr={
                                   'info': "Real part of delta",
                                   'unit': "Ry"
                               },
                               **self._cmp_args)
            v2 = self._crt_var(lvl,
                               'Imdelta',
                               'f8',
                               dim,
                               chunksizes=csize,
                               attr={
                                   'info': "Imaginary part of delta",
                                   'unit': "Ry"
                               },
                               **self._cmp_args)
            for i in range(len(delta.spin)):
                sl[-2] = i
                v1[sl] = delta._csr._D[:, i].real * eV2Ry
                v2[sl] = delta._csr._D[:, i].imag * eV2Ry

        else:
            v = self._crt_var(lvl,
                              'delta',
                              'f8',
                              dim,
                              chunksizes=csize,
                              attr={
                                  'info': "delta",
                                  'unit': "Ry"
                              },
                              **self._cmp_args)
            for i in range(len(delta.spin)):
                sl[-2] = i
                v[sl] = delta._csr._D[:, i] * eV2Ry
Exemplo n.º 27
0
Arquivo: delta.py Projeto: lwk205/sisl
    def _read_class(self, cls, **kwargs):
        """ Reads a class model from a file """

        # Ensure that the geometry is written
        geom = self.read_geometry()

        # Determine the type of delta we are storing...
        E = kwargs.get('E', None)

        ilvl, ik, iE = self._get_lvl_k_E(**kwargs)

        # Get the level
        lvl = self._get_lvl(ilvl)

        if iE < 0 and ilvl in [3, 4]:
            raise ValueError(
                "Energy {0} eV does not exist in the file.".format(E))
        if ik < 0 and ilvl in [2, 4]:
            raise ValueError("k-point requested does not exist in the file.")

        if ilvl == 1:
            sl = [slice(None)] * 2
        elif ilvl == 2:
            sl = [slice(None)] * 3
            sl[0] = ik
        elif ilvl == 3:
            sl = [slice(None)] * 3
            sl[0] = iE
        elif ilvl == 4:
            sl = [slice(None)] * 4
            sl[0] = ik
            sl[1] = iE

        # Now figure out what data-type the delta is.
        if 'Redelta' in lvl.variables:
            # It *must* be a complex valued Hamiltonian
            is_complex = True
            dtype = np.complex128
        elif 'delta' in lvl.variables:
            is_complex = False
            dtype = np.float64

        # Get number of spins
        nspin = len(self.dimensions['spin'])

        # Now create the sparse matrix stuff (we re-create the
        # array, hence just allocate the smallest amount possible)
        C = cls(geom, nspin, nnzpr=1, dtype=dtype, orthogonal=True)

        C._csr.ncol = _a.arrayi(lvl.variables['n_col'][:])
        # Update maximum number of connections (in case future stuff happens)
        C._csr.ptr = np.insert(_a.cumsumi(C._csr.ncol), 0, 0)
        C._csr.col = _a.arrayi(lvl.variables['list_col'][:]) - 1

        # Copy information over
        C._csr._nnz = len(C._csr.col)
        C._csr._D = np.empty([C._csr.ptr[-1], nspin], dtype)
        if is_complex:
            for ispin in range(nspin):
                sl[-2] = ispin
                C._csr._D[:, ispin].real = lvl.variables['Redelta'][sl] * Ry2eV
                C._csr._D[:, ispin].imag = lvl.variables['Imdelta'][sl] * Ry2eV
        else:
            for ispin in range(nspin):
                sl[-2] = ispin
                C._csr._D[:, ispin] = lvl.variables['delta'][sl] * Ry2eV

        return C
    def density(self, grid, spinor=None, tol=1e-7, eta=False):
        r""" Expand the density matrix to the charge density on a grid

        This routine calculates the real-space density components on a specified grid.

        This is an *in-place* operation that *adds* to the current values in the grid.

        Note: To calculate :math:`\rho(\mathbf r)` in a unit-cell different from the
        originating geometry, simply pass a grid with a unit-cell different than the originating
        supercell.

        The real-space density is calculated as:

        .. math::
            \rho(\mathbf r) = \sum_{\nu\mu}\phi_\nu(\mathbf r)\phi_\mu(\mathbf r) D_{\nu\mu}

        While for non-collinear/spin-orbit calculations the density is determined from the
        spinor component (`spinor`) by

        .. math::
           \rho_{\boldsymbol\sigma}(\mathbf r) = \sum_{\nu\mu}\phi_\nu(\mathbf r)\phi_\mu(\mathbf r) \sum_\alpha [\boldsymbol\sigma \mathbf \rho_{\nu\mu}]_{\alpha\alpha}

        Here :math:`\boldsymbol\sigma` corresponds to a spinor operator to extract relevant quantities. By passing the identity matrix the total charge is added. By using the Pauli matrix :math:`\boldsymbol\sigma_x`
        only the :math:`x` component of the density is added to the grid (see `Spin.X`).

        Parameters
        ----------
        grid : Grid
           the grid on which to add the density (the density is in ``e/Ang^3``)
        spinor : (2,) or (2, 2), optional
           the spinor matrix to obtain the diagonal components of the density. For un-polarized density matrices
           this keyword has no influence. For spin-polarized it *has* to be either 1 integer or a vector of
           length 2 (defaults to total density).
           For non-collinear/spin-orbit density matrices it has to be a 2x2 matrix (defaults to total density).
        tol : float, optional
           DM tolerance for accepted values. For all density matrix elements with absolute values below
           the tolerance, they will be treated as strictly zeros.
        eta: bool, optional
           show a progressbar on stdout
        """
        try:
            # Once unique has the axis keyword, we know we can safely
            # use it in this routine
            # Otherwise we raise an ImportError
            unique([[0, 1], [2, 3]], axis=0)
        except:
            raise NotImplementedError(
                self.__class__.__name__ +
                '.density requires numpy >= 1.13, either update '
                'numpy or do not use this function!')

        geometry = self.geometry
        # Check that the atomic coordinates, really are all within the intrinsic supercell.
        # If not, it may mean that the DM does not conform to the primary unit-cell paradigm
        # of matrix elements. It complicates things.
        fxyz = geometry.fxyz
        f_min = fxyz.min()
        f_max = fxyz.max()
        if f_min < 0 or 1. < f_max:
            warn(
                self.__class__.__name__ +
                '.density has been passed a geometry where some coordinates are '
                'outside the primary unit-cell. This may potentially lead to problems! '
                'Double check the charge density!')
        del fxyz, f_min, f_max

        # Extract sub variables used throughout the loop
        shape = _a.asarrayi(grid.shape)
        dcell = grid.dcell

        # Sparse matrix data
        csr = self._csr

        # In the following we don't care about division
        # So 1) save error state, 2) turn off divide by 0, 3) calculate, 4) turn on old error state
        old_err = np.seterr(divide='ignore', invalid='ignore')

        # Placeholder for the resulting coefficients
        DM = None
        if self.spin.kind > Spin.POLARIZED:
            if spinor is None:
                # Default to the total density
                spinor = np.identity(2, dtype=np.complex128)
            else:
                spinor = _a.arrayz(spinor)
            if spinor.size != 4 or spinor.ndim != 2:
                raise ValueError(
                    self.__class__.__name__ +
                    '.density with NC/SO spin, requires a 2x2 matrix.')

            DM = _a.emptyz([self.nnz, 2, 2])
            idx = array_arange(csr.ptr[:-1], n=csr.ncol)
            if self.spin.kind == Spin.NONCOLINEAR:
                # non-collinear
                DM[:, 0, 0] = csr._D[idx, 0]
                DM[:, 1, 1] = csr._D[idx, 1]
                DM[:, 1,
                   0] = csr._D[idx,
                               2] - 1j * csr._D[idx, 3]  #TODO check sign here!
                DM[:, 0, 1] = np.conj(DM[:, 1, 0])
            else:
                # spin-orbit
                DM[:, 0, 0] = csr._D[idx, 0] + 1j * csr._D[idx, 4]
                DM[:, 1, 1] = csr._D[idx, 1] + 1j * csr._D[idx, 5]
                DM[:, 1,
                   0] = csr._D[idx,
                               2] - 1j * csr._D[idx, 3]  #TODO check sign here!
                DM[:, 0, 1] = csr._D[idx, 6] + 1j * csr._D[idx, 7]

            # Perform dot-product with spinor, and take out the diagonal real part
            DM = dot(DM, spinor.T)[:, [0, 1], [0, 1]].sum(1).real

        elif self.spin.kind == Spin.POLARIZED:
            if spinor is None:
                spinor = _a.onesd(2)

            elif isinstance(spinor, Integral):
                # extract the provided spin-polarization
                s = _a.zerosd(2)
                s[spinor] = 1.
                spinor = s
            else:
                spinor = _a.arrayd(spinor)

            if spinor.size != 2 or spinor.ndim != 1:
                raise ValueError(
                    self.__class__.__name__ +
                    '.density with polarized spin, requires spinor '
                    'argument as an integer, or a vector of length 2')

            idx = array_arange(csr.ptr[:-1], n=csr.ncol)
            DM = csr._D[idx, 0] * spinor[0] + csr._D[idx, 1] * spinor[1]

        else:
            idx = array_arange(csr.ptr[:-1], n=csr.ncol)
            DM = csr._D[idx, 0]

        # Create the DM csr matrix.
        csrDM = csr_matrix(
            (DM, csr.col[idx], np.insert(np.cumsum(csr.ncol), 0, 0)),
            shape=(self.shape[:2]),
            dtype=DM.dtype)

        # Clean-up
        del idx, DM

        # To heavily speed up the construction of the density we can recreate
        # the sparse csrDM matrix by summing the lower and upper triangular part.
        # This means we only traverse the sparse UPPER part of the DM matrix
        # I.e.:
        #    psi_i * DM_{ij} * psi_j + psi_j * DM_{ji} * psi_i
        # is equal to:
        #    psi_i * (DM_{ij} + DM_{ji}) * psi_j
        # Secondly, to ease the loops we extract the main diagonal (on-site terms)
        # and store this for separate usage
        csr_sum = [None] * geometry.n_s
        no = geometry.no
        primary_i_s = geometry.sc_index([0, 0, 0])
        for i_s in range(geometry.n_s):
            # Extract the csr matrix
            o_start, o_end = i_s * no, (i_s + 1) * no
            csr = csrDM[:, o_start:o_end]
            if i_s == primary_i_s:
                csr_sum[i_s] = triu(csr) + tril(csr, -1).transpose()
            else:
                csr_sum[i_s] = csr

        # Recreate the column-stacked csr matrix
        csrDM = ss_hstack(csr_sum, format='csr')
        del csr, csr_sum

        # Remove all zero elements (note we use the tolerance here!)
        csrDM.data = np.where(np.fabs(csrDM.data) > tol, csrDM.data, 0.)

        # Eliminate zeros and sort indices etc.
        csrDM.eliminate_zeros()
        csrDM.sort_indices()
        csrDM.prune()

        # 1. Ensure the grid has a geometry associated with it
        sc = grid.sc.copy()
        if grid.geometry is None:
            # Create the actual geometry that encompass the grid
            ia, xyz, _ = geometry.within_inf(sc)
            if len(ia) > 0:
                grid.set_geometry(Geometry(xyz, geometry.atom[ia], sc=sc))

        # Instead of looping all atoms in the supercell we find the exact atoms
        # and their supercell indices.
        add_R = _a.zerosd(3) + geometry.maxR()
        # Calculate the required additional vectors required to increase the fictitious
        # supercell by add_R in each direction.
        # For extremely skewed lattices this will be way too much, hence we make
        # them square.
        o = sc.toCuboid(True)
        sc = SuperCell(o._v, origo=o.origo) + np.diag(2 * add_R)
        sc.origo -= add_R

        # Retrieve all atoms within the grid supercell
        # (and the neighbours that connect into the cell)
        IA, XYZ, ISC = geometry.within_inf(sc)

        # Retrieve progressbar
        eta = tqdm_eta(len(IA), self.__class__.__name__ + '.density', 'atom',
                       eta)

        cell = geometry.cell
        atom = geometry.atom
        axyz = geometry.axyz
        a2o = geometry.a2o

        def xyz2spherical(xyz, offset):
            """ Calculate the spherical coordinates from indices """
            rx = xyz[:, 0] - offset[0]
            ry = xyz[:, 1] - offset[1]
            rz = xyz[:, 2] - offset[2]

            # Calculate radius ** 2
            xyz_to_spherical_cos_phi(rx, ry, rz)
            return rx, ry, rz

        def xyz2sphericalR(xyz, offset, R):
            """ Calculate the spherical coordinates from indices """
            rx = xyz[:, 0] - offset[0]
            idx = indices_fabs_le(rx, R)
            ry = xyz[idx, 1] - offset[1]
            ix = indices_fabs_le(ry, R)
            ry = ry[ix]
            idx = idx[ix]
            rz = xyz[idx, 2] - offset[2]
            ix = indices_fabs_le(rz, R)
            ry = ry[ix]
            rz = rz[ix]
            idx = idx[ix]
            if len(idx) == 0:
                return [], [], [], []
            rx = rx[idx]

            # Calculate radius ** 2
            ix = indices_le(rx**2 + ry**2 + rz**2, R**2)
            idx = idx[ix]
            if len(idx) == 0:
                return [], [], [], []
            rx = rx[ix]
            ry = ry[ix]
            rz = rz[ix]
            xyz_to_spherical_cos_phi(rx, ry, rz)
            return idx, rx, ry, rz

        # Looping atoms in the sparse pattern is better since we can pre-calculate
        # the radial parts and then add them.
        # First create a SparseOrbital matrix, then convert to SparseAtom
        spO = SparseOrbital(geometry, dtype=np.int16)
        spO._csr = SparseCSR(csrDM)
        spA = spO.toSparseAtom(dtype=np.int16)
        del spO
        na = geometry.na
        # Remove the diagonal part of the sparse atom matrix
        off = na * primary_i_s
        for ia in range(na):
            del spA[ia, off + ia]

        # Get pointers and delete the atomic sparse pattern
        # The below complexity is because we are not finalizing spA
        csr = spA._csr
        a_ptr = np.insert(_a.cumsumi(csr.ncol), 0, 0)
        a_col = csr.col[array_arange(csr.ptr, n=csr.ncol)]
        del spA, csr

        # Get offset in supercell in orbitals
        off = geometry.no * primary_i_s
        origo = grid.origo
        # TODO sum the non-origo atoms to the csrDM matrix
        #      this would further decrease the loops required.

        # Loop over all atoms in the grid-cell
        for ia, ia_xyz, isc in zip(IA, XYZ - origo.reshape(1, 3), ISC):
            # Get current atom
            ia_atom = atom[ia]
            IO = a2o(ia)
            IO_range = range(ia_atom.no)
            cell_offset = (cell * isc.reshape(3, 1)).sum(0) - origo

            # Extract maximum R
            R = ia_atom.maxR()
            if R <= 0.:
                warn("Atom '{}' does not have a wave-function, skipping atom.".
                     format(ia_atom))
                eta.update()
                continue

            # Retrieve indices of the grid for the atomic shape
            idx = grid.index(ia_atom.toSphere(ia_xyz))

            # Now we have the indices for the largest orbital on the atom

            # Subsequently we have to loop the orbitals and the
            # connecting orbitals
            # Then we find the indices that overlap with these indices
            # First reduce indices to inside the grid-cell
            idx[idx[:, 0] < 0, 0] = 0
            idx[shape[0] <= idx[:, 0], 0] = shape[0] - 1
            idx[idx[:, 1] < 0, 1] = 0
            idx[shape[1] <= idx[:, 1], 1] = shape[1] - 1
            idx[idx[:, 2] < 0, 2] = 0
            idx[shape[2] <= idx[:, 2], 2] = shape[2] - 1

            # Remove duplicates, requires numpy >= 1.13
            idx = unique(idx, axis=0)
            if len(idx) == 0:
                eta.update()
                continue

            # Get real-space coordinates for the current atom
            # as well as the radial parts
            grid_xyz = dot(idx, dcell)

            # Perform loop on connection atoms
            # Allocate the DM_pj arrays
            # This will have a size equal to number of elements times number of
            # orbitals on this atom
            # In this way we do not have to calculate the psi_j multiple times
            DM_io = csrDM[IO:IO + ia_atom.no, :].tolil()
            DM_pj = _a.zerosd([ia_atom.no, grid_xyz.shape[0]])

            # Now we perform the loop on the connections for this atom
            # Remark that we have removed the diagonal atom (it-self)
            # As that will be calculated in the end
            for ja in a_col[a_ptr[ia]:a_ptr[ia + 1]]:
                # Retrieve atom (which contains the orbitals)
                ja_atom = atom[ja % na]
                JO = a2o(ja)
                jR = ja_atom.maxR()
                # Get actual coordinate of the atom
                ja_xyz = axyz(ja) + cell_offset

                # Reduce the ia'th grid points to those that connects to the ja'th atom
                ja_idx, ja_r, ja_theta, ja_cos_phi = xyz2sphericalR(
                    grid_xyz, ja_xyz, jR)

                if len(ja_idx) == 0:
                    # Quick step
                    continue

                # Loop on orbitals on this atom
                for jo in range(ja_atom.no):
                    o = ja_atom.orbital[jo]
                    oR = o.R

                    # Downsize to the correct indices
                    if jR - oR < 1e-6:
                        ja_idx1 = ja_idx.view()
                        ja_r1 = ja_r.view()
                        ja_theta1 = ja_theta.view()
                        ja_cos_phi1 = ja_cos_phi.view()
                    else:
                        ja_idx1 = indices_le(ja_r, oR)
                        if len(ja_idx1) == 0:
                            # Quick step
                            continue

                        # Reduce arrays
                        ja_r1 = ja_r[ja_idx1]
                        ja_theta1 = ja_theta[ja_idx1]
                        ja_cos_phi1 = ja_cos_phi[ja_idx1]
                        ja_idx1 = ja_idx[ja_idx1]

                    # Calculate the psi_j component
                    psi = o.psi_spher(ja_r1,
                                      ja_theta1,
                                      ja_cos_phi1,
                                      cos_phi=True)

                    # Now add this orbital to all components
                    for io in IO_range:
                        DM_pj[io, ja_idx1] += DM_io[io, JO + jo] * psi

                # Temporary clean up
                del ja_idx, ja_r, ja_theta, ja_cos_phi
                del ja_idx1, ja_r1, ja_theta1, ja_cos_phi1, psi

            # Now we have all components for all orbitals connection to all orbitals on atom
            # ia. We simply need to add the diagonal components

            # Loop on the orbitals on this atom
            ia_r, ia_theta, ia_cos_phi = xyz2spherical(grid_xyz, ia_xyz)
            del grid_xyz
            for io in IO_range:
                # Only loop halve the range.
                # This is because: triu + tril(-1).transpose()
                # removes the lower half of the on-site matrix.
                for jo in range(io + 1, ia_atom.no):
                    DM = DM_io[io, off + IO + jo]

                    oj = ia_atom.orbital[jo]
                    ojR = oj.R

                    # Downsize to the correct indices
                    if R - ojR < 1e-6:
                        ja_idx1 = slice(None)
                        ja_r1 = ia_r.view()
                        ja_theta1 = ia_theta.view()
                        ja_cos_phi1 = ia_cos_phi.view()
                    else:
                        ja_idx1 = indices_le(ia_r, ojR)
                        if len(ja_idx1) == 0:
                            # Quick step
                            continue

                        # Reduce arrays
                        ja_r1 = ia_r[ja_idx1]
                        ja_theta1 = ia_theta[ja_idx1]
                        ja_cos_phi1 = ia_cos_phi[ja_idx1]

                    # Calculate the psi_j component
                    DM_pj[io, ja_idx1] += DM * oj.psi_spher(
                        ja_r1, ja_theta1, ja_cos_phi1, cos_phi=True)

                # Calculate the psi_i component
                # Note that this one *also* zeroes points outside the shell
                # I.e. this step is important because it "nullifies" all but points where
                # orbital io is defined.
                psi = ia_atom.orbital[io].psi_spher(ia_r,
                                                    ia_theta,
                                                    ia_cos_phi,
                                                    cos_phi=True)
                DM_pj[io, :] += DM_io[io, off + IO + io] * psi
                DM_pj[io, :] *= psi

            # Temporary clean up
            ja_idx1 = ja_r1 = ja_theta1 = ja_cos_phi1 = None
            del ia_r, ia_theta, ia_cos_phi, psi, DM_io

            # Now add the density
            grid.grid[idx[:, 0], idx[:, 1], idx[:, 2]] += DM_pj.sum(0)

            # Clean-up
            del DM_pj, idx

            eta.update()
        eta.close()

        # Reset the error code for division
        np.seterr(**old_err)
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
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