-
Notifications
You must be signed in to change notification settings - Fork 73
/
potential_utilities.py
executable file
·236 lines (179 loc) · 8.78 KB
/
potential_utilities.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
from numpy import ones, array, sqrt, trapz
from scipy.sparse import dia_matrix
from scipy.sparse.linalg import eigs
from solcore.science_tracker import science_reference
from solcore.constants import *
import numpy as np
from operator import itemgetter
sort_simultaneous = lambda *lists: [list(x) for x in zip(*sorted(zip(*lists), key=itemgetter(0)))]
from . import structure_utilities
from .graphics import Graph, GraphData
def tridiag_euler(V, z, m, periodic=False, num_eigenvalues=10, quasiconfined=0):
"""
Returns eignvalue and eigenvectors of an arbitrary potential.
A tridiagonal matrix is constructed by writing the variable effective
mass Schrodinger equation over a series of mesh points. The eigenvalues
of the matrix correspond to the allowed energy levels of the system.
The previous solver, eig, has been replaced by the spare matrix version, eigs, that is faster to compute
"""
science_reference("Varible effective mass Schordinger equation and tridiagonal solution method.",
"Frensley, W. R. (1991). \
Numerical evaluation of resonant states. \
Superlattices and Microstructures, 11(3), 347350. \
doi:10.1016/0749-6036(92)90396-M")
N = len(V)
dz = np.gradient(z)
m = m * ones(N)
# Vectorise effective mass differences to avoid a loop
m = np.insert(m, (0, len(m)), (m[0], m[-1]))
m_a = m[0:-2] # m_(j-1) from Frensley, W. R. (1991)
m_b = m[1:-1] # m_j
m_c = m[2:] # m_(j+1)
# These are the interior diagonals of equation 18 in the above ref.
axis = hbar ** 2 / (4 * dz ** 2) * (1 / m_a + 2 / m_b + 1 / m_c) + V # d_j from Frensley, W. R. (1991)
upper = hbar ** 2 / (4 * dz ** 2) * (1 / m_a + 1 / m_b) # s_(j+1)
lower = hbar ** 2 / (4 * dz ** 2) * (1 / m_b + 1 / m_c) # s_j
if periodic:
TopRight = np.zeros(len(axis))
BottomLeft = np.zeros(len(axis))
TopRight[-1] = -lower[-1] # The last point becomes the one before the first one
BottomLeft[0] = -upper[0] # The first point becomes the one after the last one
index = (-N + 1, -1, 0, 1, N - 1)
diagonals = (BottomLeft, -lower, axis, -upper, TopRight)
else:
index = (-1, 0, 1)
diagonals = (-lower, axis, -upper)
H = dia_matrix((diagonals, index), shape=(N, N))
# H[0,-1] = -lower[-1] # The last point becomes the one before the first one
# H[-1,0] = -upper[0] # The first point becomes the one after the last one
sigma = np.min(V) # The top of the potential (valence band) is the target energy for the eigenvalues
# The heavy numerical calculation
E, Psi = eigs(H, k=num_eigenvalues, which='LR', sigma=sigma)
# Allow for quasi confined levels to go through. They can be discarded later with the filter
confined_levels = [i for i, e in enumerate(E) if e < quasiconfined * q]
E, Psi = E[confined_levels].real, array(Psi[:, confined_levels]).transpose()
Psi = [(p / sqrt(trapz(p * p, x=z))).real for p in Psi]
E, Psi = sort_simultaneous(E, Psi)
return E, Psi
def schroedinger_solve(x, V, m, num_eigenvalues=10, periodic=False, offset=0, electron=True, quasiconfined=0):
"""Returns normalised wavefuctions from the potential profile.
Arguments:
x -- spatial grid
V -- potential
m -- effective mass
Keywords:
electron -- whether the wavefunctions describe electrons or holes (default: True)
num_eigenvalues -- Number of eigenvalues to calculate (default = 10)
periodic -- not to sure what this does (default: False)
"""
if electron:
v_shift = max(V) + offset
E, psi = tridiag_euler(V - v_shift, x, m, num_eigenvalues=num_eigenvalues, periodic=periodic,
quasiconfined=quasiconfined)
E = list() if len(E) == 0 else np.array(E) + v_shift
else:
v_shift = min(V) - offset
E, psi = tridiag_euler(-V + v_shift, x, m, num_eigenvalues=num_eigenvalues, periodic=periodic,
quasiconfined=quasiconfined)
E = list() if len(E) == 0 else -np.array(E) + v_shift
return E, psi
def __potentials_to_wavefunctions_energies_internal(x, Ve, me, Vhh, mhh, Vlh, mlh, num_eigenvalues=10, periodic=False,
offset=0, filter_strength=0, structure=None, quasiconfined=0):
"""Returns normalised wavefuctions from the potential profile.
Arguments:
x -- spatial grid
Ve -- electron potential
me -- electron effective mass
Vlh -- light hole potential
mlh -- light hole effective mass
Vhh -- heavy hole potential
mhh -- heavy hole effective mass
Keywords:
num_eigenvalues -- Number of eigenvalues to calculate (default = 10)
periodic -- not to sure what this does (default: False)
"""
Ee, psi_e = schroedinger_solve(x, Ve, me, num_eigenvalues, periodic, offset, electron=True,
quasiconfined=quasiconfined)
Ehh, psi_hh = schroedinger_solve(x, Vhh, mhh, num_eigenvalues, periodic, offset, electron=False,
quasiconfined=quasiconfined)
Elh, psi_lh = schroedinger_solve(x, Vlh, mlh, num_eigenvalues, periodic, offset, electron=False,
quasiconfined=quasiconfined)
if filter_strength != 0:
assert structure is not None, "Need to provide structure to find well regions for filtering"
print("filtering")
Ee, psi_e = discard_unconfined(x, structure, Ee, psi_e, filter_strength)
Ehh, psi_hh = discard_unconfined(x, structure, Ehh, psi_hh, filter_strength)
Elh, psi_lh = discard_unconfined(x, structure, Elh, psi_lh, filter_strength)
# Ee, psi_e = discard_unconfined_energy(Ee, psi_e, Ve, quasiconfined)
# Ehh, psi_hh = discard_unconfined_energy(Ehh, psi_hh, Vhh, quasiconfined)
# Elh, psi_lh = discard_unconfined_energy(Elh, psi_lh, Vlh, quasiconfined)
# print(me/electron_mass, mhh/electron_mass, mlh/electron_mass)
# print(me_plane, mhh_plane, mlh_plane)
#
# import sys
# sys.exit()
return x, Ee, psi_e, Ehh, psi_hh, Elh, psi_lh
def discard_unconfined_energy(E, psi, V, quasiconfined):
before = len(E)
maxE = min(abs(V[0]), max(abs(V))) + quasiconfined
try:
E, psi = zip(*[(E_i, psi_i)
for (E_i, psi_i) in zip(E, psi)
if abs(E_i) <= maxE])
except ValueError as exception:
print("Warning: wavefunction filter removed all states for this band, try reducing the filter strength.")
return ([], [])
print("Wavefunction filter removed %d state%s." % (before - len(E), "" if (before - len(E)) == 1 else "s"))
return E, psi
def discard_unconfined(x, structure, E, psi, threshold=0.8):
if threshold == 0: # bypass the filter code, saving time
return E, psi
indx = structure_utilities.well_regions(x, structure)
before = len(E)
# for (E_i, psi_i) in zip(E, psi):
# print (psi_i[indx])
try:
E, psi = zip(*[(E_i, psi_i)
for (E_i, psi_i) in zip(E, psi)
if np.trapz(psi_i[indx] ** 2, x=x[indx]) / np.trapz(psi_i ** 2, x=x) >= threshold])
except ValueError as exception:
print("Warning: wavefunction filter removed all states for this band, try reducing the filter strength.")
return ([], [])
print("Wavefunction filter removed %d state%s." % (before - len(E), "" if (before - len(E)) == 1 else "s"))
return E, psi
def potentials_to_wavefunctions_energies(x, Ve, me, Vhh, mhh, Vlh, mlh, num_eigenvalues=10, periodic=False, offset=0,
filter_strength=0, structure=None, quasiconfined=0, **kwargs):
x, Ee, psi_e, Ehh, psi_hh, Elh, psi_lh = __potentials_to_wavefunctions_energies_internal(
x, Ve, me, Vhh, mhh, Vlh, mlh, num_eigenvalues,
periodic, offset,
filter_strength, structure,
quasiconfined)
return {
"x": x,
"Ee": Ee,
"psi_e": psi_e,
"Ehh": Ehh,
"psi_hh": psi_hh,
"Elh": Elh,
"psi_lh": psi_lh,
"Ve": Ve,
"me": me,
"Vhh": Vhh,
"Vlh": Vlh,
"mhh": mhh,
"mlh": mlh
}
def graph(x, Ve, me, Vhh, mhh, Vlh, mlh, **kwargs):
defaults = {
"edit": lambda x, y: (x * 1e9, y / q),
"xlabel": "Depth (nm)",
"ylabel": "Energy (eV)",
}
defaults.update(kwargs)
data = []
normalise_psi = lambda p: p * q * 5e-6
data.append(GraphData(x, Ve, color="black", linewidth=2))
data.append(GraphData(x, Vlh, color="black", linewidth=2, dashes=[1, 1]))
data.append(GraphData(x, Vhh, color="black", linewidth=2))
g = Graph(data, **defaults)
return g