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