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 test_wavelength_array_consistency(): #GH 147 from solcore.light_source import LightSource import numpy as np wl_arr = np.linspace(300, 1200, 10) * 1e-9 wl_arr2 = np.linspace(300, 1200, 20) * 1e-9 ls = LightSource(source_type='standard', version='AM1.5g', x=wl_arr, output_units='photon_flux_per_m') assert ls.spectrum()[1].shape == wl_arr.shape assert ls.spectrum(x=wl_arr2)[1].shape == wl_arr2.shape
def evaluate(self, x): light_source = LightSource(source_type='standard', version='AM1.5g') wl = np.linspace(300, 1850, 500) * 1e-9 solar_cell = self.make_cell(x) position = [1e-10] * 10 + [5e-8] V = np.linspace(0, 3.5, 300) solar_cell_solver(solar_cell, 'iv', user_options={ 'voltages': V, 'light_iv': True, 'wavelength': wl, 'mpp': True, 'light_source': light_source, 'optics_method': 'TMM', 'BL_correction': True, 'position': position }) efficiency = solar_cell.iv["Eta"] # print('Efficiency =', efficiency) return -efficiency
def __init__(self): # wavelength, materials. # make these attributes of 'self' so they can be accessed by the class object # here I am also creating lists of wavelengths and corresponding n and k data from # the Solcore materials - the reason for this is that there is currently an issue with using the Solcore # material class in parallel computations. Thus the information for the n and k data is saved here as a list # rather than a material object. self.wl = np.linspace(250, 900, 700) self.MgF2 = [ self.wl, material('MgF2')().n(self.wl * 1e-9), material('MgF2')().k(self.wl * 1e-9) ] self.Ta2O5 = [ self.wl, material('410', nk_db=True)().n(self.wl * 1e-9), material('410', nk_db=True)().k(self.wl * 1e-9) ] self.GaAs = [ 1000, self.wl, material('GaAs')().n(self.wl * 1e-9), material('GaAs')().k(self.wl * 1e-9) ] # assuming an AM0 spectrum spectr = LightSource(source_type='standard', version='AM0', x=self.wl, output_units='photon_flux_per_m', concentration=1).spectrum(self.wl * 1e-9)[1] self.spectrum = spectr / max(spectr)
def calculate_light_source(self) -> LightSource: """ Calculate the lightsource based on the chosen options :return: None """ try: # First we create the x values x_min = self.x_min.get() x_max = self.x_max.get() x_points = self.x_points.get() concentration = self.concentration.get() # Now we retrieve the options options = self.spectrum.get_options() except Exception as err: # If any of the parameters causes problems, typically because there's text input where there should be a # number, we return the current light source print(err) return self.light_source # And now we are ready to create the spectrum try: light_source = LightSource(source_type=self.source_type.get(), x=np.linspace(x_min, x_max, x_points), output_units=self.units.get(), concentration=concentration, **options) except TypeError: return self.light_source return light_source
def plot_qe(self, data: pd.DataFrame, spectrum: LightSource) -> None: """ Plots the QE data and the different components, if any. If a spectrum is provided, it also calculates the Jsc of each junction :param data: Data to plot as a Pandas dataframe :param spectrum: A LightSource object with the spectrum, needed to calculate the Jsc of each junction :return: None """ wl = data['WL'] * 1e9 # Wavelength in nanometers color = 'red' # Default color; not really use i = 0 # Counter markerstyle = ('o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X') colors = ('r', 'g', 'b', 'm', 'c') losses = ('srh', 'rad', 'aug', 'surf', 'surb') losses_done = False # To accumulate the total light absorbed total_A = np.zeros_like(data['WL']) # And then the reflection y1 = 1 - data['R'] y2 = np.ones_like(data['R']) self.axes.fill_between(wl, y1, y2, color='yellow', alpha=0.5, linewidth=3, label='R') # Now we plot what happens with light absorbed for name in data.columns.values: if 'A' in name: total_A += data[name] p = self.axes.plot(wl, data[name], '--', linewidth=3, label=name) color = p[-1].get_color() i = 0 y1 = data[name] elif 'EQE' in name and len(name) <= 5: self.axes.plot(wl, data[name], linewidth=3, color=color, label=name) elif any(loss in name for loss in losses): # For the PDD model we have losses. We plot them as shaded areas label = losses[i] if (not losses_done and i < 5) else None y2 = y1 y1 = y1 - data[name] self.axes.plot(wl, y1, ':', color=color) self.axes.fill_between(wl, y1, y2, facecolor=colors[i], alpha=0.5, label=label) i += 1 losses_done = losses_done or i > 4 elif 'EQE' in name: # And for the DA model we have contributions to the QE. We plot them with different markers. self.axes.plot(wl, data[name], color=color, marker=markerstyle[i], fillstyle='none', markevery=5, label=name) i += 1 # If a spectrum is given, we calculate the current related to each of the QE if spectrum is not None and name[:4] == 'EQE': current = data[name].values * spectrum.spectrum(x=wl, output_units='photon_flux_per_nm')[1] self.outputs[name] = q * np.trapz(current, wl) self.axes.plot(wl, total_A, 'k--', linewidth=2, label='Total A') self.axes.set_xlim(left=min(wl), right=max(wl)) self.axes.set_ylim(bottom=0, top=1)
def light_source(wavelength): from solcore.light_source import LightSource return LightSource( source_type="standard", version="AM1.5g", x=np.linspace(350, 1000, 301) * 1e-9, output_units="photon_flux_per_m", concentration=1, )
def __init__(self): # initialize an instance of the class; set some information which will be used in each iteration of the calculation: # materials, wavelengths, the light source T = 298 wl = np.linspace(300, 1900, 800) # materials SiGeSn = material('SiGeSn')(T=T) GaAs = material('GaAs')(T=T) InGaP = material('GaInP')(In=0.5, T=T) Ge = material('Ge')(T=T) # make these attributes of 'self' so they can be accessed by the class object # here I am also creating lists of wavelengths and corresponding n and k data from # the Solcore materials - the reason for this is that there is currently an issue with using the Solcore # material class in parallel computations. Thus the information for the n and k data is saved here as a list # rather than a material object (see the documentation of OptiStack for the different acceptable formats # to pass optical constants for an OptiStack self.wl = wl self.SiGeSn = [ self.wl, SiGeSn.n(self.wl * 1e-9), SiGeSn.k(self.wl * 1e-9) ] self.Ge = [self.wl, Ge.n(self.wl * 1e-9), Ge.k(self.wl * 1e-9)] self.InGaP = [ self.wl, InGaP.n(self.wl * 1e-9), InGaP.k(self.wl * 1e-9) ] self.GaAs = [self.wl, GaAs.n(self.wl * 1e-9), GaAs.k(self.wl * 1e-9)] self.MgF2 = [ self.wl, material('MgF2')().n(self.wl * 1e-9), material('MgF2')().k(self.wl * 1e-9) ] self.Ta2O5 = [ self.wl, material('410', nk_db=True)().n(self.wl * 1e-9), material('410', nk_db=True)().k(self.wl * 1e-9) ] # assuming an AM1.5G spectrum self.spectr = LightSource(source_type='standard', version='AM1.5g', x=self.wl, output_units='photon_flux_per_nm', concentration=1).spectrum(self.wl)[1]
def __init__(self): """ Make the wavelength and the materials n and k data object attributes. The n and k data are extracted from the Solcore materials rather than using the material directly because there is currently an issue with using the Solcore material class in parallel computations. """ self.wl = np.linspace(250, 900, 700) self.MgF2 = [ self.wl, material("MgF2")().n(self.wl * 1e-9), material("MgF2")().k(self.wl * 1e-9), ] # This is Ta2O5 from the SOPRA database included in Solcore self.Ta2O5 = [ self.wl, material("TAOX1")().n(self.wl * 1e-9), material("TAOX1")().k(self.wl * 1e-9), ] self.GaAs = [ 1000, self.wl, material("GaAs")().n(self.wl * 1e-9), material("GaAs")().k(self.wl * 1e-9), ] # assuming an AM0 spectrum spectr = LightSource( source_type="standard", version="AM1.5g", x=self.wl, output_units="photon_flux_per_m", concentration=1, ).spectrum(self.wl * 1e-9)[1] # Only want to use spectrum to weight reflectivity result so don't care about # absolute values self.spectrum = spectr / max(spectr)
import numpy as np import matplotlib.pyplot as plt from solcore.light_source import LightSource # TODO If SMARTS is not installed, it keeps trying and producing errors. # LightSource is poorly designed... # The wavelength range of the spectra wl = np.linspace(300, 3000, 200) # Now different types of light sources can be defined gauss = LightSource(source_type='laser', x=wl, center=800, linewidth=50, power=200) bb = LightSource(source_type='black body', x=wl, T=5800, entendue='Sun') am15g = LightSource(source_type='standard', x=wl, version='AM1.5g') spectral = LightSource(source_type='SPECTRAL2', x=wl) # Plot comparing the different light sources plt.figure(1) plt.plot(*gauss.spectrum(), label='Gauss') plt.plot(*bb.spectrum(), label='Black body') plt.plot(*am15g.spectrum(), label='AM1.5G') plt.plot(*spectral.spectrum(), label='SPECTRAL2') try: smarts = LightSource(source_type='SMARTS', x=wl) plt.plot(*smarts.spectrum(), label='SMARTS') except TypeError:
def test_get_J_sc_diffusion_green_bottom(): from solcore.analytic_solar_cells.depletion_approximation import get_J_sc_diffusion_green 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 velocityx 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 beta1 = (xb - xa) / L if beta1 > 1.e2: xb = xa + 99. * L beta1 = (xb - xa) / L ## make a simple Beer-Lambert profile alphas = np.linspace(1e8, 10, len(wl)) # theoreical value from Fonash, Solar cell device physics eq 4.15 beta2 = (xb - xa) * alphas beta3 = -L * s / D c1 = beta2 * beta2 / (beta2 * beta2 - beta1 * beta1) * np.exp(-alphas * xa) c2 = (beta3 * beta1 / beta2 - 1.) / (beta3 * np.sinh(beta1) + np.cosh(beta1)) expected = (beta3 * np.cosh(beta1) + np.sinh(beta1)) / (beta3 * np.sinh(beta1) + np.cosh(beta1)) expected *= -beta1 / beta2 expected += 1. expected = (expected + c2 * np.exp(-beta2)) * c1 * phg / D expected += minority / L * (beta3 * np.cosh(beta1) + np.sinh(beta1)) / ( beta3 * np.sinh(beta1) + np.cosh(beta1)) result = get_J_sc_diffusion_green(xa, xb, lambda x: alphas * np.exp(-alphas * x), D, L, minority, s, phg, side='bottom') assert beta1 < 1.e2 assert result == approx(expected, rel=1.e-5) # test version for extreme L values Lmax = np.log10((xb - xa) * 1.e-2) L = np.power(10, np.random.uniform(Lmax - 2., Lmax)) # theoretical value for Beer-Lambert profile beta1 = (xb - xa) / L expected = np.exp(-beta1 - alphas * xb) - np.exp(-alphas * xa) expected *= -phg * alphas / D / (alphas + 1. / L) expected += minority / L result = get_J_sc_diffusion_green(xa, xb, lambda x: alphas * np.exp(-alphas * x), D, L, minority, s, phg, side='bottom') assert beta1 > 1.e2 assert result == approx(expected, rel=1.e-5)
def test_get_J_sc_diffusion_green_top(): from solcore.analytic_solar_cells.depletion_approximation import get_J_sc_diffusion_green 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 beta1 = (xb - xa) / L if beta1 > 1.e2: xb = xa + 99. * L beta1 = (xb - xa) / L ## make a simple Beer-Lambert profile alphas = np.linspace(1e8, 10, len(wl)) # theoreical value from Fonash, Solar cell device physics eq 4.10 # (the absorption profile has to be offset for xa != 0.) beta2 = (xb - xa) * alphas beta3 = L * s / D c1 = beta2 * beta2 / (beta2 * beta2 - beta1 * beta1) c2 = (beta3 * beta1 / beta2 + 1.) / (beta3 * np.sinh(beta1) + np.cosh(beta1)) expected = (beta3 * np.cosh(beta1) + np.sinh(beta1)) / (beta3 * np.sinh(beta1) + np.cosh(beta1)) expected *= beta1 / beta2 expected += 1. expected *= np.exp(-beta2) expected = (expected - c2) * c1 * phg / D * np.exp(-alphas * xa) expected -= minority / L * (beta3 * np.cosh(beta1) + np.sinh(beta1)) / ( beta3 * np.sinh(beta1) + np.cosh(beta1)) result = get_J_sc_diffusion_green(xa, xb, lambda x: alphas * np.exp(-alphas * x), D, L, minority, s, phg, side='top') assert beta1 < 1.e2 assert result == approx(expected, rel=1.e-5) # test version for extreme L values Lmax = np.log10((xb - xa) * 1.e-2) L = np.power(10, np.random.uniform(Lmax - 2., Lmax)) # theoretical value for Beer-Lambert profile alphas_miL = alphas - 1. / L idx_c = np.abs(alphas_miL) < 1.e-3 idx_i = np.logical_not(idx_c) expected = np.zeros_like(alphas) expected = np.exp(-alphas_miL * xa - xb / L) - np.exp(-alphas * xb) expected[idx_i] /= alphas_miL[idx_i] expected[idx_c] = np.exp(-alphas[idx_c] * xb) expected *= -phg * alphas / D expected -= minority / L result = get_J_sc_diffusion_green(xa, xb, lambda x: alphas * np.exp(-alphas * x), D, L, minority, s, phg, side='top') assert (xb - xa) / L > 1.e2 assert result == approx(expected, rel=1.e-5)
import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from solcore.light_source import LightSource from solcore.solar_cell import SolarCell from solcore.solar_cell_solver import solar_cell_solver from solcore.structure import Junction # Illumination spectrum wl = np.linspace(300, 4000, 4000) * 1e-9 light = LightSource(source_type="standard", version="AM1.5g", x=wl, output_units="photon_flux_per_m") T = 298 V = np.linspace(0, 5, 500) # This function assembles the solar cell and calculates the IV cruve def solve_MJ(EgBot, EgMid, EgTop): db_junction0 = Junction(kind="DB", T=T, Eg=EgBot, A=1, R_shunt=np.inf, n=3.5) db_junction1 = Junction(kind="DB", T=T, Eg=EgMid,
def da_light_source(): from solcore.light_source import LightSource light_source = LightSource(source_type="standard", version="AM1.5g") return light_source
def plot(self, x): light_source = LightSource(source_type='standard', version='AM1.5g') wl = np.linspace(300, 1850, 500) * 1e-9 solar_cell = self.make_cell(x) position = [1e-10] * 10 + [5e-8] V = np.linspace(0, 3.5, 300) solar_cell_solver(solar_cell, 'iv', user_options={ 'voltages': V, 'light_iv': True, 'wavelength': wl, 'mpp': True, 'light_source': light_source, 'optics_method': 'TMM', 'BL_correction': True, 'position': position }) efficiency = solar_cell.iv["Eta"] pmax = solar_cell.iv["Pmpp"] ff = solar_cell.iv["FF"] voc = solar_cell.iv["Voc"] isc = solar_cell.iv["Isc"] plt.figure() plt.plot(V, solar_cell.iv['IV'][1] / 10, 'k', linewidth=3, label='Total') plt.plot(V, -solar_cell[2].iv(V) / 10, 'b', label='GaInP') plt.plot(V, -solar_cell[3].iv(V) / 10, 'g', label='GaAs') plt.plot(V, -solar_cell[4].iv(V) / 10, 'r', label='SiGeSn') plt.plot(V, -solar_cell[5].iv(V) / 10, 'y', label='Ge') plt.text(2, 10, '$\eta = $' + str(round(efficiency * 100, 1)) + '%') plt.text(2, 8, 'Pmax=' + str(round(pmax, 1)) + 'W/m$^2$') plt.text(2, 9, 'FF = ' + str(round(ff * 100, 1)) + '%') plt.text(2, 7, 'Voc=' + str(round(voc, 1)) + 'V') plt.text(2, 6, 'Jsc=' + str(round(0.1 * isc, 1)) + 'mA/cm$^2$') plt.legend() plt.ylim(0, 18) plt.xlim(0, 3.5) plt.ylabel('Current (mA/cm$^2$)') plt.xlabel('Voltage (V)') plt.show() # print('Efficiency =', efficiency) solar_cell_solver(solar_cell, 'qe', user_options={ 'wavelength': wl, 'optics_method': 'TMM', 'BL_correction': True, 'position': position }) plt.figure() plt.plot(wl * 1e9, solar_cell[2].eqe(wl) * 100, 'b', label='InGaP') plt.plot(wl * 1e9, solar_cell[3].eqe(wl) * 100, 'g', label='InGaAs') plt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'r', label='SiGeSn') plt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'y', label='Ge') plt.plot(wl * 1e9, solar_cell.absorbed * 100, 'k--', label='Absorption') # plt.plot(wl * 1e9, solar_cell[5].eqe(wl)*100, 'y', label='Ge') plt.legend(loc='upper right') plt.xlim(290, 1850) plt.ylim(0, 100) plt.ylabel('EQE (%)') plt.xlabel('Wavelength (nm)') plt.show()
import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from solcore.light_source import LightSource from solcore.solar_cell import SolarCell from solcore.solar_cell_solver import solar_cell_solver from solcore.structure import Junction # Illumination spectrum wl = np.linspace(300, 4000, 4000) * 1e-9 light = LightSource(source_type='standard', version='AM1.5g', x=wl, output_units='photon_flux_per_m') T = 298 V = np.linspace(0, 5, 500) # This function assembles the solar cell and calculates the IV cruve def solve_MJ(EgBot, EgMid, EgTop): db_junction0 = Junction(kind='DB', T=T, Eg=EgBot, A=1, R_shunt=np.inf, n=3.5) db_junction1 = Junction(kind='DB', T=T, Eg=EgMid, A=1, R_shunt=np.inf, n=3.5) db_junction2 = Junction(kind='DB', T=T, Eg=EgTop, A=1, R_shunt=np.inf, n=3.5) my_solar_cell = SolarCell([db_junction2, db_junction1, db_junction0], T=T, R_series=0) solar_cell_solver(my_solar_cell, 'iv', user_options={'T_ambient': T, 'db_mode': 'top_hat', 'voltages': V, 'light_iv': True, 'internal_voltages': np.linspace(-6, 5, 1100), 'wavelength': wl, 'mpp': True, 'light_source': light}) return my_solar_cell
def qe_depletion(junction, options): """ Calculates the QE curve of a junction object using the depletion approximation as described in J. Nelson, “The Physics of Solar Cells”, Imperial College Press (2003). The junction is then updated with an "iqe" and several "eqe" functions that calculates the QE curve at any wavelength. :param junction: A junction object. :param options: Solver options. :return: None. """ science_reference( 'Depletion approximation', 'J. Nelson, “The Physics of Solar Cells”, Imperial College Press (2003).' ) # First we have to figure out if we are talking about a PN, NP, PIN or NIP junction T = options.T kbT = kb * T id_top, id_bottom, pRegion, nRegion, iRegion, pn_or_np = identify_layers( junction) xn, xp, xi, sn, sp, ln, lp, dn, dp, Nd, Na, ni, es = identify_parameters( junction, T, pRegion, nRegion, iRegion) niSquared = ni**2 Vbi = (kbT / q) * np.log(Nd * Na / niSquared) if not hasattr( junction, "Vbi") else junction.Vbi # Jenny p146 wn, wp = get_depletion_widths(junction, es, Vbi, 0, Na, Nd, xi) # Now it is time to calculate currents if pn_or_np == "pn": l_top, l_bottom = ln, lp 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: l_bottom, l_top = ln, lp 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 widths = [] for layer in junction: widths.append(layer.width) cum_widths = np.cumsum([0] + widths) g = junction.absorbed wl = options.wavelength wl_sp, ph = LightSource(source_type='black body', x=wl, T=6000).spectrum(output_units='photon_flux_per_m', x=wl) # The contribution from the Emitter (top side). xa = cum_widths[id_top] xb = cum_widths[id_top + 1] - w_top if options.da_mode == 'bvp': deriv = get_J_sc_diffusion_vs_WL(xa, xb, g, d_top, l_top, min_top, s_top, wl, ph, side='top') else: xbb = xb - (xb - xa) / 1001. deriv = get_J_sc_diffusion_green(xa, xbb, g, d_top, l_top, min_top, s_top, ph, side='top') j_sc_top = d_top * abs(deriv) # The contribution from the Base (bottom side). xa = cum_widths[id_bottom] + w_bottom xb = cum_widths[id_bottom + 1] if options.da_mode == 'bvp': deriv = get_J_sc_diffusion_vs_WL(xa, xb, g, d_bottom, l_bottom, min_bot, s_bottom, wl, ph, side='bottom') else: xbb = xb - (xb - xa) / 1001. deriv = get_J_sc_diffusion_green(xa, xbb, g, d_bottom, l_bottom, min_bot, s_bottom, ph, side='bottom') j_sc_bot = d_bottom * abs(deriv) # The contribution from the SCR (includes the intrinsic region, if present). xa = cum_widths[id_top + 1] - w_top xb = cum_widths[id_bottom] + w_bottom j_sc_scr = get_J_sc_SCR_vs_WL(xa, xb, g, wl, ph) # The total light absorbed, but not necessarily collected, is: xa = cum_widths[id_top] xb = cum_widths[id_bottom + 1] zz = np.linspace(xa, xb, 10001) gg = g(zz) * ph current_absorbed = np.trapz(gg, zz, axis=0) # Now, we put everything together j_sc = j_sc_top + j_sc_bot + j_sc_scr eqe = j_sc / ph eqe_emitter = j_sc_top / ph eqe_base = j_sc_bot / ph eqe_scr = j_sc_scr / ph iqe = j_sc / current_absorbed iqe[np.isnan( iqe )] = 0 # if zero current_absorbed, get NaN in previous line; want 0 IQE junction.iqe = interp1d(wl, iqe) junction.eqe = interp1d(wl, eqe, kind='linear', bounds_error=False, assume_sorted=True, fill_value=(eqe[0], eqe[-1])) junction.eqe_emitter = interp1d(wl, eqe_emitter, kind='linear', bounds_error=False, assume_sorted=True, fill_value=(eqe_emitter[0], eqe_emitter[-1])) junction.eqe_base = interp1d(wl, eqe_base, kind='linear', bounds_error=False, assume_sorted=True, fill_value=(eqe_base[0], eqe_base[-1])) junction.eqe_scr = interp1d(wl, eqe_scr, kind='linear', bounds_error=False, assume_sorted=True, fill_value=(eqe_scr[0], eqe_scr[-1])) junction.qe = State({ 'WL': wl, 'IQE': junction.iqe(wl), 'EQE': junction.eqe(wl), 'EQE_emitter': junction.eqe_emitter(wl), 'EQE_base': junction.eqe_base(wl), 'EQE_scr': junction.eqe_scr(wl) })
Layer(width=si('200nm'), material=bsf, role="BSF") ], sn=1e6, sp=1e6, T=T, kind='PDD') return output Vin = np.linspace(-2, 2.61, 201) V = np.linspace(0, 2.6, 300) wl = np.linspace(350, 1000, 301) * 1e-9 light_source = LightSource(source_type='standard', version='AM1.5g', x=wl, output_units='photon_flux_per_m', concentration=1) class TestPDD(TestCase): def test_92_light_iv(self): answer = [ 141.434980729, 2.46952886616, 0.91434377329, 319.359951949, 2.29565217391, 139.115130584, 0.319241623262 ] with tempfile.TemporaryDirectory( prefix="tmp", suffix="_sc3TESTS") as working_directory: filename = os.path.join(working_directory, 'solcore_log.txt') PDD.log(filename)
R_per_pass = np.sum(results_per_pass['r'][0], 2) R_0 = R_per_pass[0] R_escape = np.sum(R_per_pass[1:, :], 0) # only select absorbing layers, sum over passes results_per_layer_front = np.sum(results_per_pass['a'][0], 0)[:,[0,1,3,5,7,8]] results_per_layer_back = np.sum(results_per_pass['a'][1], 0) allres = np.flip(np.hstack((R_0[:,None], R_escape[:,None], results_per_layer_front, RAT['A_bulk'].T, results_per_layer_back, RAT['T'].T)),1) # calculated photogenerated current (Jsc with 100% EQE) spectr_flux = LightSource(source_type='standard', version='AM1.5g', x=wavelengths, output_units='photon_flux_per_m', concentration=1).spectrum(wavelengths)[1] Jph_Si = q * np.trapz(RAT['A_bulk'][0] * spectr_flux, wavelengths)/10 # mA/cm2 Jph_Perovskite = q * np.trapz(results_per_layer_front[:,3] * spectr_flux, wavelengths)/10 # mA/cm2 pal = sns.cubehelix_palette(13, start=.5, rot=-.9) pal.reverse() from scipy.ndimage.filters import gaussian_filter1d ysmoothed = gaussian_filter1d(allres, sigma=2, axis=0) bulk_A_text= ysmoothed[:,4] # plot total R, A, T fig = plt.figure(figsize=(5,4))
precedence goes to key value pairs in latter dicts. """ result = State() for dictionary in dict_args: result.update(dictionary) return result # General default_options.T_ambient = 298 default_options.T = 298 # Illumination spectrum default_options.wavelength = np.linspace(300, 1800, 251) * 1e-9 default_options.light_source = LightSource(source_type='standard', version='AM1.5g', x=default_options.wavelength, output_units='photon_flux_per_m') # IV control default_options.voltages = np.linspace(0, 1.2, 100) default_options.mpp = False default_options.light_iv = False default_options.internal_voltages = np.linspace(-6, 4, 1000) default_options.position = None default_options.radiative_coupling = False # Optics control default_options.optics_method = 'BL' default_options.recalculate_absorption = False default_options = merge_dicts(default_options, ASC.db_options, PDD.pdd_options,
Layer(width=110e-9, material=MgF2, role="ARC1"), Layer(width=60e-9, material=ZnS, role="ARC2"), Layer(width=30e-9, material=window_top, role="window"), GaInP_junction, Layer(width=100e-9, material=bsf_top, role="BSF"), tunnel, GaAs_junction, ], T=T, substrate=n_GaAs, ) light_source = LightSource( source_type="standard", version="AM1.5g", x=wl, output_units="photon_flux_per_m", concentration=1, ) # The definitions are all done, so we just start solving the properties, # starting with the QE. We calculate the QE curve under illumination solar_cell_solver( my_solar_cell, "qe", user_options={ "light_source": light_source, "wavelength": wl, "optics_method": "TMM", }, )
import numpy as np import matplotlib.pyplot as plt from solcore import siUnits, material, si from solcore.solar_cell import SolarCell from solcore.structure import Junction, Layer from solcore.solar_cell_solver import solar_cell_solver, default_options from solcore.light_source import LightSource from solcore.state import State incidence_angle = 45 # should be in degrees wl = np.linspace(290, 1900, 400) * 1e-9 concX = 566 # the light concentration light_source = LightSource( source_type='standard', version='AM1.5d', x=default_options.wavelength, output_units='photon_flux_per_m', concentration=concX) # define the input light source as AM1.5G all_materials = [] Al2O3 = material('Al2O3') TiO2 = material('TiO2') AlInP = material("AlInP") GaInP = material("GaInP") GaAs = material('GaAs') Ge = material("Ge") Al02Ga08As = material('AlGaAs') Al08Ga02As = material('AlGaAs') # TOP CELL - GaInP
wavelengths = np.linspace(250, 1100, 80) * 1e-9 RCWA_wl = wavelengths ropt = dict(LatticeTruncation='Circular', DiscretizedEpsilon=True, DiscretizationResolution=4, PolarizationDecomposition=False, PolarizationBasis='Default', LanczosSmoothing=True, SubpixelSmoothing=True, ConserveMemory=False, WeismannFormulation=True) light_source = LightSource(source_type='standard', version='AM0') options = State() options['rcwa_options'] = ropt options.optics_method = 'RCWA' options.wavelength = wavelengths options.light_source = light_source options.pol = 's' options.mpp = True options.light_iv = True options.position = 1e-10 options.voltages = np.linspace(-1.5, 1.5, 100) options.size = size options.orders = 20 options.parallel = True