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

Source Code for Module madgraph.loop.loop_diagram_generation

   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  """Classes for diagram generation with loop features. 
  16  """ 
  17   
  18  import array 
  19  import copy 
  20  import itertools 
  21  import logging 
  22   
  23  import madgraph.loop.loop_base_objects as loop_base_objects 
  24  import madgraph.core.base_objects as base_objects 
  25  import madgraph.core.diagram_generation as diagram_generation 
  26  import madgraph.various.misc as misc 
  27   
  28  from madgraph import MadGraph5Error 
  29  from madgraph import InvalidCmd 
  30  logger = logging.getLogger('madgraph.loop_diagram_generation') 
31 32 -def ldg_debug_info(msg,val, force=False):
33 # This subroutine has typically quite large DEBUG info. 34 # So even in debug mode, they are turned off by default. 35 # Remove the line below for loop diagram generation diagnostic 36 if not force: return 37 38 flag = "LoopGenInfo: " 39 if len(msg)>40: 40 logger.debug(flag+msg[:35]+" [...] = %s"%str(val)) 41 else: 42 logger.debug(flag+msg+''.join([' ']*(40-len(msg)))+' = %s'%str(val))
43
44 #=============================================================================== 45 # LoopAmplitude 46 #=============================================================================== 47 -class LoopAmplitude(diagram_generation.Amplitude):
48 """NLOAmplitude: process + list of diagrams (ordered) 49 Initialize with a process, then call generate_diagrams() to 50 generate the diagrams for the amplitude 51 """ 52
53 - def default_setup(self):
54 """Default values for all properties""" 55 56 # The 'diagrams' entry from the mother class is inherited but will not 57 # be used in NLOAmplitude, because it is split into the four following 58 # different categories of diagrams. 59 super(LoopAmplitude, self).default_setup() 60 self['born_diagrams'] = None 61 self['loop_diagrams'] = None 62 self['loop_UVCT_diagrams'] = base_objects.DiagramList() 63 # This is in principle equal to self['born_diagram']==[] but it can be 64 # that for some reason the born diagram can be generated but do not 65 # contribute. 66 # This will decide wether the virtual is squared against the born or 67 # itself. 68 self['has_born'] = True 69 # This where the structures obtained for this amplitudes are stored 70 self['structure_repository'] = loop_base_objects.FDStructureList() 71 72 # A list that registers what Lcut particle have already been 73 # employed in order to forbid them as loop particles in the 74 # subsequent diagram generation runs. 75 self.lcutpartemployed=[]
76
77 - def __init__(self, argument=None, loop_filter=None):
78 """Allow initialization with Process. 79 If loop_filter is not None, then it will be applied to all subsequent 80 diagram generation from this LoopAmplitude.""" 81 82 self.loop_filter = loop_filter 83 84 if isinstance(argument, base_objects.Process): 85 super(LoopAmplitude, self).__init__() 86 self.set('process', argument) 87 self.generate_diagrams() 88 elif argument != None: 89 # call the mother routine 90 super(LoopAmplitude, self).__init__(argument) 91 else: 92 # call the mother routine 93 super(LoopAmplitude, self).__init__()
94
95 - def get_sorted_keys(self):
96 """Return diagram property names as a nicely sorted list.""" 97 98 return ['process', 'diagrams', 'has_mirror_process', 'born_diagrams', 99 'loop_diagrams','has_born', 100 'structure_repository']
101
102 - def filter(self, name, value):
103 """Filter for valid amplitude property values.""" 104 105 if name == 'diagrams': 106 if not isinstance(value, base_objects.DiagramList): 107 raise self.PhysicsObjectError, \ 108 "%s is not a valid DiagramList" % str(value) 109 for diag in value: 110 if not isinstance(diag,loop_base_objects.LoopDiagram) and \ 111 not isinstance(diag,loop_base_objects.LoopUVCTDiagram): 112 raise self.PhysicsObjectError, \ 113 "%s contains a diagram which is not an NLODiagrams." % str(value) 114 if name == 'born_diagrams': 115 if not isinstance(value, base_objects.DiagramList): 116 raise self.PhysicsObjectError, \ 117 "%s is not a valid DiagramList" % str(value) 118 for diag in value: 119 if not isinstance(diag,loop_base_objects.LoopDiagram): 120 raise self.PhysicsObjectError, \ 121 "%s contains a diagram which is not an NLODiagrams." % str(value) 122 if name == 'loop_diagrams': 123 if not isinstance(value, base_objects.DiagramList): 124 raise self.PhysicsObjectError, \ 125 "%s is not a valid DiagramList" % str(value) 126 for diag in value: 127 if not isinstance(diag,loop_base_objects.LoopDiagram): 128 raise self.PhysicsObjectError, \ 129 "%s contains a diagram which is not an NLODiagrams." % str(value) 130 if name == 'has_born': 131 if not isinstance(value, bool): 132 raise self.PhysicsObjectError, \ 133 "%s is not a valid bool" % str(value) 134 if name == 'structure_repository': 135 if not isinstance(value, loop_base_objects.FDStructureList): 136 raise self.PhysicsObjectError, \ 137 "%s is not a valid bool" % str(value) 138 139 else: 140 super(LoopAmplitude, self).filter(name, value) 141 142 return True
143
144 - def set(self, name, value):
145 """Redefine set for the particular case of diagrams""" 146 147 if name == 'diagrams': 148 if self.filter(name, value): 149 self['born_diagrams']=base_objects.DiagramList([diag for diag in value if \ 150 not isinstance(diag,loop_base_objects.LoopUVCTDiagram) and diag['type']==0]) 151 self['loop_diagrams']=base_objects.DiagramList([diag for diag in value if \ 152 not isinstance(diag,loop_base_objects.LoopUVCTDiagram) and diag['type']!=0]) 153 self['loop_UVCT_diagrams']=base_objects.DiagramList([diag for diag in value if \ 154 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]) 155 156 else: 157 return super(LoopAmplitude, self).set(name, value) 158 159 return True
160
161 - def get(self, name):
162 """Redefine get for the particular case of '*_diagrams' property""" 163 164 if name == 'diagrams': 165 if self['process'] and self['loop_diagrams'] == None: 166 self.generate_diagrams() 167 return base_objects.DiagramList(self['born_diagrams']+\ 168 self['loop_diagrams']+\ 169 self['loop_UVCT_diagrams']) 170 171 if name == 'born_diagrams': 172 if self['born_diagrams'] == None: 173 # Have not yet generated born diagrams for this process 174 if self['process']['has_born']: 175 if self['process']: 176 self.generate_born_diagrams() 177 else: 178 self['born_diagrams']=base_objects.DiagramList() 179 180 return LoopAmplitude.__bases__[0].get(self, name) #return the mother routine
181 182 # Functions of the different tasks performed in generate_diagram
183 - def choose_order_config(self):
184 """ Choose the configuration of non-perturbed coupling orders to be 185 retained for all diagrams. This is used when the user did not specify 186 any order. """ 187 chosen_order_config = {} 188 min_wgt = self['born_diagrams'].get_min_order('WEIGHTED') 189 # Scan the born diagrams of minimum weight to chose a configuration 190 # of non-perturbed orders. 191 min_non_pert_order_wgt = -1 192 for diag in [d for d in self['born_diagrams'] if \ 193 d.get_order('WEIGHTED')==min_wgt]: 194 non_pert_order_wgt = min_wgt - sum([diag.get_order(order)*\ 195 self['process']['model']['order_hierarchy'][order] for order in \ 196 self['process']['perturbation_couplings']]) 197 if min_non_pert_order_wgt == -1 or \ 198 non_pert_order_wgt<min_non_pert_order_wgt: 199 chosen_order_config = self.get_non_pert_order_config(diag) 200 logger.info("Chosen coupling orders configuration: (%s)"\ 201 %self.print_config(chosen_order_config)) 202 return chosen_order_config
203
205 """If squared orders (other than WEIGHTED) are defined, then they can be 206 used for determining what is the expected upper bound for the order 207 restricting loop diagram generation.""" 208 for order, value in self['process']['squared_orders'].items(): 209 if order.upper()!='WEIGHTED' and order not in self['process']['orders']: 210 # If the bound is of type '>' we cannot say anything 211 if self['process'].get('sqorders_types')[order]=='>': 212 continue 213 # If there is no born, the min order will simply be 0 as it should. 214 bornminorder=self['born_diagrams'].get_min_order(order) 215 if value>=0: 216 self['process']['orders'][order]=value-bornminorder 217 elif self['process']['has_born']: 218 # This means the user want the leading if order=-1 or N^n 219 # Leading term if order=-n. If there is a born diag, we can 220 # infer the necessary maximum order in the loop: 221 # bornminorder+2*(n-1). 222 # If there is no born diag, then we cannot say anything. 223 self['process']['orders'][order]=bornminorder+2*(-value-1)
224
225 - def guess_loop_orders(self, user_orders):
226 """Guess the upper bound for the orders for loop diagram generation 227 based on either no squared orders or simply 'Weighted'""" 228 229 hierarchy = self['process']['model']['order_hierarchy'] 230 231 # Maximum of the hierarchy weigtht among all perturbed order 232 max_pert_wgt = max([hierarchy[order] for order in \ 233 self['process']['perturbation_couplings']]) 234 235 # In order to be sure to catch the corrections to all born diagrams that 236 # the user explicitly asked for with the amplitude orders, we take here 237 # the minimum weighted order as being the maximum between the min weighted 238 # order detected in the Born diagrams and the weight computed from the 239 # user input amplitude orders. 240 user_min_wgt = 0 241 242 # One can chose between the two behaviors below. It is debatable which 243 # one is best. The first one tries to only consider the loop which are 244 # dominant, even when the user selects the amplitude orders and the 245 # second chosen here makes sure that the user gets a correction of the 246 # desired type for all the born diagrams generated with its amplitude 247 # order specification. 248 # min_born_wgt=self['born_diagrams'].get_min_order('WEIGHTED') 249 min_born_wgt=max(self['born_diagrams'].get_min_order('WEIGHTED'), 250 sum([hierarchy[order]*val for order, val in user_orders.items() \ 251 if order!='WEIGHTED'])) 252 253 if 'WEIGHTED' not in [key.upper() for key in \ 254 self['process']['squared_orders'].keys()]: 255 # Then we guess it from the born 256 self['process']['squared_orders']['WEIGHTED']= 2*(min_born_wgt+\ 257 max_pert_wgt) 258 259 # Now we know that the remaining weighted orders which can fit in 260 # the loop diagram is (self['target_weighted_order']- 261 # min_born_weighted_order) so for each perturbed order we just have to 262 # take that number divided by its hierarchy weight to have the maximum 263 # allowed order for the loop diagram generation. Of course, 264 # we don't overwrite any order already defined by the user. 265 if self['process']['squared_orders']['WEIGHTED']>=0: 266 trgt_wgt=self['process']['squared_orders']['WEIGHTED']-min_born_wgt 267 else: 268 trgt_wgt=min_born_wgt+(-self['process']['squared_orders']['WEIGHTED']+1)*2 269 # We also need the minimum number of vertices in the born. 270 min_nvert=min([len([1 for vert in diag['vertices'] if vert['id']!=0]) \ 271 for diag in self['born_diagrams']]) 272 # And the minimum weight for the ordered declared as perturbed 273 min_pert=min([hierarchy[order] for order in \ 274 self['process']['perturbation_couplings']]) 275 276 for order, value in hierarchy.items(): 277 if order not in self['process']['orders']: 278 # The four cases below come from a study of the maximal order 279 # needed in the loop for the weighted order needed and the 280 # number of vertices available. 281 if order in self['process']['perturbation_couplings']: 282 if value!=1: 283 self['process']['orders'][order]=\ 284 int((trgt_wgt-min_nvert-2)/(value-1)) 285 else: 286 self['process']['orders'][order]=int(trgt_wgt) 287 else: 288 if value!=1: 289 self['process']['orders'][order]=\ 290 int((trgt_wgt-min_nvert-2*min_pert)/(value-1)) 291 else: 292 self['process']['orders'][order]=\ 293 int(trgt_wgt-2*min_pert) 294 # Now for the remaining orders for which the user has not set squared 295 # orders neither amplitude orders, we use the max order encountered in 296 # the born (and add 2 if this is a perturbed order). 297 # It might be that this upper bound is better than the one guessed 298 # from the hierarchy. 299 for order in self['process']['model']['coupling_orders']: 300 neworder=self['born_diagrams'].get_max_order(order) 301 if order in self['process']['perturbation_couplings']: 302 neworder+=2 303 if order not in self['process']['orders'].keys() or \ 304 neworder<self['process']['orders'][order]: 305 self['process']['orders'][order]=neworder
306
307 - def filter_from_order_config(self, diags, config, discarded_configurations):
308 """ Filter diags to select only the diagram with the non perturbed orders 309 configuration config and update discarded_configurations.Diags is the 310 name of the key attribute of this class containing the diagrams to 311 filter.""" 312 newdiagselection = base_objects.DiagramList() 313 for diag in self[diags]: 314 diag_config = self.get_non_pert_order_config(diag) 315 if diag_config == config: 316 newdiagselection.append(diag) 317 elif diag_config not in discarded_configurations: 318 discarded_configurations.append(diag_config) 319 self[diags] = newdiagselection
320
321 - def remove_Furry_loops(self, model, structs):
322 """ Remove the loops which are zero because of Furry theorem. So as to 323 limit any possible mistake in case of BSM model, I limit myself here to 324 removing SM-quark loops with external legs with an odd number of photons, 325 possibly including exactly two gluons.""" 326 327 new_diag_selection = base_objects.DiagramList() 328 329 n_discarded = 0 330 for diag in self['loop_diagrams']: 331 if diag.get('tag')==[]: 332 raise MadGraph5Error, "The loop diagrams should have been tagged"+\ 333 " before going through the Furry filter." 334 335 loop_line_pdgs = diag.get_loop_lines_pdgs() 336 attached_pdgs = diag.get_pdgs_attached_to_loop(structs) 337 if (attached_pdgs.count(22)%2==1) and \ 338 (attached_pdgs.count(21) in [0,2]) and \ 339 (all(pdg in [22,21] for pdg in attached_pdgs)) and \ 340 (abs(loop_line_pdgs[0]) in list(range(1,7))) and \ 341 (all(abs(pdg)==abs(loop_line_pdgs[0]) for pdg in loop_line_pdgs)): 342 n_discarded += 1 343 else: 344 new_diag_selection.append(diag) 345 346 self['loop_diagrams'] = new_diag_selection 347 348 if n_discarded > 0: 349 logger.debug(("MadLoop discarded %i diagram%s because they appeared"+\ 350 " to be zero because of Furry theorem.")%(n_discarded,'' if \ 351 n_discarded<=1 else 's'))
352 353 @staticmethod
354 - def get_loop_filter(filterdef):
355 """ Returns a function which applies the filter corresponding to the 356 conditional expression encoded in filterdef.""" 357 358 def filter(diag, structs, model, id): 359 """ The filter function generated '%s'."""%filterdef 360 361 loop_pdgs = diag.get_loop_lines_pdgs() 362 struct_pdgs = diag.get_pdgs_attached_to_loop(structs) 363 loop_masses = [model.get_particle(pdg).get('mass') for pdg in loop_pdgs] 364 struct_masses = [model.get_particle(pdg).get('mass') for pdg in struct_pdgs] 365 if not eval(filterdef.lower(),{'n':len(loop_pdgs), 366 'loop_pdgs':loop_pdgs, 367 'struct_pdgs':struct_pdgs, 368 'loop_masses':loop_masses, 369 'struct_masses':struct_masses, 370 'id':id}): 371 return False 372 else: 373 return True
374 375 return filter
376
377 - def user_filter(self, model, structs, filter=None):
378 """ User-defined user-filter. By default it is not called, but the expert 379 user can turn it on and code here is own filter. Some default examples 380 are provided here. 381 The tagging of the loop diagrams must be performed before using this 382 user loop filter""" 383 384 # By default the user filter does nothing if filter is not set, 385 # if you want to turn it on and edit it by hand, then set the 386 # variable edit_filter_manually to True 387 edit_filter_manually = False 388 if not edit_filter_manually and filter in [None,'None']: 389 return 390 if isinstance(filter,str) and filter.lower() == 'true': 391 edit_filter_manually = True 392 filter=None 393 394 395 if filter not in [None,'None']: 396 filter_func = LoopAmplitude.get_loop_filter(filter) 397 else: 398 filter_func = None 399 400 new_diag_selection = base_objects.DiagramList() 401 discarded_diags = base_objects.DiagramList() 402 i=0 403 for diag in self['loop_diagrams']: 404 if diag.get('tag')==[]: 405 raise MadGraph5Error, "Before using the user_filter, please "+\ 406 "make sure that the loop diagrams have been tagged first." 407 valid_diag = True 408 i=i+1 409 410 # Apply the custom filter specified if any 411 if filter_func: 412 try: 413 valid_diag = filter_func(diag, structs, model, i) 414 except Exception as e: 415 raise InvalidCmd("The user-defined filter '%s' did not"%filter+ 416 " returned the following error:\n > %s"%str(e)) 417 418 # if any([abs(pdg) not in range(1,7) for pdg in diag.get_loop_lines_pdgs()]): 419 # valid_diag = False 420 421 # if any([abs(i)!=1000021 for i in diag.get_loop_lines_pdgs()]): 422 # valid_diag=False 423 # if len(diag.get_loop_lines_pdgs())<4: 424 # valid_diag = False 425 426 # connected_id = diag.get_pdgs_attached_to_loop(structs) 427 # if connected_id.count(22)!=2 or not all(abs(pdg) in range(7) for pdg in diag.get_loop_lines_pdgs()): 428 # valid_diag=False 429 430 # Ex. 0: Chose a specific diagram number, here the 8th one for ex. 431 # if i not in [31]: 432 # valid_diag = False 433 434 # Ex. 0: Keeps only the top quark loops. 435 # if any([pdg not in [6,-6] for pdg in diag.get_loop_lines_pdgs()]): 436 # valid_diag = False 437 438 # Ex. 1: Chose the topology, i.e. number of loop line. 439 # Notice that here particles and antiparticles are not 440 # differentiated and always the particle PDG is returned. 441 # In this example, only boxes are selected. 442 # if len(diag.get_loop_lines_pdgs())>2 and \ 443 # any([i in diag.get_loop_lines_pdgs() for i in[24,-24,23]]): 444 # valid_diag=False 445 446 # Ex. 2: Use the pdgs of the particles directly attached to the loop. 447 # In this example, we forbid the Z to branch off the loop. 448 # connected_id = diag.get_pdgs_attached_to_loop(structs) 449 # if 22 not in connected_id: 450 # valid_diag=False 451 452 # Ex. 3: Filter based on the mass of the particles running in the 453 # loop. It shows how to access the particles properties from 454 # the PDG. 455 # In this example, only massive parts. are allowed in the loop. 456 # if 'ZERO' in [model.get_particle(pdg).get('mass') for pdg in \ 457 # diag.get_loop_lines_pdgs()]: 458 # valid_diag=False 459 460 # Ex. 4: Complicated filter which gets rid of all bubble diagrams made 461 # of two vertices being the four gluon vertex and the effective 462 # glu-glu-Higgs vertex. 463 # if len(diag.get_loop_lines_pdgs())==2: 464 # bubble_lines_pdgs=[abs(diag.get('canonical_tag')[0][0]), 465 # abs(diag.get('canonical_tag')[0][0])] 466 # first_vertex_pdgs=bubble_lines_pdgs+\ 467 # [abs(structs.get_struct(struct_ID).get('binding_leg').get('id')) \ 468 # for struct_ID in diag.get('canonical_tag')[0][1]] 469 # second_vertex_pdgs=bubble_lines_pdgs+\ 470 # [abs(structs.get_struct(struct_ID).get('binding_leg').get('id')) \ 471 # for struct_ID in diag.get('canonical_tag')[1][1]] 472 # first_vertex_pdgs.sort() 473 # second_vertex_pdgs.sort() 474 # bubble_vertices=[first_vertex_pdgs,second_vertex_pdgs] 475 # bubble_vertices.sort() 476 # if bubble_vertices==[[21,21,21,21],[21,21,25]]: 477 # valid_diag=False 478 479 # If you need any more advanced function for your filter and cannot 480 # figure out how to implement them. Just contact the authors. 481 482 if valid_diag: 483 new_diag_selection.append(diag) 484 else: 485 discarded_diags.append(diag) 486 487 self['loop_diagrams'] = new_diag_selection 488 if filter in [None,'None']: 489 warn_msg = """ 490 The user-defined loop diagrams filter is turned on and discarded %d loops."""\ 491 %len(discarded_diags) 492 else: 493 warn_msg = """ 494 The loop diagrams filter '%s' is turned on and discarded %d loops."""\ 495 %(filter,len(discarded_diags)) 496 logger.warning(warn_msg)
497
498 - def filter_loop_for_perturbative_orders(self):
499 """ Filter the loop diagrams to make sure they belong to the class 500 of coupling orders perturbed. """ 501 502 # First define what are the set of particles allowed to run in the loop. 503 allowedpart=[] 504 for part in self['process']['model']['particles']: 505 for order in self['process']['perturbation_couplings']: 506 if part.is_perturbating(order,self['process']['model']): 507 allowedpart.append(part.get_pdg_code()) 508 break 509 510 newloopselection=base_objects.DiagramList() 511 warned=False 512 warning_msg = ("Some loop diagrams contributing to this process"+\ 513 " are discarded because they are not pure (%s)-perturbation.\nMake sure"+\ 514 " you did not want to include them.")%\ 515 ('+'.join(self['process']['perturbation_couplings'])) 516 for i,diag in enumerate(self['loop_diagrams']): 517 # Now collect what are the coupling orders building the loop which 518 # are also perturbed order. 519 loop_orders=diag.get_loop_orders(self['process']['model']) 520 pert_loop_order=set(loop_orders.keys()).intersection(\ 521 set(self['process']['perturbation_couplings'])) 522 # Then make sure that the particle running in the loop for all 523 # diagrams belong to the set above. Also make sure that there is at 524 # least one coupling order building the loop which is in the list 525 # of the perturbed order. 526 valid_diag=True 527 if (diag.get_loop_line_types()-set(allowedpart))!=set() or \ 528 pert_loop_order==set([]): 529 valid_diag=False 530 if not warned: 531 logger.warning(warning_msg) 532 warned=True 533 if len([col for col in [ 534 self['process'].get('model').get_particle(pdg).get('color') \ 535 for pdg in diag.get_pdgs_attached_to_loop(\ 536 self['structure_repository'])] if col!=1])==1: 537 valid_diag=False 538 539 if valid_diag: 540 newloopselection.append(diag) 541 self['loop_diagrams']=newloopselection
542 # To monitor what are the diagrams filtered, simply comment the line 543 # directly above and uncomment the two directly below. 544 # self['loop_diagrams'] = base_objects.DiagramList( 545 # [diag for diag in self['loop_diagrams'] if diag not in newloopselection]) 546
547 - def check_factorization(self,user_orders):
548 """ Makes sure that all non perturbed orders factorize the born diagrams 549 """ 550 warning_msg = "All Born diagrams do not factorize the same sum of power(s) "+\ 551 "of the the perturbed order(s) %s.\nThis is potentially dangerous"+\ 552 " as the real-emission diagrams from aMC@NLO will not be consistent"+\ 553 " with these virtual contributions." 554 if self['process']['has_born']: 555 trgt_summed_order = sum([self['born_diagrams'][0].get_order(order) 556 for order in self['process']['perturbation_couplings']]) 557 for diag in self['born_diagrams'][1:]: 558 if sum([diag.get_order(order) for order in self['process'] 559 ['perturbation_couplings']])!=trgt_summed_order: 560 logger.warning(warning_msg%' '.join(self['process'] 561 ['perturbation_couplings'])) 562 break 563 564 warning_msg = "All born diagrams do not factorize the same power of "+\ 565 "the order %s which is not perturbed and for which you have not"+\ 566 "specified any amplitude order. \nThis is potentially dangerous"+\ 567 " as the real-emission diagrams from aMC@NLO will not be consistent"+\ 568 " with these virtual contributions." 569 if self['process']['has_born']: 570 for order in self['process']['model']['coupling_orders']: 571 if order not in self['process']['perturbation_couplings'] and \ 572 order not in user_orders.keys(): 573 order_power=self['born_diagrams'][0].get_order(order) 574 for diag in self['born_diagrams'][1:]: 575 if diag.get_order(order)!=order_power: 576 logger.warning(warning_msg%order) 577 break
578 579 # Helper function
580 - def get_non_pert_order_config(self, diagram):
581 """ Return a dictionary of all the coupling orders of this diagram which 582 are not the perturbed ones.""" 583 return dict([(order, diagram.get_order(order)) for \ 584 order in self['process']['model']['coupling_orders'] if \ 585 not order in self['process']['perturbation_couplings'] ])
586
587 - def print_config(self,config):
588 """Return a string describing the coupling order configuration""" 589 res = [] 590 for order in self['process']['model']['coupling_orders']: 591 try: 592 res.append('%s=%d'%(order,config[order])) 593 except KeyError: 594 res.append('%s=*'%order) 595 return ','.join(res)
596
597 - def generate_diagrams(self, loop_filter=None, diagram_filter=None):
598 """ Generates all diagrams relevant to this Loop Process """ 599 600 # Description of the algorithm to guess the leading contribution. 601 # The summed weighted order of each diagram will be compared to 602 # 'target_weighted_order' which acts as a threshold to decide which 603 # diagram to keep. Here is an example on how MG5 sets the 604 # 'target_weighted_order'. 605 # 606 # In the sm process uu~ > dd~ [QCD, QED] with hierarchy QCD=1, QED=2 we 607 # would have at leading order contribution like 608 # (QED=4) , (QED=2, QCD=2) , (QCD=4) 609 # leading to a summed weighted order of respectively 610 # (4*2=8) , (2*2+2*1=6) , (4*1=4) 611 # at NLO in QCD and QED we would have the following possible contributions 612 # (QED=6), (QED=4,QCD=2), (QED=2,QCD=4) and (QCD=6) 613 # which translate into the following weighted orders, respectively 614 # 12, 10, 8 and 6 615 # So, now we take the largest weighted order at born level, 4, and add two 616 # times the largest weight in the hierarchy among the order for which we 617 # consider loop perturbation, in this case 2*2 wich gives us a 618 # target_weighted_order of 8. based on this we will now keep all born 619 # contributions and exclude the NLO contributions (QED=6) and (QED=4,QCD=2) 620 621 # Use the globally defined loop_filter if the locally defined one is empty 622 if (not self.loop_filter is None) and (loop_filter is None): 623 loop_filter = self.loop_filter 624 625 logger.debug("Generating %s "\ 626 %self['process'].nice_string().replace('Process', 'process')) 627 628 # Hierarchy and model shorthands 629 model = self['process']['model'] 630 hierarchy = model['order_hierarchy'] 631 632 # Later, we will specify the orders for the loop amplitude. 633 # It is a temporary change that will be reverted after loop diagram 634 # generation. We then back up here its value prior modification. 635 user_orders=copy.copy(self['process']['orders']) 636 # First generate the born diagram if the user asked for it 637 if self['process']['has_born']: 638 bornsuccessful = self.generate_born_diagrams() 639 ldg_debug_info("# born diagrams after first generation",\ 640 len(self['born_diagrams'])) 641 else: 642 self['born_diagrams'] = base_objects.DiagramList() 643 bornsuccessful = True 644 logger.debug("Born diagrams generation skipped by user request.") 645 646 # Make sure that all orders specified belong to the model: 647 for order in self['process']['orders'].keys()+\ 648 self['process']['squared_orders'].keys(): 649 if not order in model.get('coupling_orders') and \ 650 order != 'WEIGHTED': 651 raise InvalidCmd("Coupling order %s not found"%order +\ 652 " in any interaction of the current model %s."%model['name']) 653 654 # The decision of whether the virtual must be squared against the born or the 655 # virtual is made based on whether there are Born or not unless the user 656 # already asked for the loop squared. 657 if self['process']['has_born']: 658 self['process']['has_born'] = self['born_diagrams']!=[] 659 self['has_born'] = self['process']['has_born'] 660 661 ldg_debug_info("User input born orders",self['process']['orders']) 662 ldg_debug_info("User input squared orders", 663 self['process']['squared_orders']) 664 ldg_debug_info("User input perturbation",\ 665 self['process']['perturbation_couplings']) 666 667 # Now, we can further specify the orders for the loop amplitude. 668 # Those specified by the user of course remain the same, increased by 669 # two if they are perturbed. It is a temporary change that will be 670 # reverted after loop diagram generation. 671 user_orders=copy.copy(self['process']['orders']) 672 user_squared_orders=copy.copy(self['process']['squared_orders']) 673 674 # If the user did not specify any order, we can expect him not to be an 675 # expert. So we must make sure the born all factorize the same powers of 676 # coupling orders which are not perturbed. If not we chose a configuration 677 # of non-perturbed order which has the smallest total weight and inform 678 # the user about this. It is then stored below for later filtering of 679 # the loop diagrams. 680 chosen_order_config={} 681 if self['process']['squared_orders']=={} and \ 682 self['process']['orders']=={} and self['process']['has_born']: 683 chosen_order_config = self.choose_order_config() 684 685 discarded_configurations = [] 686 # The born diagrams are now filtered according to the chose configuration 687 if chosen_order_config != {}: 688 self.filter_from_order_config('born_diagrams', \ 689 chosen_order_config,discarded_configurations) 690 691 # Before proceeding with the loop contributions, we must make sure that 692 # the born diagram generated factorize the same sum of power of the 693 # perturbed couplings. If this is not true, then it is very 694 # cumbersome to get the real radiation contribution correct and consistent 695 # with the computations of the virtuals (for now). 696 # Also, when MadLoop5 guesses the a loop amplitude order on its own, it 697 # might decide not to include some subleading loop which might be not 698 # be consistently neglected for now in the MadFKS5 so that its best to 699 # warn the user that he should enforce that target born amplitude order 700 # to any value of his choice. 701 self.check_factorization(user_orders) 702 703 # Now find an upper bound for the loop diagram generation. 704 self.guess_loop_orders_from_squared() 705 706 # If the user had not specified any fixed squared order other than 707 # WEIGHTED, we will use the guessed weighted order to assign a bound to 708 # the loop diagram order. Later we will check if the order deduced from 709 # the max order appearing in the born diagrams is a better upper bound. 710 # It will set 'WEIGHTED' to the desired value if it was not already set 711 # by the user. This is why you see the process defined with 'WEIGHTED' 712 # in the squared orders no matter the user input. Leave it like this. 713 if [k.upper() for k in self['process']['squared_orders'].keys()] in \ 714 [[],['WEIGHTED']] and self['process']['has_born']: 715 self.guess_loop_orders(user_orders) 716 717 # Finally we enforce the use of the orders specified for the born 718 # (augmented by two if perturbed) by the user, no matter what was 719 # the best guess performed above. 720 for order in user_orders.keys(): 721 if order in self['process']['perturbation_couplings']: 722 self['process']['orders'][order]=user_orders[order]+2 723 else: 724 self['process']['orders'][order]=user_orders[order] 725 if 'WEIGHTED' in user_orders.keys(): 726 self['process']['orders']['WEIGHTED']=user_orders['WEIGHTED']+\ 727 2*min([hierarchy[order] for order in \ 728 self['process']['perturbation_couplings']]) 729 730 ldg_debug_info("Orders used for loop generation",\ 731 self['process']['orders']) 732 733 # Make sure to warn the user if we already possibly excluded mixed order 734 # loops by smartly setting up the orders 735 warning_msg = ("Some loop diagrams contributing to this process might "+\ 736 "be discarded because they are not pure (%s)-perturbation.\nMake sure"+\ 737 " there are none or that you did not want to include them.")%(\ 738 ','.join(self['process']['perturbation_couplings'])) 739 740 if self['process']['has_born']: 741 for order in model['coupling_orders']: 742 if order not in self['process']['perturbation_couplings']: 743 try: 744 if self['process']['orders'][order]< \ 745 self['born_diagrams'].get_max_order(order): 746 logger.warning(warning_msg) 747 break 748 except KeyError: 749 pass 750 751 # Now we can generate the loop diagrams. 752 totloopsuccessful=self.generate_loop_diagrams() 753 754 # If there is no born neither loop diagrams, return now. 755 if not self['process']['has_born'] and not self['loop_diagrams']: 756 self['process']['orders'].clear() 757 self['process']['orders'].update(user_orders) 758 return False 759 760 # We add here the UV renormalization contribution built in 761 # LoopUVCTDiagram. It is done before the squared order selection because 762 # it is possible that some UV-renorm. diagrams are removed as well. 763 if self['process']['has_born']: 764 self.set_Born_CT() 765 766 ldg_debug_info("#UVCTDiags generated",len(self['loop_UVCT_diagrams'])) 767 768 # Reset the orders to their original specification by the user 769 self['process']['orders'].clear() 770 self['process']['orders'].update(user_orders) 771 772 # If there was no born, we will guess the WEIGHT squared order only now, 773 # based on the minimum weighted order of the loop contributions, if it 774 # was not specified by the user. 775 if not self['process']['has_born'] and not \ 776 self['process']['squared_orders'] and not\ 777 self['process']['orders'] and hierarchy: 778 pert_order_weights=[hierarchy[order] for order in \ 779 self['process']['perturbation_couplings']] 780 self['process']['squared_orders']['WEIGHTED']=2*(\ 781 self['loop_diagrams'].get_min_order('WEIGHTED')+\ 782 max(pert_order_weights)-min(pert_order_weights)) 783 784 ldg_debug_info("Squared orders after treatment",\ 785 self['process']['squared_orders']) 786 ldg_debug_info("#Diags after diagram generation",\ 787 len(self['loop_diagrams'])) 788 789 790 # If a special non perturbed order configuration was chosen at the 791 # beginning because of the absence of order settings by the user, 792 # the corresponding filter is applied now to loop diagrams. 793 # List of discarded configurations 794 if chosen_order_config != {}: 795 self.filter_from_order_config('loop_diagrams', \ 796 chosen_order_config,discarded_configurations) 797 # # Warn about discarded configurations. 798 if discarded_configurations!=[]: 799 msg = ("The contribution%s of th%s coupling orders "+\ 800 "configuration%s %s discarded :%s")%(('s','ese','s','are','\n')\ 801 if len(discarded_configurations)>1 else ('','is','','is',' ')) 802 msg = msg + '\n'.join(['(%s)'%self.print_config(conf) for conf \ 803 in discarded_configurations]) 804 msg = msg + "\nManually set the coupling orders to "+\ 805 "generate %sthe contribution%s above."%(('any of ','s') if \ 806 len(discarded_configurations)>1 else ('','')) 807 logger.info(msg) 808 809 # The minimum of the different orders used for the selections can 810 # possibly increase, after some loop diagrams are selected out. 811 # So this check must be iterated until the number of diagrams 812 # remaining is stable. 813 # We first apply the selection rules without the negative constraint. 814 # (i.e. QCD=1 for LO contributions only) 815 regular_constraints = dict([(key,val) for (key,val) in 816 self['process']['squared_orders'].items() if val>=0]) 817 negative_constraints = dict([(key,val) for (key,val) in 818 self['process']['squared_orders'].items() if val<0]) 819 while True: 820 ndiag_remaining=len(self['loop_diagrams']+self['born_diagrams']) 821 self.check_squared_orders(regular_constraints) 822 if len(self['loop_diagrams']+self['born_diagrams'])==ndiag_remaining: 823 break 824 # And then only the negative ones 825 if negative_constraints!={}: 826 # It would be meaningless here to iterate because <order>=-X would 827 # have a different meaning every time. 828 # notice that this function will change the negative values of 829 # self['process']['squared_orders'] to their corresponding positive 830 # constraint for the present process. 831 # For example, u u~ > d d~ QCD^2=-2 becomes u u~ > d d~ QCD=2 832 # because the LO QCD contribution has QED=4, QCD=0 and the NLO one 833 # selected with -2 is QED=2, QCD=2. 834 self.check_squared_orders(negative_constraints,user_squared_orders) 835 836 ldg_debug_info("#Diags after constraints",len(self['loop_diagrams'])) 837 ldg_debug_info("#Born diagrams after constraints",len(self['born_diagrams'])) 838 ldg_debug_info("#UVCTDiags after constraints",len(self['loop_UVCT_diagrams'])) 839 840 # Now the loop diagrams are tagged and filtered for redundancy. 841 tag_selected=[] 842 loop_basis=base_objects.DiagramList() 843 for diag in self['loop_diagrams']: 844 diag.tag(self['structure_repository'],model) 845 # Make sure not to consider wave-function renormalization, vanishing tadpoles, 846 # or redundant diagrams 847 if not diag.is_wf_correction(self['structure_repository'], \ 848 model) and not diag.is_vanishing_tadpole(model) and \ 849 diag['canonical_tag'] not in tag_selected: 850 loop_basis.append(diag) 851 tag_selected.append(diag['canonical_tag']) 852 853 self['loop_diagrams']=loop_basis 854 855 # Now select only the loops corresponding to the perturbative orders 856 # asked for. 857 self.filter_loop_for_perturbative_orders() 858 859 if len(self['loop_diagrams'])==0 and len(self['born_diagrams'])!=0: 860 raise InvalidCmd('All loop diagrams discarded by user selection.\n'+\ 861 'Consider using a tree-level generation or relaxing the coupling'+\ 862 ' order constraints.') 863 # If there is no born neither loop diagrams after filtering, return now. 864 if not self['process']['has_born'] and not self['loop_diagrams']: 865 self['process']['squared_orders'].clear() 866 self['process']['squared_orders'].update(user_squared_orders) 867 return False 868 869 870 # Discard diagrams which are zero because of Furry theorem 871 self.remove_Furry_loops(model,self['structure_repository']) 872 873 # Apply here some user-defined filter. 874 # For expert only, you can edit your own filter by modifying the 875 # user_filter() function which by default does nothing but in which you 876 # will find examples of common filters. 877 self.user_filter(model,self['structure_repository'], filter=loop_filter) 878 879 # Set the necessary UV/R2 CounterTerms for each loop diagram generated 880 self.set_LoopCT_vertices() 881 882 # Now revert the squared order. This function typically adds to the 883 # squared order list the target WEIGHTED order which has been detected. 884 # This is typically not desired because if the user types in directly 885 # what it sees on the screen, it does not get back the same process. 886 # for example, u u~ > d d~ [virt=QCD] becomes 887 # u u~ > d d~ [virt=QCD] WEIGHTED=6 888 # but of course the photon-gluon s-channel Born interference is not 889 # counted in. 890 # However, if you type it in generate again with WEIGHTED=6, you will 891 # get it. 892 self['process']['squared_orders'].clear() 893 self['process']['squared_orders'].update(user_squared_orders) 894 895 # The computation below is just to report what split order are computed 896 # and which one are considered (i.e. kept using the order specifications) 897 self.print_split_order_infos() 898 899 # Give some info about the run 900 nLoopDiag = 0 901 nCT={'UV':0,'R2':0} 902 for ldiag in self['loop_UVCT_diagrams']: 903 nCT[ldiag['type'][:2]]+=len(ldiag['UVCT_couplings']) 904 for ldiag in self['loop_diagrams']: 905 nLoopDiag+=1 906 nCT['UV']+=len(ldiag.get_CT(model,'UV')) 907 nCT['R2']+=len(ldiag.get_CT(model,'R2')) 908 909 # The identification of numerically equivalent diagrams is done here. 910 # Simply comment the line above to remove it for testing purposes 911 # (i.e. to make sure it does not alter the result). 912 nLoopsIdentified = self.identify_loop_diagrams() 913 if nLoopsIdentified > 0: 914 logger.debug("A total of %d loop diagrams "%nLoopsIdentified+\ 915 "were identified with equivalent ones.") 916 logger.info("Contributing diagrams generated: "+\ 917 "%d Born, %d%s loops, %d R2, %d UV"%(len(self['born_diagrams']), 918 len(self['loop_diagrams']),'(+%d)'%nLoopsIdentified \ 919 if nLoopsIdentified>0 else '' ,nCT['R2'],nCT['UV'])) 920 921 ldg_debug_info("#Diags after filtering",len(self['loop_diagrams'])) 922 ldg_debug_info("# of different structures identified",\ 923 len(self['structure_repository'])) 924 925 return (bornsuccessful or totloopsuccessful)
926
927 - def identify_loop_diagrams(self):
928 """ Uses a loop_tag characterizing the loop with only physical 929 information about it (mass, coupling, width, color, etc...) so as to 930 recognize numerically equivalent diagrams and group them together, 931 such as massless quark loops in pure QCD gluon loop amplitudes.""" 932 933 # This dictionary contains key-value pairs of the form 934 # (loop_tag, DiagramList) where the loop_tag key unambiguously 935 # characterizes a class of equivalent diagrams and the DiagramList value 936 # lists all the diagrams belonging to this class. 937 # In the end, the first diagram of this DiagramList will be used as 938 # the reference included in the numerical code for the loop matrix 939 # element computations and all the others will be omitted, being 940 # included via a simple multiplicative factor applied to the first one. 941 diagram_identification = {} 942 943 for i, loop_diag in enumerate(self['loop_diagrams']): 944 loop_tag = loop_diag.build_loop_tag_for_diagram_identification( 945 self['process']['model'], self.get('structure_repository'), 946 use_FDStructure_ID_for_tag = True) 947 # We store the loop diagrams in a 2-tuple that keeps track of 'i' 948 # so that we don't lose their original order. It is just for 949 # convenience, and not strictly necessary. 950 try: 951 diagram_identification[loop_tag].append((i+1,loop_diag)) 952 except KeyError: 953 diagram_identification[loop_tag] = [(i+1,loop_diag)] 954 955 # Now sort the loop_tag keys according to their order of appearance 956 sorted_loop_tag_keys = sorted(diagram_identification.keys(), 957 key=lambda k:diagram_identification[k][0][0]) 958 959 new_loop_diagram_base = base_objects.DiagramList([]) 960 n_loops_identified = 0 961 for loop_tag in sorted_loop_tag_keys: 962 n_diag_in_class = len(diagram_identification[loop_tag]) 963 n_loops_identified += n_diag_in_class-1 964 new_loop_diagram_base.append(diagram_identification[loop_tag][0][1]) 965 # We must add the counterterms of all the identified loop diagrams 966 # to the reference one. 967 new_loop_diagram_base[-1]['multiplier'] = n_diag_in_class 968 for ldiag in diagram_identification[loop_tag][1:]: 969 new_loop_diagram_base[-1].get('CT_vertices').extend( 970 copy.copy(ldiag[1].get('CT_vertices'))) 971 if n_diag_in_class > 1: 972 ldg_debug_info("# Diagram equivalence class detected","#(%s) -> #%d"\ 973 %(','.join('%d'%diag[0] for diag in diagram_identification[loop_tag][1:])+ 974 (',' if n_diag_in_class==2 else ''),diagram_identification[loop_tag][0][0])) 975 976 977 self.set('loop_diagrams',new_loop_diagram_base) 978 return n_loops_identified
979
980 - def print_split_order_infos(self):
981 """This function is solely for monitoring purposes. It reports what are 982 the coupling order combination which are obtained with the diagram 983 genarated and among those which ones correspond to those selected by 984 the process definition and which ones are the extra combinations which 985 comes as a byproduct of the computation of the desired one. The typical 986 example is that if you ask for d d~ > u u~ QCD^2==2 [virt=QCD, QED], 987 you will not only get (QCD,QED)=(2,2);(2,4) which are the desired ones 988 but the code output will in principle also be able to return 989 (QCD,QED)=(4,0);(4,2);(0,4);(0,6) because they involve the same amplitudes 990 """ 991 992 hierarchy = self['process']['model']['order_hierarchy'] 993 994 sqorders_types=copy.copy(self['process'].get('sqorders_types')) 995 # The WEIGHTED order might have been automatically assigned to the 996 # squared order constraints, so we must assign it a type if not specified 997 if 'WEIGHTED' not in sqorders_types: 998 sqorders_types['WEIGHTED']='<=' 999 1000 sorted_hierarchy = [order[0] for order in \ 1001 sorted(hierarchy.items(), key=lambda el: el[1])] 1002 1003 loop_SOs = set(tuple([d.get_order(order) for order in sorted_hierarchy]) 1004 for d in self['loop_diagrams']+self['loop_UVCT_diagrams']) 1005 1006 if self['process']['has_born']: 1007 born_SOs = set(tuple([d.get_order(order) for order in \ 1008 sorted_hierarchy]) for d in self['born_diagrams']) 1009 else: 1010 born_SOs = set([]) 1011 1012 born_sqSOs = set(tuple([x + y for x, y in zip(b1_SO, b2_SO)]) for b1_SO 1013 in born_SOs for b2_SO in born_SOs) 1014 if self['process']['has_born']: 1015 ref_amps = born_SOs 1016 else: 1017 ref_amps = loop_SOs 1018 loop_sqSOs = set(tuple([x + y for x, y in zip(b_SO, l_SO)]) for b_SO in 1019 ref_amps for l_SO in loop_SOs) 1020 1021 # Append the corresponding WEIGHT of each contribution 1022 sorted_hierarchy.append('WEIGHTED') 1023 born_sqSOs = sorted([b_sqso+(sum([b*hierarchy[sorted_hierarchy[i]] for 1024 i, b in enumerate(b_sqso)]),) for b_sqso in born_sqSOs], 1025 key=lambda el: el[1]) 1026 loop_sqSOs = sorted([l_sqso+(sum([l*hierarchy[sorted_hierarchy[i]] for 1027 i, l in enumerate(l_sqso)]),) for l_sqso in loop_sqSOs], 1028 key=lambda el: el[1]) 1029 1030 1031 logger.debug("Coupling order combinations considered:"+\ 1032 " (%s)"%','.join(sorted_hierarchy)) 1033 1034 # Now check what is left 1035 born_considered = [] 1036 loop_considered = [] 1037 for i, sqSOList in enumerate([born_sqSOs,loop_sqSOs]): 1038 considered = [] 1039 extra = [] 1040 for sqSO in sqSOList: 1041 for sqo, constraint in self['process']['squared_orders'].items(): 1042 sqo_index = sorted_hierarchy.index(sqo) 1043 # Notice that I assume here that the negative coupling order 1044 # constraint should have been replaced here (by its 1045 # corresponding positive value). 1046 if (sqorders_types[sqo]=='==' and 1047 sqSO[sqo_index]!=constraint ) or \ 1048 (sqorders_types[sqo] in ['=','<='] and 1049 sqSO[sqo_index]>constraint) or \ 1050 (sqorders_types[sqo] in ['>'] and 1051 sqSO[sqo_index]<=constraint): 1052 extra.append(sqSO) 1053 break; 1054 1055 # Set the ones considered to be the complement of the omitted ones 1056 considered = [sqSO for sqSO in sqSOList if sqSO not in extra] 1057 1058 if i==0: 1059 born_considered = considered 1060 name = "Born" 1061 if not self['process']['has_born']: 1062 logger.debug(" > No Born contributions for this process.") 1063 continue 1064 elif i==1: 1065 loop_considered = considered 1066 name = "loop" 1067 1068 if len(considered)==0: 1069 logger.debug(" > %s : None"%name) 1070 else: 1071 logger.debug(" > %s : %s"%(name,' '.join(['(%s,W%d)'%( 1072 ','.join(list('%d'%s for s in c[:-1])),c[-1]) 1073 for c in considered]))) 1074 1075 if len(extra)!=0: 1076 logger.debug(" > %s (not selected but available): %s"%(name,' '. 1077 join(['(%s,W%d)'%(','.join(list('%d'%s for s in e[:-1])), 1078 e[-1]) for e in extra]))) 1079 1080 # In case it is needed, the considered orders are returned 1081 # (it is used by some of the unit tests) 1082 return (born_considered, 1083 [sqSO for sqSO in born_sqSOs if sqSO not in born_considered], 1084 loop_considered, 1085 [sqSO for sqSO in loop_sqSOs if sqSO not in loop_considered])
1086 1087
1088 - def generate_born_diagrams(self):
1089 """ Generates all born diagrams relevant to this NLO Process """ 1090 1091 bornsuccessful, self['born_diagrams'] = \ 1092 diagram_generation.Amplitude.generate_diagrams(self,True) 1093 1094 return bornsuccessful
1095
1096 - def generate_loop_diagrams(self):
1097 """ Generates all loop diagrams relevant to this NLO Process """ 1098 1099 # Reinitialize the loop diagram container 1100 self['loop_diagrams']=base_objects.DiagramList() 1101 totloopsuccessful=False 1102 1103 # Make sure to start with an empty l-cut particle list. 1104 self.lcutpartemployed=[] 1105 1106 # It is important to try and obtain an ordering of the loop diagrams 1107 # as canonical as possible so that loop ME for identical processes that 1108 # differ only by the external massless flavors can be identified and 1109 # merged together. We therefore choose to always start with QCD and QED 1110 # perturbation if specified and then follow alphabetical order. 1111 pert_orders = \ 1112 (['QCD'] if 'QCD' in self['process']['perturbation_couplings'] else [])+\ 1113 (['QED'] if 'QED' in self['process']['perturbation_couplings'] else [])+\ 1114 sorted(order for order in self['process']['perturbation_couplings'] if 1115 order not in ['QCD','QED']) 1116 1117 whole_spin_no_anti = [] 1118 whole_spin_has_anti = [] 1119 half_spin_no_anti = [] 1120 half_spin_has_anti = [] 1121 for order in pert_orders: 1122 lcutPart=[particle for particle in \ 1123 self['process']['model']['particles'] if \ 1124 (particle.is_perturbating(order, self['process']['model']) and \ 1125 particle.get_pdg_code() not in \ 1126 self['process']['forbidden_particles'])] 1127 whole_spin_no_anti += filter(lambda p: 1128 p.get('spin')%2==1 and p.get('self_antipart') 1129 and p not in whole_spin_no_anti, lcutPart) 1130 whole_spin_has_anti += filter(lambda p: 1131 p.get('spin')%2==1 and not p.get('self_antipart') 1132 and p not in whole_spin_has_anti, lcutPart) 1133 half_spin_no_anti += filter(lambda p: 1134 p.get('spin')%2==0 and p.get('self_antipart') 1135 and p not in half_spin_no_anti, lcutPart) 1136 half_spin_has_anti += filter(lambda p: 1137 p.get('spin')%2==0 and not p.get('self_antipart') 1138 and p not in half_spin_has_anti, lcutPart) 1139 1140 # In an effort to canonically order loop diagrams generated, we 1141 # choose here to always start with whole integer spin particles and then 1142 # half-integer ones and in each of these subset, put first self-antiparticles. 1143 # The remaining degeneracy is fixed by ordering by PDGs. 1144 for l in [whole_spin_no_anti,whole_spin_has_anti, 1145 half_spin_no_anti,half_spin_has_anti]: 1146 l.sort(key=lambda p: p.get('pdg_code')) 1147 1148 # Finally add them together to the list of l-cut particles to be processed. 1149 lcutPart = whole_spin_no_anti + whole_spin_has_anti + \ 1150 half_spin_no_anti + half_spin_has_anti 1151 1152 # misc.sprint(" lcutPart=",[part.get('name') for part in lcutPart]) 1153 for part in lcutPart: 1154 if part.get_pdg_code() not in self.lcutpartemployed: 1155 # First create the two L-cut particles to add to the process. 1156 # Remember that in the model only the particles should be 1157 # tagged as contributing to the a perturbation. Never the 1158 # anti-particle. We chose here a specific orientation for 1159 # the loop momentum flow, say going IN lcutone and OUT 1160 # lcuttwo. We also define here the 'positive' loop fermion 1161 # flow by always setting lcutone to be a particle and 1162 # lcuttwo the corresponding anti-particle. 1163 ldg_debug_info("Generating loop diagram with L-cut type",\ 1164 part.get_name()) 1165 lcutone=base_objects.Leg({'id': part.get_pdg_code(), 1166 'state': True, 1167 'loop_line': True}) 1168 lcuttwo=base_objects.Leg({'id': part.get_anti_pdg_code(), 1169 'state': True, 1170 'loop_line': True}) 1171 self['process'].get('legs').extend([lcutone,lcuttwo]) 1172 # WARNING, it is important for the tagging to notice here 1173 # that lcuttwo is the last leg in the process list of legs 1174 # and will therefore carry the highest 'number' attribute as 1175 # required to insure that it will never be 'propagated' to 1176 # any output leg. 1177 1178 # We generate the diagrams now 1179 loopsuccessful, lcutdiaglist = \ 1180 super(LoopAmplitude, self).generate_diagrams(True) 1181 1182 # Now get rid of all the previously defined l-cut particles. 1183 leg_to_remove=[leg for leg in self['process']['legs'] \ 1184 if leg['loop_line']] 1185 for leg in leg_to_remove: 1186 self['process']['legs'].remove(leg) 1187 1188 # The correct L-cut type is specified 1189 for diag in lcutdiaglist: 1190 diag.set('type',part.get_pdg_code()) 1191 self['loop_diagrams']+=lcutdiaglist 1192 1193 # Update the list of already employed L-cut particles such 1194 # that we never use them again in loop particles 1195 self.lcutpartemployed.append(part.get_pdg_code()) 1196 self.lcutpartemployed.append(part.get_anti_pdg_code()) 1197 1198 ldg_debug_info("#Diags generated w/ this L-cut particle",\ 1199 len(lcutdiaglist)) 1200 # Accordingly update the totloopsuccessful tag 1201 if loopsuccessful: 1202 totloopsuccessful=True 1203 1204 # Reset the l-cut particle list 1205 self.lcutpartemployed=[] 1206 1207 return totloopsuccessful
1208 1209
1210 - def set_Born_CT(self):
1211 """ Scan all born diagrams and add for each all the corresponding UV 1212 counterterms. It creates one LoopUVCTDiagram per born diagram and set 1213 of possible coupling_order (so that QCD and QED wavefunction corrections 1214 are not in the same LoopUVCTDiagram for example). Notice that this takes 1215 care only of the UV counterterm which factorize with the born and the 1216 other contributions like the UV mass renormalization are added in the 1217 function setLoopCTVertices""" 1218 1219 # return True 1220 # ============================================ 1221 # Including the UVtree contributions 1222 # ============================================ 1223 1224 # The following lists the UV interactions potentially giving UV counterterms 1225 # (The UVmass interactions is accounted for like the R2s) 1226 UVCTvertex_interactions = base_objects.InteractionList() 1227 for inter in self['process']['model']['interactions'].get_UV(): 1228 if inter.is_UVtree() and len(inter['particles'])>1 and \ 1229 inter.is_perturbating(self['process']['perturbation_couplings']) \ 1230 and (set(inter['orders'].keys()).intersection(\ 1231 set(self['process']['perturbation_couplings'])))!=set([]) and \ 1232 (any([set(loop_parts).intersection(set(self['process']\ 1233 ['forbidden_particles']))==set([]) for loop_parts in \ 1234 inter.get('loop_particles')]) or \ 1235 inter.get('loop_particles')==[[]]): 1236 UVCTvertex_interactions.append(inter) 1237 1238 # Temporarly give the tagging order 'UVCT_SPECIAL' to those interactions 1239 self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']=0 1240 self['process']['model'].get('coupling_orders').add('UVCT_SPECIAL') 1241 for inter in UVCTvertex_interactions: 1242 neworders=copy.copy(inter.get('orders')) 1243 neworders['UVCT_SPECIAL']=1 1244 inter.set('orders',neworders) 1245 # Refresh the model interaction dictionary while including those special 1246 # interactions 1247 self['process']['model'].actualize_dictionaries(useUVCT=True) 1248 1249 # Generate the UVCTdiagrams (born diagrams with 'UVCT_SPECIAL'=0 order 1250 # will be generated along) 1251 self['process']['orders']['UVCT_SPECIAL']=1 1252 1253 UVCTsuccessful, UVCTdiagrams = \ 1254 super(LoopAmplitude, self).generate_diagrams(True) 1255 1256 for UVCTdiag in UVCTdiagrams: 1257 if UVCTdiag.get_order('UVCT_SPECIAL')==1: 1258 newUVCTDiag = loop_base_objects.LoopUVCTDiagram({\ 1259 'vertices':copy.deepcopy(UVCTdiag['vertices'])}) 1260 UVCTinter = newUVCTDiag.get_UVCTinteraction(self['process']['model']) 1261 newUVCTDiag.set('type',UVCTinter.get('type')) 1262 # This interaction counter-term must be accounted for as many times 1263 # as they are list of loop_particles defined and allowed for by 1264 # the process. 1265 newUVCTDiag.get('UVCT_couplings').append((len([1 for loop_parts \ 1266 in UVCTinter.get('loop_particles') if set(loop_parts).intersection(\ 1267 set(self['process']['forbidden_particles']))==set([])])) if 1268 loop_parts!=[[]] else 1) 1269 self['loop_UVCT_diagrams'].append(newUVCTDiag) 1270 1271 # Remove the additional order requirement in the born orders for this 1272 # process 1273 del self['process']['orders']['UVCT_SPECIAL'] 1274 # Remove the fake order added to the selected UVCT interactions 1275 del self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL'] 1276 self['process']['model'].get('coupling_orders').remove('UVCT_SPECIAL') 1277 for inter in UVCTvertex_interactions: 1278 del inter.get('orders')['UVCT_SPECIAL'] 1279 # Revert the model interaction dictionaries to default 1280 self['process']['model'].actualize_dictionaries(useUVCT=False) 1281 1282 # Set the correct orders to the loop_UVCT_diagrams 1283 for UVCTdiag in self['loop_UVCT_diagrams']: 1284 UVCTdiag.calculate_orders(self['process']['model']) 1285 1286 # ============================================ 1287 # Wavefunction renormalization 1288 # ============================================ 1289 1290 if not self['process']['has_born']: 1291 return UVCTsuccessful 1292 1293 # We now scan each born diagram, adding the necessary wavefunction 1294 # renormalizations 1295 for bornDiag in self['born_diagrams']: 1296 # This dictionary takes for keys the tuple 1297 # (('OrderName1',power1),...,('OrderNameN',powerN) representing 1298 # the power brought by the counterterm and the value is the 1299 # corresponding LoopUVCTDiagram. 1300 # The last entry is of the form ('EpsilonOrder', value) to put the 1301 # contribution of each different EpsilonOrder to different 1302 # LoopUVCTDiagrams. 1303 LoopUVCTDiagramsAdded={} 1304 for leg in self['process']['legs']: 1305 counterterm=self['process']['model'].get_particle(abs(leg['id'])).\ 1306 get('counterterm') 1307 for key, value in counterterm.items(): 1308 if key[0] in self['process']['perturbation_couplings']: 1309 for laurentOrder, CTCoupling in value.items(): 1310 # Create the order key of the UV counterterm 1311 orderKey=[(key[0],2),] 1312 orderKey.sort() 1313 orderKey.append(('EpsilonOrder',-laurentOrder)) 1314 CTCouplings=[CTCoupling for loop_parts in key[1] if 1315 set(loop_parts).intersection(set(self['process']\ 1316 ['forbidden_particles']))==set([])] 1317 if CTCouplings!=[]: 1318 try: 1319 LoopUVCTDiagramsAdded[tuple(orderKey)].get(\ 1320 'UVCT_couplings').extend(CTCouplings) 1321 except KeyError: 1322 LoopUVCTDiagramsAdded[tuple(orderKey)]=\ 1323 loop_base_objects.LoopUVCTDiagram({\ 1324 'vertices':copy.deepcopy(bornDiag['vertices']), 1325 'type':'UV'+('' if laurentOrder==0 else 1326 str(-laurentOrder)+'eps'), 1327 'UVCT_orders':{key[0]:2}, 1328 'UVCT_couplings':CTCouplings}) 1329 1330 for LoopUVCTDiagram in LoopUVCTDiagramsAdded.values(): 1331 LoopUVCTDiagram.calculate_orders(self['process']['model']) 1332 self['loop_UVCT_diagrams'].append(LoopUVCTDiagram) 1333 1334 return UVCTsuccessful
1335
1336 - def set_LoopCT_vertices(self):
1337 """ Scan each loop diagram and recognizes what are the R2/UVmass 1338 CounterTerms associated to them """ 1339 #return # debug 1340 # We first create a base dictionary with as a key (tupleA,tupleB). For 1341 # each R2/UV interaction, tuple B is the ordered tuple of the loop 1342 # particles (not anti-particles, so that the PDG is always positive!) 1343 # listed in its loop_particles attribute. Tuple A is the ordered tuple 1344 # of external particles PDGs. making up this interaction. The values of 1345 # the dictionary are a list of the interaction ID having the same key 1346 # above. 1347 CT_interactions = {} 1348 for inter in self['process']['model']['interactions']: 1349 if inter.is_UVmass() or inter.is_UVloop() or inter.is_R2() and \ 1350 len(inter['particles'])>1 and inter.is_perturbating(\ 1351 self['process']['perturbation_couplings']): 1352 # This interaction might have several possible loop particles 1353 # yielding the same CT. So we add this interaction ID 1354 # for each entry in the list loop_particles. 1355 for i, lparts in enumerate(inter['loop_particles']): 1356 keya=copy.copy(lparts) 1357 keya.sort() 1358 if inter.is_UVloop(): 1359 # If it is a CT of type UVloop, then do not specify the 1360 # keya (leave it empty) but make sure the particles 1361 # specified as loop particles are not forbidden before 1362 # adding this CT to CT_interactions 1363 if (set(self['process']['forbidden_particles']) & \ 1364 set(lparts)) != set([]): 1365 continue 1366 else: 1367 keya=[] 1368 keyb=[part.get_pdg_code() for part in inter['particles']] 1369 keyb.sort() 1370 key=(tuple(keyb),tuple(keya)) 1371 # We keep track of 'i' (i.e. the position of the 1372 # loop_particle list in the inter['loop_particles']) so 1373 # that each coupling in a vertex of type 'UVloop' is 1374 # correctly accounted for since the keya is always replaced 1375 # by an empty list since the constraint on the loop particles 1376 # is simply that there is not corresponding forbidden 1377 # particles in the process definition and not that the 1378 # actual particle content of the loop generate matches. 1379 # 1380 # This can also happen with the type 'UVmass' or 'R2' 1381 # CTvertex ex1( 1382 # type='UVmass' 1383 # [...] 1384 # loop_particles=[[[d,g],[d,g]]]) 1385 # Which is a bit silly but can happen and would mean that 1386 # we must account twice for the coupling associated to each 1387 # of these loop_particles. 1388 # One might imagine someone doing it with 1389 # loop_particles=[[[],[]]], for example, because he wanted 1390 # to get rid of the loop particle constraint for some reason. 1391 try: 1392 CT_interactions[key].append((inter['id'],i)) 1393 except KeyError: 1394 CT_interactions[key]=[(inter['id'],i),] 1395 1396 # The dictionary CTmass_added keeps track of what are the CounterTerms of 1397 # type UVmass or R2 already added and prevents us from adding them again. 1398 # For instance, the fermion boxes with four external gluons exists in 6 copies 1399 # (with different crossings of the external legs each time) and the 1400 # corresponding R2 must be added only once. The key of this dictionary 1401 # characterizing the loop is (tupleA,tupleB). Tuple A is made from the 1402 # list of the ID of the external structures attached to this loop and 1403 # tuple B from list of the pdg of the particles building this loop. 1404 1405 # Notice that when a CT of type UVmass is specified with an empty 1406 # loop_particles attribute, then it means it must be added once for each 1407 # particle with a matching topology, irrespectively of the loop content. 1408 # Whenever added, such a CT is put in the dictionary CT_added with a key 1409 # having an empty tupleB. 1410 # Finally, because CT interactions of type UVloop do specify a 1411 # loop_particles attribute, but which serves only to be filtered against 1412 # particles forbidden in the process definition, they will also be added 1413 # with an empty tupleB. 1414 CT_added = {} 1415 1416 for diag in self['loop_diagrams']: 1417 # First build the key from this loop for the CT_interaction dictionary 1418 # (Searching Key) and the key for the CT_added dictionary (tracking Key) 1419 searchingKeyA=[] 1420 # Notice that searchingKeyB below also serves as trackingKeyB 1421 searchingKeyB=[] 1422 trackingKeyA=[] 1423 for tagElement in diag['canonical_tag']: 1424 for structID in tagElement[1]: 1425 trackingKeyA.append(structID) 1426 searchingKeyA.append(self['process']['model'].get_particle(\ 1427 self['structure_repository'][structID]['binding_leg']['id']).\ 1428 get_pdg_code()) 1429 searchingKeyB.append(self['process']['model'].get_particle(\ 1430 tagElement[0]).get('pdg_code')) 1431 searchingKeyA.sort() 1432 # We do not repeat particles present many times in the loop 1433 searchingKeyB=list(set(searchingKeyB)) 1434 searchingKeyB.sort() 1435 trackingKeyA.sort() 1436 # I repeat, they are two kinds of keys: 1437 # searchingKey: 1438 # This serves to scan the CT interactions defined and then find 1439 # which ones match a given loop topology and particle. 1440 # trackingKey: 1441 # Once some CT vertices are identified to be a match for a loop, 1442 # the trackingKey is used in conjunction with the dictionary 1443 # CT_added to make sure that this CT has not already been included. 1444 1445 # Each of these two keys above, has the format 1446 # (tupleA, tupleB) 1447 # with tupleB being the loop_content and either contains the set of 1448 # loop particles PDGs of the interaction (for the searchingKey) 1449 # or of the loops already scanned (trackingKey). It can also be 1450 # empty when considering interactions of type UVmass or R2 which 1451 # have an empty loop_particle attribute or those of type UVloop. 1452 # TupleA is the set of external particle PDG (for the searchingKey) 1453 # and the unordered list of structID attached to the loop (for the 1454 # trackingKey) 1455 searchingKeySimple=(tuple(searchingKeyA),()) 1456 searchingKeyLoopPart=(tuple(searchingKeyA),tuple(searchingKeyB)) 1457 trackingKeySimple=(tuple(trackingKeyA),()) 1458 trackingKeyLoopPart=(tuple(trackingKeyA),tuple(searchingKeyB)) 1459 # Now we look for a CT which might correspond to this loop by looking 1460 # for its searchingKey in CT_interactions 1461 1462 # misc.sprint("I have the following CT_interactions=",CT_interactions) 1463 try: 1464 CTIDs=copy.copy(CT_interactions[searchingKeySimple]) 1465 except KeyError: 1466 CTIDs=[] 1467 try: 1468 CTIDs.extend(copy.copy(CT_interactions[searchingKeyLoopPart])) 1469 except KeyError: 1470 pass 1471 if not CTIDs: 1472 continue 1473 # We have found some CT interactions corresponding to this loop 1474 # so we must make sure we have not included them already 1475 try: 1476 usedIDs=copy.copy(CT_added[trackingKeySimple]) 1477 except KeyError: 1478 usedIDs=[] 1479 try: 1480 usedIDs.extend(copy.copy(CT_added[trackingKeyLoopPart])) 1481 except KeyError: 1482 pass 1483 1484 for CTID in CTIDs: 1485 # Make sure it has not been considered yet and that the loop 1486 # orders match 1487 if CTID not in usedIDs and diag.get_loop_orders(\ 1488 self['process']['model'])==\ 1489 self['process']['model']['interaction_dict'][CTID[0]]['orders']: 1490 # Create the amplitude vertex corresponding to this CT 1491 # and add it to the LoopDiagram treated. 1492 CTleglist = base_objects.LegList() 1493 for tagElement in diag['canonical_tag']: 1494 for structID in tagElement[1]: 1495 CTleglist.append(\ 1496 self['structure_repository'][structID]['binding_leg']) 1497 CTVertex = base_objects.Vertex({'id':CTID[0], \ 1498 'legs':CTleglist}) 1499 diag['CT_vertices'].append(CTVertex) 1500 # Now add this CT vertex to the CT_added dictionary so that 1501 # we are sure it will not be double counted 1502 if self['process']['model']['interaction_dict'][CTID[0]]\ 1503 ['loop_particles'][CTID[1]]==[] or \ 1504 self['process']['model']['interaction_dict'][CTID[0]].\ 1505 is_UVloop(): 1506 try: 1507 CT_added[trackingKeySimple].append(CTID) 1508 except KeyError: 1509 CT_added[trackingKeySimple] = [CTID, ] 1510 else: 1511 try: 1512 CT_added[trackingKeyLoopPart].append(CTID) 1513 except KeyError: 1514 CT_added[trackingKeyLoopPart] = [CTID, ] 1515 # Now make sure that the CT vertices added are cannonically ordered 1516 # (using coupling names and color and lorenz strings) 1517 # This plays a role when doing comparisons for potential merging of 1518 # identical processes (i.e. Identical HelasMatrixElement where only 1519 # massless flavors differ). 1520 diag['CT_vertices'].sort(key=lambda ct: 1521 self['process']['model'].get_interaction(ct.get('id')).canonical_repr())
1522 1523
1524 - def create_diagram(self, vertexlist):
1525 """ Return a LoopDiagram created.""" 1526 return loop_base_objects.LoopDiagram({'vertices':vertexlist})
1527
1528 - def copy_leglist(self, leglist):
1529 """ Returns a DGLoopLeg list instead of the default copy_leglist 1530 defined in base_objects.Amplitude """ 1531 1532 dgloopleglist=base_objects.LegList() 1533 for leg in leglist: 1534 dgloopleglist.append(loop_base_objects.DGLoopLeg(leg)) 1535 1536 return dgloopleglist
1537
1538 - def convert_dgleg_to_leg(self, vertexdoublelist):
1539 """ Overloaded here to convert back all DGLoopLegs into Legs. """ 1540 for vertexlist in vertexdoublelist: 1541 for vertex in vertexlist: 1542 if not isinstance(vertex['legs'][0],loop_base_objects.DGLoopLeg): 1543 continue 1544 vertex['legs'][:]=[leg.convert_to_leg() for leg in \ 1545 vertex['legs']] 1546 return True
1547
1548 - def get_combined_legs(self, legs, leg_vert_ids, number, state):
1549 """Create a set of new legs from the info given.""" 1550 1551 looplegs=[leg for leg in legs if leg['loop_line']] 1552 1553 # Get rid of all vanishing tadpoles 1554 #Ease the access to the model 1555 model=self['process']['model'] 1556 exlegs=[leg for leg in looplegs if leg['depth']==0] 1557 if(len(exlegs)==2): 1558 if(any([part['mass'].lower()=='zero' for pdg,part in model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])): 1559 return [] 1560 1561 # Correctly propagate the loopflow 1562 loopline=(len(looplegs)==1) 1563 mylegs = [] 1564 for i, (leg_id, vert_id) in enumerate(leg_vert_ids): 1565 # We can now create the set of possible merged legs. 1566 # However, we make sure that its PDG is not in the list of 1567 # L-cut particles we already explored. If it is, we simply reject 1568 # the diagram. 1569 if not loopline or not (leg_id in self.lcutpartemployed): 1570 # Reminder: The only purpose of the "depth" flag is to get rid 1571 # of (some, not all) of the wave-function renormalization 1572 # already during diagram generation. We reckognize a wf 1573 # renormalization diagram as follows: 1574 if len(legs)==2 and len(looplegs)==2: 1575 # We have candidate 1576 depths=(looplegs[0]['depth'],looplegs[1]['depth']) 1577 if (0 in depths) and (-1 not in depths) and depths!=(0,0): 1578 # Check that the PDG of the outter particle in the 1579 # wavefunction renormalization bubble is equal to the 1580 # one of the inner particle. 1581 continue 1582 1583 # If depth is not 0 because of being an external leg and not 1584 # the propagated PDG, then we set it to -1 so that from that 1585 # point we are sure the diagram will not be reckognized as a 1586 # wave-function renormalization. 1587 depth=-1 1588 # When creating a loop leg from exactly two external legs, we 1589 # set the depth to the PDG of the external non-loop line. 1590 if len(legs)==2 and loopline and (legs[0]['depth'],\ 1591 legs[1]['depth'])==(0,0): 1592 if not legs[0]['loop_line']: 1593 depth=legs[0]['id'] 1594 else: 1595 depth=legs[1]['id'] 1596 # In case of two point interactions among two same particle 1597 # we propagate the existing depth 1598 if len(legs)==1 and legs[0]['id']==leg_id: 1599 depth=legs[0]['depth'] 1600 # In all other cases we set the depth to -1 since no 1601 # wave-function renormalization diagram can arise from this 1602 # side of the diagram construction. 1603 1604 mylegs.append((loop_base_objects.DGLoopLeg({'id':leg_id, 1605 'number':number, 1606 'state':state, 1607 'from_group':True, 1608 'depth': depth, 1609 'loop_line': loopline}), 1610 vert_id)) 1611 return mylegs
1612
1613 - def get_combined_vertices(self, legs, vert_ids):
1614 """Allow for selection of vertex ids.""" 1615 1616 looplegs=[leg for leg in legs if leg['loop_line']] 1617 nonlooplegs=[leg for leg in legs if not leg['loop_line']] 1618 1619 # Get rid of all vanishing tadpoles 1620 model=self['process']['model'] 1621 exlegs=[leg for leg in looplegs if leg['depth']==0] 1622 if(len(exlegs)==2): 1623 if(any([part['mass'].lower()=='zero' for pdg,part in \ 1624 model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])): 1625 return [] 1626 1627 1628 # Get rid of some wave-function renormalization diagrams already during 1629 # diagram generation already.In a similar manner as in get_combined_legs. 1630 if(len(legs)==3 and len(looplegs)==2): 1631 depths=(looplegs[0]['depth'],looplegs[1]['depth']) 1632 if (0 in depths) and (-1 not in depths) and depths!=(0,0): 1633 return [] 1634 1635 return vert_ids
1636 1637 # Helper function 1638
1639 - def check_squared_orders(self, sq_order_constrains, user_squared_orders=None):
1640 """ Filters the diagrams according to the constraints on the squared 1641 orders in argument and wether the process has a born or not. """ 1642 1643 diagRef=base_objects.DiagramList() 1644 AllLoopDiagrams=base_objects.DiagramList(self['loop_diagrams']+\ 1645 self['loop_UVCT_diagrams']) 1646 1647 AllBornDiagrams=base_objects.DiagramList(self['born_diagrams']) 1648 if self['process']['has_born']: 1649 diagRef=AllBornDiagrams 1650 else: 1651 diagRef=AllLoopDiagrams 1652 1653 sqorders_types=copy.copy(self['process'].get('sqorders_types')) 1654 1655 # The WEIGHTED order might have been automatically assigned to the 1656 # squared order constraints, so we must assign it a type if not specified 1657 if 'WEIGHTED' not in sqorders_types: 1658 sqorders_types['WEIGHTED']='<=' 1659 1660 if len(diagRef)==0: 1661 # If no born contributes but they were supposed to ( in the 1662 # case of self['process']['has_born']=True) then it means that 1663 # the loop cannot be squared against anything and none should 1664 # contribute either. The squared order constraints are just too 1665 # tight for anything to contribute. 1666 AllLoopDiagrams = base_objects.DiagramList() 1667 1668 1669 # Start by filtering the loop diagrams 1670 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(diagRef, 1671 sq_order_constrains, sqorders_types) 1672 # And now the Born ones if there are any 1673 if self['process']['has_born']: 1674 # We consider both the Born*Born and Born*Loop squared terms here 1675 AllBornDiagrams = AllBornDiagrams.apply_positive_sq_orders( 1676 AllLoopDiagrams+AllBornDiagrams, sq_order_constrains, sqorders_types) 1677 1678 # Now treat the negative squared order constraint (at most one) 1679 neg_orders = [(order, value) for order, value in \ 1680 sq_order_constrains.items() if value<0] 1681 if len(neg_orders)==1: 1682 neg_order, neg_value = neg_orders[0] 1683 # If there is a Born contribution, then the target order will 1684 # be computed over all Born*Born and Born*loop contributions 1685 if self['process']['has_born']: 1686 AllBornDiagrams, target_order =\ 1687 AllBornDiagrams.apply_negative_sq_order( 1688 base_objects.DiagramList(AllLoopDiagrams+AllBornDiagrams), 1689 neg_order,neg_value,sqorders_types[neg_order]) 1690 # Now we must filter the loop diagrams using to the target_order 1691 # computed above from the LO and NLO contributions 1692 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders( 1693 diagRef,{neg_order:target_order}, 1694 {neg_order:sqorders_types[neg_order]}) 1695 1696 # If there is no Born, then the situation is completely analoguous 1697 # to the tree level case since it is simply Loop*Loop 1698 else: 1699 AllLoopDiagrams, target_order = \ 1700 AllLoopDiagrams.apply_negative_sq_order( 1701 diagRef,neg_order,neg_value,sqorders_types[neg_order]) 1702 1703 # Substitute the negative value to this positive one 1704 # (also in the backed up values in user_squared_orders so that 1705 # this change is permanent and we will still have access to 1706 # it at the output stage) 1707 self['process']['squared_orders'][neg_order]=target_order 1708 user_squared_orders[neg_order]=target_order 1709 1710 elif len(neg_orders)>1: 1711 raise MadGraph5Error('At most one negative squared order constraint'+\ 1712 ' can be specified, not %s.'%str(neg_orders)) 1713 1714 if self['process']['has_born']: 1715 self['born_diagrams'] = AllBornDiagrams 1716 self['loop_diagrams']=[diag for diag in AllLoopDiagrams if not \ 1717 isinstance(diag,loop_base_objects.LoopUVCTDiagram)] 1718 self['loop_UVCT_diagrams']=[diag for diag in AllLoopDiagrams if \ 1719 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1720
1721 - def order_diagram_set(self, diag_set, split_orders):
1722 """ This is a helper function for order_diagrams_according_to_split_orders 1723 and intended to be used from LoopHelasAmplitude only""" 1724 1725 # The dictionary below has keys being the tuple (split_order<i>_values) 1726 # and values being diagram lists sharing the same split orders. 1727 diag_by_so = {} 1728 1729 for diag in diag_set: 1730 so_key = tuple([diag.get_order(order) for order in split_orders]) 1731 try: 1732 diag_by_so[so_key].append(diag) 1733 except KeyError: 1734 diag_by_so[so_key]=base_objects.DiagramList([diag,]) 1735 1736 so_keys = diag_by_so.keys() 1737 # Complete the order hierarchy by possibly missing defined order for 1738 # which we set the weight to zero by default (so that they are ignored). 1739 order_hierarchy = self.get('process').get('model').get('order_hierarchy') 1740 order_weights = copy.copy(order_hierarchy) 1741 for so in split_orders: 1742 if so not in order_hierarchy.keys(): 1743 order_weights[so]=0 1744 1745 # Now order the keys of diag_by_so by the WEIGHT of the split_orders 1746 # (and only those, the orders not included in the split_orders do not 1747 # count for this ordering as they could be mixed in any given group). 1748 so_keys = sorted(so_keys, key = lambda elem: (sum([power*order_weights[\ 1749 split_orders[i]] for i,power in enumerate(elem)]))) 1750 1751 # Now put the diagram back, ordered this time, in diag_set 1752 diag_set[:] = [] 1753 for so_key in so_keys: 1754 diag_set.extend(diag_by_so[so_key])
1755 1756
1757 - def order_diagrams_according_to_split_orders(self, split_orders):
1758 """ Reorder the loop and Born diagrams (if any) in group of diagrams 1759 sharing the same coupling orders are put together and these groups are 1760 order in decreasing WEIGHTED orders. 1761 Notice that this function is only called for now by the 1762 LoopHelasMatrixElement instances at the output stage. 1763 """ 1764 1765 # If no split order is present (unlikely since the 'corrected order' 1766 # normally is a split_order by default, then do nothing 1767 if len(split_orders)==0: 1768 return 1769 1770 self.order_diagram_set(self['born_diagrams'], split_orders) 1771 self.order_diagram_set(self['loop_diagrams'], split_orders) 1772 self.order_diagram_set(self['loop_UVCT_diagrams'], split_orders)
1773
1774 #=============================================================================== 1775 # LoopMultiProcess 1776 #=============================================================================== 1777 -class LoopMultiProcess(diagram_generation.MultiProcess):
1778 """LoopMultiProcess: MultiProcess with loop features. 1779 """ 1780 1781 @classmethod
1782 - def get_amplitude_from_proc(cls, proc, **opts):
1783 """ Return the correct amplitude type according to the characteristics 1784 of the process proc """ 1785 return LoopAmplitude({"process": proc},**opts)
1786
1787 #=============================================================================== 1788 # LoopInducedMultiProcess 1789 #=============================================================================== 1790 -class LoopInducedMultiProcess(diagram_generation.MultiProcess):
1791 """Special mode for the LoopInduced.""" 1792 1793 @classmethod
1794 - def get_amplitude_from_proc(cls,proc,**opts):
1795 """ Return the correct amplitude type according to the characteristics of 1796 the process proc """ 1797 return LoopAmplitude({"process": proc, 'has_born':False},**opts)
1798