def ProcessStructure(device, meshpoints, wavelengths=None, use_Adachi=False): """ This function reads a dictionary containing all the device structure, extract the electrical and optical properties of the materials, and loads all that information into the Fortran variables. Finally, it initiallise the device (in fortran) calculating an initial mesh and all the properties as a function of the possition. :param device: A dictionary containing the device structure. See PDD.DeviceStructure :param wavelengths: (Optional) Wavelengths at which to calculate the optical properties. :param use_Adachi: (Optional) If Adachi model should be use to calculate the dielectric constant of the material. :return: Dictionary containing the device structure properties as a function of the position. """ print('Processing structure...') # First, we clean any previous data from the Fortran code dd.reset() output = {} if wavelengths is not None: output['Optics'] = {} output['Optics']['wavelengths'] = wavelengths # We dump the structure information to the Fotran module and initialise the structure i = 0 while i < device['numlayers']: layer = device['layers'][i]['properties'] args_list = [ layer['width'], asUnit(layer['band_gap'], 'eV'), asUnit(layer['electron_affinity'], 'eV'), layer['electron_mobility'], layer['hole_mobility'], layer['Nc'], layer['Nv'], layer['electron_minority_lifetime'], layer['hole_minority_lifetime'], layer['permittivity'] * Epsi0, layer['radiative_recombination'], layer['electron_auger_recombination'], layer['hole_auger_recombination'], layer['Na'], layer['Nd'] ] dd.addlayer(args=args_list) i = i + device['layers'][i]['numlayers'] # We set the surface recombination velocities. This needs to be improved at some point # to consider other boundary conditions dd.frontboundary("ohmic", device['sn'], device['sp'], 0) dd.backboundary("ohmic", device['sn'], device['sp'], 0) dd.initdevice(meshpoints) print('...done!\n') output['Properties'] = DumpInputProperties() return output
def test_64_quantum_mechanics_schrodinger(self): bulk = material("GaAs")(T=293) barrier = material("GaAsP")(T=293, P=0.1) bulk.strained = False barrier.strained = True top_layer = Layer(width=si("30nm"), material=bulk) inter = Layer(width=si("3nm"), material=bulk) barrier_layer = Layer(width=si("15nm"), material=barrier) bottom_layer = top_layer E = np.linspace(1.15, 1.5, 300) * q alfas = np.zeros((len(E), 6)) alfas[:, 0] = E / q alpha_params = { "well_width": si("7.2nm"), "theta": 0, "eps": 12.9 * vacuum_permittivity, "espace": E, "hwhm": si("6meV"), "dimensionality": 0.16, "line_shape": "Gauss" } QW = material("InGaAs")(T=293, In=0.2) QW.strained = True well_layer = Layer(width=si("7.2nm"), material=QW) my_structure = Structure( [top_layer, barrier_layer, inter] + 1 * [well_layer, inter, barrier_layer, inter] + [bottom_layer], substrate=bulk) band_edge, bands = QM.schrodinger(my_structure, quasiconfined=0, num_eigenvalues=20, alpha_params=alpha_params, calculate_absorption=True) for key in band_edge['E']: for i in range(len(band_edge['E'][key])): band_edge['E'][key][i] = solcore.asUnit( band_edge['E'][key][i], 'eV') * 1000 band_edge['E'][key][i] = round(band_edge['E'][key][i]) Ehh = np.all(np.equal(band_edge['E']['Ehh'], my_energies['Ehh'])) Elh = np.all(np.equal(band_edge['E']['Elh'], my_energies['Elh'])) Ee = np.all(np.equal(band_edge['E']['Ee'], my_energies['Ee'])) idx = 100 out = [band_edge['alpha'][0][idx] / q, band_edge['alpha'][1][idx]] # Test over the energies self.assertTrue(Ehh and Elh and Ee) # Test over the absorption coefficent at a given energy for i, data in enumerate(out): self.assertAlmostEqual(out[i], my_absorption[i])
def _add_solcore_layer(self, layer): """ Adds a Solcore layer to the end (bottom) of the stack, extracting its thickness and n and k data. :param layer: The Solcore layer :return: None """ self.widths.append(solcore.asUnit(layer.width, 'nm')) self.models.append([]) self.n_data.append(np_cache(layer.material.n)) self.k_data.append(np_cache(layer.material.k))
def strain_calculation_parameters(substrate_material, layer_material, should_print=False, SO=False): """Will extract materials parameters and perform a bit of conditioning to the values. Returns a solcore State object with the following keys: -- ac, the conduction band deformation potential -- av, the valence band deformation potential -- b, the valence band splitting deformation potential -- C11, element of the elastic stiffness tensor -- C12, element of the elastic stiffness tensor -- a0, the unstrained substrate lattice constant -- a, the unstrained layer lattice constant -- epsilon, in-plain strain -- epsilon_perp, out-plain strain -- e_xx, in-plain strain (e_xx = epsilon) -- e_yy, in-plain strain (e_yy = epsilon) -- e_zz, out-plain strain (e_zz = epsilon_perp) -- Tre, element of come matrix (Tre = e_xx + e_yy + e_zz) -- Pe, parameter use by Chuang -- Qe, parameter use by Chuang -- cb_hydrostatic_shift, CB moved by this amount -- vb_hydrostatic_shift, VB moved by this amount -- vb_shear_splitting, VB split by this amount (i.e. HH/LH separation) -- delta_Ec, final conduction band shift -- delta_Elh, final light hole band shift -- delta_Ehh, final heavy hole band shift Care has to be taken when calculating the energy shifts because different sign conversion are used by different authors. Here we use the approach of S. L. Chuang, 'Physics of optoelectronic devices'. Note that this requires that the 'a_v' deformation potential to be positive, where as Vurgaftman defines this a negative! """ sub = substrate_material mat = layer_material k = State() # deformation potentials k.av = abs(mat.a_v) # make sure that av is positive for this calculation k.ac = mat.a_c # Vurgaftman uses the convention that this is negative k.b = mat.b # Matrix elements from elastic stiffness tensor k.C11 = mat.c11 k.C12 = mat.c12 if should_print: print(sub, mat) # Strain fractions k.a0 = sub.lattice_constant k.a = mat.lattice_constant k.epsilon = (k.a0 - k.a) / k.a # in-plain strain k.epsilon_perp = -2 * k.C12 / k.C11 * k.epsilon # out-plain k.e_xx = k.epsilon k.e_yy = k.epsilon k.e_zz = k.epsilon_perp k.Tre = (k.e_xx + k.e_yy + k.e_zz) k.Pe = -k.av * k.Tre k.Qe = -k.b / 2 * (k.e_xx + k.e_yy - 2 * k.e_zz) k.cb_hydrostatic_shift = k.ac * k.Tre k.vb_hydrostatic_shift = k.av * k.Tre k.vb_shear_splitting = 2 * k.b * (1 + 2 * k.C12 / k.C11) * k.epsilon # Shifts and splittings k.delta_Ec = k.ac * k.Tre if should_print: print(k.ac, k.Tre) k.delta_Ehh = -k.Pe - k.Qe k.delta_Elh = -k.Pe + k.Qe k.delta_Eso = 0.0 if SO: k.delta = mat.spin_orbit_splitting shift = k.delta**2 + 2 * k.delta * k.Qe + 9 * k.Qe**2 k.delta_Elh = -k.Pe + 0.5 * (k.Qe - k.delta + np.sqrt(shift)) k.delta_Eso = -k.Pe + 0.5 * (k.Qe - k.delta - np.sqrt(shift)) strain_calculation_asserts(k, should_print=should_print) if should_print: print() print("Lattice:") print("a0", k.a0) print("a", k.a) print() print("Deformation potentials:") print("ac = ", solcore.asUnit(k.ac, 'eV')) print("av = ", solcore.asUnit(k.av, 'eV')) print("ac - av = ", solcore.asUnit(k.ac - k.av, 'eV')) print("b = ", solcore.asUnit(k.b, 'eV')) print() print("Matrix elements from elastic stiffness tensor:") print("C_11 = ", solcore.asUnit(k.C11, "GPa")) print("C_12 = ", solcore.asUnit(k.C12, "GPa")) print() print("Strain fractions:") print("e_xx = e_yy = epsilon = ", k.epsilon) print("e_zz = epsilon_perp = ", k.epsilon_perp) print("e_xx + e_yy + e_zz = Tre = ", k.Tre) print() print("Shifts and splittings:") print("Pe = -av * Tre = ", solcore.asUnit(k.Pe, 'eV')) print("Qe = -b/2*(e_xx + e_yy - 2*e_zz) = ", solcore.asUnit(k.Qe, 'eV')) print("dEc = ac * Tre = ", solcore.asUnit(k.delta_Ec, 'eV')) print("dEhh = av * Tre + b[1 + 2*C_11/C_12]*epsilon = -Pe - Qe = ", solcore.asUnit(k.delta_Ehh, 'eV')) print("dElh = av * Tre - b[1 + 2*C_11/C_12]*epsilon = -Pe + Qe = ", solcore.asUnit(k.delta_Elh, 'eV')) print() return k
except ValueError: raise ValueError("Critical thickness infinite?") result.append(convert(hc / 4, "Ang", final_unit)) if during_growth else result.append( convert(hc, "Ang", final_unit)) return array(result) if __name__ == "__main__": T = linspace(300, 1000, 10) from solcore.graphing import * result = critical_thickness(layer_material="GaInAs", lattice_material="GaAs", layer_fraction=0.24, T=T, final_unit="nm") g = Graph( GraphData(T, asUnit(result, 'nm'), label="Matthews-Blakeslee $\\frac{h_c}{4}$"), # , "-", 1,"red"), # yscale="log", # xlim=(0.03,0.53), # ylim=(1,1000), xlabel="Temperature (K)", ylabel="Critical Thickness (nm)", # grid=True, title="InGaAs critical thickness" # labels = ([(x_at_024,y_at_024,"x=0.24, $\\frac{h_c}{4}$=%.2fnm"%y_at_024)]) ) g.draw() T = 300 x = linspace(0, 1, 100)[1:] result = critical_thickness(layer_material="GaInAs", lattice_material="GaAs", layer_fraction=x, T=T, final_unit="nm") print(result)
{"__builtins__": self.builtins_replacement}, others) in_si_units = siUnits(non_converted_unit, units) if use_units else non_converted_unit return in_si_units def __assemble_builtins(self): self.builtins_replacement = {"max": numpy.max, "min": numpy.min} self.builtins_replacement.update(math.__dict__) if __name__ == "__main__": import os v = ParameterSystem() v.add_source( "v", os.path.split(__file__)[0] + "/plugins/vurgaftman/builtins/endpoints.txt") v.add_source( "v2", os.path.split(__file__)[0] + "/plugins/vurgaftman/builtins/bowing_tree.txt") print( solcore.asUnit( v.get_parameter("GaAsSb.5", "band_gap", verbose=True, T=300), "eV")) print((v.get_parameter("GaAsSb.75", "band_gap", verbose=True, T=300), "eV")) print((v.get_parameter("GaAsSb.5", "band_gap", verbose=True, T=300), "eV"))
def test_quantum_mechanics_schrodinger(): """ Testing schrodinger equation solver """ bulk = material("GaAs")(T=293) barrier = material("GaAsP")(T=293, P=0.1) bulk.strained = False barrier.strained = True top_layer = Layer(width=si("30nm"), material=bulk) inter = Layer(width=si("3nm"), material=bulk) barrier_layer = Layer(width=si("15nm"), material=barrier) bottom_layer = top_layer E = np.linspace(1.15, 1.5, 300) * q alfas = np.zeros((len(E), 6)) alfas[:, 0] = E / q alpha_params = { "well_width": si("7.2nm"), "theta": 0, "eps": 12.9 * vacuum_permittivity, "espace": E, "hwhm": si("6meV"), "dimensionality": 0.16, "line_shape": "Gauss", } QW = material("InGaAs")(T=293, In=0.2) QW.strained = True well_layer = Layer(width=si("7.2nm"), material=QW) my_structure = Structure( [ top_layer, barrier_layer, inter, well_layer, inter, barrier_layer, inter, bottom_layer, ], substrate=bulk, ) band_edge, bands = QM.schrodinger( my_structure, quasiconfined=0, num_eigenvalues=20, alpha_params=alpha_params, calculate_absorption=True, ) for key in band_edge["E"]: for i in range(len(band_edge["E"][key])): band_edge["E"][key][i] = solcore.asUnit(band_edge["E"][key][i], "eV") * 1000 band_edge["E"][key][i] = round(band_edge["E"][key][i]) Ehh = np.all(np.equal(band_edge["E"]["Ehh"], my_energies["Ehh"])) Elh = np.all(np.equal(band_edge["E"]["Elh"], my_energies["Elh"])) Ee = np.all(np.equal(band_edge["E"]["Ee"], my_energies["Ee"])) idx = 100 out = [band_edge["alpha"][0][idx] / q, band_edge["alpha"][1][idx]] # Test over the energies assert Ehh and Elh and Ee # Test over the absorption coefficent at a given energy for i, data in enumerate(out): assert out[i] == approx(my_absorption[i], rel=1e-4)
zz = np.linspace(0, 1, 100) lat = np.zeros_like(zz) eg = np.zeros_like(zz) colors = plt.cm.jet(np.linspace(0, 1, len(mat))) fig = plt.figure(figsize=(6, 4.5)) ax = fig.add_subplot(111) for j, (m, x) in enumerate(zip(mat, xx)): for i, z in enumerate(zz): param = {x: z} new_mat = material(m)(T=300, **param) lat[i] = new_mat.lattice_constant * 1e10 eg[i] = asUnit(new_mat.band_gap, 'eV') ax.plot(lat, eg, label=m, color=colors[j]) lat2 = [] eg2 = [] for (m, p) in zip(mat2, pos): new_mat = material(m)(T=300) lat2.append(new_mat.lattice_constant * 1e10) eg2.append(asUnit(new_mat.band_gap, 'eV')) ax.annotate(m, xy=p) plt.plot(lat2, eg2, 'ko')
def ProcessStructure(device, meshpoints, wavelengths=None, use_Adachi=False): """ This function reads a dictionary containing all the device structure, extract the electrical and optical properties of the materials, and loads all that information into the Fortran variables. Finally, it initiallise the device (in fortran) calculating an initial mesh and all the properties as a function of the possition. :param device: A dictionary containing the device structure. See PDD.DeviceStructure :param wavelengths: (Optional) Wavelengths at which to calculate the optical properties. :param use_Adachi: (Optional) If Adachi model should be use to calculate the dielectric constant of the material. :return: Dictionary containing the device structure properties as a function of the position. """ print('Processing structure...') # First, we clean any previous data from the Fortran code dd.reset() output = {} if wavelengths is not None: output['Optics'] = {} output['Optics']['wavelengths'] = wavelengths calculate_absorption = True else: calculate_absorption = False # We dump the structure information to the Fotran module and initialise the structure i = 0 first = 0 last = -1 while i < device['numlayers']: if device['layers'][i]['label'] in [ 'optics', 'Optics', 'metal', 'Metal' ]: # Optics or metal layers. They are not included in the PDD solver and are just used for optics if i == first: first = i + 1 i = i + device['layers'][i]['numlayers'] continue else: last = i - device['numlayers'] - 1 break if device['layers'][i]['group'] is None: # We have a normal layer layer = device['layers'][i]['properties'] args_list = [ layer['width'], asUnit(layer['band_gap'], 'eV'), asUnit(layer['electron_affinity'], 'eV'), layer['electron_mobility'], layer['hole_mobility'], layer['Nc'], layer['Nv'], layer['electron_minority_lifetime'], layer['hole_minority_lifetime'], layer['permittivity'] * Epsi0, layer['radiative_recombination'], layer['electron_auger_recombination'], layer['hole_auger_recombination'], layer['Na'], layer['Nd'] ] dd.addlayer(args=args_list) # We load the absorption coeficients if necessary if calculate_absorption: if 'absorption' in layer.keys(): layer['absorption'][1] = np.interp( wavelengths, layer['absorption'][0], layer['absorption'][1]).tolist() layer['absorption'][0] = wavelengths.tolist() else: layer['absorption'] = LoadAbsorption(device['layers'][i], device['T'], wavelengths, use_Adachi=use_Adachi) dd.addabsorption(layer['absorption'][1], wavelengths) else: # We have a group of several layers, usually a QW with 'numlayers' repeated 'repeat' times. for j in range(device['layers'][i]['repeat']): for k in range(device['layers'][i]['numlayers']): layer = device['layers'][i + k]['properties'] args_list = [ layer['width'], asUnit(layer['band_gap'], 'eV'), asUnit(layer['electron_affinity'], 'eV'), layer['electron_mobility'], layer['hole_mobility'], layer['Nc'], layer['Nv'], layer['electron_minority_lifetime'], layer['hole_minority_lifetime'], layer['permittivity'] * Epsi0, layer['radiative_recombination'], layer['electron_auger_recombination'], layer['hole_auger_recombination'], layer['Na'], layer['Nd'] ] dd.addlayer(args=args_list) # We load the absorption coeficients if necessary if calculate_absorption: if 'absorption' in layer.keys(): layer['absorption'][1] = np.interp( wavelengths, layer['absorption'][0], layer['absorption'][1]).tolist() layer['absorption'][0] = wavelengths.tolist() else: layer['absorption'] = LoadAbsorption( device['layers'][i + k], device['T'], wavelengths, use_Adachi=use_Adachi) dd.addabsorption(layer['absorption'][1], wavelengths) i = i + device['layers'][i]['numlayers'] # We set the surface recombination velocities. This needs to be improved at some to consider other boundary conditions dd.frontboundary("ohmic", device['layers'][first]['properties']['sn'], device['layers'][first]['properties']['sp'], 0) dd.backboundary("ohmic", device['layers'][last]['properties']['sn'], device['layers'][last]['properties']['sp'], 0) dd.initdevice(meshpoints) print('...done!\n') output['Properties'] = DumpInputProperties() return output