pysolarcell 1.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.
@@ -0,0 +1,2 @@
1
+ from solarcell import Layer, Stack, SolarCell, PARALLEL, SERIES, AM15G
2
+ from materials import *
@@ -0,0 +1,69 @@
1
+ import pandas as pd
2
+ import scipy.interpolate
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ class Material:
7
+ def __init__(self, name, bandgap):
8
+ self.name = name
9
+ self.bandgap = bandgap # eV
10
+ self.n = lambda x: 1
11
+ self.k = lambda x: 0
12
+
13
+ def setN(self, wavelengths, n):
14
+ """Set the real refractive index
15
+
16
+ :param wavelengths: List of wavelengths (nm)
17
+ :param n: Refractive index at each wavelength
18
+ :return: Creates function n(lambda)
19
+ """
20
+
21
+ self.n = scipy.interpolate.interp1d(wavelengths, n, fill_value=1, bounds_error=False)
22
+
23
+ def setK(self, wavelengths, k):
24
+ """Set the extinction coefficient
25
+
26
+ :param wavelengths: List of wavelengths (nm)
27
+ :param k: Extinction coefficient at each wavelength
28
+ :return: Creates function k(lambda)
29
+ """
30
+
31
+ self.k = scipy.interpolate.interp1d(wavelengths, k, fill_value=0, bounds_error=False)
32
+
33
+ def plot(self):
34
+ wavelengths = np.linspace(300, 1200, 500)
35
+ plt.plot(wavelengths, self.n(wavelengths), label='n')
36
+ plt.plot(wavelengths, self.k(wavelengths), label='k')
37
+ plt.xlabel('Wavelength (nm)')
38
+ plt.ylabel('n, k')
39
+ plt.legend()
40
+ plt.title(f'Optical Properties of {self.name}')
41
+ plt.show()
42
+
43
+
44
+ def PEROVSKITE():
45
+ n_data = pd.read_csv('materials/CsPbI3_PSK_n.txt', sep='\t', names=['Wavelength', 'n'])
46
+ k_data = pd.read_csv('materials/CsPbI3_PSK_k.txt', sep='\t', names=['Wavelength', 'k'])
47
+
48
+ material = Material('Perovskite', 1.65)
49
+ material.setN(n_data['Wavelength'] * 1e3, n_data['n'])
50
+ material.setK(k_data['Wavelength'] * 1e3, k_data['k'])
51
+
52
+ return material
53
+
54
+
55
+ def SILICON():
56
+ n_data = pd.read_csv('materials/Si_n.txt', sep='\t', names=['Wavelength', 'n'])
57
+ k_data = pd.read_csv('materials/Si_k.txt', sep='\t', names=['Wavelength', 'k'])
58
+
59
+ material = Material('Silicon', 1.12)
60
+ material.setN(n_data['Wavelength'] * 1e3, n_data['n'])
61
+ material.setK(k_data['Wavelength'] * 1e3, k_data['k'])
62
+
63
+ return material
64
+
65
+ if __name__ == '__main__':
66
+ perovskite = PEROVSKITE()
67
+ perovskite.plot()
68
+ silicon = SILICON()
69
+ silicon.plot()
@@ -0,0 +1,608 @@
1
+ import sympy as sp
2
+ import numpy as np
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+ import scipy.integrate
6
+ import scipy.interpolate
7
+
8
+ from materials import Material, PEROVSKITE, SILICON
9
+
10
+ # Constants
11
+ h = 6.62607015e-34 # Js
12
+ c = 299792458 # m/s
13
+ k = 1.380649e-23 # J/K
14
+ q = 1.60217e-19 # C
15
+ sigma = 5.670374419e-8 # W/m^2/K^4
16
+ pi = np.pi
17
+ n_points = 100 # Change to 1000 for straight lines
18
+
19
+ np.seterr(all='raise')
20
+
21
+ AM15G_CONST = pd.read_csv('spectra/AM1.5G.txt', skiprows=1, sep='\t',
22
+ names=['Wavelength', 'AM0', 'Spectral Irradiance', 'AM1.5D'])
23
+ AM15D_CONST = pd.read_csv('spectra/AM1.5G.txt', skiprows=1, sep='\t',
24
+ names=['Wavelength', 'AM0', 'AM1.5G', 'Spectral Irradiance'])
25
+ AM0_CONST = pd.read_csv('spectra/AM1.5G.txt', skiprows=1, sep='\t',
26
+ names=['Wavelength','Spectral Irradiance','AM1.5G', 'AM1.5D'])
27
+ LED1_CONST = pd.read_csv('spectra/LED1.txt', skiprows=2, sep='\t',
28
+ names=['Wavelength','Spectral Irradiance'])
29
+ LED2_CONST = pd.read_csv('spectra/LED2.txt', skiprows=3, sep='\t',
30
+ names=['Wavelength','Spectral Irradiance'])
31
+ print(LED2_CONST.head())
32
+
33
+ def AM15G():
34
+ return AM15G_CONST.copy()
35
+
36
+ def AM15D():
37
+ return AM15D_CONST.copy()
38
+
39
+ def AM0():
40
+ return AM0_CONST.copy()
41
+
42
+ def LED1():
43
+ return LED1_CONST.copy()
44
+
45
+ def LED2():
46
+ return LED2_CONST.copy()
47
+
48
+ def PARALLEL(*cells):
49
+ return [*cells, True]
50
+
51
+ def SERIES(*cells):
52
+ return [*cells, False]
53
+
54
+
55
+ class Layer:
56
+ def __init__(self, name, bandgap, iqe=1, thickness: float=0, k=None, area=100, Rs=0, Rsh=np.inf, T=298, n=1):
57
+ """Creates a new layer of a solar cell stack. This can be created from a thickness and absorption data or a bandgap.
58
+
59
+ :param name: Name of the layer (str)
60
+ :param bandgap: Bandgap of the material (float)
61
+ :param iqe: Internal quantum efficiency (float)
62
+ :param thickness: Thickness of the layer (nm)
63
+ :param k: Extinction coefficient (unitless) as a function of wavelength (nm)
64
+ :param bandgap: Bandgap energy (eV)
65
+ """
66
+
67
+ self.name = name
68
+ self.bandgap = bandgap # Note: Bandgap must be greater than 1.05 eV for 1200 nm
69
+ self.iqe = iqe
70
+ self.thickness = thickness
71
+ self.k = k
72
+ self.area = area
73
+ self.Rs = Rs
74
+ self.Rsh = Rsh
75
+ self.T = T
76
+ self.n = n
77
+
78
+ self.properties = {}
79
+
80
+ self.type = None
81
+
82
+ if self.thickness != 0 and self.k is not None:
83
+ self.type = 'absorption'
84
+ else:
85
+ self.type = 'bandgap'
86
+
87
+ def __repr__(self):
88
+ return f'Layer({self.name})'
89
+
90
+ @staticmethod
91
+ def fromMaterial(name, material: Material, thickness: float, *args):
92
+ """Create a layer from a given material of some thickness
93
+
94
+ :param name: Name of the layer
95
+ :param material: Material of the layer
96
+ :param thickness: Thickness of the layer (nm)
97
+ :param args: Other parameters
98
+ :return: Layer of the given material
99
+ """
100
+
101
+ return Layer(name, material.bandgap, thickness, material.k, *args)
102
+
103
+ def toSolarCell(self, light_spectrum):
104
+ """Converts the layer to a solar cell using a light spectrum
105
+
106
+ :param light_spectrum: The incident spectrum
107
+ :return: The solar cell from the layer AND changes the spectrum in-place
108
+ """
109
+ Eg = self.bandgap * q # Energy in J
110
+ u = Eg / (k * self.T)
111
+
112
+ # Diode saturation current (mA/cm^2)
113
+ J0 = ((15 * q * sigma * self.T ** 3) / (k * pi ** 4)
114
+ * scipy.integrate.quad(lambda x: x ** 2 / (np.exp(x) - 1), u, 500)[0] / 10)
115
+
116
+ if self.type == 'bandgap':
117
+ lambda_g = h * c / Eg * 1e9 # Bandgap wavelength in nm
118
+ EQE = np.zeros_like(light_spectrum['Wavelength'], dtype=np.float64)
119
+ EQE[light_spectrum['Wavelength'] < lambda_g] = self.iqe
120
+
121
+ if self.type == 'absorption':
122
+ # I/I0 = exp(-4 pi k x / lambda) with x in nm and lambda in nm
123
+ EQE = (1 - np.exp(-4 * pi * self.k(light_spectrum['Wavelength']) * self.thickness / light_spectrum['Wavelength'])) * self.iqe
124
+
125
+ spectral_response = q * light_spectrum['Wavelength'] / (h * c) * EQE * 1e-9
126
+
127
+ Jsc = scipy.integrate.trapezoid(light_spectrum['Spectral Irradiance'] * spectral_response,
128
+ light_spectrum['Wavelength']) / 10 # mA/cm^2
129
+
130
+ Voc = self.n * k * self.T / q * np.log(Jsc / J0 + 1)
131
+
132
+ solar_cell = SolarCell(Jsc, Voc, area=self.area, Rs=self.Rs, Rsh=self.Rsh, T=self.T, n=self.n)
133
+
134
+ self.properties['Jsc'] = Jsc
135
+ self.properties['Voc'] = Voc
136
+ self.properties['JV'] = solar_cell.jv()
137
+ self.properties['FF'] = solar_cell.ff()
138
+ self.properties['MPP'] = solar_cell.mpp()
139
+ self.properties['EQE'] = (light_spectrum['Wavelength'], light_spectrum['Spectral Irradiance'] * EQE)
140
+ self.properties['Incident Power'] = scipy.integrate.trapezoid(light_spectrum['Spectral Irradiance'], light_spectrum['Wavelength']) / 10
141
+
142
+ # Calculate light after the cell
143
+ light_spectrum['Spectral Irradiance'] = light_spectrum['Spectral Irradiance'] * (1 - EQE)
144
+
145
+ return solar_cell
146
+
147
+ def eqe(self):
148
+ """Returns the external quantum efficiency of the cell as a function of wavelength
149
+
150
+ :return: Quantum efficiency (%) as a function of wavelength
151
+ """
152
+
153
+ if 'EQE' not in self.properties.keys():
154
+ print('Warning: You must convert this layer to a solar cell first.')
155
+
156
+ EQE = self.properties['EQE']
157
+ return EQE[0], EQE[1] / AM15G()['Spectral Irradiance'] * 100
158
+
159
+ def jv(self):
160
+ """Returns the JV curve of the cell as a tuple (voltages, currents)
161
+
162
+ :return: JV curve as a tuple (voltages, currents)
163
+ """
164
+
165
+ if 'JV' not in self.properties.keys():
166
+ print('Warning: You must convert this layer to a solar cell first.')
167
+
168
+ return self.properties['JV']
169
+
170
+ def ff(self):
171
+ if 'FF' not in self.properties.keys():
172
+ print('Warning: You must convert this layer to a solar cell first.')
173
+
174
+ return self.properties['FF']
175
+
176
+ def mpp(self):
177
+ if 'MPP' not in self.properties.keys():
178
+ print('Warning: You must convert this layer to a solar cell first.')
179
+
180
+ return self.properties['MPP']
181
+
182
+ def efficiency(self):
183
+ """Returns the efficiency of the cell assuming AM1.5G spectrum
184
+
185
+ :return: Efficiency (%)
186
+ """
187
+ if 'MPP' not in self.properties.keys():
188
+ print('Warning: You must convert this layer to a solar cell first.')
189
+
190
+ return self.properties['MPP'][1] / self.properties['Incident Power'] * 100
191
+
192
+
193
+ class Stack:
194
+ def __init__(self, layers, lamp=None, name=''):
195
+ """Creates a stack of cells. Usage SERIES(layer1, PARALLEL(layer2, layer3))
196
+
197
+ :param layers: List of layers
198
+ :param lamp: The initial light source
199
+ :param name: The name of the stack
200
+ """
201
+
202
+ self.name = name
203
+ self.layers = layers
204
+ self.lamp = lamp
205
+ self.flattened_layers = []
206
+
207
+ if self.lamp is None:
208
+ self.lamp = AM15G()
209
+
210
+ self.incident_power = scipy.integrate.trapezoid(self.lamp['Spectral Irradiance'], self.lamp['Wavelength']) / 10
211
+
212
+ self.jv_curve = None
213
+ self.properties = {}
214
+
215
+ def flatten(self, layers, parallel=False):
216
+ if isinstance(layers, Layer):
217
+ self.flattened_layers.append(layers)
218
+
219
+ # Convert layer to IV curve
220
+ cell = layers.toSolarCell(self.lamp)
221
+
222
+ # if parallel:
223
+ # return cell.jv()
224
+
225
+ currents = cell.currents
226
+ voltages = cell.V(currents)
227
+ return voltages, cell.ItoJ(currents)
228
+
229
+ if isinstance(layers, list):
230
+ assert len(layers) == 3, 'Stack formatted incorrectly'
231
+
232
+ jv1 = self.flatten(layers[0], parallel=layers[2])
233
+ jv2 = self.flatten(layers[1], parallel=layers[2])
234
+ if layers[2]:
235
+ return Stack.add_jv_parallel(jv1, jv2)
236
+
237
+ return Stack.add_jv_series(jv1, jv2)
238
+
239
+ raise ValueError('Stack formatted incorrectly')
240
+
241
+
242
+ def jv(self):
243
+ # Use precomputed JV curve
244
+ if 'JV' not in self.properties.keys():
245
+ self.properties['JV'] = self.flatten(self.layers)
246
+
247
+ return self.properties['JV']
248
+
249
+ def eqe(self):
250
+ if 'EQE' not in self.properties.keys():
251
+ wavelengths = None
252
+ EQE = None
253
+ for layer in self.flattened_layers:
254
+ if EQE is None:
255
+ wavelengths, EQE = layer.eqe()
256
+ else:
257
+ EQE += layer.eqe()[1]
258
+
259
+ self.properties['EQE'] = (wavelengths, EQE)
260
+
261
+ return self.properties['EQE']
262
+
263
+ def mpp(self):
264
+ """Finds the maximum power point of the solar cell stack
265
+
266
+ :return: Tuple of ((voltage, current), maximum power)
267
+ """
268
+
269
+ if 'MPP' not in self.properties.keys():
270
+
271
+ voltages, currents = self.jv()
272
+
273
+ max_index = np.argmax(voltages * currents)
274
+
275
+ self.properties['MPP'] = ((voltages[max_index], currents[max_index]),
276
+ voltages[max_index] * currents[max_index])
277
+
278
+ return self.properties['MPP']
279
+
280
+ def ff(self):
281
+ """Finds the fill factor of the solar cell stack
282
+
283
+ :return: Fill factor of the solar cell stack (%)
284
+ """
285
+
286
+ if 'FF' not in self.properties.keys():
287
+ voltages, currents = self.jv()
288
+ jv_func = scipy.interpolate.interp1d(voltages, currents, fill_value='extrapolate')
289
+ jsc = jv_func(0)
290
+ voc = voltages[np.argmin(np.abs(currents))]
291
+
292
+ self.properties['Jsc'] = jsc
293
+ self.properties['Voc'] = voc
294
+
295
+ self.properties['FF'] = self.mpp()[1] / (jsc * voc) * 100
296
+
297
+ return self.properties['FF']
298
+
299
+ def efficiency(self):
300
+ return self.mpp()[1] / self.incident_power * 100
301
+
302
+ def solve(self):
303
+ self.jv()
304
+ self.eqe()
305
+ self.mpp()
306
+ self.ff()
307
+
308
+ print('-' * 6 + self.name + '-' * 6)
309
+ print(f'Voc = {self.properties['Voc']:.2f} V')
310
+ print(f'Jsc = {self.properties['Jsc']:.2f} mA/cm^2')
311
+ print(f'Efficiency = {self.efficiency():.2f}%')
312
+ print(f'FF = {self.properties['FF']:.2f}%')
313
+ print('-' * (len(self.name) + 12))
314
+
315
+ return self.properties
316
+
317
+
318
+ @staticmethod
319
+ def add_jv_parallel(jv1, jv2):
320
+ """Adds IV curves (tuples of voltage, current) in parallel
321
+
322
+ :param jv1: First IV curve
323
+ :param jv2: Second IV curve
324
+ :return: New IV curve (tuple of voltage, current)
325
+ """
326
+
327
+ j1 = scipy.interpolate.interp1d(*jv1, fill_value='extrapolate')
328
+ j2 = scipy.interpolate.interp1d(*jv2, fill_value='extrapolate')
329
+
330
+ voltages = np.linspace(0, min(max(jv1[0]), max(jv2[0])) + 0.05, n_points)
331
+ j = j1(voltages) + j2(voltages)
332
+
333
+ return voltages, j
334
+
335
+ @staticmethod
336
+ def add_jv_series(jv1, jv2):
337
+ """Adds IV curves (tuples of voltage, current) in series
338
+
339
+ :param jv1: First IV curve
340
+ :param jv2: Second IV curve
341
+ :return: New IV curve (tuple of voltage, current)
342
+ """
343
+
344
+ v1 = scipy.interpolate.interp1d(jv1[1], jv1[0], fill_value='extrapolate')
345
+ v2 = scipy.interpolate.interp1d(jv2[1], jv2[0], fill_value='extrapolate')
346
+
347
+ currents = np.linspace(0, min(max(jv1[1]), max(jv2[1])) + 0.05, n_points)
348
+ voltages = v1(currents) + v2(currents)
349
+
350
+ return voltages, currents
351
+
352
+
353
+ class SolarCell:
354
+ def __init__(self, Jsc, Voc, area=100, Rs=0, Rsh=np.inf, T=298, n=1):
355
+ """Class representing a solar cell
356
+
357
+ :param Jsc: Ideal short circuit current density (mA/cm^2)
358
+ :param Voc: Ideal open circuit voltage (V)
359
+ :param area: Area of cell (cm^2)
360
+ :param Rs: Series resistance (Ohm cm^2)
361
+ :param Rsh: Shunt resistance (Ohm cm^2)
362
+ :param T: Temperature
363
+ :param n: Ideality factor
364
+ """
365
+
366
+ self.Isc = Jsc * area / 1000 # Ideal short circuit current in mA/cm^2
367
+ self.Jsc = Jsc
368
+ self.Voc = Voc # Ideal open circuit voltage in V
369
+ self.area = area # Area in cm^2
370
+ self.Rs = Rs / area
371
+ self.Rsh = Rsh / area
372
+ self.T = T
373
+ self.n = n
374
+
375
+ self.voltages = np.linspace(0, self.Voc, n_points)
376
+ self.currents = np.linspace(0, self.Isc, n_points)
377
+ self.I0 = self.Isc / (np.exp(q * self.Voc / (self.n * k * self.T)))
378
+
379
+ self.I = None
380
+ self.V = None
381
+ self.calculate_currents()
382
+ self.calculate_voltages()
383
+
384
+ def calculate_currents(self):
385
+ """Returns an array of currents
386
+ """
387
+
388
+ if self.Rs == 0 and self.Rsh == np.inf:
389
+ self.I = lambda v: self.Isc - self.I0 * (np.exp(q * v / (self.n * k * self.T)) - 1)
390
+ return
391
+
392
+ I = sp.Symbol('I')
393
+ result = np.zeros_like(self.voltages, dtype=np.float64)
394
+
395
+ for index, v in enumerate(self.voltages):
396
+ guess = self.Isc - self.I0 * (sp.exp(q * v / (self.n * k * self.T)) - 1)
397
+ result[index] = sp.nsolve(self.Isc
398
+ - self.I0 * (sp.exp((v + I * self.Rs) / (self.n * k * self.T / q)) - 1)
399
+ - (v + I * self.Rs) / self.Rsh - I, guess)
400
+
401
+ self.I = scipy.interpolate.interp1d(self.voltages, result, fill_value='extrapolate')
402
+
403
+ def calculate_voltages(self):
404
+ """Returns an array of voltages
405
+ """
406
+
407
+ if self.Rs == 0 and self.Rsh == np.inf:
408
+ self.V = lambda i: (self.n * k * self.T) / q * np.log((self.Isc - i) / self.I0 + 1)
409
+ return
410
+
411
+ V = sp.Symbol('V')
412
+ result = np.zeros_like(self.currents, dtype=np.float64)
413
+
414
+ for index, i in enumerate(self.currents):
415
+ guess = (self.n * k * self.T) / q * sp.log((self.Isc - i) / self.I0 + 1)
416
+ result[index] = sp.nsolve(self.Isc
417
+ - self.I0 * (sp.exp((V + i * self.Rs) / (self.n * k * self.T / q)) - 1)
418
+ - (V + i * self.Rs) / self.Rsh - i, guess)
419
+
420
+ self.V = scipy.interpolate.interp1d(self.currents, result, fill_value='extrapolate')
421
+
422
+ def jv(self):
423
+ I = self.I(self.voltages)
424
+
425
+ return self.voltages, self.ItoJ(I)
426
+
427
+ def mpp(self):
428
+ """Finds the maximum power point of the solar cell
429
+
430
+ :return: Tuple of ((voltage, current), maximum power)
431
+ """
432
+ currents = self.I(self.voltages)
433
+ max_index = np.argmax(self.voltages * currents)
434
+
435
+ return (self.voltages[max_index], self.ItoJ(currents[max_index])), self.voltages[max_index] * currents[
436
+ max_index]
437
+
438
+ def ff(self):
439
+ """Finds the fill factor of the solar cell
440
+
441
+ :return: Fill factor of the solar cell (%)
442
+ """
443
+
444
+ voltages, currents = self.jv()
445
+ jv_func = scipy.interpolate.interp1d(voltages, currents, fill_value='extrapolate')
446
+ jsc = jv_func(0)
447
+ voc = voltages[np.argmin(np.abs(currents))]
448
+
449
+ return self.mpp()[1] / (jsc * voc) * 100
450
+
451
+ def ItoJ(self, current):
452
+ return current * 1000 / self.area
453
+
454
+ @staticmethod
455
+ def mpp_from_jv(voltages, currents):
456
+ """Finds the maximum power point from a JV curve
457
+
458
+ :param voltages: Voltage points
459
+ :param currents: Current points (mA/cm^2)
460
+ :return: Tuple of ((voltage, current), maximum power)
461
+ """
462
+ max_index = np.argmax(voltages * currents)
463
+ max_power = voltages[max_index] * currents[max_index]
464
+ # print(f'Maximum Power: {max_power} W')
465
+
466
+ return (voltages[max_index], currents[max_index]), max_power
467
+
468
+ @staticmethod
469
+ def add_parallel(cell1: 'SolarCell', cell2: 'SolarCell'):
470
+ """Returns the JV curve of two solar cells in parallel
471
+
472
+ :param cell1: First solar cell
473
+ :param cell2: Second solar cell
474
+ :return: Tuple of (voltages, currents)
475
+ """
476
+
477
+ voltages = np.linspace(0, min(cell1.Voc, cell2.Voc) + 0.05, n_points)
478
+ currents = cell1.ItoJ(cell1.I(voltages)) + cell2.ItoJ(cell2.I(voltages))
479
+
480
+ return voltages, currents
481
+
482
+ @staticmethod
483
+ def add_series(cell1: 'SolarCell', cell2: 'SolarCell'):
484
+ """Returns the JV curve of two solar cells in series
485
+
486
+ :param cell1: First solar cell
487
+ :param cell2: Second solar cell
488
+ :return: Tuple of (voltages, currents)
489
+ """
490
+
491
+ currents = np.linspace(0, min(cell1.Isc, cell2.Isc) + 0.05, n_points)
492
+ voltages = cell1.V(currents) + cell2.V(currents)
493
+
494
+ return voltages, cell1.ItoJ(currents)
495
+
496
+
497
+ def plot_iv(*layers):
498
+ """Plots the JV curve for all layers in layers
499
+
500
+ :param layers: The list of layers or stacks to plot
501
+ :return: Figure and axes for further modification
502
+ """
503
+
504
+ fig = plt.figure()
505
+ ax = fig.add_subplot(111)
506
+
507
+ for layer in layers:
508
+ ax.plot(*layer.jv(), label=layer.name)
509
+
510
+ ax.set_xlim(left=0)
511
+ ax.set_ylim(bottom=0)
512
+ ax.legend(loc='best')
513
+ ax.set_xlabel('Voltage (V)')
514
+ ax.set_ylabel('Current Density ($mA/cm^2$)')
515
+ fig.tight_layout()
516
+
517
+ return fig, ax
518
+
519
+
520
+ def plot_eqe(*layers):
521
+ """Plots the external quantum efficiency for all layers in layers
522
+
523
+ :param layers: The list of layers or stacks to plot
524
+ :return: Figure and axes for further modification
525
+ """
526
+ fig = plt.figure()
527
+ ax = fig.add_subplot(111)
528
+
529
+ for layer in layers:
530
+ ax.plot(*layer.eqe(), label=layer.name)
531
+ ax.fill_between(*layer.eqe(), 0, alpha=0.2)
532
+
533
+ ax.legend()
534
+ ax.set_xlabel('Wavelength (nm)')
535
+ ax.set_ylabel('EQE (%)')
536
+ fig.tight_layout()
537
+
538
+ return fig, ax
539
+
540
+ if __name__ == '__main__':
541
+ # layer1 = Layer('Cell 1 (3.00 eV)', bandgap=1.63, iqe=1, Rs=0, Rsh=np.inf)
542
+ # layer2 = Layer('Cell 2 (1.77 eV)', bandgap=0.97, iqe=1, Rs=0, Rsh=np.inf)
543
+ # # layer1 = Layer('Cell 1 (3.00 eV)', bandgap=1.63, iqe=1)
544
+ # # layer2 = Layer('Cell 2 (1.77 eV)', bandgap=0.97, iqe=1)
545
+ #
546
+ # parallel = Stack(PARALLEL(layer1, layer2), name='Parallel')
547
+ # series = Stack(SERIES(layer1, layer2), name='Series')
548
+ # # cell1 = Stack(layer1, name='Cell 1')
549
+ # # cell2 = Stack(layer2, name='Cell 2')
550
+ #
551
+ # # parallel.solve()
552
+ # series.solve()
553
+ # # cell1.solve()
554
+ # # cell2.solve()
555
+ #
556
+ # fig, ax = plot_iv(layer1, layer2, parallel, series)
557
+ # # ax.set_xlim([0, 2])
558
+ # # plot_iv(cell1)
559
+ # plt.show()
560
+ #
561
+ # plot_eqe(layer1, layer2, parallel)
562
+ # plt.show()
563
+
564
+ # bandgaps = np.linspace(0.77, 3, 40)
565
+ # powers = np.zeros_like(bandgaps)
566
+ # for i, bandgap in enumerate(bandgaps):
567
+ # layer = Layer('Cell', bandgap=bandgap, iqe=1, Rs=0, Rsh=np.inf)
568
+ # cell = Stack(layer, 'Stack')
569
+ #
570
+ # powers[i] = cell.efficiency()
571
+
572
+ # plt.plot(bandgaps, powers)
573
+ #
574
+ # cell1 = Layer('Cell 1', 1.4605263157894737, iqe=1)
575
+ # cell2 = Layer('Cell 2',1.3578947368421053, iqe=1)
576
+ # cell3 = Layer('Cell 3', 1.2552631578947369, iqe=1)
577
+ # parallel = Stack(PARALLEL(PARALLEL(cell1, cell2), cell3))
578
+ # series = Stack(SERIES(SERIES(cell1, cell2), cell3))
579
+ #
580
+ # mixed1 = Stack(PARALLEL(SERIES(cell1, cell2), cell3))
581
+ # mixed2 = Stack(SERIES(PARALLEL(cell1, cell2), cell3))
582
+ # mixed3 = Stack(PARALLEL(cell1, SERIES(cell2, cell3)))
583
+ # mixed4 = Stack(SERIES(cell1, PARALLEL(cell2, cell3)))
584
+ #
585
+ # mixed4.solve()
586
+
587
+ layer1 = Layer('Cell 1 (3.00 eV)', bandgap=1.63, iqe=1, Rs=0, Rsh=np.inf)
588
+ layer2 = Layer('Cell 2 (1.77 eV)', bandgap=0.96, iqe=1, Rs=0, Rsh=np.inf)
589
+
590
+ parallel = Stack(PARALLEL(layer1, layer2), name='Parallel')
591
+ series = Stack(SERIES(layer1, layer2), name='Series')
592
+ # cell1 = Stack(layer1, name='Cell 1')
593
+ # cell2 = Stack(layer2, name='Cell 2')
594
+
595
+ parallel.solve()
596
+ series.solve()
597
+ # cell1.solve()
598
+ # cell2.solve()
599
+
600
+ fig, ax = plot_iv(layer1, layer2, parallel, series)
601
+ # ax.set_xlim([0, 2])
602
+ # plot_iv(cell1)
603
+ plt.show()
604
+
605
+ plot_eqe(layer1, layer2, parallel)
606
+ plt.show()
607
+
608
+ # TODO: Reflectance, recombination, diffusion length
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: pysolarcell
3
+ Version: 1.0.0
4
+ Summary: Solar cell simulation software
5
+ Home-page: https://github.com/AustL/PySolarCell
6
+ Author: AustL
7
+ Author-email: 21chydra@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: sympy
15
+ Requires-Dist: numpy
16
+ Requires-Dist: pandas
17
+ Requires-Dist: matplotlib
18
+ Requires-Dist: scipy
19
+
@@ -0,0 +1,7 @@
1
+ pysolarcell/__init__.py,sha256=-66t1TV3FiM0lTqaBeSdOEvfqrQHMP7HhAdIwk68lrU,95
2
+ pysolarcell/materials.py,sha256=XXyJxhqzjPIBmZGZ46AiRjcWm9nHV8nXe6VU70GiIcc,2253
3
+ pysolarcell/solarcell.py,sha256=BrQjVBFl4W9zBZXTOJenFCroY0e737XGOaCwAz3jm8o,20777
4
+ pysolarcell-1.0.0.dist-info/METADATA,sha256=-fxLYc_NEdxWLdwKkJYd-b_8aGdzO7HnwcOrgTwgOGc,544
5
+ pysolarcell-1.0.0.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
6
+ pysolarcell-1.0.0.dist-info/top_level.txt,sha256=5lL5swYKdjDD4okvPdwP14s29NkV9Yh2i2Wx9OzaTM0,12
7
+ pysolarcell-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (74.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pysolarcell