def _add_raw_nk_layer(self, layer): """ Adds a layer to the end (bottom) of the stack. The layer must be defined as a list containing the layer thickness in nm, the wavelength, the n and the k data as array-like objects. :param layer: The new layer to add as [thickness, wavelength, n, k] :return: None """ # We make sure that the wavelengths are increasing, revering the arrays otherwise. if layer[1][0] > layer[1][-1]: layer[1] = layer[1][::-1] layer[2] = layer[2][::-1] layer[3] = layer[3][::-1] self.widths.append(layer[0]) if len(layer) >= 5: self.models.append(layer[4]) c = solcore.si(layer[5][0], 'nm') w = solcore.si(layer[5][1], 'nm') d = layer[5][2] # = 0 for increasing, =1 for decreasing def mix(x): out = 1 + np.exp(-(x - c) / w) out = d + (-1)**d * 1 / out return out n_data = np_cache(lambda x: self.models[-1].n_and_k(x) * mix(x) + ( 1 - mix(x)) * interp1d(x=solcore.si(layer[1], 'nm'), y=layer[2], fill_value=layer[2][-1])(x)) k_data = np_cache(lambda x: interp1d(x=solcore.si(layer[1], 'nm'), y=layer[3], fill_value=layer[3][-1])(x)) self.n_data.append(n_data) self.k_data.append(k_data) else: self.models.append([]) self.n_data.append( np_cache( interp1d(x=solcore.si(layer[1], 'nm'), y=layer[2], fill_value=layer[2][-1]))) self.k_data.append( np_cache( interp1d(x=solcore.si(layer[1], 'nm'), y=layer[3], fill_value=layer[3][-1])))
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_R(obj, x, icol): cols = sns.cubehelix_palette(len(use_orders), start=.5, rot=-.9, reverse=True) GaAs = material('GaAs_WVASE')() Air = material('Air')() InAlP = material('InAlP_WVASE')() InGaP = material('InGaP_WVASE')() Ag = material('Ag_Jiang')() # MgF2 = material('MgF2')() # Ta2O5 = material('410', nk_db=True)() SiN = material('SiN_SE')() R_data = np.loadtxt('Talbot_precursor_R.csv', delimiter=',') grating1 = [Layer(si(x[2] * x[1], 'nm'), SiN)] grating2 = [ Layer(si((1 - x[1]) * x[2], 'nm'), SiN, geometry=[{ 'type': 'circle', 'mat': Ag, 'center': (0, 0), 'radius': x[3], 'angle': 0 }]) ] solar_cell = SolarCell([ Layer(material=GaAs, width=si('25nm')), Layer(material=InGaP, width=si('19nm')), Layer(material=GaAs, width=si(x[0], 'nm')), Layer(material=InAlP, width=si('18nm')) ] + grating1 + grating2, substrate=Ag) S4_setup = rcwa_structure(solar_cell, obj.size, obj.orders, obj.options, Air, Ag) RAT = S4_setup.calculate() total_A = np.sum(RAT['A_layer'], axis=1) # plt.figure() plt.plot(S4_setup.wavelengths * 1e9, RAT['R'], label=str(obj.orders), color=cols[icol]) return S4_setup.wavelengths * 1e9, interp1d(R_data[:, 0], R_data[:, 1])( S4_setup.wavelengths * 1e9)
def __init__(self, orders): x = 500 self.orders = orders EQE_data = np.loadtxt('data/V2A_9_EQE.dat', skiprows=129) # anti-reflection coating #ARC1 = [Layer(si('60nm'), SiN)] self.size = ((x, 0), (x / 2, np.sin(np.pi / 3) * x)) wavelengths = np.linspace(303, 1000, 160) * 1e-9 RCWA_wl = wavelengths options = { 'nm_spacing': 0.5, 'n_theta_bins': 100, 'c_azimuth': 1e-7, 'pol': 's', 'wavelengths': RCWA_wl, 'theta_in': 0, 'phi_in': 0, 'parallel': True, 'n_jobs': -1, 'phi_symmetry': np.pi / 2, 'project_name': 'ultrathin' } ropt = dict(LatticeTruncation='Circular', DiscretizedEpsilon=True, DiscretizationResolution=4, PolarizationDecomposition=False, PolarizationBasis='Default', LanczosSmoothing=True, SubpixelSmoothing=True, ConserveMemory=False, WeismannFormulation=True) options['rcwa_options'] = ropt self.options = options spect = np.loadtxt('AM0.csv', delimiter=',') self.AM0 = interp1d(spect[:, 0], spect[:, 1])(wavelengths * 1e9) self.EQE_meas = interp1d(EQE_data[:, 0], EQE_data[:, 1])(wavelengths * 1e9)
def __init__(self, energy, e2): """ Constructor of the PolySegment oscillator. By default, the amplitude and broadening are set to zero meaning that there are no oscillator. :param energy: Energies (eV) :param e2: Epsilon_2 values at the corresponding energies (dimensionless) """ self.energy = energy self.e2 = e2 self.epsi2 = interp1d(x=energy, y=e2) self.var = len(e2)
def test_dark_iv_depletion_np(np_junction): from solcore.analytic_solar_cells.depletion_approximation import iv_depletion, get_depletion_widths, get_j_dark, get_Jsrh, identify_layers, identify_parameters from scipy.interpolate import interp1d test_junc, options = np_junction options.light_iv = False T = options.T test_junc[0].voltage = options.internal_voltages id_top, id_bottom, pRegion, nRegion, iRegion, pn_or_np = identify_layers(test_junc[0]) xn, xp, xi, sn, sp, ln, lp, dn, dp, Nd, Na, ni, es = identify_parameters(test_junc[0], T, pRegion, nRegion, iRegion) niSquared = ni**2 kbT = kb * T Vbi = (kbT / q) * np.log(Nd * Na / niSquared) V = np.where(test_junc[0].voltage < Vbi - 0.001, test_junc[0].voltage, Vbi - 0.001) wn, wp = get_depletion_widths(test_junc[0], es, Vbi, V, Na, Nd, xi) w = wn + wp + xi l_bottom, l_top = ln, lp x_bottom, x_top = xp, xn 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 JtopDark = get_j_dark(x_top, w_top, l_top, s_top, d_top, V, min_top, T) JbotDark = get_j_dark(x_bottom, w_bottom, l_bottom, s_bottom, d_bottom, V, min_bot, T) JpDark, JnDark = JbotDark, JtopDark lifetime_n = ln ** 2 / dn lifetime_p = lp ** 2 / dp # Jenny p163 Jrec = get_Jsrh(ni, V, Vbi, lifetime_p, lifetime_n, w, kbT) J_sc_top = 0 J_sc_bot = 0 J_sc_scr = 0 current = Jrec + JnDark + JpDark + V / 1e14- J_sc_top - J_sc_bot - J_sc_scr iv = interp1d(test_junc[0].voltage, current, kind='linear', bounds_error=False, assume_sorted=True, fill_value=(current[0], current[-1]), copy=True) iv_depletion(test_junc[0], options) assert test_junc[0].iv(options.internal_voltages) == approx(iv(options.internal_voltages), nan_ok=True)
from solcore.interpolate import interp1d from solcore.solar_cell import SolarCell from solcore.structure import Junction, Layer from solcore.solar_cell_solver import solar_cell_solver all_materials = [] def this_dir_file(f): return os.path.join(os.path.split(__file__)[0], f) # We need to build the solar cell layer by layer. # We start from the AR coating. In this case, we load it from an an external file refl_nm = np.loadtxt(this_dir_file("MgF-ZnS_AR.csv"), unpack=True, delimiter=",") ref = interp1d(x=siUnits(refl_nm[0], "nm"), y=refl_nm[1], bounds_error=False, fill_value=0) # TOP CELL - GaInP # Now we build the top cell, which requires the n and p sides of GaInP and a window layer. # We also load the absorption coefficient from an external file. We also add some extra parameters needed for the # calculation such as the minority carriers diffusion lengths AlInP = material("AlInP") InGaP = material("GaInP") window_material = AlInP(Al=0.52) top_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, "cm-3"), hole_diffusion_length=si("200nm")) top_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, "cm-3"), electron_diffusion_length=si("1um")) all_materials.append(window_material) all_materials.append(top_cell_n_material) all_materials.append(top_cell_p_material)
def __init__(self): GaAs = material('GaAs_WVASE')() Air = material('Air')() InAlP = material('InAlP_WVASE')() InGaP = material('InGaP_WVASE')() Ag = material('Ag_Jiang')() SiN = material('SiN_SE')() x = 600 # anti-reflection coating ARC1 = [Layer(si('60nm'), SiN)] size = ((x, 0), (x / 2, np.sin(np.pi / 3) * x)) wavelengths = np.linspace(250, 930, 200) * 1e-9 RCWA_wl = wavelengths options = { 'nm_spacing': 0.5, 'n_theta_bins': 100, 'c_azimuth': 1e-7, 'pol': 's', 'wavelengths': RCWA_wl, 'theta_in': 0, 'phi_in': 0, 'parallel': True, 'n_jobs': -1, 'phi_symmetry': np.pi / 2, 'project_name': 'ultrathin' } ropt = dict(LatticeTruncation='Circular', DiscretizedEpsilon=True, DiscretizationResolution=4, PolarizationDecomposition=False, PolarizationBasis='Default', LanczosSmoothing=True, SubpixelSmoothing=True, ConserveMemory=False, WeismannFormulation=True) options['rcwa_options'] = ropt grating = [ Layer(si(100, 'nm'), SiN, geometry=[{ 'type': 'circle', 'mat': Ag, 'center': (0, 0), 'radius': 100, 'angle': 0 }]) ] solar_cell = SolarCell(ARC1 + [ Layer(material=InGaP, width=si('19nm')), Layer(material=GaAs, width=si('86nm')), Layer(material=InAlP, width=si('18nm')) ] + grating) self.S4_setup = rcwa_structure(solar_cell, size, 37, options, Air, Ag) spect = np.loadtxt('AM0.csv', delimiter=',') self.AM0 = interp1d(spect[:, 0], spect[:, 1])(wavelengths * 1e9)
def schrodinger(structure, plot_bands=False, kpoints=40, krange=1e9, num_eigenvalues=10, symmetric=True, quasiconfined=0.0, return_qw_boolean_for_layer=False, Efield=0, blur=False, blurmode="even", mode='kp8x8_bulk', step_size=None, minimum_step_size=0, smallest_feature_steps=20, filter_strength=0, periodic=False, offset=0, graphtype=[], calculate_absorption=False, alpha_params=None): """ Solves the Schrodinger equation of a 1 dimensional structure. Depending on the inputs, the method for solving the problem is more or less sophisticated. In all cases, the output includes the band structure and effective masses around k=0 as a function of the position, the energy levels of the QW (electrons and holes) and the wavefunctions, although for the kp4x4 and kp6x6 modes, this is provided as a function of the k value, and therefore there is much more information. :param structure: The strucutre to solve :param plot_bands: (False) If the bands should be plotted :param kpoints: (30) The number of points in the k.txt space :param krange: (1e-9) The range in the k space :param num_eigenvalues: (10) Maximum number of eigenvalues to calculate :param symmetric: (True) If the structure is symmetric, in which case the calculation can be speed up :param quasiconfined: (0.0) Energy above the band edges that an energy level can have before rejecting it :param return_qw_boolean_for_layer: (False) Return an boolean array indicating which positions are inside the QW :param Efield: (0) Electric field. :param blur: (False) If the potentials and effective masses have to be blurred :param blurmode: ('even') Other values are 'right' and 'left' :param mode: ('kp4x4') The mode of calculating the bands and effective masses. See 'structure_utilities.structure_to_potentials' :param step_size: (None) The discretization step of the structure. If none, it is estimated based on the smallest feature. :param minimum_step_size: (0) The minimum step size. :param smallest_feature_steps: (20) The number of steps in the smallest feature :param filter_strength: (0) If > 0, defines the fraction of the wavefunction that has to be inside the QW in order to consider it 'confined' :param periodic: (False) If the strucuture is periodic. Affects the boundary conditions. :param offset: (0) Energy offset used in the calculation of the energy levels in the case of the 'bulk' solvers :param graphtype: [] If 'potential', the band profile and wavefunctions are ploted :return: A dictionary containing the band structure and wavefunctions as a function of the position and k """ if Efield != 0: symmetric = False aligned_structure = VBO_align(structure) potentials = structure_to_potentials( aligned_structure, return_qw_boolean_for_layer=return_qw_boolean_for_layer, Efield=Efield, blur=blur, blurmode=blurmode, mode=mode, step_size=step_size, minimum_step_size=minimum_step_size, smallest_feature_steps=smallest_feature_steps) # Now that we have the potential and effective masses ready, we solve the problem. if mode in ['kp4x4', 'kp6x6']: bands = solve_bandstructure_QW(potentials, num=num_eigenvalues, kpoints=kpoints, krange=krange, symmetric=symmetric, quasiconfined=quasiconfined, plot_bands=plot_bands) result_band_edge = { "x": bands['x'], "potentials": { key: potentials[key] for key in potentials.keys() if key[0] in "Vx" }, "effective_masses": { key: potentials[key] for key in potentials.keys() if key[0] in "mx" }, "wavefunctions": {key: bands[key][:, 0] for key in bands.keys() if 'psi' in key}, "E": {key: bands[key][:, 0] for key in bands.keys() if key[0] in 'E'}, } else: bands = potentials_to_wavefunctions_energies( structure=structure, num_eigenvalues=num_eigenvalues, filter_strength=filter_strength, offset=offset, periodic=periodic, quasiconfined=quasiconfined, **potentials) result_band_edge = { "x": bands['x'], "potentials": { key: potentials[key] for key in potentials.keys() if key[0] in "Vx" }, "effective_masses": { key: potentials[key] for key in potentials.keys() if key[0] in "mx" }, "wavefunctions": {key: bands[key] for key in bands.keys() if 'psi' in key}, "E": { key: np.array(bands[key]) for key in bands.keys() if key[0] in "E" }, } if "potentials" in graphtype: schrodinger_plt = graphics.split_schrodinger_graph(result_band_edge) schrodinger_plt.draw() if calculate_absorption: result_band_edge["alpha"] = calc_alpha(result_band_edge, **alpha_params) result_band_edge["alphaE"] = interp1d(x=result_band_edge["alpha"][0], y=result_band_edge["alpha"][1]) return result_band_edge, bands