Exemple #1
0
    def get_doping(
        self,
        fermi_level: float,
        temperature: float,
        return_electron_hole_conc: bool = False
    ) -> Union[float, Tuple[float, float, float]]:
        """
        Calculate the doping (majority carrier concentration) at a given
        fermi level  and temperature. A simple Left Riemann sum is used for
        integrating the density of states over energy & equilibrium Fermi-Dirac
        distribution.

        Args:
            fermi_level: The fermi_level level in Hartree.
            temperature: The temperature in Kelvin.
            return_electron_hole_conc: Whether to also return the separate
                electron and hole concentrations at the doping level.

        Returns:
            If return_electron_hole_conc is False: the doping concentration in
            units of 1/cm^3. Negative values indicate that the majority carriers
            are electrons (n-type doping) whereas positive values indicates the
            majority carriers are holes (p-type doping).

            If return_electron_hole_conc is True: the doping concentration,
            electron concentration and hole concentration as a tuple.
        """
        if temperature == 0.:
            occ = np.where(self.energies < fermi_level, 1., 0.)
            occ[self.energies == fermi_level] = .5
        else:
            kbt = temperature * units.BOLTZMANN
            if self.atomic_units:
                occ = FD(self.energies, fermi_level, kbt)
            else:
                occ = FD(self.energies * units.eV, fermi_level * units.eV, kbt)

        wdos = self.tdos * occ
        num_electrons = wdos.sum() * self.de
        conc = (num_electrons - self.nelect) / self._conv

        if return_electron_hole_conc:
            cb_conc = wdos[self.energies > self.efermi].sum() * self.de
            vb_conc = wdos[self.energies <= self.efermi].sum() * self.de
            cb_conc = cb_conc / self._conv
            vb_conc = (self.nelect - vb_conc) / self._conv
            return conc, cb_conc, vb_conc

        else:
            return conc
Exemple #2
0
def _get_weighted_dos(energies,
                      dos,
                      fermi_level,
                      temperature,
                      atomic_units=True):
    if temperature == 0.0:
        occ = np.where(energies < fermi_level, 1.0, 0.0)
        occ[energies == fermi_level] = 0.5
    else:
        kbt = temperature * units.BOLTZMANN
        if atomic_units:
            occ = FD(energies, fermi_level, kbt)
        else:
            occ = FD(energies * ev_to_hartree, fermi_level * ev_to_hartree,
                     kbt)

    wdos = dos * occ
    return wdos
Exemple #3
0
def _get_fd(energy, amset_data):
    f = np.zeros(amset_data.fermi_levels.shape)

    for n, t in np.ndindex(amset_data.fermi_levels.shape):
        f[n, t] = FD(
            energy,
            amset_data.fermi_levels[n, t],
            amset_data.temperatures[t] * units.BOLTZMANN,
        )
    return f
Exemple #4
0
def calculate_inverse_screening_length_sq(amset_data, static_dielectric):
    inverse_screening_length_sq = np.zeros(amset_data.fermi_levels.shape)

    tdos = amset_data.dos.tdos
    energies = amset_data.dos.energies
    fermi_levels = amset_data.fermi_levels
    vol = amset_data.structure.volume

    for n, t in np.ndindex(inverse_screening_length_sq.shape):
        ef = fermi_levels[n, t]
        temp = amset_data.temperatures[t]
        f = FD(energies, ef, temp * units.BOLTZMANN)
        integral = np.trapz(tdos * f * (1 - f), x=energies)
        inverse_screening_length_sq[n, t] = (
            integral * 4 * np.pi /
            (static_dielectric * BOLTZMANN * temp * vol))

    return inverse_screening_length_sq
Exemple #5
0
    def calculate_rate(self, spin, b_idx, k_idx, energy_diff=None):
        rlat = self.amset_data.structure.lattice.reciprocal_lattice.matrix
        ir_kpoints_idx = self.amset_data.ir_kpoints_idx
        energy = self.amset_data.energies[spin][b_idx, ir_kpoints_idx][k_idx]

        if energy_diff:
            energy += energy_diff

        tbs = self.amset_data.tetrahedral_band_structure

        tet_dos, tet_mask, cs_weights, tet_contributions = tbs.get_tetrahedra_density_of_states(
            spin,
            energy,
            return_contributions=True,
            symmetry_reduce=False,
            # band_idx=b_idx,
        )

        if len(tet_dos) == 0:
            return 0

        # next, get k-point indices and band_indices
        property_mask, band_kpoint_mask, band_mask, kpoint_mask = tbs.get_masks(
            spin, tet_mask)

        k = self.amset_data.ir_kpoints[k_idx]
        k_primes = self.amset_data.kpoints[kpoint_mask]

        overlap = self.amset_data.overlap_calculator.get_overlap(
            spin, b_idx, k, band_mask, k_primes)

        # put overlap back in array with shape (nbands, nkpoints)
        all_overlap = np.zeros(self.amset_data.energies[spin].shape)
        all_overlap[band_kpoint_mask] = overlap

        # now select the properties at the tetrahedron vertices
        vert_overlap = all_overlap[property_mask]

        # get interpolated overlap and k-point at centre of tetrahedra cross sections
        tet_overlap = get_cross_section_values(vert_overlap,
                                               *tet_contributions)
        tetrahedra = tbs.tetrahedra[spin][tet_mask]

        # have to deal with the case where the tetrahedron cross section crosses the
        # zone boundary. This is a slight inaccuracy but we just treat the
        # cross section as if it is on one side of the boundary
        tet_kpoints = self.amset_data.kpoints[tetrahedra]
        base_kpoints = tet_kpoints[:, 0][:, None, :]
        k_diff = pbc_diff(tet_kpoints, base_kpoints) + pbc_diff(
            base_kpoints, k)
        k_diff = np.dot(k_diff, rlat)

        intersections = get_cross_section_values(k_diff,
                                                 *tet_contributions,
                                                 average=False)
        projected_intersections = get_projected_intersections(intersections)

        if energy_diff:
            f = np.zeros(self.amset_data.fermi_levels.shape)

            for n, t in np.ndindex(self.amset_data.fermi_levels.shape):
                f[n, t] = FD(
                    energy,
                    self.amset_data.fermi_levels[n, t],
                    self.amset_data.temperatures[t] * units.BOLTZMANN,
                )

            functions = np.array([
                m.factor(energy_diff <= 0, f)
                for m in self.inelastic_scatterers
            ])
        else:
            functions = np.array([m.factor() for m in self.elastic_scatterers])

        rates = np.array([
            integrate_function_over_cross_section(
                f,
                projected_intersections,
                *tet_contributions[0:3],
                return_shape=self.amset_data.fermi_levels.shape,
                cross_section_weights=cs_weights) for f in functions
        ])

        # sometimes the projected intersections can be nan when the density of states
        # contribution is infinitesimally small; this catches those errors
        rates[np.isnan(rates)] = 0

        rates /= self.amset_data.structure.lattice.reciprocal_lattice.volume
        rates *= tet_overlap

        return np.sum(rates, axis=-1)