1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Definitions of all basic objects with extra features to treat loop
17 diagrams"""
18
19 from __future__ import absolute_import
20 import copy
21 import itertools
22 import logging
23 import numbers
24 import os
25 import re
26 import madgraph.core.color_algebra as color
27 import madgraph.core.diagram_generation as diagram_generation
28 import madgraph.core.base_objects as base_objects
29 import madgraph.various.misc as misc
30 from madgraph import MadGraph5Error, MG5DIR
31 from six.moves import range
32 from six.moves import zip
33
34 logger = logging.getLogger('madgraph.loop_base_objects')
40 """LoopDiagram: Contains an additional tag to uniquely identify the diagram
41 if it contains a loop. Also has many additional functions useful only
42 for loop computations.
43 """
44
45
46
47
48
49
50
51 cutting_method = 'optimal'
52
54 """Default values for all properties"""
55
56 super(LoopDiagram,self).default_setup()
57
58
59
60
61
62 self['tag'] = []
63
64
65
66
67
68
69 self['canonical_tag'] = []
70
71
72
73
74 self['type'] = 0
75
76
77
78
79 self['multiplier'] = 1
80
81
82 self['CT_vertices'] = base_objects.VertexList()
83
84
85
86
87 self['contracted_diagram'] = None
88
89 - def filter(self, name, value):
90 """Filter for valid diagram property values."""
91
92 if name == 'tag':
93 if not isinstance(value, list):
94 raise self.PhysicsObjectError("%s is not a valid tag" % str(value))
95 else:
96 for item in value:
97 if (len(item)!=3 or \
98 not isinstance(item[0],base_objects.Leg) or \
99 not isinstance(item[1],list)) or \
100 not isinstance(item[2],base_objects.Vertex):
101 raise self.PhysicsObjectError("%s is not a valid tag" % str(value))
102
103 if name == 'canonical_tag':
104 if not isinstance(value, list):
105 raise self.PhysicsObjectError("%s is not a valid tag" % str(value))
106 else:
107 for item in value:
108 if (len(item)!=3 or not isinstance(item[0],int) or \
109 not isinstance(item[1],list)) or \
110 not isinstance(item[2],int):
111 raise self.PhysicsObjectError("%s is not a valid canonical_tag" % str(value))
112
113 if name == 'CT_vertices':
114 if not isinstance(value, base_objects.VertexList):
115 raise self.PhysicsObjectError("%s is not a valid VertexList object" % str(value))
116
117 if name == 'type':
118 if not isinstance(value, int):
119 raise self.PhysicsObjectError("%s is not a valid integer" % str(value))
120
121 if name == 'multiplier':
122 if not isinstance(value, int):
123 raise self.PhysicsObjectError("%s is not a valid integer" % str(value))
124
125 if name == 'contracted_diagram':
126 if not isinstance(value, base_objects.Diagram):
127 raise self.PhysicsObjectError("%s is not a valid Diagram." % str(value))
128
129 else:
130 super(LoopDiagram, self).filter(name, value)
131
132 return True
133
135 """Return particle property names as a nicely sorted list."""
136
137 return ['vertices', 'CT_vertices', 'orders', 'type', 'tag']
138
140 """Returns a nicely formatted string of the diagram content."""
141
142
143 if self['type']==0:
144 return super(LoopDiagram,self).nice_string()
145
146 mystr=''
147 if not self['vertices']:
148 return '()'
149 if self['canonical_tag']:
150 mystr = mystr+'canonical tag: '+str(self['canonical_tag'])+'\n'
151 if self['CT_vertices']:
152 mystr = mystr+'CT vertex ids:'
153 for ctvx in self['CT_vertices']:
154 mystr = mystr +' '+str(ctvx.get('id'))
155 mystr = mystr+'\n'
156 if self['vertices']:
157 mystr = mystr+'Loop vertices: ('
158 for vert in self['vertices']:
159 mystr = mystr + '('
160 for leg in vert['legs'][:-1]:
161 if leg['loop_line']:
162 mystr = mystr + str(leg['number']) + \
163 '(%s*)' % str(leg['id']) + ','
164 else:
165 mystr = mystr + str(leg['number']) + \
166 '(%s)' % str(leg['id']) + ','
167
168 if self['vertices'].index(vert) < len(self['vertices']) - 1:
169
170 mystr = mystr[:-1] + '>'
171 if vert['legs'][-1]['loop_line']:
172 mystr = mystr + str(vert['legs'][-1]['number']) + \
173 '(%s*)' % str(vert['legs'][-1]['id']) + ','
174 else:
175 mystr = mystr + str(vert['legs'][-1]['number']) + \
176 '(%s)' % str(vert['legs'][-1]['id']) + ','
177 mystr = mystr + 'id:' + str(vert['id']) + '),'
178 mystr = mystr[:-1] + ')'
179 mystr += " (%s)" % ",".join(["%s=%d" % (key, self['orders'][key]) \
180 for key in self['orders'].keys()])+"\n"
181 if struct_list and self['tag']:
182 for i, tag_elem in enumerate(self['tag']):
183 for j, struct in enumerate(tag_elem[1]):
184 if len(tag_elem[1])>1:
185 mystr += 'Struct. #'+str(j+1)+\
186 ' on loop vx #'+str(i+1)+": "+\
187 struct_list[struct].nice_string_vertices()+"\n"
188 else:
189 mystr += 'Struct. on loop vx #'+str(i+1)+": "+\
190 struct_list[struct].nice_string_vertices()+"\n"
191
192 mystr=mystr[:-1]
193
194 return mystr
195
197 """This is the old function used without tag which means that no
198 canonical loop information can be produced. It will be used for
199 unit test only and moved there when I'll implement them."""
200
201
202
203 if len(self.get('vertices'))==0:
204 raise MadGraph5Error("Function get_contracted_loop_diagram()"+\
205 "called for the first time without specifying struct_rep "+\
206 "for a diagram already tagged.")
207
208
209 contracted_vertex_last_loop_leg = None
210
211
212 vertices_after_contracted_vertex = []
213 vertices_before_contracted_vertex = []
214
215
216
217
218 contracted_vertex_leg_daughters_nb = []
219
220
221
222 for vertex in self.get('vertices')[:-1]:
223
224 if not any(l['loop_line'] for l in vertex.get('legs')):
225
226
227 if any((l.get('number') in contracted_vertex_leg_daughters_nb) \
228 for l in vertex.get('legs')[:-1]):
229 vertices_after_contracted_vertex.append(vertex)
230 contracted_vertex_leg_daughters_nb.append(vertex.get('legs')[-1])
231 else:
232 vertices_before_contracted_vertex.append(vertex)
233 else:
234
235 contracted_vertex.get('legs').extend(
236 [l for l in vertex.get('legs')[:-1] if not l['loop_line']])
237
238
239 contracted_vertex.get('PDGs').extend([l.get('id') for l in
240 vertex.get('legs') if not l['loop_line']])
241
242
243
244 if not vertex.get('legs')[-1]['loop_line']:
245
246 contracted_vertex_last_loop_leg = vertex.get('legs')[-1]
247
248
249 if any(l['loop_line'] for l in self.get('vertices')[-1].get('legs')):
250
251 contracted_vertex.get('legs').extend([l for l in
252 self.get('vertices')[-1].get('legs') if not l['loop_line']])
253
254 else:
255 vertices_after_contracted_vertex.append(self.get('vertices')[-1])
256
257
258 contracted_diagram_vertices.extend(vertices_before_contracted_vertex)
259 if not contracted_vertex_last_loop_leg is None:
260 contracted_vertex.get('legs').append(contracted_vertex_last_loop_leg)
261
262 if len(contracted_vertex.get('legs'))==1:
263 stop
264 contracted_diagram_vertices.append(contracted_vertex)
265 contracted_diagram_vertices.extend(vertices_after_contracted_vertex)
266
267 contracted_diagram = base_objects.Diagram(
268 {'vertices':contracted_diagram_vertices,'orders':self.get('orders')})
269
270 return contracted_diagram
271
274 """ This function returns what will be used as the 'loop_tag' attribute
275 of the ContractedVertex instance in the function 'get_contracted_loop_diagram'.
276 It is important since it is what is used by MG5_aMC to decide
277 if two processes have *exactly* the same matrix element and can be
278 identified.
279 There is no need to characterize the details of the FDStructures attached
280 to the loops because these are already compared using the rest of the
281 DiagramTag structure. All we need is to identify a structure by its
282 external leg numbers."""
283
284 canonical_tag = self['canonical_tag']
285
286
287
288
289
290
291
292 loop_parts_tagging = [[]]*len(canonical_tag)
293 for i, tag_elem in enumerate(canonical_tag):
294 loop_part = model.get_particle(tag_elem[0])
295 loop_parts_tagging[i] = (loop_part.get('spin'),
296 loop_part.get('color'),
297 loop_part.get('self_antipart'),
298 loop_part.get('mass'),
299 loop_part.get('width'),
300 loop_part.get('is_part'))
301
302
303
304 FDStructs_tagging = [[]]*len(canonical_tag)
305 for i, tag_elem in enumerate(canonical_tag):
306 for struct_ID in tag_elem[1]:
307 if not use_FDStructure_ID_for_tag:
308
309
310
311
312
313
314
315
316
317
318
319 pass
320
321
322 else:
323
324
325
326
327
328
329
330 FDStructs_tagging[i].append(struct_ID)
331
332 FDStructs_tagging[i].sort()
333 FDStructs_tagging[i] = tuple(FDStructs_tagging[i])
334
335
336
337
338
339
340
341 interactions_tagging = [[]]*len(canonical_tag)
342 for i, tag_elem in enumerate(canonical_tag):
343 inter = model.get_interaction(tag_elem[2])
344 coup_keys = sorted(inter.get('couplings').keys())
345 interactions_tagging[i]=(
346 tuple((key, inter.get('couplings')[key]) for key in coup_keys),
347 tuple(str(c) for c in inter.get('color')),
348 tuple(inter.get('lorentz')))
349
350 return tuple(
351
352 list(zip(
353
354 loop_parts_tagging,
355
356 FDStructs_tagging,
357
358 interactions_tagging,
359 ))
360
361 + sorted(self.get_loop_orders(model).items())
362 )
363
365 """ Returns a base_objects.Diagram which correspond to this loop diagram
366 with the loop shrunk to a point. If struct_rep is no specified, then
367 the tagging will proceed assuming no FDStructure has been identified yet.
368 Otherwise, it will possible reuse them an update the repository."""
369
370 if self['type']<=0:
371 return copy.copy(self)
372
373 if self['contracted_diagram']:
374 return self['contracted_diagram']
375
376
377
378 if not self['canonical_tag'] or struct_rep is None:
379 n_external_legs = len(base_objects.LegList([l for l in
380 self.get_external_legs() if not l['loop_line']]))
381
382
383 start_in, end_in = n_external_legs +1, n_external_legs+2
384 for l in self['vertices'][0]['legs']:
385 if l.same(start_in):
386 break
387 elif l.same(end_in):
388 start_in, end_in = end_in, start_in
389 break
390
391 if struct_rep is None:
392 struct_rep = FDStructureList([])
393 self.tag(struct_rep, model, start_in=start_in, end_in=end_in,
394 synchronize=False)
395
396 contracted_diagram_vertices = base_objects.VertexList()
397
398
399
400
401 contracted_vertex = base_objects.ContractedVertex({
402 'id':-2,
403 'loop_orders':self.get_loop_orders(model),
404 'loop_tag': self.build_loop_tag_for_diagram_identification(model, struct_rep)
405 })
406
407
408
409
410
411 for tagelem in self['tag']:
412 contracted_vertex.get('legs').extend([struct_rep[
413 struct_ID].get('binding_leg') for struct_ID in tagelem[1]])
414
415
416 contracted_vertex.get('PDGs').extend([struct_rep[struct_ID].
417 get('binding_leg').get('id') for struct_ID in tagelem[1]])
418 contracted_diagram_vertices.extend(sum([struct_rep[
419 struct_ID].get('vertices') for struct_ID in tagelem[1]],[]))
420
421
422 contracted_diagram_vertices.append(contracted_vertex)
423
424 contracted_diagram = base_objects.Diagram(
425 {'vertices':contracted_diagram_vertices,'orders':self.get('orders')})
426
427 self['contracted_diagram'] = contracted_diagram
428
429 return contracted_diagram
430
431 - def get_CT(self,model,string=None):
432 """ Returns the CounterTerms of the type passed in argument. If None
433 it returns all of them. """
434 if string:
435 return base_objects.VertexList([vert for vert in \
436 self['CT_vertices'] if string in \
437 model['interaction_dict'][vert['id']]['type']])
438 else:
439 return self['CT_vertices']
440
442 """ Return none if there is no loop or if a tag has not yet been set and
443 returns True if this graph contains a purely fermionic loop and False if
444 not. """
445
446 if(self['tag']):
447 for part in self['tag']:
448 if not model.get('particle_dict')[part[0].get('id')].is_fermion():
449 return False
450 return True
451 else:
452 return False
453
455 """ Return None if there is no loop or if a tag has not yet been set and
456 returns True if this graph contains a tadpole loop and False if not. """
457
458 if(self['tag']):
459 if(len(self['tag'])==1):
460 return True
461 else:
462 return False
463 else:
464 return None
465
467 """Return None if there is no loop or if a tag has not yet been set and
468 returns True if this graph contains a vanishing tadpole loop and False
469 if not. """
470
471 if not self.is_tadpole():
472 return False
473
474
475 if(len(self['tag'][0][1])<=1):
476 return True
477
478 return any([part['mass'].lower()=='zero' for pdg,part in \
479 model.get('particle_dict').items() if \
480 pdg==abs(self['tag'][0][0]['id'])])
481
483 """ Return None if there is no loop or if a tag has not yet been set and
484 returns True if this graph contains a wave-function correction and False
485 if not. """
486
487 if self['tag'] :
488
489 if len(self['tag'])==2 and len(self['tag'][0][1])==1 \
490 and len(self['tag'][1][1])==1:
491
492 if struct_rep[self['tag'][0][1][0]].is_external() or \
493 struct_rep[self['tag'][1][1][0]].is_external():
494
495 inLegID=struct_rep[self['tag'][0][1][0]]['binding_leg']['id']
496 outLegID=struct_rep[self['tag'][1][1][0]]['binding_leg']['id']
497 return True
498
499
500 if len(self['tag'])==1 and len(self['tag'][0][1])==2 and \
501 (struct_rep[self['tag'][0][1][0]].is_external() or
502 struct_rep[self['tag'][0][1][1]].is_external()):
503 return True
504
505 return False
506 else:
507 return None
508
510 """Return the number of loop lines. """
511 if self['tag']:
512 return len(self['tag'])
513 else:
514 return None
515
516 @classmethod
518 """ Computes the weighting function S for this structure 'i' such that
519 S(i)>0 for each any i, S(i)!=S(j) if i['external_legs']!=j['external_legs']
520 and S(i+j)>max(S(i),S(j)). """
521
522 external_numbers=[leg['number'] for id in FD_ids_list for leg in \
523 struct_rep.get_struct(id).get('external_legs')]
524 external_numbers.sort()
525 weight=0
526 for i, number in enumerate(external_numbers):
527 weight=i*number_legs+number
528 return weight
529
530 @classmethod
532 """ This function chooses the place where to cut the loop in order to
533 maximize the loop wavefunction recycling in the open loops method.
534 This amounts to cut just before the combined structure with smallest
535 weight and then chose the direction to go towards the one with smallest
536 weight."""
537
538 tag=copy.deepcopy(intag)
539 number_legs=len(external_legs)
540
541
542 weights=[cls.compute_weight(t[1],struct_rep,number_legs) for t in tag]
543 imin = weights.index(min(weights))
544 tag=tag[imin:]+tag[:imin]
545 weights=weights[imin:]+weights[:imin]
546
547
548 rev_tag=cls.mirrored_tag(tag, model)
549
550 rev_tag=rev_tag[-1:]+rev_tag[:-1]
551 rev_weights=[cls.compute_weight(t[1],struct_rep,number_legs) for t in rev_tag]
552
553
554 if len(tag)==1:
555 return tag
556 elif len(tag)==2:
557 if abs(tag[0][0]['id'])>abs(tag[1][0]['id']):
558 return rev_tag
559 else:
560 return tag
561 else:
562 if rev_weights[1]<weights[1]:
563 return rev_tag
564 else:
565 return tag
566
567 @classmethod
569 """ This function chooses where to cut the loop. It returns the
570 canonical tag corresponding to this unambiguous choice."""
571
572
573
574
575
576
577
578 canonical_tag=copy.deepcopy(tag)
579 canonical_tag=cls.make_canonical_cyclic(canonical_tag)
580 canonical_mirrored_tag=copy.deepcopy(canonical_tag)
581 canonical_mirrored_tag=cls.mirrored_tag(canonical_mirrored_tag,model)
582
583 canonical_mirrored_tag=canonical_mirrored_tag[-1:]+\
584 canonical_mirrored_tag[:-1]
585
586
587
588
589
590
591
592
593
594
595
596
597 if (len(tag)==2 and abs(canonical_mirrored_tag[0][0]['id'])>\
598 abs(canonical_tag[0][0]['id'])) or (len(tag)>2 and \
599 canonical_mirrored_tag[1][1]<canonical_tag[1][1]):
600 canonical_tag=canonical_mirrored_tag
601
602 return canonical_tag
603
604 - def tag(self, struct_rep, model, start_in=None, end_in=None, synchronize=True):
605 """ Construct the tag of the diagram providing the loop structure
606 of it. """
607
608
609
610 loopVertexList=base_objects.VertexList()
611
612
613
614
615 external_legs = base_objects.LegList([l for l in
616 self.get_external_legs() if not l['loop_line']])
617 n_initial = len([1 for leg in external_legs if not leg['state']])
618
619 if start_in is None or end_in is None:
620 start_in = len(external_legs)+1
621 end_in = len(external_legs)+2
622
623
624
625 if isinstance(start_in,int) and isinstance(end_in,int):
626 start=start_in
627 end=end_in
628 elif isinstance(start_in,base_objects.Leg) and \
629 isinstance(end_in,base_objects.Leg):
630 start=start_in.get('number')
631 end=end_in.get('number')
632 else:
633 raise MadGraph5Error("In the diagram tag function, 'start' and "+\
634 " 'end' must be either integers or Leg objects.")
635
636 if self.process_next_loop_leg(struct_rep,-1,-1,start,end,\
637 loopVertexList, model, external_legs):
638
639
640
641
642
643
644
645
646 if self.cutting_method=='default':
647
648 canonical_tag=self.choose_default_lcut(self['tag'],model)
649 elif self.cutting_method=='optimal':
650
651
652 canonical_tag=self.choose_optimal_lcut(self['tag'],struct_rep,
653 model, external_legs)
654 else:
655 raise MadGraph5Error('The cutting method %s is not implemented.'\
656 %self.cutting_method)
657
658 self['tag']=canonical_tag
659
660
661
662 if synchronize:
663 self.synchronize_loop_vertices_with_tag(model,n_initial,
664 struct_rep,start,end)
665
666
667
668
669 self['canonical_tag']=[[t[0]['id'],t[1],t[2]] for t in canonical_tag]
670 return True
671 else:
672 raise self.PhysicsObjectError("Loop diagram tagging failed.")
673 return False
674
675
676 @classmethod
678 """ Generate a loop vertex from incoming legs myleglist and the
679 interaction with id vertID of the model given in argument """
680
681 ref_dict_to1 = model.get('ref_dict_to1')
682
683
684 key=tuple(sorted([leg.get('id') for leg in myleglist]))
685 if key in ref_dict_to1:
686 for interaction in ref_dict_to1[key]:
687
688 if interaction[1]==vertID:
689
690
691
692 legid = interaction[0]
693
694
695 number = min([leg.get('number') for leg in\
696 myleglist])
697
698
699
700
701 if n_initial>1 and len(myleglist)>1 and len([leg for leg in myleglist if leg.get('state') == False]) == 1:
702 state = False
703 else:
704 state = True
705 myleglist.append(base_objects.Leg(\
706 {'number': number,\
707 'id': legid,\
708 'state': state,
709 'loop_line': True}))
710
711 return base_objects.Vertex({'legs':myleglist,'id':vertID})
712 else:
713 raise cls.PhysicsObjectError("An interaction from the original L-cut diagram could"+\
714 " not be found when reconstructing the loop vertices.")
715
716 - def process_next_loop_leg(self, structRep, fromVert, fromPos, currLeg, \
717 endLeg, loopVertexList, model, external_legs):
718 """ Finds a loop leg and what is the next one. Also identify and tag the
719 FD structure attached in between these two loop legs. It adds the
720 corresponding tuple to the diagram tag and calls iself again to treat
721 the next loop leg. Return True when tag successfully computed."""
722
723 nextLoopLeg=None
724 legPos=-2
725 vertPos=-2
726 FDStructureIDList=[]
727 vertFoundID=-1
728 n_initial = len([1 for leg in external_legs if not leg['state']])
729
730
731 def process_loop_interaction(i,j,k,pos):
732 """For vertex position 'i' and loop leg position 'j'. Find the
733 structure attached to leg k of this loop interaction, tag it and
734 update the loop tag."""
735 FDStruct=FDStructure()
736
737
738
739 canonical = self.construct_FDStructure(i,pos,\
740 self['vertices'][i].get('legs')[k],FDStruct)
741
742 if not canonical:
743 raise self.PhysicsObjectError("Failed to reconstruct a FDStructure.")
744
745
746
747 if isinstance(canonical,int):
748 FDStruct.set('canonical',(((canonical,),0),))
749 elif isinstance(canonical,tuple):
750 FDStruct.set('canonical',canonical)
751 else:
752 raise self.PhysicsObjectError("Non-proper behavior of the construct_FDStructure function")
753
754
755
756 myStructID=-1
757 myFDStruct=structRep.get_struct(FDStruct.get('canonical'))
758 if not myFDStruct:
759
760
761 myStructID=len(structRep)
762
763
764 FDStruct.set('id',myStructID)
765
766
767
768 FDStruct.generate_vertices(model, external_legs)
769 structRep.append(FDStruct)
770 else:
771
772
773
774
775
776
777
778 myStructID=myFDStruct.get('id')
779
780 FDStructureIDList.append(myStructID)
781
782
783
784
785 vertRange=list(range(len(self['vertices'])))
786
787
788 if not fromVert == -1:
789 if fromPos == -1:
790
791
792
793 vertRange=vertRange[fromVert+1:]
794 else:
795
796
797
798
799 vertRange=vertRange[:fromVert]
800 vertRange.reverse()
801
802
803 for i in vertRange:
804
805
806
807
808 legRange=list(range(len(self['vertices'][i].get('legs'))))
809 if fromPos == -1:
810
811 if not i==len(self['vertices'])-1:
812 legRange=legRange[:-1]
813 else:
814
815
816 if i==len(self['vertices'])-1:
817 continue
818 else:
819 legRange=legRange[-1:]
820 for j in legRange:
821 if self['vertices'][i].get('legs')[j].same(currLeg):
822 vertPos=i
823 vertFoundID=self['vertices'][i]['id']
824
825
826
827 if isinstance(currLeg,int):
828 currLeg=base_objects.Leg(self['vertices'][i].get('legs')[j])
829
830
831 for k in [ind for ind in range(len(self['vertices'][i].get('legs'))) if not ind==j]:
832
833
834
835
836
837
838
839 if not i==len(self['vertices'])-1 \
840 and k==len(self['vertices'][i].get('legs'))-1:
841 pos=-1
842 else:
843 pos=k
844
845 if self['vertices'][i].get('legs')[k].get('loop_line'):
846 if not nextLoopLeg:
847 nextLoopLeg=self['vertices'][i].get('legs')[k]
848 legPos=pos
849 else:
850 raise self.PhysicsObjectError(" An interaction has more than two loop legs.")
851 else:
852 process_loop_interaction(i,j,k,pos)
853
854
855 break
856 if nextLoopLeg:
857 break
858
859
860 if not nextLoopLeg:
861
862
863 return False
864
865
866
867
868 if FDStructureIDList and vertFoundID not in [0,-1]:
869
870
871
872
873
874 myleglist=base_objects.LegList([copy.copy(\
875 structRep[FDindex]['binding_leg']) for FDindex in \
876 FDStructureIDList])
877
878
879
880
881
882
883
884 if loopVertexList:
885 self['tag'].append([copy.copy(\
886 loopVertexList[-1]['legs'][-1]),\
887 sorted(FDStructureIDList),vertFoundID])
888 myleglist.append(loopVertexList[-1]['legs'][-1])
889 else:
890 self['tag'].append([copy.copy(currLeg),\
891 sorted(FDStructureIDList),vertFoundID])
892 new_input_leg = copy.copy(currLeg)
893 if fromPos!=-1:
894
895
896
897
898
899
900 new_input_leg.set('id',model.get_particle(
901 new_input_leg.get('id')).get_anti_pdg_code())
902 myleglist.append(new_input_leg)
903
904
905
906
907
908
909
910
911
912
913
914
915 loopVertexList.append(\
916 self.generate_loop_vertex(myleglist,model,n_initial,vertFoundID))
917
918
919 if nextLoopLeg.same(endLeg):
920
921
922
923 if vertFoundID not in [0,-1]:
924 starting_Leg=copy.copy(myleglist[-1])
925 legid=model.get_particle(myleglist[-1]['id']).get_anti_pdg_code()
926 state=myleglist[-1].get('state')
927 else:
928 starting_Leg=copy.copy(currLeg)
929 legid=model.get_particle(currLeg['id']).get_anti_pdg_code()
930 state=currLeg.get('state')
931
932 loopVertexList.append(base_objects.Vertex(\
933 {'legs':base_objects.LegList([starting_Leg,\
934 base_objects.Leg({'number': endLeg,
935 'id': legid,
936 'state': state,
937 'loop_line': True})]),
938 'id':-1}))
939
940
941
942 return True
943 else:
944
945
946 return self.process_next_loop_leg(structRep, vertPos, legPos, \
947 nextLoopLeg, endLeg, loopVertexList, model, external_legs)
948
951 """ Construct the loop vertices from the tag of the loop diagram."""
952
953 if not self['tag']:
954 return
955
956 ref_dict_to1 = model.get('ref_dict_to1')
957
958
959 loopVertexList=base_objects.VertexList()
960 for i, t in enumerate(self['tag']):
961
962
963 myleglist=base_objects.LegList([copy.copy(\
964 struct_rep[FDindex]['binding_leg']) for FDindex in t[1]])
965 if i==0:
966 starting_leg=copy.copy(t[0])
967
968
969
970
971
972
973
974
975
976
977
978
979
980 if model.get_particle(starting_leg['id']).get('is_part'):
981 starting_leg['number']=lcut_part_number
982 end_number=lcut_antipart_number
983 else:
984 starting_leg['number']=lcut_antipart_number
985 end_number=lcut_part_number
986 starting_leg['state']=True
987 else:
988 starting_leg=loopVertexList[-1].get('legs')[-1]
989 self['tag'][i][0]=starting_leg
990 myleglist.append(starting_leg)
991 loopVertexList.append(self.generate_loop_vertex(myleglist,
992 model,n_initial,t[2]))
993
994
995 first_leg=copy.copy(loopVertexList[-1].get('legs')[-1])
996 sec_leg_id=model.get_particle(first_leg['id']).get_anti_pdg_code()
997 second_leg=base_objects.Leg({'number': end_number,
998 'id': sec_leg_id,
999 'state': first_leg.get('state'),
1000 'loop_line': True})
1001 loopVertexList.append(base_objects.Vertex(\
1002 {'legs':base_objects.LegList([first_leg,second_leg]),
1003 'id':-1}))
1004
1005 self['type'] = abs(first_leg['id'])
1006 self['vertices'] = loopVertexList
1007
1009 """ Construct iteratively a Feynman Diagram structure attached to a Loop,
1010 given at each step a vertex and the position of the leg this function is
1011 called from. At the same time, it constructs a canonical representation
1012 of the structure which is a tuple with each element corresponding to
1013 a 2-tuple ((external_parent_legs),vertex_ID). The external parent legs
1014 tuple is ordered as growing and the construction of the canonical
1015 representation is such that the 2-tuples appear in a fixed order.
1016 This functions returns a tuple of 2-tuple like above for the vertex
1017 where currLeg was found or false if fails.
1018
1019 To illustrate this algorithm, we take a concrete example,
1020 the following structure:
1021
1022 4 5 6 7
1023 1 3 \/2 \/ <- Vertex ID, left=73 and right=99
1024 \ / | \ / <- Vertex ID, left=34 and right=42
1025 | |4 |
1026 1\ | /2
1027 \|/ <- Vertex ID=72
1028 |
1029 |1
1030
1031 For this structure with external legs (1,2,3,5,6,7) and current created
1032 1, the canonical tag will be
1033
1034 (((1,2,3,4,5,6,7),72),((1,3),34),((2,6,7),42),((6,7),99),((4,5),73))
1035 """
1036 nextLeg = None
1037 legPos=-2
1038 vertPos=-2
1039
1040 vertRange=list(range(len(self['vertices'])))
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061 vertBuffer=[]
1062
1063
1064
1065
1066 parentBuffer=[[],0]
1067
1068
1069
1070
1071
1072 if fromPos == -1:
1073
1074
1075
1076 vertRange=vertRange[fromVert+1:]
1077 else:
1078
1079
1080
1081
1082
1083 vertRange=vertRange[:fromVert]
1084 vertRange.reverse()
1085
1086
1087
1088
1089
1090 pos=-2
1091
1092
1093 def process_leg(vertID, legID):
1094 """ Treats the leg equal to currLeg found in the place located by
1095 self['vertices'][vertID].get('legs')[legID]"""
1096
1097
1098
1099 parentBuffer[1]=self['vertices'][vertID].get('id')
1100
1101
1102
1103
1104
1105
1106
1107 legPos=-2
1108 for k in [ind for ind in \
1109 range(len(self['vertices'][vertID].get('legs'))) if ind!=legID]:
1110
1111
1112
1113 if not self['vertices'][vertID].get('id'):
1114 return self.construct_FDStructure(vertID, k,\
1115 self['vertices'][vertID].get('legs')[k], FDStruct)
1116
1117 if k==len(self['vertices'][vertID].get('legs'))-1 \
1118 and not vertID==len(self['vertices'])-1:
1119 legPos=-1
1120 else:
1121 legPos=k
1122
1123 branch=self.construct_FDStructure(i, legPos, \
1124 self['vertices'][vertID].get('legs')[k], FDStruct)
1125 if not branch:
1126 raise self.PhysicsObjectError("Failed to reconstruct a FDStructure.")
1127
1128 if isinstance(branch,int):
1129 parentBuffer[0].append(branch)
1130
1131
1132 elif isinstance(branch,tuple):
1133 parentBuffer[0]+=list(branch[0][0])
1134 vertBuffer.append(branch)
1135 else:
1136 raise self.PhysicsObjectError("Non-proper behavior of the construct_FDStructure function")
1137 return legPos
1138
1139
1140
1141
1142 for i in vertRange:
1143
1144
1145
1146
1147
1148 legRange=list(range(len(self['vertices'][i].get('legs'))))
1149 if fromPos == -1:
1150
1151 if not i==len(self['vertices'])-1:
1152 legRange=legRange[:-1]
1153 else:
1154
1155
1156 if i==len(self['vertices'])-1:
1157 continue
1158 else:
1159 legRange=legRange[-1:]
1160
1161
1162
1163 findVert=False
1164
1165 for j in legRange:
1166 if self['vertices'][i].get('legs')[j].same(currLeg):
1167
1168 pos=process_leg(i,j)
1169
1170
1171 findVert=True
1172 break;
1173 if findVert:
1174 break;
1175
1176 if(pos == -2):
1177 if(not fromPos == -1):
1178
1179 FDStruct.get('external_legs').append(copy.copy(currLeg))
1180 return currLeg.get('number')
1181 else:
1182 raise self.PhysicsObjectError(" A structure is malformed.")
1183 else:
1184
1185
1186
1187
1188
1189
1190
1191 vertBuffer.sort()
1192
1193
1194
1195
1196
1197
1198 vertBufferFlat=[]
1199 for t in vertBuffer:
1200 for u in t:
1201 vertBufferFlat.append(u)
1202
1203
1204 parentBuffer[0].sort()
1205
1206 vertBufferFlat.insert(0,(tuple(parentBuffer[0]),parentBuffer[1]))
1207 return tuple(vertBufferFlat)
1208
1209
1210
1212 """ Return the starting loop line of this diagram, i.e. lcut leg one."""
1213 for v in self['vertices']:
1214 for l in v['legs']:
1215 if l['loop_line']:
1216 return l
1217
1219 """ Return the finishing line of this diagram, i.e. lcut leg two.
1220 Notice that this function is only available when the loop diagram is
1221 constructed with the special two-point vertex with id -1. """
1222
1223 assert self['vertices'][-1].get('id')==-1, "Loop diagrams must finish "+\
1224 " with vertex with id '-1' for get_finishing_loop_line to be called"
1225
1226 return max(self['vertices'][-1].get('legs'), key=lambda l: l['number'])
1227
1229 """ Return a set with one occurence of each different PDG code of the
1230 particles running in the loop. By convention, the PDF of the particle,
1231 not the antiparticle, is stored in this list. Using the tag would be
1232 quicker, but we want this function to be available before tagging as
1233 well"""
1234 return set([abs(l['id']) for v in self['vertices'] for l in v['legs'] \
1235 if l['loop_line']])
1236
1238 """ Return a dictionary with one entry per type of order appearing in
1239 the interactions building the loop flow. The corresponding keys are the
1240 number of type this order appear in the diagram. """
1241
1242 loop_orders = {}
1243 for vertex in self['vertices']:
1244
1245
1246
1247 if vertex['id'] not in [0,-1] and len([1 for leg \
1248 in vertex['legs'] if leg['loop_line']])==2:
1249 vertex_orders = model.get_interaction(vertex['id'])['orders']
1250 for order in vertex_orders.keys():
1251 if order in list(loop_orders.keys()):
1252 loop_orders[order]+=vertex_orders[order]
1253 else:
1254 loop_orders[order]=vertex_orders[order]
1255 return loop_orders
1256
1257
1258 @classmethod
1260 """ Perform cyclic permutations on the tag given in parameter such that
1261 the structure with the lowest ID appears first."""
1262
1263 if not atag:
1264 return []
1265
1266 imin=-2
1267 minStructID=-2
1268 for i, part in enumerate(atag):
1269 if minStructID==-2 or min(part[1])<minStructID:
1270 minStructID=min(part[1])
1271 imin=i
1272
1273 atag=atag[imin:]+atag[:imin]
1274
1275 return atag
1276
1277 @classmethod
1279 """ Performs a mirror operation on A COPY of the tag and returns it. """
1280
1281 if not atag:
1282 return []
1283
1284
1285 revTag=[(copy.deepcopy(elem[0]), copy.copy(elem[1]), \
1286 copy.copy(elem[2])) for elem in atag]
1287
1288
1289 revTag.reverse()
1290
1291 shiftBuff=revTag[-1]
1292 for i in range(len(revTag)-1):
1293 revTag[-(i+1)]=[revTag[-(i+2)][0],revTag[-(i+1)][1],revTag[-(i+1)][2]]
1294 revTag[0]=[shiftBuff[0],revTag[0][1],revTag[0][2]]
1295
1296
1297
1298 nonselfantipartlegs = [ elem[0] for elem in revTag if not \
1299 model.get('particle_dict')[elem[0].get('id')]['self_antipart'] ]
1300 for leg in nonselfantipartlegs:
1301 leg.set('id',\
1302 model.get('particle_dict')[leg.get('id')].get_anti_pdg_code())
1303
1304 return revTag
1305
1306
1307
1308
1309
1311 """ Returns the pdgs of the lines running in the loop while not
1312 differentiating the particles from the anti-particles """
1313
1314 return [abs(tag_elem[0].get('id')) for tag_elem in self['tag']]
1315
1317 """ Returns the pdgs of the lines directly branching off the loop."""
1318
1319 return [structs.get_struct(struct_ID).get('binding_leg').get('id') \
1320 for tag_elem in self['tag'] for struct_ID in tag_elem[1]]
1321
1327 """ A special kind of LoopDiagram which does not contain a loop but only
1328 specifies all UV counter-term which factorize the the same given born
1329 and bringing in the same orders. UV mass renormalization does not belong to
1330 this class of counter-term for example, and it is added along with the R2
1331 interactions."""
1332
1334 """Default values for all properties"""
1335
1336 super(LoopUVCTDiagram,self).default_setup()
1337
1338
1339 self['type']='UV'
1340 self['UVCT_orders']={}
1341 self['UVCT_couplings']=[]
1342
1343 - def filter(self, name, value):
1344 """Filter for valid diagram property values."""
1345
1346 if name == 'UVCT_couplings':
1347 if not isinstance(value, list):
1348 raise self.PhysicsObjectError("%s is not a valid list" % str(value))
1349 else:
1350 for elem in value:
1351 if not isinstance(elem, str) and not isinstance(elem, int):
1352 raise self.PhysicsObjectError("%s is not a valid string" % str(value))
1353
1354 if name == 'UVCT_orders':
1355 if not isinstance(value, dict):
1356 raise self.PhysicsObjectError("%s is not a valid dictionary" % str(value))
1357
1358 if name == 'type':
1359 if not isinstance(value, str):
1360 raise self.PhysicsObjectError("%s is not a valid string" % str(value))
1361
1362 else:
1363 super(LoopUVCTDiagram, self).filter(name, value)
1364
1365 return True
1366
1368 """Return particle property names as a nicely sorted list."""
1369
1370 return ['vertices', 'UVCT_couplings', 'UVCT_orders', 'type', 'orders']
1371
1373 """ Finds the UV counter-term interaction present in this UVCTDiagram """
1374
1375 for vert in self['vertices']:
1376 if vert.get('id') != 0:
1377 if model.get_interaction(vert.get('id')).is_UV():
1378 return model.get_interaction(vert.get('id'))
1379
1380 return None
1381
1383 """Calculate the actual coupling orders of this diagram. Note
1384 that the special order WEIGTHED corresponds to the sum of
1385 hierarchies for the couplings."""
1386
1387 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')])
1388 weight = 0
1389 for couplings in [model.get('interaction_dict')[vertex.get('id')].\
1390 get('orders') for vertex in self['vertices'] if \
1391 vertex.get('id') != 0]+[self['UVCT_orders']]:
1392 for coupling in couplings:
1393 coupling_orders[coupling] += couplings[coupling]
1394 weight += sum([model.get('order_hierarchy')[c]*n for \
1395 (c,n) in couplings.items()])
1396 coupling_orders['WEIGHTED'] = weight
1397 self.set('orders', coupling_orders)
1398
1400 """Returns a nicely formatted string of the diagram content."""
1401 res=''
1402 if self['vertices']:
1403 res=res+super(LoopUVCTDiagram,self).nice_string()
1404 if self['UVCT_couplings']:
1405 res=res+'UV renorm. vertices: '
1406 res=res+','.join(str(vert) for vert in self['UVCT_couplings'])+'\n'
1407 if self['UVCT_orders']:
1408 res=res+'UVCT orders: '
1409 res=res+','.join(order for order in self['UVCT_orders'].keys())+'\n'
1410 if self['type']:
1411 res=res+'UVCT type: '+self['type']
1412
1413 return res
1414
1415
1416
1417
1418 -class LoopModel(base_objects.Model):
1419 """A class to store all the model information with advanced feature
1420 to compute loop process."""
1421
1423 """Make sure to copy over the attribute map_CTcoup_CTparam if the
1424 first instance used is a LoopModel"""
1425
1426 if len(args)>0 and isinstance(args[0],LoopModel):
1427 if hasattr(args[0],'map_CTcoup_CTparam'):
1428 self.map_CTcoup_CTparam = copy.copy(args[0].map_CTcoup_CTparam)
1429 if hasattr(args[0],'notused_ct_params'):
1430 self.notused_ct_params = list(args[0].notused_ct_params)
1431
1432 super(LoopModel,self).__init__(*args,**opts)
1433
1435 super(LoopModel,self).default_setup()
1436 self['perturbation_couplings'] = []
1437
1438
1439
1440
1441
1442
1443
1444
1445 self['coupling_orders_counterterms']={}
1446
1447
1448
1449
1450
1451 if not hasattr(self,'map_CTcoup_CTparam'):
1452 self.map_CTcoup_CTparam = {}
1453
1454
1455 - def filter(self, name, value):
1456 """Filter for model property values"""
1457
1458 if name == 'perturbation_couplings':
1459 if not isinstance(value, list):
1460 raise self.PhysicsObjectError("Object of type %s is not a list" % \
1461 type(value))
1462 for order in value:
1463 if not isinstance(order, str):
1464 raise self.PhysicsObjectError("Object of type %s is not a string" % \
1465 type(order))
1466 else:
1467 super(LoopModel,self).filter(name,value)
1468
1469 return True
1470
1472 """This function actualizes the dictionaries"""
1473
1474 if useUVCT:
1475 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1476 self['interactions'].generate_ref_dict(useR2UV=False,useUVCT=True)
1477 else:
1478 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1479 self['interactions'].generate_ref_dict()
1480 self['ref_dict_to0'].update(
1481 self['particles'].generate_ref_dict())
1482
1484 """Return process property names as a nicely sorted list."""
1485
1486 return ['name', 'particles', 'parameters', 'interactions', 'couplings',
1487 'lorentz','perturbation_couplings','conserved_charge']
1488
1489
1490
1491
1492 -class DGLoopLeg(base_objects.Leg):
1493 """A class only used during the loop diagram generation. Exactly like leg
1494 except for a few other parameters only useful during the loop diagram
1495 generation."""
1496
1508
1512
1513 - def filter(self, name, value):
1514 """Filter for model property values"""
1515
1516 if name == 'depth':
1517 if not isinstance(value, int):
1518 raise self.PhysicsObjectError("Object of type %s is not a int" % \
1519 type(value))
1520 else:
1521 super(DGLoopLeg,self).filter(name,value)
1522
1523 return True
1524
1526 """Return process property names as a nicely sorted list."""
1527
1528 return ['id', 'number', 'state', 'from_group','loop_line','depth',
1529 'polarization']
1530
1532 """ Converts a DGLoopLeg back to a Leg. Basically removes the extra
1533 attributes """
1534
1535 aleg=base_objects.Leg()
1536 for key in aleg.get_sorted_keys():
1537 aleg.set(key,self[key])
1538
1539
1540 return aleg
1541
1542
1543
1544
1545 -class FDStructure(base_objects.PhysicsObject):
1546 """FDStructure:
1547 list of vertices (ordered). This is part of a diagram.
1548 """
1549
1558
1560 """Returns wether the structure is simply made of an external particle
1561 only"""
1562 if (len(self['canonical'])==1 and self['canonical'][0][1]==0):
1563 return True
1564 else:
1565 return False
1566
1567 - def filter(self, name, value):
1568 """Filter for valid FDStructure property values."""
1569
1570 if name == 'vertices':
1571 if not isinstance(value, base_objects.VertexList):
1572 raise self.PhysicsObjectError("%s is not a valid VertexList object" % str(value))
1573
1574 if name == 'id':
1575 if not isinstance(value, int):
1576 raise self.PhysicsObjectError("id %s is not an integer" % repr(value))
1577
1578 if name == 'weight':
1579 if not isinstance(value, int):
1580 raise self.PhysicsObjectError("weight %s is not an integer" % repr(value))
1581
1582 if name == 'external_legs':
1583 if not isinstance(value, base_objects.LegList):
1584 raise self.PhysicsObjectError("external_legs %s is not a valid Leg List" % str(value))
1585
1586 if name == 'binding_leg':
1587 if not isinstance(value, base_objects.Leg):
1588 raise self.PhysicsObjectError("binding_leg %s is not a valid Leg" % str(value))
1589
1590 if name == 'canonical':
1591 if not isinstance(value, tuple):
1592 raise self.PhysicsObjectError("canonical %s is not a valid tuple" % str(value))
1593
1594 return True
1595
1597 """Return particle property names as a nicely sorted list."""
1598
1599 return ['id','external_legs','binding_leg','canonical','vertices']
1600
1602 """Returns a nicely formatted string of the structure content."""
1603
1604 mystr=''
1605
1606 if not self['id']==-1:
1607 mystr=mystr+'id: '+str(self['id'])+',\n'
1608 else:
1609 return '()'
1610
1611 if self['canonical']:
1612 mystr=mystr+'canonical_repr.: '+str(self['canonical'])+',\n'
1613
1614 if self['external_legs']:
1615 mystr=mystr+'external_legs: { '
1616 for leg in self['external_legs'][:-1]:
1617 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) \
1618 + ', '
1619 mystr = mystr + str(self['external_legs'][-1]['number']) + \
1620 '(%s)' % str(self['external_legs'][-1]['id']) + ' },\n'
1621 mystr = mystr+'binding_leg: '+str(self['binding_leg']['number']) +\
1622 '(%s)' % str(self['binding_leg']['id'])
1623 return mystr
1624
1626 """Returns a nicely formatted string of the structure vertices."""
1627 mystr=''
1628 if self['vertices']:
1629 mystr = mystr+'('
1630 for vert in self['vertices']:
1631 mystr = mystr + '('
1632 for leg in vert['legs'][:-1]:
1633 mystr = mystr + str(leg['number']) + \
1634 '(%s)' % str(leg['id']) + ','
1635 mystr = mystr[:-1] + '>'
1636 mystr = mystr + str(vert['legs'][-1]['number']) +\
1637 '(%s)' % str(vert['legs'][-1]['id']) + ','
1638 mystr = mystr + 'id:' + str(vert['id']) + '),'
1639 mystr = mystr[:-1] + ')'
1640 return mystr
1641 elif len(self['external_legs'])==1:
1642 return '('+str(self['external_legs'][0]['number'])+\
1643 '('+str(self['external_legs'][0]['id'])+'))'
1644 else:
1645 return '()'
1646
1647
1649 """ This functions generate the vertices building this structure,
1650 starting from the outter legs going towards the binding leg.
1651 It uses the interactions dictionaries from the model. """
1652
1653 if isinstance(model, base_objects.Process):
1654 assert external_legs is None
1655
1656 external_legs= model.get('legs')
1657 model = model['model']
1658 assert external_legs is not None
1659 assert isinstance(model, base_objects.Model)
1660
1661
1662
1663
1664 self.set('vertices',base_objects.VertexList())
1665
1666 tag=copy.copy(self['canonical'])
1667
1668
1669 ref_dict_to1 = model.get('ref_dict_to1')
1670
1671 if not tag:
1672 raise self.PhysicsObjectError("The canonical tag of the FD structure is not set yet, so that the "+\
1673 "reconstruction of the vertices cannot be performed.")
1674
1675
1676 leglist = copy.deepcopy(external_legs)
1677
1678
1679 legDict={}
1680 for leg in leglist:
1681 legDict[leg['number']]=leg
1682
1683
1684
1685 if len(tag)==1 and len(tag[0][0])==1:
1686
1687 self['binding_leg']=copy.deepcopy(legDict[tag[0][0][0]])
1688 return
1689
1690
1691 tag=list(tag)
1692 tag.reverse()
1693
1694
1695
1696 for i, tagelem in enumerate(tag):
1697 tag[i]=list(tagelem)
1698 tag[i][0]=base_objects.LegList([legDict[myleg] for myleg in \
1699 tagelem[0]])
1700
1701
1702
1703
1704
1705 while tag:
1706
1707
1708 legs=tag[0][0]
1709
1710
1711 key=tuple(sorted([leg.get('id') for leg in legs]))
1712 if key in ref_dict_to1:
1713 for interaction in ref_dict_to1[key]:
1714
1715 if interaction[1]==tag[0][1]:
1716
1717
1718 legid = interaction[0]
1719
1720
1721 number = min([leg.get('number') for leg in legs])
1722
1723
1724 if len([leg for leg in legs if leg.get('state') == False]) == 1:
1725 state = False
1726 else:
1727 state = True
1728 legs.append(base_objects.Leg({'number': number,\
1729 'id': legid,\
1730 'state': state,
1731 'loop_line': False}))
1732
1733 self.get('vertices').append(base_objects.Vertex(\
1734 {'legs':legs,'id':interaction[1]}))
1735 break
1736
1737
1738
1739 for i, tagelement in enumerate(tag[1:]):
1740 Found=False
1741 for leg in legs[:-1]:
1742 try:
1743 tag[i+1][0].remove(leg)
1744 Found=True
1745 except Exception:
1746 pass
1747 if Found:
1748 tag[i+1][0].append(legs[-1])
1749
1750
1751
1752 if len(tag)==1:
1753 self['binding_leg']=copy.deepcopy(legs[-1])
1754
1755
1756
1757 tag.pop(0)
1758
1759 else:
1760 raise self.PhysicsObjectError("The canonical tag of the FD structure is corrupted because one "+\
1761 "interaction does not exist.")
1762
1767 """List of FDStructure objects
1768 """
1769
1771 """Test if object obj is a valid Diagram for the list."""
1772
1773 return isinstance(obj, FDStructure)
1774
1776 """Return the FDStructure of the list with the corresponding canonical
1777 tag if ID is a tuple or the corresponding ID if ID is an integer.
1778 It returns the structure if it founds it, or None if it was not found"""
1779 if isinstance(ID, int):
1780 for FDStruct in self:
1781 if FDStruct.get('id')==ID:
1782 return FDStruct
1783 return None
1784 elif isinstance(ID, tuple):
1785 for FDStruct in self:
1786 if FDStruct.get('canonical')==ID:
1787 return FDStruct
1788 return None
1789 else:
1790 raise self.PhysicsObjectListError("The ID %s specified for get_struct is not an integer or tuple"%\
1791 repr(object))
1792
1794 """Returns a nicely formatted string"""
1795 mystr = str(len(self)) + ' FD Structures:\n'
1796 for struct in self:
1797 mystr = mystr + " " + struct.nice_string() + '\n'
1798 return mystr[:-1]
1799