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