forked from rubel75/BerryPI
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmmn2pathphase.py
336 lines (258 loc) · 11.8 KB
/
mmn2pathphase.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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/usr/bin/env python
from __future__ import print_function # Python 2 & 3 compatible print function
import sys
import numpy
import functools # needed for functools.reduce()
def parse_win_mp_grid(f):
parse_line_list = lambda line, delimiter, T : [T(y) for y in [x.strip() for x in line.strip().split(delimiter)] if y]
for line in f.readlines():
if 'mp_grid' in line:
# mp_grid : A B C
# Split in two by :, take second half
return parse_line_list(line.split(':')[1], ' ', int)
### Read file case.nnkp between lines "begin nnkpts" and "end nnkpts"
def parse_nnkp_nnkpts(f):
nnkpts = []
with f as input_data:
for line in input_data:
if line.strip() == 'begin nnkpts':
break
for line in input_data:
if line.strip() == 'end nnkpts':
break
line1 = line.strip()
line2 = line1.split()
line3 = map(int, line2)
line4 = tuple(line3) # change type for compatibility
if len(line2) == 5: # fits to format (kp1, kp2, G1, G2, G3)
nnkpts.append(line4)
return nnkpts
### case.mmn parsing
def parse_pair_info_line(line):
'''Converts a pair-info line into k1, k2, and a G vector
'''
# expected format(5i8)
k1 = float(line[0:8])
k2 = float(line[9:16])
G = (float(line[17:24]), float(line[25:32]), float(line[33:40]))
return k1, k2, G
def parse_matrix_element_line(line):
'''Converts a matrix element line into a value
'''
# expected format(32f18.12)
real_part = float(line[0:18])
imaginary_part = float(line[19:36])
return real_part + imaginary_part * 1j
def parse_mmn_info_line(line):
# expected format: write(unit_mmn,'(3I12)') Nb, Nk, Nntot
n_energy = int(line[0:12])
n_pairs = int(line[13:24])
n_neighbours = int(line[25:36])
return n_energy, n_pairs, n_neighbours
###
def determine_neighbours(D, d, P = [0,1,2]):
'''Computes a bidirectional graph of points who are adjacent in the
grid of dimensions `D', in the forward direction given by `d'.
The value at each node in the graph is a tuple containing the
linear index of the neighbour in the direction `d' and another
containing the linear index of the neighbour in the direction `-d'.
The resulting graph will be acyclic. The end-points of each path
are forward or backward values of None.
The order of the dimensions for computing the linear index are
given by P (C-style [0,1,2], FORTRAN-style [2,1,0])
Additionally, a list of all tuples of pairs of points is generated.
These tuples contain the linear index of the first point, the
linear index of the second point, and the G vector of the second
point.
Returns: pair tuple list, neighbour graph
'''
# Helper functions
product = lambda l : functools.reduce(lambda x,y : x*y, l, 1)
vector_add = lambda v1,v2 : [x + y for x, y in zip(v1,v2)]
permute = lambda v,P: [v[i] for i in P]
linear_index = lambda v,D: sum(c*i for i,c in zip(v,[product(D[:i]) for i in range(len(D))]))
def wrap_vector(v,d,D):
# Put v in bounds of D
# Return the new v and the G vector
G = [0,0,0]
for i, j in enumerate(d):
# Wrap i at boundaries
if j != 0:
if v[i] < 0 or v[i] >= D[i]:
v[i] = v[i] % D[i]
G = d
return v,G
# Determine the neighbours defining each path in provided direction
nnkpts = []
neighbour_graph = {}
# We iterate through each point in the mesh deriving the linear index
# for the point and its neighbour in the positive d direction.
# We will keep track of the fact that the neighbours are neighbours
# in two ways:
# 1) a bidirectional graph (A will point forward to B and B
# will point backward to A)
# 2) a list of pairs (A and B will become a tuple in this list)
for a in range(D[0]):
for b in range(D[1]):
for c in range(D[2]):
# Build k-point and neighbour vectors
v = [a, b, c]
v_neighbour, G = wrap_vector(vector_add(v, d), d, D)
# Get indices for vectors
i = linear_index(permute(v, P), permute(D, P)) + 1
i_neighbour = linear_index(permute(v_neighbour, P), permute(D, P)) + 1
# Add pair to graph of neighbours
if i not in neighbour_graph:
neighbour_graph[i] = [None, None] # Create the node
if i_neighbour not in neighbour_graph:
neighbour_graph[i_neighbour] = [None, None] # Create the neighbour node
# Save the pair in the graph (... if the pair doesn't extend
# the volume boundaries)
if G.count(0) == 3:
neighbour_graph[i][1] = i_neighbour # A --> B (forward)
neighbour_graph[i_neighbour][0] = i # A <-- B (backward)
# Remember neighbours as a tuple
nnkpts.append((i, i_neighbour, G[0], G[1], G[2]))
return nnkpts, neighbour_graph
def print_usage():
print("Usage: mmn2pathphase case [direction] [-w]")
print(" direction x, y, or z; for <x, y, z> (default x)")
print(" -w option is for Weyl k-path calculation")
###############################################################################
# Begin MAIN
###############################################################################
def main(args):
#VERBOSE = True
VERBOSE = False # Whether or not to print extra info
### case.win parsing
parse_line_list = lambda line, delimiter, T : [T(y) for y in [x.strip() for x in line.strip().split(delimiter)] if y]
if len(args) < 2:
print("Error: no case or direction provided")
exit(1)
spOption = '' # no spin polarization by default
wCalc = False # no Weyl point path by default
for arg in args: # check for spin polarization and Weyl path in arguments
if '-up' in arg:
spOption = 'up'
elif '-dn' in arg:
spOption = 'dn'
elif '-w' in arg:
wCalc = True
# Get case name from arguments
case_name = args[0]
# Get direction from arguments
direction_args = {'x': [1, 0, 0],
'y': [0, 1, 0],
'z': [0, 0, 1]}
if len(args) > 2:
if args[1] not in direction_args:
print("Error: unknown direction", args[1])
exit(1)
direction = direction_args[args[1]]
else:
direction = direction_args['x']
# Initialize some lists
phases = {} # for index k, the phase between k and its neighbour along the specified direction
phase_sums = [] # for index k, the accumulated phase along the path in the specified direction starting at k
# Get k-mesh information from case.win
f_win = open(case_name + '.win' + spOption, 'r')
kmesh = parse_win_mp_grid(f_win)
f_win.close()
# Determine the k(i)-k(i+1) neighbours of interest
if wCalc: # read from case.nnkp file
f_nnkp = open(case_name + '.nnkp' + spOption, 'r')
nnkpts = parse_nnkp_nnkpts(f_nnkp)
f_nnkp.close()
else: # Calculate the neighbours of interest
nnkpts, neighbour_graph = determine_neighbours(kmesh, direction, [2, 1, 0])
# > we need the pairs list (nnkpts) to discriminate values found in the
# case.mmn file
# > we need the neighbour graph (neighbour_graph) in order to find the sum
# of the phase differences along each path in the given direction
if VERBOSE:
print(nnkpts)
# Open file containing Mmn data (case.mmn[up/dn])
f_mmn = open(case_name + '.mmn' + spOption, 'r')
f_mmn.readline() # Ignore first line
n_energy, n_pairs, n_neighbours = parse_mmn_info_line(f_mmn.readline())
# Determine the phase from Mmn for each pair
# > the phase difference between points k1 and k2 in the direction provided
# will be stored in phases[k1]
for i in range(n_pairs * n_neighbours):
# Read pair info line
k1, k2, G = parse_pair_info_line(f_mmn.readline())
# Is this a pair in one of our paths?
if (k1, k2, G[0], G[1], G[2]) in nnkpts:
# Create empty square matrix for the pair
pair_matrix = numpy.zeros(shape=(n_energy, n_energy), dtype=complex)
# Read in the pair matrix
for a in range(n_energy):
for b in range(n_energy):
# Read matrix element line
element_value = parse_matrix_element_line(f_mmn.readline())
# Put value into matrix
pair_matrix[a, b] = element_value
# Determine the phase between the pair from the matrix determinant
det_Mmn = numpy.linalg.det(pair_matrix)
phases[k1] = numpy.angle(det_Mmn)
if VERBOSE:
print("(k1, k2, G)=", k1, k2, G)
print("pair_matrix Mmn=", pair_matrix)
print("eig(pair_matrix)=", numpy.linalg.eig(pair_matrix)[0])
print("det_Mmn=", det_Mmn)
print("numpy.angle(det_Mmn)=", numpy.angle(det_Mmn))
print('---')
else:
if VERBOSE:
print('Discarding','<',k1,',', k2,'> <', G[0], \
',', G[1], ',', G[2], '>')
print('---')
# Read over and discard the data if it's not a pair of interest
for a in range(n_energy):
for b in range(n_energy):
parse_matrix_element_line(f_mmn.readline())
f_mmn.close()
if wCalc:
#print("Berry phase along the path (rad) =",phases.values())
print("[ BerryPI ]", "Berry phase sum (rad) =", sum(phases.values()))
return
# Get the sum of phases for each path
for k, neighbours in neighbour_graph.items():
k_prev = neighbours[0]
k_next = neighbours[1]
# We'll see if this node is an endpoint. If it is, we'll traverse
# backwards summing the phase differences until we reach the start
if k_next is None: # Graph ends at k with no k_next
phase_sum = phases[k]
# Propagate backward through graph, accumulating phase difference
# at each pair (this _must_ be acyclic)
while k_prev:
# .. accumulate
phase_sum = phase_sum + phases[k_prev]
# .. move onto the next node
k = k_prev
neighbours = neighbour_graph[k_prev]
k_prev = neighbours[0]
# k should be the starting item in the graph.
# Record starting k and the accumulated phase
phase_sums.append((k, phase_sum))
# Sort accumulated phases by ascending k
phase_sums.sort(key=lambda x:x[0]) # TODO use sorted
# Write out path phases
f_pathphase = open(case_name + '.pathphase' + spOption, 'w')
# Write out number of paths
f_pathphase.write('%4d\n' % len(phase_sums))
# Write out path direction vector
f_pathphase.write(' %2d %2d %2d\n' % (direction[0], direction[1], direction[2]))
# Write out phases
for k, phase_sum in phase_sums:
f_pathphase.write(' %6d %.12f\n' % (k, phase_sum))
f_pathphase.close()
return phase_sums
###############################################################################
# end MAIN
###############################################################################
if __name__ == "__main__":
main(sys.argv[1:]) # path all arguments except for the first one
###############################################################################
###############################################################################