Ejemplo n.º 1
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method. Internally, it creates an OptiStack and then it calculates the optical properties of the whole structure.

    :param solar_cell: A solar_cell object
    :param options: Options for the solver
    :return: None
    """
    wl = options.wavelength

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    all_layers = []
    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            for i, layer in enumerate(layer_object):
                all_layers.append(layer)

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys() else True
    stack = OptiStack(all_layers, no_back_reflexion=no_back_reflexion)

    #dist = np.logspace(-1, np.log10(solar_cell.width * 1e9), int(1000 * np.log10(solar_cell.width * 1e9)))
    dist = np.arange(0, solar_cell.width * 1e9, 1)
    # print(len(dist))
    position = options.position if 'position' in options.keys() else dist

    print('Calculating RAT...')
    RAT = calculate_rat(stack, wl * 1e9, coherent=True)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(stack, wl * 1e9, dist=position)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    for j in range(len(solar_cell)):
        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.transmitted = (1 - RAT['R'] - all_absorbed) * initial
    solar_cell.absorbed = all_absorbed * initial
Ejemplo n.º 2
0
def test_TMM_absorption_profile():
    GaAs = material("GaAs")(T=300)

    my_structure = Structure([
        Layer(si(3000, "nm"), material=GaAs),
        Layer(si(300, "um"), material=GaAs)
    ])

    out = calculate_absorption_profile(my_structure,
                                       np.array([800]),
                                       z_limit=3000,
                                       steps_size=20)

    data_path = Path(__file__).parent / "data" / "absorption_profile.txt"
    data = tuple(np.loadtxt(data_path))

    assert all([d == approx(o) for d, o in zip(data, out["absorption"][0])])
Ejemplo n.º 3
0
def calculate_optics(device, wavelengths, dist=None):
    """ Uses the transfer matrix solver to calculate the optical properties of the structure: that is, the reflection
    and the absorption as a function of the position.

    :param device: A device structure
    :param wavelengths: The wavelengths at which to calculate the optical information (in m)
    :param dist: The positions at which to calculate the absorption (in m). If None, it is calculated internally.
    :return: A dictionary with the reflection, the position, the wavelengths and the absorption as a function of
    the wavelength and position.
    """

    output = {}
    output['wavelengths'] = wavelengths
    wl = wavelengths * 1e9  # Input is in meters but the calculators use nm
    if dist is None:
        d = dist
    else:
        d = dist * 1e9  # Input is in meters but the calculators use nm

    rat = calculate_rat(device, wl)
    output['R'] = rat['R']

    absorption = calculate_absorption_profile(device, wl, dist=d)

    output['absorption'] = absorption['absorption'] * 1e9
    output['position'] = absorption['position'] * 1e-9

    optics_thickness = 0
    for layer in device['layers']:
        if layer['label'] in ['optics', 'Optics']:
            optics_thickness += layer['properties']['width']
        else:
            break

    output['position'] -= optics_thickness

    return output
Ejemplo n.º 4
0
def test_TMM_absorption_profile():
    GaAs = material("GaAs")(T=300)

    my_structure = Structure(
        [Layer(si(3000, "nm"), material=GaAs), Layer(si(300, "um"), material=GaAs)]
    )

    out = calculate_absorption_profile(
        my_structure, np.array([800]), z_limit=3000, steps_size=20
    )

    data = (
        0.00093920198054733134,
        0.00091329190431268755,
        0.00088809661793623094,
        0.00086359640227330484,
        0.00083977208217782804,
        0.00081660501149482764,
        0.0007940770584669893,
        0.00077217059154379222,
        0.00075086846558214263,
        0.00073015400842769127,
        0.00071001100786633451,
        0.00069042369893569059,
        0.00067137675158661923,
        0.00065285525868512457,
        0.00063484472434525627,
        0.00061733105258387328,
        0.00060030053628839001,
        0.00058373984648887986,
        0.00056763602192612503,
        0.00055197645890746013,
        0.00053674890144246557,
        0.0005219414316507909,
        0.00050754246043459913,
        0.00049354071840833585,
        0.00047992524707871981,
        0.00046668539026805409,
        0.0004538107857741483,
        0.00044129135726031623,
        0.00042911730636911071,
        0.00041727910505361793,
        0.00040576748812031177,
        0.00039457344597762961,
        0.00038368821758459675,
        0.00037310328359397839,
        0.00036281035968459497,
        0.00035280139007758007,
        0.00034306854123150837,
        0.00033360419571145685,
        0.00032440094622720458,
        0.00031545158983589954,
        0.00030674912230466134,
        0.00029828673262870248,
        0.00029005779770068137,
        0.00028205587712711315,
        0.00027427470818778237,
        0.00026670820093421067,
        0.00025935043342334711,
        0.00025219564708274435,
        0.00024523824220360293,
        0.00023847277355814448,
        0.00023189394613789383,
        0.00022549661100952902,
        0.0002192757612850577,
        0.00021322652820316562,
        0.00020734417731867015,
        0.00020162410479709653,
        0.00019606183381147725,
        0.00019065301103855347,
        0.0001853934032516379,
        0.00018027889400747007,
        0.00017530548042447405,
        0.00017046927004989423,
        0.00016576647781335848,
        0.00016119342306448588,
        0.00015674652669221659,
        0.00015242230832361372,
        0.00014821738359994165,
        0.00014412846152789071,
        0.00014015234190387394,
        0.00013628591280938143,
        0.00013252614817542982,
        0.0001288701054142039,
        0.00012531492311603331,
        0.00012185781880990432,
        0.00011849608678575335,
        0.00011522709597683633,
        0.00011204828790051968,
        0.00010895717465587773,
        0.00010595133697653208,
        0.00010302842233720751,
        0.00010018614311252307,
        9.7422274786577231e-05,
        9.4734654211925496e-05,
        9.2121177916588886e-05,
        8.9579800457766819e-05,
        8.7108532820967001e-05,
        8.470544086329888e-05,
        8.2368643799712795e-05,
        8.009631273099949e-05,
        7.7886669212397987e-05,
        7.573798386169243e-05,
        7.3648575005706959e-05,
        7.1616807364140996e-05,
        6.9641090769713272e-05,
        6.7719878923614451e-05,
        6.5851668185292527e-05,
        6.4034996395625842e-05,
        6.2268441732560816e-05,
        6.0550621598319883e-05,
        5.8880191537308663e-05,
        5.7255844183874585e-05,
        5.5676308239094561e-05,
        5.41403474757902e-05,
        5.2646759770991723e-05,
        5.1194376165094236e-05,
        4.9782059946968693e-05,
        4.8408705764312587e-05,
        4.7073238758543685e-05,
        4.5774613723559514e-05,
        4.4511814287704784e-05,
        4.3283852118305772e-05,
        4.2089766148149713e-05,
        4.0928621823303459e-05,
        3.9799510371682887e-05,
        3.8701548091800482e-05,
        3.7633875661134488e-05,
        3.6595657463578247e-05,
        3.5586080935443529e-05,
        3.4604355929505788e-05,
        3.3649714096593824e-05,
        3.2721408284239568e-05,
        3.1818711951917646e-05,
        3.0940918602416916e-05,
        3.0087341228898868e-05,
        2.9257311777210295e-05,
        2.8450180623029266e-05,
        2.7665316063435288e-05,
        2.6902103822505617e-05,
        2.6159946570550893e-05,
        2.5438263456613905e-05,
        2.4736489653865175e-05,
        2.4054075917540213e-05,
        2.3390488155071715e-05,
        2.2745207008081107e-05,
        2.211772744590139e-05,
        2.1507558370314028e-05,
        2.0914222231189692e-05,
        2.0337254652732693e-05,
        1.9776204070036316e-05,
        1.9230631375664536e-05,
        1.8700109575983667e-05,
        1.8184223456974879e-05,
        1.7682569259266036e-05,
        1.7194754362128619e-05,
        1.6720396976192213e-05,
        1.6259125844636267e-05,
        1.5810579952625165e-05,
        1.5374408244759177e-05,
        1.4950269350320186e-05,
        1.4537831316097222e-05,
    )

    assert all([d == approx(o) for d, o in zip(data, out["absorption"][0])])
Ejemplo n.º 5
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method.
    Internally, it creates an OptiStack and then it calculates the optical properties of the whole structure.
    A substrate can be specified in the SolarCell object, which is treated as a semi-infinite transmission medium.
    Shading can also be specified (as a fraction).

    Relevant options are 'wl' (the wavelengths, in m), the incidence angle 'theta' (in degrees), the polarization 'pol' ('s',
    'p' or 'u'), 'position' (locations in m at which depth-dependent absorption is calculated), 'no_back_reflexion' and 'BL_correction'.
    'no_back_reflexion' sets whether reflections from the back surface are suppressed (if set to True, the default),
    or taken into account (if set to False).

    If 'BL_correction' is set to True, thick layers (thickness > 10*maximum wavelength) are treated incoherently using
    the Beer-Lambert law, to avoid the calculation of unphysical interference oscillations in the R/A/T spectra.

    A coherency_list option can be provided: this should have elements equal to the total number of layers (if a Junction
    contains multiple Layers, each should have its own entry in the coherency list). Each element is either 'c' for coherent
    treatment of that layer or 'i' for incoherent treatment.

    :param solar_cell: A SolarCell object
    :param options: Options for the solver
    :return: None
    """
    wl = options.wavelength
    BL_correction = options.BL_correction if 'BL_correction' in options.keys(
    ) else True
    theta = options.theta if 'theta' in options.keys(
    ) else 0  # angle IN DEGREES
    pol = options.pol if 'pol' in options.keys() else 'u'

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    all_layers = []
    widths = []
    n_layers_junction = []

    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)
            widths.append(layer_object.width)
            n_layers_junction.append(1)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            n_layers_junction.append(len(layer_object))
            for i, layer in enumerate(layer_object):
                all_layers.append(layer)
                widths.append(layer.width)

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys(
    ) else True

    full_stack = OptiStack(all_layers,
                           no_back_reflexion=no_back_reflexion,
                           substrate=solar_cell.substrate)

    if 'coherency_list' in options.keys():
        coherency_list = options.coherency_list
        coherent = False
        assert len(coherency_list) == full_stack.num_layers, \
            'Error: The coherency list must have as many elements (now {}) as the ' \
            'number of layers (now {}).'.format(len(coherency_list), full_stack.num_layers)
    else:
        coherency_list = None
        coherent = True

    if BL_correction and any(
            widths > 10 *
            np.max(wl)):  # assume it's safe to ignore interference effects
        make_incoherent = np.where(np.array(widths) > 10 * np.max(wl))[0]
        print('Treating layer(s) ' + str(make_incoherent).strip('[]') +
              ' incoherently')
        if not 'coherency_list' in options.keys():
            coherency_list = np.array(len(all_layers) * ['c'])
            coherent = False
        else:
            coherency_list = np.array(coherency_list)
        coherency_list[make_incoherent] = 'i'
        coherency_list = coherency_list.tolist()

    position = options.position * 1e9
    profile_position = position[position < sum(full_stack.widths)]

    print('Calculating RAT...')
    RAT = calculate_rat(full_stack,
                        wl * 1e9,
                        angle=theta,
                        coherent=coherent,
                        coherency_list=coherency_list,
                        no_back_reflexion=no_back_reflexion,
                        pol=pol)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(full_stack,
                                       wl * 1e9,
                                       dist=profile_position,
                                       angle=theta,
                                       no_back_reflexion=no_back_reflexion,
                                       pol=pol,
                                       coherent=coherent,
                                       coherency_list=coherency_list)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out, initial)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    layer = 0
    A_per_layer = np.array(
        RAT['A_per_layer'][1:-1])  # first entry is R, last entry is T
    for j in range(len(solar_cell)):

        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])
        solar_cell[j].layer_absorption = initial * np.sum(
            A_per_layer[layer:(layer + n_layers_junction[j])], axis=0)
        layer = layer + n_layers_junction[j]

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.absorbed = sum(
        [solar_cell[x].layer_absorption for x in np.arange(len(solar_cell))])
    solar_cell.transmitted = initial - solar_cell.reflected - solar_cell.absorbed
Ejemplo n.º 6
0
from solcore.structure import Structure, Layer

# First we defined a couple of materials, for example, GaAs and AlGAs
GaAs = material('GaAs')(T=300)
AlGaAs = material('AlGaAs')(T=300, Al=0.3)

# Now, let's build the structure
my_structure = Structure([
    Layer(si(30, 'nm'), material=AlGaAs),
    Layer(si(3000, 'nm'), material=GaAs),
    Layer(si(300, 'um'), material=GaAs),
])

# We want to calculate the absorption profile of this structure as a function of the position and wavelength
x = np.linspace(400, 1000, 200)
out = calculate_absorption_profile(my_structure, x, steps_size=1, z_limit=3000)

# Finally, we plot the absorption profile. Note that absorption at short wavelengths take place near the surface of the
# structure, in the AlGaAs layer and top of the GaAs layer, while longer wavelengths penetrate more. Wavelengths beyond
# the GaAs band edge are not absorbed.
plt.figure(1)
ax = plt.contourf(out['position'], x, out['absorption'], 200)
plt.xlabel('Position (nm)')
plt.ylabel('Wavelength (nm)')
cbar = plt.colorbar()
cbar.set_label('Absorption (1/nm)')

# We can also check what is the overall light absorption in the AlGaAs and GaAs epilayers as that represents the total
# light absorption in our solar cell and therefore the maximum EQE (light absorbed in the thick substrate lost).
# The absorption is mostly limited by the reflexion in the front surface. Clearly, this solar cell needs an AR coating.
A = np.zeros_like(x)
Ejemplo n.º 7
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method

    :param solar_cell:
    :param options:
    :return:
    """
    wl = options.wavelength

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    widths = []
    offset = 0
    all_layers = []
    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)
            widths.append(layer_object.width)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            junction_width = 0
            try:
                for i, layer in enumerate(layer_object):
                    all_layers.append(layer)
                    junction_width += layer.width
                    widths.append(layer.width)

                solar_cell[j].width = junction_width

            except TypeError as err:
                print(
                    'ERROR in "solar_cell_solver: TMM solver":\n'
                    '\tNo layers found in Junction or TunnelJunction objects.')
                raise err

        solar_cell[j].offset = offset
        offset += layer_object.width

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys(
    ) else True
    stack = OptiStack(all_layers, no_back_reflexion=no_back_reflexion)

    dist = np.logspace(0, np.log10(offset * 1e9),
                       int(300 * np.log10(offset * 1e9)))
    position = options.position if 'position' in options.keys() else dist

    print('Calculating RAT...')
    RAT = calculate_rat(stack, wl * 1e9, coherent=True)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(stack, wl * 1e9, dist=position)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    for j in range(len(solar_cell)):
        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.transmitted = (1 - RAT['R'] - all_absorbed) * initial
    solar_cell.absorbed = all_absorbed * initial