Source code for solcore.absorption_calculator.sopra_db

"""This module gives access to the vast library of optical constant data, made freely available by the SOPRA-SA
optoelectronics company founded in 1948. For further detail on the data and SOPRA-SA see the legacy website:
http://www.sspectra.com/sopra.html"""

import numpy as np
import os, sys
import re
from natsort import natsorted
from configparser import ConfigParser
from solcore.science_tracker import science_reference
from solcore import config, SOLCORE_ROOT

SOPRA_PATH = os.path.abspath(config['Others']['sopra'].replace('SOLCORE_ROOT', SOLCORE_ROOT))
compounds_path = os.path.join(SOPRA_PATH, "compounds.txt")
compounds_info = ConfigParser()
compounds_info.read(compounds_path)


# Defining the SOPRA_DB class variable
[docs]class sopra_database: """Import the SOPRA_DB module from the solcore.material_system package and get started by selecting a material from the extensive list that SOPRA-SA compiled; >>> GaAs = sopra_database('GaAs') Once imported a number of useful methods can be called to return n, k and alpha data for the desired material. """ science_reference("All optical constant data made avaialble by SOPRA-SA", "http://www.sspectra.com/sopra.html") def __init__(self, Material): # Define filepath to the SOPRA database for file import... self.__SOPRA_PATH = SOPRA_PATH # Load in SOPRA_DB.csv database file DB = np.genfromtxt(os.path.join(self.__SOPRA_PATH, "SOPRA_DB_Updated.csv"), delimiter=",", dtype=str) self.__fname = None for fname, symbol, range, info in DB: if re.fullmatch(Material.upper(), fname) is not None: self.__fname = fname self.path = os.path.join(self.__SOPRA_PATH, self.__fname + ".MAT") # self.info contains all detail loaded from SOPRA_DB,csv file... self.info = {"Material": symbol, "Wavelength (nm)": range, "File Info": info, "File Path": self.path} # If the material name is incorrect then the material attribute is not written to if self.__fname is None: print("SOPRA_DB :: ERROR :: Material not found in SOPRA_DB... Check materials list...") print("Similar Matches ::") for fname, symbol, range, info in DB: if re.match(Material.upper(), fname) is not None: print(fname) # If the exception is caught, exit the program as nothing else useful can be done... # sys.exit() raise SOPRAError("Material not found in SOPRA database: {}".format(Material))
[docs] @staticmethod def material_list(): """ SOPRA_DB.material_list() :: Loads a list (.pdf file) of all available SOPRA materials. """ print("Opening List of Available Materials in the SOPRA database") # Need different treatment depending on computer OS. if sys.platform == 'darwin': # Find spaces in the filename and add a \ before (for unix based systems) directory = SOPRA_PATH.split(" ") new_path = directory[0] for i in range(1, len(directory), 1): new_path = new_path + "\ " + directory[i] os.system("open " + os.path.join(new_path, "List_Of_Files_Updated_PDF.pdf")) elif sys.platform == 'linux': # Find spaces in the filename and add a \ before (for unix based systems) directory = SOPRA_PATH.split(" ") new_path = directory[0] for i in range(1, len(directory), 1): new_path = new_path + "\ " + directory[i] os.system("xdg-open " + os.path.join(new_path, "List_Of_Files_Updated_PDF.pdf")) elif sys.platform == 'win32': # Find spaces in the filename and add a \ before (for unix based systems) os.system("start " + os.path.join(SOPRA_PATH, "List_Of_Files_Updated_PDF.pdf"))
[docs] def load_n(self, Lambda=None): """ SOPRA_DB.load_n(Lambda) :: Load refractive index (n) data of the requested material. Optional argument Lambda allows user to specify a custom wavelength range. data will be interpolated into this range before output. Returns: Tuple of (Wavelength, n)""" try: os.stat(self.path) except FileNotFoundError: print('load_n :: WARNING :: There is no individual data file for, ' + self.material + ".") print('This material may be part of a set of varying composition, check the materials list...') sys.exit() # Load in data from file... Wav, n = np.genfromtxt(self.path, delimiter="*", skip_header=3, skip_footer=3, usecols=(2, 3), unpack=True) if Lambda is not None: # Interpolate in range specified by Lambda... n_interp = np.interp(Lambda, Wav, n) return (Lambda, n_interp) else: return (Wav, n)
[docs] def load_k(self, Lambda=None): """ SOPRA_DB.load_k(Lambda) :: Load refractive index (n) data of the requested material. Optional argument Lambda allows user to specify a custom wavelength range. data will be interpolated into this range before output. Returns: Tuple of (Wavelength, k) """ try: os.stat(self.path) except FileNotFoundError: print('load_k :: WARNING :: There is no individual data file for, ' + self.material + ".") print('This material may be part of a set of varying composition, check the materials list...') sys.exit() # Load in data from file... Wav, k = np.genfromtxt(self.path, delimiter="*", skip_header=3, skip_footer=3, usecols=(2, 4), unpack=True) if Lambda is not None: # Interpolate in range specified by Lambda... k_interp = np.interp(Lambda, Wav, k) return (Lambda, k_interp) else: return (Wav, k)
[docs] def load_alpha(self, Lambda=None): """ SOPRA_DB.load_alpha(Lambda) :: Load refractive index (n) data of the requested material. Optional argument Lambda allows user to specify a custom wavelength range. data will be interpolated into this range before output. Returns: Tuple of (Wavelength, alpha) """ Wav, k = self.load_k(Lambda=Lambda) return (Wav, ((4 * np.pi) / (Wav * 1E-9)) * k)
[docs] def load_temperature(self, Lambda, T=300): """ SOPRA_DB.load_temperature(T, Lambda) :: Loads n and k data for a set of materials with temperature dependent data sets Optional argument T defaults to 300K Required argument Lambda specifies a wavelength range and the data is interpolated to fit. This is a required argument here as not all data sets in a group are the same length (will be fixed in a subsequent update). Returns: Tuple of (Wavelength, n, k) """ T_degC = T - 273.15 # Convert from Kelvin to degC (units given in the data)... # Navigate to the correct folder that contains temperature dependent data... path = os.path.join(self.__SOPRA_PATH, self.__fname + "_T") try: os.stat(path) except FileNotFoundError: print("load_temperature :: WARNING :: Material folder does not exists... Check materials list...") # If material folder is not found exit program as nothing more useful can be done... sys.exit() # if folder exists, read in files from folder... Folder = natsorted(os.listdir(path)) DATA = [] TEMP = [] for files in Folder: # .DS_Store is a metadata file used in Mac OS X to store various file/ folder info. Ignoring... if ".DS_Store" not in files: # extract temperature from filename... Num = re.findall("[-+]?\d+[\.]?\d*", files) Num.append("0") TEMP.append(float(Num[0])) Wav, n, k = np.genfromtxt(os.path.join(path, files), delimiter="*", skip_header=3, skip_footer=3, usecols=(2, 3, 4), unpack=True) if Lambda is not None: # Interpolate if the Lambda argument is specified, if not pass loaded Wav, n and k... n_interp = np.interp(Lambda, Wav, n) k_interp = np.interp(Lambda, Wav, k) DATA.append((Lambda, n_interp, k_interp, float(Num[0]))) else: DATA.append((Wav, n, k, float(Num[0]))) # Check and see if the entered temperature is within the range of data... if T_degC <= min(TEMP): print("load_temperature :: WARNING :: Desired Temperature < than the minimum (%6.1f K)" % ( min(TEMP) + 273.15)) print("Returned interpolated data will be that at Tmin = %6.1f K" % (min(TEMP) + 273.15)) elif T_degC >= max(TEMP): print("load_temperature :: WARNING :: Desired Temperature > than the maximum (%6.1f K)" % ( max(TEMP) + 273.15)) print("Returned interpolated data will be that at Tmax = %6.1f K" % (max(TEMP) + 273.15)) # use linear interpolation to interpolate the data at the desired temperature... n_interp_data = [] k_interp_data = [] # In range of all wavelenghs... for i in range(0, len(DATA[0][0])): T_list = [] k_at_T = [] n_at_T = [] # At each wavelenth, build a list of n and k at each T... for X, n, k, temp in DATA: T_list.append(temp) k_at_T.append(k[i]) n_at_T.append(n[i]) # Interpolate the point corresponding to T and build the new data array... n_interp_data.append(np.interp(T - 273.19, T_list, n_at_T)) k_interp_data.append(np.interp(T - 273.19, T_list, k_at_T)) # Return the Wavelength vector and the new n and k data... return (DATA[0][0], n_interp_data, k_interp_data)
[docs] def load_composition(self, Lambda, **kwargs): """ SOPRA_DB.load_temperature(T, Lambda) :: Loads n and k data for a set of materials with varying composition. Required argument Lambda specifies a wavelength range and the data is interpolated to fit. This is a required argument here as not all data sets in a group are the same length (will be fixed in a subsequent update). Keyword argument :: Specify the factional material and fraction of the desired alloy. Returns: Tuple of (Wavelength, n, k) """ # Use of keyword args allows the user to specify the required material fraction for neatness... for material in kwargs: mat_fraction = material frac = kwargs[material] # Navigate to the correct folder that contains temperature dependent data... path = os.path.join(self.__SOPRA_PATH, self.__fname + "_" + mat_fraction.upper()) try: os.stat(path) except FileNotFoundError: print("load_composition :: WARNING :: Material folder does not exists... Check materials list or check" + " that composition material is correct...") # If material folder is not found exit program as nothing more useful can be done... sys.exit() # if folder exists, read in files from folder... Folder = natsorted(os.listdir(path)) DATA = [] COMP = [] for files in Folder: # .DS_Store is a metadata file used in Mac OS X to store various file/ folder info. Ignoring... if ".DS_Store" not in files: # extract temperature from filename... Num = re.findall("[-+]?\d+[\.]?\d*", files) Num.append("0") COMP.append(float(Num[0])) Wav, n, k = np.genfromtxt(os.path.join(path, files), delimiter="*", skip_header=3, skip_footer=3, usecols=(2, 3, 4), unpack=True) if Lambda is not None: # Interpolate if the Lambda argument is specified, if not pass loaded Wav, n and k... n_interp = np.interp(Lambda, Wav, n) k_interp = np.interp(Lambda, Wav, k) DATA.append((Lambda, n_interp, k_interp, float(Num[0]))) else: DATA.append((Wav, n, k, float(Num[0]))) # Check and see if the entered temperature is within the range of data... if frac <= min(COMP): print("load_composition :: WARNING :: Desired composition < than the minimum (%6.1f %%)" % min(COMP)) print("Returned interpolated data will be that at %6.1f %%" % min(COMP)) elif frac >= max(COMP): print("load_composition :: WARNING :: Desired composition > than the maximum (%6.1f %%)" % max(COMP)) print("Returned interpolated data will be that at %6.1f %%" % max(COMP)) # use linear interpolation to interpolate the data at the desired temperature... n_interp_data = [] k_interp_data = [] # In range of all wavelenghs... for i in range(0, len(DATA[0][0])): x_list = [] k_at_C = [] n_at_C = [] # At each wavelenth, build a list of n and k at each T... for X, n, k, x in DATA: x_list.append(x) k_at_C.append(k[i]) n_at_C.append(n[i]) # Interpolate the point corresponding to T and build the new data array... n_interp_data.append(np.interp(frac, x_list, n_at_C)) k_interp_data.append(np.interp(frac, x_list, k_at_C)) # Return the Wavelength vector and the new n and k data... return (DATA[0][0], n_interp_data, k_interp_data)
[docs]class SOPRAError(Exception): def __init__(self, message): self.message = message