Ejemplo n.º 1
0
    def update_units_labels_and_values(self) -> None:
        """ Updates the x units labels and fields

        :return: None
        """
        # If x units haven't changed, we do nothing
        new_x = self.units.get().split('_')[-1]
        old_x = self.units_x.get()
        if new_x == old_x:
            return

        self.units_x.set(new_x)

        old_min = self.x_min.get()
        old_max = self.x_max.get()

        if all(t in ('nm', 'eV') for t in (old_x, new_x)):
            new_min = eVnm(old_min)
            new_max = eVnm(old_max)
        elif all(t in ('nm', 'J') for t in (old_x, new_x)):
            new_min = nmJ(old_min)
            new_max = nmJ(old_max)
        elif all(t in ('nm', 'm') for t in (old_x, new_x)):
            factor = 1e-9 if old_x == 'nm' else 1e9
            new_min = factor * old_min
            new_max = factor * old_max
        elif all(t in ('nm', 'hz') for t in (old_x, new_x)):
            new_min = nmHz(old_min)
            new_max = nmHz(old_max)
        elif all(t in ('m', 'J') for t in (old_x, new_x)):
            new_min = mJ(old_min)
            new_max = mJ(old_max)
        elif all(t in ('m', 'eV') for t in (old_x, new_x)):
            factor = h * c / q
            new_min = factor / old_min
            new_max = factor / old_max
        elif all(t in ('m', 'hz') for t in (old_x, new_x)):
            factor = c
            new_min = factor / old_min
            new_max = factor / old_max
        elif all(t in ('J', 'eV') for t in (old_x, new_x)):
            factor = q if old_x == 'eV' else 1 / q
            new_min = factor * old_min
            new_max = factor * old_max
        elif all(t in ('J', 'hz') for t in (old_x, new_x)):
            factor = 1 / h if old_x == 'J' else h
            new_min = factor * old_min
            new_max = factor * old_max
        else:
            # eV <-> hz
            factor = q / h if old_x == 'eV' else h / q
            new_min = factor * old_min
            new_max = factor * old_max

        # Now we have to check if maximum and minimum are in the correct order, reversing them, otherwise
        if new_min > new_max:
            new_min, new_max = new_max, new_min

        self.x_min.set(format(new_min, '.4'))
        self.x_max.set(format(new_max, '.4'))
Ejemplo n.º 2
0
    def _get_power_density_per_eV(self, energy):
        """ Function that returns the spectrum in power density per eV.

        :param energy: Array with the energies at which to calculate the spectrum (in eV)
        :return: The spectrum in the chosen units.
        """
        wavelength = eVnm(energy)[::-1]
        output = self._spectrum(wavelength)
        energy_eV, output = spectral_conversion_nm_ev(wavelength, output)
        return output
Ejemplo n.º 3
0
def photon_flux_per_ev(spectrum: Callable[[np.ndarray], np.ndarray], x: np.ndarray):
    """ Function that returns the spectrum in photon flux per eV.

    The input spectrum is assumed to be in power density per nanometer.

    :param spectrum: The spectrum to interpolate.
    :param x: Array with the energies (in eV)
    :return: The spectrum in the chosen units.
    """
    wavelength = eVnm(x)[::-1]
    output = spectrum(wavelength)
    _, output = spectral_conversion_nm_ev(wavelength, output)
    return output / (q * x)
Ejemplo n.º 4
0
        else:
            return False
    else:
        relative = max(a, b) / min(a, b) - 1.
        if relative < relative_precision:
            return True
        else:
            return False


def independent_nm_ev(x, y):
    return UnitsSystem().eVnm(x)[::-1], y[::-1]


def independent_nm_J(x, y):
    return UnitsSystem().nmJ(x)[::-1], y[::-1]


def independent_m_J(x, y):
    return reverse(UnitsSystem().mJ(x)), reverse(y)


def reverse(x):
    return x[::-1]


if __name__ == '__main__':
    UnitsSystem()

    print(solcore.eVnm(1240))
Ejemplo n.º 5
0
def test_units_correctly_calculated():
    a_nm = 1239.8417166827828
    assert a_nm == approx(solcore.eVnm(1))
Ejemplo n.º 6
0
 def test_03_units_correctly_calculated(self):
     a_nm = 1239.8417166827828
     self.assertAlmostEqual(a_nm, solcore.eVnm(1))
Ejemplo n.º 7
0
def calculate_junction_sr(junc,
                          energies,
                          bs,
                          bs_initial,
                          V,
                          printParameters=False):
    """ Calculates the total quantum efficiency, the QE splitted by regions, photocurrent and other parameters for a
    given junction at a given voltage.

    :param junc: The junction object
    :param energies: The energies at which to perform the calculation
    :param bs: The spectral power density reaching the junction
    :param bsInitial: The initial power density
    :param V: The voltage at which to perform the calculations (not implemented, yet)
    :param printParameters: If a list of all parameters must be printed
    :return:
    """

    # First we have to figure out if we are talking about a PN, NP, PIN or NIP junction
    sn = 0 if not hasattr(junc, "sn") else junc.sn
    sp = 0 if not hasattr(junc, "sp") else junc.sp

    # First we search for the emitter and check if it is n-type or p-type
    idx = 0
    pn_or_np = 'pn'
    for layer in junc:
        if layer.role is not 'emitter':
            idx += 1
        else:
            Na = 0
            Nd = 0
            if hasattr(layer.material, 'Na'): Na = layer.material.Na
            if hasattr(layer.material, 'Nd'): Nd = layer.material.Nd
            if Na < Nd:
                pn_or_np = "np"
                nRegion = junc[idx]
            else:
                pRegion = junc[idx]
            break

    # Now we check for an intrinsic region and, if there is, for the base.
    if junc[idx + 1].role is 'intrinsic':
        iRegion = junc[idx + 1]

        if junc[idx + 2].role is 'base':
            if pn_or_np == "pn":
                nRegion = junc[idx + 2]
            else:
                pRegion = junc[idx + 2]
        else:
            raise RuntimeError(
                'ERROR processing junctions: A layer following the "intrinsic" layer must be defined as '
                '"base".')

    # If there is no intrinsic region, we check directly the base
    elif junc[idx + 1].role is 'base':
        if pn_or_np == "pn":
            nRegion = junc[idx + 1]
        else:
            pRegion = junc[idx + 1]
        iRegion = None

    else:
        raise RuntimeError(
            'ERROR processing junctions: A layer following the "emitter" must be defined as "intrinsic"'
            'or "base".')

    # With all regions identified, it's time to start doing calculations
    T = nRegion.material.T
    kbT = kb * T
    Egap = nRegion.material.band_gap

    xp = pRegion.width
    xn = nRegion.width
    xi = 0 if iRegion is None else iRegion.width

    # Now we have to get all the material parameters needed for the calculation
    if hasattr(junc, "dielectric_constant"):
        es = junc.dielectric_constant
    else:
        es = nRegion.material.permittivity * vacuum_permittivity  # equal for n and p.  I hope.

    # For the diffusion lenght, subscript n and p refer to the carriers, electrons and holes
    if hasattr(junc, "ln"):
        ln = junc.ln
    else:
        ln = pRegion.material.electron_diffusion_length

    if hasattr(junc, "lp"):
        lp = junc.lp
    else:
        lp = nRegion.material.hole_diffusion_length

    # For the diffusion coefficient, n and p refer to the regions, n side and p side. Yeah, it's confusing...
    if hasattr(junc, "mup"):
        dp = junc.mup * kb * T / q
    else:
        dp = pRegion.material.electron_mobility * kb * T / q

    if hasattr(junc, "mun"):
        dn = junc.mun * kb * T / q
    else:
        dn = nRegion.material.hole_mobility * kb * T / q

    # Effective masses and effective density of states
    mEff_h = nRegion.material.eff_mass_hh_z * electron_mass
    mEff_e = pRegion.material.eff_mass_electron * electron_mass

    Nv = 2 * (mEff_h * kb * T / (2 * pi * hbar**2))**1.5  # Jenny p58
    Nc = 2 * (mEff_e * kb * T / (2 * pi * hbar**2))**1.5
    niSquared = Nc * Nv * np.exp(-Egap / (kb * T))
    ni = np.sqrt(niSquared)

    Na = pRegion.material.Na
    Nd = nRegion.material.Nd
    Vbi = (kb * T / q) * np.log(Nd * Na / niSquared) if not hasattr(
        junc, "Vbi") else junc.Vbi  # Jenny p146

    # And now we account for the possible applied voltage, which can be, at most, equal to Vbi
    V = min(Vbi, V)
    Vbi = Vbi - V

    # It's time to calculate the depletion widths
    if not hasattr(junc, "wp") or not hasattr(junc, "wn"):

        if hasattr(junc, "depletion_approximation"
                   ) and junc.depletion_approximation == "one-sided abrupt":
            print(
                "using one-sided abrupt junction approximation for depletion width"
            )
            science_reference(
                "Sze abrupt junction approximation",
                "Sze: The Physics of Semiconductor Devices, 2nd edition, John Wiley & Sons, Inc (2007)"
            )
            wp = np.sqrt(2 * es * Vbi / (q * Na))
            wn = np.sqrt(2 * es * Vbi / (q * Nd))

        else:
            wn = (-xi + np.sqrt(xi**2 + 2. * es * Vbi / q *
                                (1 / Na + 1 / Nd))) / (1 + Nd / Na)
            wp = (-xi + np.sqrt(xi**2 + 2. * es * Vbi / q *
                                (1 / Na + 1 / Nd))) / (1 + Na / Nd)

    wn = wn if not hasattr(junc, "wn") else junc.wn
    wp = wp if not hasattr(junc, "wp") else junc.wp

    # we have an array of alpha values that needs to be interpolated to the right energies
    alphaN = nRegion.material.alphaE(
        energies)  # create numpy array at right energies.
    alphaP = pRegion.material.alphaE(
        energies)  # create numpy array at right energies.
    alphaI = iRegion.material.alphaE(energies) if iRegion else 0
    depleted_width = wn + wp + xi
    bs_incident_on_top = bs

    # Now it is time to calculate currents
    if pn_or_np == "pn":
        bs_incident_on_bottom = bs * np.exp(-alphaP * xp - alphaN * wn -
                                            xi * alphaI)
        bs_incident_on_depleted = bs * np.exp(-alphaP * (xp - wp))
        alphaTop = alphaP
        alphaBottom = alphaN

        l_top, l_bottom = ln, lp
        x_top, x_bottom = xp, xn
        w_top, w_bottom = wp, wn
        s_top, s_bottom = sp, sn
        d_top, d_bottom = dp, dn
        min_top, min_bot = niSquared / Na, niSquared / Nd
    else:
        bs_incident_on_bottom = bs * np.exp(-alphaN * xn - alphaP * wp -
                                            xi * alphaI)
        bs_incident_on_depleted = bs * np.exp(-alphaN * (xn - wn))
        alphaTop = alphaN
        alphaBottom = alphaP

        l_bottom, l_top = ln, lp
        x_bottom, x_top = xp, xn
        w_bottom, w_top = wp, wn
        s_bottom, s_top = sp, sn
        d_bottom, d_top = dp, dn
        min_bot, min_top = niSquared / Na, niSquared / Nd

    j_top, JtopDark = get_j_top(x_top, w_top, l_top, s_top, d_top, alphaTop,
                                bs_incident_on_top, V, min_top, T)
    j_bottom, JbotDark = get_j_bot(x_bottom, w_bottom, l_bottom, s_bottom,
                                   d_bottom, alphaBottom,
                                   bs_incident_on_bottom, V, min_bot, T)

    jgen = q * bs_incident_on_depleted * (
        1 - np.exp(-alphaI * xi - alphaN * wn - alphaP * wp)
    )  # jgen. Jenny, p. 159

    # hereby we define the subscripts to refer to the layer in which the current is generated:
    if pn_or_np == "pn":
        jn, jp = j_bottom, j_top
        JnDark, JpDark = JbotDark, JtopDark
    else:
        jp, jn = j_bottom, j_top
        JpDark, JnDark = JbotDark, JtopDark

    # These might not be the right lifetimes. Actually, they are not as they include all recombination processes, not
    # just SRH recombination, which is what the equation in Jenny, p159 refers to. Let´ leave them, for now.
    lifetime_n = ln**2 / dn
    lifetime_p = lp**2 / dp  # Jenny p163

    # Jrec. Jenny, p.159. Note capital J. This does not need integrating over energies
    Jrec = q * ni * (wn + wp + xi) / np.sqrt(
        lifetime_n * lifetime_p) * np.sinh(q * V /
                                           (2 * kbT)) / (q * Vbi / kbT) * pi

    # jgen = q* bs*(1 - exp(-depleted_width*alpha))*exp(-(xn-wn)*alpha);
    nDepletionCharge = wn * Nd * q
    pDepletionCharge = wp * Na * q

    Vbi2 = (0.5 * (wn + wp) + xi) * pDepletionCharge / es

    good_indeces = np.isfinite(jn) * np.isfinite(jp) * np.isfinite(jgen)

    energies = energies[good_indeces]
    jn = jn[good_indeces]
    jp = jp[good_indeces]
    jgen = jgen[good_indeces]

    # jn[jn < 0] = 0
    # jp[jp < 0] = 0
    # jgen[jgen < 0] = 0

    bs_initial = bs_initial[good_indeces]

    Jn = np.trapz(y=jn, x=energies)
    Jp = np.trapz(y=jp, x=energies)
    Jgen = np.trapz(y=jgen, x=energies)

    if printParameters:
        jSum = list((jn + jp + jgen) / bs_initial / q)
        peakQE = max(jSum)
        BandgapEV = convert(Egap, "J", "eV")
        peakQEE = convert(energies[jSum.index(peakQE)], "J", "eV")

        parameterDictionary = {
            "typee": "PIN" if iRegion is not None else "PN",
            "Na": Na,
            "Nd": Nd,
            "mEff_e": mEff_e,
            "mEff_h": mEff_h,
            "dp": dp,
            "dn": dn,
            "T": T,
            "es": es,
            "Vbi": Vbi,
            "xpUm": convert(xp, "m", "um"),
            "xnUm": convert(xn, "m", "um"),
            "BandgapEV": BandgapEV,
            "BandgapNM": eVnm(BandgapEV),
            "pMatrialString": str(pRegion.material),
            "nMatrialString": str(nRegion.material),
            "relativeEffectiveMassE": mEff_e / electron_mass,
            "relativeEffectiveMassH": mEff_h / electron_mass,
            "wnNm": convert(wn, "m", "nm"),
            "wpNm": convert(wp, "m", "nm"),
            "NaCM": convert(Na, "m-3", "cm-3"),
            "NdCM": convert(Nd, "m-3", "cm-3"),
            "permittivity": es / vacuum_permittivity,
            "peakQE": peakQE * 100,
            "peakQEE": peakQEE,
            "peakQENM": eVnm(peakQEE),
            "lpum": convert(lp, "m", "um"),
            "lnum": convert(ln, "m", "um"),
            "nQ": convert(nDepletionCharge, "m-2", "cm-2"),
            "pQ": convert(pDepletionCharge, "m-2", "cm-2"),
            "pDepletionCharge": pDepletionCharge,
            "nDepletionCharge": nDepletionCharge,
            "fieldMax": pDepletionCharge / (es),
            "iRegionString": "",
            "Vbi2": Vbi2,
            "ni": ni
        }
        if iRegion is not None:
            parameterDictionary["iRegionString"] = """
| i region: {xiUm:.2f} um {iMatrialString}
|   field: {fieldMax:.3e} V/m""".format(
                **{
                    "iMatrialString": str(iRegion.material),
                    "xiUm": convert(xi, "m", "um"),
                    "fieldMax": pDepletionCharge / (es),
                })

        print("""\n
| Calculating {typee} QE. Active Parameters:
|
| p region: {xpUm:.2f} um {pMatrialString}
|   Na = {Na:.3e} m-3 ({NaCM:.3e} cm-3)
|   minority carrier (electron) diffusion length: {lpum:.3f} um
|   minority carrier (electron) effective mass: {relativeEffectiveMassE:.3f} (relative) {mEff_e:.3e} kg (absolute)
|   minority carrier (electron) diffusivity = {dp:.3e} m2/s
|   depletion width: {wpNm:.3f} nm
|   charge in depletion region: {pDepletionCharge:.3e} C m-2 ({pQ:.3e} C cm-2){iRegionString}
| n region: {xnUm:.2f} um {nMatrialString}
|   Nd = {Nd:.3e} m-3 ({NdCM:.3e} cm-3)
|   minority carrier (hole) diffusion length: {lnum:.3f} um
|   minority carrier (hole) effective mass: {relativeEffectiveMassH:.3f} (relative) {mEff_h:.3e} kg (absolute)
|   minority carrier (hole) diffusivity = {dn:.3e} m2/s
|   depletion width: {wnNm:.3f} nm
|   charge in depletion region: {nDepletionCharge:.3e} C m-2 ({nQ:.3e} C cm-2)
| Bandgap: {BandgapEV:.3f} eV ({BandgapNM:.2f} nm)
| Temperature: {T:.2f} K
| permittivity: {permittivity:.3f} (relative) {es:.3e} A s V-1 m-1 (absolute)
| built-in Voltage: {Vbi:.3f} V
| peak field: {fieldMax:.3e} V/m
| ni: {ni:e}
| Result:
| \tPeak QE = {peakQE:.1f} % at {peakQEE:.3f} eV ({peakQENM:.2f} nm)""".format(
            **parameterDictionary))

    return {
        "qe_n": jn / q / bs_initial,
        "qe_p": jp / q / bs_initial,
        "qe_scr": jgen / q / bs_initial,
        "qe_tot": (jn + jp + jgen) / q / bs_initial,
        "Jn_sc": Jn,
        "Jp_sc": Jp,
        "Jscr_sc": Jgen,
        "Jn_dif": JnDark,
        "Jp_dif": JpDark,
        "Jscr_srh": Jrec,
        "J": (Jn + Jp + Jgen - Jrec - JnDark - JpDark),
        "e": energies,
        "Temporary locals dictionary for radiative efficiency": locals()
    }