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})" )
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)
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
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)
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
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)
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)
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
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)
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])
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
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
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()
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)
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
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)
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)
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)
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
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)
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)
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)
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)
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)
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
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
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
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)