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