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_rat(): GaAs = material("GaAs")(T=300) my_structure = Structure([Layer(si(3000, "nm"), material=GaAs)]) wavelength = np.linspace(450, 1100, 300) idx = np.argmin(abs(wavelength - 800)) out = calculate_rat( my_structure, wavelength, coherent=True, no_back_reflexion=False ) out = (out["R"][idx], out["A"][idx], out["T"][idx]) data = (0.33328918841743332, 0.65996607786373396, 0.0067447337188326914) assert all([d == approx(o) for d, o in zip(data, out)])
def test_42_TMM_rat(self): GaAs = material('GaAs')(T=300) my_structure = Structure([ Layer(si(3000, 'nm'), material=GaAs), ]) wavelength = np.linspace(450, 1100, 300) idx = np.argmin(abs(wavelength - 800)) out = calculate_rat(my_structure, wavelength, coherent=True, no_back_reflexion=False) out = (out['R'][idx], out['A'][idx], out['T'][idx]) data = (0.33328918841743332, 0.65996607786373396, 0.0067447337188326914) for i in range(3): self.assertAlmostEqual(data[i], out[i])
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
# Build the optical stack... stack = Structure([[117, 1240 / E_eV, mgf_nk[1], mgf_nk[2]], [80, 1240 / E_eV, sic_nk[1], sic_nk[2]], [61, 1240 / E_eV, zns_nk[1], zns_nk[2]], [25, 1240 / E_eV, alinp_nk[1], alinp_nk[2]], [350000, 1240 / E_eV, gainp_nk[1], gainp_nk[2]]]) angles = np.linspace(0, 80, 10) RAT_angles = [] print("Calculate RAT ::") for theta in angles: print("Calculating at angle :: %4.1f deg" % theta) # Calculate RAT data... rat_data = calculate_rat(stack, angle=theta, wavelength=1240 / E_eV) RAT_angles.append((theta, rat_data["R"], rat_data["A"])) colors = plt.cm.jet(np.linspace(1, 0, len(RAT_angles))) fig, ax2 = plt.subplots(1, 1) for i, RAT in enumerate(RAT_angles): ax2.plot(1240 / E_eV, RAT[1] * 100, ls="-", color=colors[i], label="%4.1f$^\circ$" % RAT[0]) ax2.plot(1240 / E_eV, RAT[2] * 100, ls="--", color=colors[i])
plt.xlabel('Wavelength (nm)') plt.ylabel('Reflection / Absorption') #plt.legend() plt.show() from solcore.absorption_calculator import calculate_rat ## pure TMM all_layers = front_materials + [Layer(bulkthick, Ge)] + back_materials coh_list = len(front_materials) * ['c'] + ['i'] + ['c'] TMM_res = calculate_rat(all_layers, wavelength=wavelengths * 1e9, substrate=Air, no_back_reflection=False, angle=options['theta_in'] * 180 / np.pi, coherent=False, coherency_list=coh_list, pol=options['pol']) plt.figure() plt.plot(options['wavelengths'] * 1e9, TMM_res['R'], label='R') plt.plot(options['wavelengths'] * 1e9, TMM_res['T'], label='T') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][len(front_materials) + 1], label='Ge') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][1] + TMM_res['A_per_layer'][2], label='ARC') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][3],
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
plt.show() inc_angle = angle_vector[angle_index, 1] * 180 / np.pi from solcore.absorption_calculator import OptiStack, calculate_rat layers = [Layer(500e-9, GaAs), Layer(200e-9, Ge)] OptSt = OptiStack(layers, no_back_reflection=False, substrate=Si, incidence=Air) RAT = calculate_rat(OptSt, options['wavelengths'] * 1e9, angle=inc_angle, no_back_reflection=False, pol=options['pol']) Ge_e = np.real((Ge.n(wavelengths) + 1j * Ge.k(wavelengths))**2) GaAs_e = np.real((GaAs.n(wavelengths) + 1j * GaAs.k(wavelengths))**2) plt.figure() plt.plot(wavelengths * 1e9, RAT['A_per_layer'][1] / Af_1, label='A1 TMM/RCWA') plt.plot(wavelengths * 1e9, RAT['A_per_layer'][2] / Af_2, label='A2 TMM/RCWA') plt.plot(wavelengths * 1e9, RAT['R'] / Rf, label='R TMM/RCWA') plt.plot(wavelengths * 1e9, RAT['T'] / Tf, label='T TMM/RCWA') #plt.plot(wavelengths*1e9, 1/GaAs.n(wavelengths)) #plt.plot(wavelengths*1e9, 1/Ge.n(wavelengths)) #plt.plot(wavelengths*1e9, Af_1, '--', label='Af_1') #plt.plot(wavelengths*1e9, Af_2, ':', label='Af_2')
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
Layer(50e-9, GaAs), Layer(20e-6, Si), Layer(100E-9, GaInP), Layer(70e-9, GaAs) ] from solcore.absorption_calculator import OptiStack, calculate_rat optist = OptiStack(layers, incidence=Air, substrate=SiN, no_back_reflection=False) TMM_res = calculate_rat(optist, options['wavelengths'] * 1e9, angle=options['theta_in'] * 180 / np.pi, pol=options['pol'], coherent=False, coherency_list=['c', 'c', 'c', 'i', 'c', 'c'], no_back_reflection=False) plt.figure() plt.plot(options['wavelengths'] * 1e9, TMM_res['R'], label='R') plt.plot(options['wavelengths'] * 1e9, TMM_res['T'], label='T') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][4], label='A_bulk (Si)') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][1], label='SiN') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][2], label='InGaP') plt.plot(options['wavelengths'] * 1e9, TMM_res['A_per_layer'][3], label='GaAs')
e_inf = [3.7883, 3.8915, 3.8982] A = [16.038, 36.556, 36.806] Br = [0.11112, 0.10413, 0.088618] label = ['150', '250', '350'] ls = ['solid', 'dashed', 'dashdot'] for i in range(len(e_inf)): # We create the oscillators for each layer drud = Drude(An=A[i], Brn=Br[i]) # Then we put them together inside a dielectric model model = DielectricConstantModel(e_inf=e_inf[i], oscillators=[drud]) # We might want to calculate the RAT of the films out = calculate_rat([[300, model]], x) plt.figure(1) plt.plot(x / 1000, out['R'], 'b', label='R ' + label[i], ls=ls[i]) plt.plot(x / 1000, out['A'], 'r', label='A ' + label[i], ls=ls[i]) plt.plot(x / 1000, out['T'], 'g', label='T ' + label[i], ls=ls[i]) # And also want to know the n and k data n = model.n_and_k(x) plt.figure(2) plt.plot(x / 1000, np.real(n), 'b', label='n ' + label[i], ls=ls[i]) plt.plot(x / 1000, np.imag(n), 'g', label='k ' + label[i], ls=ls[i]) plt.figure(1) plt.xlabel('Wavelength (µm)')
wavelengths = np.linspace(900, 1200, 30) sim_fig6 = np.loadtxt('data/optos_fig6_sim.csv', delimiter=',') sim_fig7 = np.loadtxt('data/optos_fig7_sim.csv', delimiter=',') sim_fig8 = np.loadtxt('data/optos_fig8_sim.csv', delimiter=',') rayflare_fig6 = np.loadtxt('fig6_rayflare.txt') rayflare_fig7 = np.loadtxt('fig7_rayflare.txt') rayflare_fig8 = np.loadtxt('fig8_rayflare.txt') # planar Si = material('Si_OPTOS')() Air = material('Air')() struc = OptiStack([Layer(si('200um'), Si)], substrate=Air) RAT = calculate_rat(struc, wavelength=wavelengths, coherent=True) fig = plt.figure() plt.plot(sim_fig6[:,0], sim_fig6[:,1], '--', color=palhf[0], label= 'OPTOS - rear grating (a)') plt.plot(wavelengths, rayflare_fig6, '-o', color=palhf[0], label='RayFlare - rear grating (a)', fillstyle='none') plt.plot(sim_fig7[:,0], sim_fig7[:,1], '--', color=palhf[1], label= 'OPTOS - front pyramids (b)') plt.plot(wavelengths, rayflare_fig7, '-o', color=palhf[1], label= 'RayFlare - front pyramids (b)', fillstyle='none') plt.plot(sim_fig8[:,0], sim_fig8[:,1], '--', color=palhf[2], label= 'OPTOS - grating + pyramids (c)') plt.plot(wavelengths, rayflare_fig8, '-o', color=palhf[2], label= 'RayFlare - grating + pyramids (c)', fillstyle='none') plt.plot(wavelengths, RAT['A_per_layer'][1], '-k', label='Planar') plt.legend(loc='lower left') plt.xlabel('Wavelength (nm)') plt.ylabel('Absorption in Si') plt.xlim([900, 1200]) plt.ylim([0, 1]) fig.savefig('OPTOScomp.pdf', bbox_inches='tight', format='pdf')