def pivot(self, elec=None, in_device=False, sort=False): """ Return the pivoting indices for a specific electrode Parameters ---------- elec : str or int the corresponding electrode to return the self-energy from in_device : bool, optional If ``True`` the pivoting table will be translated to the device region orbitals sort : bool, optional Whether the returned indices are sorted. Mostly useful if the self-energies are returned sorted as well. Examples -------- >>> se = tbtsencSileTBtrans(...) # doctest: +SKIP >>> se.pivot() # doctest: +SKIP [3, 4, 6, 5, 2] >>> se.pivot(sort=True) # doctest: +SKIP [2, 3, 4, 5, 6] >>> se.pivot(0) # doctest: +SKIP [2, 3] >>> se.pivot(0, in_device=True) # doctest: +SKIP [4, 0] >>> se.pivot(0, in_device=True, sort=True) # doctest: +SKIP [0, 1] >>> se.pivot(0, sort=True) # doctest: +SKIP [2, 3] """ if elec is None: if in_device and sort: return _a.arangei(self.no_d) pvt = self._value('pivot') - 1 if in_device: # Count number of elements that we need to subtract from each orbital subn = _a.onesi(self.no) subn[pvt] = 0 pvt -= _a.cumsumi(subn)[pvt] elif sort: pvt = np.sort(pvt) return pvt if in_device: pvt = self._value('pivot') - 1 if sort: pvt = np.sort(pvt) # Get electrode pivoting elements se_pvt = self._value('pivot', tree=self._elec(elec)) - 1 if sort: # Sort pivoting indices # Since we know that pvt is also sorted, then # the resulting in_device would also return sorted # indices se_pvt = np.sort(se_pvt) if in_device: # translate to the device indices se_pvt = in1d(pvt, se_pvt, assume_unique=True).nonzero()[0] return se_pvt
def _read_class(self, cls, **kwargs): """ Reads a class model from a file """ # Ensure that the geometry is written geom = self.read_geometry() # Determine the type of dH we are storing... E = kwargs.get('E', None) ilvl, ik, iE = self._get_lvl_k_E(**kwargs) # Get the level lvl = self._get_lvl(ilvl) if iE < 0 and ilvl in [3, 4]: raise ValueError("Energy {0} eV does not exist in the file.".format(E)) if ik < 0 and ilvl in [2, 4]: raise ValueError("k-point requested does not exist in the file.") if ilvl == 1: sl = [slice(None)] * 2 elif ilvl == 2: sl = [slice(None)] * 3 sl[0] = ik elif ilvl == 3: sl = [slice(None)] * 3 sl[0] = iE elif ilvl == 4: sl = [slice(None)] * 4 sl[0] = ik sl[1] = iE # Now figure out what data-type the dH is. if 'RedH' in lvl.variables: # It *must* be a complex valued Hamiltonian is_complex = True dtype = np.complex128 elif 'dH' in lvl.variables: is_complex = False dtype = np.float64 # Now create the tight-binding stuff (we re-create the # array, hence just allocate the smallest amount possible) C = cls(geom, 1, nnzpr=1, dtype=dtype, orthogonal=True) C._csr.ncol = _a.arrayi(lvl.variables['n_col'][:]) # Update maximum number of connections (in case future stuff happens) C._csr.ptr = np.insert(_a.cumsumi(C._csr.ncol), 0, 0) C._csr.col = _a.arrayi(lvl.variables['list_col'][:]) - 1 # Copy information over C._csr._nnz = len(C._csr.col) C._csr._D = np.empty([C._csr.ptr[-1], 1], dtype) if is_complex: C._csr._D[:, 0].real = lvl.variables['RedH'][sl] * Ry2eV C._csr._D[:, 0].imag = lvl.variables['ImdH'][sl] * Ry2eV else: C._csr._D[:, 0] = lvl.variables['dH'][sl] * Ry2eV return C
def lineark(self, ticks=False): """ A 1D array which corresponds to the delta-k values of the path This is meant for plotting Examples -------- >>> p = BandStructure(...) # doctest: +SKIP >>> eigs = Hamiltonian.eigh(p) # doctest: +SKIP >>> for i in range(len(Hamiltonian)): # doctest: +SKIP ... plt.plot(p.lineark(), eigs[:, i]) # doctest: +SKIP >>> p = BandStructure(...) # doctest: +SKIP >>> eigs = Hamiltonian.eigh(p) # doctest: +SKIP >>> lk, kt, kl = p.lineark(True) # doctest: +SKIP >>> plt.xticks(kt, kl) # doctest: +SKIP >>> for i in range(len(Hamiltonian)): # doctest: +SKIP ... plt.plot(lk, eigs[:, i]) # doctest: +SKIP Parameters ---------- ticks : bool, optional if `True` the ticks for the points are also returned lk, xticks, label_ticks, lk = BandStructure.lineark(True) Returns ------- linear_k : The positions in reciprocal space determined by the distance between points k_tick : Linear k-positions of the points, only returned if `ticks` is ``True`` k_label : Labels at `k_tick`, only returned if `ticks` is ``True`` """ # Calculate points k = [self.tocartesian(pnt) for pnt in self.point] # Get difference between points dk = np.diff(k, axis=0) # Calculate the cumultative distance between points k_len = np.insert(_a.cumsumd((dk ** 2).sum(1) ** .5), 0, 0.) xtick = [None] * len(k) # Prepare output array dK = _a.emptyd(len(self)) # short-hand ls = np.linspace xtick = np.insert(_a.cumsumi(self.division), 0, 0) for i in range(len(dk)): n = self.division[i] end = i == len(dk) - 1 dK[xtick[i]:xtick[i+1]] = ls(k_len[i], k_len[i+1], n, dtype=np.float64, endpoint=end) xtick[-1] -= 1 # Get label tick, in case self.name is a single string 'ABCD' label_tick = [a for a in self.name] if ticks: return dK, dK[xtick], label_tick return dK
def _mulliken(self): # Calculate the Mulliken elements # First we re-create the sparse matrix as required for csr_matrix ptr = self._csr.ptr ncol = self._csr.ncol # Indices of non-zero elements idx = array_arange(ptr[:-1], n=ncol) # Create the new pointer array new_ptr = _a.emptyi(len(ptr)) new_ptr[0] = 0 col = self._csr.col[idx] _a.cumsumi(ncol, out=new_ptr[1:]) # The shape of the matrices shape = self.shape[:2] # Create list of charges to be returned Q = list() if self.orthogonal: # We only need the diagonal elements S = csr_matrix(shape, dtype=self.dtype) S.setdiag(1.) for i in range(self.shape[2]): DM = csr_matrix((self._csr._D[idx, i], col, new_ptr), shape=shape) Q.append(DM.multiply(S)) Q[-1].eliminate_zeros() else: # We now what S is and do it element-wise. q = self._csr._D[idx, :-1] * self._csr._D[idx, self.S_idx].reshape( -1, 1) for i in range(q.shape[1]): Q.append(csr_matrix((q[:, i], col, new_ptr), shape=shape)) Q[-1].eliminate_zeros() return Q
def write_geometry(self, geometry): """ Creates the NetCDF file and writes the geometry information """ sile_raise_write(self) # Create initial dimensions self.write_supercell(geometry.sc) self._crt_dim(self, 'no_s', np.prod(geometry.nsc) * geometry.no) self._crt_dim(self, 'no_u', geometry.no) self._crt_dim(self, 'na_u', geometry.na) # Create initial geometry v = self._crt_var(self, 'lasto', 'i4', ('na_u', )) v.info = 'Last orbital of equivalent atom' v = self._crt_var(self, 'xa', 'f8', ('na_u', 'xyz')) v.info = 'Atomic coordinates' v.unit = 'Bohr' # Save stuff self.variables['xa'][:] = geometry.xyz / Bohr2Ang bs = self._crt_grp(self, 'BASIS') b = self._crt_var(bs, 'basis', 'i4', ('na_u', )) b.info = "Basis of each atom by ID" orbs = _a.emptyi([geometry.na]) for ia, a, isp in geometry.iter_species(): b[ia] = isp + 1 orbs[ia] = a.no if a.tag in bs.groups: # Assert the file sizes if bs.groups[a.tag].Number_of_orbitals != a.no: raise ValueError( ('File {}' ' has erroneous data in regards of ' 'of the alreay stored dimensions.').format(self.file)) else: ba = bs.createGroup(a.tag) ba.ID = np.int32(isp + 1) ba.Atomic_number = np.int32(a.Z) ba.Mass = a.mass ba.Label = a.tag ba.Element = a.symbol ba.Number_of_orbitals = np.int32(a.no) # Store the lasto variable as the remaining thing to do self.variables['lasto'][:] = _a.cumsumi(orbs)
def pivot(self, in_device=False, sort=False): """ Pivoting orbitals for the full system Parameters ---------- in_device : bool, optional whether the pivoting elements are with respect to the device region sort : bool, optional whether the pivoting elements are sorted """ if in_device and sort: return _a.arangei(self.no_d) pvt = self._value('pivot') - 1 if in_device: subn = _a.onesi(self.no) subn[pvt] = 0 pvt -= _a.cumsumi(subn)[pvt] elif sort: pvt = np.sort(pvt) return pvt
def pivot(self, elec=None, in_device=False, sort=False): """ Return the pivoting indices for a specific electrode (in the device region) or the device Parameters ---------- elec : str or int the corresponding electrode to return the pivoting indices from in_device : bool, optional If ``True`` the pivoting table will be translated to the device region orbitals. If `sort` is also true, this would correspond to the orbitals directly translated to the geometry ``self.geometry.sub(self.a_dev)``. sort : bool, optional Whether the returned indices are sorted. Mostly useful if you want to handle the device in a non-pivoted order. Examples -------- >>> se = tbtncSileTBtrans(...) >>> se.pivot() [3, 4, 6, 5, 2] >>> se.pivot(sort=True) [2, 3, 4, 5, 6] >>> se.pivot(0) [2, 3] >>> se.pivot(0, in_device=True) [4, 0] >>> se.pivot(0, in_device=True, sort=True) [0, 1] >>> se.pivot(0, sort=True) [2, 3] See Also -------- pivot_down : for the pivot table for electrodes down-folding regions """ if elec is None: if in_device and sort: return _a.arangei(self.no_d) pvt = self._value('pivot') - 1 if in_device: # Count number of elements that we need to subtract from each orbital subn = _a.onesi(self.no) subn[pvt] = 0 pvt -= _a.cumsumi(subn)[pvt] elif sort: pvt = npsort(pvt) return pvt # Get electrode pivoting elements se_pvt = self._value('pivot', tree=self._elec(elec)) - 1 if sort: # Sort pivoting indices # Since we know that pvt is also sorted, then # the resulting in_device would also return sorted # indices se_pvt = npsort(se_pvt) if in_device: pvt = self._value('pivot') - 1 if sort: pvt = npsort(pvt) # translate to the device indices se_pvt = indices(pvt, se_pvt, 0) return se_pvt
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_class(self, cls, **kwargs): """ Reads a class model from a file """ # Ensure that the geometry is written geom = self.read_geometry() # Determine the type of delta we are storing... E = kwargs.get('E', None) ilvl, ik, iE = self._get_lvl_k_E(**kwargs) # Get the level lvl = self._get_lvl(ilvl) if iE < 0 and ilvl in [3, 4]: raise ValueError(f"Energy {E} eV does not exist in the file.") if ik < 0 and ilvl in [2, 4]: raise ValueError("k-point requested does not exist in the file.") if ilvl == 1: sl = [slice(None)] * 2 elif ilvl == 2: sl = [slice(None)] * 3 sl[0] = ik elif ilvl == 3: sl = [slice(None)] * 3 sl[0] = iE elif ilvl == 4: sl = [slice(None)] * 4 sl[0] = ik sl[1] = iE # Now figure out what data-type the delta is. if 'Redelta' in lvl.variables: # It *must* be a complex valued Hamiltonian is_complex = True dtype = np.complex128 elif 'delta' in lvl.variables: is_complex = False dtype = np.float64 # Get number of spins nspin = len(self.dimensions['spin']) # Now create the sparse matrix stuff (we re-create the # array, hence just allocate the smallest amount possible) C = cls(geom, nspin, nnzpr=1, dtype=dtype, orthogonal=True) C._csr.ncol = _a.arrayi(lvl.variables['n_col'][:]) # Update maximum number of connections (in case future stuff happens) C._csr.ptr = np.insert(_a.cumsumi(C._csr.ncol), 0, 0) C._csr.col = _a.arrayi(lvl.variables['list_col'][:]) - 1 # Copy information over C._csr._nnz = len(C._csr.col) C._csr._D = np.empty([C._csr.ptr[-1], nspin], dtype) if is_complex: for ispin in range(nspin): sl[-2] = ispin C._csr._D[:, ispin].real = lvl.variables['Redelta'][sl] * Ry2eV C._csr._D[:, ispin].imag = lvl.variables['Imdelta'][sl] * Ry2eV else: for ispin in range(nspin): sl[-2] = ispin C._csr._D[:, ispin] = lvl.variables['delta'][sl] * Ry2eV # Convert from isc to sisl isc _csr_from_sc_off(C.geometry, lvl.variables['isc_off'][:, :], C._csr) _mat_spin_convert(C) return C