Example #1
0
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
Example #2
0
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
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
 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])
Example #7
0
    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')
Example #8
0
    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)
Example #10
0
    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')