示例#1
0
    def phase(self, method='max', return_indices=False):
        r""" Calculate the Euler angle (phase) for the elements of the state, in the range :math:`]-\pi;\pi]`

        Parameters
        ----------
        method : {'max', 'all'}
           for max, the phase for the element which has the largest absolute magnitude is returned,
           for all, all phases are calculated
        return_indices : bool, optional
           return indices for the elements used when ``method=='max'``
        """
        if method == 'max':
            idx = _argmax(_abs(self.state), 1)
            if return_indices:
                return _phase(self.state[_a.arangei(len(self)), idx]), idx
            return _phase(self.state[_a.arangei(len(self)), idx])
        elif method == 'all':
            return _phase(self.state)
        raise ValueError(f"{self.__class__.__name__}.phase only accepts method in [max, all]")
示例#2
0
文件: _help.py 项目: silsgs/sisl
def _csr_from_sc_off(geom, sc_off, csr):
    """ Internal routine to convert *any* SparseCSR matrix from sisl nsc to siesta nsc """
    nsc = geom.sc.nsc.astype(np.int32)
    sc = geom.sc.__class__([1], nsc=nsc)
    sc.sc_off = sc_off
    from_sc_off = sc.sc_index(geom.sc_off)
    # this transfers the local siesta csr matrix ordering to the geometry ordering
    col_from = (from_sc_off.reshape(-1, 1) * geom.no +
                _a.arangei(geom.no).reshape(1, -1)).ravel()
    _csr_from(col_from, csr)
    def _Pk_dense(self,
                  k=(0, 0, 0),
                  dtype=None,
                  gauge='R',
                  format='csr',
                  _dim=0):
        """ Sparse matrix (``scipy.sparse.csr_matrix``) at `k` for a polarized 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 gauge != 'R':
            raise ValueError('Only the cell vector gauge has been implemented')

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

        if not np.allclose(k, 0.):
            if np.dtype(dtype).kind != 'c':
                raise ValueError(
                    self.__class__.__name__ +
                    " setup at k different from Gamma requires a complex matrix"
                )

        # sparse matrix dimension (self.no)
        V = np.empty([len(self), len(self)], dtype=dtype)

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

        # Now create offsets
        offsets = -_a.arangei(0, len(phases) * self.no, self.no)
        # Do not cast to dtype, that is done below, then we retain precision
        diag = diags(phases, offsets,
                     shape=(self.shape[1], self.shape[0])).toarray()

        V[:, :] = dot(self.tocsr(_dim).toarray(), diag)

        if format == 'array':
            return V
        elif format == 'dense':
            return np.asmatrix(V)
        # It must be a sparse matrix we inquire
        return csr_matrix(V).asformat(format)
示例#4
0
        def func(*args, coords=None, name=method.__name__, **kwargs):
            # xarray specific data (default to function name)
            bz = self._obj

            # retrieve ALL data
            array = array_func(*args, **kwargs)

            # Create coords
            if coords is None:
                coords = [('k', _a.arangei(len(bz)))]
                for i, v in enumerate(array.shape[1:]):
                    coords.append((f"v{i+1}", _a.arangei(v)))
            else:
                coords = list(coords)
                coords.insert(0, ('k', _a.arangei(len(bz))))
                for i in range(1, len(coords)):
                    if isinstance(coords[i], str):
                        coords[i] = (coords[i], _a.arangei(array.shape[i]))
            attrs = {'bz': bz, 'parent': bz.parent}

            return xarray.DataArray(array, coords=coords, name=name, attrs=attrs)
示例#5
0
    def remove(self, idx, axis):
        """ Removes certain indices from a specified axis.

        Works exactly opposite to `sub`.

        Parameters
        ----------
        idx : array_like
           the indices of the grid axis `axis` to be removed
        axis : int
           the axis segment from which we remove all indices `idx`
        """
        ret_idx = np.delete(_a.arangei(self.shape[axis]), _a.asarrayi(idx))
        return self.sub(ret_idx, axis)
示例#6
0
    def sub_part(self, idx, axis, above):
        """ Retains parts of the grid via above/below designations.

        Works exactly opposite to `remove_part`

        Parameters
        ----------
        idx : int
           the index of the grid axis `axis` to be retained
           for ``above=True`` grid[idx:,...]
           for ``above=False`` grid[:idx,...]
        axis : int
           the axis segment from which we retain the indices `idx`
        above: bool
           if ``True`` will retain the grid:
              ``grid[idx:,...]``
           else it will retain the grid:
              ``grid[:idx,...]``
        """
        if above:
            sub = _a.arangei(idx, self.shape[axis])
        else:
            sub = _a.arangei(0, idx)
        return self.sub(sub, axis)
示例#7
0
文件: phonon.py 项目: sofiasanz/sisl
def _displacement(mode, hw, mass):
    """ Real space displacements """
    idx = (hw == 0).nonzero()[0]
    U = mode.copy()
    U[idx, :] = 0.

    # Now create the remaining displacements
    idx = delete(_a.arangei(mode.shape[0]), idx)

    # Generate displacement factor
    factor = _displacement_const / fabs(hw[idx]).reshape(-1, 1) ** 0.5

    U.shape = (mode.shape[0], -1, 3)
    U[idx, :, :] = (mode[idx, :] * factor).reshape(-1, mass.shape[0], 3) / mass.reshape(1, -1, 1) ** 0.5

    return U
示例#8
0
    def swapaxes(self, a, b):
        """ Returns Grid with swapped axis

        If ``swapaxes(0,1)`` it returns the 0 in the 1 values.
        """
        # Create index vector
        idx = _a.arangei(3)
        idx[b] = a
        idx[a] = b
        s = np.copy(self.shape)
        d = self.__sc_geometry_dict()
        d['sc'] = d['sc'].swapaxes(a, b)
        grid = self.__class__(s[idx], bc=self.bc[idx],
                              dtype=self.dtype, **d)
        # We need to force the C-order or we loose the contiguity
        grid.grid = np.copy(np.swapaxes(self.grid, a, b), order='C')
        return grid
示例#9
0
    def change_gauge(self, gauge):
        r""" In-place change of the gauge of the state coefficients

        The two gauges are related through:

        .. math::

            \tilde C_j = e^{i\mathbf k\mathbf r_j} C_j

        where :math:`C_j` and :math:`\tilde C_j` belongs to the ``r`` and ``R`` gauge, respectively.

        Parameters
        ----------
        gauge : {'R', 'r'}
            specify the new gauge for the mode coefficients
        """
        # These calls will fail if the gauge is not specified.
        # In that case it will not do anything
        if self.info.get('gauge', gauge) == gauge:
            # Quick return
            return

        # Update gauge value
        self.info['gauge'] = gauge

        # Check that we can do a gauge transformation
        k = _a.asarrayd(self.info.get('k', [0., 0., 0.]))
        if k.dot(k) <= 0.000000001:
            return

        g = self.parent.geometry
        phase = g.xyz[g.o2a(_a.arangei(g.no)), :] @ (k @ g.rcell)

        try:
            if self.parent.spin.has_noncolinear:
                # for NC/SOC we have a 2x2 spin-box per orbital
                phase = np.repeat(phase, 2)
        except:
            pass

        if gauge == 'r':
            # R -> r gauge tranformation.
            self.state *= exp(-1j * phase).reshape(1, -1)
        elif gauge == 'R':
            # r -> R gauge tranformation.
            self.state *= exp(1j * phase).reshape(1, -1)
示例#10
0
    def real_space_coupling(self, ret_indices=False):
        """ Return the real-space coupling parent where they fold into the parent real-space unit cell

        The resulting parent object only contains the inner-cell couplings for the elements that couple
        out of the real-space matrix.

        Parameters
        ----------
        ret_indices : bool, optional
           if true, also return the atomic indices (corresponding to `real_space_parent`) that encompass the coupling matrix

        Returns
        -------
        parent : parent object only retaining the elements of the atoms that couple out of the primary unit cell
        atom_index : indices for the atoms that couple out of the geometry (`ret_indices`)
        """
        opt = self._options
        s_ax = opt['semi_axis']
        k_ax = opt['k_axis']
        PC = self.parent.tile(max(1, self._unfold[s_ax]), s_ax).tile(self._unfold[k_ax], k_ax)

        # Geometry short-hand
        g = PC.geometry
        # Remove all inner-cell couplings (0, 0, 0) to figure out the
        # elements that couple out of the real-space region
        n = PC.shape[0]
        idx = g.sc.sc_index([0, 0, 0])
        cols = _a.arangei(n) + idx * n
        csr = PC._csr.copy([0]) # we just want the sparse pattern, so forget about the other elements
        csr.delete_columns(cols, keep_shape=True)
        # Now PC only contains couplings along the k and semi-inf directions
        # Extract the connecting orbitals and reduce them to unique atomic indices
        orbs = g.osc2uc(csr.col[array_arange(csr.ptr[:-1], n=csr.ncol)], True)
        atom_idx = g.o2a(orbs, True)
        # Only retain coupling atoms
        PC = PC.sub(atom_idx)

        # Remove all out-of-cell couplings such that we only have inner-cell couplings.
        nsc = PC.nsc.copy()
        nsc[s_ax] = 1
        nsc[k_ax] = 1
        PC.set_nsc(nsc)
        if ret_indices:
            return PC, atom_idx
        return PC
示例#11
0
    def pivot(self, in_device=False, sort=False):
        """ Pivoting orbitals for the full system

        Parameters
        ----------
        in_device : bool, optional
           whether the pivoting elements are with respect to the device region
        sort : bool, optional
           whether the pivoting elements are sorted
        """
        if in_device and sort:
            return _a.arangei(self.no_d)
        pvt = self._value('pivot') - 1
        if in_device:
            subn = _a.onesi(self.no)
            subn[pvt] = 0
            pvt -= _a.cumsumi(subn)[pvt]
        elif sort:
            pvt = np.sort(pvt)
        return pvt
示例#12
0
文件: phonon.py 项目: fyalcin/sisl
    def change_gauge(self, gauge):
        r""" In-place change of the gauge of the mode coefficients

        The two gauges are related through:

        .. math::

            \tilde C_j = e^{i\mathbf k\mathbf r_j} C_j

        where :math:`C_j` belongs to the gauge ``R`` and :math:`\tilde C_j` is in the gauge
        ``r``.

        Parameters
        ----------
        gauge : {'R', 'r'}
            specify the new gauge for the mode coefficients
        """
        # These calls will fail if the gauge is not specified.
        # In that case it will not do anything
        if self.info.get('gauge', gauge) == gauge:
            # Quick return
            return

        # Update gauge value
        self.info['gauge'] = gauge

        # Check that we can do a gauge transformation
        k = _a.asarrayd(self.info.get('k'))
        if (k**2).sum()**0.5 <= 0.000001:
            return

        g = self.parent.geometry
        phase = dot(g.xyz[g.o2a(_a.arangei(g.no)), :], dot(k, g.rcell))

        if gauge == 'r':
            self.state *= np.exp(1j * phase).reshape(1, -1)
        elif gauge == 'R':
            self.state *= np.exp(-1j * phase).reshape(1, -1)
示例#13
0
    def _spectral_column(self, elec):
        # To calculate the full A we simply calculate the
        # G column where the electrode resides
        nb = len(self.btd)
        nbm1 = nb - 1

        # These are the indices in the device (after pivoting)
        # So they refer to the
        idx = self.elec[elec].pvt_dev

        # Find parts we need to calculate
        block1 = (idx.min() < self.btd_cum).nonzero()[0][0]
        block2 = (idx.max() < self.btd_cum).nonzero()[0][0]
        if block1 == block2:
            blocks = [block1]
        else:
            blocks = [block1, block2]

        # We can only have 2 consecutive blocks for
        # a Gamma, so same for BTD
        assert len(blocks) <= 2

        n = len(self.pvt)
        G = np.empty([n, len(idx)], dtype=self._data.A[0].dtype)

        c = np.append(0, self.btd_cum)
        A = self._data.A
        B = self._data.B
        C = self._data.C
        tX = self._data.tX
        tY = self._data.tY
        for b in blocks:
            # Find the indices in the block
            i = idx[c[b] <= idx]
            i = i[i < c[b + 1]].astype(np.int32)

            c_idx = _a.arangei(c[b], c[b + 1]).reshape(-1, 1)
            b_idx = indices_only(c_idx.ravel(), i)

            if b == blocks[0]:
                r_idx = np.arange(len(b_idx))
            else:
                r_idx = np.arange(len(idx) - len(b_idx), len(idx))

            sl = slice(c[b], c[b + 1])
            if b == 0:
                G[sl, r_idx] = inv_destroy(A[b] - C[b + 1] @ tX[b])[:, b_idx]
            elif b == nbm1:
                G[sl, r_idx] = inv_destroy(A[b] - B[b - 1] @ tY[b])[:, b_idx]
            else:
                G[sl, r_idx] = inv_destroy(A[b] - B[b - 1] @ tY[b] - C[b + 1] @ tX[b])[:, b_idx]

            if len(blocks) == 1:
                break

            # Now calculate the thing (above below)
            sl = slice(c[b], c[b + 1])
            if b == blocks[0]:
                # Calculate below
                slp = slice(c[b + 1], c[b + 2])
                G[slp, r_idx] = - tX[b] @ G[sl, r_idx]
            else:
                # Calculate above
                slp = slice(c[b - 1], c[b])
                G[slp, r_idx] = - tY[b] @ G[sl, r_idx]

        # Now we can calculate the Gf column above
        b = blocks[0]
        slp = slice(c[b], c[b + 1])
        for b in range(blocks[0] - 1, -1, -1):
            sl = slice(c[b], c[b + 1])
            G[sl, :] = - tY[b + 1] @ G[slp, :]
            slp = sl

        # All blocks below
        b = blocks[-1]
        slp = slice(c[b], c[b + 1])
        for b in range(blocks[-1] + 1, nb):
            sl = slice(c[b], c[b + 1])
            G[sl, :] = - tX[b - 1] @ G[slp, :]
            slp = sl

        # Now calculate the full spectral function
        return G @ self._data.gamma[elec] @ dagger(G)
示例#14
0
    def pivot(self, elec=None, in_device=False, sort=False):
        """ Return the pivoting indices for a specific electrode (in the device region) or the device

        Parameters
        ----------
        elec : str or int
           the corresponding electrode to return the pivoting indices from
        in_device : bool, optional
           If ``True`` the pivoting table will be translated to the device region orbitals.
           If `sort` is also true, this would correspond to the orbitals directly translated
           to the geometry ``self.geometry.sub(self.a_dev)``.
        sort : bool, optional
           Whether the returned indices are sorted. Mostly useful if you want to handle
           the device in a non-pivoted order.

        Examples
        --------
        >>> se = tbtncSileTBtrans(...)
        >>> se.pivot()
        [3, 4, 6, 5, 2]
        >>> se.pivot(sort=True)
        [2, 3, 4, 5, 6]
        >>> se.pivot(0)
        [2, 3]
        >>> se.pivot(0, in_device=True)
        [4, 0]
        >>> se.pivot(0, in_device=True, sort=True)
        [0, 1]
        >>> se.pivot(0, sort=True)
        [2, 3]

        See Also
        --------
        pivot_down : for the pivot table for electrodes down-folding regions
        """
        if elec is None:
            if in_device and sort:
                return _a.arangei(self.no_d)
            pvt = self._value('pivot') - 1
            if in_device:
                # Count number of elements that we need to subtract from each orbital
                subn = _a.onesi(self.no)
                subn[pvt] = 0
                pvt -= _a.cumsumi(subn)[pvt]
            elif sort:
                pvt = npsort(pvt)
            return pvt

        # Get electrode pivoting elements
        se_pvt = self._value('pivot', tree=self._elec(elec)) - 1
        if sort:
            # Sort pivoting indices
            # Since we know that pvt is also sorted, then
            # the resulting in_device would also return sorted
            # indices
            se_pvt = npsort(se_pvt)

        if in_device:
            pvt = self._value('pivot') - 1
            if sort:
                pvt = npsort(pvt)
            # translate to the device indices
            se_pvt = indices(pvt, se_pvt, 0)
        return se_pvt
示例#15
0
    def green(self, E, dtype=None):
        r""" Calculate the real-space Green function

        The real space Green function is calculated via:

        .. math::
            \mathbf G^\mathcal{R}(E) = \sum_{\mathbf k} \mathbf G_{\mathbf k}(E)

        Parameters
        ----------
        E : float/complex
           energy to evaluate the real-space Green function at
        dtype : numpy.dtype, optional
          the resulting data type, default to ``np.complex128``
        """
        opt = self._options

        # Retrieve integration k-grid
        bz = opt['bz']
        try:
            # If the BZ implements TRS (MonkhorstPack) then force it
            trs = bz._trs
        except:
            trs = opt['trs']

        if dtype is None:
            dtype = complex128

        # Now we are to calculate the real-space self-energy
        if E.imag == 0:
            E = E.real + 1j * opt['eta']

        # Used axes
        s_ax = opt['semi_axis']
        k_ax = opt['k_axis']

        # Calculation options
        # calculate both left and right at the same time.
        SE = self._calc['SE'].self_energy_lr

        # Define Bloch unfolding routine and number of tiles along the semi-inf direction
        unfold = self._unfold.copy()
        tile = unfold[s_ax]
        unfold[s_ax] = 1
        bloch = Bloch(unfold)

        if tile == 1:
            # When not tiling, it can be simplified quite a bit
            M0 = self._calc['SE'].spgeom0
            M0Pk = M0.Pk
            if self.parent.orthogonal:
                # Orthogonal *always* identity
                S0E = identity(len(M0), dtype=dtype) * E
                def _calc_green(k, no, tile, idx0):
                    SL, SR = SE(E, k, dtype=dtype)
                    return inv(S0E - M0Pk(k, dtype=dtype, format='array') - SL - SR, True)
            else:
                M0Sk = M0.Sk
                def _calc_green(k, no, tile, idx0):
                    SL, SR = SE(E, k, dtype=dtype)
                    return inv(M0Sk(k, dtype=dtype, format='array') * E - M0Pk(k, dtype=dtype, format='array') - SL - SR, True)

        else:
            M1 = self._calc['SE'].spgeom1
            M1Pk = M1.Pk
            if self.parent.orthogonal:
                def _calc_green(k, no, tile, idx0):
                    # Calculate left/right self-energies
                    Gf, A2 = SE(E, k, dtype=dtype, bulk=True) # A1 == Gf, because of memory usage
                    B = - M1Pk(k, dtype=dtype, format='array')
                    # C = conjugate(B.T)

                    tY = solve(Gf, conjugate(B.T), True, True)
                    Gf = inv(A2 - dot(B, tY), True)
                    tX = solve(A2, B, True, True)

                    # Since this is the pristine case, we know that
                    # G11 and G22 are the same:
                    #  G = [A1 - C.tX]^-1 == [A2 - B.tY]^-1

                    G = empty([tile, no, tile, no], dtype=dtype)
                    G[idx0, :, idx0, :] = Gf.reshape(1, no, no)
                    for i in range(1, tile):
                        G[idx0[i:], :, idx0[:-i], :] = - dot(tX, G[i-1, :, 0, :]).reshape(1, no, no)
                        G[idx0[:-i], :, idx0[i:], :] = - dot(tY, G[0, :, i-1, :]).reshape(1, no, no)
                    return G.reshape(tile * no, -1)

            else:
                M1Sk = M1.Sk
                def _calc_green(k, no, tile, idx0):
                    Gf, A2 = SE(E, k, dtype=dtype, bulk=True) # A1 == Gf, because of memory usage
                    tY = M1Sk(k, dtype=dtype, format='array') # S
                    tX = M1Pk(k, dtype=dtype, format='array') # H
                    B = tY * E - tX
                    # C = _conj(tY.T) * E - _conj(tX.T)

                    tY = solve(Gf, conjugate(tY.T) * E - conjugate(tX.T), True, True)
                    Gf = inv(A2 - dot(B, tY), True)
                    tX = solve(A2, B, True, True)

                    G = empty([tile, no, tile, no], dtype=dtype)
                    G[idx0, :, idx0, :] = Gf.reshape(1, no, no)
                    for i in range(1, tile):
                        G[idx0[i:], :, idx0[:-i], :] = - dot(tX, G[i-1, :, 0, :]).reshape(1, no, no)
                        G[idx0[:-i], :, idx0[i:], :] = - dot(tY, G[0, :, i-1, :]).reshape(1, no, no)
                    return G.reshape(tile * no, -1)

        # Create functions used to calculate the real-space Green function
        # For TRS we only-calculate +k and average by using G(k) = G(-k)^T
        # The extra arguments is because the internal decorator is actually pretty slow
        # to filter out unused arguments.

        # If using Bloch's theorem we need to wrap the Green function calculation
        # as the method call.
        if len(bloch) > 1:
            def _func_bloch(k, no, tile, idx0, weight=None, parent=None):
                return bloch(_calc_green, k, no=no, tile=tile, idx0=idx0)
        else:
            _func_bloch = _calc_green

        # Tiling indices
        idx0 = _a.arangei(tile)
        no = len(self.parent)

        # calculate the Green function
        G = bz.asaverage().call(_func_bloch, no=no, tile=tile, idx0=idx0)
        if trs:
            # Faster to do it once, than per G
            return (G + G.T) * 0.5
        return G
示例#16
0
    def _green_diag_block(self, idx):
        nb = len(self.btd)
        nbm1 = nb - 1

        # Find parts we need to calculate
        block1 = (idx.min() < self.btd_cum).nonzero()[0][0]
        block2 = (idx.max() < self.btd_cum).nonzero()[0][0]
        if block1 == block2:
            blocks = [block1]
        else:
            blocks = list(range(block1, block2+1))
        assert len(blocks) <= 2

        n = self.btd[blocks].sum()
        G = np.empty([n, len(idx)], dtype=self._data.A[0].dtype)

        btd = self.btd
        c = np.append(0, self.btd_cum)
        A = self._data.A
        B = self._data.B
        C = self._data.C
        tX = self._data.tX
        tY = self._data.tY
        for b in blocks:
            # Find the indices in the block
            i = idx[c[b] <= idx].copy()
            i = i[i < c[b + 1]].astype(np.int32)

            c_idx = _a.arangei(c[b], c[b + 1]).reshape(-1, 1)
            b_idx = indices_only(c_idx.ravel(), i)
            # Subtract the first block to put it only in the sub-part
            c_idx -= c[blocks[0]]

            if b == blocks[0]:
                sl = slice(0, btd[b])
                r_idx = np.arange(len(b_idx))
            else:
                sl = slice(btd[blocks[0]], btd[blocks[0]] + btd[b])
                r_idx = np.arange(len(idx) - len(b_idx), len(idx))

            if b == 0:
                G[sl, r_idx] = inv_destroy(A[b] - C[b + 1] @ tX[b])[:, b_idx]
            elif b == nbm1:
                G[sl, r_idx] = inv_destroy(A[b] - B[b - 1] @ tY[b])[:, b_idx]
            else:
                G[sl, r_idx] = inv_destroy(A[b] - B[b - 1] @ tY[b] - C[b + 1] @ tX[b])[:, b_idx]

            if len(blocks) == 1:
                break

            # Now calculate the thing (below/above)
            if b == blocks[0]:
                # Calculate below
                slp = slice(btd[b], btd[b] + btd[blocks[1]])
                G[slp, r_idx] = - tX[b] @ G[sl, r_idx]
            else:
                # Calculate above
                slp = slice(0, btd[blocks[0]])
                G[slp, r_idx] = - tY[b] @ G[sl, r_idx]

        return blocks, G
示例#17
0
    def write_geometry(self, geometry, dynamic=True, group_species=False):
        r""" Writes the geometry to the contained file

        Parameters
        ----------
        geometry : Geometry
           geometry to be written to the file
        dynamic : None, bool or list, optional
           define which atoms are dynamic in the VASP run (default is True,
           which means all atoms are dynamic).
           If None, the resulting file will not contain any dynamic flags
        group_species: bool, optional
           before writing `geometry` first re-order species to
           have species in consecutive blocks (see `geometry_group`)

        Examples
        --------
        >>> car = carSileVASP('POSCAR', 'w')
        >>> geom = geom.graphene()
        >>> geom.write(car) # regular car without Selective Dynamics
        >>> geom.write(car, dynamic=False) # fix all atoms
        >>> geom.write(car, dynamic=[False, (True, False, True)]) # fix 1st and y coordinate of 2nd

        See Also
        --------
        geometry_group: method used to group atoms together according to their species
        """
        # Check that we can write to the file
        sile_raise_write(self)

        if group_species:
            geometry, idx = self.geometry_group(geometry, ret_index=True)
        else:
            # small hack to allow dynamic
            idx = _a.arangei(len(geometry))

        # LABEL
        self._write('sisl output\n')

        # Scale
        self._write('  1.\n')

        # Write unit-cell
        fmt = ('   ' + '{:18.9f}' * 3) + '\n'
        for i in range(3):
            self._write(fmt.format(*geometry.cell[i]))

        # Figure out how many species
        pt = PeriodicTable()
        s, d = [], []
        ia = 0
        while ia < geometry.na:
            atom = geometry.atoms[ia]
            specie = geometry.atoms.specie[ia]
            ia_end = (np.diff(geometry.atoms.specie[ia:]) != 0).nonzero()[0]
            if len(ia_end) == 0:
                # remaining atoms
                ia_end = geometry.na
            else:
                ia_end = ia + ia_end[0] + 1
            s.append(pt.Z_label(atom.Z))
            d.append(ia_end - ia)
            ia += d[-1]

        fmt = ' {:s}' * len(d) + '\n'
        self._write(fmt.format(*s))
        fmt = ' {:d}' * len(d) + '\n'
        self._write(fmt.format(*d))
        if dynamic is None:
            # We write in direct mode
            dynamic = [None] * len(geometry)

            def todyn(fix):
                return '\n'
        else:
            self._write('Selective dynamics\n')
            b2s = {True: 'T', False: 'F'}

            def todyn(fix):
                if isinstance(fix, bool):
                    return ' {0} {0} {0}\n'.format(b2s[fix])
                return ' {} {} {}\n'.format(b2s[fix[0]], b2s[fix[1]],
                                            b2s[fix[2]])

        self._write('Cartesian\n')

        if isinstance(dynamic, bool):
            dynamic = [dynamic] * len(geometry)

        fmt = '{:18.9f}' * 3
        for ia in geometry:
            self._write(
                fmt.format(*geometry.xyz[ia, :]) + todyn(dynamic[idx[ia]]))
示例#18
0
    def read_scf(self, key="scf", iscf=-1, imd=None, as_dataframe=False):
        r""" Parse SCF information and return a table of SCF information depending on what is requested

        Parameters
        ----------
        key : {'scf', 'ts-scf'}
            parse SCF information from Siesta SCF or TranSiesta SCF
        iscf : int, optional
            which SCF cycle should be stored. If ``-1`` only the final SCF step is stored,
            for None *all* SCF cycles are returned. When `iscf` values queried are not found they
            will be truncated to the nearest SCF step.
        imd: int or None, optional
            whether only a particular MD step is queried, if None, all MD steps are
            parsed and returned. A negative number wraps for the last MD steps.
        as_dataframe: boolean, optional
            whether the information should be returned as a `pandas.DataFrame`. The advantage of this
            format is that everything is indexed and therefore you know what each value means.You can also
            perform operations very easily on a dataframe. 
        """

        #These are the properties that are written in SIESTA scf
        props = ["iscf", "Eharris", "E_KS", "FreeEng", "dDmax", "Ef", "dHmax"]

        if not iscf is None:
            if iscf == 0:
                raise ValueError(
                    f"{self.__class__.__name__}.read_scf requires iscf argument to *not* be 0!"
                )
        if not imd is None:
            if imd == 0:
                raise ValueError(
                    f"{self.__class__.__name__}.read_scf requires imd argument to *not* be 0!"
                )

        def reset_d(d, line):
            if line.startswith('SCF cycle converged'):
                if len(d['data']) > 0:
                    d['_final_iscf'] = 1
            elif line.startswith('SCF cycle continued'):
                d['_final_iscf'] = 0

        if key.lower() == 'scf':

            def parse_next(line, d):
                line = line.strip().replace('*', '0')
                reset_d(d, line)
                if line.startswith('ts-Vha:'):
                    d['ts-Vha'] = float(line.split()[1])
                elif line.startswith('scf:'):
                    d['_found_iscf'] = True
                    if len(line) == 97:
                        # this should be for Efup/dwn
                        # but I think this will fail for as_dataframe (TODO)
                        data = [
                            int(line[5:9]),
                            float(line[9:25]),
                            float(line[25:41]),
                            float(line[41:57]),
                            float(line[57:67]),
                            float(line[67:77]),
                            float(line[77:87]),
                            float(line[87:97])
                        ]
                    elif len(line) == 87:
                        data = [
                            int(line[5:9]),
                            float(line[9:25]),
                            float(line[25:41]),
                            float(line[41:57]),
                            float(line[57:67]),
                            float(line[67:77]),
                            float(line[77:87])
                        ]
                    else:
                        # Populate DATA by splitting
                        data = line.split()
                        data = [int(data[1])] + list(map(float, data[2:]))
                    d['data'] = data

        elif key.lower() == 'ts-scf':
            props.append("ts-Vha")

            def parse_next(line, d):
                line = line.strip().replace('*', '0')
                reset_d(d, line)
                if line.startswith('ts-Vha:'):
                    d['ts-Vha'] = float(line.split()[1])
                elif line.startswith('ts-q:'):
                    data = line.split()[1:]
                    try:
                        d['ts-q'] = list(map(float, data))
                    except:
                        # We are probably reading a device list
                        # ensure that props are appended
                        if props[-1] != data[-1]:
                            props.extend(data)
                        pass
                elif line.startswith('ts-scf:'):
                    d['_found_iscf'] = True
                    if len(line) == 100:
                        data = [
                            int(line[8:12]),
                            float(line[12:28]),
                            float(line[28:44]),
                            float(line[44:60]),
                            float(line[60:70]),
                            float(line[70:80]),
                            float(line[80:90]),
                            float(line[90:100]), d['ts-Vha']
                        ] + d['ts-q']
                    elif len(line) == 90:
                        data = [
                            int(line[8:12]),
                            float(line[12:28]),
                            float(line[28:44]),
                            float(line[44:60]),
                            float(line[60:70]),
                            float(line[70:80]),
                            float(line[80:90]), d['ts-Vha']
                        ] + d['ts-q']
                    else:
                        # Populate DATA by splitting
                        data = line.split()
                        data = [int(data[1])] + list(map(
                            float, data[2:])) + [d['ts-Vha']] + d['ts-q']
                    d['data'] = data

        # A temporary dictionary to hold information while reading the output file
        d = {
            '_found_iscf': False,
            '_final_iscf': 0,
            'data': [],
        }
        md = []
        scf = []
        for line in self:
            parse_next(line, d)
            if d['_found_iscf']:
                d['_found_iscf'] = False
                data = d['data']
                if len(data) == 0:
                    continue

                if iscf is None or iscf < 0:
                    scf.append(data)
                elif data[0] <= iscf:
                    # this ensures we will retain the latest iscf in
                    # case the requested iscf is too big
                    scf = data

            if d['_final_iscf'] == 1:
                d['_final_iscf'] = 2
            elif d['_final_iscf'] == 2:
                d['_final_iscf'] = 0
                data = d['data']
                if len(data) == 0:
                    # this traps the case where we read ts-scf
                    # but find the final scf iteration.
                    # In that case we don't have any data.
                    scf = []
                    continue

                if len(scf) == 0:
                    # this traps cases where final_iscf has
                    # been trickered but we haven't collected anything.
                    # I.e. if key == scf but ts-scf also exists.
                    continue

                # First figure out which iscf we should store
                if iscf is None:  # or iscf > 0
                    # scf is correct
                    pass
                elif iscf < 0:
                    # truncate to 0
                    scf = scf[max(len(scf) + iscf, 0)]

                # Populate md
                md.append(np.array(scf))
                # Reset SCF data
                scf = []

                # In case we wanted a given MD step and it's this one, just stop reading
                # We are going to return the last MD (see below)
                if imd == len(md):
                    break

        # Define the function that is going to convert the information of a MDstep to a Dataset
        if as_dataframe:
            import pandas as pd

            def MDstep_dataframe(scf):
                scf = np.atleast_2d(scf)
                return pd.DataFrame(scf[..., 1:],
                                    index=pd.Index(scf[..., 0].ravel().astype(
                                        np.int32),
                                                   name="iscf"),
                                    columns=props[1:])

        # Now we know how many MD steps there are

        # We will return stuff based on what the user requested
        # For pandas DataFrame this will be dependent
        #  1. all MD steps requested => imd == index, iscf == column (regardless of iscf==none|int)
        #  2. 1 MD step requested => iscf == index

        if imd is None:
            if as_dataframe:
                if len(md) == 0:
                    # return an empty dataframe (with imd as index)
                    return pd.DataFrame(index=pd.Index([], name="imd"),
                                        columns=props)
                # Regardless of what the user requests we will always have imd == index
                # and iscf a column, a user may easily change this.
                df = pd.concat(map(MDstep_dataframe, md),
                               keys=_a.arangei(1,
                                               len(md) + 1),
                               names=["imd"])
                if iscf is not None:
                    df.reset_index("iscf", inplace=True)
                return df

            if iscf is None:
                # since each MD step may be a different number of SCF steps
                # we cannot convert to a dense array
                return md
            return np.array(md)

        # correct imd to ensure we check against the final size
        imd = min(len(md) - 1, max(len(md) + imd, 0))
        if len(md) == 0:
            # no data collected
            if as_dataframe:
                return pd.DataFrame(index=pd.Index([], name="iscf"),
                                    columns=props[1:])
            return np.array(md[imd])

        if imd > len(md):
            raise ValueError(
                f"{self.__class__.__name__}.read_scf could not find requested MD step ({imd})."
            )

        # If a certain imd was requested, get it
        # Remember that if imd is positive, we stopped reading at the moment we reached it
        scf = np.array(md[imd])
        if as_dataframe:
            return MDstep_dataframe(scf)
        return scf
示例#19
0
文件: _help.py 项目: sofiasanz/sisl
def _csr_from(col_from, csr):
    """ Internal routine to convert columns in a SparseCSR matrix """
    # local csr matrix ordering
    col_to = _a.arangei(csr.shape[1])
    csr.translate_columns(col_from, col_to)
示例#20
0
文件: bands.py 项目: sofiasanz/sisl
    def read_data(self, as_dataarray=False):
        """ Returns data associated with the bands file

        Parameters
        --------
        as_dataarray: boolean, optional
            if `True`, the information is returned as an `xarray.DataArray`
            Ticks (if read) are stored as an attribute of the DataArray 
            (under `array.ticks` and `array.ticklabels`)
        """
        band_lines = False

        # Luckily the data is in eV
        Ef = float(self.readline())
        # Read the total length of the path (not used)
        _, _ = map(float, self.readline().split())
        l = self.readline()
        try:
            _, _ = map(float, l.split())
            band_lines = True
        except:
            # We are dealing with a band-points file
            pass

        # orbitals, n-spin, n-k
        if band_lines:
            l = self.readline()
        no, ns, nk = map(int, l.split())

        # Create the data to contain all band points
        b = _a.emptyd([nk, ns, no])

        # for band-lines
        if band_lines:
            k = _a.emptyd([nk])
            for ik in range(nk):
                l = [float(x) for x in self.readline().split()]
                k[ik] = l[0]
                del l[0]
                # Now populate the eigenvalues
                while len(l) < ns * no:
                    l.extend([float(x) for x in self.readline().split()])
                l = _a.arrayd(l)
                l.shape = (ns, no)
                b[ik, :, :] = l[:, :] - Ef
            # Now we need to read the labels for the points
            xlabels = []
            labels = []
            nl = int(self.readline())
            for _ in range(nl):
                l = self.readline().split()
                xlabels.append(float(l[0]))
                labels.append((' '.join(l[1:])).replace("'", ''))
            vals = (xlabels, labels), k, b

        else:
            k = _a.emptyd([nk, 3])
            for ik in range(nk):
                l = [float(x) for x in self.readline().split()]
                k[ik, :] = l[0:3]
                del l[2]
                del l[1]
                del l[0]
                # Now populate the eigenvalues
                while len(l) < ns * no:
                    l.extend([float(x) for x in self.readline().split()])
                l = _a.arrayd(l)
                l.shape = (ns, no)
                b[ik, :, :] = l[:, :] - Ef
            vals = k, b

        if as_dataarray:
            from xarray import DataArray

            ticks = {
                "ticks": xlabels,
                "ticklabels": labels
            } if band_lines else {}

            return DataArray(b,
                             name="Energy",
                             attrs=ticks,
                             coords=[("k", k),
                                     ("spin", _a.arangei(0, b.shape[1])),
                                     ("band", _a.arangei(0, b.shape[2]))])

        return vals
示例#21
0
    def orbital_momentum(self, projection='orbital', method='onsite'):
        r""" Calculate orbital angular momentum on either atoms or orbitals

        Currently this implementation equals the Siesta implementation in that
        the on-site approximation is enforced thus limiting the calculated quantities
        to obey the following conditions:

        1. Same atom
        2. :math:`l>0`
        3. :math:`l_\nu \equiv l_\mu`
        4. :math:`m_\nu \neq m_\mu`
        5. :math:`\zeta_\nu \equiv \zeta_\mu`

        This allows one to sum the orbital angular moments on a per atom site.

        Parameters
        ----------
        projection : {'orbital', 'atom'}
            whether the angular momentum is resolved per atom, or per orbital
        method : {'onsite'}
            method used to calculate the angular momentum

        Returns
        -------
        numpy.ndarray
            orbital angular momentum with the last dimension equalling the :math:`L_x`, :math:`L_y` and :math:`L_z` components
        """
        # Check that the spin configuration is correct
        if not self.spin.is_spinorbit:
            raise ValueError(
                f"{self.__class__.__name__}.orbital_momentum requires a spin-orbit matrix"
            )

        # First we calculate
        orb_lmZ = _a.emptyi([self.no, 3])
        for atom, idx in self.geometry.atoms.iter(True):
            # convert to FIRST orbital index per atom
            oidx = self.geometry.a2o(idx)
            # loop orbitals
            for io, orb in enumerate(atom):
                orb_lmZ[oidx + io, :] = orb.l, orb.m, orb.Z

        # Now we need to calculate the stuff
        DM = self.copy()
        # The Siesta convention *only* calculates contributions
        # in the primary unit-cell.
        DM.set_nsc([1] * 3)
        geom = DM.geometry
        csr = DM._csr

        # The siesta moments are only *on-site* per atom.
        # 1. create a logical index for the matrix elements
        #    that is true for ia-ia interaction and false
        #    otherwise
        idx = repeat(_a.arangei(geom.no), csr.ncol)
        aidx = geom.o2a(idx)

        # Sparse matrix indices for data
        sidx = array_arange(csr.ptr[:-1], n=csr.ncol, dtype=np.int32)
        jdx = csr.col[sidx]
        ajdx = geom.o2a(jdx)

        # Now only take the elements that are *on-site* and which are *not*
        # having the same m quantum numbers (if the orbital index is the same
        # it means they have the same m quantum number)
        #
        # 1. on the same atom
        # 2. l > 0
        # 3. same quantum number l
        # 4. different quantum number m
        # 5. same zeta
        onsite_idx = ((aidx == ajdx) & \
                      (orb_lmZ[idx, 0] > 0) & \
                      (orb_lmZ[idx, 0] == orb_lmZ[jdx, 0]) & \
                      (orb_lmZ[idx, 1] != orb_lmZ[jdx, 1]) & \
                      (orb_lmZ[idx, 2] == orb_lmZ[jdx, 2])).nonzero()[0]
        # clean variables we don't need
        del aidx, ajdx

        # Now reduce arrays to the orbital connections that obey the
        # above criteria
        idx = idx[onsite_idx]
        idx_l = orb_lmZ[idx, 0]
        idx_m = orb_lmZ[idx, 1]
        jdx = jdx[onsite_idx]
        jdx_m = orb_lmZ[jdx, 1]
        sidx = sidx[onsite_idx]

        # Sum the spin-box diagonal imaginary parts
        DM = csr._D[sidx][:, [4, 5]].sum(1)

        # Define functions to calculate L projections
        def La(idx_l, DM, sub):
            if len(sub) == 0:
                return []
            return (idx_l[sub] * (idx_l[sub] + 1) * 0.5)**0.5 * DM[sub]

        def Lb(idx_l, DM, sub):
            if len(sub) == 0:
                return
            return (idx_l[sub] * (idx_l[sub] + 1) - 2)**0.5 * 0.5 * DM[sub]

        def Lc(idx, idx_l, DM, sub):
            if len(sub) == 0:
                return [], []
            sub = sub[idx_l[sub] >= 3]
            if len(sub) == 0:
                return [], []
            return idx[sub], (idx_l[sub] *
                              (idx_l[sub] + 1) - 6)**0.5 * 0.5 * DM[sub]

        # construct for different m
        # in Siesta the spin orbital angular momentum
        # is calculated by swapping i and j indices.
        # This is somewhat confusing to me, so I reversed everything.
        # This will probably add to the confusion when comparing the two
        # Additionally Siesta calculates L for <i|L|j> and then does:
        #    L(:) = [L(3), -L(2), -L(1)]
        # Here we *directly* store the quantities used.
        # Pre-allocate the L_xyz quantity per orbital.
        L = np.zeros([geom.no, 3])
        L0 = L[:, 0]
        L1 = L[:, 1]
        L2 = L[:, 2]

        # Pre-calculate all those which have m_i + m_j == 0
        b = (idx_m + jdx_m == 0).nonzero()[0]
        subtract.at(L2, idx[b], idx_m[b] * DM[b])
        del b

        #   mi == 0
        i_m = idx_m == 0
        #     mj == -1
        sub = logical_and(i_m, jdx_m == -1).nonzero()[0]
        subtract.at(L0, idx[sub], La(idx_l, DM, sub))
        #     mj == 1
        sub = logical_and(i_m, jdx_m == 1).nonzero()[0]
        add.at(L1, idx[sub], La(idx_l, DM, sub))

        #   mi == 1
        i_m = idx_m == 1
        #     mj == -2
        sub = logical_and(i_m, jdx_m == -2).nonzero()[0]
        subtract.at(L0, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 0
        sub = logical_and(i_m, jdx_m == 0).nonzero()[0]
        subtract.at(L1, idx[sub], La(idx_l, DM, sub))
        #     mj == 2
        sub = logical_and(i_m, jdx_m == 2).nonzero()[0]
        add.at(L1, idx[sub], Lb(idx_l, DM, sub))

        #   mi == -1
        i_m = idx_m == -1
        #     mj == -2
        sub = logical_and(i_m, jdx_m == -2).nonzero()[0]
        add.at(L1, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 0
        sub = logical_and(i_m, jdx_m == 0).nonzero()[0]
        add.at(L0, idx[sub], La(idx_l, DM, sub))
        #     mj == 2
        sub = logical_and(i_m, jdx_m == 2).nonzero()[0]
        add.at(L0, idx[sub], Lb(idx_l, DM, sub))

        #   mi == 2
        i_m = idx_m == 2
        #     mj == -3
        sub = logical_and(i_m, jdx_m == -3).nonzero()[0]
        subtract.at(L0, *Lc(idx, idx_l, DM, sub))
        #     mj == -1
        sub = logical_and(i_m, jdx_m == -1).nonzero()[0]
        subtract.at(L0, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 1
        sub = logical_and(i_m, jdx_m == 1).nonzero()[0]
        subtract.at(L1, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 3
        sub = logical_and(i_m, jdx_m == 3).nonzero()[0]
        add.at(L1, *Lc(idx, idx_l, DM, sub))

        #   mi == -2
        i_m = idx_m == -2
        #     mj == -3
        sub = logical_and(i_m, jdx_m == -3).nonzero()[0]
        add.at(L1, *Lc(idx, idx_l, DM, sub))
        #     mj == -1
        sub = logical_and(i_m, jdx_m == -1).nonzero()[0]
        subtract.at(L1, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 1
        sub = logical_and(i_m, jdx_m == 1).nonzero()[0]
        add.at(L0, idx[sub], Lb(idx_l, DM, sub))
        #     mj == 3
        sub = logical_and(i_m, jdx_m == 3).nonzero()[0]
        add.at(L0, *Lc(idx, idx_l, DM, sub))

        #   mi == -3
        i_m = idx_m == -3
        #     mj == -2
        sub = logical_and(i_m, jdx_m == -2).nonzero()[0]
        subtract.at(L1, *Lc(idx, idx_l, DM, sub))
        #     mj == 2
        sub = logical_and(i_m, jdx_m == 2).nonzero()[0]
        add.at(L0, *Lc(idx, idx_l, DM, sub))

        #   mi == 3
        i_m = idx_m == 3
        #     mj == -2
        sub = logical_and(i_m, jdx_m == -2).nonzero()[0]
        subtract.at(L0, *Lc(idx, idx_l, DM, sub))
        #     mj == 2
        sub = logical_and(i_m, jdx_m == 2).nonzero()[0]
        subtract.at(L1, *Lc(idx, idx_l, DM, sub))

        if "orbital" == projection:
            return L
        elif "atom" == projection:
            # Now perform summation per atom
            l = np.zeros([geom.na, 3], dtype=L.dtype)
            add.at(l, geom.o2a(np.arange(geom.no)), L)
            return l
        raise ValueError(
            f"{self.__class__.__name__}.orbital_momentum must define projection to be 'orbital' or 'atom'."
        )