def parameterised_material(self, name): """ The function that actually creates the material class. """ if "x" in ParameterSystem().database.options(name): self.composition = [ParameterSystem().database.get(name, "x")] else: self.composition = [] class SpecificMaterial(BaseMaterial): material_string = name composition = self.composition def __init__(self, T=300, **kwargs): BaseMaterial.__init__(self, T=T, **kwargs) if name.lower() in self.sources.keys(): SpecificMaterial.material_directory = self.sources[name.lower()] extension = '' if len(SpecificMaterial.composition) == 0: extension = '.txt' SpecificMaterial.n_path = os.path.join( SpecificMaterial.material_directory, 'n' + extension) SpecificMaterial.k_path = os.path.join( SpecificMaterial.material_directory, 'k' + extension) SpecificMaterial.__name__ = name self.known_materials[name] = SpecificMaterial return SpecificMaterial
def __getattr__(self, attrname): # only used for unknown attributes. if attrname == "n": try: return self.n except: return self.n_interpolated if attrname == "k": try: return self.k except: return self.k_interpolated if attrname == "electron_affinity": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: # from http://en.wikipedia.org/wiki/Anderson's_rule and GaAs values return (0.17 + 4.59) * q - self.valence_band_offset - self.band_gap if attrname == "electron_mobility": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: return calculate_mobility(self.material_string, False, self.Nd, self.main_fraction) if attrname == "hole_mobility": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: return calculate_mobility(self.material_string, True, self.Na, self.main_fraction) if attrname == "Nc": return 2 * (2 * pi * self.eff_mass_electron_Gamma * m0 * kb * self.T / h**2)**1.5 if attrname == "Nv": # Strictly speaking, this is valid only for III-V, zinc-blend semiconductors mhh = self.eff_mass_hh_z mlh = self.eff_mass_lh_z Nvhh = 2 * (2 * pi * mhh * m0 * kb * self.T / h**2)**1.5 Nvlh = 2 * (2 * pi * mlh * m0 * kb * self.T / h**2)**1.5 return Nvhh + Nvlh if attrname == "ni": return np.sqrt(self.Nc * self.Nv * np.exp(-self.band_gap / (kb * self.T))) if attrname == "radiative_recombination": inter = lambda E: self.n(E)**2 * self.alphaE(E) * np.exp(-E / ( kb * self.T)) * E**2 upper = self.band_gap + 10 * kb * self.T return 1.0 / self.ni**2 * 2 * pi / (h**3 * c**2) * quad( inter, 0, upper)[0] kwargs = { element: getattr(self, element) for element in self.composition } kwargs["T"] = self.T return ParameterSystem().get_parameter(self.material_string, attrname, **kwargs)
def create_new_material(mat_name, n_source, k_source, parameter_source=None, overwrite=False): """ This function adds a new material to Solcore's material_data folder, so that it can be called like a built-in material. It needs a name for the new material, and source files for the n and k data and other parameters which will be copied into the material_data/Custom folder. :param mat_name: the name of the new material :param n_source: path of the n values (txt file, first column wavelength in m, second column n) :param k_source: path of the n values (txt file, first column wavelength in m, second column k) :param: parameter_source: file with list of parameters for the new material """ PARAMETER_PATH = os.path.join(config.user_folder, "custom_parameters.txt") if "custom" not in config.parameters(): if not os.path.isfile(PARAMETER_PATH): open(PARAMETER_PATH, "a").close() config["Parameters", "custom"] = PARAMETER_PATH CUSTOM_PATH = os.path.join(config.user_folder, "custom_materials") # check if there is already a material with this name if (mat_name in sorted(ParameterSystem().database.sections()) or mat_name in config.materials()) and not overwrite: answer = input( f"A material named {mat_name} already exists in the database." f"Do you want to overwrite it [y/n]?") if answer.lower() != "y": return # create a folder in the custom materials folders folder = os.path.join(CUSTOM_PATH, mat_name + "-Material") if not os.path.exists(folder) and folder != "": os.makedirs(folder) # copy n and k data files to the material's folder copyfile(n_source, os.path.join(folder, "n.txt")) copyfile(k_source, os.path.join(folder, "k.txt")) config["Materials", mat_name] = folder # append the parameters for the new material params = ConfigParser() params.optionxform = str if parameter_source is not None: params.read([PARAMETER_PATH, parameter_source]) with open(PARAMETER_PATH, "w") as fp: params.write(fp) else: params.read([PARAMETER_PATH]) params[mat_name] = {} with open(PARAMETER_PATH, "w") as fp: params.write(fp) print("Material created with optical constants n and k only.") ParameterSystem().read()
def __getattr__(self, attrname): # only used for unknown attributes if attrname == "n": try: return self.n except: return self.n_interpolated if attrname == "k": try: return self.k except: return self.k_interpolated if attrname == "electron_affinity": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: # from http://en.wikipedia.org/wiki/Anderson's_rule and GaAs values return (0.17 + 4.59) * q - self.valence_band_offset - self.band_gap if attrname == "electron_mobility": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: return calculate_mobility(self.material_string, False, self.Nd, self.main_fraction) if attrname == "hole_mobility": try: return ParameterSystem().get_parameter(self.material_string, attrname) except: return calculate_mobility(self.material_string, True, self.Na, self.main_fraction) kwargs = { element: getattr(self, element) for element in self.composition } kwargs["T"] = self.T return ParameterSystem().get_parameter(self.material_string, attrname, **kwargs)
UnitsSystem(config['Units']) # And now we load some functions form it. si = UnitsSystem().si asUnit = UnitsSystem().asUnit siUnits = UnitsSystem().siUnits sensibleUnits = UnitsSystem().sensibleUnits siUnitFromString = UnitsSystem().siUnitFromString convert = UnitsSystem().convert guess_dimension = UnitsSystem().guess_dimension nmJ = UnitsSystem().nmJ mJ = UnitsSystem().mJ eVnm = UnitsSystem().eVnm nmHz = UnitsSystem().nmHz spectral_conversion_nm_ev = UnitsSystem().spectral_conversion_nm_ev spectral_conversion_nm_hz = UnitsSystem().spectral_conversion_nm_hz eV = UnitsSystem().eV # And the same with the Parameter system from solcore.parameter_system import ParameterSystem ParameterSystem(config['Parameters']) get_parameter = ParameterSystem().get_parameter # And the same with the Materials system from solcore.material_system import MaterialSystem MaterialSystem(config['Materials']) material = MaterialSystem().material
def material(self, name, sopra=False, nk_db=False): """ This function checks if the requested material exists and creates a class that contains its properties, assuming that the material does not exists in the database, yet. Such class will serve as the base class for all the derived materials based on that SpecificMaterial. For example, N-type GaAs and P-type GaAs use the same SpecificMaterial, just with a different doping, and the happens with GaAs at 300K or 200K. The derived materials based on a SpecificMaterial are instances of the SpecificMaterial class. >>> GaAs = solcore.material('GaAs') # The SpecificMaterial class >>> n_GaAs = GaAs(Nd=1e23) # Instance of the class >>> p_GaAs = GaAs(Na=1e22) # Another instance of GaAs with different doping >>> AlGaAs = solcore.material('AlGaAs') # The SpecificMaterial class >>> AlGaAs_1 = AlGaAs(Al=0.3) # Instance of the class. For compounds, the variable element MUST be present >>> AlGaAs_2 = AlGaAs(Al=0.7, T=290) # Different composition and T (the default is T=300K) The material is created from the parameters in the parameter_system and the n and k data if available. If the n and k data does not exists - at all or for that composition - then n=1 and k=0 at all wavelengths. Keep in mind that the available n and k data is valid only at room temperature. :param name: Name of the material :param sopra: If a SOPRA material must be used, rather than the normal database material, in case both exist. :return: A class of that material """ suffix = '' # First we check if the material exists. If not, some help is provided try: if sopra: sopra_database(Material=name) suffix = '_sopra' elif nk_db: suffix = '_nk' else: ParameterSystem().database.options(name) except configparser.NoSectionError: try: sopra_database(Material=name) sopra = True suffix = '_sopra' except: valid_materials = sorted(ParameterSystem().database.sections()) valid_materials.remove('Immediate Calculables') valid_materials.remove('Final Calculables') print( '\nMaterial ERROR: "{}" is not in the semiconductors database or in the SOPRA database. Valid semiconductor materials are: ' .format(name)) for v in valid_materials: if "x" in ParameterSystem().database.options(v): x = ParameterSystem().database.get(v, 'x') print('\t {} \tx = {}'.format(v, x)) else: print('\t {}'.format(v)) print( '\nIn compounds, check that the order of the elements is the correct one (eg. GaInSb is OK but InGaSb' ' is not).') val = input( '\nDo you want to see the list of available SOPRA materials (y/n)?' ) if val in 'Yy': sopra_database.material_list() sys.exit() except solcore.absorption_calculator.sopra_db.SOPRAError: pass # Then we check if the material has already been created. If not, we create it. if name + suffix in self.known_materials: return self.known_materials[name + suffix] elif sopra: return self.sopra_material(name) elif nk_db: return self.nk_material(name) else: return self.parameterised_material(name)
def create_new_material(mat_name, n_source, k_source, parameter_source = None): """ This function adds a new material to Solcore's material_data folder, so that it can be called like a built-in material. It needs a name for the new material, and source files for the n and k data and other parameters which will be copied into the material_data/Custom folder. :param mat_name: the name of the new material :param n_source: path of the n values (txt file, first column wavelength in m, second column n) :param k_source: path of the n values (txt file, first column wavelength in m, second column k) :return: parameter_source: file with list of materials for the new material """ CUSTOM_PATH = os.path.abspath(config['Others']['custom_mats'].replace('SOLCORE_ROOT', SOLCORE_ROOT)) PARAMETER_PATH = os.path.abspath(config['Parameters']['custom'].replace('SOLCORE_ROOT', SOLCORE_ROOT)) # check if there is already a material with this name if mat_name not in sorted(ParameterSystem().database.sections()): # create a folder in the custom materials folders folder = os.path.join(CUSTOM_PATH, mat_name + '-Material') if not os.path.exists(folder) and folder != "": os.makedirs(folder) else: print('This material already exists (or at least, a folder for it).') # copy n and k data files to the material's folder copyfile(n_source, os.path.join(folder, 'n.txt')) copyfile(k_source, os.path.join(folder, 'k.txt')) # create the parameter file if it doesn't already exist if not os.path.isfile(PARAMETER_PATH): open(PARAMETER_PATH, 'a').close() # append the parameters for the new material fout = open(PARAMETER_PATH, "r") existing_parameters = fout.read() fout.close() if not '[' + mat_name + ']' in existing_parameters: # make sure the names match if parameter_source is not None: fin = open(parameter_source, "r") parameters = fin.read() + '\n\n' parameters = sub("\[[^]]*\]", lambda x: x.group(0).replace(x.group(0), '[' + mat_name + ']'), parameters) fin.close() else: parameters = '[' + mat_name + ']\n\n' print('Material created with optical constants n and k only, no other parameters provided.') fout = open(PARAMETER_PATH, "a") fout.write(parameters) fout.close() else: print('There are already parameters for this material in the custom parameter file at ' + PARAMETER_PATH) # modify the user's config file (in their home folder) to include the relevant paths new_entry = mat_name + ' = ' + config['Others']['custom_mats'] + '/' + mat_name + '-Material\n' home_folder = os.path.expanduser('~') user_config = os.path.join(home_folder, '.solcore_config.txt') existing_config = open(user_config, 'r').read() if not new_entry in existing_config: add_source('Materials', mat_name, config['Others']['custom_mats'] + '/' + mat_name + '-Material') else: print('A path for this material was already added to the Solcore config file in the home directory.') else: print('There is already a material with this name - choose a different one.')
# And now we load some functions form it. si = us.si asUnit = us.asUnit siUnits = us.siUnits sensibleUnits = us.sensibleUnits siUnitFromString = us.siUnitFromString convert = us.convert guess_dimension = us.guess_dimension nmJ = us.nmJ mJ = us.mJ eVnm = us.eVnm nmHz = us.nmHz spectral_conversion_nm_ev = us.spectral_conversion_nm_ev spectral_conversion_nm_hz = us.spectral_conversion_nm_hz eV = us.eV # And the same with the Parameter system from solcore.parameter_system import ParameterSystem ps = ParameterSystem(config.parameters) config.register_observer("Parameters", ps.read) get_parameter = ps.get_parameter # And the same with the Materials system from solcore.material_system import MaterialSystem ms = MaterialSystem(config.materials) config.register_observer("Materials", ms.read) material = ms.material