Beispiel #1
0
    def __init__(self,
                 sc,
                 atol=1.e-5,
                 offset=(0., 0., 0.),
                 foffset=(0., 0., 0.)):
        if isinstance(sc, SuperCellChild):
            sc = sc.sc
        elif not isinstance(sc, SuperCell):
            sc = SuperCell(sc)

        # Unit-cell to fractionalize
        self._cell = sc.cell.copy()
        # lengths of lattice vectors
        self._length = sc.length.copy().reshape(1, 3)
        # inverse cell (for fractional coordinate calculations)
        self._icell = cell_invert(self._cell)
        # absolute tolerance [Ang]
        self._atol = atol
        # offset of coordinates before calculating the fractional coordinates
        self._offset = _a.arrayd(offset).reshape(1, 3)
        # fractional offset before comparing to the integer part of the fractional coordinate
        self._foffset = _a.arrayd(foffset).reshape(1, 3)

        super().__init__(
            f"fracsite(atol={self._atol}, offset={self._offset}, foffset={self._foffset})"
        )
Beispiel #2
0
    def __init__(self, parent, point, division, name=None):
        super(BandStructure, self).__init__(parent)

        # Copy over points
        self.point = _a.arrayd(point)

        # If the array has fewer points we try and determine
        if self.point.shape[1] < 3:
            if self.point.shape[1] != np.sum(self.parent.nsc > 1):
                raise ValueError('Could not determine the non-periodic direction')

            # fix the points where there are no periodicity
            for i in [0, 1, 2]:
                if self.parent.nsc[i] == 1:
                    self.point = np.insert(self.point, i, 0., axis=1)

        # Ensure the shape is correct
        self.point.shape = (-1, 3)

        # Now figure out what to do with the divisions
        if isinstance(division, Integral):

            # Calculate points (we need correct units for distance)
            kpts = [self.tocartesian(pnt) for pnt in self.point]
            if len(kpts) == 2:
                dists = [sum(np.diff(kpts, axis=0) ** 2) ** .5]
            else:
                dists = sum(np.diff(kpts, axis=0)**2, axis=1) ** .5
            dist = sum(dists)

            div = np.floor(dists / dist * division).astype(dtype=np.int32)
            n = sum(div)
            if n < division:
                div[-1] +=1
                n = sum(div)
            while n < division:
                # Get the separation of k-points
                delta = dist / n

                idx = np.argmin(dists - delta * div)
                div[idx] += 1

                n = sum(div)

            division = div[:]

        self.division = _a.arrayi(division)
        self.division.shape = (-1,)

        if name is None:
            self.name = 'ABCDEFGHIJKLMNOPQRSTUVXYZ'[:len(self.point)]
        else:
            self.name = name

        self._k = _a.arrayd([k for k in self])
        self._w = np.ones(len(self.k)) / len(self.k)
Beispiel #3
0
    def _get_lvl_k_E(self, **kwargs):
        """ Return level, k and E indices, in that order.

        The indices are negative if a new index needs to be created.
        """
        # Determine the type of dH we are storing...
        k = kwargs.get('k', None)
        if k is not None:
            k = _a.asarrayd(k).flatten()
        E = kwargs.get('E', None)

        if (k is None) and (E is None):
            ilvl = 1
        elif (k is not None) and (E is None):
            ilvl = 2
        elif (k is None) and (E is not None):
            ilvl = 3
            # Convert to Rydberg
            E = E * eV2Ry
        elif (k is not None) and (E is not None):
            ilvl = 4
            # Convert to Rydberg
            E = E * eV2Ry

        try:
            lvl = self._get_lvl(ilvl)
        except:
            return ilvl, -1, -1

        # Now determine the energy and k-indices
        iE = -1
        if ilvl in [3, 4]:
            if lvl.variables['E'].size != 0:
                Es = _a.arrayd(lvl.variables['E'][:])
                iE = np.argmin(np.abs(Es - E))
                if abs(Es[iE] - E) > 0.0001:
                    iE = -1

        ik = -1
        if ilvl in [2, 4]:
            if lvl.variables['kpt'].size != 0:
                kpt = _a.arrayd(lvl.variables['kpt'][:])
                kpt.shape = (-1, 3)
                ik = np.argmin(np.abs(kpt - k[None, :]).sum(axis=1))
                if not np.allclose(kpt[ik, :], k, atol=0.0001):
                    ik = -1

        return ilvl, ik, iE
Beispiel #4
0
    def _read_geometry_atomic(self, line, species=None):
        """ Wrapper for reading the geometry as in the outcoor output """
        species = _ensure_species(species)

        # Now we have outcoor
        Ang = 'Ang' in line

        # Read in data
        xyz = []
        atom = []
        line = self.readline()
        while len(line.strip()) > 0:
            line = line.split()
            xyz.append([float(x) for x in line[1:4]])
            atom.append(species[int(line[4]) - 1])
            line = self.readline()

        # Retrieve the unit-cell (but do not skip file-descriptor position)
        # This is because the current unit-cell is not always written.
        pos = self.fh.tell()
        cell = self._read_supercell_outcell()
        self.fh.seek(pos, os.SEEK_SET)

        # Convert xyz
        xyz = _a.arrayd(xyz)
        if not Ang:
            xyz *= Bohr2Ang

        return Geometry(xyz, atom, sc=cell)
Beispiel #5
0
    def in_primitive(k):
        """ Move the k-point into the primitive point(s) ]-0.5 ; 0.5]

        Parameters
        ----------
        k : array_like
           k-point(s) to move into the primitive cell

        Returns
        -------
        k : all k-points moved into the primitive cell
        """
        k = _a.arrayd(k) % 1.

        # Ensure that we are in the interval ]-0.5; 0.5]
        idx = (k.ravel() > 0.5).nonzero()[0]
        while len(idx) > 0:
            k[np.unravel_index(idx, k.shape)] -= 1.
            idx = (k.ravel() > 0.5).nonzero()[0]

        idx = (k.ravel() < -0.5).nonzero()[0]
        while len(idx) > 0:
            k[np.unravel_index(idx, k.shape)] += 1.
            idx = (k.ravel() < -0.5).nonzero()[0]

        return k
Beispiel #6
0
    def unfold_points(self, k):
        r""" Return a list of k-points to be evaluated for this objects unfolding

        The k-point `k` is with respect to the unfolded geometry.
        The return list of `k` points are the k-points required to be sampled in the
        folded geometry.

        Parameters
        ----------
        k : (3,) of float
           k-point evaluation corresponding to the unfolded unit-cell

        Returns
        -------
        k_unfold
            a list of ``np.prod(self.bloch)`` k-points used for the unfolding
        """
        k = _a.arrayd(k)

        # Create expansion points
        B = self._bloch
        unfold = _a.emptyd([B[2], B[1], B[0], 3])
        # Use B-casting rules (much simpler)
        unfold[:, :, :, 0] = (aranged(B[0]).reshape(1, 1, -1) + k[0]) / B[0]
        unfold[:, :, :, 1] = (aranged(B[1]).reshape(1, -1, 1) + k[1]) / B[1]
        unfold[:, :, :, 2] = (aranged(B[2]).reshape(-1, 1, 1) + k[2]) / B[2]
        # Back-transform shape
        return unfold.reshape(-1, 3)
Beispiel #7
0
    def _r_supercell_outcell(self):
        """ Wrapper for reading the unit-cell from the outcoor block """

        # Read until outcell is found
        found, line = self.step_to("outcell: Unit cell vectors")
        if not found:
            raise ValueError(
                f"{self.__class__.__name__}._r_supercell_outcell did not find outcell key"
            )

        Ang = 'Ang' in line

        # We read the unit-cell vectors (in Ang)
        cell = []
        line = self.readline()
        while len(line.strip()) > 0:
            line = line.split()
            cell.append([float(x) for x in line[:3]])
            line = self.readline()

        cell = _a.arrayd(cell)

        if not Ang:
            cell *= Bohr2Ang

        return SuperCell(cell)
Beispiel #8
0
    def read_geometry(self, *args, **kwargs):
        """ Returns `Geometry` object from this file """
        sc = self.read_supercell()

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

        # Create list with correct number of orbitals
        lasto = _a.arrayi(np.copy(self.lasto) + 1)
        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
Beispiel #9
0
    def read_basis(self):
        """ Returns data associated with the ion.xml file """
        # Get the element-tree
        ET = ElementTree(None, self.file)
        root = ET.getroot()

        # Get number of orbitals
        label = root.find('label').text.strip()
        Z = int(root.find('z').text)  # atomic number
        mass = float(root.find('mass').text)

        # Read in the PAO's
        paos = root.find('paos')

        # Now loop over all orbitals
        orbital = []

        # All orbital data
        Bohr2Ang = unit_convert('Bohr', 'Ang')
        for orb in paos:

            n = int(orb.get('n'))
            l = int(orb.get('l'))
            z = int(orb.get('z'))  # zeta

            q0 = float(orb.get('population'))

            P = not int(orb.get('ispol')) == 0

            # Radial components
            rad = orb.find('radfunc')
            npts = int(rad.find('npts').text)

            # Grid spacing in Bohr (conversion is done later
            # because the normalization is easier)
            delta = float(rad.find('delta').text)

            # Read in data to a list
            dat = list(map(float, rad.find('data').text.split()))

            # Since the readed data has fewer significant digits we
            # might as well re-create the table of the radial component.
            r = aranged(npts) * delta

            # To get it per Ang**3
            # TODO, check that this is correct.
            # The fact that we have to have it normalized means that we need
            # to convert psi /sqrt(Bohr**3) -> /sqrt(Ang**3)
            # \int psi^\dagger psi == 1
            psi = arrayd(dat[1::2]) * r**l / Bohr2Ang**(3. / 2.)

            # Create the sphericalorbital and then the atomicorbital
            sorb = SphericalOrbital(l, (r * Bohr2Ang, psi), q0)

            # This will be -l:l (this is the way siesta does it)
            orbital.extend(sorb.toAtomicOrbital(n=n, Z=z, P=P))

        # Now create the atom and return
        return Atom(Z, orbital, mass=mass, tag=label)
Beispiel #10
0
    def solve_lagrange(self):
        r""" Calculate the coefficients according to Pulay's method, return everything + Lagrange multiplier """
        hist = self.history
        n_h = len(hist)
        metric = self._metric

        if n_h == 0:
            # Externally the coefficients should reflect the weight per previous iteration.
            # The mixing weight is an additional parameter
            return _a.arrayd([1.]), 100.
        elif n_h == 1:
            return _a.arrayd([1.]), metric(hist[0][-1], hist[0][-1])

        # Initialize the matrix to be solved against
        B = _a.emptyd([n_h + 1, n_h + 1])

        # Fill matrix B
        for i in range(n_h):
            ei = hist[i][-1]
            B[i, i] = metric(ei, ei)
            for j in range(i + 1, n_h):
                ej = hist[j][-1]

                B[i, j] = metric(ei, ej)
                B[j, i] = B[i, j]
        B[:, n_h] = 1.
        B[n_h, :] = 1.
        B[n_h, n_h] = 0.

        # Although B contains 1 and a number on the order of
        # number of elements (self._hist.size), it seems very
        # numerically stable.

        # Create RHS
        RHS = _a.zerosd(n_h + 1)
        RHS[-1] = 1

        try:
            # Apparently we cannot use assume_a='sym'
            # Is this because sym also implies positive definitiness?
            # However, these are matrices of order ~30, so we don't care
            c = solve(B, RHS)
            return c[:-1], -c[-1]
        except np.linalg.LinAlgError as e:
            # We have a LinalgError
            return _a.arrayd([1.]), metric(hist[-1][-1], hist[-1][-1])
Beispiel #11
0
 def return_forces(Fs):
     # Handle cases where we can't now if they are found
     if Fs is None: return None
     Fs = _a.arrayd(Fs)
     if max and total:
         return (Fs[..., :-1], Fs[..., -1])
     elif max and not all:
         return Fs.ravel()[0]
     return Fs
Beispiel #12
0
    def read_supercell(self):
        """ Returns `SuperCell` object from this file """
        cell = _a.arrayd(np.copy(self.cell))
        cell.shape = (3, 3)

        nsc = self._value('nsc')
        sc = SuperCell(cell, nsc=nsc)
        sc.sc_off = self._value('isc_off')
        return sc
Beispiel #13
0
    def __init__(self, parent, k=None, weight=None):
        self.set_parent(parent)

        # Gamma point
        if k is None:
            self._k = _a.zerosd([1, 3])
            self._w = _a.onesd(1)
        else:
            self._k = _a.arrayd(k).reshape(-1, 3)
            if weight is None:
                n = self._k.shape[0]
                self._w = _a.onesd(n) / n
            else:
                self._w = _a.arrayd(weight).ravel()
        if len(self.k) != len(self.weight):
            raise ValueError(self.__class__.__name__ + '.__init__ requires input k-points and weights to be of equal length.')

        # Instantiate the array call
        self.asarray()
Beispiel #14
0
        def next_stress():
            line = self.readline()
            while not ('siesta: Stress tensor' in line and key in line):
                line = self.readline()
                if line == '':
                    return None

            # Now read data
            S = []
            for _ in range(3):
                line = self.readline().split()
                S.append([float(x) for x in line[-3:]])

            return _a.arrayd(S)
Beispiel #15
0
    def read_supercell(self):
        """ Returns the `SuperCell` object from this file """
        cell = _a.arrayd(np.copy(self._value('cell')))
        cell.shape = (3, 3)

        nsc = self._value('nsc')
        sc = SuperCell(cell, nsc=nsc)
        try:
            sc.sc_off = self._value('isc_off')
        except:
            # This is ok, we simply do not have the supercell offsets
            pass

        return sc
Beispiel #16
0
        def next_force():

            line = self.readline()
            while not 'siesta: Atomic forces' in line:
                line = self.readline()
                if line == '':
                    return None

            # Now read data
            F = []
            line = self.readline()
            if 'siesta:' in line:
                # This is the final summary, we don't need to read it as it does not contain new information
                # and also it make break things since max forces are not written there
                return None

            # First, we encounter the atomic forces
            while '---' not in line:
                line = line.split()
                if not (total or max):
                    F.append([float(x) for x in line[-3:]])
                line = self.readline()
                if line == '':
                    break

            line = self.readline()
            # Then, the total forces
            if total:
                F = [float(x) for x in line.split()[-3:]]

            line = self.readline()
            #And after that we can read the max force
            if max and len(line.split()) != 0:
                line = self.readline()
                maxF = float(line.split()[1])

                # In case total is also requested, we are going to store it all in the same variable
                # It will be separated later
                if total:
                    F = (*F, maxF)
                else:
                    F = maxF

            return _a.arrayd(F)
Beispiel #17
0
    def _r_geometry_omx(self, *args, **kwargs):
        """ Returns `Geometry` """
        sc = self.read_supercell(order=['omx'])

        na = self.get('Atoms.Number', default=0)
        conv = self.get('Atoms.SpeciesAndCoordinates.Unit', default='Ang')
        data = self.get('Atoms.SpeciesAndCoordinates')
        if data is None:
            raise SislError('Cannot find key: Atoms.SpeciesAndCoordinates')

        if na == 0:
            # Default to the size of the labels
            na = len(data)

        # Reduce to the number of atoms.
        data = data[:na]

        atoms = self.read_basis(order=['omx'])

        def find_atom(tag):
            if atoms is None:
                return Atom(tag)
            for atom in atoms:
                if atom.tag == tag:
                    return atom
            raise SislError(
                'Error when reading the basis for atomic tag: {}.'.format(tag))

        xyz = []
        atom = []
        for dat in data:
            d = dat.split()
            atom.append(find_atom(d[1]))
            xyz.append(list(map(float, dat.split()[2:5])))
        xyz = _a.arrayd(xyz)

        if conv == 'AU':
            xyz *= units('Bohr', 'Ang')
        elif conv == 'FRAC':
            xyz = np.dot(xyz, sc.cell)

        return Geometry(xyz, atom=atom, sc=sc)
Beispiel #18
0
    def __init__(self, cell, nsc=None, origo=None):

        if nsc is None:
            nsc = [1, 1, 1]

        # If the length of cell is 6 it must be cell-parameters, not
        # actual cell coordinates
        self.cell = self.tocell(cell)

        if origo is None:
            self._origo = _a.zerosd(3)
        else:
            self._origo = _a.arrayd(origo)
            if self._origo.size != 3:
                raise ValueError("Origo *must* be 3 numbers.")

        # Set the volume
        self._update_vol()

        self.nsc = _a.onesi(3)
        # Set the super-cell
        self.set_nsc(nsc=nsc)
Beispiel #19
0
    def _read_geometry_outcoor(self, line, species=None):
        """ Wrapper for reading the geometry as in the outcoor output """
        species = _ensure_species(species)

        # Now we have outcoor
        scaled = 'scaled' in line
        fractional = 'fractional' in line
        Ang = 'Ang' in line

        # Read in data
        xyz = []
        spec = []
        line = self.readline()
        while len(line.strip()) > 0:
            line = line.split()
            xyz.append([float(x) for x in line[:3]])
            spec.append(int(line[3]))
            line = self.readline()

        # in outcoor we know it is always just after
        cell = self._read_supercell_outcell()
        xyz = _a.arrayd(xyz)

        # Now create the geometry
        if scaled:
            # The output file for siesta does not
            # contain the lattice constant.
            # So... :(
            raise ValueError(
                "Could not read the lattice-constant for the scaled geometry")
        elif fractional:
            xyz = xyz.dot(cell.cell)
        elif not Ang:
            xyz *= Bohr2Ang

        # Assign the correct species
        geom = Geometry(xyz, [species[ia - 1] for ia in spec], sc=cell)

        return geom
Beispiel #20
0
    def _read_supercell_outcell(self):
        """ Wrapper for reading the unit-cell from the outcoor block """

        # Read until outcell is found
        line = self.readline()
        while not 'outcell: Unit cell vectors' in line:
            line = self.readline()

        Ang = 'Ang' in line

        # We read the unit-cell vectors (in Ang)
        cell = []
        line = self.readline()
        while len(line.strip()) > 0:
            line = line.split()
            cell.append([float(x) for x in line[:3]])
            line = self.readline()

        cell = _a.arrayd(cell)

        if not Ang:
            cell *= Bohr2Ang

        return SuperCell(cell)
Beispiel #21
0
    def _r_geometry_outcoor(self, line, atoms=None):
        """ Wrapper for reading the geometry as in the outcoor output """
        atoms_order = _ensure_atoms(atoms)

        # Now we have outcoor
        scaled = 'scaled' in line
        fractional = 'fractional' in line
        Ang = 'Ang' in line

        # Read in data
        xyz = []
        atoms = []
        line = self.readline()
        while len(line.strip()) > 0:
            line = line.split()
            xyz.append([float(x) for x in line[:3]])
            atoms.append(atoms_order[int(line[3]) - 1])
            line = self.readline()

        # in outcoor we know it is always just after
        cell = self._r_supercell_outcell()
        xyz = _a.arrayd(xyz)

        # Now create the geometry
        if scaled:
            # The output file for siesta does not
            # contain the lattice constant.
            # So... :(
            raise ValueError(
                "Could not read the lattice-constant for the scaled geometry")
        elif fractional:
            xyz = xyz.dot(cell.cell)
        elif not Ang:
            xyz *= Bohr2Ang

        return Geometry(xyz, atoms, sc=cell)
Beispiel #22
0
    def __init__(self, parent, nkpt, displacement=None, size=None, centered=True, trs=True):
        super(MonkhorstPack, self).__init__(parent)

        if isinstance(nkpt, Integral):
            nkpt = np.diag([nkpt] * 3)
        elif isinstance(nkpt[0], Integral):
            nkpt = np.diag(nkpt)

        # Now we have a matrix of k-points
        if np.any(nkpt - np.diag(np.diag(nkpt)) != 0):
            raise NotImplementedError(self.__class__.__name__ + " with off-diagonal components is not implemented yet")

        if displacement is None:
            displacement = np.zeros(3, np.float64)
        elif isinstance(displacement, Real):
            displacement = np.zeros(3, np.float64) + displacement

        if size is None:
            size = _a.onesd(3)
        elif isinstance(size, Real):
            size = _a.zerosd(3) + size
        else:
            size = _a.arrayd(size)

        # Retrieve the diagonal number of values
        Dn = np.diag(nkpt).astype(np.int32)
        if np.any(Dn) == 0:
            raise ValueError(self.__class__.__name__ + ' *must* be initialized with '
                             'diagonal elements different from 0.')

        i_trs = -1
        if trs:
            # Figure out which direction to TRS
            nmax = 0
            for i in [0, 1, 2]:
                if displacement[i] in [0., 0.5] and Dn[i] > nmax:
                    nmax = Dn[i]
                    i_trs = i
            if nmax == 1:
                i_trs = -1
            if i_trs == -1:
                # If we still haven't decided (say for weird displacements)
                # simply take the one with the maximum number of k-points.
                i_trs = np.argmax(Dn)

        # Calculate k-points and weights along all directions
        kw = [self.grid(Dn[i], displacement[i], size[i], centered, i == i_trs) for i in (0, 1, 2)]

        self._k = _a.emptyd((kw[0][0].size, kw[1][0].size, kw[2][0].size, 3))
        self._w = _a.onesd(self._k.shape[:-1])
        for i in (0, 1, 2):
            k = kw[i][0].reshape(-1, 1, 1)
            w = kw[i][1].reshape(-1, 1, 1)
            self._k[..., i] = np.rollaxis(k, 0, i + 1)
            self._w[...] *= np.rollaxis(w, 0, i + 1)

        del kw
        self._k.shape = (-1, 3)
        self._k = np.where(self._k > .5, self._k - 1, self._k)
        self._w.shape = (-1,)

        # Store information regarding size and diagonal elements
        # This information is basically only necessary when
        # we want to replace special k-points
        self._diag = Dn # vector
        self._displ = displacement # vector
        self._size = size # vector
        self._centered = centered
        self._trs = i_trs
    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)
Beispiel #24
0
    def read_moment(self, orbital=False, quantity='S', last=True, all=False):
        """ Reads the moments from the Siesta output file
        These will only be present in case of spin-orbit coupling.

        Parameters
        ----------
        orbital: bool, False
           return a table with orbitally resolved
           moments.
        quantity: str, 'S'
           return the spin-moments or the L moments
        last: bool, True
           only read the last force
        all: bool, False
           return a list of all forces (like an MD)
           If `True` `last` is ignored
        """

        # Read until outcoor is found
        itt = iter(self)
        while not 'moments: Atomic' in next(itt):
            if next(itt) == '':
                return None

        # The moments are printed in SPECIES list
        next(itt)  # empty
        next(itt)  # empty

        na = 0
        # Loop the species
        tbl = []
        # Read the species label
        while True:
            next(itt)  # ""
            next(itt)  # Atom    Orb ...
            # Loop atoms in this species list
            while True:
                line = next(itt)
                if line.startswith('Species') or \
                   line.startswith('--'):
                    break
                line = ' '
                atom = []
                ia = 0
                while not line.startswith('--'):
                    line = next(itt).split()
                    if ia == 0:
                        ia = int(line[0])
                    elif ia != int(line[0]):
                        raise ValueError("Error in moments formatting.")
                    # Track maximum number of atoms
                    na = max(ia, na)
                    if quantity == 'S':
                        atom.append([float(x) for x in line[4:7]])
                    elif quantity == 'L':
                        atom.append([float(x) for x in line[7:10]])
                line = next(itt).split()  # Total ...
                if not orbital:
                    ia = int(line[0])
                    if quantity == 'S':
                        atom.append([float(x) for x in line[4:7]])
                    elif quantity == 'L':
                        atom.append([float(x) for x in line[8:11]])
                tbl.append((ia, atom))
            if line.startswith('--'):
                break

        # Sort according to the atomic index
        moments = [] * na

        # Insert in the correct atomic
        for ia, atom in tbl:
            moments[ia - 1] = atom

        if not all:
            return _a.arrayd(moments)
        return moments
    def read_data(self, as_dataarray=False):
        r""" Returns data associated with the PDOS file

        For spin-polarized calculations the returned values are up/down, orbitals, energy.
        For non-collinear calculations the returned values are sum/x/y/z, orbitals, energy.

        Parameters
        ----------
        as_dataarray: bool, optional
           If True the returned PDOS is a `xarray.DataArray` with energy, spin
           and orbital information as coordinates in the data.
           The geometry, unit and Fermi level are stored as attributes in the
           DataArray.

        Returns
        -------
        geom : Geometry instance with positions, atoms and orbitals. The
               orbitals of these atoms are `AtomicOrbital` instances.
        E : the energies at which the PDOS has been evaluated at (if the Fermi-level is present the energies
            are shifted to :math:`E - E_F = 0`, this will *only* be done from Siesta 4.0.2 and later).
        PDOS : an array of DOS, for non-polarized calculations it has dimension ``(atom.no, len(E))``,
               else it has dimension ``(nspin, atom.no, len(E))``.
        DataArray : if `as_dataarray` is True, only this data array is returned, in this case
               all data can be post-processed using the `xarray` selection routines.
        """
        # Get the element-tree
        ET = ElementTree('pdos', self.file)
        root = ET.getroot()

        # Get number of orbitals
        nspin = int(root.find('nspin').text)
        # Try and find the fermi-level
        Ef = root.find('fermi_energy')
        E = arrayd(list(map(float, root.find('energy_values').text.split())))
        if Ef is not None:
            Ef = float(Ef.text)
            E -= Ef
        ne = len(E)

        # All coordinate, atoms and species data
        xyz = []
        atoms = []
        atom_species = []

        def ensure_size(ia):
            while len(atom_species) <= ia:
                atom_species.append(None)
                xyz.append(None)

        def ensure_size_orb(ia, i):
            while len(atoms) <= ia:
                atoms.append([])
            while len(atoms[ia]) <= i:
                atoms[ia].append(None)

        if nspin == 4:

            def process(D):
                tmp = np.empty(D.shape[0], D.dtype)
                tmp[:] = D[:, 3]
                D[:, 3] = D[:, 0] - D[:, 1]
                D[:, 0] = D[:, 0] + D[:, 1]
                D[:, 1] = D[:, 2]
                D[:, 2] = tmp[:]
                return D
        else:

            def process(D):
                return D

        if as_dataarray:
            import xarray as xr
            if nspin == 1:
                spin = ['sum']
            elif nspin == 2:
                spin = ['up', 'down']
            elif nspin == 4:
                spin = ['sum', 'x', 'y' 'z']

            # Dimensions of the PDOS data-array
            dims = ['E', 'spin', 'n', 'l', 'm', 'zeta', 'polarization']

            shape = (ne, nspin, 1, 1, 1, 1, 1)

            def to(o, DOS):
                # Coordinates for this dataarray
                coords = [E, spin, [o.n], [o.l], [o.m], [o.Z], [o.P]]

                return xr.DataArray(data=process(DOS).reshape(shape),
                                    dims=dims,
                                    coords=coords,
                                    name='PDOS')

        else:

            def to(o, DOS):
                return process(DOS)

        D = []
        for orb in root.findall('orbital'):

            # Short-hand function to retrieve integers for the attributes
            def oi(name):
                return int(orb.get(name))

            # Get indices
            ia = oi('atom_index') - 1
            i = oi('index') - 1

            species = orb.get('species')

            # Create the atomic orbital
            try:
                Z = oi('Z')
            except:
                try:
                    Z = PeriodicTable().Z(species)
                except:
                    # Unknown
                    Z = -1

            try:
                P = orb.get('P') == 'true'
            except:
                P = False

            ensure_size(ia)
            xyz[ia] = list(map(float, orb.get('position').split()))
            atom_species[ia] = Z

            # Construct the atomic orbital
            O = AtomicOrbital(n=oi('n'), l=oi('l'), m=oi('m'), Z=oi('z'), P=P)

            # We know that the index is far too high. However,
            # this ensures a consecutive orbital
            ensure_size_orb(ia, i)
            atoms[ia][i] = O

            # it is formed like : spin-1, spin-2 (however already in eV)
            DOS = arrayd(list(map(float,
                                  orb.find('data').text.split()))).reshape(
                                      -1, nspin)

            if as_dataarray:
                if len(D) == 0:
                    D = to(O, DOS)
                else:
                    D = D.combine_first(to(O, DOS))
            else:
                D.append(process(DOS))

        # Now we need to parse the data
        # First reduce the atom
        atoms = [[o for o in a if o] for a in atoms]
        atoms = Atoms([Atom(Z, os) for Z, os in zip(atom_species, atoms)])
        geom = Geometry(arrayd(xyz) * Bohr2Ang, atoms)

        if as_dataarray:
            # Add attributes
            D.attrs['geometry'] = geom
            D.attrs['units'] = '1/eV'
            if Ef is None:
                D.attrs['Ef'] = 'Unknown'
            else:
                D.attrs['Ef'] = Ef

            return D

        return geom, E, np.moveaxis(np.stack(D, axis=0), 2, 0)
Beispiel #26
0
    def param_circle(self, sc, N_or_dk, kR, normal, origo, loop=False):
        r""" Create a parameterized k-point list where the k-points are generated on a circle around an origo

        The generated circle is a perfect circle in the reciprocal space (Cartesian coordinates).
        To generate a perfect circle in units of the reciprocal lattice vectors one can
        generate the circle for a diagonal supercell with side-length :math:`2\pi`, see
        example below.

        Parameters
        ----------
        sc : SuperCell, or SuperCellChild
           the supercell used to construct the k-points
        N_or_dk : int
           number of k-points generated using the parameterization (if an integer),
           otherwise it specifies the discretization length on the circle (in 1/Ang),
           If the latter case will use less than 4 points a warning will be raised and
           the number of points increased to 4.
        kR : float
           radius of the k-point. In 1/Ang
        normal : array_like of float
           normal vector to determine the circle plane
        origo : array_like of float
           origo of the circle used to generate the circular parameterization
        loop : bool, optional
           whether the first and last point are equal

        Examples
        --------

        >>> sc = SuperCell([1, 1, 10, 90, 90, 60])
        >>> bz = BrillouinZone.param_circle(sc, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])

        To generate a circular set of k-points in reduced coordinates (reciprocal

        >>> sc = SuperCell([1, 1, 10, 90, 90, 60])
        >>> bz = BrillouinZone.param_circle(sc, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])
        >>> bz_rec = BrillouinZone.param_circle(2*np.pi, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])
        >>> bz.k[:, :] = bz_rec.k[:, :]

        Returns
        -------
        BrillouinZone : with the parameterized k-points.
        """
        if isinstance(N_or_dk, Integral):
            N = N_or_dk
        else:
            # Calculate the required number of points
            N = int(kR ** 2 * np.pi / N_or_dk + 0.5)
            if N < 4:
                N = 4
                info('BrillouinZone.param_circle increased the number of circle points to 4.')

        # Conversion object
        bz = BrillouinZone(sc)

        normal = _a.arrayd(normal)
        origo = _a.arrayd(origo)
        k_n = bz.tocartesian(normal)
        k_o = bz.tocartesian(origo)

        # Generate a preset list of k-points on the unit-circle
        if loop:
            radians = _a.aranged(N) / (N-1) * 2 * np.pi
        else:
            radians = _a.aranged(N) / N * 2 * np.pi
        k = _a.emptyd([N, 3])
        k[:, 0] = np.cos(radians)
        k[:, 1] = np.sin(radians)
        k[:, 2] = 0.

        # Now generate the rotation
        _, theta, phi = cart2spher(k_n)
        if theta != 0:
            pv = _a.arrayd([k_n[0], k_n[1], 0])
            pv /= fnorm(pv)
            q = Quaternion(phi, pv, rad=True) * Quaternion(theta, [0, 0, 1], rad=True)
        else:
            q = Quaternion(0., [0, 0, k_n[2] / abs(k_n[2])], rad=True)

        # Calculate k-points
        k = q.rotate(k)
        k *= kR / fnorm(k).reshape(-1, 1)
        k = bz.toreduced(k + k_o)

        # The sum of weights is equal to the BZ area
        W = np.pi * kR ** 2
        w = np.repeat([W / N], N)

        return BrillouinZone(sc, k, w)
Beispiel #27
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, 'orbital': [-1. for _ in range(no)]})
                    xyz.append([float(f) for f in ls[1:4]])
                    l = self.readline()
                xyz = _a.arrayd(xyz)
                xyz.shape = (-1, 3)
                self.readline()  # step past the block

        # Create geometry with associated supercell and atoms
        geom = Geometry(xyz, atom=Atom[Z], sc=SuperCell(cell, nsc))

        return geom
Beispiel #28
0
    def _r_geometry_multiple(self, steps, ret_data=False, squeeze=False):
        asteps = steps
        steps = dict((step, i) for i, step in enumerate(steps))

        # initialize all things
        cell = [None] * len(steps)
        cell_set = [False] * len(steps)
        xyz_set = [False] * len(steps)
        atom = [None for _ in steps]
        xyz = [None for _ in steps]
        data = [None for _ in steps]
        data_set = [not ret_data for _ in steps]

        line = " "
        all_loaded = False

        while line != '' and not all_loaded:
            line = self.readline()

            if line.isspace():
                continue
            kw = line.split()[0]
            if kw not in ("CONVVEC", "PRIMVEC", "PRIMCOORD"):
                continue

            step = _get_kw_index(line)
            if step != -1 and step not in steps:
                continue

            if step not in steps and step == -1:
                step = idstep = istep = None
            else:
                idstep = steps[step]
                istep = idstep

            if kw == "CONVVEC":
                if step is None:
                    if not any(cell_set):
                        cell_set = [True] * len(cell_set)
                    else:
                        continue
                elif cell_set[istep]:
                    continue
                else:
                    cell_set[istep] = True

                icell = _a.zerosd([3, 3])
                for i in range(3):
                    line = self.readline()
                    icell[i] = line.split()
                if step is None:
                    cell = [icell] * len(cell)
                else:
                    cell[istep] = icell

            elif kw == "PRIMVEC":
                if step is None:
                    cell_set = [True] * len(cell_set)
                else:
                    cell_set[istep] = True

                icell = _a.zerosd([3, 3])
                for i in range(3):
                    line = self.readline()
                    icell[i] = line.split()
                if step is None:
                    cell = [icell] * len(cell)
                else:
                    cell[istep] = icell

            elif kw == "PRIMCOORD":
                if step is None:
                    raise ValueError(f"{self.__class__.__name__}"
                        " contains an unindexed (or somehow malformed) 'PRIMCOORD'"
                        " section but you've asked for a particular index. This"
                        f" shouldn't happen. line:\n {line}"
                    )

                iatom = []
                ixyz = []
                idata = []
                line = self.readline().split()
                for _ in range(int(line[0])):
                    line = self.readline().split()
                    if not xyz_set[istep]:
                        iatom.append(int(line[0]))
                        ixyz.append([float(x) for x in line[1:4]])
                    if ret_data and len(line) > 4:
                        idata.append([float(x) for x in line[4:]])
                if not xyz_set[istep]:
                    atom[istep] = iatom
                    xyz[istep] = ixyz
                    xyz_set[istep] = True
                data[idstep] = idata
                data_set[idstep] = True

            all_loaded = all(xyz_set) and all(cell_set) and all(data_set)

        if not all(xyz_set):
            which = [asteps[i] for i in np.flatnonzero(xyz_set)]
            raise ValueError(f"{self.__class__.__name__} file did not contain atom coordinates for the following requested index: {which}")

        if ret_data:
            data = _a.arrayd(data)
            if data.size == 0:
                data.shape = (len(steps), len(xyz[0]), 0)

        xyz = _a.arrayd(xyz)
        cell = _a.arrayd(cell)
        atom = _a.arrayi(atom)

        geoms = []
        for istep in range(len(steps)):
            if len(atom) == 0:
                geoms.append(si.Geometry(xyz[istep], sc=SuperCell(cell[istep])))
            elif len(atom[0]) == 1 and atom[0][0] == -999:
                # should we perhaps do AtomUnknown?
                geoms.append(None)
            else:
                geoms.append(Geometry(xyz[istep], atoms=atom[istep], sc=SuperCell(cell[istep])))

        if squeeze and len(steps) == 1:
            geoms = geoms[0]
            if ret_data:
                data = data[0]

        if ret_data:
            return geoms, data
        return geoms
Beispiel #29
0
    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
Beispiel #30
0
 def offset(self, isc=None):
     """ Returns the supercell offset of the supercell index """
     if isc is None:
         return _a.arrayd([0, 0, 0])
     return dot(isc, self.cell)