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
1430 super(LoopModel,self).__init__(*args,**opts)
1431
1433 super(LoopModel,self).default_setup()
1434 self['perturbation_couplings'] = []
1435
1436
1437
1438
1439
1440
1441
1442
1443 self['coupling_orders_counterterms']={}
1444
1445
1446
1447
1448
1449 if not hasattr(self,'map_CTcoup_CTparam'):
1450 self.map_CTcoup_CTparam = {}
1451
1452
1453 - def filter(self, name, value):
1454 """Filter for model property values"""
1455
1456 if name == 'perturbation_couplings':
1457 if not isinstance(value, list):
1458 raise self.PhysicsObjectError("Object of type %s is not a list" % \
1459 type(value))
1460 for order in value:
1461 if not isinstance(order, str):
1462 raise self.PhysicsObjectError("Object of type %s is not a string" % \
1463 type(order))
1464 else:
1465 super(LoopModel,self).filter(name,value)
1466
1467 return True
1468
1470 """This function actualizes the dictionaries"""
1471
1472 if useUVCT:
1473 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1474 self['interactions'].generate_ref_dict(useR2UV=False,useUVCT=True)
1475 else:
1476 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1477 self['interactions'].generate_ref_dict()
1478 self['ref_dict_to0'].update(
1479 self['particles'].generate_ref_dict())
1480
1482 """Return process property names as a nicely sorted list."""
1483
1484 return ['name', 'particles', 'parameters', 'interactions', 'couplings',
1485 'lorentz','perturbation_couplings','conserved_charge']
1486
1487
1488
1489
1490 -class DGLoopLeg(base_objects.Leg):
1491 """A class only used during the loop diagram generation. Exactly like leg
1492 except for a few other parameters only useful during the loop diagram
1493 generation."""
1494
1506
1510
1511 - def filter(self, name, value):
1512 """Filter for model property values"""
1513
1514 if name == 'depth':
1515 if not isinstance(value, int):
1516 raise self.PhysicsObjectError("Object of type %s is not a int" % \
1517 type(value))
1518 else:
1519 super(DGLoopLeg,self).filter(name,value)
1520
1521 return True
1522
1524 """Return process property names as a nicely sorted list."""
1525
1526 return ['id', 'number', 'state', 'from_group','loop_line','depth',
1527 'polarization']
1528
1530 """ Converts a DGLoopLeg back to a Leg. Basically removes the extra
1531 attributes """
1532
1533 aleg=base_objects.Leg()
1534 for key in aleg.get_sorted_keys():
1535 aleg.set(key,self[key])
1536
1537
1538 return aleg
1539
1540
1541
1542
1543 -class FDStructure(base_objects.PhysicsObject):
1544 """FDStructure:
1545 list of vertices (ordered). This is part of a diagram.
1546 """
1547
1556
1558 """Returns wether the structure is simply made of an external particle
1559 only"""
1560 if (len(self['canonical'])==1 and self['canonical'][0][1]==0):
1561 return True
1562 else:
1563 return False
1564
1565 - def filter(self, name, value):
1566 """Filter for valid FDStructure property values."""
1567
1568 if name == 'vertices':
1569 if not isinstance(value, base_objects.VertexList):
1570 raise self.PhysicsObjectError("%s is not a valid VertexList object" % str(value))
1571
1572 if name == 'id':
1573 if not isinstance(value, int):
1574 raise self.PhysicsObjectError("id %s is not an integer" % repr(value))
1575
1576 if name == 'weight':
1577 if not isinstance(value, int):
1578 raise self.PhysicsObjectError("weight %s is not an integer" % repr(value))
1579
1580 if name == 'external_legs':
1581 if not isinstance(value, base_objects.LegList):
1582 raise self.PhysicsObjectError("external_legs %s is not a valid Leg List" % str(value))
1583
1584 if name == 'binding_leg':
1585 if not isinstance(value, base_objects.Leg):
1586 raise self.PhysicsObjectError("binding_leg %s is not a valid Leg" % str(value))
1587
1588 if name == 'canonical':
1589 if not isinstance(value, tuple):
1590 raise self.PhysicsObjectError("canonical %s is not a valid tuple" % str(value))
1591
1592 return True
1593
1595 """Return particle property names as a nicely sorted list."""
1596
1597 return ['id','external_legs','binding_leg','canonical','vertices']
1598
1600 """Returns a nicely formatted string of the structure content."""
1601
1602 mystr=''
1603
1604 if not self['id']==-1:
1605 mystr=mystr+'id: '+str(self['id'])+',\n'
1606 else:
1607 return '()'
1608
1609 if self['canonical']:
1610 mystr=mystr+'canonical_repr.: '+str(self['canonical'])+',\n'
1611
1612 if self['external_legs']:
1613 mystr=mystr+'external_legs: { '
1614 for leg in self['external_legs'][:-1]:
1615 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) \
1616 + ', '
1617 mystr = mystr + str(self['external_legs'][-1]['number']) + \
1618 '(%s)' % str(self['external_legs'][-1]['id']) + ' },\n'
1619 mystr = mystr+'binding_leg: '+str(self['binding_leg']['number']) +\
1620 '(%s)' % str(self['binding_leg']['id'])
1621 return mystr
1622
1624 """Returns a nicely formatted string of the structure vertices."""
1625 mystr=''
1626 if self['vertices']:
1627 mystr = mystr+'('
1628 for vert in self['vertices']:
1629 mystr = mystr + '('
1630 for leg in vert['legs'][:-1]:
1631 mystr = mystr + str(leg['number']) + \
1632 '(%s)' % str(leg['id']) + ','
1633 mystr = mystr[:-1] + '>'
1634 mystr = mystr + str(vert['legs'][-1]['number']) +\
1635 '(%s)' % str(vert['legs'][-1]['id']) + ','
1636 mystr = mystr + 'id:' + str(vert['id']) + '),'
1637 mystr = mystr[:-1] + ')'
1638 return mystr
1639 elif len(self['external_legs'])==1:
1640 return '('+str(self['external_legs'][0]['number'])+\
1641 '('+str(self['external_legs'][0]['id'])+'))'
1642 else:
1643 return '()'
1644
1645
1647 """ This functions generate the vertices building this structure,
1648 starting from the outter legs going towards the binding leg.
1649 It uses the interactions dictionaries from the model. """
1650
1651 if isinstance(model, base_objects.Process):
1652 assert external_legs is None
1653
1654 external_legs= model.get('legs')
1655 model = model['model']
1656 assert external_legs is not None
1657 assert isinstance(model, base_objects.Model)
1658
1659
1660
1661
1662 self.set('vertices',base_objects.VertexList())
1663
1664 tag=copy.copy(self['canonical'])
1665
1666
1667 ref_dict_to1 = model.get('ref_dict_to1')
1668
1669 if not tag:
1670 raise self.PhysicsObjectError("The canonical tag of the FD structure is not set yet, so that the "+\
1671 "reconstruction of the vertices cannot be performed.")
1672
1673
1674 leglist = copy.deepcopy(external_legs)
1675
1676
1677 legDict={}
1678 for leg in leglist:
1679 legDict[leg['number']]=leg
1680
1681
1682
1683 if len(tag)==1 and len(tag[0][0])==1:
1684
1685 self['binding_leg']=copy.deepcopy(legDict[tag[0][0][0]])
1686 return
1687
1688
1689 tag=list(tag)
1690 tag.reverse()
1691
1692
1693
1694 for i, tagelem in enumerate(tag):
1695 tag[i]=list(tagelem)
1696 tag[i][0]=base_objects.LegList([legDict[myleg] for myleg in \
1697 tagelem[0]])
1698
1699
1700
1701
1702
1703 while tag:
1704
1705
1706 legs=tag[0][0]
1707
1708
1709 key=tuple(sorted([leg.get('id') for leg in legs]))
1710 if key in ref_dict_to1:
1711 for interaction in ref_dict_to1[key]:
1712
1713 if interaction[1]==tag[0][1]:
1714
1715
1716 legid = interaction[0]
1717
1718
1719 number = min([leg.get('number') for leg in legs])
1720
1721
1722 if len([leg for leg in legs if leg.get('state') == False]) == 1:
1723 state = False
1724 else:
1725 state = True
1726 legs.append(base_objects.Leg({'number': number,\
1727 'id': legid,\
1728 'state': state,
1729 'loop_line': False}))
1730
1731 self.get('vertices').append(base_objects.Vertex(\
1732 {'legs':legs,'id':interaction[1]}))
1733 break
1734
1735
1736
1737 for i, tagelement in enumerate(tag[1:]):
1738 Found=False
1739 for leg in legs[:-1]:
1740 try:
1741 tag[i+1][0].remove(leg)
1742 Found=True
1743 except Exception:
1744 pass
1745 if Found:
1746 tag[i+1][0].append(legs[-1])
1747
1748
1749
1750 if len(tag)==1:
1751 self['binding_leg']=copy.deepcopy(legs[-1])
1752
1753
1754
1755 tag.pop(0)
1756
1757 else:
1758 raise self.PhysicsObjectError("The canonical tag of the FD structure is corrupted because one "+\
1759 "interaction does not exist.")
1760
1765 """List of FDStructure objects
1766 """
1767
1769 """Test if object obj is a valid Diagram for the list."""
1770
1771 return isinstance(obj, FDStructure)
1772
1774 """Return the FDStructure of the list with the corresponding canonical
1775 tag if ID is a tuple or the corresponding ID if ID is an integer.
1776 It returns the structure if it founds it, or None if it was not found"""
1777 if isinstance(ID, int):
1778 for FDStruct in self:
1779 if FDStruct.get('id')==ID:
1780 return FDStruct
1781 return None
1782 elif isinstance(ID, tuple):
1783 for FDStruct in self:
1784 if FDStruct.get('canonical')==ID:
1785 return FDStruct
1786 return None
1787 else:
1788 raise self.PhysicsObjectListError("The ID %s specified for get_struct is not an integer or tuple"%\
1789 repr(object))
1790
1792 """Returns a nicely formatted string"""
1793 mystr = str(len(self)) + ' FD Structures:\n'
1794 for struct in self:
1795 mystr = mystr + " " + struct.nice_string() + '\n'
1796 return mystr[:-1]
1797