-
Notifications
You must be signed in to change notification settings - Fork 71
/
sopra_db.py
executable file
·337 lines (247 loc) · 13.9 KB
/
sopra_db.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
"""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
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))
@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"))
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)
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)
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)
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)
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)
class SOPRAError(Exception):
def __init__(self, message):
self.message = message