def calc_n(H, q): r""" General method to obtain the spin densities for periodic or finite systems at a given temperature It obtains the spin densities from the direct diagonalization of the Hamiltonian (``H.H``) taking into account a possible overlap matrix (``H.H.S``): .. math:: \langle n_{i\sigma} \rangle = \sum_{\alpha}f_{\alpha\sigma}\sum_{j}c^{\alpha}_{i\sigma}c^{*\alpha}_{j\sigma}S_{ij} Where :math:`f_{\alpha\sigma}` is the weight of eigenstate :math:`\alpha` for spin :math:`\sigma` at temperature ``kT`` (Fermi-Dirac distribution), :math:`c^{\alpha}_{i\sigma}` are coefficients for eigenstate :math:`\alpha` with spin :math:`\sigma` represented in the basis of atomic orbitals Parameters ---------- H: HubbardHamiltonian `hubbard.HubbardHamiltonian` object of the system to obtain the spin-densities from q: array_like charge resolved in spin channels (first index for up-electrons and second index for down-electrons) See Also ------------ sisl.physics.electron.EigenstateElectron.norm2: sisl routine to obtain the dot product of the eigenstates with the overlap matrix """ # Create fermi-level determination distribution dist = sisl.get_distribution('fermi_dirac', smearing=H.kT) Ef = H.H.fermi_level(H.mp, q=q, distribution=dist) if not isinstance(Ef, (tuple, list, np.ndarray)): Ef = np.array([Ef]) dist = [ sisl.get_distribution('fermi_dirac', smearing=H.kT, x0=Ef[s]) for s in range(H.spin_size) ] ni = np.zeros((H.spin_size, H.sites)) Etot = 0 # Solve eigenvalue problems def calc_occ(k, weight, spin): n = np.empty_like(ni) es = H.eigenstate(k, spin=spin) # Reduce to occupied stuff occ = es.occupation(dist[spin]) * weight n = einsum('i,ij->j', occ, es.norm2(False).real) Etot = es.eig.dot(occ) # Return values return n, Etot # Loop k-points and weights for w, k in zip(H.mp.weight, H.mp.k): for s in range(H.spin_size): n, etot = calc_occ(k, w, s) ni[s] += n Etot += etot # Return spin densities and total energy # if the Hamiltonian is not spin-polarized multiply Etot by 2 for spin degeneracy return ni, (2. / H.spin_size) * Etot
def calc_n(H, q): r""" General method to obtain the spin densities for periodic or finite systems at a given temperature It obtains the spin densities from the direct diagonalization of the Hamiltonian (``H.H``) taking into account a possible overlap matrix (``H.H.S``): .. math:: \langle n_{i\sigma} \rangle = \sum_{\alpha}f_{\alpha\sigma}\sum_{j}c^{\alpha}_{i\sigma}c^{*\alpha}_{j\sigma}S_{ij} Where :math:`f_{\alpha\sigma}` is the weight of eigenstate :math:`\alpha` for spin :math:`\sigma` at temperature ``kT`` (Fermi-Dirac distribution), :math:`c^{\alpha}_{i\sigma}` are coefficients for eigenstate :math:`\alpha` with spin :math:`\sigma` represented in the basis of atomic orbitals Parameters ---------- H: HubbardHamiltonian `hubbard.HubbardHamiltonian` object of the system to obtain the spin-densities from q: array_like charge resolved in spin channels (first index for up-electrons and second index for down-electrons) See Also ------------ sisl.physics.electron.EigenstateElectron.norm2: sisl routine to obtain the dot product of the eigenstates with the overlap matrix """ # Create fermi-level determination distribution dist = sisl.get_distribution('fermi_dirac', smearing=H.kT) Ef = H.H.fermi_level(H.mp, q=q, distribution=dist) dist_up = sisl.get_distribution('fermi_dirac', smearing=H.kT, x0=Ef[0]) dist_dn = sisl.get_distribution('fermi_dirac', smearing=H.kT, x0=Ef[1]) ni = np.zeros((2, H.sites)) Etot = 0 # Solve eigenvalue problems def calc_occ(k, weight): n = np.empty_like(ni) es_up = H.eigenstate(k, spin=0) es_dn = H.eigenstate(k, spin=1) # Reduce to occupied stuff occ_up = es_up.occupation(dist_up) * weight n[0] = einsum('i,ij->j', occ_up, es_up.norm2(False).real) occ_dn = es_dn.occupation(dist_dn) * weight n[1] = einsum('i,ij->j', occ_dn, es_dn.norm2(False).real) Etot = es_up.eig.dot(occ_up) + es_dn.eig.dot(occ_dn) # Return values return n, Etot # Loop k-points and weights for w, k in zip(H.mp.weight, H.mp.k): n, etot = calc_occ(k, w) ni += n Etot += etot return ni, Etot
def fermi_level(self, q=[None, None], dist='fermi_dirac'): """ Find the fermi level for a certain charge `q` at a certain `kT` Parameters ---------- q: array_like, optional charge per spin channel. First index for spin up, second index for dn dist: str or sisl.distribution, optional distribution function See Also ------------ sisl.physics.Hamiltonian.fermi_level : sisl class function Returns ------- Ef: numpy.array Fermi-level for each spin channel """ Q = 1 * q for i in (0, 1): if Q[i] is None: Q[i] = self.q[i] if isinstance(dist, str): dist = sisl.get_distribution(dist, smearing=self.kT) Ef = self.H.fermi_level(self.mp, q=Q, distribution=dist) return Ef
def fermi_level(self, q=[None, None], distribution='fermi_dirac'): """ Find the fermi level for a certain charge `q` at a certain `kT` Parameters ---------- q: array_like, optional charge per spin channel. First index for spin up, second index for dn If the Hamiltonian is unpolarized q should have only one component otherwise it will take the first one distribution: str or sisl.distribution, optional distribution function See Also ------------ sisl.physics.Hamiltonian.fermi_level : sisl class function Returns ------- Ef: numpy.array Fermi-level for each spin channel """ Q = 1 * q for i in range(self.spin_size): if Q[i] is None: Q[i] = self.q[i] if isinstance(distribution, str): distribution = sisl.get_distribution(distribution, smearing=self.kT) Ef = self.H.fermi_level(self.mp, q=Q, distribution=distribution) return Ef
def PDOS(self, egrid, eta=1e-3, spin=[0, 1], dist='Lorentzian', eref=0.): """ Obtains the projected density of states (PDOS) of the system with a distribution function Parameters ---------- egrid: array_like Energy grid at which the DOS will be calculated. eta: float, optional Smearing parameter spin: int, optional If spin=0(1) it calculates the DOS for up (down) electrons in the system. If spin is not specified it returns DOS_up + DOS_dn. dist: str or sisl.distribution, optional distribution for the convolution, defaults to Lorentzian eref: float, optional energy reference, defaults to zero See Also ------------ sisl.get_distribution: sisl method to create distribution function sisl.physics.electron.PDOS: sisl method to obtain PDOS Returns ------- PDOS: numpy.ndarray projected density of states at the given energies for the selected spin """ # Ensure spin is iterable if not isinstance(spin, (list, np.ndarray)): spin = [spin] # Check if egrid is numpy.ndarray if not isinstance(egrid, np.ndarray): egrid = np.array(egrid) if isinstance(dist, str): dist = sisl.get_distribution(dist, smearing=eta) else: warnings.warn( "Using distribution created outside this function. The energy reference may be shifted if the distribution is calculated with respect to a non-zero energy value" ) # Obtain PDOS pdos = 0 for ispin in spin: ev, evec = self.eigh(eigvals_only=False, spin=ispin) ev -= eref pdos += sisl.physics.electron.PDOS(egrid, ev, evec.T, distribution=dist) return pdos
def test_wrap_oplist(self, setup): R, param = [0.1, 1.5], [1, 2.1] H = Hamiltonian(setup.g.copy()) H.construct([R, param]) bz = MonkhorstPack(H, [10, 10, 1]) E = np.linspace(-4, 4, 1000) dist = get_distribution('gaussian', smearing=0.05) def wrap(es, parent, k, weight): DOS = es.DOS(E, distribution=dist) PDOS = es.PDOS(E, distribution=dist) vel = es.velocity() * es.occupation().reshape(-1, 1) return oplist([DOS, PDOS, vel]) bz.asaverage() results = bz.eigenstate(wrap=wrap) assert np.allclose(bz.DOS(E, distribution=dist), results[0]) assert np.allclose(bz.PDOS(E, distribution=dist), results[1])
def __init__(self, HubbardHamiltonian, k=[0, 0, 0], spin=0, direction=[1, 0, 0], origo=[0, 0, 0], projection='2D', nx=601, gamma_x=0.5, dx=5.0, dist_x='gaussian', ne=501, gamma_e=0.05, emax=10., dist_e='lorentzian', vmin=0, vmax=None, scale='linear', **kwargs): super().__init__(**kwargs) ev, evec = HubbardHamiltonian.eigh(k=k, eigvals_only=False, spin=spin) ev -= HubbardHamiltonian.find_midgap() xyz = np.array(HubbardHamiltonian.geometry.xyz[:]) # coordinates relative to selected origo xyz -= np.array(origo).reshape(1, 3) # distance along projection axis unitvec = np.array(direction) unitvec = unitvec / unitvec.dot(unitvec)**0.5 coord = xyz.dot(unitvec) xmin, xmax = min(coord) - dx, max(coord) + dx emin, emax = -emax, emax # Broaden along real-space axis x = np.linspace(xmin, xmax, nx) dist_x = sisl.get_distribution(dist_x, smearing=gamma_x) xcoord = x.reshape(-1, 1) - coord.reshape(1, -1) # (nx, natoms) if projection.upper() == '1D': # distance perpendicular to projection axis perp = xyz - coord.reshape(-1, 1) * unitvec perp = np.einsum('ij,ij->i', perp, perp) xcoord = (xcoord**2 + perp)**0.5 DX = dist_x(xcoord) # Broaden along energy axis e = np.linspace(emin, emax, ne) dist_e = sisl.get_distribution(dist_e, smearing=gamma_e) DE = dist_e(e.reshape(-1, 1) - ev.reshape(1, -1)) # (ne, norbs) # Compute DOS prob_dens = np.abs(evec)**2 DOS = DX.dot(prob_dens).dot(DE.T) intdat = np.sum(DOS) * (x[1] - x[0]) * (e[1] - e[0]) print('Integrated LDOS spectrum (states within plot):', intdat, DOS.shape) cm = plt.cm.hot if scale == 'log': if vmin == 0: vmin = 1e-4 norm = colors.LogNorm(vmin=vmin) else: # Linear scale norm = colors.Normalize(vmin=vmin) self.imshow = self.axes.imshow(DOS.T, extent=[xmin, xmax, emin, emax], cmap=cm, \ origin='lower', norm=norm, vmax=vmax) title = f'LDOS projection in {projection.upper()}' if projection.upper() == '1D': title += r': origo [%.2f,%.2f,%.2f] (\AA)' % tuple(origo) self.set_title(title) self.set_xlabel(r'distance along [%.2f,%.2f,%.2f] (\AA)' % tuple(direction)) self.set_ylabel(r'$E-E_\mathrm{midgap}$ (eV)') self.set_xlim(xmin, xmax) self.set_ylim(emin, emax) self.axes.set_aspect('auto')
def __init__(self, Hdev, elec_SE, elec_idx, CC=None, V=0, **kwargs): """ Initialize NEGF class """ # Global charge neutral reference energy (conveniently named fermi) self.Ef = 0. self.kT = Hdev.kT self.eta = 0.1 # Immediately retrieve the distribution dist = sisl.get_distribution('fermi_dirac', smearing=self.kT) if not CC: CC = os.path.split(__file__)[0] + "/EQCONTOUR" # Extract weights and energy points from CC contour_weight = sisl.io.tableSile(CC).read_data() self.CC_eq = np.array([contour_weight[0] + 1j * contour_weight[1]]) self.w_eq = (contour_weight[2] + 1j * contour_weight[3]) / np.pi self.NEQ = V != 0 self.mu = np.zeros(len(elec_SE)) if self.NEQ: # in case the user has WBL electrodes self.mu[0] = V * 0.5 self.mu[1] = -V * 0.5 # Define equilibrium contours self.CC_eq = np.array( [self.CC_eq[0] + self.mu[0], self.CC_eq[0] + self.mu[1]]) # Integration path for the non-Eq window dE = 0.01 self.CC_neq = np.arange( min(self.mu) - 5 * self.kT, max(self.mu) + 5 * self.kT + dE, dE) + 1j * 0.001 # Weights for the non-Eq integrals w_neq = dE * (dist(self.CC_neq.real - self.mu[0]) - dist(self.CC_neq.real - self.mu[1])) # Store weights for calculation of DELTA_L [0] and DELTA_R [1] self.w_neq = np.array([w_neq, -w_neq]) / _pi else: self.CC_neq = np.array([]) def convert2SelfEnergy(elec_SE, mu): if isinstance(elec_SE, (tuple, list)): # here HH *must* be a HubbardHamiltonian (otherwise it will probably crash) HH, semi_inf = elec_SE Ef_elec = HH.fermi_level(q=HH.q, distribution=dist) # Shift each electrode with its Fermi-level and chemical potential # Since the electrodes are *bulk* i.e. the entire electronic structure # is simply shifted HH.shift(-Ef_elec + mu) return sisl.RecursiveSI(HH.H, semi_inf) # If elec_SE is already a SelfEnergy instance # this will work since SelfEnergy instances overloads unknown attributes # to the parent. # This will only shift the electronic structure of the RecursiveSI.spgeom0 # and not RecursiveSI.spgeom1. However, for orthogonal basis, this is equivalent. # TODO this should be changed when non-orthogonal basis' are used try: elec_SE.shift(mu) except: # the parent does not have the shift method pass return elec_SE # convert all matrices to a sisl.SelfEnergy instance self.elec_SE = list(map(convert2SelfEnergy, elec_SE, self.mu)) self.elec_idx = [np.array(idx).reshape(-1, 1) for idx in elec_idx] # Ensure commensurate shapes for SE, idx in zip(self.elec_SE, self.elec_idx): assert len(SE) == len(idx) # For a bias calcualtion, ensure that only the first # two electrodes are RecursiveSI (all others should be WideBandSE) if self.NEQ: for i in range(2): assert isinstance(self.elec_SE[i], sisl.RecursiveSI) for i in range(2, len(self.elec_SE)): assert isinstance(self.elec_SE[i], sisl.WideBandSE) # In case all self-energies are WB, then we can change the eta value if all(map(lambda obj: isinstance(obj, sisl.WideBandSE), self.elec_SE)): # The wide-band limit ensures that all electrons comes at a constant rate per # energy. # Although this *could* potentially be too high, then I think it should be ok # since the electrodes more govern the DOS. self.eta = 1. # spin, electrode self._ef_SE = _nested_list(2, len(self.elec_SE)) # spin, EQ-contour, energy, electrode self._cc_eq_SE = _nested_list(Hdev.spin_size, *self.CC_eq.shape, len(self.elec_SE)) # spin, energy, electrode self._cc_neq_SE = _nested_list(Hdev.spin_size, self.CC_neq.shape[0], len(self.elec_SE)) for i, se in enumerate(self.elec_SE): for spin in range(Hdev.spin_size): # Map self-energy at the Fermi-level of each electrode into the device region self._ef_SE[spin][i] = se.self_energy(1j * self.eta, spin=spin) for cc_eq_i, CC_eq in enumerate(self.CC_eq): for ic, cc in enumerate(CC_eq): # Do it also for each point in the CC, for all EQ CC self._cc_eq_SE[spin][cc_eq_i][ic][i] = se.self_energy( cc, spin=spin) if self.NEQ: for ic, cc in enumerate(self.CC_neq): # And for each point in the Neq CC self._cc_neq_SE[spin][ic][i] = se.self_energy( cc, spin=spin)
# Build zigzag GNR ZGNR = sisl.geom.zgnr(2) # and 3NN TB Hamiltonian H_elec = sp2(ZGNR, t1=2.7, t2=0.2, t3=0.18) # Hubbard Hamiltonian of elecs MFH_elec = HubbardHamiltonian(H_elec, U=U, nkpt=[102, 1, 1], kT=kT) # Initialize spin densities MFH_elec.set_polarization([0], dn=[-1]) # Ensure we break symmetry # Converge Electrode Hamiltonians dn = MFH_elec.converge(density.calc_n, mixer=sisl.mixing.PulayMixer(weight=.7, history=7), tol=1e-10) dist = sisl.get_distribution('fermi_dirac', smearing=kT) Ef_elec = MFH_elec.H.fermi_level(MFH_elec.mp, q=MFH_elec.q, distribution=dist) print("Electrode Ef = ", Ef_elec) # Shift each electrode with its Fermi-level and write it to netcdf file MFH_elec.H.shift(-Ef_elec) MFH_elec.H.write('MFH_elec.nc') # Central region is a repetition of the electrodes without PBC HC = H_elec.tile(3, axis=0) HC.set_nsc([1, 1, 1]) # Map electrodes in the device region elec_indx = [range(len(H_elec)), range(-len(H_elec), 0)] # MFH object MFH_HC = HubbardHamiltonian(HC.H, n=np.tile(MFH_elec.n, 3), U=U, kT=kT)
def __init__(self, HH, E, sites=[], spin=[0, 1], ext_geom=None, realspace=False, eta=1e-3, distribution='lorentzian', **kwargs): # Set default kwargs if realspace: if 'facecolor' not in kwargs: kwargs['facecolor'] = 'None' if 'cmap' not in kwargs: kwargs['cmap'] = 'Greys' else: if 'cmap' not in kwargs: kwargs['cmap'] = plt.cm.bwr super().__init__(HH.geometry, ext_geom=ext_geom, **kwargs) x = HH.geometry[:, 0] y = HH.geometry[:, 1] if realspace: if 'shape' not in kwargs: kwargs['shape'] = [100, 100, 1] if 'vmin' not in kwargs: kwargs['vmin'] = 0 xmin, xmax, ymin, ymax = self.xmin, self.xmax, self.ymin, self.ymax if 'sc' not in kwargs: if 'z' in kwargs: origin = [xmin, ymin, -kwargs['z']] else: raise ValueError( 'Either a SC or the z coordinate to slice the real space grid needs to be passed' ) kwargs['sc'] = sisl.SuperCell([xmax - xmin, ymax - ymin, 1000], origin=origin) if 'axis' not in kwargs: kwargs['axis'] = 2 grid = 0 for s in spin: ev, evec = HH.eigh(spin=s, eigvals_only=False) if 'energy_window' in kwargs: energy_window = kwargs['energy_window'] window_states = np.where(np.abs(ev - E) < energy_window)[0] else: window_states = range(HH.sites) # Computing grids for all states is too slow... Let's use a window energy_window = np.where(np.abs(ev - E) < 1.5)[0] for ni in window_states: v = evec[:, ni] grid_n = real_space_grid(self.geometry, kwargs['sc'], v, kwargs['shape'], mode='wavefunction') f = sisl.get_distribution(distribution, smearing=eta, x0=ev[ni]) weight = f(E) # Slice it to obtain a 2D grid slice_grid = grid_n.swapaxes(kwargs['axis'], 2).grid[:, :, 0].T grid += (slice_grid.real**2 + slice_grid.imag**2) * weight self.__realspace__(grid, **kwargs) self.imshow.set_cmap(plt.cm.afmhot) else: pdos = HH.PDOS(E, eta=eta, spin=spin, dist=distribution) self.axes.scatter(x, y, pdos, 'b') for i, s in enumerate(sites): self.axes.text(x[s], y[s], '%i' % i, fontsize=15, color='r')