def shift(self, E, DM): r""" Shift the energy density matrix to a common energy by using a reference density matrix This is equal to performing this operation: .. math:: \mathfrak E_\sigma = \mathfrak E_\sigma + E \boldsymbol \rho_\sigma where :math:`\mathfrak E_\sigma` correspond to the spin diagonal components of the energy density matrix and :math:`\boldsymbol \rho_\sigma` is the spin diagonal components of the corresponding density matrix. Parameters ---------- E : float or (2,) the energy (in eV) to shift the energy density matrix, if two values are passed the two first spin-components get shifted individually. DM : DensityMatrix density matrix corresponding to the same geometry """ if not self.spsame(DM): raise SislError(self.__class__.__name__ + '.shift requires the input DM to have ' 'the same sparsity as the shifted object.') E = _a.asarrayd(E) if E.size == 1: E = np.tile(E, 2) if np.abs(E).sum() == 0.: # When the energy is zero, there is no shift return for i in range(min(self.spin.spins, 2)): self._csr._D[:, i] += DM._csr._D[:, i] * E[i]
def find_atom(tag): if atoms is None: return Atom(tag) for atom in atoms: if atom.tag == tag: return atom raise SislError(f'Error when reading the basis for atomic tag: {tag}.')
def write_hamiltonian(self, H, **kwargs): """ Writes the Hamiltonian to a siesta.TSHS file """ # Ensure the Hamiltonian is finalized H.finalize() # Extract the data to pass to the fortran routine cell = H.geom.cell * Ang2Bohr xyz = H.geom.xyz * Ang2Bohr # Pointer to CSR matrix csr = H._csr # Get H and S if H.orthogonal: h = (csr._D * eV2Ry).astype(np.float64, 'C', copy=False) s = csr.diags(1., dim=1) # Ensure all data is correctly formatted (i.e. have the same sparsity pattern s.align(csr) s.finalize() if s.nnz != len(h): raise SislError( "The diagonal elements of your orthogonal Hamiltonian have not been defined, " "this is a requirement.") s = (s._D[:, 0]).astype(np.float64, 'C', copy=False) else: h = (csr._D[:, :H.S_idx] * eV2Ry).astype(np.float64, 'C', copy=False) s = (csr._D[:, H.S_idx]).astype(np.float64, 'C', copy=False) # Ensure shapes (say if only 1 spin) h.shape = (-1, len(H.spin)) s.shape = (-1, ) # Get shorter variants nsc = H.geom.nsc[:] isc = H.geom.sc.sc_off[:, :] # I can't seem to figure out the usage of f2py # Below I get an error if xyz is not transposed and h is transposed, # however, they are both in C-contiguous arrays and this is indeed weird... :( _siesta.write_tshs_hs(self.file, nsc[0], nsc[1], nsc[2], cell.T, xyz.T, H.geom.firsto, csr.ncol, csr.col + 1, h, s, isc.T, nspin=len(H.spin), na_u=H.geom.na, no_u=H.geom.no, nnz=H.nnz)
def write_hamiltonian(self, H, **kwargs): """ Writes the Hamiltonian to a siesta.TSHS file """ csr = H._csr.copy() if csr.nnz == 0: raise SileError( str(self) + '.write_hamiltonian cannot write ' 'a zero element sparse matrix!') # Convert to siesta CSR _csr_to_siesta(H.geometry, csr) # TODO consider removing finalize here! # If tbtrans really needs this, then we should definitely do this # in tbtrans! # I.e. we should probably just do finalize(sort=False) csr.finalize() _mat_spin_convert(csr, H.spin) # Extract the data to pass to the fortran routine cell = H.geometry.cell xyz = H.geometry.xyz # Get H and S if H.orthogonal: h = csr._D.astype(np.float64, 'C', copy=False) s = csr.diags(1., dim=1) # Ensure all data is correctly formatted (i.e. have the same sparsity pattern s.align(csr) s.finalize() if s.nnz != len(h): raise SislError( 'The diagonal elements of your orthogonal Hamiltonian ' 'have not been defined, this is a requirement.') s = (s._D[:, 0]).astype(np.float64, 'C', copy=False) else: h = csr._D[:, :H.S_idx].astype(np.float64, 'C', copy=False) s = csr._D[:, H.S_idx].astype(np.float64, 'C', copy=False) # Ensure shapes (say if only 1 spin) h.shape = (-1, len(H.spin)) s.shape = (-1, ) # Get shorter variants nsc = H.geometry.nsc[:].astype(np.int32) isc = _siesta.siesta_sc_off(*nsc) # I can't seem to figure out the usage of f2py # Below I get an error if xyz is not transposed and h is transposed, # however, they are both in C-contiguous arrays and this is indeed weird... :( _siesta.write_tshs_hs(self.file, nsc[0], nsc[1], nsc[2], cell.T, xyz.T, H.geometry.firsto, csr.ncol, csr.col + 1, h, s, isc) _bin_check(self, 'write_hamiltonian', 'could not write Hamiltonian and overlap matrix.')
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 _call(self, *args, **kwargs): data_axis = kwargs.pop('data_axis', None) grid_unit = kwargs.pop('grid_unit', 'b') func = getattr(self.parent, self._bz_attr) wrap = allow_kwargs('parent', 'k', 'weight')(kwargs.pop('wrap', _do_nothing)) eta = tqdm_eta(len(self), self.__class__.__name__ + '.asgrid', 'k', kwargs.pop('eta', False)) parent = self.parent k = self.k w = self.weight # Extract information from the MP grid, these values # define the Grid size, etc. diag = self._diag.copy() if not np.all(self._displ == 0): raise SislError(self.__class__.__name__ + '.{} requires the displacement to be 0 for all k-points.'.format(self._bz_attr)) displ = self._displ.copy() size = self._size.copy() steps = size / diag if self._centered: offset = np.where(diag % 2 == 0, steps, steps / 2) else: offset = np.where(diag % 2 == 0, steps / 2, steps) # Instead of doing # _in_primitive(k) + 0.5 - offset # we can do it here # _in_primitive(k) + offset' offset -= 0.5 # Check the TRS direction trs_axis = self._trs _in_primitive = self.in_primitive _rint = np.rint _int32 = np.int32 def k2idx(k): # In case TRS is applied two indices may be returned return _rint((_in_primitive(k) - offset) / steps).astype(_int32) # To find the opposite k-point, do this # idx[i] = [diag[i] - idx[i] - 1, idx[i] # with i in [0, 1, 2] # Create cell from the reciprocal cell. if grid_unit == 'b': cell = np.diag(self._size) else: cell = parent.sc.rcell * self._size.reshape(1, -1) / units('Ang', grid_unit) # Find the grid origo origo = -(cell * 0.5).sum(0) # Calculate first k-point (to get size and dtype) v = wrap(func(*args, k=k[0], **kwargs), parent=parent, k=k[0], weight=w[0]) if data_axis is None: if v.size != 1: raise SislError(self.__class__.__name__ + '.{} requires one value per-kpoint because of the 3D grid values'.format(self._bz_attr)) else: # Check the weights weights = self.grid(diag[data_axis], displ[data_axis], size[data_axis], centered=self._centered, trs=trs_axis == data_axis)[1] # Correct the Grid size diag[data_axis] = len(v) # Create the orthogonal cell direction to ensure it is orthogonal # Since array axis is cyclic for negative numbers, we simply do this cell[data_axis, :] = cross(cell[data_axis-1, :], cell[data_axis-2, :]) # Check whether we should rotate it if cart2spher(cell[data_axis, :])[2] > pi / 4: cell[data_axis, :] *= -1 # Correct cell for the grid if trs_axis >= 0: origo[trs_axis] = 0. # Correct offset since we only have the positive halve if self._diag[trs_axis] % 2 == 0 and not self._centered: offset[trs_axis] = steps[trs_axis] / 2 else: offset[trs_axis] = 0. # Find number of points if trs_axis != data_axis: diag[trs_axis] = len(self.grid(diag[trs_axis], displ[trs_axis], size[trs_axis], centered=self._centered, trs=True)[1]) # Create the grid in the reciprocal cell sc = SuperCell(cell, origo=origo) grid = Grid(diag, sc=sc, dtype=v.dtype) if data_axis is None: grid[k2idx(k[0])] = v else: idx = k2idx(k[0]).tolist() weight = weights[idx[data_axis]] idx[data_axis] = slice(None) grid[idx] = v * weight del v # Now perform calculation if data_axis is None: for i in range(1, len(k)): grid[k2idx(k[i])] = wrap(func(*args, k=k[i], **kwargs), parent=parent, k=k[i], weight=w[i]) eta.update() else: for i in range(1, len(k)): idx = k2idx(k[i]).tolist() weight = weights[idx[data_axis]] idx[data_axis] = slice(None) grid[idx] = wrap(func(*args, k=k[i], **kwargs), parent=parent, k=k[i], weight=w[i]) * weight eta.update() eta.close() return grid
def replace(self, k, mp): r""" Replace a k-point with a new set of k-points from a Monkhorst-Pack grid This method tries to replace an area corresponding to `mp.size` around the k-point `k` such that the k-points are replaced. This enables one to zoom in on specific points in the Brillouin zone for detailed analysis. Parameters ---------- k : array_like k-point in this object to replace mp : MonkhorstPack object containing the replacement k-points. Examples -------- This example creates a zoomed-in view of the :math:`\Gamma`-point by replacing it with a 3x3x3 Monkhorst-Pack grid. >>> sc = SuperCell(1.) >>> mp = MonkhorstPack(sc, [3, 3, 3]) >>> mp.replace([0, 0, 0], MonkhorstPack(sc, [3, 3, 3], size=1./3)) This example creates a zoomed-in view of the :math:`\Gamma`-point by replacing it with a 4x4x4 Monkhorst-Pack grid. >>> sc = SuperCell(1.) >>> mp = MonkhorstPack(sc, [3, 3, 3]) >>> mp.replace([0, 0, 0], MonkhorstPack(sc, [4, 4, 4], size=1./3)) This example creates a zoomed-in view of the :math:`\Gamma`-point by replacing it with a 4x4x1 Monkhorst-Pack grid. >>> sc = SuperCell(1.) >>> mp = MonkhorstPack(sc, [3, 3, 3]) >>> mp.replace([0, 0, 0], MonkhorstPack(sc, [4, 4, 1], size=1./3)) Raises ------ SislError : if the size of the replacement `MonkhorstPack` grid is not compatible with the k-point spacing in this object. """ # First we find all k-points within k +- mp.size # Those are the points we wish to remove. # Secondly we need to ensure that the k-points we remove are occupying *exactly* # the Brillouin zone we wish to replace. if not isinstance(mp, MonkhorstPack): raise ValueError('Object `mp` is not a MonkhorstPack object') # We can easily figure out the BZ that each k-point is averaging k_vol = self._size / self._diag # Compare against the size of this one # Since we can remove more than one k-point, we require that the # size of the replacement MP is an integer multiple of the # k-point volumes. k_int = mp._size / k_vol if not np.allclose(np.rint(k_int), k_int): raise SislError(self.__class__.__name__ + '.reduce could not replace k-point, BZ ' 'volume replaced is not equivalent to the inherent k-point volume.') k_int = np.rint(k_int) # 1. find all k-points k = self.in_primitive(k).reshape(1, 3) dk = (mp._size / 2).reshape(1, 3) # Find all points within [k - dk; k + dk] # Since the volume of each k-point is non-zero we know that no k-points will be located # on the boundary. # This does remove boundary points because we shift everything into the positive # plane. diff_k = self.in_primitive(self.k % 1. - k % 1.) idx = np.logical_and.reduce(np.abs(diff_k) <= dk, axis=1).nonzero()[0] if len(idx) == 0: raise SislError(self.__class__.__name__ + '.reduce could not find any points to replace.') # Now we have the k-points we need to remove # Figure out if the total weight is consistent total_weight = self.weight[idx].sum() replace_weight = mp.weight.sum() if abs(total_weight - replace_weight) < 1e-8: weight_factor = 1. elif abs(total_weight - replace_weight * 2) < 1e-8: weight_factor = 2. if self._trs < 0: info(self.__class__.__name__ + '.reduce assumes that the replaced k-point has double weights.') else: print('k-point to replace:') print(' ', k.ravel()) print('delta-k:') print(' ', dk.ravel()) print('Found k-indices that will be replaced:') print(' ', idx) print('k-points replaced:') print(self.k[idx, :]) raise SislError(self.__class__.__name__ + '.reduce could not assert the weights are consistent during replacement.') self._k = np.delete(self._k, idx, axis=0) self._w = np.delete(self._w, idx) # Append the new k-points and weights self._k = np.concatenate((self._k, mp._k), axis=0) self._w = np.concatenate((self._w, mp._w * weight_factor))