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