-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathwick.py
434 lines (368 loc) · 13.4 KB
/
wick.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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#!/bin/python3
import sys
import copy
import itertools
from IPython.display import display, Latex
def do_wick(string):
debug = False
if debug: print('Input string:',string)
idx_orb = 0
idx_op = 2
pos = [i for i in range(len(string))]
# Extraction of the operators
## By class of orb
### i
i_op, pos_i_op = extract_op(string,'i',idx_orb,pos)
if debug: print('i op:',i_op,'\nPos i op:',pos_i_op)
### a
a_op, pos_a_op = extract_op(string,'a',idx_orb,pos)
if debug: print('a op:',a_op,'\nPos a op:',pos_a_op)
## By class of op
### i+
i_crea, pos_i_crea = extract_op(i_op,'+',idx_op,pos_i_op)
if debug: print('i+:',i_crea,'\nPos i+:',pos_i_crea)
### i-
i_anni, pos_i_anni = extract_op(i_op,'-',idx_op,pos_i_op)
if debug: print('i-:',i_anni,'\nPos i-:',pos_i_anni)
### a+
a_crea, pos_a_crea = extract_op(a_op,'+',idx_op,pos_a_op)
if debug: print('a+:',a_crea,'\nPos a+:',pos_a_crea)
### a-
a_anni, pos_a_anni = extract_op(a_op,'-',idx_op,pos_a_op)
if debug: print('a-:',a_anni,'\nPos a-:',pos_a_anni)
# Build delta
## i+ i-
delta_i = build_delta(i_crea,i_anni,pos_i_crea,pos_i_anni)
if debug: print('Delta i:', delta_i)
## a- a+
delta_a = build_delta(a_anni,a_crea,pos_a_anni,pos_a_crea)
if debug: print('Delta a:', delta_a)
#sys.exit()
# Creation of all the products of hole deltas
res_i = combine_one_class_delta(delta_i,i_op,string)
res_i.insert(0,[[]])
if debug: print('res_i (possible products of i deltas):',res_i)
# Creation of all the products of particle deltas
res_a = combine_one_class_delta(delta_a,a_op,string)
res_a.insert(0,[[]])
if debug: print('res_a (possible products of a deltas):',res_a)
# Combination of the products of kronecker deltas coming from different classes of orbitals
res = []
# Iteration over the order (order <=> number of deltas) for i
for order_i in res_i:
# Iteration over the list of product of deltas i of a given order
for list_delta_i in order_i:
# As for i
for order_a in res_a:
# As for i
for list_delta_a in order_a:
# If there is at least one term for a
if len(list_delta_a) > 0:
# the different deltas a are append after the i ones
tmp = copy.deepcopy(list_delta_i)
for delta_a in list_delta_a:
tmp.append(delta_a)
res.append(tmp)
# If there is no a delta, we just keep the i ones
else:
tmp = copy.deepcopy(list_delta_i)
res.append(tmp)
# Ordering of the terms by increasing number of deltas
max_len = 0
for elem in res:
if len(elem) > max_len:
max_len = len(elem)
# Reordering of the list of deltas depending on the number of deltas
tmp_ordered = [[] for i in range(max_len+1)]
for elem in res:
tmp_ordered[len(elem)].append(elem)
# Finally the right order
list_deltas = []
for l_deltas in tmp_ordered:
for elem in l_deltas:
list_deltas.append(elem)
list_signs = []
for elem in list_deltas:
list_signs.append(extract_sign(elem,string))
# copy of the initial string
list_str = [string for i in range(len(list_signs))]
# Removing the op of the string implied in a contraction
acc = []
for s,d in zip(list_str,list_deltas):
tmp = []
for elem in s:
is_contracted = False
for delta in d:
for op in delta:
if op == elem:
is_contracted = True
break
if (not is_contracted):
tmp.append(elem)
acc.append(tmp)
list_str = acc
if debug:
for sign,d,s in zip(list_signs,list_deltas,list_str):
print(sign,d,s)
return list_signs, list_deltas, list_str
# The idea is to build the string by starting from the list of delta
# From this list we can build a list of pairs of deltas that do not share the same idx
# From this list of pairs of delta we can built a list of triplet of delta
# and so on ...
def combine_one_class_delta(delta_i,i_op,string):
order_delta_i = []
if len(delta_i) == 0:
return order_delta_i
acc = []
if len(delta_i) != 0:
for d in delta_i:
acc.append([d])
else:
acc.append([(0,0)])
order_delta_i.append(acc)
for i in range(1,len(i_op)//2+1):
# List of term
acc1 = []
# a term = 1 or many delta that are multiplied
list_term = order_delta_i[i-1]
# list of delta of each term, 1 or many delta that are multiplied
acc2 = []
for list_delta in list_term:
list_op = []
# a single delta
for delta in list_delta:
# the op that are contracted with the delta
for op in delta:
list_op.append(op)
# add delta, one on the existing delta and we look if we can do the contraction in
# addition do the contractions already done
for add_delta in delta_i:
acc3 = copy.deepcopy(list_delta)
idx_last = find_idx_elem(acc3[-1] ,delta_i)
idx_add = find_idx_elem(add_delta,delta_i)
if idx_add <= idx_last:
continue
is_in = False
# check if the operator in the delta we want to add is already in another contraction
for op1 in add_delta:
for op2 in list_op:
if op1 == op2:
is_in = True
if is_in: continue
if is_in: continue
acc3.append(add_delta)
acc2.append(acc3)
#if there is no i-multiple delta
if len(acc2) == 0:
break
order_delta_i.append(acc2)
# sign
#for list_term in order_delta_i:
# for list_delta in list_term:
# sign = extract_sign(list_delta,string)
return order_delta_i
# Product of two lists
# To compute the sign based on a list of delta and the original string of operators
def extract_sign(list_delta,string):
# position of the op in the different deltas
list_pos = []
for delta in list_delta:
tmp = []
for op in delta:
pos = find_idx_elem(op,string)
tmp.append(pos)
list_pos.append((tmp[0],tmp[1]))
# Number of crossing lines in the contractions
nb_cross = 0
for j in range(0,len(list_pos)-1):
for k in range(j+1,len(list_pos)):
pi_j = list_pos[j][0]
pf_j = list_pos[j][1]
pi_k = list_pos[k][0]
pf_k = list_pos[k][1]
# Crossing : ...pi_j ... pi_k ... pf_j ... pf_k...
if (pf_k > pf_j) and (pi_k < pf_j) and (pi_k > pi_j) :
nb_cross = nb_cross + 1
# Number of permutation required to do the contraction
nb_perm = 0
for pos in list_pos:
pi = pos[0]
pf = pos[1]
nb_perm = nb_perm + pf-pi-1
# Final sign
sign = (-1)**nb_cross * (-1)**nb_perm
return sign
# extract a type of operator based on a pattern at the idxth position in string[:]
def extract_op(string,pattern,idx,pos):
debug = False
# Check type
if type(string) != type(['a','b']):
print('Type mismatch function extract_op arg 1')
sys.exit()
if type(pattern) != type('a'):
print('Type mismatch function extract_op arg 2')
sys.exit()
if type(idx) != type(1):
print('Type mismatch function extract_op arg 3')
sys.exit()
if type(pos) != type([1,2]):
print('Type mismatch function extract_op arg 4')
sys.exit()
# Debug
if debug: print('string:',string)
if debug: print('pattern:',pattern)
if debug: print('idx:',idx)
res = []
new_pos = []
i = 0
for elem in string:
if elem[idx] == pattern:
res.append(elem)
new_pos.append(pos[i])
i = i + 1
return res, new_pos
# Build all the possible kronecker delta using 2 list of operators
# and their position in the original string of operator
def build_delta(list_op1,list_op2,list_pos1,list_pos2):
debug = False
idx_spin = 3
idx_act = 4
if debug: print('List op 1:',list_op1,'\List op 2:',list_op2)
if debug: print('List pos 1:',list_pos1,'\List pos 2:',list_pos2)
nb_idx = len(list_op1[0])
if nb_idx < 4 or nb_idx > 5:
print('The operators must have at least 4 indexes and maximum 5 indexes.')
sys.exit()
for elem in list_op1:
if len(elem) != nb_idx:
print('All the operators must share the same number of indexes.')
sys.exit()
a1 = ''
a2 = ' '
res = []
for op1, pos1 in zip(list_op1,list_pos1):
s1 = op1[idx_spin]
if nb_idx == 5:
a1 = op1[idx_act]
for op2, pos2 in zip(list_op2,list_pos2):
s2 = op2[idx_spin]
if nb_idx == 5:
a2 = op2[idx_act]
# if alpha-beta spin
if (s1 == 'a' and s2 == 'b') or (s1 == 'b' and s2 == 'a'):
continue
# if active-active contraction or not active-not active contraction
if a1 == a2 and nb_idx > idx_act:
continue
if pos2 > pos1:
res.append([op1,op2])
if debug: print('Res:',res)
return res
# To search the index of an element in a list
def find_idx_elem(elem,list_elem):
i = 0
for d in list_elem:
if d == elem:
break
else:
i = i + 1
# check
if i == len(list_elem):
print('elem not found in find_idx_elem')
sys.exit()
return i
# To put creation operator on the right
def put_crea_to_left(sign,string):
acc = []
acc_pos = []
tmp = []
idx = 2
string_pos = [i for i in range(len(string))]
for elem,pos in zip(string,string_pos):
if elem[idx] == '+':
acc.append(elem)
acc_pos.append(pos)
else:
tmp.append(elem)
order = [i for i in range(len(acc))]
d = 0
for pi,pf in zip(acc_pos,order):
d = d + abs(pi-pf)
sign = pow(-1,d) * sign
for elem in tmp:
acc.append(elem)
return sign, acc
class Wicked_str():
def __init__(self,sign,deltas,ops):
self.sign = sign
self.deltas = deltas
self.ops = ops
self.tex = self.to_latex()
def to_latex(self):
sign = self.sign
deltas = self.deltas
ops = self.ops
if sign > 0:
tex = '+ '
else:
tex = '- '
tx = deltas_to_tex(deltas)
tex = tex + tx
if len(ops) > 0:
tex = tex + '\\left\\{'
for op in ops:
#print(op)
o = latexify(op)
tex = tex + o
tex = tex + '\\right\\}_N'
return tex
def crea_to_left(self):
self.sign, self.ops = put_crea_to_left(self.sign,self.ops)
self.tex = self.to_latex()
def tex_show(self):
print(self.tex)
def eq_show(self):
display(Latex(f'${self.tex}$'))
def deltas_to_tex(deltas):
tex = ''
for delta in deltas:
d1 = str(delta[0][1])+ '_{$'+ delta[0][3] + '}'
d2 = str(delta[1][1])+ '_{$'+ delta[1][3] + '}'
tex = tex + '\delta('+d1+','+d2+') \ '
tex = tex.replace('$a','\\alpha')
tex = tex.replace('$b','\\beta')
tex = tex.replace('$g','')
return tex
def latexify(op):
tex = op[0]+'^{'+op[2]+'}'+'_{'+op[1]+'_{$'+op[3]+'}'+'}'
tex = tex.replace('+','\dagger')
tex = tex.replace('-','')
tex = tex.replace('$a','\\alpha')
tex = tex.replace('$b','\\beta')
tex = tex.replace('$g','g')
return tex
# 1: orbital class, i for occupied, a for unoccupied (for Fermi vacuum).
# For the true vacuum, use a.
# 2: orbital label
# 3: operator type, + for creation, - for annihilation
# 4: spin, a for $\alpha$, b for $\beta$, g for general (could be $\alpha$ or $\beta$)
# 5: optional, to avoid contraction between some operators
# (two operators with the same 5th index cannot be contracted together).
if __name__ == "__main__":
s = ['ip+g','aq-g','ir-g','is+g','at+g','iu-g']
#s = ['ix+g','iy+g','aw-g','av-g','iq+g','ip-g']
#From this, we can call the function "do_wick" on s to apply
# Wick's theorem and generate 3 lists, one for the signs, one
# for the kronecker delta and one for the normal ordered string
# containing the remaining uncontracted operators (WARNING:
# the operators are not put in normal order in these strings,
# but you can reorder them since it will only change the sign.
# That's why we write them as $\{...\}_N$).
list_sign, list_deltas, list_string = do_wick(s)
for sign,deltas,string in zip(list_sign, list_deltas, list_string):
# Creates an object Wicked_str to print or display latex code
obj = Wicked_str(sign,deltas,string)
# to print the each element of the result with latex format
obj.tex_show()
# to show the latex equation in a Jupyter Notebook
#obj.eq_show()