root / ase / transport / calculators.py @ 1
Historique | Voir | Annoter | Télécharger (15,15 ko)
1 |
import numpy as np |
---|---|
2 |
|
3 |
from numpy import linalg |
4 |
from ase.transport.selfenergy import LeadSelfEnergy, BoxProbe |
5 |
from ase.transport.greenfunction import GreenFunction |
6 |
from ase.transport.tools import subdiagonalize, cutcoupling, tri2full, dagger,\ |
7 |
rotate_matrix
|
8 |
|
9 |
|
10 |
class TransportCalculator: |
11 |
"""Determine transport properties of a device sandwiched between
|
12 |
two semi-infinite leads using a Green function method.
|
13 |
"""
|
14 |
|
15 |
def __init__(self, **kwargs): |
16 |
"""Create the transport calculator.
|
17 |
|
18 |
Parameters
|
19 |
==========
|
20 |
h : (N, N) ndarray
|
21 |
Hamiltonian matrix for the central region.
|
22 |
s : {None, (N, N) ndarray}, optional
|
23 |
Overlap matrix for the central region.
|
24 |
Use None for an orthonormal basis.
|
25 |
h1 : (N1, N1) ndarray
|
26 |
Hamiltonian matrix for lead1.
|
27 |
h2 : {None, (N2, N2) ndarray}, optional
|
28 |
Hamiltonian matrix for lead2. You may use None if lead1 and lead2
|
29 |
are identical.
|
30 |
s1 : {None, (N1, N1) ndarray}, optional
|
31 |
Overlap matrix for lead1. Use None for an orthonomormal basis.
|
32 |
hc1 : {None, (N1, N) ndarray}, optional
|
33 |
Hamiltonian coupling matrix between the first principal
|
34 |
layer in lead1 and the central region.
|
35 |
hc2 : {None, (N2, N} ndarray), optional
|
36 |
Hamiltonian coupling matrix between the first principal
|
37 |
layer in lead2 and the central region.
|
38 |
sc1 : {None, (N1, N) ndarray}, optional
|
39 |
Overlap coupling matrix between the first principal
|
40 |
layer in lead1 and the central region.
|
41 |
sc2 : {None, (N2, N) ndarray}, optional
|
42 |
Overlap coupling matrix between the first principal
|
43 |
layer in lead2 and the central region.
|
44 |
energies : {None, array_like}, optional
|
45 |
Energy points for which calculated transport properties are
|
46 |
evaluated.
|
47 |
eta : {1.0e-5, float}, optional
|
48 |
Infinitesimal for the central region Green function.
|
49 |
eta1/eta2 : {1.0e-5, float}, optional
|
50 |
Infinitesimal for lead1/lead2 Green function.
|
51 |
align_bf : {None, int}, optional
|
52 |
Use align_bf=m to shift the central region
|
53 |
by a constant potential such that the m'th onsite element
|
54 |
in the central region is aligned to the m'th onsite element
|
55 |
in lead1 principal layer.
|
56 |
logfile : {None, str}, optional
|
57 |
Write a logfile to file with name `logfile`.
|
58 |
Use '-' to write to std out.
|
59 |
eigenchannels: {0, int}, optional
|
60 |
Number of eigenchannel transmission coefficients to
|
61 |
calculate.
|
62 |
pdos : {None, (N,) array_like}, optional
|
63 |
Specify which basis functions to calculate the
|
64 |
projected density of states for.
|
65 |
dos : {False, bool}, optional
|
66 |
The total density of states of the central region.
|
67 |
box: XXX
|
68 |
YYY
|
69 |
|
70 |
If hc1/hc2 are None, they are assumed to be identical to
|
71 |
the coupling matrix elements between neareste neighbor
|
72 |
principal layers in lead1/lead2.
|
73 |
|
74 |
Examples
|
75 |
========
|
76 |
>>> import numpy as np
|
77 |
>>> h = np.array((0,)).reshape((1,1))
|
78 |
>>> h1 = np.array((0, -1, -1, 0)).reshape(2,2)
|
79 |
>>> energies = np.arange(-3, 3, 0.1)
|
80 |
>>> calc = TransportCalculator(h=h, h1=h1, energies=energies)
|
81 |
>>> T = calc.get_transmission()
|
82 |
|
83 |
"""
|
84 |
|
85 |
# The default values for all extra keywords
|
86 |
self.input_parameters = {'energies': None, |
87 |
'h': None, |
88 |
'h1': None, |
89 |
'h2': None, |
90 |
's': None, |
91 |
's1': None, |
92 |
's2': None, |
93 |
'hc1': None, |
94 |
'hc2': None, |
95 |
'sc1': None, |
96 |
'sc2': None, |
97 |
'box': None, |
98 |
'align_bf': None, |
99 |
'eta1': 1e-5, |
100 |
'eta2': 1e-5, |
101 |
'eta': 1e-5, |
102 |
'logfile': None, # '-', |
103 |
'eigenchannels': 0, |
104 |
'dos': False, |
105 |
'pdos': [],
|
106 |
} |
107 |
self.initialized = False # Changed Hamiltonians? |
108 |
self.uptodate = False # Changed energy grid? |
109 |
self.set(**kwargs)
|
110 |
|
111 |
def set(self, **kwargs): |
112 |
for key in kwargs: |
113 |
if key in ['h', 'h1', 'h2', 'hc1', 'hc2', |
114 |
's', 's1', 's2', 'sc1', 'sc2', |
115 |
'eta', 'eta1', 'eta2', 'align_bf', 'box']: |
116 |
self.initialized = False |
117 |
self.uptodate = False |
118 |
break
|
119 |
elif key in ['energies', 'eigenchannels', 'dos', 'pdos']: |
120 |
self.uptodate = False |
121 |
elif key not in self.input_parameters: |
122 |
raise KeyError, '\'%s\' not a vaild keyword' % key |
123 |
|
124 |
self.input_parameters.update(kwargs)
|
125 |
log = self.input_parameters['logfile'] |
126 |
if log is None: |
127 |
class Trash: |
128 |
def write(self, s): |
129 |
pass
|
130 |
def flush(self): |
131 |
pass
|
132 |
self.log = Trash()
|
133 |
elif log == '-': |
134 |
from sys import stdout |
135 |
self.log = stdout
|
136 |
elif 'logfile' in kwargs: |
137 |
self.log = open(log, 'w') |
138 |
|
139 |
def initialize(self): |
140 |
if self.initialized: |
141 |
return
|
142 |
|
143 |
print >> self.log, '# Initializing calculator...' |
144 |
|
145 |
p = self.input_parameters
|
146 |
if p['s'] == None: |
147 |
p['s'] = np.identity(len(p['h'])) |
148 |
|
149 |
identical_leads = False
|
150 |
if p['h2'] == None: |
151 |
p['h2'] = p['h1'] # Lead2 is idendical to lead1 |
152 |
identical_leads = True
|
153 |
|
154 |
if p['s1'] == None: |
155 |
p['s1'] = np.identity(len(p['h1'])) |
156 |
|
157 |
if p['s2'] == None and not identical_leads: |
158 |
p['s2'] = np.identity(len(p['h2'])) # Orthonormal basis for lead 2 |
159 |
else: # Lead2 is idendical to lead1 |
160 |
p['s2'] = p['s1'] |
161 |
|
162 |
|
163 |
h_mm = p['h']
|
164 |
s_mm = p['s']
|
165 |
pl1 = len(p['h1']) / 2 |
166 |
pl2 = len(p['h2']) / 2 |
167 |
h1_ii = p['h1'][:pl1, :pl1]
|
168 |
h1_ij = p['h1'][:pl1, pl1:2 * pl1] |
169 |
s1_ii = p['s1'][:pl1, :pl1]
|
170 |
s1_ij = p['s1'][:pl1, pl1:2 * pl1] |
171 |
h2_ii = p['h2'][:pl2, :pl2]
|
172 |
h2_ij = p['h2'][pl2: 2 * pl2, :pl2] |
173 |
s2_ii = p['s2'][:pl2, :pl2]
|
174 |
s2_ij = p['s2'][pl2: 2 * pl2, :pl2] |
175 |
|
176 |
if p['hc1'] is None: |
177 |
nbf = len(h_mm)
|
178 |
h1_im = np.zeros((pl1, nbf), complex)
|
179 |
s1_im = np.zeros((pl1, nbf), complex)
|
180 |
h1_im[:pl1, :pl1] = h1_ij |
181 |
s1_im[:pl1, :pl1] = s1_ij |
182 |
p['hc1'] = h1_im
|
183 |
p['sc1'] = s1_im
|
184 |
else:
|
185 |
h1_im = p['hc1']
|
186 |
if p['sc1'] is not None: |
187 |
s1_im = p['sc1']
|
188 |
else:
|
189 |
s1_im = np.zeros(h1_im.shape, complex)
|
190 |
p['sc1'] = s1_im
|
191 |
|
192 |
if p['hc2'] is None: |
193 |
h2_im = np.zeros((pl2, nbf), complex)
|
194 |
s2_im = np.zeros((pl2, nbf), complex)
|
195 |
h2_im[-pl2:, -pl2:] = h2_ij |
196 |
s2_im[-pl2:, -pl2:] = s2_ij |
197 |
p['hc2'] = h2_im
|
198 |
p['sc2'] = s2_im
|
199 |
else:
|
200 |
h2_im = p['hc2']
|
201 |
if p['sc2'] is not None: |
202 |
s2_im = p['sc2']
|
203 |
else:
|
204 |
s2_im = np.zeros(h2_im.shape, complex)
|
205 |
p['sc2'] = s2_im
|
206 |
|
207 |
align_bf = p['align_bf']
|
208 |
if align_bf != None: |
209 |
diff = (h_mm[align_bf, align_bf] - h1_ii[align_bf, align_bf]) \ |
210 |
/ s_mm[align_bf, align_bf] |
211 |
print >> self.log, '# Aligning scat. H to left lead H. diff=', diff |
212 |
h_mm -= diff * s_mm |
213 |
|
214 |
# Setup lead self-energies
|
215 |
# All infinitesimals must be > 0
|
216 |
assert np.all(np.array((p['eta'], p['eta1'], p['eta2'])) > 0.0) |
217 |
self.selfenergies = [LeadSelfEnergy((h1_ii, s1_ii),
|
218 |
(h1_ij, s1_ij), |
219 |
(h1_im, s1_im), |
220 |
p['eta1']),
|
221 |
LeadSelfEnergy((h2_ii, s2_ii), |
222 |
(h2_ij, s2_ij), |
223 |
(h2_im, s2_im), |
224 |
p['eta2'])]
|
225 |
box = p['box']
|
226 |
if box is not None: |
227 |
print 'Using box probe!' |
228 |
self.selfenergies.append(
|
229 |
BoxProbe(eta=box[0], a=box[1], b=box[2], energies=box[3], |
230 |
S=s_mm, T=0.3))
|
231 |
|
232 |
#setup scattering green function
|
233 |
self.greenfunction = GreenFunction(selfenergies=self.selfenergies, |
234 |
H=h_mm, |
235 |
S=s_mm, |
236 |
eta=p['eta'])
|
237 |
|
238 |
self.initialized = True |
239 |
|
240 |
def update(self): |
241 |
if self.uptodate: |
242 |
return
|
243 |
|
244 |
p = self.input_parameters
|
245 |
self.energies = p['energies'] |
246 |
nepts = len(self.energies) |
247 |
nchan = p['eigenchannels']
|
248 |
pdos = p['pdos']
|
249 |
self.T_e = np.empty(nepts)
|
250 |
if p['dos']: |
251 |
self.dos_e = np.empty(nepts)
|
252 |
if pdos != []:
|
253 |
self.pdos_ne = np.empty((len(pdos), nepts)) |
254 |
if nchan > 0: |
255 |
self.eigenchannels_ne = np.empty((nchan, nepts))
|
256 |
|
257 |
for e, energy in enumerate(self.energies): |
258 |
Ginv_mm = self.greenfunction.retarded(energy, inverse=True) |
259 |
lambda1_mm = self.selfenergies[0].get_lambda(energy) |
260 |
lambda2_mm = self.selfenergies[1].get_lambda(energy) |
261 |
a_mm = linalg.solve(Ginv_mm, lambda1_mm) |
262 |
b_mm = linalg.solve(dagger(Ginv_mm), lambda2_mm) |
263 |
T_mm = np.dot(a_mm, b_mm) |
264 |
if nchan > 0: |
265 |
t_n = linalg.eigvals(T_mm).real |
266 |
self.eigenchannels_ne[:, e] = np.sort(t_n)[-nchan:]
|
267 |
self.T_e[e] = np.sum(t_n)
|
268 |
else:
|
269 |
self.T_e[e] = np.trace(T_mm).real
|
270 |
|
271 |
print >> self.log, energy, self.T_e[e] |
272 |
self.log.flush()
|
273 |
|
274 |
if p['dos']: |
275 |
self.dos_e[e] = self.greenfunction.dos(energy) |
276 |
|
277 |
if pdos != []:
|
278 |
self.pdos_ne[:, e] = np.take(self.greenfunction.pdos(energy), |
279 |
pdos) |
280 |
|
281 |
self.uptodate = True |
282 |
|
283 |
def print_pl_convergence(self): |
284 |
self.initialize()
|
285 |
pl1 = len(self.input_parameters['h1']) / 2 |
286 |
|
287 |
h_ii = self.selfenergies[0].h_ii |
288 |
s_ii = self.selfenergies[0].s_ii |
289 |
ha_ii = self.greenfunction.H[:pl1, :pl1]
|
290 |
sa_ii = self.greenfunction.S[:pl1, :pl1]
|
291 |
c1 = np.abs(h_ii - ha_ii).max() |
292 |
c2 = np.abs(s_ii - sa_ii).max() |
293 |
print 'Conv (h,s)=%.2e, %2.e' % (c1, c2) |
294 |
|
295 |
def plot_pl_convergence(self): |
296 |
self.initialize()
|
297 |
pl1 = len(self.input_parameters['h1']) / 2 |
298 |
hlead = self.selfenergies[0].h_ii.real.diagonal() |
299 |
hprincipal = self.greenfunction.H.real.diagonal[:pl1]
|
300 |
|
301 |
import pylab as pl |
302 |
pl.plot(hlead, label='lead')
|
303 |
pl.plot(hprincipal, label='principal layer')
|
304 |
pl.axis('tight')
|
305 |
pl.show() |
306 |
|
307 |
def get_transmission(self): |
308 |
self.initialize()
|
309 |
self.update()
|
310 |
return self.T_e |
311 |
|
312 |
def get_dos(self): |
313 |
self.initialize()
|
314 |
self.update()
|
315 |
return self.dos_e |
316 |
|
317 |
def get_eigenchannels(self, n=None): |
318 |
"""Get ``n`` first eigenchannels."""
|
319 |
self.initialize()
|
320 |
self.update()
|
321 |
if n is None: |
322 |
n = self.input_parameters['eigenchannels'] |
323 |
return self.eigenchannels_ne[:n] |
324 |
|
325 |
def get_pdos(self): |
326 |
self.initialize()
|
327 |
self.update()
|
328 |
return self.pdos_ne |
329 |
|
330 |
def subdiagonalize_bfs(self, bfs, apply=False): |
331 |
self.initialize()
|
332 |
bfs = np.array(bfs) |
333 |
p = self.input_parameters
|
334 |
h_mm = p['h']
|
335 |
s_mm = p['s']
|
336 |
ht_mm, st_mm, c_mm, e_m = subdiagonalize(h_mm, s_mm, bfs) |
337 |
if apply: |
338 |
self.uptodate = False |
339 |
h_mm[:] = ht_mm |
340 |
s_mm[:] = st_mm |
341 |
# Rotate coupling between lead and central region
|
342 |
for alpha, sigma in enumerate(self.selfenergies): |
343 |
sigma.h_im[:] = np.dot(sigma.h_im, c_mm) |
344 |
sigma.s_im[:] = np.dot(sigma.s_im, c_mm) |
345 |
|
346 |
c_mm = np.take(c_mm, bfs, axis=0)
|
347 |
c_mm = np.take(c_mm, bfs, axis=1)
|
348 |
return ht_mm, st_mm, e_m, c_mm
|
349 |
|
350 |
def cutcoupling_bfs(self, bfs, apply=False): |
351 |
self.initialize()
|
352 |
bfs = np.array(bfs) |
353 |
p = self.input_parameters
|
354 |
h_pp = p['h'].copy()
|
355 |
s_pp = p['s'].copy()
|
356 |
cutcoupling(h_pp, s_pp, bfs) |
357 |
if apply: |
358 |
self.uptodate = False |
359 |
p['h'][:] = h_pp
|
360 |
p['s'][:] = s_pp
|
361 |
for alpha, sigma in enumerate(self.selfenergies): |
362 |
for m in bfs: |
363 |
sigma.h_im[:, m] = 0.0
|
364 |
sigma.s_im[:, m] = 0.0
|
365 |
return h_pp, s_pp
|
366 |
|
367 |
def lowdin_rotation(self, apply=False): |
368 |
p = self.input_parameters
|
369 |
h_mm = p['h']
|
370 |
s_mm = p['s']
|
371 |
eig, rot_mm = linalg.eigh(s_mm) |
372 |
eig = np.abs(eig) |
373 |
rot_mm = np.dot(rot_mm / np.sqrt(eig), dagger(rot_mm)) |
374 |
if apply: |
375 |
self.uptodate = False |
376 |
h_mm[:] = rotate_matrix(h_mm, rot_mm) # rotate C region
|
377 |
s_mm[:] = rotate_matrix(s_mm, rot_mm) |
378 |
for alpha, sigma in enumerate(self.selfenergies): |
379 |
sigma.h_im[:] = np.dot(sigma.h_im, rot_mm) # rotate L-C coupl.
|
380 |
sigma.s_im[:] = np.dot(sigma.s_im, rot_mm) |
381 |
|
382 |
return rot_mm
|
383 |
|
384 |
def get_left_channels(self, energy, nchan=1): |
385 |
self.initialize()
|
386 |
g_s_ii = self.greenfunction.retarded(energy)
|
387 |
lambda_l_ii = self.selfenergies[0].get_lambda(energy) |
388 |
lambda_r_ii = self.selfenergies[1].get_lambda(energy) |
389 |
|
390 |
if self.greenfunction.S is None: |
391 |
s_s_qsrt_ii = s_s_isqrt = np.identity(len(g_s_ii))
|
392 |
else:
|
393 |
s_mm = self.greenfunction.S
|
394 |
s_s_i, s_s_ii = linalg.eig(s_mm) |
395 |
s_s_i = np.abs(s_s_i) |
396 |
s_s_sqrt_i = np.sqrt(s_s_i) # sqrt of eigenvalues
|
397 |
s_s_sqrt_ii = np.dot(s_s_ii * s_s_sqrt_i, dagger(s_s_ii)) |
398 |
s_s_isqrt_ii = np.dot(s_s_ii / s_s_sqrt_i, dagger(s_s_ii)) |
399 |
|
400 |
lambdab_r_ii = np.dot(np.dot(s_s_isqrt_ii, lambda_r_ii),s_s_isqrt_ii) |
401 |
a_l_ii = np.dot(np.dot(g_s_ii, lambda_l_ii), dagger(g_s_ii)) |
402 |
ab_l_ii = np.dot(np.dot(s_s_sqrt_ii, a_l_ii), s_s_sqrt_ii) |
403 |
lambda_i, u_ii = linalg.eig(ab_l_ii) |
404 |
ut_ii = np.sqrt(lambda_i / (2.0 * np.pi)) * u_ii
|
405 |
m_ii = 2 * np.pi * np.dot(np.dot(dagger(ut_ii), lambdab_r_ii),ut_ii)
|
406 |
T_i,c_in = linalg.eig(m_ii) |
407 |
T_i = np.abs(T_i) |
408 |
|
409 |
channels = np.argsort(-T_i)[:nchan] |
410 |
c_in = np.take(c_in, channels, axis=1)
|
411 |
T_n = np.take(T_i, channels) |
412 |
v_in = np.dot(np.dot(s_s_isqrt_ii, ut_ii), c_in) |
413 |
|
414 |
return T_n, v_in
|