├── .gitattributes ├── .gitignore ├── 20181021_profiling-result.Result ├── JV.jpg ├── LICENSE ├── README.md ├── __pycache__ ├── constants.cpython-36.pyc ├── continuity.cpython-36.pyc ├── continuity_n.cpython-36.pyc ├── continuity_p.cpython-36.pyc ├── initialization.cpython-36.pyc ├── main.cpython-36.pyc ├── photogeneration.cpython-36.pyc ├── poisson.cpython-36.pyc ├── recombination.cpython-36.pyc ├── thomas_tridiag_solve.cpython-36.pyc └── utilities.cpython-36.pyc ├── constants.py ├── continuity_n.py ├── continuity_p.py ├── gen_rate.inp ├── initialization.py ├── main.py ├── parameters.inp ├── photogeneration.py ├── poisson.py ├── recombination.py ├── thomas_tridiag_solve.py └── utilities.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | #data output 32 | *.txt 33 | 34 | #QT builds 35 | *.stash 36 | *.pdb 37 | *.Debug 38 | *.Release 39 | Makefile 40 | *.ilk 41 | *.user 42 | -------------------------------------------------------------------------------- /20181021_profiling-result.Result: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/20181021_profiling-result.Result -------------------------------------------------------------------------------- /JV.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/JV.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Timofey Golubev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drift-Diffusion_models 2 | 3 | Here is a 1D model written in Python which solves the semiconductor Poisson-Drift-Diffusion equations using finite-differences. This models simulates a solar cell under illumination, but can be adapted to other semiconductor devices as well. It can be modified to solve other systems (i.e. through changing the boundary conditions, adding recombination rates, and modifying the generation rate). 4 | 5 | The equations are solved using the self-consistent iterative approach called the Gummel method. In order to ensure numerical stability for the continuity equations, Scharfetter Gummel discretization as well as linear mixing of old and new solutions is used. 6 | 7 | ### Performance 8 | The code has been accelerated using Numba @jit decorators. Sample CPU Times: 9 | Without Numba: 469.7 sec 10 | With Numba: 73.7 sec 11 | 12 | The conclusion here is that Numba gives a significant performance boost with low effort. You may read about Numba here: 13 | http://numba.pydata.org/ 14 | 15 | ## C++ and Matlab implementations 16 | 17 | You can find my C++ and Matlab implementations of this same model as well as 2D and 3D versions here: 18 | 19 | https://github.com/tgolubev/Drift-Diffusion_models-Cpp_Matlab 20 | 21 | ### Performance comparison: 22 | 23 | For the 1D code with mesh size dx = 0.25nm and a system size of 300nm: 24 | 25 | Python: 69.8 sec 26 | Matlab: 40 sec 27 | C++: 3.7 sec 28 | 29 | Therefore, currently the C++ version is much faster, perhaps with a disadvantage of being less elegant to read. 30 | 31 | ## Citing 32 | If you use this code in a scientific publication, I would appreciate citations to the following paper: 33 | 34 | T. Golubev, D. Liu, R. Lunt, P. Duxbury. Understanding the impact of C60 at the interface of perovskite solar cells via drift-diffusion modeling. AIP Advances 9, 035026 (2019) https://aip.scitation.org/doi/full/10.1063/1.5068690 35 | 36 | -------------------------------------------------------------------------------- /__pycache__/constants.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/constants.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/continuity.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/continuity.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/continuity_n.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/continuity_n.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/continuity_p.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/continuity_p.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/initialization.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/initialization.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/main.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/main.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/photogeneration.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/photogeneration.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/poisson.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/poisson.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/recombination.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/recombination.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/thomas_tridiag_solve.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/thomas_tridiag_solve.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/utilities.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgolubev/Drift-Diffusion_Python/d409e692be77452da099e7b612f6e8e2fef41b67/__pycache__/utilities.cpython-36.pyc -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Oct 20 03:46:33 2018 4 | 5 | @author: Tim 6 | """ 7 | 8 | #Physical Constants (these should not be changed) (constexpr b/c known at compile-time) 9 | q = 1.60217646e-19 #elementary charge, C 10 | kb = 1.3806503e-23 #Boltzmann const., J/k 11 | T = 296. #temperature K 12 | Vt = (kb*T)/q #thermal voltage V 13 | epsilon_0 = 8.85418782e-12 #F/m 14 | -------------------------------------------------------------------------------- /continuity_n.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This contains everything needed to set up the continuity equation for electrons, using 8 | Scharfetter-Gummel discretization. 9 | 10 | """ 11 | 12 | import numpy as np, math 13 | import constants as const 14 | from numba import jit 15 | 16 | class Continuity_n(): 17 | ''' 18 | This class groups all values related to the electron continuity equations, making it convenient 19 | to access these values through an instance of the class. 20 | ''' 21 | 22 | def __init__(self, params): 23 | 24 | num_cell = params.num_cell 25 | 26 | # allocate the arrays 27 | self.B_n1 = np.zeros(num_cell+1) 28 | self.B_n2 = np.zeros(num_cell+1) 29 | 30 | self.main_diag = np.zeros(num_cell) 31 | self.upper_diag = np.zeros(num_cell-1) 32 | self.lower_diag = np.zeros(num_cell-1) 33 | self.rhs = np.zeros(num_cell) #right hand side 34 | 35 | # setup the constant arrays and variables 36 | self.n_mob = (params.n_mob_active/params.mobil)*np.ones(num_cell+1) 37 | self.Cn = params.dx*params.dx/(const.Vt*params.N*params.mobil) #coeffient in front of rhs 38 | self.n_leftBC = (params.N_LUMO*math.exp(-(params.E_gap - params.phi_a)/const.Vt))/params.N #this is anode 39 | self.n_rightBC = (params.N_LUMO*math.exp(-params.phi_c/const.Vt))/params.N 40 | 41 | 42 | @jit 43 | def setup_eqn(self, V, Un): 44 | ''' 45 | Sets up the left and right side of the continuity matrix equation for electrons. The tridiagonal matrix 46 | is stored in an efficient way by only storing the 3 diagonals. 47 | ''' 48 | 49 | # update values of B_n1(V) and B_n2(V), needed in the Scharfetter-Gummel discretization 50 | bernoulli_fnc_n(V, self.B_n1, self.B_n2) 51 | 52 | # set rhs 53 | self.rhs = -self.Cn * Un 54 | self.rhs[1] -= self.n_mob[0]*self.B_n2[1]*self.n_leftBC; 55 | self.rhs[-1] -= self.n_mob[-1]*self.B_n1[-1]*self.n_rightBC; 56 | 57 | # set main diagonal 58 | self.main_diag[1:] = -(self.n_mob[1:-1]*self.B_n1[1:-1] + self.n_mob[2:]*self.B_n2[2:]) 59 | 60 | # set lower diagonal 61 | self.lower_diag[1:] = self.n_mob[2:-1]*self.B_n2[2:-1] 62 | 63 | # set upper diagonal 64 | self.upper_diag[1:] = self.n_mob[2:-1]*self.B_n1[2:-1] 65 | 66 | 67 | @jit(nopython = True) 68 | def bernoulli_fnc_n(V, B_n1, B_n2): 69 | ''' 70 | This updates the values of B_n1(V) and B_n2(V) (attributes of Continuity_n class) which are 71 | used in the Scharfetter-Gummel formalism of the continuity equation 72 | 73 | B_n1 = dV/(exp(dV)-1) 74 | B_n2 = -dV/(exp(-dV) -1) = B_n1 * exp(dV) 75 | 76 | No return value 77 | ''' 78 | 79 | dV = np.empty(len(V)) 80 | 81 | for i in range(1,len(V)): 82 | dV[i] = V[i] - V[i-1] 83 | 84 | B_n1[1:] = dV[1:]/(np.exp(dV[1:]) - 1.0) 85 | B_n2[1:] = B_n1[1:]*np.exp(dV[1:]) 86 | -------------------------------------------------------------------------------- /continuity_p.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This contains everything needed to set up the continuity equation for holes 8 | (quasi-particle corresponding to lack of an electron), using Scharfetter-Gummel discretization. 9 | """ 10 | 11 | import numpy as np 12 | import constants as const 13 | from numba import jit 14 | 15 | class Continuity_p(): 16 | ''' 17 | This class groups all values related to the hole (quasi-particle corresponding to lack of an electron) 18 | continuity equation, making it convenient to access these values through an instance of the class. 19 | ''' 20 | 21 | def __init__(self, params): 22 | 23 | num_cell = params.num_cell 24 | 25 | # allocate the arrays 26 | self.B_p1 = np.zeros(num_cell+1) 27 | self.B_p2 = np.zeros(num_cell+1) 28 | 29 | self.main_diag = np.zeros(num_cell) 30 | self.upper_diag = np.zeros(num_cell-1) 31 | self.lower_diag = np.zeros(num_cell-1) 32 | self.rhs = np.zeros(num_cell) 33 | 34 | # setup the constant arrays and variables 35 | self.p_mob = (params.p_mob_active/params.mobil)*np.ones(num_cell+1) 36 | self.Cp = params.dx*params.dx/(const.Vt*params.N*params.mobil) 37 | self.p_leftBC = (params.N_HOMO*np.exp(-params.phi_a/const.Vt))/params.N 38 | self.p_rightBC = (params.N_HOMO*np.exp(-(params.E_gap - params.phi_c)/const.Vt))/params.N 39 | 40 | @jit 41 | def setup_eqn(self, V, Up): 42 | ''' 43 | Sets up the left and right side of the continuity matrix equation for holes. The tridiagonal matrix 44 | is stored in an efficient way by only storing the 3 diagonals. 45 | ''' 46 | 47 | # update values of B_p1(V) and B_p2(V), needed in the Scharfetter-Gummel discretization 48 | bernoulli_fnc_p(V, self.B_p1, self.B_p2) 49 | 50 | # set rhs 51 | self.rhs = -self.Cp * Up 52 | self.rhs[1] -= self.p_mob[0]*self.B_p1[1]*self.p_leftBC 53 | self.rhs[-1] -= self.p_mob[-1]*self.B_p2[-1]*self.p_rightBC 54 | 55 | # set main diagonal 56 | self.main_diag[1:] = -(self.p_mob[1:-1]*self.B_p2[1:-1] + self.p_mob[2:]*self.B_p1[2:]) #[1:-1] means go to 1 before last element 57 | 58 | # set lower diagonal 59 | self.lower_diag[1:] = self.p_mob[2:-1]*self.B_p1[2:-1] 60 | 61 | # set upper diagonal 62 | self.upper_diag[1:] = self.p_mob[2:-1]*self.B_p2[2:-1] 63 | 64 | 65 | # this is defined outside of the class b/c is faster this way 66 | @jit(nopython = True) 67 | def bernoulli_fnc_p(V, B_p1, B_p2): 68 | ''' 69 | This updates the values of B_p1(V) and B_p2(V) (attributes of Continuity_p class) which are 70 | used in the Scharfetter-Gummel formalism of the continuity equation 71 | 72 | B_p1 = dV/(exp(dV)-1) 73 | B_p2 = -dV/(exp(-dV) -1) = B_p1 * exp(dV) 74 | 75 | No return value 76 | ''' 77 | 78 | dV = np.empty(len(V)) 79 | 80 | for i in range(1,len(V)): 81 | dV[i] = V[i] - V[i-1] 82 | 83 | B_p1[1:] = dV[1:]/(np.exp(dV[1:]) - 1.0) #note: B_p's have length num_cell+1 since defined based on V 84 | B_p2[1:] = B_p1[1:]*np.exp(dV[1:]) 85 | 86 | 87 | -------------------------------------------------------------------------------- /gen_rate.inp: -------------------------------------------------------------------------------- 1 | 1.83560545e+22 2 | 1.82755381e+22 3 | 1.81954608e+22 4 | 1.81158192e+22 5 | 1.80366097e+22 6 | 1.79578290e+22 7 | 1.78794737e+22 8 | 1.78015404e+22 9 | 1.77240259e+22 10 | 1.76469268e+22 11 | 1.75702398e+22 12 | 1.74939618e+22 13 | 1.74180894e+22 14 | 1.73426194e+22 15 | 1.72675487e+22 16 | 1.71928742e+22 17 | 1.71185926e+22 18 | 1.70447010e+22 19 | 1.69711961e+22 20 | 1.68980750e+22 21 | 1.68253347e+22 22 | 1.67529720e+22 23 | 1.66809841e+22 24 | 1.66093680e+22 25 | 1.65381207e+22 26 | 1.64672393e+22 27 | 1.63967209e+22 28 | 1.63265627e+22 29 | 1.62567619e+22 30 | 1.61873155e+22 31 | 1.61182208e+22 32 | 1.60494751e+22 33 | 1.59810755e+22 34 | 1.59130194e+22 35 | 1.58453040e+22 36 | 1.57779267e+22 37 | 1.57108848e+22 38 | 1.56441756e+22 39 | 1.55777966e+22 40 | 1.55117451e+22 41 | 1.54460185e+22 42 | 1.53806144e+22 43 | 1.53155301e+22 44 | 1.52507632e+22 45 | 1.51863112e+22 46 | 1.51221716e+22 47 | 1.50583419e+22 48 | 1.49948198e+22 49 | 1.49316029e+22 50 | 1.48686887e+22 51 | 1.48060749e+22 52 | 1.47437591e+22 53 | 1.46817391e+22 54 | 1.46200125e+22 55 | 1.45585771e+22 56 | 1.44974306e+22 57 | 1.44365707e+22 58 | 1.43759953e+22 59 | 1.43157021e+22 60 | 1.42556890e+22 61 | 1.41959538e+22 62 | 1.41364944e+22 63 | 1.40773086e+22 64 | 1.40183944e+22 65 | 1.39597497e+22 66 | 1.39013723e+22 67 | 1.38432604e+22 68 | 1.37854118e+22 69 | 1.37278245e+22 70 | 1.36704966e+22 71 | 1.36134262e+22 72 | 1.35566112e+22 73 | 1.35000497e+22 74 | 1.34437399e+22 75 | 1.33876798e+22 76 | 1.33318676e+22 77 | 1.32763014e+22 78 | 1.32209794e+22 79 | 1.31658998e+22 80 | 1.31110608e+22 81 | 1.30564605e+22 82 | 1.30020974e+22 83 | 1.29479695e+22 84 | 1.28940752e+22 85 | 1.28404128e+22 86 | 1.27869806e+22 87 | 1.27337769e+22 88 | 1.26808001e+22 89 | 1.26280485e+22 90 | 1.25755205e+22 91 | 1.25232145e+22 92 | 1.24711290e+22 93 | 1.24192624e+22 94 | 1.23676131e+22 95 | 1.23161795e+22 96 | 1.22649603e+22 97 | 1.22139538e+22 98 | 1.21631586e+22 99 | 1.21125733e+22 100 | 1.20621964e+22 101 | 1.20120264e+22 102 | 1.19620619e+22 103 | 1.19123017e+22 104 | 1.18627442e+22 105 | 1.18133880e+22 106 | 1.17642320e+22 107 | 1.17152747e+22 108 | 1.16665147e+22 109 | 1.16179509e+22 110 | 1.15695819e+22 111 | 1.15214065e+22 112 | 1.14734233e+22 113 | 1.14256313e+22 114 | 1.13780290e+22 115 | 1.13306154e+22 116 | 1.12833892e+22 117 | 1.12363493e+22 118 | 1.11894945e+22 119 | 1.11428237e+22 120 | 1.10963357e+22 121 | 1.10500294e+22 122 | 1.10039037e+22 123 | 1.09579575e+22 124 | 1.09121898e+22 125 | 1.08665995e+22 126 | 1.08211855e+22 127 | 1.07759469e+22 128 | 1.07308826e+22 129 | 1.06859915e+22 130 | 1.06412728e+22 131 | 1.05967255e+22 132 | 1.05523486e+22 133 | 1.05081411e+22 134 | 1.04641021e+22 135 | 1.04202307e+22 136 | 1.03765261e+22 137 | 1.03329873e+22 138 | 1.02896134e+22 139 | 1.02464036e+22 140 | 1.02033570e+22 141 | 1.01604728e+22 142 | 1.01177502e+22 143 | 1.00751884e+22 144 | 1.00327866e+22 145 | 9.99054391e+21 146 | 9.94845966e+21 147 | 9.90653308e+21 148 | 9.86476342e+21 149 | 9.82314994e+21 150 | 9.78169193e+21 151 | 9.74038867e+21 152 | 9.69923948e+21 153 | 9.65824367e+21 154 | 9.61740057e+21 155 | 9.57670952e+21 156 | 9.53616987e+21 157 | 9.49578100e+21 158 | 9.45554226e+21 159 | 9.41545307e+21 160 | 9.37551280e+21 161 | 9.33572088e+21 162 | 9.29607673e+21 163 | 9.25657978e+21 164 | 9.21722948e+21 165 | 9.17802528e+21 166 | 9.13896665e+21 167 | 9.10005307e+21 168 | 9.06128403e+21 169 | 9.02265902e+21 170 | 8.98417757e+21 171 | 8.94583918e+21 172 | 8.90764339e+21 173 | 8.86958974e+21 174 | 8.83167779e+21 175 | 8.79390710e+21 176 | 8.75627723e+21 177 | 8.71878777e+21 178 | 8.68143832e+21 179 | 8.64422847e+21 180 | 8.60715783e+21 181 | 8.57022603e+21 182 | 8.53343270e+21 183 | 8.49677747e+21 184 | 8.46026000e+21 185 | 8.42387995e+21 186 | 8.38763698e+21 187 | 8.35153076e+21 188 | 8.31556099e+21 189 | 8.27972736e+21 190 | 8.24402956e+21 191 | 8.20846732e+21 192 | 8.17304035e+21 193 | 8.13774838e+21 194 | 8.10259115e+21 195 | 8.06756840e+21 196 | 8.03267988e+21 197 | 7.99792536e+21 198 | 7.96330460e+21 199 | 7.92881739e+21 200 | 7.89446349e+21 201 | 7.86024272e+21 202 | 7.82615486e+21 203 | 7.79219972e+21 204 | 7.75837712e+21 205 | 7.72468688e+21 206 | 7.69112882e+21 207 | 7.65770279e+21 208 | 7.62440861e+21 209 | 7.59124615e+21 210 | 7.55821526e+21 211 | 7.52531580e+21 212 | 7.49254763e+21 213 | 7.45991064e+21 214 | 7.42740470e+21 215 | 7.39502970e+21 216 | 7.36278554e+21 217 | 7.33067211e+21 218 | 7.29868932e+21 219 | 7.26683708e+21 220 | 7.23511530e+21 221 | 7.20352391e+21 222 | 7.17206284e+21 223 | 7.14073201e+21 224 | 7.10953137e+21 225 | 7.07846085e+21 226 | 7.04752041e+21 227 | 7.01671000e+21 228 | 6.98602957e+21 229 | 6.95547909e+21 230 | 6.92505852e+21 231 | 6.89476784e+21 232 | 6.86460702e+21 233 | 6.83457604e+21 234 | 6.80467488e+21 235 | 6.77490353e+21 236 | 6.74526199e+21 237 | 6.71575024e+21 238 | 6.68636829e+21 239 | 6.65711614e+21 240 | 6.62799380e+21 241 | 6.59900127e+21 242 | 6.57013858e+21 243 | 6.54140572e+21 244 | 6.51280273e+21 245 | 6.48432963e+21 246 | 6.45598644e+21 247 | 6.42777318e+21 248 | 6.39968990e+21 249 | 6.37173661e+21 250 | 6.34391336e+21 251 | 6.31622019e+21 252 | 6.28865713e+21 253 | 6.26122422e+21 254 | 6.23392151e+21 255 | 6.20674905e+21 256 | 6.17970688e+21 257 | 6.15279505e+21 258 | 6.12601361e+21 259 | 6.09936262e+21 260 | 6.07284212e+21 261 | 6.04645217e+21 262 | 6.02019283e+21 263 | 5.99406416e+21 264 | 5.96806621e+21 265 | 5.94219903e+21 266 | 5.91646270e+21 267 | 5.89085727e+21 268 | 5.86538280e+21 269 | 5.84003935e+21 270 | 5.81482698e+21 271 | 5.78974576e+21 272 | 5.76479574e+21 273 | 5.73997699e+21 274 | 5.71528957e+21 275 | 5.69073354e+21 276 | 5.66630896e+21 277 | 5.64201589e+21 278 | 5.61785439e+21 279 | 5.59382452e+21 280 | 5.56992634e+21 281 | 5.54615991e+21 282 | 5.52252528e+21 283 | 5.49902252e+21 284 | 5.47565167e+21 285 | 5.45241278e+21 286 | 5.42930592e+21 287 | 5.40633113e+21 288 | 5.38348846e+21 289 | 5.36077797e+21 290 | 5.33819968e+21 291 | 5.31575366e+21 292 | 5.29343994e+21 293 | 5.27125857e+21 294 | 5.24920957e+21 295 | 5.22729300e+21 296 | 5.20550887e+21 297 | 5.18385724e+21 298 | 5.16233811e+21 299 | 5.14095153e+21 300 | 5.11969751e+21 301 | -------------------------------------------------------------------------------- /initialization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This contains everything used to read simulation parameters from file and defines a Params class, 8 | an instance of which can be used to store the parameters. 9 | """ 10 | 11 | import math, constants as const, numpy as np 12 | 13 | def is_positive(value, comment): 14 | ''' 15 | Checks if an input value is positive. 16 | Inputs: 17 | value: the input value 18 | comment: this is used to be able to output an informative error message, 19 | if the input value is invalid 20 | ''' 21 | 22 | if value <= 0: 23 | print(f"Non-positive input for {comment}\n Input was read as {value}.") 24 | raise ValueError("This input must be positive") 25 | 26 | def is_negative(value, comment): 27 | ''' 28 | Checks if an input value is positive. 29 | Inputs: 30 | value: the input value 31 | comment: this is used to be able to output an informative error message, 32 | if the input value is invalid 33 | ''' 34 | 35 | if value >= 0: 36 | print(f"Non-positive input for {comment}\n Input was read as {value}.") 37 | raise ValueError("This input must be negative") 38 | 39 | class Params(): 40 | 41 | ''' 42 | The Params class groups all of the simulation parameters parameters into a parameters object. 43 | Initialization of a Params instance, reads in the parameters from "parameters.inp" input file. 44 | ''' 45 | 46 | def __init__(self): 47 | 48 | try: 49 | parameters = open("parameters.inp", "r") 50 | except: 51 | print(f"Unable to open file parameters.inp") 52 | 53 | try: 54 | comment = parameters.readline() 55 | tmp = parameters.readline().split() 56 | self.L = float(tmp[0]) #note: floats in python are double precision 57 | comment = tmp[1] 58 | is_positive(self.L, comment) 59 | 60 | tmp = parameters.readline().split() 61 | self.N_LUMO = float(tmp[0]) 62 | comment = tmp[1] 63 | is_positive(self.N_LUMO, comment) 64 | 65 | tmp = parameters.readline().split() 66 | self.N_HOMO = float(tmp[0]) 67 | comment = tmp[1] 68 | is_positive(self.N_HOMO, comment) 69 | 70 | tmp = parameters.readline().split() 71 | self.Photogen_scaling = float(tmp[0]) 72 | comment = tmp[1] 73 | is_positive(self.Photogen_scaling, comment) 74 | 75 | tmp = parameters.readline().split() 76 | self.phi_a = float(tmp[0]) 77 | comment = tmp[1] 78 | is_positive(self.phi_a , comment) 79 | 80 | tmp = parameters.readline().split() 81 | self.phi_c = float(tmp[0]) 82 | comment = tmp[1] 83 | is_positive(self.phi_c, comment) 84 | 85 | tmp = parameters.readline().split() 86 | self.eps_active = float(tmp[0]) 87 | comment = tmp[1] 88 | is_positive(self.eps_active, comment) 89 | 90 | tmp = parameters.readline().split() 91 | self.p_mob_active = float(tmp[0]) 92 | comment = tmp[1] 93 | is_positive(self.p_mob_active, comment) 94 | 95 | tmp = parameters.readline().split() 96 | self.n_mob_active = float(tmp[0]) 97 | comment = tmp[1] 98 | is_positive(self.n_mob_active, comment) 99 | 100 | tmp = parameters.readline().split() 101 | self.mobil = float(tmp[0]) 102 | comment = tmp[1] 103 | is_positive(self.mobil, comment) 104 | 105 | tmp = parameters.readline().split() 106 | self.E_gap = float(tmp[0]) 107 | comment = tmp[1] 108 | is_positive(self.E_gap, comment) 109 | 110 | tmp = parameters.readline().split() 111 | self.active_CB = float(tmp[0]) 112 | comment = tmp[1] 113 | is_negative(self.active_CB, comment) 114 | 115 | tmp = parameters.readline().split() 116 | self.active_VB = float(tmp[0]) 117 | comment = tmp[1] 118 | is_negative(self.active_VB, comment) 119 | 120 | tmp = parameters.readline().split() 121 | self.WF_anode = float(tmp[0]) 122 | comment = tmp[1] 123 | is_positive(self.WF_anode, comment) 124 | 125 | tmp = parameters.readline().split() 126 | self.WF_cathode = float(tmp[0]) 127 | comment = tmp[1] 128 | is_positive(self.WF_cathode, comment) 129 | 130 | tmp = parameters.readline().split() 131 | self.k_rec = float(tmp[0]) 132 | comment = tmp[1] 133 | is_positive(self.k_rec, comment) 134 | 135 | tmp = parameters.readline().split() 136 | self.dx = float(tmp[0]) 137 | comment = tmp[1] 138 | is_positive(self.dx, comment) 139 | 140 | tmp = parameters.readline().split() 141 | self.Va_min= float(tmp[0]) 142 | 143 | tmp = parameters.readline().split() 144 | self.Va_max = float(tmp[0]) 145 | 146 | tmp = parameters.readline().split() 147 | self.increment = float(tmp[0]) 148 | comment = tmp[1] 149 | is_positive(self.increment, comment) 150 | 151 | tmp = parameters.readline().split() 152 | self.w_eq = float(tmp[0]) 153 | comment = tmp[1] 154 | is_positive(self.w_eq, comment) 155 | 156 | tmp = parameters.readline().split() 157 | self.w_i = float(tmp[0]) 158 | comment = tmp[1] 159 | is_positive(self.w_i, comment) 160 | 161 | tmp = parameters.readline().split() 162 | self.tolerance_i = float(tmp[0]) 163 | comment = tmp[1] 164 | is_positive(self.tolerance_i , comment) 165 | 166 | tmp = parameters.readline().split() 167 | self.w_reduce_factor = float(tmp[0]) 168 | comment = tmp[1] 169 | is_positive(self.w_reduce_factor, comment) 170 | 171 | tmp = parameters.readline().split() 172 | self.tol_relax_factor = float(tmp[0]) 173 | comment = tmp[1] 174 | is_positive(self.tol_relax_factor, comment) 175 | 176 | tmp = parameters.readline().split() 177 | self.gen_rate_file_name = tmp[0] 178 | 179 | # calculated parameters 180 | self.N = self.N_HOMO 181 | self.num_cell = math.ceil(self.L/self.dx) 182 | self.E_trap = self.active_VB + self.E_gap/2.0 # traps are assumed to be at 1/2 of the bandgap 183 | self.n1 = self.N_LUMO*np.exp(-(self.active_CB - self.E_trap)/const.Vt) 184 | self.p1 = self.N_HOMO*np.exp(-(self.E_trap - self.active_VB)/const.Vt) 185 | 186 | except: 187 | print(tmp) 188 | print("Invalid Input. Fix it and rerun") 189 | 190 | 191 | # The following functions are mostly to make the main code a bit more readable and obvious what 192 | # is being done. 193 | 194 | def reduce_w(self): 195 | ''' 196 | Reduces the weighting factor (w) (used for linear mixing of old and new solutions) by w_reduce_factor 197 | which is defined in the input parameters 198 | ''' 199 | self.w = self.w/self.w_reduce_factor 200 | 201 | def relax_tolerance(self): 202 | ''' 203 | Relax the criterea for determining convergence of a solution by the tol_relax_factor. 204 | This is sometimes necessary for hard to converge situations. 205 | The relaxing of tolerance is done automatically when convergence issues are detected. 206 | ''' 207 | self.tolerance = self.tolerance*self.tol_relax_factor 208 | 209 | def use_tolerance_eq(self): 210 | ''' 211 | Use the convergence tolerance meant for equilibrium condition run. This tolerance is usually 212 | higher than the regular tolerance due the problem is more difficult to converge when simulating 213 | at 0 applied voltage. 214 | ''' 215 | self.tolerance = self.tolerance_eq 216 | 217 | def use_tolerance_i(self): 218 | ''' 219 | Use the initial convergence tolerance specified (before any relaxing of the tolerance). 220 | ''' 221 | self.tolerance = self.tolerance_i 222 | 223 | def use_w_i(self): 224 | ''' 225 | Use the initially specified weighting factor (w) (used for linear mixing of old and new solutions). 226 | ''' 227 | self.w = self.w_i 228 | 229 | def use_w_eq(self): 230 | ''' 231 | Use the weighting factor (w) (used for linear mixing of old and new solutions) for the equilibrium 232 | condition run. This is usually lower than the regular w due the problem is more difficult to 233 | converge when simulating at 0 applied voltage. 234 | ''' 235 | self.w = self.w_eq 236 | 237 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | =================================================================================================== 4 | Solving 1D Poisson + Drift Diffusion semiconductor equations for a solar cell using 5 | Scharfetter-Gummel discretization 6 | 7 | Created on Fri Oct 19, 2018 8 | 9 | @author: Timofey Golubev 10 | 11 | The code as is will simulate a current-vs-voltage curve 12 | of a generic solar cell made of an active layer and electrodes. 13 | More equations for carrier recombination can be easily added. 14 | 15 | Photogeneration rate will be inputed from an input file (the name of the file can be specified in 16 | the parameters input file parameters.inp). For example the output of an optical model or an analytic 17 | expression for photogeneration rate can be used. Generation rate file 18 | should contain num_cell-2 number of entries in a single column, corresponding to 19 | the the generation rate at each mesh point (except the endpoints). 20 | 21 | The code can also be applied to non-illuminated devices by 22 | setting photogeneration rate to 0. 23 | 24 | =================================================================================================== 25 | """ 26 | 27 | import continuity_n, continuity_p, initialization, photogeneration, poisson, recombination 28 | import thomas_tridiag_solve as thomas, utilities, constants as const, time 29 | 30 | import numpy as np, matplotlib.pyplot as plt, math 31 | 32 | params = initialization.Params() 33 | num_cell = params.num_cell 34 | Vbi = params.WF_anode - params.WF_cathode +params.phi_a +params.phi_c 35 | num_V = math.floor((params.Va_max-params.Va_min)/params.increment) + 1 36 | params.tolerance_eq = 100*params.tolerance_i 37 | 38 | JV = open("JV.txt", "w") 39 | JV.write("# Voltage (V) \t Current (A/m^2) \n") 40 | 41 | # ------------------------------------------------------------------------------------------------- 42 | # Construct objects 43 | poiss = poisson.Poisson(params) 44 | recombo = recombination.Recombo(params) 45 | cont_p = continuity_p.Continuity_p(params) 46 | cont_n = continuity_n.Continuity_n(params) 47 | photogen = photogeneration.get_photogeneration(params) 48 | 49 | # initialize arrays 50 | oldp = np.zeros(num_cell); newp = np.zeros(num_cell); 51 | oldn = np.zeros(num_cell); newn = np.zeros(num_cell); 52 | oldV = np.zeros(num_cell+1); newV = np.zeros(num_cell+1); V = np.zeros(num_cell+1); 53 | Un = np.zeros(num_cell); Up = np.zeros(num_cell); 54 | R_Langevin = np.zeros(num_cell); photogen_rate = np.zeros(num_cell); 55 | Jp = np.zeros(num_cell); Jn = np.zeros(num_cell); 56 | J_total = np.zeros(num_cell); error_np_vector = np.zeros(num_cell); 57 | 58 | # Initial conditions 59 | min_dense = min(cont_n.n_leftBC, cont_p.p_rightBC) 60 | n = min_dense * np.ones(num_cell) 61 | p = min_dense * np.ones(num_cell) 62 | 63 | V_leftBC = -((Vbi)/(2*const.Vt) - params.phi_a/const.Vt) 64 | V_rightBC = (Vbi)/(2*const.Vt) - params.phi_c/const.Vt 65 | diff = (V_rightBC - V_leftBC)/num_cell 66 | V[0] = V_leftBC #fill V(0) here for use in Beroulli later 67 | for i in range(1, num_cell): 68 | V[i] = V[i-1] + diff 69 | V[num_cell] = V_rightBC 70 | 71 | # note: poisson matrix is already set up when we constructed the poisson object 72 | 73 | start = time.time() 74 | 75 | ###################################### MAIN LOOP ################################################### 76 | 77 | for Va_cnt in range(0, num_V + 2): 78 | not_converged = False 79 | not_cnv_cnt = 0 80 | 81 | # equilibrium run 82 | if Va_cnt == 0: 83 | params.use_tolerance_eq() 84 | params.use_w_eq() 85 | Va = 0 86 | else: 87 | Va = params.Va_min + params.increment * (Va_cnt -1) 88 | 89 | if params.tolerance > 1e-5: 90 | print("Warning: Tolerance has been increased to > 1e-5. Results will be inaccurate") 91 | 92 | if Va_cnt == 1: 93 | params.use_tolerance_i(); #reset tolerance back 94 | params.use_w_i(); 95 | photogen_rate = photogeneration.get_photogeneration(params); 96 | 97 | # Apply the voltage boundary conditions 98 | V_leftBC = -((Vbi-Va)/(2*const.Vt) - params.phi_a/const.Vt) 99 | V_rightBC = (Vbi-Va)/(2*const.Vt) - params.phi_c/const.Vt 100 | V[0] = V_leftBC 101 | V[num_cell] = V_rightBC 102 | 103 | print(f"Va = {Va:2.2f} V") 104 | 105 | error_np = 1.0 106 | it = 0 107 | while error_np > params.tolerance: 108 | #print(error_np) 109 | 110 | #------------------------------ Solve Poisson Equation-------------------------------------- 111 | 112 | poiss.set_rhs(n, p, V_leftBC, V_rightBC) 113 | oldV = V 114 | newV = thomas.thomas_solve(poiss.main_diag, poiss.upper_diag, poiss.lower_diag, poiss.rhs) 115 | 116 | newV[0] = V[0] 117 | newV[num_cell] = V[num_cell] 118 | 119 | # Mix old and new solutions for V (for interior elements), for stability of algorithm 120 | if it > 0: 121 | V[1:] = newV[1:]*params.w + oldV[1:]*(1.0 - params.w) 122 | else: 123 | V = newV 124 | 125 | # reset BC's 126 | V[0] = V_leftBC 127 | V[num_cell] = V_rightBC 128 | 129 | #---------------------------Calculate net generation rate----------------------------------- 130 | 131 | R_Langevin = recombo.compute_R_Langevin(R_Langevin, n, p, params.N, params.k_rec, params.n1, params.p1) 132 | Un[1:] = photogen_rate[1:] - R_Langevin[1:] 133 | Up[1:] = photogen_rate[1:] - R_Langevin[1:] 134 | 135 | #-----------------Solve equations for electron and hole density (n and p)------------------- 136 | 137 | cont_n.setup_eqn(V, Un) 138 | oldn = n 139 | newn = thomas.thomas_solve(cont_n.main_diag, cont_n.upper_diag, cont_n.lower_diag, cont_n.rhs) 140 | 141 | cont_p.setup_eqn(V, Up) 142 | oldp = p 143 | newp = thomas.thomas_solve(cont_p.main_diag, cont_p.upper_diag, cont_p.lower_diag, cont_p.rhs) 144 | 145 | # if get negative p's or n's set them = 0 146 | for val in newp: 147 | if val < 0.0: 148 | val = 0 149 | for val in newn: 150 | if val < 0.0: 151 | val = 0 152 | 153 | #--------------Calculate the difference (error) between prev. and current solution---------- 154 | 155 | old_error = error_np 156 | for i in range(1, num_cell): 157 | if (newp[i] != 0) and (newn[i] != 0): 158 | error_np_vector[i] = (abs(newp[i]-oldp[i]) + abs(newn[i]-oldn[i]))/abs(oldp[i]+oldn[i]) 159 | 160 | error_np = max(error_np_vector) 161 | error_np_vector = np.zeros(num_cell) # refill with 0's so have fresh one for next it 162 | 163 | # auto decrease w if not converging 164 | if error_np >= old_error: 165 | not_cnv_cnt = not_cnv_cnt+1 166 | if not_cnv_cnt > 2000: 167 | params.reduce_w() 168 | params.relax_tolerance() 169 | 170 | # linear mixing of old and new solutions for stability 171 | p[1:num_cell] = newp[1:num_cell]*params.w + oldp[1:num_cell]*(1.0 - params.w) 172 | n[1:num_cell] = newn[1:num_cell]*params.w + oldn[1:num_cell]*(1.0 - params.w) 173 | p[0] = cont_p.p_leftBC 174 | n[0] = cont_n.n_leftBC 175 | # note: we are not including the right boundary point in p and n here 176 | 177 | it += 1 178 | 179 | # END of while loop 180 | 181 | # ------------- Calculate currents using Scharfetter-Gummel definition------------------------- 182 | 183 | for i in range(1, num_cell): 184 | Jp[i] = (-(const.q*const.Vt*params.N*params.mobil/params.dx) * cont_p.p_mob[i] 185 | *(p[i]*cont_p.B_p2[i] - p[i-1]*cont_p.B_p1[i])) 186 | 187 | Jn[i] = ((const.q*const.Vt*params.N*params.mobil/params.dx) * cont_n.n_mob[i] 188 | *(n[i]*cont_n.B_n1[i] - n[i-1]*cont_n.B_n2[i])) 189 | 190 | J_total[i] = Jp[i] + Jn[i]; 191 | 192 | #----------------------------Write results to file---------------------------------------------- 193 | if Va_cnt > 0: 194 | JV.write(f"{Va:2.2f} \t\t\t {J_total[math.floor(params.num_cell/2)]:4.8f} \n") 195 | 196 | # End of main loop 197 | 198 | 199 | endtime = time.time() 200 | print(f"Total CPU time: {endtime-start}") 201 | JV.close() 202 | 203 | # Plot Results 204 | V, J = np.loadtxt("JV.txt", usecols=(0,1), unpack = True) # usecols specifies columns to use, unpack specifies to use tuple unpacking 205 | plt.xlim(params.Va_min, params.Va_max) 206 | plt.ylim(-250, 100) 207 | plt.plot(V, J) 208 | plt.xlabel('Voltage ($V$)') 209 | plt.ylabel('Current ($A/m^2$)') # TeX markup 210 | plt.grid(True) 211 | plt.savefig("JV.jpg", dpi = 1200) #specify the dpi for a high resolution image 212 | 213 | -------------------------------------------------------------------------------- /parameters.inp: -------------------------------------------------------------------------------- 1 | //NOTE:IF-HAVE-ANY-SPACES-IN-COMMENTS-IT-WILL-FAIL 2 | 300.0e-9 device-thickness(m) 3 | 1e24 N-LUMO 4 | 1e24 N-HOMO 5 | 7e27 Photogeneration-scaling 6 | 0.2 anode-injection-barrier-phi-a 7 | 0.1 cathode-injection-barrier-phi-c 8 | 3.0 eps_active 9 | 4.5e-6 p_mob_active 10 | 4.5e-6 n_mob_active 11 | 5e-6 mobil-scaling-for-mobility 12 | 1.5 E_gap 13 | -3.9 active_CB 14 | -5.4 active_VB 15 | 4.8 WF_anode 16 | 3.7 WF_cathode 17 | 6e-17 k_rec 18 | 1e-9 dx 19 | -0.5 Va_min 20 | 1.2 Va_max 21 | 0.01 increment 22 | 0.01 w_eq 23 | 0.2 w_i 24 | 5e-12 tolerance_i 25 | 2.0 w_reduce_factor 26 | 10.0 tol_relax_factor 27 | gen_rate.inp GenRateFileName 28 | 29 | -------------------------------------------------------------------------------- /photogeneration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This just contains the function for reading photogeneration rate from a generation rate data file. 8 | """ 9 | 10 | import numpy as np 11 | 12 | def get_photogeneration(params): 13 | ''' 14 | Reads photogeneration rate from an input file. 15 | Inputs: 16 | params: Params object which contains several necessary parameters such as the name of generation 17 | rate file as well as the photogeneration scaling to use. The photogeneration scaling 18 | is determined by finding a value which will result in the correct short-circuit current. 19 | ''' 20 | 21 | try: 22 | gen_file = open(params.gen_rate_file_name, "r") 23 | except: 24 | print(f"Unable to open file{params.gen_rate_file_name}") 25 | 26 | photogen_rate = np.loadtxt(params.gen_rate_file_name) 27 | #photogen_rate = np.ones(params.num_cell+1) 28 | 29 | photogen_rate = params.Photogen_scaling * photogen_rate/np.max(photogen_rate) 30 | 31 | gen_file.close() 32 | 33 | return photogen_rate 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /poisson.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Tim 6 | """ 7 | 8 | import numpy as np 9 | import constants as const 10 | from numba import jit 11 | 12 | class Poisson(): 13 | ''' 14 | This class groups all values related to the Poisson equation, making it convenient 15 | to access these values through an instance of the class. Initialization of an instance of Poisson 16 | will also set the values of the diagonals in the Poisson matrix, since they stay constant during 17 | the simulation. 18 | ''' 19 | 20 | def __init__(self, params): 21 | num_cell = params.num_cell 22 | self.epsilon = params.eps_active*np.ones(num_cell+1) # relative dielectric constant 23 | self.main_diag = np.ones(num_cell) 24 | self.upper_diag = np.ones(num_cell-1) 25 | self.lower_diag = np.ones(num_cell-1) 26 | 27 | # since the values of the Poisson matrix do not change during the simulation, we initialize 28 | # them only once here. 29 | self.main_diag[1:] = -2*self.epsilon[1:num_cell] 30 | self.upper_diag[1:] = self.epsilon[1:num_cell-1] 31 | self.lower_diag[1:] = self.epsilon[1:num_cell-1] 32 | 33 | self.rhs = np.zeros(num_cell) 34 | 35 | self.CV = params.N*params.dx*params.dx*const.q/(const.epsilon_0*const.Vt) 36 | 37 | 38 | @jit 39 | def set_rhs(self, n, p, V_left_BC, V_right_BC): 40 | ''' 41 | Update the right hand side of the Poisson equation. This is done in every iteration of the 42 | self consistent method due to changing charge density values and applied voltage boundary conditions. 43 | 44 | Inputs: 45 | n: electron density 46 | p: hole density 47 | V_left_BC: left electric potential boundary condition 48 | V_right_BC: right electric potential boundary condition 49 | ''' 50 | 51 | self.rhs = self.CV * (n - p) 52 | 53 | self.rhs[1] -= self.epsilon[0] * V_left_BC 54 | self.rhs[-1] -= self.epsilon[-1] * V_right_BC 55 | -------------------------------------------------------------------------------- /recombination.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This contains functions to calculate recombination rates. More types of recombination will be 8 | added later. 9 | """ 10 | 11 | import numpy as np 12 | from numba import jit 13 | 14 | class Recombo(): 15 | 16 | def __init__(self, params): 17 | self.R_Langevin = np.zeros(params.num_cell) 18 | 19 | @jit 20 | def compute_R_Langevin(self, R_Langevin, n, p, N, k_rec, n1, p1): 21 | ''' 22 | Computes bimolecular Langevin recombination rate. 23 | Inputs: 24 | R_Langevin: the empty numpy array. This is input explicitely b/c of a speedup over accessing 25 | it through the recombo object. 26 | n: electron density 27 | p: hole density 28 | N: density of states scaling factor 29 | k_rec: recombination coefficient 30 | n1: N_LUMO*exp(-(E_LUMO - Et)/(k_B T)) number of electrons in the LUMO band when the electron’s quasi-Fermi energy 31 | equals the trap energy Et 32 | p1: N_HOMO*exp(-(Et - E_HOMO)/(k_B T)) number of holes in the HOMO band when hole’s quasi-Fermi 33 | energy equals Et 34 | n1 and p1 are defined inside of initialization.py 35 | 36 | Output: R_Langevin recombination rate array, indexed from 1. 37 | ''' 38 | 39 | R_Langevin[1:] = k_rec*(N*N*n[1:]*p[1:] - n1*p1) 40 | 41 | # negative recombination values are unphysical 42 | for val in R_Langevin: 43 | if val < 0: 44 | val = 0 45 | 46 | return R_Langevin -------------------------------------------------------------------------------- /thomas_tridiag_solve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Timofey Golubev 6 | 7 | This contains an implementation of the Thomas algorithm for solving a tridiagonal matrix equation. 8 | """ 9 | import numpy as np 10 | import time 11 | from numba import jit 12 | 13 | @jit(nopython=True, parallel = True) 14 | def thomas_solve(diagonal, upper, lower, rhs): 15 | ''' 16 | Solves a tridiagonal matrix equation using the Thomas algorithm [1]. 17 | Inputs: The matrix is passed in terms of 3 NumPy arrays corresponding to the upper, lower, and main 18 | diagonal. 19 | rhs: array for the right hand side 20 | 21 | 22 | Reference: 23 | [1] https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm 24 | ''' 25 | 26 | num_elements = len(diagonal) - 1 27 | 28 | #start = time.clock() 29 | 30 | diagonal = np.copy(diagonal) #do a copy to ensure the original diag is not changed 31 | 32 | x = np.empty(num_elements+2) 33 | 34 | # Forward substitution 35 | for i in range(2, num_elements + 1): # recall range uses [ ) ] 36 | cdiag_ratio = lower[i-1]/diagonal[i-1] 37 | diagonal[i] -= cdiag_ratio * upper[i-1] 38 | rhs[i] -= cdiag_ratio * rhs[i-1] 39 | 40 | # Backward substitution 41 | x[num_elements] = rhs[num_elements]/diagonal[num_elements] #lin eqn. corresponding to last row 42 | for i in range(num_elements, 1, -1): 43 | x[i-1] = (rhs[i-1] - x[i]*upper[i-1])/diagonal[i-1] 44 | 45 | #end = time.clock() 46 | 47 | return x -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Oct 19, 2018 4 | 5 | @author: Tim 6 | """ 7 | 8 | --------------------------------------------------------------------------------