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  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
50 - def default_setup(self):
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
86 - def filter(self, name, value):
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
139 - def get_sorted_keys(self):
140 """Return particle property names as a nicely sorted list.""" 141 142 return ['vertices', 'CT_vertices', 'orders', 'type', 'tag']
143
144 - def nice_string(self, struct_list=None ):
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
201 - def get_contracted_loop_diagram_without_tag(self, struct_rep=None):
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
369 - def get_contracted_loop_diagram(self, model, struct_rep=None):
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
436 - def get_CT(self,model,string=None):
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
446 - def is_fermion_loop(self, model):
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
459 - def is_tadpole(self):
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
471 - def is_vanishing_tadpole(self,model):
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
487 - def is_wf_correction(self, struct_rep, model):
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
514 - def get_nloopline(self):
515 """Return the number of loop lines. """ 516 if self['tag']: 517 return len(self['tag']) 518 else: 519 return None
520 521 @classmethod
522 - def compute_weight(cls, FD_ids_list, struct_rep, number_legs):
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
536 - def choose_optimal_lcut(cls,intag,struct_rep, model, external_legs):
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
573 - def choose_default_lcut(cls,tag, model):
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
609 - def tag(self, struct_rep, model, start_in=None, end_in=None, synchronize=True):
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
683 - def generate_loop_vertex(cls,myleglist, model, n_initial, vertID):
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
1020 - def construct_FDStructure(self, fromVert, fromPos, currLeg, FDStruct):
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
1226 - def get_starting_loop_line(self):
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
1233 - def get_finishing_loop_line(self):
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
1243 - def get_loop_line_types(self):
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
1252 - def get_loop_orders(self,model):
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
1274 - def make_canonical_cyclic(cls,atag):
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
1293 - def mirrored_tag(cls,atag, model):
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
1325 - def get_loop_lines_pdgs(self):
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
1331 - def get_pdgs_attached_to_loop(self,structs):
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
1348 - def default_setup(self):
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
1358 - def filter(self, name, value):
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
1386 - def get_sorted_keys(self):
1387 """Return particle property names as a nicely sorted list.""" 1388 1389 return ['vertices', 'UVCT_couplings', 'UVCT_orders', 'type', 'orders']
1390
1391 - def get_UVCTinteraction(self, model):
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
1401 - def calculate_orders(self, model):
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
1418 - def nice_string(self):
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
1441 - def __init__(self,*args,**opts):
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
1451 - def default_setup(self):
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
1472 - def filter(self, name, value):
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
1490 - def actualize_dictionaries(self, useUVCT=False):
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
1502 - def get_sorted_keys(self):
1503 """Return process property names as a nicely sorted list.""" 1504 1505 return ['name', 'particles', 'parameters', 'interactions', 'couplings', 1506 'lorentz','perturbation_couplings','conserved_charge']
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
1516 - def __init__(self,argument=None):
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
1528 - def default_setup(self):
1529 super(DGLoopLeg,self).default_setup() 1530 self['depth'] = 0
1531
1532 - def filter(self, name, value):
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
1545 - def get_sorted_keys(self):
1546 """Return process property names as a nicely sorted list.""" 1547 1548 return ['id', 'number', 'state', 'from_group','loop_line','depth']
1549
1550 - def convert_to_leg(self):
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
1568 - def default_setup(self):
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
1577 - def is_external(self):
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
1585 - def filter(self, name, value):
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
1620 - def get_sorted_keys(self):
1621 """Return particle property names as a nicely sorted list.""" 1622 1623 return ['id','external_legs','binding_leg','canonical','vertices']
1624
1625 - def nice_string(self):
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
1649 - def nice_string_vertices(self):
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
1672 - def generate_vertices(self, model, external_legs=None):
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
1797 - def is_valid_element(self, obj):
1798 """Test if object obj is a valid Diagram for the list.""" 1799 1800 return isinstance(obj, FDStructure)
1801
1802 - def get_struct(self, ID):
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
1821 - def nice_string(self):
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