Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: topocm
Views: 392
1
import kwant
2
from kwant import physics
3
import numpy as np
4
import warnings
5
6
# TODO: When the numpy behavior is updated and widespread, remove the fix
7
with warnings.catch_warnings():
8
warnings.simplefilter("ignore")
9
split_fixed = np.split(np.zeros((0, 2)), 2)[0].shape == (0, 2)
10
if split_fixed:
11
split = np.split
12
else:
13
def split(array, n, axis=0):
14
if array.shape[axis] != 0:
15
return np.split(array, n, axis)
16
else:
17
return n * [array]
18
19
def block_diag(*matrices):
20
"""Construct a block diagonal matrix out of the input matrices.
21
22
Like scipy.linalg.block_diag, but works for zero size matrices."""
23
# TODO: Use scipy block_diag once
24
# https://github.com/scipy/scipy/issues/4908 is fixed and
25
# the fix widespread.
26
rows, cols = np.sum([mat.shape for mat in matrices], axis=0)
27
b_mat = np.zeros((rows,cols), dtype='complex')
28
rows, cols = 0, 0
29
for mat in matrices:
30
new_rows = rows + mat.shape[0]
31
new_cols = cols + mat.shape[1]
32
b_mat[rows:new_rows, cols:new_cols] = mat
33
rows, cols = new_rows, new_cols
34
return b_mat
35
36
37
class ConservationInfiniteSystem(kwant.system.InfiniteSystem):
38
def __init__(self, lead, projector_list):
39
"""A lead with a conservation law.
40
41
Lead modes have definite values of the conservation law observable."""
42
self._wrapped = lead
43
self.projector_list = projector_list
44
np.testing.assert_allclose(sum([projector.dot(projector.conj().T) for projector
45
in projector_list]),
46
np.eye(max(projector_list[0].shape)), rtol=1e-5,
47
atol=1e-8)
48
49
def hamiltonian(self, i, j, *args):
50
return self._wrapped.hamiltonian(i, j, *args)
51
52
def hamiltonian_submatrix(self, args=(), to_sites=None, from_sites=None,
53
sparse=False, return_norb=False):
54
return self._wrapped.hamiltonian_submatrix(args, to_sites, from_sites,
55
sparse, return_norb)
56
57
def pos(self, site):
58
return self._wrapped.pos(site)
59
60
def cell_hamiltonian(self, args=(), sparse=False):
61
return self._wrapped.cell_hamiltonian(args, sparse)
62
63
def inter_cell_hopping(self, args=(), sparse=False):
64
return self._wrapped.inter_cell_hopping(args, sparse)
65
66
def modes(self, energy=0, args=()):
67
def basis_change(a, b):
68
return b.T.conj().dot(a).dot(b)
69
70
projector_list = self.projector_list
71
72
ham = self.cell_hamiltonian(args)
73
hop = self.inter_cell_hopping(args)
74
shape = ham.shape
75
assert len(shape) == 2
76
assert shape[0] == shape[1]
77
# Subtract energy from the diagonal.
78
ham.flat[::ham.shape[0] + 1] -= energy
79
80
# Project out blocks
81
ham_blocks = [basis_change(ham, projector)
82
for projector in projector_list]
83
hop_blocks = [basis_change(hop, projector)
84
for projector in projector_list]
85
86
# Check that h, t are actually block-diagonal
87
inverse_transform = np.hstack(projector_list).T.conj()
88
np.testing.assert_allclose(basis_change(block_diag(*ham_blocks),
89
inverse_transform), ham, rtol=1e-5,
90
atol=1e-8)
91
np.testing.assert_allclose(basis_change(block_diag(*hop_blocks),
92
inverse_transform), hop, rtol=1e-5,
93
atol=1e-8)
94
95
# symm_modes[i] is a tuple containing PropagatingModes and
96
# StabilizedModes objects for block i.
97
symm_modes = [physics.modes(h, t) for h, t in
98
zip(ham_blocks, hop_blocks)]
99
100
# Get wavefunctions to correct size and transform them back to the
101
# tight binding basis.
102
wave_functions = [modes[0].wave_functions for modes in symm_modes]
103
for i, wave_function in enumerate(wave_functions):
104
wave_functions[i] = projector_list[i].dot(wave_function)
105
106
def rearrange(arr_list):
107
"""Split and rearrange a list of arrays.
108
109
Takes first halves of evey array in the list, puts them first, then
110
the second halves.
111
"""
112
lis = [split(arr, 2) for arr in arr_list]
113
114
if not lis:
115
return np.r_[tuple(arr_list)]
116
else:
117
lefts, rights = zip(*lis)
118
return np.r_[tuple(lefts + rights)]
119
120
# Reorder by direction of propagation
121
wave_functions = rearrange([wf.T for wf in wave_functions]).T
122
momenta = rearrange([modes[0].momenta for modes in symm_modes])
123
velocities = rearrange([modes[0].velocities for modes in symm_modes])
124
125
nmodes = [modes[1].nmodes for modes in symm_modes]
126
prop_modes = kwant.physics.PropagatingModes(wave_functions, velocities,
127
momenta)
128
# Store the number of modes per block as an attribute.
129
# nmodes[i] is the number of left or right moving modes in block i.
130
prop_modes.block_nmodes = nmodes
131
132
# Pick out left moving, right moving and evanescent modes from vecs and
133
# vecslmbdainv, and combine into block diagonal matrices. vecs[i] is a
134
# tuple, such that vecs[i][0] is the left-movers, vecs[i][1]
135
# right-movers and vecs[i][2] the evanescent states for block
136
# i. vecslmbdainv has the same structure.
137
vecs = [(modes[1].vecs[:, :n], modes[1].vecs[:, n:(2*n)],
138
modes[1].vecs[:, (2*n):]) for n, modes in
139
zip(nmodes, symm_modes)]
140
vecslmbdainv = [(modes[1].vecslmbdainv[:, :n],
141
modes[1].vecslmbdainv[:, n:(2*n)],
142
modes[1].vecslmbdainv[:, (2*n):])
143
for n,modes in zip(nmodes, symm_modes)]
144
145
lvecs, rvecs, evvecs = zip(*vecs)
146
lvecs = block_diag(*lvecs)
147
rvecs = block_diag(*rvecs)
148
evvecs = block_diag(*evvecs)
149
lvecslmbdainv, rvecslmbdainv, evvecslmbdainv = zip(*vecslmbdainv)
150
lvecslmbdainv = block_diag(*lvecslmbdainv)
151
rvecslmbdainv = block_diag(*rvecslmbdainv)
152
evvecslmbdainv = block_diag(*evvecslmbdainv)
153
154
vecs = np.hstack((lvecs,rvecs,evvecs))
155
vecslmbdainv = np.hstack((lvecslmbdainv,rvecslmbdainv,evvecslmbdainv))
156
157
# Combine sqrt_hops into a block diagonal matrix. If it is None,
158
# replace with identity matrix
159
sqrt_hops = []
160
for modes, projector in zip(symm_modes, projector_list):
161
if modes[1].sqrt_hop is None:
162
sqrt_hops.append(np.eye(min(projector.shape)))
163
else:
164
sqrt_hops.append(modes[1].sqrt_hop)
165
# Gather into a matrix and project back to TB basis
166
sqrt_hops = (np.hstack(projector_list)).dot(block_diag(*sqrt_hops))
167
168
stab_modes = kwant.physics.StabilizedModes(vecs, vecslmbdainv,
169
sum(nmodes), sqrt_hops)
170
171
return prop_modes, stab_modes
172
173
174
class SymmetricSMatrix(kwant.solvers.common.SMatrix):
175
def __init__(self, SMatrix):
176
"""A scattering matrix that understands leads with conservation laws.
177
178
Extends the `kwant.SMatrix` by providing a way to query transmission
179
between subblocks corresponding to definite values of the conserved
180
quantity.
181
"""
182
# SMatrix doesn't have any private instance attributes, so copying its
183
# dictionary only uses the public interface.
184
self.__dict__ = SMatrix.__dict__
185
# in_offsets marks beginnings and ends of blocks in the scattering
186
# matrix corresponding to the in leads. The end of the block
187
# corresponding to lead i is taken as the beginning of the block of
188
# i+1. Same for out leads. For each lead block of the scattering
189
# matrix, we want to pick out the computed blocks of the conservation
190
# law. The offsets of these symmetry blocks are stored in
191
# block_offsets, for all leads. List of lists containing the sizes of
192
# symmetry blocks in all leads. block_sizes[i][j] is the number of left
193
# or right moving modes in symmetry block j of lead
194
# i. len(block_sizes[i]) is the number of blocks in lead i.
195
leads_block_sizes = []
196
for info in self.lead_info:
197
# If a lead does not have block structure, append None.
198
leads_block_sizes.append(getattr(info, 'block_nmodes', None))
199
self.leads_block_sizes = leads_block_sizes
200
block_offsets = []
201
for lead_block_sizes in self.leads_block_sizes: # Cover all leads
202
if lead_block_sizes is None:
203
block_offsets.append(lead_block_sizes)
204
else:
205
block_offset = np.zeros(len(lead_block_sizes) + 1, int)
206
block_offset[1:] = np.cumsum(lead_block_sizes)
207
block_offsets.append(block_offset)
208
# Symmetry block offsets for all leads - or None if lead does not have
209
# blocks.
210
self.block_offsets = block_offsets
211
# Pick out symmetry block offsets for in and out leads
212
self.in_block_offsets = \
213
np.array(self.block_offsets)[list(self.in_leads)]
214
self.out_block_offsets = \
215
np.array(self.block_offsets)[list(self.out_leads)]
216
# Block j of in lead i starts at in_block_offsets[i][j]
217
218
def out_block_coords(self, lead_out):
219
"""Return a slice with the rows in the block corresponding to lead_out.
220
"""
221
if isinstance(lead_out, tuple):
222
lead_ind, block_ind = lead_out
223
lead_ind = self.out_leads.index(lead_ind)
224
return slice(self.out_offsets[lead_ind] +
225
self.out_block_offsets[lead_ind][block_ind],
226
self.out_offsets[lead_ind] +
227
self.out_block_offsets[lead_ind][block_ind + 1])
228
else:
229
lead_out = self.out_leads.index(lead_out)
230
return slice(self.out_offsets[lead_out],
231
self.out_offsets[lead_out + 1])
232
233
def in_block_coords(self, lead_in):
234
"""
235
Return a slice with the columns in the block corresponding to lead_in.
236
"""
237
if isinstance(lead_in, tuple):
238
lead_ind, block_ind = lead_in
239
lead_ind = self.in_leads.index(lead_ind)
240
return slice(self.in_offsets[lead_ind] +
241
self.in_block_offsets[lead_ind][block_ind],
242
self.in_offsets[lead_ind] +
243
self.in_block_offsets[lead_ind][block_ind + 1])
244
else:
245
lead_in = self.in_leads.index(lead_in)
246
return slice(self.in_offsets[lead_in],
247
self.in_offsets[lead_in + 1])
248
249
def transmission(self, lead_out, lead_in):
250
"""Return transmission from lead_in to lead_out.
251
"""
252
# If transmission is between leads and not subblocks, we can use
253
# unitarity (like SMatrix does).
254
if not (isinstance(lead_in, tuple) or isinstance(lead_out, tuple)):
255
return super().transmission(lead_out, lead_in)
256
else:
257
return np.linalg.norm(self.submatrix(lead_out, lead_in)) ** 2
258
259