| Trees | Indices | Help |
|---|
|
|
1 ################################################################################
2 #
3 # Copyright (c) 2009 The MadGraph5_aMC@NLO Development team and Contributors
4 #
5 # This file is a part of the MadGraph5_aMC@NLO project, an application which
6 # automatically generates Feynman diagrams and matrix elements for arbitrary
7 # high-energy processes in the Standard Model and beyond.
8 #
9 # It is subject to the MadGraph5_aMC@NLO license which should accompany this
10 # distribution.
11 #
12 # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
13 #
14 ################################################################################
15
16 """Definitions of all basic objects with extra features to treat loop
17 diagrams"""
18
19 import copy
20 import itertools
21 import logging
22 import numbers
23 import os
24 import re
25 import madgraph.core.color_algebra as color
26 import madgraph.core.diagram_generation as diagram_generation
27 import madgraph.core.base_objects as base_objects
28 import madgraph.various.misc as misc
29 from madgraph import MadGraph5Error, MG5DIR
30
31 logger = logging.getLogger('madgraph.loop_base_objects')
32
33 #===============================================================================
34 # LoopDiagram
35 #===============================================================================
36 -class LoopDiagram(base_objects.Diagram):
37 """LoopDiagram: Contains an additional tag to uniquely identify the diagram
38 if it contains a loop. Also has many additional functions useful only
39 for loop computations.
40 """
41
42 # The class variable below select what algorithm is used for choosing where
43 # to cut the loops. The possibilities are:
44 # 'optimal' -> will use choos_optimal_lcut()
45 # 'default' -> will use chose_default_lcut()
46 # In principle it is always 'optimal'. But it can be changed by process_check
47 # for the purpose of the check permutation command.
48 cutting_method = 'optimal'
49
51 """Default values for all properties"""
52
53 super(LoopDiagram,self).default_setup()
54 # This tag specifies the particular structure of this loop, cut at
55 # and ordered in the same way as originally generated. It contains
56 # the full information about the loop vertices and the loop legs.
57 # It is of the form:
58 # [(Leg,[Structure_IDs],VertexID), (...), ...]
59 self['tag'] = []
60 # This tag uniquely define a loop particle. It is not used for born,
61 # R2 and UV diagrams. It is only a list of integers, so not too
62 # heavy to store. It is what allows for diagram selection.
63 # It is of the form:
64 # [(LegPDG,[Structure_IDs],VertexID), (...), ...]
65 # But ordered in a canonical unambiguous way.
66 self['canonical_tag'] = []
67 # This information is in principle recoverable from the VertexList but
68 # it is faster to store it as a single integer.
69 # It is the (positive) PDG of the (particle, not anti-particle) L-cut
70 # particle for a loop diagram.
71 self['type'] = 0
72 # Loop diagrams can be identified to others which are numerically exactly
73 # equivalent. This is the case for example for the closed massles quark
74 # loops. In this case, only one copy of the diagram is kept and this
75 # multiplier attribute is set the to number of identified diagrams.
76 self['multiplier'] = 1
77 # This stores the list of amplitudes vertices which give the R2/UV
78 # counter-terms to this loop.
79 self['CT_vertices'] = base_objects.VertexList()
80 # The 'contracted_diagram' is the diagram constructed by the function
81 # get_contracted_loop_diagram' which corresponds to the list of vertices
82 # to construct the equivalent diagram to this one when the loop is shrunk
83 # to one point.
84 self['contracted_diagram'] = None
85
87 """Filter for valid diagram property values."""
88
89 if name == 'tag':
90 if not isinstance(value, list):
91 raise self.PhysicsObjectError, \
92 "%s is not a valid tag" % str(value)
93 else:
94 for item in value:
95 if (len(item)!=3 or \
96 not isinstance(item[0],base_objects.Leg) or \
97 not isinstance(item[1],list)) or \
98 not isinstance(item[2],base_objects.Vertex):
99 raise self.PhysicsObjectError, \
100 "%s is not a valid tag" % str(value)
101
102 if name == 'canonical_tag':
103 if not isinstance(value, list):
104 raise self.PhysicsObjectError, \
105 "%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, \
112 "%s is not a valid canonical_tag" % str(value)
113
114 if name == 'CT_vertices':
115 if not isinstance(value, base_objects.VertexList):
116 raise self.PhysicsObjectError, \
117 "%s is not a valid VertexList object" % str(value)
118
119 if name == 'type':
120 if not isinstance(value, int):
121 raise self.PhysicsObjectError, \
122 "%s is not a valid integer" % str(value)
123
124 if name == 'multiplier':
125 if not isinstance(value, int):
126 raise self.PhysicsObjectError, \
127 "%s is not a valid integer" % str(value)
128
129 if name == 'contracted_diagram':
130 if not isinstance(value, base_objects.Diagram):
131 raise self.PhysicsObjectError, \
132 "%s is not a valid Diagram." % str(value)
133
134 else:
135 super(LoopDiagram, self).filter(name, value)
136
137 return True
138
140 """Return particle property names as a nicely sorted list."""
141
142 return ['vertices', 'CT_vertices', 'orders', 'type', 'tag']
143
145 """Returns a nicely formatted string of the diagram content."""
146
147 # Return the mother nice_string if this LoopDiagram is of born type.
148 if self['type']==0:
149 return super(LoopDiagram,self).nice_string()
150
151 mystr=''
152 if not self['vertices']:
153 return '()'
154 if self['canonical_tag']:
155 mystr = mystr+'canonical tag: '+str(self['canonical_tag'])+'\n'
156 if self['CT_vertices']:
157 mystr = mystr+'CT vertex ids:'
158 for ctvx in self['CT_vertices']:
159 mystr = mystr +' '+str(ctvx.get('id'))
160 mystr = mystr+'\n'
161 if self['vertices']:
162 mystr = mystr+'Loop vertices: ('
163 for vert in self['vertices']:
164 mystr = mystr + '('
165 for leg in vert['legs'][:-1]:
166 if leg['loop_line']:
167 mystr = mystr + str(leg['number']) + \
168 '(%s*)' % str(leg['id']) + ','
169 else:
170 mystr = mystr + str(leg['number']) + \
171 '(%s)' % str(leg['id']) + ','
172
173 if self['vertices'].index(vert) < len(self['vertices']) - 1:
174 # Do not want ">" in the last vertex
175 mystr = mystr[:-1] + '>'
176 if vert['legs'][-1]['loop_line']:
177 mystr = mystr + str(vert['legs'][-1]['number']) + \
178 '(%s*)' % str(vert['legs'][-1]['id']) + ','
179 else:
180 mystr = mystr + str(vert['legs'][-1]['number']) + \
181 '(%s)' % str(vert['legs'][-1]['id']) + ','
182 mystr = mystr + 'id:' + str(vert['id']) + '),'
183 mystr = mystr[:-1] + ')'
184 mystr += " (%s)" % ",".join(["%s=%d" % (key, self['orders'][key]) \
185 for key in self['orders'].keys()])+"\n"
186 if struct_list and self['tag']:
187 for i, tag_elem in enumerate(self['tag']):
188 for j, struct in enumerate(tag_elem[1]):
189 if len(tag_elem[1])>1:
190 mystr += 'Struct. #'+str(j+1)+\
191 ' on loop vx #'+str(i+1)+": "+\
192 struct_list[struct].nice_string_vertices()+"\n"
193 else:
194 mystr += 'Struct. on loop vx #'+str(i+1)+": "+\
195 struct_list[struct].nice_string_vertices()+"\n"
196 #remove the unecessary last \n on the line
197 mystr=mystr[:-1]
198
199 return mystr
200
202 """This is the old function used without tag which means that no
203 canonical loop information can be produced. It will be used for
204 unit test only and moved there when I'll implement them."""
205
206 # Without the tagging information we will have to reconstruct the
207 # contracted diagrams with the unordered vertices
208 if len(self.get('vertices'))==0:
209 raise MadGraph5Error, "Function get_contracted_loop_diagram()"+\
210 "called for the first time without specifying struct_rep "+\
211 "for a diagram already tagged."
212
213 # The leg below will be the outgoing one
214 contracted_vertex_last_loop_leg = None
215 # List here the vertices which have to appear after and before the
216 # contracted loop vertex.
217 vertices_after_contracted_vertex = []
218 vertices_before_contracted_vertex = []
219 # To know if a given vertex must be placed after or before the
220 # the contracted loop vertex, we must now the list of leg numbers
221 # which have been "generated" starting from the one outgoing leg of
222 # the contracted loop vertex.
223 contracted_vertex_leg_daughters_nb = []
224
225 # We need a different treatment for the amplitude-type vertex
226 # (the last one) for which all legs are incoming.
227 for vertex in self.get('vertices')[:-1]:
228 # If the interaction had nothing to do with a loop, just add it
229 if not any(l['loop_line'] for l in vertex.get('legs')):
230 # before the contracted vertex if it didn't need any of
231 # the leg numbers it generated
232 if any((l.get('number') in contracted_vertex_leg_daughters_nb) \
233 for l in vertex.get('legs')[:-1]):
234 vertices_after_contracted_vertex.append(vertex)
235 contracted_vertex_leg_daughters_nb.append(vertex.get('legs')[-1])
236 else:
237 vertices_before_contracted_vertex.append(vertex)
238 else:
239 # Add to the mothers of the contracted vertex
240 contracted_vertex.get('legs').extend(
241 [l for l in vertex.get('legs')[:-1] if not l['loop_line']])
242 # Extend the list of PDGs making up this interaction.
243 # This is useful for the DigramChainTag.
244 contracted_vertex.get('PDGs').extend([l.get('id') for l in
245 vertex.get('legs') if not l['loop_line']])
246 # If the outgoing leg is not a loop line but the vertex still
247 # has two loop lines as mothers, then it is the vertex that we
248 # must replace by the contracted loop vertex
249 if not vertex.get('legs')[-1]['loop_line']:
250 # The contracted vertex is not of amplitude type here
251 contracted_vertex_last_loop_leg = vertex.get('legs')[-1]
252
253 # Treat the last vertex now
254 if any(l['loop_line'] for l in self.get('vertices')[-1].get('legs')):
255 # Add to the mothers of the contracted vertex
256 contracted_vertex.get('legs').extend([l for l in
257 self.get('vertices')[-1].get('legs') if not l['loop_line']])
258
259 else:
260 vertices_after_contracted_vertex.append(self.get('vertices')[-1])
261
262
263 contracted_diagram_vertices.extend(vertices_before_contracted_vertex)
264 if not contracted_vertex_last_loop_leg is None:
265 contracted_vertex.get('legs').append(contracted_vertex_last_loop_leg)
266
267 if len(contracted_vertex.get('legs'))==1:
268 stop
269 contracted_diagram_vertices.append(contracted_vertex)
270 contracted_diagram_vertices.extend(vertices_after_contracted_vertex)
271
272 contracted_diagram = base_objects.Diagram(
273 {'vertices':contracted_diagram_vertices,'orders':self.get('orders')})
274
275 return contracted_diagram
276
277 - def build_loop_tag_for_diagram_identification(self, model, FDStrut_rep,
278 use_FDStructure_ID_for_tag = False):
279 """ This function returns what will be used as the 'loop_tag' attribute
280 of the ContractedVertex instance in the function 'get_contracted_loop_diagram'.
281 It is important since it is what is used by MG5_aMC to decide
282 if two processes have *exactly* the same matrix element and can be
283 identified.
284 There is no need to characterize the details of the FDStructures attached
285 to the loops because these are already compared using the rest of the
286 DiagramTag structure. All we need is to identify a structure by its
287 external leg numbers."""
288
289 canonical_tag = self['canonical_tag']
290
291 # First create a list of objects we want to use to identify the particles
292 # running in the loop. We use here the same strategy as in the function
293 # 'vertex_id_from_vertex' of IdentifyMETag.
294 # However, in addition to what one has in IdentifyMETag, we must also
295 # keep track of the attribute 'is_part' since this provides the
296 # direction of the loop flow.
297 loop_parts_tagging = [[]]*len(canonical_tag)
298 for i, tag_elem in enumerate(canonical_tag):
299 loop_part = model.get_particle(tag_elem[0])
300 loop_parts_tagging[i] = (loop_part.get('spin'),
301 loop_part.get('color'),
302 loop_part.get('self_antipart'),
303 loop_part.get('mass'),
304 loop_part.get('width'),
305 loop_part.get('is_part'))
306
307 # Now create a list of objects which we want to use to uniquely
308 # identify each structure attached to the loop for the loop_tag.
309 FDStructs_tagging = [[]]*len(canonical_tag)
310 for i, tag_elem in enumerate(canonical_tag):
311 for struct_ID in tag_elem[1]:
312 if not use_FDStructure_ID_for_tag:
313 # The FDStructures will be probed by the rest of the
314 # DiagramTag, it is therefore not necessary to include any
315 # information regarding the structures in the loop_tag.
316 # However, notice that this means that the same loop attached
317 # to structures (1,2,3,4), in this order, and another one
318 # attached to the same structure but in a different order,
319 # say (1,4,3,2), will share the same DiagramTag (because the
320 # structure are the same but in a different order) since the
321 # loop_tag doesn't account for any information regarding the
322 # structures. This is ok because the Diagram is only intended
323 # for process identifications.
324 pass
325 # FDStructs_tagging[i].extend([leg.get('number') for leg in
326 # FDStrut_rep.get_struct(struct_ID).get('external_legs')])
327 else:
328 # For the loop diagram identification (within a given process)
329 # we must account for the FDStructure, and it is then
330 # simplest to just use their ID (since all loop diagrams
331 # have been tagged with the same FDStructure repository in
332 # this case, so that the FDStructure ID is really unique).
333 # There is no need to use the 'canonical' attribute of the
334 # structure ID.
335 FDStructs_tagging[i].append(struct_ID)
336
337 FDStructs_tagging[i].sort()
338 FDStructs_tagging[i] = tuple(FDStructs_tagging[i])
339
340 # We want to identify processes together if their diagrams
341 # are made of the same interactions which can however have different
342 # ID's for different process (i.e. the ID of the 'gdd~' interaction is
343 # different than the one of 'gss~'). We again use the same strategy
344 # as in the function 'vertex_id_from_vertex' of IdentifyMETag.
345 # So we create a list of objects we want to use to tag the loop interactions
346 interactions_tagging = [[]]*len(canonical_tag)
347 for i, tag_elem in enumerate(canonical_tag):
348 inter = model.get_interaction(tag_elem[2])
349 coup_keys = sorted(inter.get('couplings').keys())
350 interactions_tagging[i]=(
351 tuple((key, inter.get('couplings')[key]) for key in coup_keys),
352 tuple(str(c) for c in inter.get('color')),
353 tuple(inter.get('lorentz')))
354
355 return tuple(
356 # For each loop vertex, we must identify the following three things
357 zip(
358 # Loop particle identification
359 loop_parts_tagging,
360 # FDStructure identification
361 FDStructs_tagging,
362 # Loop interactions identification
363 interactions_tagging,
364 )
365 # Finally make sure that the loop orders are the same
366 + sorted(self.get_loop_orders(model).items())
367 )
368
370 """ Returns a base_objects.Diagram which correspond to this loop diagram
371 with the loop shrunk to a point. If struct_rep is no specified, then
372 the tagging will proceed assuming no FDStructure has been identified yet.
373 Otherwise, it will possible reuse them an update the repository."""
374
375 if self['type']<=0:
376 return copy.copy(self)
377
378 if self['contracted_diagram']:
379 return self['contracted_diagram']
380
381 # If this loop diagram hasn't been tagged yet, we must do that now.
382 # (or if the structure repository is not provided
383 if not self['canonical_tag'] or struct_rep is None:
384 n_external_legs = len(base_objects.LegList([l for l in
385 self.get_external_legs() if not l['loop_line']]))
386
387 # use natural ordering for loop tagging
388 start_in, end_in = n_external_legs +1, n_external_legs+2
389 for l in self['vertices'][0]['legs']:
390 if l.same(start_in):
391 break
392 elif l.same(end_in):
393 start_in, end_in = end_in, start_in
394 break
395
396 if struct_rep is None:
397 struct_rep = FDStructureList([])
398 self.tag(struct_rep, model, start_in=start_in, end_in=end_in,
399 synchronize=False)
400
401 contracted_diagram_vertices = base_objects.VertexList()
402 # We give this vertex the special ID -2 so that whenever MG5_aMC tries
403 # to retrieve an information in typically gets from the model interaction
404 # it will instead get it from the 'loop_info' provided by the contracted
405 # vertex of its corresponding vertex_id in a Tag
406 contracted_vertex = base_objects.ContractedVertex({
407 'id':-2,
408 'loop_orders':self.get_loop_orders(model),
409 'loop_tag': self.build_loop_tag_for_diagram_identification(model, struct_rep)
410 })
411
412 # Using the 'tag' information, the construction of the contracted diagram
413 # quite simple. First scan all structures to add their vertices and at
414 # the same time construct the legs of the final vertex which corresponds
415 # to the shrunk loop.
416 for tagelem in self['tag']:
417 contracted_vertex.get('legs').extend([struct_rep[
418 struct_ID].get('binding_leg') for struct_ID in tagelem[1]])
419 # Extend the list of PDGs making up this interaction.
420 # This is useful for the DigramChainTag.
421 contracted_vertex.get('PDGs').extend([struct_rep[struct_ID].
422 get('binding_leg').get('id') for struct_ID in tagelem[1]])
423 contracted_diagram_vertices.extend(sum([struct_rep[
424 struct_ID].get('vertices') for struct_ID in tagelem[1]],[]))
425
426 # Add the shrunk vertex to the contracted diagram vertices list.
427 contracted_diagram_vertices.append(contracted_vertex)
428
429 contracted_diagram = base_objects.Diagram(
430 {'vertices':contracted_diagram_vertices,'orders':self.get('orders')})
431
432 self['contracted_diagram'] = contracted_diagram
433
434 return contracted_diagram
435
437 """ Returns the CounterTerms of the type passed in argument. If None
438 it returns all of them. """
439 if string:
440 return base_objects.VertexList([vert for vert in \
441 self['CT_vertices'] if string in \
442 model['interaction_dict'][vert['id']]['type']])
443 else:
444 return self['CT_vertices']
445
447 """ Return none if there is no loop or if a tag has not yet been set and
448 returns True if this graph contains a purely fermionic loop and False if
449 not. """
450
451 if(self['tag']):
452 for part in self['tag']:
453 if not model.get('particle_dict')[part[0].get('id')].is_fermion():
454 return False
455 return True
456 else:
457 return False
458
460 """ Return None if there is no loop or if a tag has not yet been set and
461 returns True if this graph contains a tadpole loop and False if not. """
462
463 if(self['tag']):
464 if(len(self['tag'])==1):
465 return True
466 else:
467 return False
468 else:
469 return None
470
472 """Return None if there is no loop or if a tag has not yet been set and
473 returns True if this graph contains a vanishing tadpole loop and False
474 if not. """
475
476 if not self.is_tadpole():
477 return False
478
479 # absorbed by renormalization of vev
480 if(len(self['tag'][0][1])<=1):
481 return True
482 # massless tadpole
483 return any([part['mass'].lower()=='zero' for pdg,part in \
484 model.get('particle_dict').items() if \
485 pdg==abs(self['tag'][0][0]['id'])])
486
488 """ Return None if there is no loop or if a tag has not yet been set and
489 returns True if this graph contains a wave-function correction and False
490 if not. """
491
492 if self['tag'] :
493 # Makes sure only one current flows off each side of the bubble
494 if len(self['tag'])==2 and len(self['tag'][0][1])==1 \
495 and len(self['tag'][1][1])==1:
496 # Checks that at least one of the two structure is external
497 if struct_rep[self['tag'][0][1][0]].is_external() or \
498 struct_rep[self['tag'][1][1][0]].is_external():
499 # Check that the two binding legs are of the same nature
500 inLegID=struct_rep[self['tag'][0][1][0]]['binding_leg']['id']
501 outLegID=struct_rep[self['tag'][1][1][0]]['binding_leg']['id']
502 return True
503
504 # check a wf correction with tadpole (massive)
505 if len(self['tag'])==1 and len(self['tag'][0][1])==2 and \
506 (struct_rep[self['tag'][0][1][0]].is_external() or
507 struct_rep[self['tag'][0][1][1]].is_external()):
508 return True
509
510 return False
511 else:
512 return None
513
515 """Return the number of loop lines. """
516 if self['tag']:
517 return len(self['tag'])
518 else:
519 return None
520
521 @classmethod
523 """ Computes the weighting function S for this structure 'i' such that
524 S(i)>0 for each any i, S(i)!=S(j) if i['external_legs']!=j['external_legs']
525 and S(i+j)>max(S(i),S(j)). """
526
527 external_numbers=[leg['number'] for id in FD_ids_list for leg in \
528 struct_rep.get_struct(id).get('external_legs')]
529 external_numbers.sort()
530 weight=0
531 for i, number in enumerate(external_numbers):
532 weight=i*number_legs+number
533 return weight
534
535 @classmethod
537 """ This function chooses the place where to cut the loop in order to
538 maximize the loop wavefunction recycling in the open loops method.
539 This amounts to cut just before the combined structure with smallest
540 weight and then chose the direction to go towards the one with smallest
541 weight."""
542
543 tag=copy.deepcopy(intag)
544 number_legs=len(external_legs)
545
546 # Put the smallest weight first
547 weights=[cls.compute_weight(t[1],struct_rep,number_legs) for t in tag]
548 imin = weights.index(min(weights))
549 tag=tag[imin:]+tag[:imin]
550 weights=weights[imin:]+weights[:imin]
551
552 # Now chose the direction
553 rev_tag=cls.mirrored_tag(tag, model)
554 # Put it back with the smallest weight first
555 rev_tag=rev_tag[-1:]+rev_tag[:-1]
556 rev_weights=[cls.compute_weight(t[1],struct_rep,number_legs) for t in rev_tag]
557
558 # Finally return the appropriate tag
559 if len(tag)==1:
560 return tag
561 elif len(tag)==2:
562 if abs(tag[0][0]['id'])>abs(tag[1][0]['id']):
563 return rev_tag
564 else:
565 return tag
566 else:
567 if rev_weights[1]<weights[1]:
568 return rev_tag
569 else:
570 return tag
571
572 @classmethod
574 """ This function chooses where to cut the loop. It returns the
575 canonical tag corresponding to this unambiguous choice."""
576 # We then construct the canonical_tag such that it is a cyclic
577 # permutation of tag such that the first loop vertex appearing in
578 # canonical_tag is the one carrying the structure with the lowest
579 # ID. This is a safe procedure because a given structure can only
580 # appear once in a diagram since FDStructures are characterized by
581 # the particle numbers and a given particle number can only appear
582 # once in a diagram.
583 canonical_tag=copy.deepcopy(tag)
584 canonical_tag=cls.make_canonical_cyclic(canonical_tag)
585 canonical_mirrored_tag=copy.deepcopy(canonical_tag)
586 canonical_mirrored_tag=cls.mirrored_tag(canonical_mirrored_tag,model)
587 # We must put it back in the canonical cyclic order
588 canonical_mirrored_tag=canonical_mirrored_tag[-1:]+\
589 canonical_mirrored_tag[:-1]
590 # Now to relieve the remaining ambiguity due to the mirrored L-cut
591 # diagram, we chose among the two equivalent tag 'canonical_tag' and
592 # 'canonical_mirrored_tag' the one having the lowest structure ID in
593 # second position (this is equivalent as saying that we always
594 # construct the tag starting next to the lowest structure ID and
595 # in the direction of the next-to-lowest structure ID). This is
596 # irrelevant in the case of tadpoles (len(tag)==1) and bubbles made
597 # of the same particle. If these bubbles are not made of the same
598 # two particle, the tag chosen is the one starting from the biggest
599 # particle id.
600 # Remove the redundant bubble diagrams, like [a W- a] and [W+ a W-]
601 # add abs when it is a bubble,i.e. len(tag)==2
602 if (len(tag)==2 and abs(canonical_mirrored_tag[0][0]['id'])>\
603 abs(canonical_tag[0][0]['id'])) or (len(tag)>2 and \
604 canonical_mirrored_tag[1][1]<canonical_tag[1][1]):
605 canonical_tag=canonical_mirrored_tag
606
607 return canonical_tag
608
610 """ Construct the tag of the diagram providing the loop structure
611 of it. """
612
613 # Create the container for the new vertices which create the loop flow
614 # It is dummy at this stage
615 loopVertexList=base_objects.VertexList()
616
617 # We create here the list of external legs. It will be used in each call
618 # of process_next_loop_leg to generate the FDStructure vertices, so
619 # it is more efficient to create it here once only.
620 external_legs = base_objects.LegList([l for l in
621 self.get_external_legs() if not l['loop_line']])
622 n_initial = len([1 for leg in external_legs if not leg['state']])
623
624 if start_in is None or end_in is None:
625 start_in = len(external_legs)+1
626 end_in = len(external_legs)+2
627
628 # Notice here that start and end can be either the Legs object
629 # specification of the two L-cut particles or simply their 'number'.
630 if isinstance(start_in,int) and isinstance(end_in,int):
631 start=start_in
632 end=end_in
633 elif isinstance(start_in,base_objects.Leg) and \
634 isinstance(end_in,base_objects.Leg):
635 start=start_in.get('number')
636 end=end_in.get('number')
637 else:
638 raise MadGraph5Error, "In the diagram tag function, 'start' and "+\
639 " 'end' must be either integers or Leg objects."
640
641 if self.process_next_loop_leg(struct_rep,-1,-1,start,end,\
642 loopVertexList, model, external_legs):
643 # Possible check here is:
644 #mytype=self['type']
645 #self.synchronize_loop_vertices_with_tag(process['model'],
646 # struct_rep,start,end)
647 #assert(loopVertexList==self['vertices'] and mytype==self['type'])
648
649 # Different choices of the loop cut can be made suited for different
650 # optimizations.
651 if self.cutting_method=='default':
652 # The default one has no specific property.
653 canonical_tag=self.choose_default_lcut(self['tag'],model)
654 elif self.cutting_method=='optimal':
655 # The choice below is optimized for recycling the loop wavefunction
656 # in the open loops method.
657 canonical_tag=self.choose_optimal_lcut(self['tag'],struct_rep,
658 model, external_legs)
659 else:
660 raise MadGraph5Error, 'The cutting method %s is not implemented.'\
661 %self.cutting_method
662 # The tag of the diagram is now updated with the canonical tag
663 self['tag']=canonical_tag
664 # We assign here the loopVertexList to the list of vertices
665 # building this loop diagram. Keep in mind the the structures are
666 # factored out.
667 if synchronize:
668 self.synchronize_loop_vertices_with_tag(model,n_initial,
669 struct_rep,start,end)
670 # Now we just have to replace, in the canonical_tag, the legs with
671 # the corresponding leg PDG since this is the only thing that matter
672 # when building a canonical representation for the loop to perform
673 # the selection of the loop basis.
674 self['canonical_tag']=[[t[0]['id'],t[1],t[2]] for t in canonical_tag]
675 return True
676 else:
677 raise self.PhysicsObjectError, \
678 "Loop diagram tagging failed."
679 return False
680
681
682 @classmethod
684 """ Generate a loop vertex from incoming legs myleglist and the
685 interaction with id vertID of the model given in argument """
686 # Define easy access point
687 ref_dict_to1 = model.get('ref_dict_to1')
688 # Now we make sure we can combine those legs together (and
689 # obtain the output particle ID)
690 key=tuple(sorted([leg.get('id') for leg in myleglist]))
691 if ref_dict_to1.has_key(key):
692 for interaction in ref_dict_to1[key]:
693 # Find the interaction with the right ID
694 if interaction[1]==vertID:
695 # Create the output Leg and add it to the
696 # existing list
697 #1) id is like defined by ref_dict_to1
698 legid = interaction[0]
699 # 2) number is the minimum of leg numbers
700 # involved in the combination
701 number = min([leg.get('number') for leg in\
702 myleglist])
703 # 3) state is final, unless there is exactly
704 # one initial state particle involved in the
705 # combination -> t-channel
706 # For a decay process there is of course no t-channel
707 if n_initial>1 and len(myleglist)>1 and len(filter( \
708 lambda leg: leg.get('state') == False, myleglist)) == 1:
709 state = False
710 else:
711 state = True
712 myleglist.append(base_objects.Leg(\
713 {'number': number,\
714 'id': legid,\
715 'state': state,
716 'loop_line': True}))
717 # Now we can add the corresponding vertex
718 return base_objects.Vertex({'legs':myleglist,'id':vertID})
719 else:
720 raise cls.PhysicsObjectError, \
721 "An interaction from the original L-cut diagram could"+\
722 " not be found when reconstructing the loop vertices."
723
724 - def process_next_loop_leg(self, structRep, fromVert, fromPos, currLeg, \
725 endLeg, loopVertexList, model, external_legs):
726 """ Finds a loop leg and what is the next one. Also identify and tag the
727 FD structure attached in between these two loop legs. It adds the
728 corresponding tuple to the diagram tag and calls iself again to treat
729 the next loop leg. Return True when tag successfully computed."""
730
731 nextLoopLeg=None
732 legPos=-2
733 vertPos=-2
734 FDStructureIDList=[]
735 vertFoundID=-1
736 n_initial = len([1 for leg in external_legs if not leg['state']])
737
738 # Helper function to process a loop interaction once found
739 def process_loop_interaction(i,j,k,pos):
740 """For vertex position 'i' and loop leg position 'j'. Find the
741 structure attached to leg k of this loop interaction, tag it and
742 update the loop tag."""
743 FDStruct=FDStructure()
744 # Launch here the iterative construction of the FDStructure
745 # constructing the four-vector current of leg at position k
746 # in vertex i.
747 canonical = self.construct_FDStructure(i,pos,\
748 self['vertices'][i].get('legs')[k],FDStruct)
749
750 if not canonical:
751 raise self.PhysicsObjectError, \
752 "Failed to reconstruct a FDStructure."
753
754 # The branch was directly an external leg, so it the canonical
755 # repr of this struct is simply ((legID),0).
756 if isinstance(canonical,int):
757 FDStruct.set('canonical',(((canonical,),0),))
758 elif isinstance(canonical,tuple):
759 FDStruct.set('canonical',canonical)
760 else:
761 raise self.PhysicsObjectError, \
762 "Non-proper behavior of the construct_FDStructure function"
763
764 # First check if this structure exists in the dictionary of the
765 # structures already obtained in the diagrams for this process
766 myStructID=-1
767 myFDStruct=structRep.get_struct(FDStruct.get('canonical'))
768 if not myFDStruct:
769 # It is a new structure that must be added to dictionary
770 # struct Rep
771 myStructID=len(structRep)
772 # A unique ID is given to the Struct we add to the
773 # dictionary.
774 FDStruct.set('id',myStructID)
775 # And we now ask the structure to create its vertices,
776 # starting from the outter legs going inwards towards the
777 # binding leg.
778 FDStruct.generate_vertices(model, external_legs)
779 structRep.append(FDStruct)
780 else:
781 # We get here the ID of the FDstruct recognised which has
782 # already been added to the dictionary. Note that using the
783 # unique ID for the canonical tag of the tree cut-loop
784 # diagrams has pros and cons. In particular, it makes
785 # shorter diagram tags yielding shorter selection but at
786 # the same time it makes the recovery of the full FDStruct
787 # object from it's ID more cumbersome.
788 myStructID=myFDStruct.get('id')
789
790 FDStructureIDList.append(myStructID)
791
792 # == Code begins ==
793 # We will scan the whole vertex list to look for the next loop
794 # interaction.
795 vertRange=range(len(self['vertices']))
796 # If we just start the iterative procedure, then from_vert=-1 and we
797 # must look for the "start" loop leg in the entire vertices list
798 if not fromVert == -1:
799 if fromPos == -1:
800 # If the last loop leg was the vertex output (i.e. last in the
801 # vertex leg list) then we must look for it in the vertices
802 # located after the one where it was found (i.e. from_vert).
803 vertRange=vertRange[fromVert+1:]
804 else:
805 # If the last loop leg was in the vertex inputs (i.e. not last
806 # in the vertex leg list) then we must look where it in the
807 # vertices located before where it was found (i.e. from_vert),
808 # starting from the closest to fromVert (hence the reverse())
809 vertRange=vertRange[:fromVert]
810 vertRange.reverse()
811 # Look in the vertices in vertRange if it can finds the loop leg asked
812 # for.
813 for i in vertRange:
814 # If the last loop leg was an output of its vertex, we must look for
815 # it in the INPUTS of the vertices before. However, it it was an
816 # input of its vertex we must look in the OUTPUT of the vertices
817 # forehead
818 legRange=range(len(self['vertices'][i].get('legs')))
819 if fromPos == -1:
820 # In the last vertex of the list, all entries are input
821 if not i==len(self['vertices'])-1:
822 legRange=legRange[:-1]
823 else:
824 # If looking for an output, then skip the last vertex of the
825 # list which only has inputs.
826 if i==len(self['vertices'])-1:
827 continue
828 else:
829 legRange=legRange[-1:]
830 for j in legRange:
831 if self['vertices'][i].get('legs')[j].same(currLeg):
832 vertPos=i
833 vertFoundID=self['vertices'][i]['id']
834 # If currLeg was just an integer from the first call to
835 # process_next_loop_leg, we can now change it to the Leg
836 # it really correspond to.
837 if isinstance(currLeg,int):
838 currLeg=base_objects.Leg(self['vertices'][i].get('legs')[j])
839
840 # We can now process this loop interaction found...
841 for k in filter(lambda ind: not ind==j, \
842 range(len(self['vertices'][i].get('legs')))):
843 # ..for the structure k
844 # pos gives the direction in which to look for
845 # nextLoopLeg from vertPos. It is after vertPos
846 # (i.e. then pos=-1) only when the next loop leg was
847 # found to be the output (i.e. so positioned last in
848 # the vertex leg list) of the vertex at vertPos. Note that
849 # for the last vertex in the list, all entries are input.
850 if not i==len(self['vertices'])-1 \
851 and k==len(self['vertices'][i].get('legs'))-1:
852 pos=-1
853 else:
854 pos=k
855
856 if self['vertices'][i].get('legs')[k].get('loop_line'):
857 if not nextLoopLeg:
858 nextLoopLeg=self['vertices'][i].get('legs')[k]
859 legPos=pos
860 else:
861 raise self.PhysicsObjectError, \
862 " An interaction has more than two loop legs."
863 else:
864 process_loop_interaction(i,j,k,pos)
865 # Now that we have found loop leg curr_leg, we can get out
866 # of the two searching loop.
867 break
868 if nextLoopLeg:
869 break
870
871 # To make sure we found the next loop vertex
872 if not nextLoopLeg:
873 # Returns False in case of a malformed diagram where it has been
874 # impossible to find the loop leg looked for.
875 return False
876
877 # The FDStructureIDList can be empty in case of an identity vertex.
878 # We need to skip the vertex construction and the tag actualization
879 # in that case
880 if FDStructureIDList and vertFoundID not in [0,-1]:
881 # We now have constructed all the FDStructures attached at this
882 # vertex of the loop and we have identified the two loop legs.
883 # So we can add the corresponding vertex to loopVertexList
884
885 # Create the list of legs from the FDStructures
886 myleglist=base_objects.LegList([copy.copy(\
887 structRep[FDindex]['binding_leg']) for FDindex in \
888 FDStructureIDList])
889
890
891 # Add The original loop leg we started from. We either take it
892 # from starting leg (at the first call of process_next_loop_leg)
893 # or from the output leg of the latest Leg we added to
894 # loopVertexList. Also, the tag is updated here using the same
895 # rule.
896 if loopVertexList:
897 self['tag'].append([copy.copy(\
898 loopVertexList[-1]['legs'][-1]),\
899 sorted(FDStructureIDList),vertFoundID])
900 myleglist.append(loopVertexList[-1]['legs'][-1])
901 else:
902 self['tag'].append([copy.copy(currLeg),\
903 sorted(FDStructureIDList),vertFoundID])
904 new_input_leg = copy.copy(currLeg)
905 if fromPos!=-1:
906 # In this case the currLeg is an *output* of the current
907 # loop vertex (the last loop vertex must have been a 2-point
908 # dummy one otherwise loopVertexList wouldn't be empty).
909 # To have this leg as an *input* of the loop vertex we are
910 # constructing with generate_loop_vertex, we must switch
911 # the id of the new_input_leg to its corresponding anti pdg.
912 new_input_leg.set('id',model.get_particle(
913 new_input_leg.get('id')).get_anti_pdg_code())
914 myleglist.append(new_input_leg)
915
916 # Now depending we reached the last loop vertex or not, we will
917 # create a current (with ref_dict_to1) or a wavefunction plus
918 # a trivial two-point amplitude with interaction id=-1 which
919 # plays the role of a conventional amplitude. This allow for
920 # having only wavefunctions in the loop and therefore compute
921 # the loop lorentz trace easily.
922 # WARNING: This is very important here that the endLeg has the
923 # maximal attribute 'number' among all other legs, because this
924 # guarantees that its number is NOT propagated and that as soon
925 # as we reach this number, we reached the EXTERNAL outter leg
926 # which set the end of the tagging algorithm.
927 loopVertexList.append(\
928 self.generate_loop_vertex(myleglist,model,n_initial,vertFoundID))
929 # check that the particle/anti-particle is set correctly
930
931 if nextLoopLeg.same(endLeg):
932 # Now we can add the corresponding 'fake' amplitude vertex
933 # with flagged id = -1
934 # If last vertex was dummy, then recover the original leg
935 if vertFoundID not in [0,-1]:
936 starting_Leg=copy.copy(myleglist[-1])
937 legid=model.get_particle(myleglist[-1]['id']).get_anti_pdg_code()
938 state=myleglist[-1].get('state')
939 else:
940 starting_Leg=copy.copy(currLeg)
941 legid=model.get_particle(currLeg['id']).get_anti_pdg_code()
942 state=currLeg.get('state')
943
944 loopVertexList.append(base_objects.Vertex(\
945 {'legs':base_objects.LegList([starting_Leg,\
946 base_objects.Leg({'number': endLeg,
947 'id': legid,
948 'state': state,
949 'loop_line': True})]),
950 'id':-1}))
951 # Returns true since we reached the end loop leg.
952 # Again, it is very important that this end loop leg has the
953 # maximal number (see comment above)
954 return True
955 else:
956 # This is where the recursion happens. We have not reached the
957 # end loop leg yet, so we iterate the procedure.
958 return self.process_next_loop_leg(structRep, vertPos, legPos, \
959 nextLoopLeg, endLeg, loopVertexList, model, external_legs)
960
961 - def synchronize_loop_vertices_with_tag(self,model,n_initial,struct_rep,
962 lcut_part_number,lcut_antipart_number):
963 """ Construct the loop vertices from the tag of the loop diagram."""
964
965 if not self['tag']:
966 return
967 # Easy access point to the interaction dictionary
968 ref_dict_to1 = model.get('ref_dict_to1')
969
970 # Create the container for the new vertices which create the loop flow
971 loopVertexList=base_objects.VertexList()
972 for i, t in enumerate(self['tag']):
973 # Tag elements are organized like this
974 # (Incoming_loop_leg,[Structures_ID_list],vertex_ID)
975 myleglist=base_objects.LegList([copy.copy(\
976 struct_rep[FDindex]['binding_leg']) for FDindex in t[1]])
977 if i==0:
978 starting_leg=copy.copy(t[0])
979 # Remember here that it is crucial to stick to one convention
980 # chosen here to be that the lcut leg 'start_number' always
981 # is a particle and the lcut leg 'end_number' always is the
982 # corresponding anti-particle. (if not self). This is to ensure
983 # a correct evaluation of the fermion number for amplitude.
984 # Also and alternatively, it would have been possible at this
985 # stage to have the end_number and starting_number set to the
986 # same value while assigning the delta in color and lorentz as
987 # the structure of this 2-point closing interaction.
988 # There would have been one such interaction per particle in the
989 # model so it would be natural to create this interaction when
990 # importing the model. This is a cleaner implementation which
991 # I will be setting up soon.
992 if model.get_particle(starting_leg['id']).get('is_part'):
993 starting_leg['number']=lcut_part_number
994 end_number=lcut_antipart_number
995 else:
996 starting_leg['number']=lcut_antipart_number
997 end_number=lcut_part_number
998 starting_leg['state']=True
999 else:
1000 starting_leg=loopVertexList[-1].get('legs')[-1]
1001 self['tag'][i][0]=starting_leg
1002 myleglist.append(starting_leg)
1003 loopVertexList.append(self.generate_loop_vertex(myleglist,
1004 model,n_initial,t[2]))
1005 # Now we can add the corresponding 'fake' amplitude vertex
1006 # with flagged id = -1
1007 first_leg=copy.copy(loopVertexList[-1].get('legs')[-1])
1008 sec_leg_id=model.get_particle(first_leg['id']).get_anti_pdg_code()
1009 second_leg=base_objects.Leg({'number': end_number,
1010 'id': sec_leg_id,
1011 'state': first_leg.get('state'),
1012 'loop_line': True})
1013 loopVertexList.append(base_objects.Vertex(\
1014 {'legs':base_objects.LegList([first_leg,second_leg]),
1015 'id':-1}))
1016
1017 self['type'] = abs(first_leg['id'])
1018 self['vertices'] = loopVertexList
1019
1021 """ Construct iteratively a Feynman Diagram structure attached to a Loop,
1022 given at each step a vertex and the position of the leg this function is
1023 called from. At the same time, it constructs a canonical representation
1024 of the structure which is a tuple with each element corresponding to
1025 a 2-tuple ((external_parent_legs),vertex_ID). The external parent legs
1026 tuple is ordered as growing and the construction of the canonical
1027 representation is such that the 2-tuples appear in a fixed order.
1028 This functions returns a tuple of 2-tuple like above for the vertex
1029 where currLeg was found or false if fails.
1030
1031 To illustrate this algorithm, we take a concrete example,
1032 the following structure:
1033
1034 4 5 6 7
1035 1 3 \/2 \/ <- Vertex ID, left=73 and right=99
1036 \ / | \ / <- Vertex ID, left=34 and right=42
1037 | |4 |
1038 1\ | /2
1039 \|/ <- Vertex ID=72
1040 |
1041 |1
1042
1043 For this structure with external legs (1,2,3,5,6,7) and current created
1044 1, the canonical tag will be
1045
1046 (((1,2,3,4,5,6,7),72),((1,3),34),((2,6,7),42),((6,7),99),((4,5),73))
1047 """
1048 nextLeg = None
1049 legPos=-2
1050 vertPos=-2
1051
1052 vertRange=range(len(self['vertices']))
1053
1054 # Say we are at the beginning of the structure reconstruction algorithm
1055 # of the structure above, with currLeg=1 so it was found in the vertex
1056 # ID=72 with legs (1,1,4,2). Then, this function will call itself on
1057 # the particles 1,4 and 2. Each of these calls will return a list of
1058 # 2-tuples or a simple integer being the leg ID for the case of an
1059 # external line, like leg 4 in our example.
1060 # So the two lists of 2-tuples returned will be put in the list
1061 # "reprBuffer". In fact the 2-tuple are nested in another 2-tuple with
1062 # the first element being the legID of the current vertex. This helps
1063 # the sorting of these 2-tuple in a growing order of their originating
1064 # legID. In this example, once the procedure is finished with vertex
1065 # ID=72, reprBuffer would be:
1066 # [(((1,3),34),),(((4,5),73),),(((2,6,7),42),((6,7),99))]
1067 # (Still needs to be sorted and later transformed to a tuple)
1068 # The 2-tuple corresponding to the mother vertex (so ID=72 in the
1069 # example) is constructed in vertBuffer (the parent lines list is
1070 # progressevely filled with the identified external particle of each
1071 # leg). and will be put in front of vertBuffer and then transformed to
1072 # a tuple to form the output of the function.
1073 vertBuffer=[]
1074
1075 # Each of the parent legs identified for this vertex are put in the
1076 # first element of a list called here parentBufer.
1077 # The second element stores the vertex ID where currLeg was found.
1078 parentBuffer=[[],0]
1079
1080 # If fromPos == -1 then the leg was an output of its vertex so we must
1081 # look for it in the vertices following fromVert. If the leg was an
1082 # input of its vertex then we must look for it in the vertices
1083 # preceding fromVert.
1084 if fromPos == -1:
1085 # If the last loop leg was the vertex output (i.e. last in the
1086 # vertex leg list) then we must look for it in the vertices
1087 # located after the one where it was found (i.e. from_vert).
1088 vertRange=vertRange[fromVert+1:]
1089 else:
1090 # If the last loop leg was in the vertex inputs (i.e. not last
1091 # in the vertex leg list) then we must look where it in the
1092 # vertices located before where it was found (i.e. from_vert)
1093 # starting from the clostest to the actual vertex
1094 # (hence the reverse())
1095 vertRange=vertRange[:fromVert]
1096 vertRange.reverse()
1097
1098 # The variable below serves two purposes:
1099 # 1) It labels the position of the particle in the vertex (-1 = output)
1100 # 2) If at the end equals to -2, then it means that the particle looked
1101 # for has not been found.
1102 pos=-2
1103
1104 # Helper function
1105 def process_leg(vertID, legID):
1106 """ Treats the leg equal to currLeg found in the place located by
1107 self['vertices'][vertID].get('legs')[legID]"""
1108
1109 # The id of the vertex where currLeg was found is stored in the
1110 # second element of parentBuffer.
1111 parentBuffer[1]=self['vertices'][vertID].get('id')
1112 # We can add this vertex to the FDStructure vertex list, in the
1113 # "right" order so that a virtual particle in the inputs of some
1114 # vertex appears always AFTER the vertex where this particle was the
1115 # output.
1116
1117 # Now we must continue the iterative procedure for each of the other
1118 # leg of the vertex found.
1119 legPos=-2
1120 for k in [ind for ind in \
1121 range(len(self['vertices'][vertID].get('legs'))) if ind!=legID]:
1122 # If we found currLeg in an identity vertex we directly skip it
1123 # for what regards the construction of the cannonical
1124 # representation.
1125 if not self['vertices'][vertID].get('id'):
1126 return self.construct_FDStructure(vertID, k,\
1127 self['vertices'][vertID].get('legs')[k], FDStruct)
1128
1129 if k==len(self['vertices'][vertID].get('legs'))-1 \
1130 and not vertID==len(self['vertices'])-1:
1131 legPos=-1
1132 else:
1133 legPos=k
1134 # We get here the structure of each branch of the actual vertex.
1135 branch=self.construct_FDStructure(i, legPos, \
1136 self['vertices'][vertID].get('legs')[k], FDStruct)
1137 if not branch:
1138 raise self.PhysicsObjectError, \
1139 "Failed to reconstruct a FDStructure."
1140 # That means that this branch was an external leg.
1141 if isinstance(branch,int):
1142 parentBuffer[0].append(branch)
1143 # If it is a list it means that the branch contains at least
1144 # one further vertex.
1145 elif isinstance(branch,tuple):
1146 parentBuffer[0]+=list(branch[0][0])
1147 vertBuffer.append(branch)
1148 else:
1149 raise self.PhysicsObjectError, \
1150 "Non-proper behavior of the construct_FDStructure function"
1151 return legPos
1152
1153 # == Beginning of the code ==
1154 # Look the vertices in vertRange if it can find the parents of currLeg
1155 # once it is found call the function below process_leg
1156 for i in vertRange:
1157 # We must look in the output of these vertices if the leg was
1158 # previously found as an input of its vertex. In case it was an
1159 # output of its vertices, then we must look in the inputs of
1160 # these vertices. Remember that the last vertex of the list has only
1161 # inputs.
1162 legRange=range(len(self['vertices'][i].get('legs')))
1163 if fromPos == -1:
1164 # In the last vertex of the list, all entries are input
1165 if not i==len(self['vertices'])-1:
1166 legRange=legRange[:-1]
1167 else:
1168 # If looking for an output, then skip the last vertex of the
1169 # list which only has inputs.
1170 if i==len(self['vertices'])-1:
1171 continue
1172 else:
1173 legRange=legRange[-1:]
1174
1175 # Breaking off a double nested loop using findVert. A neater way of
1176 # doing it would be to use exceptions.
1177 findVert=False
1178 # Now search over the leg range for currLeg
1179 for j in legRange:
1180 if self['vertices'][i].get('legs')[j].same(currLeg):
1181 # Now call the function to process the leg found.
1182 pos=process_leg(i,j)
1183 # Now that we have found the vertex with currLeg and treated
1184 # it, we must get out of the searching loop.
1185 findVert=True
1186 break;
1187 if findVert:
1188 break;
1189
1190 if(pos == -2):
1191 if(not fromPos == -1):
1192 # In this case, the leg has not been found. It is an external leg.
1193 FDStruct.get('external_legs').append(copy.copy(currLeg))
1194 return currLeg.get('number')
1195 else:
1196 raise self.PhysicsObjectError, \
1197 " A structure is malformed."
1198 else:
1199 # In this case a vertex with currLeg has been found and we must
1200 # return the list of tuple described above. First let's sort the
1201 # list so that the branches comes in a fixed order which is
1202 # irrelevant but not trivial here. First comes the branches
1203 # involving the smallest number of vertices. Among those who have
1204 # an equal number of vertices, those with the smallest ID for the
1205 # external legs come first.
1206 vertBuffer.sort()
1207 # Now flatten the list to have a list of tuple instead of a list
1208 # of tuple made of tuples. In the above example, this corresponds
1209 # to go from
1210 # [(((1,3),34),),(((4,5),73),),(((2,6,7),42),((6,7),99))]
1211 # to
1212 # [((1,3),34),((4,5),73),((2,6,7),42),((6,7),99)]
1213 vertBufferFlat=[]
1214 for t in vertBuffer:
1215 for u in t:
1216 vertBufferFlat.append(u)
1217
1218 # Sort the parent lines
1219 parentBuffer[0].sort()
1220 # Add the 2-tuple corresponding to the vertex where currLeg was found.
1221 vertBufferFlat.insert(0,(tuple(parentBuffer[0]),parentBuffer[1]))
1222 return tuple(vertBufferFlat)
1223
1224 # Helper function
1225
1227 """ Return the starting loop line of this diagram, i.e. lcut leg one."""
1228 for v in self['vertices']:
1229 for l in v['legs']:
1230 if l['loop_line']:
1231 return l
1232
1234 """ Return the finishing line of this diagram, i.e. lcut leg two.
1235 Notice that this function is only available when the loop diagram is
1236 constructed with the special two-point vertex with id -1. """
1237
1238 assert self['vertices'][-1].get('id')==-1, "Loop diagrams must finish "+\
1239 " with vertex with id '-1' for get_finishing_loop_line to be called"
1240
1241 return max(self['vertices'][-1].get('legs'), key=lambda l: l['number'])
1242
1244 """ Return a set with one occurence of each different PDG code of the
1245 particles running in the loop. By convention, the PDF of the particle,
1246 not the antiparticle, is stored in this list. Using the tag would be
1247 quicker, but we want this function to be available before tagging as
1248 well"""
1249 return set([abs(l['id']) for v in self['vertices'] for l in v['legs'] \
1250 if l['loop_line']])
1251
1253 """ Return a dictionary with one entry per type of order appearing in
1254 the interactions building the loop flow. The corresponding keys are the
1255 number of type this order appear in the diagram. """
1256
1257 loop_orders = {}
1258 for vertex in self['vertices']:
1259 # We do not count the identity vertex nor the vertices building the
1260 # external FDStructures (possibly left over if not synchronized with
1261 # the tag).
1262 if vertex['id'] not in [0,-1] and len([1 for leg \
1263 in vertex['legs'] if leg['loop_line']])==2:
1264 vertex_orders = model.get_interaction(vertex['id'])['orders']
1265 for order in vertex_orders.keys():
1266 if order in loop_orders.keys():
1267 loop_orders[order]+=vertex_orders[order]
1268 else:
1269 loop_orders[order]=vertex_orders[order]
1270 return loop_orders
1271
1272
1273 @classmethod
1275 """ Perform cyclic permutations on the tag given in parameter such that
1276 the structure with the lowest ID appears first."""
1277
1278 if not atag:
1279 return []
1280
1281 imin=-2
1282 minStructID=-2
1283 for i, part in enumerate(atag):
1284 if minStructID==-2 or min(part[1])<minStructID:
1285 minStructID=min(part[1])
1286 imin=i
1287
1288 atag=atag[imin:]+atag[:imin]
1289
1290 return atag
1291
1292 @classmethod
1294 """ Performs a mirror operation on A COPY of the tag and returns it. """
1295
1296 if not atag:
1297 return []
1298
1299 # Make a local copy (since we will act on the leg object of the tag)
1300 revTag=[(copy.deepcopy(elem[0]), copy.copy(elem[1]), \
1301 copy.copy(elem[2])) for elem in atag]
1302
1303 # reverse it
1304 revTag.reverse()
1305 # shift right all legs
1306 shiftBuff=revTag[-1]
1307 for i in range(len(revTag)-1):
1308 revTag[-(i+1)]=[revTag[-(i+2)][0],revTag[-(i+1)][1],revTag[-(i+1)][2]]
1309 revTag[0]=[shiftBuff[0],revTag[0][1],revTag[0][2]]
1310 # When reading the tag in the opposite direction, all particles will
1311 # appear as antiparticle and we need to flip their pdg in order to keep
1312 # the same convention.
1313 nonselfantipartlegs = [ elem[0] for elem in revTag if not \
1314 model.get('particle_dict')[elem[0].get('id')]['self_antipart'] ]
1315 for leg in nonselfantipartlegs:
1316 leg.set('id',\
1317 model.get('particle_dict')[leg.get('id')].get_anti_pdg_code())
1318
1319 return revTag
1320
1321
1322 # Helper functions for the user_filter in the loop diagram generation. They
1323 # are not used by any other part of MadLoop.
1324
1326 """ Returns the pdgs of the lines running in the loop while not
1327 differentiating the particles from the anti-particles """
1328
1329 return [abs(tag_elem[0].get('id')) for tag_elem in self['tag']]
1330
1332 """ Returns the pdgs of the lines directly branching off the loop."""
1333
1334 return [structs.get_struct(struct_ID).get('binding_leg').get('id') \
1335 for tag_elem in self['tag'] for struct_ID in tag_elem[1]]
1336
1337 #===============================================================================
1338 # LoopDiagram
1339 #===============================================================================
1340
1341 -class LoopUVCTDiagram(base_objects.Diagram):
1342 """ A special kind of LoopDiagram which does not contain a loop but only
1343 specifies all UV counter-term which factorize the the same given born
1344 and bringing in the same orders. UV mass renormalization does not belong to
1345 this class of counter-term for example, and it is added along with the R2
1346 interactions."""
1347
1349 """Default values for all properties"""
1350
1351 super(LoopUVCTDiagram,self).default_setup()
1352 # These attributes store the specifics of the UV counter-term
1353 # contribution of this diagram
1354 self['type']='UV'
1355 self['UVCT_orders']={}
1356 self['UVCT_couplings']=[]
1357
1359 """Filter for valid diagram property values."""
1360
1361 if name == 'UVCT_couplings':
1362 if not isinstance(value, list):
1363 raise self.PhysicsObjectError, \
1364 "%s is not a valid list" % str(value)
1365 else:
1366 for elem in value:
1367 if not isinstance(elem, str) and not isinstance(elem, int):
1368 raise self.PhysicsObjectError, \
1369 "%s is not a valid string" % str(value)
1370
1371 if name == 'UVCT_orders':
1372 if not isinstance(value, dict):
1373 raise self.PhysicsObjectError, \
1374 "%s is not a valid dictionary" % str(value)
1375
1376 if name == 'type':
1377 if not isinstance(value, str):
1378 raise self.PhysicsObjectError, \
1379 "%s is not a valid string" % str(value)
1380
1381 else:
1382 super(LoopUVCTDiagram, self).filter(name, value)
1383
1384 return True
1385
1387 """Return particle property names as a nicely sorted list."""
1388
1389 return ['vertices', 'UVCT_couplings', 'UVCT_orders', 'type', 'orders']
1390
1392 """ Finds the UV counter-term interaction present in this UVCTDiagram """
1393
1394 for vert in self['vertices']:
1395 if vert.get('id') != 0:
1396 if model.get_interaction(vert.get('id')).is_UV():
1397 return model.get_interaction(vert.get('id'))
1398
1399 return None
1400
1402 """Calculate the actual coupling orders of this diagram. Note
1403 that the special order WEIGTHED corresponds to the sum of
1404 hierarchies for the couplings."""
1405
1406 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')])
1407 weight = 0
1408 for couplings in [model.get('interaction_dict')[vertex.get('id')].\
1409 get('orders') for vertex in self['vertices'] if \
1410 vertex.get('id') != 0]+[self['UVCT_orders']]:
1411 for coupling in couplings:
1412 coupling_orders[coupling] += couplings[coupling]
1413 weight += sum([model.get('order_hierarchy')[c]*n for \
1414 (c,n) in couplings.items()])
1415 coupling_orders['WEIGHTED'] = weight
1416 self.set('orders', coupling_orders)
1417
1419 """Returns a nicely formatted string of the diagram content."""
1420 res=''
1421 if self['vertices']:
1422 res=res+super(LoopUVCTDiagram,self).nice_string()
1423 if self['UVCT_couplings']:
1424 res=res+'UV renorm. vertices: '
1425 res=res+','.join(str(vert) for vert in self['UVCT_couplings'])+'\n'
1426 if self['UVCT_orders']:
1427 res=res+'UVCT orders: '
1428 res=res+','.join(order for order in self['UVCT_orders'].keys())+'\n'
1429 if self['type']:
1430 res=res+'UVCT type: '+self['type']
1431
1432 return res
1433
1434 #===============================================================================
1435 # LoopModel
1436 #===============================================================================
1437 -class LoopModel(base_objects.Model):
1438 """A class to store all the model information with advanced feature
1439 to compute loop process."""
1440
1442 """Make sure to copy over the attribute map_CTcoup_CTparam if the
1443 first instance used is a LoopModel"""
1444
1445 if len(args)>0 and isinstance(args[0],LoopModel):
1446 if hasattr(args[0],'map_CTcoup_CTparam'):
1447 self.map_CTcoup_CTparam = copy.copy(args[0].map_CTcoup_CTparam)
1448
1449 super(LoopModel,self).__init__(*args,**opts)
1450
1452 super(LoopModel,self).default_setup()
1453 self['perturbation_couplings'] = []
1454 # The 'coupling_orders_counterterms' has all coupling orders
1455 # as keys and values are tuple of the form:
1456 # (loop_particles, counterterm, laurent_order)
1457 # where loop_particles are defined as usual:
1458 # [[lpartID1, lpartID2, ...], [lpartID1bis, lpartID2bis, ...],...]
1459 # and the counterterm is a string giving the name of the coupling
1460 # representing the counterterm and finally 'laurent_order' is to which
1461 # laurent order this counterterm contributes.
1462 self['coupling_orders_counterterms']={}
1463
1464 # This attribute is not registered as a key to this object's dictionary
1465 # because it is not a new physical attribute to the model.
1466 # It is the mapping between couplings (in values of the dict) and the
1467 # list of CTparameter names which enter in its expression (in the keys).
1468 if not hasattr(self,'map_CTcoup_CTparam'):
1469 self.map_CTcoup_CTparam = {}
1470
1471
1473 """Filter for model property values"""
1474
1475 if name == 'perturbation_couplings':
1476 if not isinstance(value, list):
1477 raise self.PhysicsObjectError, \
1478 "Object of type %s is not a list" % \
1479 type(value)
1480 for order in value:
1481 if not isinstance(order, str):
1482 raise self.PhysicsObjectError, \
1483 "Object of type %s is not a string" % \
1484 type(order)
1485 else:
1486 super(LoopModel,self).filter(name,value)
1487
1488 return True
1489
1491 """This function actualizes the dictionaries"""
1492
1493 if useUVCT:
1494 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1495 self['interactions'].generate_ref_dict(useR2UV=False,useUVCT=True)
1496 else:
1497 [self['ref_dict_to0'], self['ref_dict_to1']] = \
1498 self['interactions'].generate_ref_dict()
1499 self['ref_dict_to0'].update(
1500 self['particles'].generate_ref_dict())
1501
1507
1508 #===============================================================================
1509 # DGLoopLeg
1510 #===============================================================================
1511 -class DGLoopLeg(base_objects.Leg):
1512 """A class only used during the loop diagram generation. Exactly like leg
1513 except for a few other parameters only useful during the loop diagram
1514 generation."""
1515
1517 """ Allow for initializing a DGLoopLeg of a Leg """
1518 if not isinstance(argument, base_objects.Leg):
1519 if argument:
1520 super(DGLoopLeg,self).__init__(argument)
1521 else:
1522 super(DGLoopLeg,self).__init__()
1523 else:
1524 super(DGLoopLeg,self).__init__()
1525 for key in argument.get_sorted_keys():
1526 self.set(key,argument[key])
1527
1531
1533 """Filter for model property values"""
1534
1535 if name == 'depth':
1536 if not isinstance(value, int):
1537 raise self.PhysicsObjectError, \
1538 "Object of type %s is not a int" % \
1539 type(value)
1540 else:
1541 super(DGLoopLeg,self).filter(name,value)
1542
1543 return True
1544
1546 """Return process property names as a nicely sorted list."""
1547
1548 return ['id', 'number', 'state', 'from_group','loop_line','depth']
1549
1551 """ Converts a DGLoopLeg back to a Leg. Basically removes the extra
1552 attributes """
1553
1554 aleg=base_objects.Leg()
1555 for key in aleg.get_sorted_keys():
1556 aleg.set(key,self[key])
1557
1558 return aleg
1559
1560 #===============================================================================
1561 # FDStructure
1562 #===============================================================================
1563 -class FDStructure(base_objects.PhysicsObject):
1564 """FDStructure:
1565 list of vertices (ordered). This is part of a diagram.
1566 """
1567
1569 """Default values for all properties"""
1570
1571 self['vertices'] = base_objects.VertexList()
1572 self['id'] = -1
1573 self['external_legs'] = base_objects.LegList()
1574 self['canonical'] = ()
1575 self['binding_leg']= base_objects.Leg()
1576
1578 """Returns wether the structure is simply made of an external particle
1579 only"""
1580 if (len(self['canonical'])==1 and self['canonical'][0][1]==0):
1581 return True
1582 else:
1583 return False
1584
1586 """Filter for valid FDStructure property values."""
1587
1588 if name == 'vertices':
1589 if not isinstance(value, base_objects.VertexList):
1590 raise self.PhysicsObjectError, \
1591 "%s is not a valid VertexList object" % str(value)
1592
1593 if name == 'id':
1594 if not isinstance(value, int):
1595 raise self.PhysicsObjectError, \
1596 "id %s is not an integer" % repr(value)
1597
1598 if name == 'weight':
1599 if not isinstance(value, int):
1600 raise self.PhysicsObjectError, \
1601 "weight %s is not an integer" % repr(value)
1602
1603 if name == 'external_legs':
1604 if not isinstance(value, base_objects.LegList):
1605 raise self.PhysicsObjectError, \
1606 "external_legs %s is not a valid Leg List" % str(value)
1607
1608 if name == 'binding_leg':
1609 if not isinstance(value, base_objects.Leg):
1610 raise self.PhysicsObjectError, \
1611 "binding_leg %s is not a valid Leg" % str(value)
1612
1613 if name == 'canonical':
1614 if not isinstance(value, tuple):
1615 raise self.PhysicsObjectError, \
1616 "canonical %s is not a valid tuple" % str(value)
1617
1618 return True
1619
1621 """Return particle property names as a nicely sorted list."""
1622
1623 return ['id','external_legs','binding_leg','canonical','vertices']
1624
1626 """Returns a nicely formatted string of the structure content."""
1627
1628 mystr=''
1629
1630 if not self['id']==-1:
1631 mystr=mystr+'id: '+str(self['id'])+',\n'
1632 else:
1633 return '()'
1634
1635 if self['canonical']:
1636 mystr=mystr+'canonical_repr.: '+str(self['canonical'])+',\n'
1637
1638 if self['external_legs']:
1639 mystr=mystr+'external_legs: { '
1640 for leg in self['external_legs'][:-1]:
1641 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) \
1642 + ', '
1643 mystr = mystr + str(self['external_legs'][-1]['number']) + \
1644 '(%s)' % str(self['external_legs'][-1]['id']) + ' },\n'
1645 mystr = mystr+'binding_leg: '+str(self['binding_leg']['number']) +\
1646 '(%s)' % str(self['binding_leg']['id'])
1647 return mystr
1648
1650 """Returns a nicely formatted string of the structure vertices."""
1651 mystr=''
1652 if self['vertices']:
1653 mystr = mystr+'('
1654 for vert in self['vertices']:
1655 mystr = mystr + '('
1656 for leg in vert['legs'][:-1]:
1657 mystr = mystr + str(leg['number']) + \
1658 '(%s)' % str(leg['id']) + ','
1659 mystr = mystr[:-1] + '>'
1660 mystr = mystr + str(vert['legs'][-1]['number']) +\
1661 '(%s)' % str(vert['legs'][-1]['id']) + ','
1662 mystr = mystr + 'id:' + str(vert['id']) + '),'
1663 mystr = mystr[:-1] + ')'
1664 return mystr
1665 elif len(self['external_legs'])==1:
1666 return '('+str(self['external_legs'][0]['number'])+\
1667 '('+str(self['external_legs'][0]['id'])+'))'
1668 else:
1669 return '()'
1670
1671
1673 """ This functions generate the vertices building this structure,
1674 starting from the outter legs going towards the binding leg.
1675 It uses the interactions dictionaries from the model. """
1676
1677 if isinstance(model, base_objects.Process):
1678 assert external_legs is None
1679 #retro-compatible way to call the function
1680 external_legs= model.get('legs')
1681 model = model['model']
1682 assert external_legs is not None
1683 assert isinstance(model, base_objects.Model)
1684
1685
1686
1687 # First empty the existing vertices
1688 self.set('vertices',base_objects.VertexList())
1689
1690 tag=copy.copy(self['canonical'])
1691
1692 # Define easy access points
1693 ref_dict_to1 = model.get('ref_dict_to1')
1694
1695 if not tag:
1696 raise self.PhysicsObjectError, \
1697 "The canonical tag of the FD structure is not set yet, so that the "+\
1698 "reconstruction of the vertices cannot be performed."
1699
1700 # Create a local copy of the external legs
1701 leglist = copy.deepcopy(external_legs)
1702
1703 # Create a dictionary to get an easy access to a given particle number
1704 legDict={}
1705 for leg in leglist:
1706 legDict[leg['number']]=leg
1707
1708 # If this structure is directly an external leg, then there is no vertex
1709 # to add
1710 if len(tag)==1 and len(tag[0][0])==1:
1711 # But we should still define the binding leg
1712 self['binding_leg']=copy.deepcopy(legDict[tag[0][0][0]])
1713 return
1714
1715 # Reverse the tag to start from the outter legs
1716 tag=list(tag)
1717 tag.reverse()
1718
1719 # Change the tuples to lists and convert the particle numbers to their
1720 # corresponding LegList object
1721 for i, tagelem in enumerate(tag):
1722 tag[i]=list(tagelem)
1723 tag[i][0]=base_objects.LegList([legDict[myleg] for myleg in \
1724 tagelem[0]])
1725
1726 # For each element of the tag, combine them with the appropriate vertex
1727 # ID, create and add the corresponding vertex to the structure's vertex
1728 # list, remove this element of the tag and substitutes the leg number
1729 # in all other tag's elements by the new leg number created.
1730 while tag:
1731 # First get an easy access to the LegList of the first tag element
1732 # we aim at treating.
1733 legs=tag[0][0]
1734
1735 # Now we make sure we can combine those legs together
1736 key=tuple(sorted([leg.get('id') for leg in legs]))
1737 if ref_dict_to1.has_key(key):
1738 for interaction in ref_dict_to1[key]:
1739 # Find the interaction with the right ID
1740 if interaction[1]==tag[0][1]:
1741 # Create the output Leg and add it to the existing list
1742 # 1) id is like defined by ref_dict_to1
1743 legid = interaction[0]
1744 # 2) number is the minimum of leg numbers involved in the
1745 # combination
1746 number = min([leg.get('number') for leg in legs])
1747 # 3) state is final, unless there is exactly one initial
1748 # state particle involved in the combination -> t-channel
1749 if len(filter(lambda leg: leg.get('state') == False,
1750 legs)) == 1:
1751 state = False
1752 else:
1753 state = True
1754 legs.append(base_objects.Leg({'number': number,\
1755 'id': legid,\
1756 'state': state,
1757 'loop_line': False}))
1758 # Now we can add the corresponding vertex
1759 self.get('vertices').append(base_objects.Vertex(\
1760 {'legs':legs,'id':interaction[1]}))
1761 break
1762
1763 # In all further elements, we should replace any combination of
1764 # the legs just merged here by the new output leg we just created.
1765 for i, tagelement in enumerate(tag[1:]):
1766 Found=False
1767 for leg in legs[:-1]:
1768 try:
1769 tag[i+1][0].remove(leg)
1770 Found=True
1771 except Exception:
1772 pass
1773 if Found:
1774 tag[i+1][0].append(legs[-1])
1775
1776 # If we are about to empty the tag we must now set the
1777 # binding_leg as the last one we produced.
1778 if len(tag)==1:
1779 self['binding_leg']=copy.deepcopy(legs[-1])
1780
1781 # Now we should remove this first element of the tag that we
1782 # just treated
1783 tag.pop(0)
1784
1785 else:
1786 raise self.PhysicsObjectError, \
1787 "The canonical tag of the FD structure is corrupted because one "+\
1788 "interaction does not exist."
1789
1790 #===============================================================================
1791 # FDStructureList
1792 #===============================================================================
1793 -class FDStructureList(base_objects.PhysicsObjectList):
1794 """List of FDStructure objects
1795 """
1796
1798 """Test if object obj is a valid Diagram for the list."""
1799
1800 return isinstance(obj, FDStructure)
1801
1803 """Return the FDStructure of the list with the corresponding canonical
1804 tag if ID is a tuple or the corresponding ID if ID is an integer.
1805 It returns the structure if it founds it, or None if it was not found"""
1806 if isinstance(ID, int):
1807 for FDStruct in self:
1808 if FDStruct.get('id')==ID:
1809 return FDStruct
1810 return None
1811 elif isinstance(ID, tuple):
1812 for FDStruct in self:
1813 if FDStruct.get('canonical')==ID:
1814 return FDStruct
1815 return None
1816 else:
1817 raise self.PhysicsObjectListError, \
1818 "The ID %s specified for get_struct is not an integer or tuple"%\
1819 repr(object)
1820
1822 """Returns a nicely formatted string"""
1823 mystr = str(len(self)) + ' FD Structures:\n'
1824 for struct in self:
1825 mystr = mystr + " " + struct.nice_string() + '\n'
1826 return mystr[:-1]
1827
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Thu Aug 17 00:27:24 2017 | http://epydoc.sourceforge.net |