solphin 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
solphin/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ import solphin.pv_fom
2
+ import solphin.db_fom
3
+ import solphin.final_results
4
+ import solphin.db_plotting
5
+ import solphin.vasp_inputs
6
+ import solphin.spectral
7
+ import solphin.optics
8
+ from .version import __version__
9
+ import solphin.resources
solphin/db_fom.py ADDED
@@ -0,0 +1,304 @@
1
+ import scipy.constants as sc
2
+ import numpy as np
3
+ import logging
4
+ from importlib.resources import files
5
+
6
+ logging.basicConfig(level=logging.INFO)
7
+
8
+ ''' This section details the calculation of the detailed balance limit efficiency and associated values.'''
9
+
10
+ h = sc.h # Planck's constant (J·s)
11
+ c = sc.c # Speed of light (m/s)
12
+ k = sc.k # Boltzmann constant (J/K)
13
+ q = sc.e # Elementary charge (Coulombs)
14
+
15
+ # Convert the spectrum to the useful units - taken from https://github.com/kaklin/sq-limit?tab=readme-ov-file
16
+
17
+ def load_spectrum(spectrum_type):
18
+
19
+ if spectrum_type == "AM1.5":
20
+ filename = 'ASTMG173.csv'
21
+
22
+ elif spectrum_type == "Fluorescent":
23
+ filename = 'fluorescent.csv'
24
+
25
+ elif spectrum_type == "Blue LED":
26
+ filename = 'led_blue.csv'
27
+
28
+ elif spectrum_type == "Green LED":
29
+ filename = 'led_green.csv'
30
+
31
+ elif spectrum_type == "Red LED":
32
+ filename = 'led_red.csv'
33
+
34
+ elif spectrum_type == "White LED":
35
+ filename = 'led_white.csv'
36
+
37
+ elif spectrum_type == "IR LED":
38
+ filename = 'led_ir.csv'
39
+
40
+ elif spectrum_type == "Photopic":
41
+ filename = 'photopic.csv'
42
+
43
+ else:
44
+ print("Unrecognisable spectrum selected")
45
+ print("Options: AM1.5, Fluorescent, Blue LED, Green LED, Red LED, White LED, IR LED, Photopic")
46
+ print("reverting to AM1.5")
47
+
48
+ filename = 'ASTMG173.csv'
49
+
50
+ csv_path = files("placeholder.resources") / f"{filename}"
51
+
52
+ with csv_path.open("r", encoding="utf-8") as f:
53
+ spectrum = np.loadtxt(f, delimiter=",", skiprows=1)
54
+
55
+ return spectrum
56
+
57
+ def convert_spectrum(spectrum):
58
+
59
+ """
60
+ Converts the input spectrum from standard format to the required units for this code.
61
+
62
+ Parameters:
63
+ spectrum(numpy.ndarray): Input spectrum loaded from a csv file with numpy.loadtxt.
64
+
65
+ Returns:
66
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
67
+
68
+ Spectrum input:
69
+ y: Irradiance (W/m2/nm)
70
+ x: Wavelength (nm)
71
+ Converted output:
72
+ y: Number of photons (Np/m2/s/dE)
73
+ x: Energy (eV)
74
+ """
75
+ converted = np.copy(spectrum)
76
+ converted[:, 0] = converted[:, 0] * 1e-9 # wavelength to m
77
+ converted[:, 1] = converted[:, 1] / 1e-9 # irradiance to W/m2/m (from W/m2/nm)
78
+
79
+ E = h * c / converted[:, 0]
80
+ d_lambda_d_E = h * c / E**2
81
+ converted[:, 1] = converted[:, 1] * d_lambda_d_E * q / E
82
+ converted[:, 0] = E / q
83
+
84
+ return converted
85
+
86
+
87
+ def photons_above_bandgap(E_gap, photon_spectrum):
88
+ """Counts number of photons above given bandgap.
89
+
90
+ Parameters:
91
+ E_gap(float): Optical Band Gap in eV
92
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
93
+
94
+ Returns:
95
+ (float): Integration of the spectrum for the number of photons above the bandgap.
96
+ """
97
+ indexes = np.where(photon_spectrum[:, 0] > E_gap)
98
+ y = photon_spectrum[indexes, 1][0]
99
+ x = photon_spectrum[indexes, 0][0]
100
+ return np.trapz(y[::-1], x[::-1])
101
+
102
+ def rr0(E_gap, photon_spectrum, Tcell):
103
+ '''
104
+ Calculates the radiative recombination rate at 0 Quasi-Fermi Level splitting.
105
+
106
+ Parameters:
107
+ E_gap(float): Optical Band Gap in eV
108
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
109
+ Tcell(float): Operating temperature of the cell in K
110
+
111
+ Returns:
112
+ Radiative recomination rate(float) in cm⁻³s⁻¹
113
+
114
+ '''
115
+ k_eV = k / q
116
+ h_eV = h / q
117
+ const = (2 * np.pi) / (c**2 * h_eV**3)
118
+
119
+ k_eV = k / q
120
+ E = photon_spectrum[::-1, ] # in increasing order of bandgap energy
121
+ egap_index = np.where(E[:, 0] >= E_gap)
122
+ numerator = E[:, 0]**2
123
+ exponential_in = E[:, 0] / (k_eV * Tcell)
124
+ denominator = np.exp(exponential_in) - 1
125
+ integrand = numerator / denominator
126
+
127
+ integral = np.trapz(integrand[egap_index], E[egap_index, 0])
128
+
129
+ result = const * integral
130
+ return result[0]
131
+
132
+ def recomb_rate(E_gap, photon_spectrum, voltage, Tcell):
133
+ '''
134
+ Calculates the radiative recombination rate.
135
+
136
+ Parameters:
137
+ E_gap(float): Optical Band Gap in eV
138
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
139
+ voltage(float): Open circuit voltage in V
140
+ Tcell(float): Operating temperature of the cell in K
141
+
142
+ Returns:
143
+ Radiative recomination rate(float) in cm⁻³s⁻¹
144
+
145
+ '''
146
+
147
+ print ('recomb rate')
148
+ return q * rr0(E_gap, photon_spectrum) * np.exp(q * voltage / (k * Tcell))
149
+
150
+ def current_density(E_gap, photon_spectrum, voltage, Tcell):
151
+ '''
152
+ Calculates the current density.
153
+
154
+ Parameters:
155
+ E_gap(float): Optical Band Gap in eV
156
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
157
+ voltage(float): Open circuit voltage in V
158
+ Tcell(float): Operating temperature of the cell in K
159
+
160
+ Returns:
161
+ Current density (float): Current that flows across a cross sectional area in C cm⁻³s⁻¹.
162
+
163
+ '''
164
+
165
+ return q * (photons_above_bandgap(E_gap, photon_spectrum) - rr0(E_gap, photon_spectrum, Tcell) * np.exp(q * voltage / (k * Tcell)))
166
+
167
+
168
+ def jsc(E_gap, photon_spectrum, Tcell):
169
+
170
+ '''
171
+ Calculates the current density.
172
+
173
+ Parameters:
174
+ E_gap(float): Optical Band Gap in eV
175
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
176
+
177
+ Returns:
178
+ Short circuit current density (float): Current that flows across a cross sectional area at 0 applied voltage in C cm⁻³s⁻¹.
179
+
180
+ '''
181
+
182
+ return current_density(E_gap, photon_spectrum, 0, Tcell)
183
+
184
+
185
+ def voc(E_gap, photon_spectrum, Tcell):
186
+ '''
187
+ Calculates the open circuit voltage.
188
+
189
+ Parameters:
190
+ E_gap(float): Optical Band Gap in eV
191
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
192
+ Tcell(float): Operating temperature of the cell in K
193
+
194
+ Returns:
195
+ Open circuit voltage (float): Maximum voltage across a solar cell with no current flow in V.
196
+
197
+ '''
198
+
199
+ # print 'voc'
200
+ return (k * Tcell / q) * np.log(photons_above_bandgap(E_gap, photon_spectrum) / rr0(E_gap, photon_spectrum, Tcell))
201
+
202
+ def v_at_mpp(E_gap, photon_spectrum):
203
+
204
+ '''
205
+ Calculates the voltage at maximum power point (mpp) of a solar cell.
206
+
207
+ Parameters:
208
+ E_gap(float): Optical Band Gap in eV
209
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
210
+
211
+ Returns:
212
+ Voltage at MPP (float): Voltage across a solar cell at the maximum power point in V.
213
+
214
+ '''
215
+
216
+ v_open = voc(E_gap, photon_spectrum)
217
+ # print v_open
218
+ v = np.linspace(0, v_open)
219
+ index = np.where(v * current_density(E_gap, photon_spectrum, v)==max(v * current_density(E_gap, photon_spectrum, v)))
220
+ return v[index][0]
221
+
222
+
223
+ def j_at_mpp(E_gap, photon_spectrum):
224
+
225
+ '''
226
+ Calculates the current at maximum power point (mpp) of a solar cell.
227
+
228
+ Parameters:
229
+ E_gap(float): Optical Band Gap in eV
230
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
231
+
232
+ Returns:
233
+ Current at MPP (float): Current across a solar cell at the maximum power point.
234
+
235
+ '''
236
+
237
+ return max_power(E_gap, photon_spectrum) / v_at_mpp(E_gap, photon_spectrum)
238
+
239
+
240
+ def max_power(E_gap, photon_spectrum, Tcell):
241
+
242
+ '''
243
+ Calculates the maximum power of a solar cell.
244
+
245
+ Parameters:
246
+ E_gap(float): Optical Band Gap in eV
247
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
248
+ Tcell(float): Operating temperature of the cell in K
249
+
250
+ Returns:
251
+ Maximum power (float): Maximum power of the solar cell in V C cm⁻³ s⁻¹.
252
+
253
+ '''
254
+
255
+ v_open = voc(E_gap, photon_spectrum, Tcell)
256
+ v = np.linspace(0, v_open)
257
+ index = np.where(v * current_density(E_gap, photon_spectrum, v, Tcell)==max(v * current_density(E_gap, photon_spectrum, v, Tcell)))
258
+ return max(v * current_density(E_gap, photon_spectrum, v, Tcell))
259
+
260
+
261
+ def max_eff(E_gap, photon_spectrum, Tcell):
262
+
263
+ '''
264
+ Calculates the maximum efficiency of a solar cell.
265
+
266
+ Parameters:
267
+ E_gap(float): Optical Band Gap in eV
268
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
269
+ Tcell(float): Operating temperature of the cell in K
270
+
271
+ Returns:
272
+ Maximum efficiency (float): Maximum effeciency of the solar cell relative to the total irradiance in %.
273
+ '''
274
+
275
+ photon_spectrum_1 = photon_spectrum[::-1, 1]
276
+ photon_spectrum_0 = photon_spectrum[::-1, 0]
277
+
278
+ irradiance = np.trapz(photon_spectrum_1 * q * photon_spectrum_0, photon_spectrum_0)
279
+ return max_power(E_gap, photon_spectrum, Tcell) / irradiance
280
+
281
+ def FillFactor(E_gap, photon_spectrum, Tcell):
282
+
283
+ '''
284
+ Calculates the fill factor of a solar cell.
285
+
286
+ Parameters:
287
+ E_gap(float): Optical Band Gap in eV
288
+ photon_spectrum(numpy.ndarray): Output spectrum as numpy ndarray.
289
+ Tcell(float): Operating temperature of the cell in K
290
+
291
+ Returns:
292
+ fill_factor (float): The fill factor of a solar cell.
293
+ '''
294
+
295
+ j_sc = jsc(E_gap, photon_spectrum)
296
+ v_oc = voc(E_gap, photon_spectrum, Tcell)
297
+ v_mpp = v_at_mpp(E_gap, photon_spectrum)
298
+ j_mpp = j_at_mpp(E_gap, photon_spectrum)
299
+
300
+ fill_factor = (j_mpp * v_mpp) / (j_sc, v_oc)
301
+
302
+ return fill_factor
303
+
304
+
solphin/db_plotting.py ADDED
@@ -0,0 +1,81 @@
1
+ import solphin.db_fom as db_fom
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+
9
+ def photons_above_bandgap_plot(spectrum, Egap):
10
+ """Plot of photons above bandgap as a function of bandgap"""
11
+ a = np.copy(spectrum)
12
+ for row in a:
13
+ # print row
14
+ row[1] = db_fom.photons_above_bandgap(row[0], spectrum)
15
+ plt.plot(a[:, 0], a[:, 1], color='#231123')
16
+
17
+ p_above_1_1 = db_fom.photons_above_bandgap(Egap, spectrum)
18
+ plt.plot([Egap], [p_above_1_1], 'ro', color='#FF6666')
19
+ plt.text(Egap+0.05, p_above_1_1, '{}eV, {:.4}'.format(Egap, p_above_1_1))
20
+
21
+ plt.xlabel('$E_{gap}$ (eV)')
22
+ plt.ylabel('# Photons $m^{-2}s^{-1}$')
23
+ plt.title('Number of above-bandgap \nphotons as a function of bandgap')
24
+ plt.show()
25
+
26
+ def iv_curve_plot(egap, spectrum, Tcell, power=False):
27
+ """Plots the ideal IV curve, or the ideal power for a given material"""
28
+ v_open = db_fom.voc(egap, spectrum, Tcell)
29
+ v = np.linspace(0, v_open)
30
+ if power:
31
+ p = v * db_fom.current_density(egap, spectrum, v, Tcell)
32
+ plt.xlabel('Voltage (V)')
33
+ plt.ylabel('Power generated ($W$)')
34
+ plt.title('Power Curve')
35
+ plt.plot(v, p, color='#231123')
36
+ else:
37
+ i = db_fom.current_density(egap, spectrum, v, Tcell)
38
+ plt.xlabel('Voltage (V)')
39
+ plt.ylabel('Current density $J$ ($Am^{-2}$)')
40
+ plt.title('IV Curve')
41
+ plt.plot(v, i, color='#231123')
42
+
43
+
44
+ def iv_curve_plot_2(egap, spectrum, Tcell, power=False):
45
+ """Plots the ideal IV curve, and the ideal power for a given material"""
46
+ v_open = db_fom.voc(egap, spectrum, Tcell)
47
+ v = np.linspace(0, v_open)
48
+
49
+ fig, ax1 = plt.subplots()
50
+ p = v * db_fom.current_density(egap, spectrum, v, Tcell)
51
+ i = db_fom.current_density(egap, spectrum, v, Tcell)
52
+
53
+ ax1.plot(v, i, color='#231123')
54
+ ax1.set_xlabel('Voltage (V)')
55
+ ax1.set_ylabel('Current density $J$ ($Am^{-2}$)')
56
+ ax1.legend(['Current'], loc=2)
57
+
58
+ ax2 = ax1.twinx()
59
+ ax2.plot(v, p, color='#FF6666')
60
+ ax2.set_ylabel('Power generated ($W$)')
61
+ ax2.legend(['Power'], loc=3)
62
+ return
63
+
64
+
65
+ def sq_limit_plot(spectrum, Egap, Tcell):
66
+ # Plot the famous SQ limit
67
+ a = np.copy(spectrum)
68
+ # Not for whole array hack to remove divide by 0 errors
69
+ for row in a[2:]:
70
+ # print row
71
+ row[1] = db_fom.max_eff(row[0], spectrum, Tcell)
72
+ # Not plotting whole array becase some bad values happen
73
+ plt.plot(a[2:, 0], a[2:, 1])
74
+ e_gap = Egap
75
+ p_above_1_1 = db_fom.max_eff(e_gap, spectrum, Tcell)
76
+ plt.plot([e_gap], [p_above_1_1], 'ro')
77
+ plt.text(e_gap+0.05, p_above_1_1, '{}eV, {:.4}'.format(e_gap, p_above_1_1))
78
+
79
+ plt.xlabel('$E_{gap}$ (eV)')
80
+ plt.ylabel('Max efficiency')
81
+ plt.title('SQ Limit')
@@ -0,0 +1,73 @@
1
+ from solphin.pv_fom import Final_equation
2
+ from solphin.db_fom import max_eff
3
+
4
+ # Calculating equation 33 from the FOM paper
5
+
6
+ def Crovetto_efficiency(E_gap, photon_spectrum, alpha, tau, sigma, dos_mass, dop_density, epsilon, mu, Tcell):
7
+
8
+ ''' Calculates the final value for the photovoltaic figure of merit from Crovetto 2024
9
+
10
+ Parameters:
11
+ E_gap(float): Optical Band Gap in eV
12
+ photon_spectrum(numpy.ndarrray): Converted input spectrum from DB_FOM.convert_spectrum y: Number of photons (Np/m2/s/dE) x: Energy (eV)
13
+ alpha(float): Spectral average of incident light in cm⁻¹
14
+ tau(float): Non-radiative recombination lifetime in s
15
+ sigma(float): Spectral dispersion of the absorption coefficient spectrum, unitless
16
+ dos_mass(float): Density of States effective mass in m₀
17
+ dop_density(float): Doping density in cm⁻³
18
+ epsilon(float): Static dielectric constant, unitless
19
+ mu(float): Charge carrier mobility in cm²V⁻¹s⁻¹
20
+ Tcell(float): Operating temperature of the cell in K
21
+
22
+ Returns:
23
+ efficiency(float): Percentage photovoltaic figure of merit efficiency.
24
+ '''
25
+
26
+ PV_FOM = Final_equation(E_gap, alpha, tau, sigma, dos_mass, dop_density, epsilon, mu)
27
+
28
+ k_1 = 3.3e-1
29
+ k_2 = 9.06e-2
30
+ k_3 = 2.48e-3
31
+
32
+ FOM_Pv_235 = PV_FOM ** (-0.235)
33
+ FOM_Pv_869 = PV_FOM ** 0.869
34
+ FOM_Pv_362 = PV_FOM ** (-0.362)
35
+
36
+ fraction = (k_1 * FOM_Pv_235) / (1 + k_2 * FOM_Pv_869)
37
+ denom_bracket = 1 + (k_3 * FOM_Pv_362)
38
+
39
+ SQ_eff = max_eff(E_gap, photon_spectrum, Tcell)
40
+ SQ = SQ_eff * 100
41
+
42
+ efficiency = SQ / ((1 + fraction) * denom_bracket)
43
+
44
+ return efficiency
45
+
46
+ # Crovetto efficiency realtive to SQ limit
47
+
48
+ def SQ_relative_Crovetto_efficiency(E_gap, photon_spectrum, alpha, tau, sigma, dos_mass, dop_density, epsilon, mu, Tcell):
49
+
50
+ ''' Calculates the final value for the photovoltaic figure of merit relative to the SQ limit from Crovetto 2024
51
+
52
+ Parameters:
53
+ E_gap(float): Optical Band Gap in eV
54
+ photon_spectrum(numpy.ndarrray): Converted input spectrum from DB_FOM.convert_spectrum y: Number of photons (Np/m2/s/dE) x: Energy (eV)
55
+ alpha(float): Spectral average of incident light in cm⁻¹
56
+ tau(float): Non-radiative recombination lifetime in s
57
+ sigma(float): Spectral dispersion of the absorption coefficient spectrum, unitless
58
+ dos_mass(float): Density of States effective mass in m₀
59
+ dop_density(float): Doping density in cm⁻³
60
+ epsilon(float): Static dielectric constant, unitless
61
+ mu(float): Charge carrier mobility in cm²V⁻¹s⁻¹
62
+ Tcell(float): Operating temperature of the cell in K
63
+
64
+ Returns:
65
+ efficiency(float): Percentage photovoltaic figure of merit efficiency relative to the SQ limit.
66
+ '''
67
+
68
+ Crovetto_eff = Crovetto_efficiency(E_gap, photon_spectrum, alpha, tau, sigma, dos_mass, dop_density, epsilon, mu, Tcell)
69
+ SQ = max_eff(E_gap, photon_spectrum, Tcell)
70
+
71
+ SQ_relative = Crovetto_eff / SQ
72
+
73
+ return SQ_relative
solphin/optics.py ADDED
@@ -0,0 +1,220 @@
1
+ from pymatgen.io.vasp import Vasprun
2
+ import numpy as np
3
+ from os.path import join
4
+ import scipy.special as sc
5
+ import pandas as pd
6
+
7
+ q=1.60217662E-19
8
+ kT=0.0258519975 # eV for T=300K
9
+ k=0.000086173325 #eV/K
10
+ h=4.135667E-15 #eVs
11
+ c=2.9979E+8 #m/s
12
+
13
+ def calc_dielectric(filename):
14
+
15
+ '''Calculates the dielectric constants from a vasprun.xml
16
+
17
+ Parameters:
18
+ filename(string): filename/ path of the vasprun, typically vasprun.xml.
19
+
20
+ Returns:
21
+ eps_full(np.array): static dielectric constant (complex, contains both real and imaginary components)
22
+ energies(np.array): energy of the incident radiation eV
23
+ '''
24
+
25
+ load_vasprun = Vasprun(filename)
26
+ dielectric = load_vasprun.dielectric
27
+
28
+ energies = np.array(dielectric[0])
29
+
30
+ real_eps = np.array(dielectric[1])[:, [[0, 3, 5], [3, 1, 4], [5, 4, 2]]]
31
+ imag_eps = np.array(dielectric[2])[:, [[0, 3, 5], [3, 1, 4], [5, 4, 2]]]
32
+ eps_full = real_eps + 1j * imag_eps
33
+
34
+ return eps_full, energies
35
+
36
+ def calc_absorption(eps_full, energies):
37
+
38
+ '''Calculates the averages of the real and imaginary components of the refractive index, absorption, losses, real and imaginary components of the
39
+ static dielectric constant.
40
+
41
+ Parameters:
42
+ eps_full(np.array): static dielectric constant (complex, contains both real and imaginary components)
43
+ energies(np.array): energy of the incident radiation eV
44
+
45
+ Returns:
46
+ data(dictionary):
47
+ '''
48
+
49
+ # take sqrt of eps matrix; if eps = V S V^-1; then eps^1/2 = V S^{1/2} V^-1;
50
+ eigvals, eigvecs = np.linalg.eig(eps_full)
51
+
52
+ # fancy einsum to calculate V S^{1/2} V^-1 at every energy
53
+ n = np.einsum("ijk,ik,ikl->ijl", eigvecs, np.sqrt(eigvals), np.linalg.inv(eigvecs))
54
+
55
+ # calculate optical absorption
56
+ alpha = n.imag * energies[:, None, None] * 4 * np.pi / 1.23984212e-4
57
+
58
+ # Invert epsilon to obtain energy-loss function
59
+ loss = -np.linalg.inv(eps_full).imag
60
+
61
+ eps = np.linalg.eigvals(eps_full).mean(axis=1)
62
+ n = np.linalg.eigvals(n).mean(axis=1)
63
+ loss = np.linalg.eigvalsh(loss).mean(axis=1)
64
+ alpha = np.linalg.eigvalsh(alpha).mean(axis=1)
65
+
66
+ data = {
67
+ "eps_real": eps.real,
68
+ "eps_imag": eps.imag,
69
+ "n_real": n.real,
70
+ "n_imag": n.imag,
71
+ "loss": loss,
72
+ "absorption": alpha,
73
+ }
74
+
75
+ return data
76
+
77
+ def print_n_real_file(data, energies):
78
+
79
+ filename = 'n_real.dat'
80
+ directory = 'dos'
81
+
82
+ if directory:
83
+ filename = join(directory, filename)
84
+
85
+ header = "energy(eV)"
86
+
87
+ header += " alpha"
88
+ data = np.stack((energies, data['n_real']), axis=1)
89
+
90
+ np.savetxt(filename, data, header=header)
91
+
92
+ def generate_n_real(filename):
93
+
94
+ eps_full, energies = calc_dielectric(filename)
95
+
96
+ data = calc_absorption(eps_full, energies)
97
+
98
+ print_n_real_file(data, energies)
99
+
100
+
101
+ def blank_flat(alpha, n, length):
102
+
103
+ #Absorptance for flat scatterer, from Matlab script
104
+ the_c = np.arcsin(1/n[0])
105
+ theta = np.linspace(0.0, the_c, num=200)
106
+ a_len = len(alpha)
107
+ t_len = len(theta)
108
+ toft = np.zeros(a_len*t_len)
109
+ toft = toft.reshape(a_len, t_len)
110
+ absorb_tr = np.zeros(a_len)
111
+
112
+ for i, a_pt in enumerate(alpha):
113
+ for j, t_pt in enumerate(theta):
114
+ toft_pt = np.exp(((2*np.multiply((-a_pt), length))/np.cos(t_pt)))
115
+ toft[i, j] = toft_pt
116
+ a_t_1 = np.trapz((toft[i,:]*np.cos(theta)*np.sin(theta)), theta)
117
+ a_t_2 = np.trapz((np.cos(theta)*np.sin(theta)), theta)
118
+ absorb_tr[i] = 1 - (a_t_1/a_t_2)
119
+
120
+ absorb = absorb_tr.conjugate()
121
+ return(absorb)
122
+
123
+
124
+ def blank_lambert(alpha, n, length):
125
+
126
+ #Absorptance for Lambertian scatterer coating, from Matlab script
127
+ x = np.multiply(2,np.multiply(alpha,length))
128
+ T = np.exp((-x)) - np.multiply(x, np.exp((-x))) + (x**2*sc.exp1(x))
129
+ R = 1.0/(np.multiply(n,n))
130
+ Abs = (1-T)/(1-T+(R*T))
131
+ Abs = np.nan_to_num(Abs)
132
+ Emi = (R*T)/(1-T+(R*T))
133
+
134
+ return(Abs)
135
+
136
+ def blank_eta(spectrum, E, alpha, n, length, Qi, trap):
137
+ #For given scatterer, calculates Blank et al. eta
138
+ dE = E[1]-E[0]
139
+ if trap == 1:
140
+ Abs = blank_flat(alpha, n, length)
141
+ elif trap == 2:
142
+ Abs = blank_lambert(alpha, n, length)
143
+
144
+ #np divide necessary to have array divide here?
145
+ phibb = 2*np.divide(np.divide(np.multiply(E,E),((h**3)*(c**2))),(np.exp(E/kT)-1))
146
+ phibb = np.nan_to_num(phibb) # NaNs to 0, as in Matlab
147
+
148
+ ps_E = spectrum[:, 0]
149
+ phi_sun = spectrum[:, 1]
150
+
151
+ # works for all E values above 0.03? won't extrapolate for 0
152
+ # values < gap shouldn't be relevant?
153
+ phisun = 10000*np.interp(E, ps_E, phi_sun)
154
+
155
+ Jsc = q*np.sum(Abs*phisun)*dE
156
+ J0rad = q*np.sum(Abs*phibb)*dE
157
+
158
+ Rrad = 4*np.pi*np.sum(alpha*(n**2)*phibb)*dE
159
+ Rnrad = (Rrad-Qi*Rrad)/Qi
160
+
161
+ pe = J0rad/(q*Rrad*length)
162
+ J0 = q*length*(Rnrad + pe*Rrad)
163
+ #Looks like this scans over only voltages between 0 and 2 V
164
+ #need testing for band gaps>2 eV?
165
+ V = np.linspace(0, 2, 1001)
166
+ Pmax = np.max(V*(Jsc - J0*(np.exp(V/kT)-1)))
167
+ eta = Pmax/1000
168
+
169
+ return(eta)
170
+
171
+ def blank_parse(folder):
172
+
173
+ # Parses outputs from current directory
174
+
175
+ abs_data = pd.read_table(f'{folder}/absorption.dat', delim_whitespace=True,
176
+ skiprows=1, header=None)
177
+ n_data = pd.read_table(f'{folder}/n_real.dat', delim_whitespace=True,
178
+ skiprows=1, header=None)
179
+
180
+ E_p = list(abs_data[0])
181
+ alpha_p = list(abs_data[1])
182
+ n_p = list(n_data[1])
183
+
184
+ return{"E": E_p, "alpha": alpha_p, "n": n_p}
185
+
186
+ def blank_calculate(spectrum, folder):
187
+
188
+ data = blank_parse(folder)
189
+
190
+ E = np.asarray(data["E"])
191
+ alpha = np.asarray(data["alpha"])
192
+ alpha = np.multiply(alpha, 100)
193
+ n = np.asarray(data["n"])
194
+
195
+ #Remove data for E>5eV, necessary for speed! Also done in Matlab
196
+
197
+ E = np.asarray([o for o in E if o <= 5])
198
+ alpha = alpha[0:(len(E))]
199
+ n = n[0:(len(E))]
200
+
201
+ #main, looping over lengths and Qi, outputs eta table
202
+ length_arr = np.logspace(-8.0, -3.0, num=36)
203
+ Qi_arr = np.logspace(0, -6, num=4)
204
+ trap = [1, 2]
205
+
206
+ for tr_pt in trap:
207
+ eta_arr = np.zeros(len(length_arr)*(len(Qi_arr)+1))
208
+ eta_arr = eta_arr.reshape(len(length_arr), (len(Qi_arr)+1))
209
+ for k, l_pt in enumerate(length_arr):
210
+ for l, q_pt in enumerate(Qi_arr):
211
+ eta_max = blank_eta(spectrum, E, alpha, n, l_pt, q_pt, tr_pt)
212
+ eta_arr[k, 0] = l_pt
213
+ eta_arr[k, l+1] = eta_max
214
+ if tr_pt == 1:
215
+ head="Thickness[m] \t Eta as fraction for Flat scatterer with Qi = 1.0, 0.01, 1E-4, 1E-6"
216
+ np.savetxt('flat_eta_out', eta_arr, header=head)
217
+ elif tr_pt == 2:
218
+ head="Thickness[m] \t Eta as fraction for Lambertian scatterer with Qi = 1.0, 0.01, 1E-4, 1E-6"
219
+ np.savetxt('lamb_eta_out', eta_arr, header=head)
220
+