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
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])])
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
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])])
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
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)
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