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