Package madgraph :: Package loop :: Module loop_base_objects
[hide private]
[frames] | no frames]

Source Code for Module madgraph.loop.loop_base_objects

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