Example #1
0
    def _add_raw_nk_layer(self, layer):
        """ Adds a layer to the end (bottom) of the stack. The layer must be defined as a list containing the layer
        thickness in nm, the wavelength, the n and the k data as array-like objects.

        :param layer: The new layer to add as [thickness, wavelength, n, k]
        :return: None
        """
        # We make sure that the wavelengths are increasing, revering the arrays otherwise.
        if layer[1][0] > layer[1][-1]:
            layer[1] = layer[1][::-1]
            layer[2] = layer[2][::-1]
            layer[3] = layer[3][::-1]

        self.widths.append(layer[0])

        if len(layer) >= 5:
            self.models.append(layer[4])
            c = solcore.si(layer[5][0], 'nm')
            w = solcore.si(layer[5][1], 'nm')
            d = layer[5][2]  # = 0 for increasing, =1 for decreasing

            def mix(x):

                out = 1 + np.exp(-(x - c) / w)
                out = d + (-1)**d * 1 / out

                return out

            n_data = np_cache(lambda x: self.models[-1].n_and_k(x) * mix(x) + (
                1 - mix(x)) * interp1d(x=solcore.si(layer[1], 'nm'),
                                       y=layer[2],
                                       fill_value=layer[2][-1])(x))
            k_data = np_cache(lambda x: interp1d(x=solcore.si(layer[1], 'nm'),
                                                 y=layer[3],
                                                 fill_value=layer[3][-1])(x))

            self.n_data.append(n_data)
            self.k_data.append(k_data)

        else:
            self.models.append([])
            self.n_data.append(
                np_cache(
                    interp1d(x=solcore.si(layer[1], 'nm'),
                             y=layer[2],
                             fill_value=layer[2][-1])))
            self.k_data.append(
                np_cache(
                    interp1d(x=solcore.si(layer[1], 'nm'),
                             y=layer[3],
                             fill_value=layer[3][-1])))
def test_get_J_sc_SCR_vs_WL():
    from solcore.light_source import LightSource
    from solcore.interpolate import interp1d
    from solcore.analytic_solar_cells.depletion_approximation import get_J_sc_SCR_vs_WL

    light_source = LightSource(source_type="standard", version="AM1.5g")
    wl = np.linspace(300, 1800, 50) * 1e-9
    wl_ls, phg = light_source.spectrum(output_units='photon_flux_per_m', x=wl)

    xa_nm = np.random.uniform(1, 1000)
    xa = xa_nm * 1e-9
    xb = np.random.uniform(xa_nm + 1, 1100) * 1e-9

    ## make a simple Beer-Lambert profile
    dist = np.linspace(0, xb, 1000)
    alphas = np.linspace(1e5, 10, len(wl))

    expn = np.exp(-alphas[:, None] * dist[None, :])

    output = alphas[:, None] * expn
    output = output.T
    gen_prof = interp1d(dist, output, axis=0)

    zz = np.linspace(xa, xb, 1002)[:-1]
    gg = gen_prof(zz) * phg
    expected = np.trapz(gg, zz, axis=0)

    result = get_J_sc_SCR_vs_WL(xa, xb, gen_prof, wl, phg)

    assert expected == approx(result)
def test_get_J_sc_diffusion_vs_WL_bottom():
    from solcore.analytic_solar_cells.depletion_approximation import get_J_sc_diffusion_vs_WL
    from scipy.integrate import solve_bvp
    from solcore.interpolate import interp1d
    from solcore.light_source import LightSource

    D = np.power(10, np.random.uniform(-5, 0)) # Diffusion coefficient
    L = np.power(10, np.random.uniform(-9, -6)) # Diffusion length
    minority = np.power(10, np.random.uniform(-7, -4))# minority carrier density
    s = np.power(10, np.random.uniform(0, 3)) # surface recombination velocity

    light_source = LightSource(source_type="standard", version="AM1.5g")
    wl = np.linspace(300, 1800, 50) * 1e-9
    wl_ls, phg = light_source.spectrum(output_units='photon_flux_per_m', x=wl)

    xa_nm = np.random.uniform(1, 1000)
    xa = xa_nm * 1e-9
    xb = np.random.uniform(xa_nm + 1, 1100) * 1e-9

    ## make a simple Beer-Lambert profile
    dist = np.linspace(0, xb, 1000)
    alphas = np.linspace(1e8, 10, len(wl))

    expn = np.exp(- alphas[:, None] * dist[None, :])

    output = alphas[:, None] * expn
    output = output.T
    gen_prof = interp1d(dist, output, axis=0)

    zz = np.linspace(xa, xb, 1002)[:-1]
    gg = gen_prof(zz) * phg

    expected = np.zeros_like(wl)

    for i in range(len(wl)):
        if np.all(gg[:, i] == 0):  # no reason to solve anything if no generation at this wavelength
            expected[i] = 0

        A = lambda x: np.interp(x, zz, gg[:, i]) / D + minority / L ** 2

        def fun(x, y):
            out1 = y[1]
            out2 = y[0] / L ** 2 - A(x)
            return np.vstack((out1, out2))

        def bc(ya, yb):
            left = ya[0]
            right = yb[1] - s / D * (yb[0] - minority)
            return np.array([left, right])

        guess = minority * np.ones((2, zz.size))
        guess[1] = np.zeros_like(guess[0])
        solution = solve_bvp(fun, bc, zz, guess)

        expected[i] = solution.y[1][0]

    result = get_J_sc_diffusion_vs_WL(xa, xb, gen_prof, D, L, minority, s, wl, phg, side='bottom')

    assert result == approx(expected)
def plot_R(obj, x, icol):

    cols = sns.cubehelix_palette(len(use_orders),
                                 start=.5,
                                 rot=-.9,
                                 reverse=True)
    GaAs = material('GaAs_WVASE')()
    Air = material('Air')()

    InAlP = material('InAlP_WVASE')()
    InGaP = material('InGaP_WVASE')()

    Ag = material('Ag_Jiang')()

    # MgF2 = material('MgF2')()
    # Ta2O5 = material('410', nk_db=True)()
    SiN = material('SiN_SE')()

    R_data = np.loadtxt('Talbot_precursor_R.csv', delimiter=',')

    grating1 = [Layer(si(x[2] * x[1], 'nm'), SiN)]

    grating2 = [
        Layer(si((1 - x[1]) * x[2], 'nm'),
              SiN,
              geometry=[{
                  'type': 'circle',
                  'mat': Ag,
                  'center': (0, 0),
                  'radius': x[3],
                  'angle': 0
              }])
    ]

    solar_cell = SolarCell([
        Layer(material=GaAs, width=si('25nm')),
        Layer(material=InGaP, width=si('19nm')),
        Layer(material=GaAs, width=si(x[0], 'nm')),
        Layer(material=InAlP, width=si('18nm'))
    ] + grating1 + grating2,
                           substrate=Ag)

    S4_setup = rcwa_structure(solar_cell, obj.size, obj.orders, obj.options,
                              Air, Ag)

    RAT = S4_setup.calculate()

    total_A = np.sum(RAT['A_layer'], axis=1)

    # plt.figure()

    plt.plot(S4_setup.wavelengths * 1e9,
             RAT['R'],
             label=str(obj.orders),
             color=cols[icol])

    return S4_setup.wavelengths * 1e9, interp1d(R_data[:, 0], R_data[:, 1])(
        S4_setup.wavelengths * 1e9)
    def __init__(self, orders):

        x = 500
        self.orders = orders

        EQE_data = np.loadtxt('data/V2A_9_EQE.dat', skiprows=129)

        # anti-reflection coating
        #ARC1 = [Layer(si('60nm'), SiN)]
        self.size = ((x, 0), (x / 2, np.sin(np.pi / 3) * x))

        wavelengths = np.linspace(303, 1000, 160) * 1e-9

        RCWA_wl = wavelengths

        options = {
            'nm_spacing': 0.5,
            'n_theta_bins': 100,
            'c_azimuth': 1e-7,
            'pol': 's',
            'wavelengths': RCWA_wl,
            'theta_in': 0,
            'phi_in': 0,
            'parallel': True,
            'n_jobs': -1,
            'phi_symmetry': np.pi / 2,
            'project_name': 'ultrathin'
        }

        ropt = dict(LatticeTruncation='Circular',
                    DiscretizedEpsilon=True,
                    DiscretizationResolution=4,
                    PolarizationDecomposition=False,
                    PolarizationBasis='Default',
                    LanczosSmoothing=True,
                    SubpixelSmoothing=True,
                    ConserveMemory=False,
                    WeismannFormulation=True)
        options['rcwa_options'] = ropt
        self.options = options

        spect = np.loadtxt('AM0.csv', delimiter=',')
        self.AM0 = interp1d(spect[:, 0], spect[:, 1])(wavelengths * 1e9)
        self.EQE_meas = interp1d(EQE_data[:, 0],
                                 EQE_data[:, 1])(wavelengths * 1e9)
    def __init__(self, energy, e2):
        """ Constructor of the PolySegment oscillator. By default, the amplitude and broadening are set to zero meaning
        that there are no oscillator.

        :param energy: Energies (eV)
        :param e2: Epsilon_2 values at the corresponding energies (dimensionless)
        """

        self.energy = energy
        self.e2 = e2
        self.epsi2 = interp1d(x=energy, y=e2)
        self.var = len(e2)
def test_dark_iv_depletion_np(np_junction):
    from solcore.analytic_solar_cells.depletion_approximation import iv_depletion, get_depletion_widths, get_j_dark, get_Jsrh, identify_layers, identify_parameters
    from scipy.interpolate import interp1d

    test_junc, options = np_junction
    options.light_iv = False
    T = options.T

    test_junc[0].voltage = options.internal_voltages

    id_top, id_bottom, pRegion, nRegion, iRegion, pn_or_np = identify_layers(test_junc[0])
    xn, xp, xi, sn, sp, ln, lp, dn, dp, Nd, Na, ni, es = identify_parameters(test_junc[0], T, pRegion, nRegion, iRegion)

    niSquared = ni**2

    kbT = kb * T

    Vbi = (kbT / q) * np.log(Nd * Na / niSquared)

    V = np.where(test_junc[0].voltage < Vbi - 0.001, test_junc[0].voltage, Vbi - 0.001)

    wn, wp = get_depletion_widths(test_junc[0], es, Vbi, V, Na, Nd, xi)

    w = wn + wp + xi

    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

    JtopDark = get_j_dark(x_top, w_top, l_top, s_top, d_top, V, min_top, T)
    JbotDark = get_j_dark(x_bottom, w_bottom, l_bottom, s_bottom, d_bottom, V, min_bot, T)

    JpDark, JnDark = JbotDark, JtopDark

    lifetime_n = ln ** 2 / dn
    lifetime_p = lp ** 2 / dp  # Jenny p163

    Jrec = get_Jsrh(ni, V, Vbi, lifetime_p, lifetime_n, w, kbT)

    J_sc_top = 0
    J_sc_bot = 0
    J_sc_scr = 0

    current = Jrec + JnDark + JpDark + V / 1e14- J_sc_top - J_sc_bot - J_sc_scr
    iv = interp1d(test_junc[0].voltage, current, kind='linear', bounds_error=False, assume_sorted=True,
                           fill_value=(current[0], current[-1]), copy=True)

    iv_depletion(test_junc[0], options)

    assert test_junc[0].iv(options.internal_voltages) == approx(iv(options.internal_voltages), nan_ok=True)
Example #8
0
from solcore.interpolate import interp1d
from solcore.solar_cell import SolarCell
from solcore.structure import Junction, Layer
from solcore.solar_cell_solver import solar_cell_solver

all_materials = []


def this_dir_file(f):
    return os.path.join(os.path.split(__file__)[0], f)


# We need to build the solar cell layer by layer.
# We start from the AR coating. In this case, we load it from an an external file
refl_nm = np.loadtxt(this_dir_file("MgF-ZnS_AR.csv"), unpack=True, delimiter=",")
ref = interp1d(x=siUnits(refl_nm[0], "nm"), y=refl_nm[1], bounds_error=False, fill_value=0)

# TOP CELL - GaInP
# Now we build the top cell, which requires the n and p sides of GaInP and a window layer.
# We also load the absorption coefficient from an external file. We also add some extra parameters needed for the
# calculation such as the minority carriers diffusion lengths
AlInP = material("AlInP")
InGaP = material("GaInP")
window_material = AlInP(Al=0.52)
top_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, "cm-3"), hole_diffusion_length=si("200nm"))
top_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, "cm-3"), electron_diffusion_length=si("1um"))

all_materials.append(window_material)
all_materials.append(top_cell_n_material)
all_materials.append(top_cell_p_material)
Example #9
0
    def __init__(self):
        GaAs = material('GaAs_WVASE')()
        Air = material('Air')()

        InAlP = material('InAlP_WVASE')()
        InGaP = material('InGaP_WVASE')()

        Ag = material('Ag_Jiang')()

        SiN = material('SiN_SE')()

        x = 600

        # anti-reflection coating
        ARC1 = [Layer(si('60nm'), SiN)]
        size = ((x, 0), (x / 2, np.sin(np.pi / 3) * x))

        wavelengths = np.linspace(250, 930, 200) * 1e-9

        RCWA_wl = wavelengths

        options = {
            'nm_spacing': 0.5,
            'n_theta_bins': 100,
            'c_azimuth': 1e-7,
            'pol': 's',
            'wavelengths': RCWA_wl,
            'theta_in': 0,
            'phi_in': 0,
            'parallel': True,
            'n_jobs': -1,
            'phi_symmetry': np.pi / 2,
            'project_name': 'ultrathin'
        }

        ropt = dict(LatticeTruncation='Circular',
                    DiscretizedEpsilon=True,
                    DiscretizationResolution=4,
                    PolarizationDecomposition=False,
                    PolarizationBasis='Default',
                    LanczosSmoothing=True,
                    SubpixelSmoothing=True,
                    ConserveMemory=False,
                    WeismannFormulation=True)

        options['rcwa_options'] = ropt

        grating = [
            Layer(si(100, 'nm'),
                  SiN,
                  geometry=[{
                      'type': 'circle',
                      'mat': Ag,
                      'center': (0, 0),
                      'radius': 100,
                      'angle': 0
                  }])
        ]
        solar_cell = SolarCell(ARC1 + [
            Layer(material=InGaP, width=si('19nm')),
            Layer(material=GaAs, width=si('86nm')),
            Layer(material=InAlP, width=si('18nm'))
        ] + grating)
        self.S4_setup = rcwa_structure(solar_cell, size, 37, options, Air, Ag)
        spect = np.loadtxt('AM0.csv', delimiter=',')

        self.AM0 = interp1d(spect[:, 0], spect[:, 1])(wavelengths * 1e9)
def schrodinger(structure,
                plot_bands=False,
                kpoints=40,
                krange=1e9,
                num_eigenvalues=10,
                symmetric=True,
                quasiconfined=0.0,
                return_qw_boolean_for_layer=False,
                Efield=0,
                blur=False,
                blurmode="even",
                mode='kp8x8_bulk',
                step_size=None,
                minimum_step_size=0,
                smallest_feature_steps=20,
                filter_strength=0,
                periodic=False,
                offset=0,
                graphtype=[],
                calculate_absorption=False,
                alpha_params=None):
    """ Solves the Schrodinger equation of a 1 dimensional structure. Depending on the inputs, the method for solving
    the problem is more or less sophisticated. In all cases, the output includes the band structure and effective
    masses around k=0 as a function of the position, the energy levels of the QW (electrons and holes) and the
    wavefunctions, although for the kp4x4 and kp6x6 modes, this is provided as a function of the k value, and therefore
    there is much more information.

    :param structure: The strucutre to solve
    :param plot_bands: (False) If the bands should be plotted
    :param kpoints: (30) The number of points in the k.txt space
    :param krange: (1e-9) The range in the k space
    :param num_eigenvalues: (10) Maximum number of eigenvalues to calculate
    :param symmetric: (True) If the structure is symmetric, in which case the calculation can be speed up
    :param quasiconfined: (0.0) Energy above the band edges that an energy level can have before rejecting it
    :param return_qw_boolean_for_layer: (False) Return an boolean array indicating which positions are inside the QW
    :param Efield: (0) Electric field.
    :param blur: (False) If the potentials and effective masses have to be blurred
    :param blurmode: ('even') Other values are 'right' and 'left'
    :param mode: ('kp4x4') The mode of calculating the bands and effective masses. See 'structure_utilities.structure_to_potentials'
    :param step_size: (None) The discretization step of the structure. If none, it is estimated based on the smallest feature.
    :param minimum_step_size: (0) The minimum step size.
    :param smallest_feature_steps: (20) The number of steps in the smallest feature
    :param filter_strength: (0) If > 0, defines the fraction of the wavefunction that has to be inside the QW in order to consider it 'confined'
    :param periodic: (False) If the strucuture is periodic. Affects the boundary conditions.
    :param offset: (0) Energy offset used in the calculation of the energy levels in the case of the 'bulk' solvers
    :param graphtype: [] If 'potential', the band profile and wavefunctions are ploted
    :return: A dictionary containing the band structure and wavefunctions as a function of the position and k
    """

    if Efield != 0:
        symmetric = False

    aligned_structure = VBO_align(structure)
    potentials = structure_to_potentials(
        aligned_structure,
        return_qw_boolean_for_layer=return_qw_boolean_for_layer,
        Efield=Efield,
        blur=blur,
        blurmode=blurmode,
        mode=mode,
        step_size=step_size,
        minimum_step_size=minimum_step_size,
        smallest_feature_steps=smallest_feature_steps)

    # Now that we have the potential and effective masses ready, we solve the problem.
    if mode in ['kp4x4', 'kp6x6']:
        bands = solve_bandstructure_QW(potentials,
                                       num=num_eigenvalues,
                                       kpoints=kpoints,
                                       krange=krange,
                                       symmetric=symmetric,
                                       quasiconfined=quasiconfined,
                                       plot_bands=plot_bands)

        result_band_edge = {
            "x": bands['x'],
            "potentials": {
                key: potentials[key]
                for key in potentials.keys() if key[0] in "Vx"
            },
            "effective_masses": {
                key: potentials[key]
                for key in potentials.keys() if key[0] in "mx"
            },
            "wavefunctions":
            {key: bands[key][:, 0]
             for key in bands.keys() if 'psi' in key},
            "E":
            {key: bands[key][:, 0]
             for key in bands.keys() if key[0] in 'E'},
        }

    else:
        bands = potentials_to_wavefunctions_energies(
            structure=structure,
            num_eigenvalues=num_eigenvalues,
            filter_strength=filter_strength,
            offset=offset,
            periodic=periodic,
            quasiconfined=quasiconfined,
            **potentials)

        result_band_edge = {
            "x": bands['x'],
            "potentials": {
                key: potentials[key]
                for key in potentials.keys() if key[0] in "Vx"
            },
            "effective_masses": {
                key: potentials[key]
                for key in potentials.keys() if key[0] in "mx"
            },
            "wavefunctions":
            {key: bands[key]
             for key in bands.keys() if 'psi' in key},
            "E": {
                key: np.array(bands[key])
                for key in bands.keys() if key[0] in "E"
            },
        }

    if "potentials" in graphtype:
        schrodinger_plt = graphics.split_schrodinger_graph(result_band_edge)
        schrodinger_plt.draw()

    if calculate_absorption:
        result_band_edge["alpha"] = calc_alpha(result_band_edge,
                                               **alpha_params)
        result_band_edge["alphaE"] = interp1d(x=result_band_edge["alpha"][0],
                                              y=result_band_edge["alpha"][1])

    return result_band_edge, bands