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 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_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 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)
# 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: pass plt.xlim(300, 3000) plt.xlabel('Wavelength (nm)') plt.ylabel('Power density (Wm$^{-2}$nm$^{-1}$)') plt.tight_layout() plt.legend()