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

Source Code for Module madgraph.loop.loop_helas_objects

   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   
  16  """Definitions of objects inheriting from the classes defined in 
  17  helas_objects.py and which have special attributes and function  
  18  devoted to the treatment of Loop processes""" 
  19   
  20  import array 
  21  import copy 
  22  import logging 
  23  import itertools 
  24  import math 
  25   
  26  import aloha 
  27  import aloha.create_aloha as create_aloha 
  28   
  29  from madgraph import MadGraph5Error 
  30  import madgraph.core.base_objects as base_objects 
  31  import madgraph.loop.loop_base_objects as loop_base_objects 
  32  import madgraph.core.diagram_generation as diagram_generation 
  33  import madgraph.loop.loop_diagram_generation as loop_diagram_generation 
  34  import madgraph.core.color_amp as color_amp 
  35  import madgraph.loop.loop_color_amp as loop_color_amp 
  36  import madgraph.core.color_algebra as color 
  37  import madgraph.core.helas_objects as helas_objects 
  38   
  39  #=============================================================================== 
  40  #  
  41  #=============================================================================== 
  42   
  43  logger = logging.getLogger('madgraph.helas_objects') 
44 45 #=============================================================================== 46 # LoopUVCTHelasAmplitude 47 #=============================================================================== 48 -class LoopHelasUVCTAmplitude(helas_objects.HelasAmplitude):
49 """LoopHelasUVCTAmplitude object, behaving exactly as an amplitude except that 50 it also contains additional vertices with coupling constants corresponding 51 to the 'UVCTVertices' defined in the 'UVCTVertices ' of the 52 loop_base_objects.LoopUVCTDiagram of the LoopAmplitude. These are stored 53 in the additional attribute 'UVCT_interaction_ids' of this class. 54 """ 55 56 # Customized constructor
57 - def __init__(self, *arguments):
58 """Constructor for the LoopHelasAmplitude. For now, it works exactly 59 as for the HelasMatrixElement one.""" 60 61 if arguments: 62 super(LoopHelasUVCTAmplitude, self).__init__(*arguments) 63 else: 64 super(LoopHelasUVCTAmplitude, self).__init__()
65
66 - def default_setup(self):
67 """Default values for all properties""" 68 69 super(LoopHelasUVCTAmplitude,self).default_setup() 70 71 # Store interactions ID of the UV counterterms related to this diagram 72 self['UVCT_couplings'] = [] 73 self['UVCT_orders'] = {}
74
75 - def filter(self, name, value):
76 """Filter for valid LoopHelasAmplitude property values.""" 77 78 if name=='UVCT_couplings': 79 if not isinstance(value, list): 80 raise self.PhysicsObjectError, \ 81 "%s is not a valid list for UVCT_couplings" % str(value) 82 for id in value: 83 if not isinstance(id, str) and not isinstance(id, int): 84 raise self.PhysicsObjectError, \ 85 "%s is not a valid string or integer for UVCT_couplings" % str(value) 86 87 if name == 'UVCT_orders': 88 if not isinstance(value, dict): 89 raise self.PhysicsObjectError, \ 90 "%s is not a valid dictionary" % str(value) 91 92 if name == 'type': 93 if not isinstance(value, str): 94 raise self.PhysicsObjectError, \ 95 "%s is not a valid string" % str(value) 96 97 else: 98 return super(LoopHelasUVCTAmplitude,self).filter(name, value)
99
100 - def get_sorted_keys(self):
101 """Return LoopHelasAmplitude property names as a nicely sorted list.""" 102 103 return super(LoopHelasUVCTAmplitude,self).get_sorted_keys()+\ 104 ['UVCT_couplings','UVCT_orders','type'] 105 106 return True
107
108 - def get_call_key(self):
109 """ Exactly as a regular HelasAmplitude except that here we must add 110 an entry to mutliply the final result by the coupling constants of the 111 interaction in UVCT_couplings if there are any""" 112 original_call_key = super(LoopHelasUVCTAmplitude,self).get_call_key() 113 114 if self.get_UVCT_couplings()=='1.0d0': 115 return original_call_key 116 else: 117 return (original_call_key[0],original_call_key[1],'UVCT')
118
119 - def get_used_UVCT_couplings(self):
120 """ Returns a list of the string UVCT_couplings defined for this 121 amplitudes. """ 122 return [coupl for coupl in self['UVCT_couplings'] if \ 123 isinstance(coupl,str)]
124
125 - def get_UVCT_couplings(self):
126 """ Returns the string corresponding to the overall UVCT coupling which 127 factorize this amplitude """ 128 if self['UVCT_couplings']==[]: 129 return '1.0d0' 130 131 answer=[] 132 integer_sum=0 133 for coupl in list(set(self['UVCT_couplings'])): 134 if isinstance(coupl,int): 135 integer_sum+=coupl 136 else: 137 answer.append(str(len([1 for c in self['UVCT_couplings'] if \ 138 c==coupl]))+'.0d0*'+coupl) 139 if integer_sum!=0: 140 answer.append(str(integer_sum)+'.0d0') 141 if answer==[] and (integer_sum==0 or integer_sum==1): 142 return '1.0d0' 143 else: 144 return '+'.join(answer)
145
146 - def get_base_diagram(self, wf_dict, vx_list = [], optimization = 1):
147 """Return the loop_base_objects.LoopUVCTDiagram which corresponds to this 148 amplitude, using a recursive method for the wavefunctions.""" 149 150 vertices = super(LoopHelasUVCTAmplitude,self).get_base_diagram(\ 151 wf_dict, vx_list, optimization)['vertices'] 152 153 return loop_base_objects.LoopUVCTDiagram({'vertices': vertices, \ 154 'UVCT_couplings': self['UVCT_couplings'], \ 155 'UVCT_orders': self['UVCT_orders'], \ 156 'type': self['type']})
157
158 - def get_helas_call_dict(self, index=1, OptimizedOutput=False,\ 159 specifyHel=True):
160 """ return a dictionary to be used for formatting 161 HELAS call. """ 162 163 164 out = helas_objects.HelasAmplitude.get_helas_call_dict(self, 165 index=index,OptimizedOutput=OptimizedOutput) 166 out['uvct'] = self.get_UVCT_couplings() 167 return out
168
169 #=============================================================================== 170 # LoopHelasAmplitude 171 #=============================================================================== 172 -class LoopHelasAmplitude(helas_objects.HelasAmplitude):
173 """LoopHelasAmplitude object, behaving exactly as an amplitude except that 174 it also contains loop wave-functions closed on themselves, building an 175 amplitude corresponding to the closed loop. 176 """ 177 178 # Customized constructor
179 - def __init__(self, *arguments):
180 """Constructor for the LoopHelasAmplitude. For now, it works exactly 181 as for the HelasMatrixElement one.""" 182 183 if arguments: 184 super(LoopHelasAmplitude, self).__init__(*arguments) 185 else: 186 super(LoopHelasAmplitude, self).__init__()
187
188 - def is_equivalent(self, other):
189 """Comparison between different LoopHelasAmplitude in order to recognize 190 which ones are equivalent at the level of the file output. 191 I decided not to overload the operator __eq__ to be sure not to interfere 192 with other functionalities of the code.""" 193 194 if(len(self.get('wavefunctions'))!=len(other.get('wavefunctions')) or 195 len(self.get('amplitudes'))!=len(other.get('amplitudes')) or 196 [len(wf.get('coupling')) for wf in self.get('wavefunctions')]!= 197 [len(wf.get('coupling')) for wf in other.get('wavefunctions')] or 198 [len(amp.get('coupling')) for amp in self.get('amplitudes')]!= 199 [len(amp.get('coupling')) for amp in other.get('amplitudes')]): 200 return False 201 202 wfArgsToCheck = ['fermionflow','lorentz','state','onshell','spin',\ 203 'is_part','self_antipart','color'] 204 for arg in wfArgsToCheck: 205 if [wf.get(arg) for wf in self.get('wavefunctions')]!=\ 206 [wf.get(arg) for wf in other.get('wavefunctions')]: 207 return False 208 209 if [wf.find_outgoing_number() for wf in self.get('wavefunctions')]!=\ 210 [wf.find_outgoing_number() for wf in other.get('wavefunctions')]: 211 return False 212 213 ampArgsToCheck = ['lorentz',] 214 for arg in ampArgsToCheck: 215 if [amp.get(arg) for amp in self.get('amplitudes')]!=\ 216 [amp.get(arg) for amp in other.get('amplitudes')]: 217 return False 218 219 # Finally just check that the loop and external mother wavefunctions 220 # of the loop wavefunctions and loop amplitudes arrive at the same places 221 # in both self and other. The characteristics of the mothers is irrelevant, 222 # the only thing that matters is that the loop-type and external-type mothers 223 # are in the same order. 224 if [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in self.get('wavefunctions')]!=\ 225 [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in other.get('wavefunctions')]: 226 return False 227 if [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in self.get('amplitudes')]!=\ 228 [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in other.get('amplitudes')]: 229 return False 230 231 return True
232
233 - def default_setup(self):
234 """Default values for all properties""" 235 236 super(LoopHelasAmplitude,self).default_setup() 237 238 # Store the wavefunctions building this loop 239 self['wavefunctions'] = helas_objects.HelasWavefunctionList() 240 # In this first version, a LoopHelasAmplitude is always built out of 241 # a single amplitude, it was realized later that one would never need 242 # more than one. But until now we kept the structure as such. 243 self['amplitudes'] = helas_objects.HelasAmplitudeList() 244 # The pairing is used for the output to know at each loop interactions 245 # how many non-loop mothers are necessary. This list is ordered as the 246 # helas calls building the loop 247 self['pairing'] = [] 248 # To keep the 'type' (L-cut particle ID) of the LoopDiagram this 249 # Loop amplitude tracks. 250 # In principle this info is recoverable from the loop wfs. 251 self['type'] = -1 252 # The loop_group_id gives the place of this LoopHelasAmplitude 253 # in the 'loop_groups' attribute of the LoopHelasMatrixElement it belongs 254 # to. 255 self['loop_group_id']=-1 256 # To store the symmetry factor of the loop 257 self['loopsymmetryfactor'] = 0
258 259 # Enhanced get function
260 - def get(self, name):
261 """Get the value of the property name.""" 262 263 if name == 'loopsymmetryfactor' and not self[name]: 264 self.calculate_loopsymmetryfactor() 265 266 return super(LoopHelasAmplitude, self).get(name)
267
268 - def filter(self, name, value):
269 """Filter for valid LoopHelasAmplitude property values.""" 270 271 if name=='wavefunctions': 272 if not isinstance(value, helas_objects.HelasWavefunctionList): 273 raise self.PhysicsObjectError, \ 274 "%s is not a valid list of HelasWaveFunctions" % str(value) 275 for wf in value: 276 if not wf['is_loop']: 277 raise self.PhysicsObjectError, \ 278 "Wavefunctions from a LoopHelasAmplitude must be from a loop." 279 280 elif name=='amplitudes': 281 if not isinstance(value, helas_objects.HelasAmplitudeList): 282 raise self.PhysicsObjectError, \ 283 "%s is not a valid list of HelasAmplitudes" % str(value) 284 285 elif name=='type' or name=='loop_group_id': 286 if not isinstance(value, int): 287 raise self.PhysicsObjectError, \ 288 "%s is not a valid integer for the attribute '%s'" %(str(value),name) 289 290 elif name == 'loopsymmetryfactor': 291 if not isinstance(value, int): 292 raise self.PhysicsObjectError, \ 293 "%s is not a valid integer for loopsymmetryfactor" % \ 294 str(value) 295 else: 296 return super(LoopHelasAmplitude,self).filter(name, value) 297 298 return True
299
300 - def get_sorted_keys(self):
301 """Return LoopHelasAmplitude property names as a nicely sorted list.""" 302 303 return super(LoopHelasAmplitude,self).get_sorted_keys()+\ 304 ['wavefunctions', 'amplitudes','loop_group_id']
305
306 - def get_lcut_size(self):
307 """ Return the wavefunction size (i.e. number of elements) based on the 308 spin of the l-cut particle """ 309 310 return helas_objects.HelasWavefunction.spin_to_size( 311 self.get_final_loop_wavefunction().get('spin'))
312
314 """ Return the starting external loop mother of this loop helas amplitude. 315 It is the loop wavefunction of the l-cut leg one.""" 316 317 loop_wf=self.get_final_loop_wavefunction() 318 loop_wf_mother=loop_wf.get_loop_mother() 319 while loop_wf_mother: 320 loop_wf=loop_wf_mother 321 loop_wf_mother=loop_wf.get_loop_mother() 322 return loop_wf
323
325 """Return the non-external loop mother of the helas amplitude building 326 this loop amplitude""" 327 328 final_lwf=[lwf for lwf in self.get('amplitudes')[0].get('mothers') if \ 329 lwf.get('mothers')] 330 if len(final_lwf)!=1: 331 raise MadGraph5Error, 'The helas amplitude building the helas loop'+\ 332 ' amplitude should be made of exactly one loop wavefunctions'+\ 333 ' with mothers.' 334 return final_lwf[0]
335
336 - def get_base_diagram(self, wf_dict, vx_list = [], optimization = 1):
337 """Return the loop_base_objects.LoopDiagram which corresponds to this 338 amplitude, using a recursive method for the wavefunctions. 339 Remember that this diagram is not tagged and structures are not 340 recognized.""" 341 342 vertices = self['amplitudes'][0].get_base_diagram(\ 343 wf_dict, vx_list, optimization)['vertices'] 344 345 return loop_base_objects.LoopDiagram({'vertices': vertices,\ 346 'type':self['type']})
347
348 - def set_mothers_and_pairing(self):
349 """ Sets the mothers of this amplitude in the same order as they will 350 be used in the arguments of the helas calls building this loop""" 351 352 if len(self.get('amplitudes'))!=1: 353 self.PhysicsObjectError, \ 354 "HelasLoopAmplitude is for now designed to contain only one \ 355 HelasAmplitude" 356 357 self.set('mothers',helas_objects.HelasWavefunctionList()) 358 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]: 359 mothersList=[wf for wf in lwf.get('mothers') if not wf['is_loop']] 360 self['mothers'].extend(mothersList) 361 self['pairing'].append(len(mothersList))
362
363 - def get_denominators(self):
364 """ Returns the denominator structure as a tuple (tupleA, tupleB) whose 365 elements are of this form ((external_part_ids),mass) where 366 external_part_ids are all the leg id building the momentum flowing in 367 the loop, i.e: 368 D_i=(q+Sum(p_j,j))^2 - m^2 369 """ 370 371 denoms=[] 372 last_loop_wf=self.get_final_loop_wavefunction() 373 last_loop_wf_mother=last_loop_wf.get_loop_mother() 374 while last_loop_wf_mother: 375 denoms.append((tuple(last_loop_wf.get_struct_external_leg_ids()), 376 last_loop_wf.get('mass'))) 377 last_loop_wf=last_loop_wf_mother 378 last_loop_wf_mother=last_loop_wf.get_loop_mother() 379 denoms.reverse() 380 381 return tuple(denoms)
382
383 - def get_masses(self):
384 """ Returns the list of the masses of the loop particles as they should 385 appear for cuttools (L-cut particles specified last) """ 386 387 masses=[] 388 if not aloha.complex_mass: 389 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]: 390 masses.append(lwf.get('mass')) 391 else: 392 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]: 393 if (lwf.get('width') == 'ZERO' or lwf.get('mass') == 'ZERO'): 394 masses.append(lwf.get('mass')) 395 else: 396 masses.append('CMASS_%s' % lwf.get('mass')) 397 return masses
398
399 - def get_couplings(self):
400 """ Returns the list of the couplings of the different helas objects 401 building this HelasLoopAmplitude. They are ordered as they will appear 402 in the helas calls.""" 403 404 return (sum([wf.get('coupling') for wf in self.get('wavefunctions') \ 405 if wf.get('coupling')!=['none']],[])\ 406 +sum([amp.get('coupling') for amp in self.get('amplitudes') if \ 407 amp.get('coupling')!=['none']],[]))
408
409 - def get_helas_call_dict(self, OptimizedOutput=False,specifyHel=True):
410 """ return a dictionary to be used for formatting 411 HELAS call. """ 412 output = {} 413 output['numLoopLines']='_%d'%(len(self.get('wavefunctions'))-2) 414 # Plus one below because fortran array start at 1. 415 output['loop_group_id']=self.get('loop_group_id')+1 416 output['ampNumber']=self.get('amplitudes')[0].get('number') 417 if len(self.get('mothers'))!=len(self.get('pairing')): 418 output['numMotherWfs']='_%d'%len(self.get('mothers')) 419 else: 420 output['numMotherWfs']='' 421 for i, pairing in enumerate(self.get('pairing')): 422 output["Pairing%d"%i]=pairing 423 output['numCouplings']='_%d'%len(self.get('coupling')) 424 output['numeratorNumber']=self.get('number') 425 output["LoopRank"]=self.get_analytic_info('wavefunction_rank') 426 if OptimizedOutput: 427 if self.get('loop_group_id')==-1: 428 output['loopNumber']=self.get('number') 429 else: 430 output['loopNumber']=self.get('loop_group_id')+1 431 else: 432 output['loopNumber']=self.get('amplitudes')[0].get('number') 433 for i , wf in enumerate(self.get('mothers')): 434 output["MotherID%d"%(i+1)]=wf.get('number') 435 for i , mass in enumerate(self.get_masses()): 436 output["LoopMass%d"%(i+1)]=mass 437 for i , coupling in enumerate(self.get('coupling')): 438 output["LoopCoupling%d"%(i+1)]=coupling 439 output["LoopSymmetryFactor"]=self.get('loopsymmetryfactor') 440 return output
441
442 - def get_call_key(self):
443 """ The helas call to a loop is simple and only depends on the number 444 of loop lines and mothers. This how it is reflected in the call key. """ 445 446 return ("LOOP",len(self.get('wavefunctions'))-2,\ 447 len(self.get('mothers')),len(self.get('coupling')))
448
449 - def get_orders(self):
450 """ Compute the orders building this loop amplitude only (not from the 451 struct wavefunctions. Uses the cached result if available.""" 452 453 if self.get('orders') != {}: 454 return self.get('orders') 455 else: 456 coupling_orders = {} 457 last_wf = self.get_final_loop_wavefunction() 458 while last_wf.get_loop_mother()!=None: 459 for order in last_wf.get('orders').keys(): 460 try: 461 coupling_orders[order] += last_wf.get('orders')[order] 462 except Exception: 463 coupling_orders[order] = last_wf.get('orders')[order] 464 last_wf = last_wf.get_loop_mother() 465 return coupling_orders
466
467 - def get_analytic_info(self, info, alohaModel=None):
468 """ Returns an analytic information of the loop numerator, for example 469 the 'wavefunction_rank' i.e. the maximum power to which the loop momentum 470 is elevated in the loop numerator. All analytic pieces of information 471 are for now identical to the one retrieved from the final_loop_wavefunction.""" 472 473 return self.get_final_loop_wavefunction().\ 474 get_analytic_info(info, alohaModel)
475
476 - def compute_analytic_information(self,alohaModel):
477 """ Make sure that all analytic pieces of information about this 478 wavefunction are computed so that they can be recycled later, typically 479 without the need of specifying an alohaModel. For now, all analytic 480 information about the loop helas amplitude are identical to those of the 481 final loop wavefunction.""" 482 483 self.get_final_loop_wavefunction().compute_analytic_information(\ 484 alohaModel)
485
486 - def calculate_fermionfactor(self):
487 """ The fermion factor is not implemented for this object but in the 488 subamplitude""" 489 self['fermion_factor']=0 490 for amp in self.get('amplitudes'): 491 amp.get('fermionfactor')
492
494 """ Calculate the loop symmetry factor. For one-loop matrix elements, 495 it is always 2 for bubble with identical particles and 1 otherwise.""" 496 497 self['loopsymmetryfactor']=1 498 499 # Make sure all particles are self-conjugated and identical in the loop 500 if len(set([wf.get('pdg_code') for wf in self.get('wavefunctions')]))==1 and \ 501 not any([not wf.get('self_antipart') for wf in self.get('wavefunctions')]): 502 # Now make sure we only include tadpoles or bubble 503 if len(self.get('wavefunctions')) in [3,4]: 504 self['loopsymmetryfactor']=2
505
506 #=============================================================================== 507 # LoopHelasDiagram 508 #=============================================================================== 509 -class LoopHelasDiagram(helas_objects.HelasDiagram):
510 """LoopHelasDiagram object, behaving exactly as a Diagram except that 511 it has a couple of additional functions which can reconstruct and 512 handle loop amplitudes. 513 """ 514
515 - def get_regular_amplitudes(self):
516 """ Quick access to ALL non-loop amplitudes, including those which are 517 inside the LoopAmplitudes defined in this diagram.""" 518 519 ampList=helas_objects.HelasAmplitudeList() 520 for loopAmp in self.get_loop_amplitudes(): 521 ampList.extend(loopAmp['amplitudes']) 522 ampList.extend(self.get_ct_amplitudes()) 523 return ampList
524
525 - def get_ct_amplitudes(self):
526 """ Quick access to the regular amplitudes defined directly in this 527 diagram (not in the LoopAmplitudes). Usually they correspond to the 528 counter-terms. """ 529 530 return helas_objects.HelasAmplitudeList([amp for amp in \ 531 self['amplitudes'] if not isinstance(amp, LoopHelasAmplitude)])
532
533 - def get_loop_amplitudes(self):
534 """ Quick access to the loop amplitudes only""" 535 536 return helas_objects.HelasAmplitudeList([amp for amp in \ 537 self['amplitudes'] if isinstance(amp, LoopHelasAmplitude)])
538
539 - def get_loop_UVCTamplitudes(self):
540 """ Quick access to the loop amplitudes only""" 541 542 return helas_objects.HelasAmplitudeList([amp for amp in \ 543 self['amplitudes'] if isinstance(amp, LoopHelasUVCTAmplitude)])
544
545 #=============================================================================== 546 # LoopHelasMatrixElement 547 #=============================================================================== 548 -class LoopHelasMatrixElement(helas_objects.HelasMatrixElement):
549 """LoopHelasMatrixElement: list of processes with identical Helas 550 calls, and the list of LoopHelasDiagrams associated with the processes. 551 It works as for the HelasMatrixElement except for the loop-related features 552 which are defined here. """ 553
554 - def default_setup(self):
555 """Default values for all properties""" 556 557 super(LoopHelasMatrixElement,self).default_setup() 558 559 # Store separately the color basis for the loop and born diagrams 560 self['born_color_basis'] = loop_color_amp.LoopColorBasis() 561 self['loop_color_basis'] = loop_color_amp.LoopColorBasis() 562 # To store the grouping of HelasLoopAmplitudes which share the same 563 # denominators. 564 # List of (key,value) where keys are tuples corresponding to the 565 # denominator structures (see get_denominators() of LoopHelasAmplitudes) 566 # and values are lists of LoopHelasAmplitudes. It is not a dictionary 567 # because we want for each LoopHelasAmplitude to assign a 'loop_group_id' 568 # which indicates where it is placed in this list 569 self['loop_groups'] = []
570
571 - def filter(self, name, value):
572 """Filter for valid diagram property values.""" 573 574 if name=='born_color_basis' or name=='loop_color_basis': 575 if not isinstance(value,color_amp.ColorBasis): 576 raise self.PhysicsObjectError, \ 577 "%s is not a valid color basis" % str(value) 578 elif name=='loop_groups': 579 if not isinstance(value,list): 580 raise self.PhysicsObjectError, \ 581 "%s is not a valid list"%str(value) 582 for (dkey, dvalue) in value: 583 if not isinstance(dvalue,helas_objects.HelasAmplitudeList): 584 raise self.PhysicsObjectError, \ 585 "%s is not a valid HelasAmplitudeList."%str(dvalue) 586 if not isinstance(dkey,tuple): 587 raise self.PhysicsObjectError, \ 588 "%s is not a valid tuple."%str(dkey) 589 else: 590 return super(LoopHelasMatrixElement,self).filter(name, value) 591 592 return True
593
594 - def get(self,name):
595 """Overload in order to return the loop_color_basis when simply asked 596 for color_basis. The setter is not updated to avoid side effects.""" 597 598 if name=='color_basis': 599 return self['loop_color_basis'] 600 elif name=='loop_groups': 601 if not self['loop_groups']: 602 self.identify_loop_groups() 603 return self['loop_groups'] 604 else: 605 return super(LoopHelasMatrixElement,self).get(name)
606
607 - def identify_loop_groups(self):
608 """ Identify what are the loops sharing the same denominators and put 609 them together in the 'loop_groups' attribute of this object. """ 610 611 identified_denom_structures=[] 612 for lamp in [lamp for ldiag in self.get_loop_diagrams() for lamp in \ 613 ldiag.get_loop_amplitudes()]: 614 denom_structure=lamp.get_denominators() 615 try: 616 denom_index=identified_denom_structures.index(denom_structure) 617 self['loop_groups'][denom_index][1].append(lamp) 618 except ValueError: 619 denom_index=len(self['loop_groups']) 620 self['loop_groups'].append((denom_structure, 621 helas_objects.HelasAmplitudeList([lamp,]))) 622 identified_denom_structures.append(denom_structure) 623 lamp.set('loop_group_id',denom_index) 624 # Now make sure that the loop amplitudes lists in values of the 625 # dictionary are ordering in decreasing ranks, so that the first one 626 # (later to be the reference amplitude) has the highest rank 627 self['loop_groups']=[(group[0],helas_objects.HelasAmplitudeList( 628 sorted(group[1],key=lambda lamp: \ 629 lamp.get_analytic_info('wavefunction_rank'),reverse=True))) 630 for group in self['loop_groups']] 631 # Also, order them so to put first the groups with the smallest 632 # reference amplitude number 633 self['loop_groups']=sorted(self['loop_groups'],key=lambda group: \ 634 group[1][0].get('number')) 635 self.update_loop_group_ids()
636
637 - def reuse_outdated_wavefunctions(self, helas_diagrams):
638 """ Make sure never to use this optimization in the loop context.""" 639 # But just make sure that me_id is simply the number. 640 for diag in helas_diagrams: 641 for wf in diag['wavefunctions']: 642 wf.set('me_id',wf.get('number')) 643 644 return helas_diagrams
645
646 - def update_loop_group_ids(self):
647 """ Make sure that the attribute 'loop_group_id' of all loop amplitudes 648 in the 'loop_groups' list is correct given the order of 'loop_groups'""" 649 650 for i, group in enumerate(self['loop_groups']): 651 for lamp in group[1]: 652 lamp.set('loop_group_id',i)
653
654 - def process_color(self):
655 """ Perform the simple color processing from a single matrix element 656 (without optimization then). This is called from the initialization 657 and overloaded here in order to have the correct treatment """ 658 659 # Generation of helas objects is assumed to be finished so we can relabel 660 # optimaly the 'number' attribute of these objects. 661 self.relabel_helas_objects() 662 self.get('loop_color_basis').build_loop(self.get('base_amplitude')) 663 if self.get('base_amplitude')['process']['has_born']: 664 self.get('born_color_basis').build_born(self.get('base_amplitude')) 665 self.set('color_matrix',\ 666 color_amp.ColorMatrix(self.get('loop_color_basis'),\ 667 self.get('born_color_basis'))) 668 else: 669 self.set('color_matrix',\ 670 color_amp.ColorMatrix(self.get('loop_color_basis')))
671
672 - def get_sorted_keys(self):
673 """Return particle property names as a nicely sorted list.""" 674 675 return ['processes', 'identical_particle_factor', 676 'diagrams', 'born_color_basis','loop_color_basis', 677 'color_matrix','base_amplitude', 'has_mirror_process', 678 'loop_groups']
679 680 # Customized constructor
681 - def __init__(self, amplitude=None, optimization=1, 682 decay_ids=[], gen_color=True, optimized_output=False):
683 """Constructor for the LoopHelasMatrixElement. For now, it works exactly 684 as for the HelasMatrixElement one.""" 685 self.optimized_output=optimized_output 686 super(LoopHelasMatrixElement, self).__init__(amplitude, optimization,\ 687 decay_ids, gen_color)
688 689 # Comparison between different amplitudes, to allow check for 690 # identical processes. Note that we are then not interested in 691 # interaction id, but in all other properties. 692
693 - def __eq__(self, other):
694 """Comparison between different loop matrix elements. It works exactly as for 695 the HelasMatrixElement for now.""" 696 697 return super(LoopHelasMatrixElement,self).__eq__(other)
698
699 - def __ne__(self, other):
700 """Overloading the nonequality operator, to make comparison easy""" 701 return not self.__eq__(other)
702
703 - def generate_helas_diagrams(self, amplitude, optimization=1, 704 decay_ids=[]):
705 """Starting from a list of LoopDiagrams from the diagram 706 generation, generate the corresponding LoopHelasDiagrams, i.e., 707 the wave functions and amplitudes (for the loops and their R2 and UV 708 counterterms). Choose between default optimization (= 1, maximum 709 recycling of wavefunctions) or no optimization (= 0, no recycling of 710 wavefunctions, useful for GPU calculations with very restricted memory). 711 712 Note that we need special treatment for decay chains, since 713 the end product then is a wavefunction, not an amplitude. 714 """ 715 716 assert isinstance(amplitude, loop_diagram_generation.LoopAmplitude), \ 717 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement" 718 assert isinstance(optimization, int), \ 719 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement" 720 721 structures = amplitude.get('structure_repository') 722 723 process = amplitude.get('process') 724 has_born = amplitude.get('has_born') 725 726 model = process.get('model') 727 728 # First make sure that the 'split_orders' are ordered according to their 729 # weight. 730 self.sort_split_orders(self.get('processes')[0].get('split_orders')) 731 732 # Before starting, and if split_orders are defined in the amplitude 733 # process, we must reorder the generated diagrams so as to put together 734 # all those which share the same coupling orders. Then, we sort these 735 # *group of diagrams* in decreasing WEIGHTED order, so that the 736 # leading contributions are placed first (I will therfore be possible 737 # to compute them only, saving the time of the rest of the computation) 738 amplitude.order_diagrams_according_to_split_orders(\ 739 self.get('processes')[0].get('split_orders')) 740 741 # All the previously defined wavefunctions 742 wavefunctions = [] 743 744 # List of dictionaries from struct ID to wave function, 745 # keeps track of the structures already scanned. 746 # The key is the struct ID and the value infos is the tuple 747 # (wfs, colorlists). 'wfs' is the list of wavefunctions, 748 # one for each color-lorentz structure of the FDStructure. 749 # Same for the 'colorlists', everything appearing 750 # in the same order in these lists 751 structID_to_infos = {} 752 753 # List of minimal information for comparison with previous 754 # wavefunctions 755 wf_mother_arrays = [] 756 # Keep track of wavefunction number 757 wf_number = 0 758 759 # Generate wavefunctions for the external particles 760 external_wavefunctions = dict([(leg.get('number'), 761 helas_objects.HelasWavefunction(\ 762 leg, 0, model, decay_ids)) \ 763 for leg in process.get('legs')]) 764 765 # To store the starting external loop wavefunctions needed 766 # (They are never output so they are not in the diagrams wavefunctions) 767 external_loop_wfs_dict={} 768 769 # For initial state bosons, need to flip part-antipart 770 # since all bosons should be treated as outgoing 771 for key in external_wavefunctions.keys(): 772 wf = external_wavefunctions[key] 773 if wf.is_boson() and wf.get('state') == 'initial' and \ 774 not wf.get('self_antipart'): 775 wf.set('is_part', not wf.get('is_part')) 776 777 # For initial state particles, need to flip PDG code (if has 778 # antipart) 779 for key in external_wavefunctions.keys(): 780 wf = external_wavefunctions[key] 781 if wf.get('leg_state') == False and \ 782 not wf.get('self_antipart'): 783 wf.flip_part_antipart() 784 785 # Initially, have one wavefunction for each external leg. 786 wf_number = len(process.get('legs')) 787 788 # Now go through the diagrams, looking for undefined wavefunctions 789 790 helas_diagrams = helas_objects.HelasDiagramList() 791 792 # Keep track of amplitude number and diagram number 793 amplitude_number = 0 794 diagram_number = 0 795 796 def process_born_diagram(diagram, wfNumber, amplitudeNumber, UVCTdiag=False): 797 """ Helper function to process a born diagrams exactly as it is done in 798 HelasMatrixElement for tree-level diagrams. This routine can also 799 process LoopUVCTDiagrams, and if so the argument UVCTdiag must be set 800 to true""" 801 802 # List of dictionaries from leg number to wave function, 803 # keeps track of the present position in the tree. 804 # Need one dictionary per coupling multiplicity (diagram) 805 number_to_wavefunctions = [{}] 806 807 # Need to keep track of the color structures for each amplitude 808 color_lists = [[]] 809 810 # Initialize wavefunctions for this diagram 811 diagram_wavefunctions = helas_objects.HelasWavefunctionList() 812 813 vertices = copy.copy(diagram.get('vertices')) 814 815 # Single out last vertex, since this will give amplitude 816 lastvx = vertices.pop() 817 818 # Go through all vertices except the last and create 819 # wavefunctions 820 for vertex in vertices: 821 822 # In case there are diagrams with multiple Lorentz/color 823 # structures, we need to keep track of the wavefunctions 824 # for each such structure separately, and generate 825 # one HelasDiagram for each structure. 826 # We use the array number_to_wavefunctions to keep 827 # track of this, with one dictionary per chain of 828 # wavefunctions 829 # Note that all wavefunctions relating to this diagram 830 # will be written out before the first amplitude is written. 831 new_number_to_wavefunctions = [] 832 new_color_lists = [] 833 for number_wf_dict, color_list in zip(number_to_wavefunctions, 834 color_lists): 835 legs = copy.copy(vertex.get('legs')) 836 last_leg = legs.pop() 837 # Generate list of mothers from legs 838 mothers = self.getmothers(legs, number_wf_dict, 839 external_wavefunctions, 840 wavefunctions, 841 diagram_wavefunctions) 842 inter = model.get('interaction_dict')[vertex.get('id')] 843 844 # Now generate new wavefunction for the last leg 845 846 # Need one amplitude for each color structure, 847 done_color = {} # store link to color 848 for coupl_key in sorted(inter.get('couplings').keys()): 849 color = coupl_key[0] 850 if color in done_color: 851 wf = done_color[color] 852 wf.get('coupling').append(inter.get('couplings')[coupl_key]) 853 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 854 continue 855 wf = helas_objects.HelasWavefunction(last_leg, \ 856 vertex.get('id'), model) 857 wf.set('coupling', [inter.get('couplings')[coupl_key]]) 858 if inter.get('color'): 859 wf.set('inter_color', inter.get('color')[coupl_key[0]]) 860 done_color[color] = wf 861 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 862 wf.set('color_key', color) 863 wf.set('mothers',mothers) 864 # Need to set incoming/outgoing and 865 # particle/antiparticle according to the fermion flow 866 # of mothers 867 wf.set_state_and_particle(model) 868 869 # Need to check for clashing fermion flow due to 870 # Majorana fermions, and modify if necessary 871 # Also need to keep track of the wavefunction number. 872 wf, wfNumber = wf.check_and_fix_fermion_flow(\ 873 wavefunctions, 874 diagram_wavefunctions, 875 external_wavefunctions, 876 wfNumber) 877 # Create new copy of number_wf_dict 878 new_number_wf_dict = copy.copy(number_wf_dict) 879 # Store wavefunction 880 try: 881 wf = diagram_wavefunctions[\ 882 diagram_wavefunctions.index(wf)] 883 except ValueError: 884 # Update wf number 885 wfNumber = wfNumber + 1 886 wf.set('number', wfNumber) 887 try: 888 # Use wf_mother_arrays to locate existing 889 # wavefunction 890 wf = wavefunctions[wf_mother_arrays.index(\ 891 wf.to_array())] 892 # Since we reuse the old wavefunction, reset 893 # wfNumber 894 wfNumber = wfNumber - 1 895 except ValueError: 896 diagram_wavefunctions.append(wf) 897 898 new_number_wf_dict[last_leg.get('number')] = wf 899 900 # Store the new copy of number_wf_dict 901 new_number_to_wavefunctions.append(\ 902 new_number_wf_dict) 903 # Add color index and store new copy of color_lists 904 new_color_list = copy.copy(color_list) 905 new_color_list.append(coupl_key[0]) 906 new_color_lists.append(new_color_list) 907 908 number_to_wavefunctions = new_number_to_wavefunctions 909 color_lists = new_color_lists 910 911 # Generate all amplitudes corresponding to the different 912 # copies of this diagram 913 if not UVCTdiag: 914 helas_diagram = helas_objects.HelasDiagram() 915 else: 916 helas_diagram = LoopHelasDiagram() 917 918 for number_wf_dict, color_list in zip(number_to_wavefunctions, 919 color_lists): 920 921 # Now generate HelasAmplitudes from the last vertex. 922 if lastvx.get('id'): 923 inter = model.get_interaction(lastvx.get('id')) 924 keys = sorted(inter.get('couplings').keys()) 925 pdg_codes = [p.get_pdg_code() for p in \ 926 inter.get('particles')] 927 else: 928 # Special case for decay chain - amplitude is just a 929 # placeholder for replaced wavefunction 930 inter = None 931 keys = [(0, 0)] 932 pdg_codes = None 933 934 # Find mothers for the amplitude 935 legs = lastvx.get('legs') 936 mothers = self.getmothers(legs, number_wf_dict, 937 external_wavefunctions, 938 wavefunctions, 939 diagram_wavefunctions).\ 940 sort_by_pdg_codes(pdg_codes, 0)[0] 941 # Need to check for clashing fermion flow due to 942 # Majorana fermions, and modify if necessary 943 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions, 944 diagram_wavefunctions, 945 external_wavefunctions, 946 None, 947 wfNumber, 948 False, 949 number_to_wavefunctions) 950 done_color = {} 951 for i, coupl_key in enumerate(keys): 952 color = coupl_key[0] 953 if inter and color in done_color.keys(): 954 amp = done_color[color] 955 amp.get('coupling').append(inter.get('couplings')[coupl_key]) 956 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 957 continue 958 if not UVCTdiag: 959 amp = helas_objects.HelasAmplitude(lastvx, model) 960 else: 961 amp = LoopHelasUVCTAmplitude(lastvx, model) 962 amp.set('UVCT_orders',diagram.get('UVCT_orders')) 963 amp.set('UVCT_couplings',diagram.get('UVCT_couplings')) 964 amp.set('type',diagram.get('type')) 965 if inter: 966 amp.set('coupling', [inter.get('couplings')[coupl_key]]) 967 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 968 if inter.get('color'): 969 amp.set('inter_color', inter.get('color')[color]) 970 amp.set('color_key', color) 971 done_color[color] = amp 972 amp.set('mothers', mothers) 973 amplitudeNumber = amplitudeNumber + 1 974 amp.set('number', amplitudeNumber) 975 # Add the list with color indices to the amplitude 976 new_color_list = copy.copy(color_list) 977 if inter: 978 new_color_list.append(color) 979 980 amp.set('color_indices', new_color_list) 981 982 # Add amplitude to amplitdes in helas_diagram 983 helas_diagram.get('amplitudes').append(amp) 984 985 # After generation of all wavefunctions and amplitudes, 986 # add wavefunctions to diagram 987 helas_diagram.set('wavefunctions', diagram_wavefunctions) 988 989 # Sort the wavefunctions according to number 990 diagram_wavefunctions.sort(lambda wf1, wf2: \ 991 wf1.get('number') - wf2.get('number')) 992 993 if optimization: 994 wavefunctions.extend(diagram_wavefunctions) 995 wf_mother_arrays.extend([wf.to_array() for wf \ 996 in diagram_wavefunctions]) 997 else: 998 wfNumber = len(process.get('legs')) 999 if self.optimized_output: 1000 # Add one for the starting external loop wavefunctions 1001 # which is fixed 1002 wfNumber = wfNumber+1 1003 1004 # Return the diagram obtained 1005 return helas_diagram, wfNumber, amplitudeNumber
1006 1007 def process_struct(sID, diag_wfs, wfNumber): 1008 """ Scan a structure, create the necessary wavefunctions, add them 1009 to the diagram wavefunctions list, and return a list of bridge 1010 wavefunctions (i.e. those attached to the loop) with a list, ordered 1011 in the same way, of color lists. Each element of these lists 1012 correspond to one choice of color-lorentz structure of this 1013 tree-structure #sID. """ 1014 1015 # List of dictionaries from leg number to wave function, 1016 # keeps track of the present position in the tree structure. 1017 # Need one dictionary per coupling multiplicity (diagram) 1018 number_to_wavefunctions = [{}] 1019 1020 # Need to keep track of the color structures for each amplitude 1021 color_lists = [[]] 1022 1023 # Bridge wavefunctions 1024 bridge_wfs = helas_objects.HelasWavefunctionList() 1025 1026 vertices = copy.copy(structures[sID].get('vertices')) 1027 1028 # First treat the special case of a structure made solely of one 1029 # external leg 1030 if len(vertices)==0: 1031 binding_leg=copy.copy(structures[sID]['binding_leg']) 1032 binding_wf = self.getmothers(base_objects.LegList([binding_leg,]), 1033 {}, 1034 external_wavefunctions, 1035 wavefunctions, 1036 diag_wfs) 1037 # Simply return the wf of this external leg along with an 1038 # empty color list 1039 return [(binding_wf[0],[])] ,wfNumber 1040 1041 # Go through all vertices except the last and create 1042 # wavefunctions 1043 for i, vertex in enumerate(vertices): 1044 1045 # In case there are diagrams with multiple Lorentz/color 1046 # structures, we need to keep track of the wavefunctions 1047 # for each such structure separately, and generate 1048 # one HelasDiagram for each structure. 1049 # We use the array number_to_wavefunctions to keep 1050 # track of this, with one dictionary per chain of 1051 # wavefunctions 1052 # Note that all wavefunctions relating to this diagram 1053 # will be written out before the first amplitude is written. 1054 new_number_to_wavefunctions = [] 1055 new_color_lists = [] 1056 for number_wf_dict, color_list in zip(number_to_wavefunctions, 1057 color_lists): 1058 legs = copy.copy(vertex.get('legs')) 1059 last_leg = legs.pop() 1060 # Generate list of mothers from legs 1061 mothers = self.getmothers(legs, number_wf_dict, 1062 external_wavefunctions, 1063 wavefunctions, 1064 diag_wfs) 1065 inter = model.get('interaction_dict')[vertex.get('id')] 1066 1067 # Now generate new wavefunction for the last leg 1068 1069 # Need one amplitude for each color structure, 1070 done_color = {} # store link to color 1071 for coupl_key in sorted(inter.get('couplings').keys()): 1072 color = coupl_key[0] 1073 if color in done_color: 1074 wf = done_color[color] 1075 wf.get('coupling').append(inter.get('couplings')[coupl_key]) 1076 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 1077 continue 1078 wf = helas_objects.HelasWavefunction(last_leg, vertex.get('id'), model) 1079 wf.set('coupling', [inter.get('couplings')[coupl_key]]) 1080 if inter.get('color'): 1081 wf.set('inter_color', inter.get('color')[coupl_key[0]]) 1082 done_color[color] = wf 1083 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 1084 wf.set('color_key', color) 1085 wf.set('mothers',mothers) 1086 ###print "in process_struct and adding wf with" 1087 ###print " mothers id:" 1088 ###for ii, mot in enumerate(mothers): 1089 ### print " mother ",ii,"=",mot['number_external'],"("+str(mot.get_pdg_code())+") number=",mot['number'] 1090 ###print " and iself =",wf['number_external'],"("+str(wf.get_pdg_code())+") number=",wf['number'] 1091 # Need to set incoming/outgoing and 1092 # particle/antiparticle according to the fermion flow 1093 # of mothers 1094 wf.set_state_and_particle(model) 1095 # Need to check for clashing fermion flow due to 1096 # Majorana fermions, and modify if necessary 1097 # Also need to keep track of the wavefunction number. 1098 wf, wfNumber = wf.check_and_fix_fermion_flow(\ 1099 wavefunctions, 1100 diag_wfs, 1101 external_wavefunctions, 1102 wfNumber) 1103 # Create new copy of number_wf_dict 1104 new_number_wf_dict = copy.copy(number_wf_dict) 1105 1106 # Store wavefunction 1107 try: 1108 wf = diag_wfs[\ 1109 diag_wfs.index(wf)] 1110 except ValueError: 1111 # Update wf number 1112 wfNumber = wfNumber + 1 1113 wf.set('number', wfNumber) 1114 try: 1115 # Use wf_mother_arrays to locate existing 1116 # wavefunction 1117 wf = wavefunctions[wf_mother_arrays.index(\ 1118 wf.to_array())] 1119 # Since we reuse the old wavefunction, reset 1120 # wfNumber 1121 wfNumber = wfNumber - 1 1122 except ValueError: 1123 diag_wfs.append(wf) 1124 1125 new_number_wf_dict[last_leg.get('number')] = wf 1126 if i==(len(vertices)-1): 1127 # Last vertex of the structure so we should define 1128 # the bridge wavefunctions. 1129 bridge_wfs.append(wf) 1130 # Store the new copy of number_wf_dict 1131 new_number_to_wavefunctions.append(\ 1132 new_number_wf_dict) 1133 # Add color index and store new copy of color_lists 1134 new_color_list = copy.copy(color_list) 1135 new_color_list.append(coupl_key[0]) 1136 new_color_lists.append(new_color_list) 1137 1138 number_to_wavefunctions = new_number_to_wavefunctions 1139 color_lists = new_color_lists 1140 1141 ###print "bridg wfs returned=" 1142 ###for wf in bridge_wfs: 1143 ### print " bridge =",wf['number_external'],"("+str(wf.get_pdg_code())+") number=",wf['number'] 1144 1145 return zip(bridge_wfs, color_lists), wfNumber
1146 1147 def getloopmothers(loopWfsIn, structIDs, color_list, diag_wfs, wfNumber): 1148 """From the incoming loop leg(s) and the list of structures IDs 1149 connected to the loop at this point, it generates the list of 1150 mothers, a list of colorlist and a number_to_wavefunctions 1151 dictionary list for which each element correspond to one 1152 lorentz-color structure of the tree-structure attached to the loop. 1153 It will launch the reconstruction procedure of the structures 1154 which have not been encountered yet.""" 1155 1156 # The mothers list and the color lists There is one element in these 1157 # lists, in the same order, for each combination of the 1158 # lorentz-color tree-structures of the FDStructures attached to 1159 # this point. 1160 mothers_list = [loopWfsIn,] 1161 color_lists = [color_list,] 1162 1163 # Scanning of the FD tree-structures attached to the loop at this 1164 # point. 1165 for sID in structIDs: 1166 try: 1167 struct_infos = structID_to_infos[sID] 1168 except KeyError: 1169 # The structure has not been encountered yet, we must 1170 # scan it 1171 struct_infos, wfNumber = \ 1172 process_struct(sID, diag_wfs, wfNumber) 1173 if optimization: 1174 # Only if there is optimization the dictionary is 1175 # because otherwise we must always rescan the 1176 # structures to correctly add all the necessary 1177 # wavefunctions to the diagram wavefunction list 1178 structID_to_infos[sID]=copy.copy(struct_infos) 1179 # The orig object are those already existing before treating 1180 # this structure 1181 new_mothers_list = [] 1182 new_color_lists = [] 1183 for mothers, orig_color_list in zip(mothers_list, color_lists): 1184 for struct_wf, struct_color_list in struct_infos: 1185 new_color_list = copy.copy(orig_color_list)+\ 1186 copy.copy(struct_color_list) 1187 new_mothers = copy.copy(mothers) 1188 new_mothers.append(struct_wf) 1189 new_color_lists.append(new_color_list) 1190 new_mothers_list.append(new_mothers) 1191 mothers_list = new_mothers_list 1192 color_lists = new_color_lists 1193 1194 ###print "getloop mothers returned with sID", structIDs 1195 ###print "len mothers_list=",len(mothers_list) 1196 ###for wf in mothers_list[0]: 1197 ### print " mother =",wf['number_external'],"("+str(wf.get_pdg_code())+") number=",wf['number'] 1198 1199 return (mothers_list, color_lists), wfNumber 1200 1201 def process_loop_diagram(diagram, wavefunctionNumber, amplitudeNumber): 1202 """ Helper function to process a the loop diagrams which features 1203 several different aspects compared to the tree born diagrams.""" 1204 1205 # Initialize here the loop helas diagram we are about to create 1206 helas_diagram = LoopHelasDiagram() 1207 1208 # List of dictionaries from leg number to wave function, 1209 # keeps track of the present position in the loop. 1210 # We only need to retain the last loop wavefunctions created 1211 # This is a list to store all the last loop wavefunctions created 1212 # due to the possibly many color-lorentz structure of the last 1213 # loop vertex. 1214 last_loop_wfs = helas_objects.HelasWavefunctionList() 1215 1216 # Need to keep track of the color structures for each amplitude 1217 color_lists = [[]] 1218 1219 # Initialize wavefunctions for this diagram 1220 diagram_wavefunctions = helas_objects.HelasWavefunctionList() 1221 1222 # Copy the original tag of the loop which contains all the necessary 1223 # information with the interaction ID in the tag replaced by the 1224 # corresponding vertex 1225 tag = copy.deepcopy(diagram.get('tag')) 1226 loop_vertices = copy.deepcopy(diagram.get('vertices')) 1227 for i in range(len(tag)): 1228 tag[i][2]=loop_vertices[i] 1229 1230 # Copy the ct vertices of the loop 1231 ct_vertices = copy.copy(diagram.get('CT_vertices')) 1232 1233 # First create the starting external loop leg 1234 external_loop_wf=helas_objects.HelasWavefunction(\ 1235 tag[0][0], 0, model, decay_ids) 1236 1237 # When on the optimized output mode, the starting loop wavefunction 1238 # can be recycled if it has the same pdg because whatever its pdg 1239 # it has the same coefficients and loop momentum zero, 1240 # so it is in principle not necessary to add it to the 1241 # diagram_wavefunction. However, this is necessary for the function 1242 # check_and_fix_fermion_flow to correctly update the dependances of 1243 # previous diagrams to an external L-cut majorana wavefunction which 1244 # needs flipping. 1245 if not self.optimized_output: 1246 wavefunctionNumber=wavefunctionNumber+1 1247 external_loop_wf.set('number',wavefunctionNumber) 1248 diagram_wavefunctions.append(external_loop_wf) 1249 else: 1250 try: 1251 external_loop_wf=\ 1252 external_loop_wfs_dict[external_loop_wf.get('pdg_code')] 1253 except KeyError: 1254 wavefunctionNumber=wavefunctionNumber+1 1255 external_loop_wf.set('number',wavefunctionNumber) 1256 external_loop_wfs_dict[external_loop_wf.get('pdg_code')]=\ 1257 external_loop_wf 1258 diagram_wavefunctions.append(external_loop_wf) 1259 1260 # Setup the starting point of the reading of the loop flow. 1261 last_loop_wfs.append(external_loop_wf) 1262 1263 def process_tag_elem(tagElem, wfNumber, lastloopwfs, colorlists): 1264 """Treat one tag element of the loop diagram (not the last one 1265 which provides an amplitude)""" 1266 1267 # We go through all the structures generated during the 1268 # exploration of the structures attached at this point 1269 # of the loop. Let's define the new color_lists and 1270 # last_loop_wfs we will use for next iteration 1271 new_color_lists = [] 1272 new_last_loop_wfs = helas_objects.HelasWavefunctionList() 1273 1274 # In case there are diagrams with multiple Lorentz/color 1275 # structures, we need to keep track of the wavefunctions 1276 # for each such structure separately, and generate 1277 # one HelasDiagram for each structure. 1278 # We use the array number_to_wavefunctions to keep 1279 # track of this, with one dictionary per chain of 1280 # wavefunctions 1281 # Note that all wavefunctions relating to this diagram 1282 # will be written out before the first amplitude is written. 1283 vertex=tagElem[2] 1284 structIDs=tagElem[1] 1285 for last_loop_wf, color_list in zip(lastloopwfs, 1286 colorlists): 1287 loopLegOut = copy.copy(vertex.get('legs')[-1]) 1288 1289 # From the incoming loop leg and the struct IDs, it generates 1290 # a list of mothers, colorlists and number_to_wavefunctions 1291 # dictionary for which each element correspond to one 1292 # lorentz-color structure of the tree-structure attached to 1293 # the loop. 1294 (motherslist, colorlists), wfNumber = \ 1295 getloopmothers(\ 1296 helas_objects.HelasWavefunctionList([last_loop_wf,]), 1297 structIDs,\ 1298 color_list, diagram_wavefunctions, wfNumber) 1299 inter = model.get('interaction_dict')[vertex.get('id')] 1300 1301 # Now generate new wavefunctions for the last leg 1302 1303 for mothers, structcolorlist in zip(motherslist, colorlists): 1304 # Need one amplitude for each color structure, 1305 done_color = {} # store link to color 1306 for coupl_key in sorted(inter.get('couplings').keys()): 1307 color = coupl_key[0] 1308 if color in done_color: 1309 wf = done_color[color] 1310 wf.get('coupling').append(inter.get('couplings')[coupl_key]) 1311 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 1312 continue 1313 wf = helas_objects.HelasWavefunction(loopLegOut, \ 1314 vertex.get('id'), model) 1315 wf.set('coupling', [inter.get('couplings')[coupl_key]]) 1316 if inter.get('color'): 1317 wf.set('inter_color', inter.get('color')[coupl_key[0]]) 1318 done_color[color] = wf 1319 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 1320 wf.set('color_key', color) 1321 wf.set('mothers',mothers) 1322 # Need to set incoming/outgoing and 1323 # particle/antiparticle according to the fermion flow 1324 # of mothers 1325 wf.set_state_and_particle(model) 1326 # Need to check for clashing fermion flow due to 1327 # Majorana fermions, and modify if necessary 1328 # Also need to keep track of the wavefunction number. 1329 wf, wfNumber = wf.check_and_fix_fermion_flow(\ 1330 wavefunctions, 1331 diagram_wavefunctions, 1332 external_wavefunctions, 1333 wfNumber) 1334 1335 # Store wavefunction 1336 try: 1337 wf = diagram_wavefunctions[\ 1338 diagram_wavefunctions.index(wf)] 1339 except ValueError: 1340 # Update wf number 1341 wfNumber = wfNumber + 1 1342 wf.set('number', wfNumber) 1343 # Depending on wether we are on the 1344 # loop_optimized_output mode or now we want to 1345 # reuse the loop wavefunctions as well. 1346 try: 1347 if not self.optimized_output: 1348 raise ValueError 1349 # Use wf_mother_arrays to locate existing 1350 # wavefunction 1351 wf = wavefunctions[wf_mother_arrays.index(\ 1352 wf.to_array())] 1353 # Since we reuse the old wavefunction, reset 1354 # wfNumber 1355 wfNumber = wfNumber - 1 1356 # To keep track of the number of loop 1357 # wfs reused 1358 self.lwf_reused += 1 1359 except ValueError: 1360 diagram_wavefunctions.append(wf) 1361 1362 # Update the last_loop_wfs list with the loop wf 1363 # we just created. 1364 new_last_loop_wfs.append(wf) 1365 # Add color index and store new copy of color_lists 1366 new_color_list = copy.copy(structcolorlist) 1367 new_color_list.append(coupl_key[0]) 1368 new_color_lists.append(new_color_list) 1369 1370 # We update the lastloopwfs list and the color_lists for the 1371 # next iteration, i.e. the treatment of the next loop vertex 1372 # by returning them to the calling environnement. 1373 return wfNumber, new_last_loop_wfs, new_color_lists 1374 1375 1376 # Go through all vertices except the last and create 1377 # wavefunctions 1378 1379 def create_amplitudes(lastvx, wfNumber, amplitudeNumber): 1380 """Treat the last tag element of the loop diagram (which 1381 provides an amplitude)""" 1382 # First create the other external loop leg closing the loop. 1383 # It will not be in the final output, and in this sense, it is 1384 # a dummy wavefunction, but it is structurally important. 1385 # Because it is only structurally important, we do not need to 1386 # add it to the list of the wavefunctions for this ME or this 1387 # HELAS loop amplitude, nor do we need to update its number. 1388 other_external_loop_wf=helas_objects.HelasWavefunction() 1389 # wfNumber=wfNumber+1 1390 for leg in [leg for leg in lastvx['legs'] if leg['loop_line']]: 1391 if last_loop_wfs[0]['number_external']!=leg['number']: 1392 other_external_loop_wf=\ 1393 helas_objects.HelasWavefunction(leg, 0, model, decay_ids) 1394 # other_external_loop_wf.set('number',wfNumber) 1395 break 1396 # diagram_wavefunctions.append(other_external_loop_wf) 1397 1398 for last_loop_wf, color_list in zip(last_loop_wfs,color_lists): 1399 # Now generate HelasAmplitudes from the last vertex. 1400 if lastvx.get('id')!=-1: 1401 raise self.PhysicsObjectError, \ 1402 "The amplitude vertex of a loop diagram must be a "+\ 1403 "two point vertex with id=-1" 1404 # skip the boson and Dirac fermions 1405 # adjust the fermion flow of external majorana loop wfs 1406 if other_external_loop_wf.is_majorana(): 1407 fix_lcut_majorana_fermion_flow(last_loop_wf,\ 1408 other_external_loop_wf) 1409 # fix the fermion flow 1410 mothers=helas_objects.HelasWavefunctionList(\ 1411 [last_loop_wf,other_external_loop_wf]) 1412 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions, 1413 diagram_wavefunctions, 1414 external_wavefunctions, 1415 None, 1416 wfNumber, 1417 False, 1418 []) # number_to_wavefunctions is useless in loop case 1419 amp = helas_objects.HelasAmplitude(lastvx, model) 1420 amp.set('interaction_id',-1) 1421 amp.set('mothers',mothers) 1422 #amp.set('mothers', helas_objects.HelasWavefunctionList(\ 1423 # [last_loop_wf,other_external_loop_wf])) 1424 amp.set('pdg_codes',[last_loop_wf.get_pdg_code(), 1425 other_external_loop_wf.get_pdg_code()]) 1426 ###print "mothers added for amp=" 1427 ###for wf in mothers: 1428 ### print " mother =",wf['number_external'],"("+str(wf.get_pdg_code())+") number=",wf['number'] 1429 # Add the list with color indices to the amplitude 1430 1431 amp.set('color_indices', copy.copy(color_list)) 1432 # Add this amplitude to the LoopHelasAmplitude of this 1433 # diagram. 1434 amplitudeNumber = amplitudeNumber + 1 1435 amp.set('number', amplitudeNumber) 1436 amp.set('type','loop') 1437 loop_amp = LoopHelasAmplitude() 1438 loop_amp.set('amplitudes',\ 1439 helas_objects.HelasAmplitudeList([amp,])) 1440 # Set the loop wavefunctions building this amplitude 1441 # by tracking them from the last loop wavefunction 1442 # added and its loop wavefunction among its mothers 1443 loop_amp_wfs=helas_objects.HelasWavefunctionList(\ 1444 [last_loop_wf,]) 1445 while loop_amp_wfs[-1].get('mothers'): 1446 loop_amp_wfs.append([lwf for lwf in \ 1447 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0]) 1448 # Sort the loop wavefunctions of this amplitude 1449 # according to their correct order of creation for 1450 # the HELAS calls (using their 'number' attribute 1451 # would work as well, but I want something less naive) 1452 # 1) Add the other L-cut particle at the end 1453 loop_amp_wfs.append(other_external_loop_wf) 1454 # 2) Reverse to have a consistent ordering of creation 1455 # of helas wavefunctions. 1456 loop_amp_wfs.reverse() 1457 loop_amp.set('wavefunctions',loop_amp_wfs) 1458 loop_amp.set('type',diagram.get('type')) 1459 # 'number' is not important as it will be redefined later. 1460 loop_amp.set('number',min([amp.get('number') for amp 1461 in loop_amp.get('amplitudes')])) 1462 loop_amp.set('coupling',loop_amp.get_couplings()) 1463 loop_amp.set('orders',loop_amp.get_orders()) 1464 helas_diagram.get('amplitudes').append(loop_amp) 1465 # here we check the two L-cut loop helas wavefunctions are 1466 # in consistent flow 1467 check_lcut_fermion_flow_consistency(\ 1468 loop_amp_wfs[0],loop_amp_wfs[1]) 1469 return wfNumber, amplitudeNumber 1470 1471 def check_lcut_fermion_flow_consistency(lcut_wf1, lcut_wf2): 1472 """Checks that the two L-cut loop helas wavefunctions have 1473 a consistent fermion flow.""" 1474 if lcut_wf1.is_boson(): 1475 if lcut_wf1.get('state')!='final' or\ 1476 lcut_wf2.get('state')!='final': 1477 raise MadGraph5Error,\ 1478 "Inconsistent flow in L-cut bosons." 1479 elif not lcut_wf1.is_majorana(): 1480 for lcut_wf in [lcut_wf1,lcut_wf2]: 1481 if not ((lcut_wf.get('is_part') and \ 1482 lcut_wf.get('state')=='outgoing') or\ 1483 (not lcut_wf.get('is_part') and\ 1484 lcut_wf.get('state')=='incoming')): 1485 raise MadGraph5Error,\ 1486 "Inconsistent flow in L-cut Dirac fermions." 1487 elif lcut_wf1.is_majorana(): 1488 if (lcut_wf1.get('state'), lcut_wf2.get('state')) not in \ 1489 [('incoming','outgoing'),('outgoing','incoming')]: 1490 raise MadGraph5Error,\ 1491 "Inconsistent flow in L-cut Majorana fermions." 1492 1493 def fix_lcut_majorana_fermion_flow(last_loop_wf,\ 1494 other_external_loop_wf): 1495 """Fix the fermion flow of the last external Majorana loop 1496 wavefunction through the fermion flow of the first external 1497 Majorana loop wavefunction.""" 1498 # skip the boson and Dirac fermions 1499 # if not other_external_loop_wf.is_majorana():return 1500 loop_amp_wfs=helas_objects.HelasWavefunctionList(\ 1501 [last_loop_wf,]) 1502 while loop_amp_wfs[-1].get('mothers'): 1503 loop_amp_wfs.append([lwf for lwf in \ 1504 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0]) 1505 loop_amp_wfs.append(other_external_loop_wf) 1506 loop_amp_wfs.reverse() 1507 # loop_amp_wfs[0] is the last external loop wavefunction 1508 # while loop_amp_wfs[1] is the first external loop wavefunction 1509 rep={'incoming':'outgoing','outgoing':'incoming'} 1510 # Check if we need to flip the state of the external L-cut majorana 1511 other_external_loop_wf['state']=rep[loop_amp_wfs[1]['state']] 1512 return 1513 1514 def process_counterterms(ct_vertices, wfNumber, amplitudeNumber): 1515 """Process the counterterms vertices defined in this loop 1516 diagram.""" 1517 1518 structIDs=[] 1519 for tagElem in tag: 1520 structIDs += tagElem[1] 1521 # Here we call getloopmothers without any incoming loop 1522 # wavefunctions such that the function will return exactly 1523 # the mother of the counter-term amplitude we wish to create 1524 # We start with an empty color list as well in this case 1525 (motherslist, colorlists), wfNumber = getloopmothers(\ 1526 helas_objects.HelasWavefunctionList(), structIDs, \ 1527 [], diagram_wavefunctions, wfNumber) 1528 1529 for mothers, structcolorlist in zip(motherslist, colorlists): 1530 for ct_vertex in ct_vertices: 1531 # Now generate HelasAmplitudes from this ct_vertex. 1532 inter = model.get_interaction(ct_vertex.get('id')) 1533 keys = sorted(inter.get('couplings').keys()) 1534 pdg_codes = [p.get_pdg_code() for p in \ 1535 inter.get('particles')] 1536 mothers.sort_by_pdg_codes(pdg_codes, 0)[0] 1537 # Need to check for clashing fermion flow due to 1538 # Majorana fermions, and modify if necessary 1539 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions, 1540 diagram_wavefunctions, 1541 external_wavefunctions, 1542 None, 1543 wfNumber, 1544 False, 1545 []) 1546 done_color = {} 1547 for i, coupl_key in enumerate(keys): 1548 color = coupl_key[0] 1549 if color in done_color.keys(): 1550 amp = done_color[color] 1551 amp.get('coupling').append(inter.get('couplings')[coupl_key]) 1552 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]]) 1553 continue 1554 amp = helas_objects.HelasAmplitude(ct_vertex, model) 1555 amp.set('coupling', [inter.get('couplings')[coupl_key]]) 1556 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]]) 1557 if inter.get('color'): 1558 amp.set('inter_color', inter.get('color')[color]) 1559 amp.set('color_key', color) 1560 done_color[color] = amp 1561 amp.set('mothers', mothers) 1562 amplitudeNumber = amplitudeNumber + 1 1563 amp.set('number', amplitudeNumber) 1564 # Add the list with color indices to the amplitude 1565 amp_color_list = copy.copy(structcolorlist) 1566 amp_color_list.append(color) 1567 amp.set('color_indices', amp_color_list) 1568 amp.set('type',inter.get('type')) 1569 1570 # Add amplitude to amplitdes in helas_diagram 1571 helas_diagram.get('amplitudes').append(amp) 1572 return wfNumber, amplitudeNumber 1573 1574 for tagElem in tag: 1575 wavefunctionNumber, last_loop_wfs, color_lists = \ 1576 process_tag_elem(tagElem, wavefunctionNumber, \ 1577 last_loop_wfs, color_lists) 1578 1579 # Generate all amplitudes corresponding to the different 1580 # copies of this diagram 1581 wavefunctionNumber, amplitudeNumber = create_amplitudes( 1582 loop_vertices[-1], wavefunctionNumber, amplitudeNumber) 1583 1584 # Add now the counter-terms vertices 1585 if ct_vertices: 1586 wavefunctionNumber, amplitudeNumber = process_counterterms(\ 1587 ct_vertices, wavefunctionNumber, amplitudeNumber) 1588 1589 # Identify among the diagram wavefunctions those from the structures 1590 # which will fill the 'wavefunctions' list of the diagram 1591 struct_wfs=helas_objects.HelasWavefunctionList(\ 1592 [wf for wf in diagram_wavefunctions if not wf['is_loop']]) 1593 loop_wfs=helas_objects.HelasWavefunctionList(\ 1594 [wf for wf in diagram_wavefunctions if wf['is_loop']]) 1595 1596 # Sort the wavefunctions according to number 1597 struct_wfs.sort(lambda wf1, wf2: \ 1598 wf1.get('number') - wf2.get('number')) 1599 1600 # After generation of all wavefunctions and amplitudes, 1601 # add wavefunctions to diagram 1602 helas_diagram.set('wavefunctions', struct_wfs) 1603 1604 # Of course we only allow to reuse the struct wavefunctions but 1605 # never the loop ones which have to be present and reused in each 1606 # loop diagram, UNLESS we are in the loop_optimized_output mode. 1607 if optimization: 1608 wavefunctions.extend(struct_wfs) 1609 wf_mother_arrays.extend([wf.to_array() for wf in struct_wfs]) 1610 if self.optimized_output: 1611 wavefunctions.extend(loop_wfs) 1612 wf_mother_arrays.extend([wf.to_array() for wf in loop_wfs]) 1613 else: 1614 wavefunctionNumber = len(process.get('legs')) 1615 if self.optimized_output: 1616 # Add one for the starting external loop wavefunctions 1617 # which is fixed 1618 wavefunctionNumber = wavefunctionNumber+1 1619 1620 # And to the loop helas diagram if under the optimized output. 1621 # In the default output, one use those stored in the loop amplitude 1622 # since they are anyway not recycled. Notice that we remove the 1623 # external L-cut loop wavefunctions from this list since they do 1624 # not need to be computed. 1625 if self.optimized_output: 1626 loop_wfs = helas_objects.HelasWavefunctionList( 1627 [lwf for lwf in loop_wfs if len(lwf.get('mothers'))>0]) 1628 helas_diagram.set('loop_wavefunctions',loop_wfs) 1629 1630 # Return the diagram obtained 1631 return helas_diagram, wavefunctionNumber, amplitudeNumber 1632 1633 # Let's first treat the born diagrams 1634 1635 if has_born: 1636 for diagram in amplitude.get('born_diagrams'): 1637 helBornDiag, wf_number, amplitude_number=\ 1638 process_born_diagram(diagram, wf_number, amplitude_number) 1639 diagram_number = diagram_number + 1 1640 helBornDiag.set('number', diagram_number) 1641 helas_diagrams.append(helBornDiag) 1642 1643 # Now we treat the loop diagrams 1644 self.lwf_reused=0 1645 for diagram in amplitude.get('loop_diagrams'): 1646 loopHelDiag, wf_number, amplitude_number=\ 1647 process_loop_diagram(diagram, wf_number, amplitude_number) 1648 diagram_number = diagram_number + 1 1649 loopHelDiag.set('number', diagram_number) 1650 helas_diagrams.append(loopHelDiag) 1651 1652 # We finally turn to the UVCT diagrams 1653 for diagram in amplitude.get('loop_UVCT_diagrams'): 1654 loopHelDiag, wf_number, amplitude_number=\ 1655 process_born_diagram(diagram, wf_number, amplitude_number, \ 1656 UVCTdiag=True) 1657 diagram_number = diagram_number + 1 1658 loopHelDiag.set('number', diagram_number) 1659 # We must add the UVCT_orders to the regular orders of the 1660 # LooopHelasUVCTAmplitude 1661 for lamp in loopHelDiag.get_loop_UVCTamplitudes(): 1662 new_orders = copy.copy(lamp.get('orders')) 1663 for order, value in lamp.get('UVCT_orders').items(): 1664 try: 1665 new_orders[order] = new_orders[order] + value 1666 except KeyError: 1667 new_orders[order] = value 1668 lamp.set('orders', new_orders) 1669 helas_diagrams.append(loopHelDiag) 1670 1671 self.set('diagrams', helas_diagrams) 1672 # Check wf order consistency 1673 if __debug__: 1674 for diag in self.get('diagrams'): 1675 # This is just a monitoring function, it will *NOT* affect the 1676 # wavefunctions list of the diagram, but just raise an Error 1677 # if the order is inconsistent, namely if a wavefunction in this 1678 # list has a mother which appears after its position in the list. 1679 diag.get('wavefunctions').check_wavefunction_numbers_order() 1680 1681 # Inform how many loop wavefunctions have been reused. 1682 if self.optimized_output: 1683 logger.debug('%d loop wavefunctions have been reused'%self.lwf_reused+ 1684 ', for a total of %d ones'%sum([len(ldiag.get('loop_wavefunctions')) 1685 for ldiag in self.get_loop_diagrams()])) 1686 1687 # Sort all mothers according to the order wanted in Helas calls 1688 for wf in self.get_all_wavefunctions(): 1689 wf.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(wf)) 1690 1691 for amp in self.get_all_amplitudes(): 1692 amp.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(amp)) 1693 # Not really necessary for the LoopHelasAmplitude as the color 1694 # indices of the amplitudes should be correct. It is however 1695 # cleaner like this. For debugging purposes we leave here an assert. 1696 gen_colors = amp.get('color_indices') 1697 amp.set('color_indices', amp.get_color_indices()) 1698 if isinstance(amp,LoopHelasAmplitude): 1699 assert (amp.get('color_indices')==gen_colors), \ 1700 "Error in the treatment of color in the loop helas diagram "+\ 1701 "generation. It could be harmless, but report this bug to be sure."+\ 1702 " The different keys are %s vs %s."%(str(gen_colors),\ 1703 str(amp.get('color_indices'))) 1704 for loopdiag in self.get_loop_diagrams(): 1705 for loopamp in loopdiag.get_loop_amplitudes(): 1706 loopamp.set_mothers_and_pairing() 1707 1708 # As a final step, we compute the analytic information for the loop 1709 # wavefunctions and amplitudes building this loop matrix element. 1710 # Because we want to have the same AlohaModel used for various 1711 # HelasMatrix elements, we perform the computation belows in the 1712 # export which will use its AlohaModel for several HelasME's. 1713 # self.compute_all_analytic_information() 1714
1715 - def get_split_orders_mapping(self):
1716 """This function returns a list and a dictionary: 1717 squared_orders, amps_orders 1718 === 1719 The squared_orders lists all contributing squared_orders as tuple whose 1720 elements are the power at which are elevated the couplings orderered as 1721 in the 'split_orders'. 1722 1723 squared_orders : All possible contributing squared orders among those 1724 specified in the process['split_orders'] argument. The elements of 1725 the list are tuples of the format 1726 ((OrderValue1,OrderValue2,...), 1727 (max_contrib_ct_amp_number, 1728 max_contrib_uvct_amp_number, 1729 max_contrib_loop_amp_number, 1730 max_contrib_group_id)) 1731 with OrderValue<i> correspond to the value of the <i>th order in 1732 process['split_orders'] (the others are summed over and therefore 1733 left unspecified). 1734 Ex for dijet with process['split_orders']=['QCD','QED']: 1735 => [((4,0),(8,2,3)),((2,2),(10,3,3)),((0,4),(20,5,4))] 1736 1737 'max_contrib_loop_amp_number': For optimization purposes, it is good to 1738 know what is the maximum loop amplitude number contributing to any given 1739 squared order. The fortran output is structured so that if the user 1740 is interested in a given squared order contribution only, then 1741 all the open loop coefficients for the amplitudes with a number above 1742 this value can be skipped. 1743 1744 'max_contrib_(uv)ct_amp_number': Same as above but for the 1745 (uv)ctamplitude number. 1746 1747 'max_contrib_group_id': The same as above, except this time 1748 it is for the loop group id used for the loop reduction. 1749 === 1750 The amps_orders is a *dictionary* with keys 1751 'born_amp_orders', 1752 'loop_amp_orders' 1753 with values being the tuples described below. 1754 1755 If process['split_orders'] is empty, all these tuples are set empty. 1756 1757 'born_amp_orders' : Exactly as for squared order except that this list specifies 1758 the contributing order values for the amplitude (i.e. not 'squared'). 1759 Also, the tuple describing the amplitude order is nested with a 1760 second one listing all amplitude numbers contributing to this order. 1761 Ex for dijet with process['split_orders']=['QCD','QED']: 1762 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))] 1763 The function returns () if the process has no borns. 1764 1765 'loop_amp_orders' : The same as for born_amp_orders but for the loop 1766 type of amplitudes only. 1767 1768 Keep in mind that the orders of the elements of the outter most list is 1769 important as it dictates the order for the corresponding "order indices" 1770 in the fortran code output by the exporters. 1771 """ 1772 1773 split_orders=self.get('processes')[0].get('split_orders') 1774 # If no split_orders are defined, then return the obvious 1775 amps_orders = {'born_amp_orders':[], 1776 'loop_amp_orders':[]} 1777 if len(split_orders)==0: 1778 self.squared_orders = [] 1779 return [],amps_orders 1780 1781 # First make sure that the 'split_orders' are ordered according to their 1782 # weight. 1783 self.sort_split_orders(split_orders) 1784 1785 process = self.get('processes')[0] 1786 # First make sure that the 'split_orders' are ordered according to their 1787 # weight. 1788 self.sort_split_orders(split_orders) 1789 loop_amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 1790 self.get_loop_diagrams(), split_orders, 1791 get_amplitudes_function = lambda diag: diag.get_loop_amplitudes(), 1792 # We chose at this stage to store not only the amplitude numbers but 1793 # also the reference reduction id in the loop grouping, necessary 1794 # for returning the max_contrib_ref_amp_numbers. 1795 get_amp_number_function = lambda amp: 1796 (amp.get('amplitudes')[0].get('number'),amp.get('loop_group_id'))) 1797 ct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 1798 self.get_loop_diagrams(), split_orders, 1799 get_amplitudes_function = lambda diag: diag.get_ct_amplitudes()) 1800 uvct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 1801 self.get_loop_UVCT_diagrams(), split_orders) 1802 1803 # With this function, we just return the contributing amplitude numbers 1804 # The format is therefore the same as for the born_amp_orders and 1805 # ct_amp_orders 1806 amps_orders['loop_amp_orders'] = dict([(lao[0], 1807 [el[0] for el in lao[1]]) for lao in loop_amp_orders]) 1808 # Now add there the ct_amp_orders and uvct_amp_orders 1809 for ct_amp_order in ct_amp_orders+uvct_amp_orders: 1810 try: 1811 amps_orders['loop_amp_orders'][ct_amp_order[0]].extend(\ 1812 list(ct_amp_order[1])) 1813 except KeyError: 1814 amps_orders['loop_amp_orders'][ct_amp_order[0]] = \ 1815 list(ct_amp_order[1]) 1816 # We must now turn it back to a list 1817 amps_orders['loop_amp_orders'] = [ 1818 (key, tuple(sorted(amps_orders['loop_amp_orders'][key]))) 1819 for key in amps_orders['loop_amp_orders'].keys()] 1820 # and re-sort it to make sure it follows an increasing WEIGHT order. 1821 order_hierarchy = self.get('processes')[0]\ 1822 .get('model').get('order_hierarchy') 1823 if set(order_hierarchy.keys()).union(set(split_orders))==\ 1824 set(order_hierarchy.keys()): 1825 amps_orders['loop_amp_orders'].sort(key= lambda so: 1826 sum([order_hierarchy[split_orders[i]]*order_power for \ 1827 i, order_power in enumerate(so[0])])) 1828 1829 # Finally the born amp orders 1830 if process.get('has_born'): 1831 born_amp_orders = self.get_split_orders_mapping_for_diagram_list(\ 1832 self.get_born_diagrams(),split_orders) 1833 1834 amps_orders['born_amp_orders'] = born_amp_orders 1835 1836 # Now we construct the interference splitting order matrix. 1837 # For this we flatten the list of many individual 2-tuples of the form 1838 # (amp_number, ref_amp_number) into one big 2-tuple of the form 1839 # (tuple_of_all_amp_numers, tuple_of_all_ref_amp_numbers). 1840 loop_orders = [(lso[0],tuple(zip(*list(lso[1])))) for lso in loop_amp_orders] 1841 1842 # For the reference orders (against which the loop and ct amps are squared) 1843 # we only need the value of the orders, not the corresponding amp numbers. 1844 if process.get('has_born'): 1845 ref_orders = [bao[0] for bao in born_amp_orders] 1846 else: 1847 ref_orders = [lao[0] for lao in loop_orders+ct_amp_orders] 1848 1849 # Temporarily we set squared_orders to be a dictionary with keys being 1850 # the actual contributing squared_orders and the values are the list 1851 # [max_contrib_uvctamp_number,max_contrib_ct_amp_number, 1852 # max_contrib_loop_amp_number, 1853 # max_contrib_ref_amp_number] 1854 1855 # In the event where they would be no contributing amplitude in one of 1856 # the four class above, then the list on which the function max will be 1857 # called will be empty and we need to have the function not crash but 1858 # return -1 instead. 1859 def smax(AmpNumList): 1860 return -1 if len(AmpNumList)==0 else max(AmpNumList)
1861 1862 squared_orders = {} 1863 for ref_order in ref_orders: 1864 for uvct_order in uvct_amp_orders: 1865 key = tuple([ord1 + ord2 for ord1,ord2 in zip(uvct_order[0], 1866 ref_order)]) 1867 try: 1868 # Finding the max_contrib_uvct_amp_number 1869 squared_orders[key][0] = smax([squared_orders[key][0]]+ 1870 list(uvct_order[1])) 1871 except KeyError: 1872 squared_orders[key] = [smax(list(uvct_order[1])),-1,-1,-1] 1873 1874 for ct_order in ct_amp_orders: 1875 key = tuple([ord1 + ord2 for ord1,ord2 in zip(ct_order[0], 1876 ref_order)]) 1877 try: 1878 # Finding the max_contrib_ct_amp_number 1879 squared_orders[key][1] = smax([squared_orders[key][1]]+ 1880 list(ct_order[1])) 1881 except KeyError: 1882 squared_orders[key] = [-1,smax(list(ct_order[1])),-1,-1] 1883 1884 for loop_order in loop_orders: 1885 key = tuple([ord1 + ord2 for ord1,ord2 in zip(loop_order[0], 1886 ref_order)]) 1887 try: 1888 # Finding the max_contrib_loop_amp_number 1889 squared_orders[key][2] = smax([squared_orders[key][2]]+ 1890 list(loop_order[1][0])) 1891 # Finding the max_contrib_loop_id 1892 squared_orders[key][3] = smax([squared_orders[key][3]]+ 1893 list(loop_order[1][1])) 1894 except KeyError: 1895 squared_orders[key] = [-1,-1,smax(list(loop_order[1][0])), 1896 smax(list(loop_order[1][1]))] 1897 1898 # To sort the squared_orders, we now turn it into a list instead of a 1899 # dictionary. Each element of the list as the format 1900 # ( squared_so_powers_tuple, 1901 # (max_uvct_amp_number, max_ct_amp_number, 1902 # max_loop_amp_number, max_loop_id) ) 1903 squared_orders = [(sqso[0],tuple(sqso[1])) for sqso in \ 1904 squared_orders.items()] 1905 # Sort the squared orders if the hierarchy defines them all. 1906 order_hierarchy = self.get('processes')[0].get('model').get('order_hierarchy') 1907 if set(order_hierarchy.keys()).union(set(split_orders))==\ 1908 set(order_hierarchy.keys()): 1909 squared_orders.sort(key= lambda so: 1910 sum([order_hierarchy[split_orders[i]]*order_power for \ 1911 i, order_power in enumerate(so[0])])) 1912 1913 # Cache the squared_orders information 1914 self.squared_orders = squared_orders 1915 1916 return squared_orders, amps_orders 1917
1918 - def get_squared_order_contribs(self):
1919 """Return the squared_order contributions as returned by the function 1920 get_split_orders_mapping. It uses the cached value self.squared_orders 1921 if it was already defined during a previous call to get_split_orders_mapping. 1922 """ 1923 1924 if not hasattr(self, "squared_orders"): 1925 self.get_split_orders_mapping() 1926 1927 return self.squared_orders
1928
1929 - def find_max_loop_coupling(self):
1930 """ Find the maximum number of loop couplings appearing in any of the 1931 LoopHelasAmplitude in this LoopHelasMatrixElement""" 1932 if len(self.get_loop_diagrams())==0: 1933 return 0 1934 return max([len(amp.get('coupling')) for amp in \ 1935 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[])])
1936
1937 - def get_max_loop_vertex_rank(self):
1938 """ Returns the maximum power of loop momentum brought by a loop 1939 interaction. For renormalizable theories, it should be no more than one. 1940 """ 1941 return max([lwf.get_analytic_info('interaction_rank') for lwf in \ 1942 self.get_all_loop_wavefunctions()])
1943
1944 - def get_max_loop_rank(self):
1945 """ Returns the rank of the contributing loop with maximum rank """ 1946 r_list = [lamp.get_analytic_info('wavefunction_rank') for ldiag in \ 1947 self.get_loop_diagrams() for lamp in ldiag.get_loop_amplitudes()] 1948 if len(r_list)==0: 1949 return 0 1950 else: 1951 return max(r_list)
1952
1953 - def get_max_loop_particle_spin(self):
1954 """ Returns the spin of the loop particle with maximum spin among all 1955 the loop contributing to this ME""" 1956 return max([lwf.get('spin') for lwf in \ 1957 self.get_all_loop_wavefunctions()])
1958
1959 - def relabel_loop_amplitudes(self):
1960 """Give a unique number to each non-equivalent (at the level of the output) 1961 LoopHelasAmplitude """ 1962 1963 LoopHelasAmplitudeRecognized=[] 1964 for lamp in \ 1965 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]): 1966 lamp.set('number',-1) 1967 for lamp2 in LoopHelasAmplitudeRecognized: 1968 if lamp.is_equivalent(lamp2): 1969 # The if statement below would be to turn the optimization off 1970 # if False: 1971 lamp.set('number',lamp2.get('number')) 1972 break; 1973 if lamp.get('number')==-1: 1974 lamp.set('number',(len(LoopHelasAmplitudeRecognized)+1)) 1975 LoopHelasAmplitudeRecognized.append(lamp)
1976
1977 - def relabel_loop_amplitudes_optimized(self):
1978 """Give a unique number to each LoopHelasAmplitude. These will be the 1979 number used for the LOOPCOEF array in the optimized output and the 1980 grouping is done in a further stage by adding all the LOOPCOEF sharing 1981 the same denominator to a given one using the 'loop_group_id' attribute 1982 of the LoopHelasAmplitudes. """ 1983 1984 lamp_number=1 1985 for lamp in \ 1986 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]): 1987 lamp.set('number',lamp_number) 1988 lamp_number += 1
1989
1990 - def relabel_loop_wfs_and_amps(self,wfnumber):
1991 """ Give the correct number for the default output to the wavefunctions 1992 and amplitudes building the loops """ 1993 1994 # We want first the CT amplitudes and only then the loop ones. 1995 CT_ampnumber=1 1996 loop_ampnumber=self.get_number_of_CT_amplitudes()+1 1997 loopwfnumber=1 1998 # Now the loop ones 1999 for loopdiag in self.get_loop_diagrams(): 2000 for wf in loopdiag.get('wavefunctions'): 2001 wf.set('number',wfnumber) 2002 wfnumber=wfnumber+1 2003 for loopamp in loopdiag.get_loop_amplitudes(): 2004 loopwfnumber=1 2005 for loopwf in loopamp['wavefunctions']: 2006 loopwf.set('number',loopwfnumber) 2007 loopwfnumber=loopwfnumber+1 2008 for amp in loopamp['amplitudes']: 2009 amp.set('number',loop_ampnumber) 2010 loop_ampnumber=loop_ampnumber+1 2011 for ctamp in loopdiag.get_ct_amplitudes(): 2012 ctamp.set('number',CT_ampnumber) 2013 CT_ampnumber=CT_ampnumber+1 2014 # Finally the loopUVCT ones 2015 for loopUVCTdiag in self.get_loop_UVCT_diagrams(): 2016 for wf in loopUVCTdiag.get('wavefunctions'): 2017 wf.set('number',wfnumber) 2018 wfnumber=wfnumber+1 2019 for amp in loopUVCTdiag.get('amplitudes'): 2020 amp.set('number',CT_ampnumber) 2021 CT_ampnumber=CT_ampnumber+1
2022
2023 - def relabel_loop_wfs_and_amps_optimized(self, wfnumber):
2024 """ Give the correct number for the optimized output to the wavefunctions 2025 and amplitudes building the loops """ 2026 CT_ampnumber=1 2027 loop_ampnumber=self.get_number_of_CT_amplitudes()+1 2028 loopwfnumber=1 2029 # Now the loop ones 2030 for loopdiag in self.get_loop_diagrams(): 2031 for wf in loopdiag.get('wavefunctions'): 2032 wf.set('number',wfnumber) 2033 wfnumber=wfnumber+1 2034 for lwf in loopdiag.get('loop_wavefunctions'): 2035 lwf.set('number',loopwfnumber) 2036 loopwfnumber=loopwfnumber+1 2037 for loopamp in loopdiag.get_loop_amplitudes(): 2038 # Set the number of the starting wavefunction (common to all 2039 # diagrams) to one. 2040 loopamp.get_starting_loop_wavefunction().set('number',0) 2041 for amp in loopamp['amplitudes']: 2042 amp.set('number',loop_ampnumber) 2043 loop_ampnumber=loop_ampnumber+1 2044 for ctamp in loopdiag.get_ct_amplitudes(): 2045 ctamp.set('number',CT_ampnumber) 2046 CT_ampnumber=CT_ampnumber+1 2047 # Finally the loopUVCT ones 2048 for loopUVCTdiag in self.get_loop_UVCT_diagrams(): 2049 for wf in loopUVCTdiag.get('wavefunctions'): 2050 wf.set('number',wfnumber) 2051 wfnumber=wfnumber+1 2052 for amp in loopUVCTdiag.get('amplitudes'): 2053 amp.set('number',CT_ampnumber) 2054 CT_ampnumber=CT_ampnumber+1
2055
2056 - def relabel_helas_objects(self):
2057 """After the generation of the helas objects, we can give up on having 2058 a unique number identifying the helas wavefunction and amplitudes and 2059 instead use a labeling which is optimal for the output of the loop process. 2060 Also we tag all the LoopHelasAmplitude which are identical with the same 2061 'number' attribute.""" 2062 2063 # Number the LoopHelasAmplitude depending of the type of output 2064 if self.optimized_output: 2065 self.relabel_loop_amplitudes_optimized() 2066 else: 2067 self.relabel_loop_amplitudes() 2068 2069 # Start with the born diagrams 2070 wfnumber=1 2071 ampnumber=1 2072 for borndiag in self.get_born_diagrams(): 2073 for wf in borndiag.get('wavefunctions'): 2074 wf.set('number',wfnumber) 2075 wfnumber=wfnumber+1 2076 for amp in borndiag.get('amplitudes'): 2077 amp.set('number',ampnumber) 2078 ampnumber=ampnumber+1 2079 2080 # Number the HelasWavefunctions and Amplitudes from the loops 2081 # depending of the type of output 2082 if self.optimized_output: 2083 self.relabel_loop_wfs_and_amps_optimized(wfnumber) 2084 for lwf in [lwf for loopdiag in self.get_loop_diagrams() for \ 2085 lwf in loopdiag.get('loop_wavefunctions')]: 2086 lwf.set('me_id',lwf.get('number')) 2087 else: 2088 self.relabel_loop_wfs_and_amps(wfnumber) 2089 2090 # Finally, for loops we do not reuse previously defined wavefunctions to 2091 # store new ones. So that 'me_id' is always equal to 'number'. 2092 for wf in self.get_all_wavefunctions(): 2093 wf.set('me_id',wf.get('number'))
2094 2095
2096 - def get_number_of_wavefunctions(self):
2097 """Gives the total number of wavefunctions for this ME, including the 2098 loop ones""" 2099 2100 return len(self.get_all_wavefunctions())
2101
2102 - def get_number_of_loop_wavefunctions(self):
2103 """ Gives the total number of loop wavefunctions for this ME.""" 2104 return sum([len(ldiag.get('loop_wavefunctions')) for ldiag in \ 2105 self.get_loop_diagrams()])
2106
2107 - def get_number_of_external_wavefunctions(self):
2108 """Gives the total number of wavefunctions for this ME, excluding the 2109 loop ones.""" 2110 2111 return sum([ len(d.get('wavefunctions')) for d in self.get('diagrams')])
2112
2113 - def get_all_wavefunctions(self):
2114 """Gives a list of all wavefunctions for this ME""" 2115 2116 allwfs=sum([d.get('wavefunctions') for d in self.get('diagrams')], []) 2117 for d in self['diagrams']: 2118 if isinstance(d,LoopHelasDiagram): 2119 for l in d.get_loop_amplitudes(): 2120 allwfs += l.get('wavefunctions') 2121 2122 return allwfs
2123
2124 - def get_all_loop_wavefunctions(self):
2125 """Gives a list of all the loop wavefunctions for this ME""" 2126 2127 return helas_objects.HelasWavefunctionList( 2128 # In the default output, this is where the loop wavefunction 2129 # are placed 2130 [lwf for ldiag in self.get_loop_diagrams() 2131 for lamp in ldiag.get_loop_amplitudes() 2132 for lwf in lamp.get('wavefunctions')]+ 2133 # In the optimized one they are directly in the 2134 # 'loop_wavefunctions' attribute of the loop diagrams 2135 [lwf for ldiag in self.get_loop_diagrams() for lwf in 2136 ldiag.get('loop_wavefunctions')])
2137
2138 - def get_number_of_amplitudes(self):
2139 """Gives the total number of amplitudes for this ME, including the loop 2140 ones.""" 2141 2142 return len(self.get_all_amplitudes())
2143
2144 - def get_number_of_CT_amplitudes(self):
2145 """Gives the total number of CT amplitudes for this ME. (i.e the amplitudes 2146 which are not LoopHelasAmplitudes nor within them.)""" 2147 2148 return sum([len(d.get_ct_amplitudes()) for d in (self.get_loop_diagrams()+ 2149 self.get_loop_UVCT_diagrams())])
2150
2151 - def get_number_of_external_amplitudes(self):
2152 """Gives the total number of amplitudes for this ME, excluding those 2153 inside the loop amplitudes. (So only one is counted per loop amplitude.) 2154 """ 2155 2156 return sum([ len(d.get('amplitudes')) for d in \ 2157 self.get('diagrams')])
2158
2159 - def get_number_of_loop_amplitudes(self):
2160 """Gives the total number of helas amplitudes for the loop diagrams of this ME, 2161 excluding those inside the loop amplitudes, but including the CT-terms. 2162 (So only one amplitude is counted per loop amplitude.) 2163 """ 2164 2165 return sum([len(d.get('amplitudes')) for d in (self.get_loop_diagrams()+ 2166 self.get_loop_UVCT_diagrams())])
2167
2168 - def get_number_of_born_amplitudes(self):
2169 """Gives the total number of amplitudes for the born diagrams of this ME 2170 """ 2171 2172 return sum([len(d.get('amplitudes')) for d in self.get_born_diagrams()])
2173
2174 - def get_all_amplitudes(self):
2175 """Gives a list of all amplitudes for this ME""" 2176 2177 allamps=sum([d.get_regular_amplitudes() for d in self.get('diagrams')], []) 2178 for d in self['diagrams']: 2179 if isinstance(d,LoopHelasDiagram): 2180 for l in d.get_loop_amplitudes(): 2181 allamps += l.get('amplitudes') 2182 2183 return allamps
2184
2185 - def get_born_diagrams(self):
2186 """Gives a list of the born diagrams for this ME""" 2187 2188 return helas_objects.HelasDiagramList([hd for hd in self['diagrams'] if\ 2189 not isinstance(hd,LoopHelasDiagram)])
2190
2191 - def get_loop_diagrams(self):
2192 """Gives a list of the loop diagrams for this ME""" 2193 2194 return helas_objects.HelasDiagramList([hd for hd in self['diagrams'] if\ 2195 isinstance(hd,LoopHelasDiagram) and\ 2196 len(hd.get_loop_amplitudes())>=1])
2197
2198 - def get_loop_UVCT_diagrams(self):
2199 """Gives a list of the loop UVCT diagrams for this ME""" 2200 2201 return helas_objects.HelasDiagramList([hd for hd in self['diagrams'] if\ 2202 isinstance(hd,LoopHelasDiagram) and\ 2203 len(hd.get_loop_UVCTamplitudes())>=1])
2204
2205 - def compute_all_analytic_information(self, alohaModel=None):
2206 """Make sure that all analytic pieces of information about all 2207 loop wavefunctions and loop amplitudes building this loop helas matrix 2208 element are computed so that they can be recycled later, typically 2209 without the need of specifying an alohaModel. 2210 Notice that for now this function is called at the end of the 2211 generat_helas_diagrams function and the alohaModel is created here. 2212 In principle, it might be better to have this function called by the 2213 exporter just after export_v4 because at this stage an alohaModel is 2214 already created and can be specified here instead of being generated. 2215 This can make a difference for very complicated models.""" 2216 2217 if alohaModel is None: 2218 # Generate it here 2219 model = self.get('processes')[0].get('model') 2220 myAlohaModel = create_aloha.AbstractALOHAModel(model.get('name')) 2221 myAlohaModel.add_Lorentz_object(model.get('lorentz')) 2222 else: 2223 # Use the one provided 2224 myAlohaModel = alohaModel 2225 2226 for lwf in self.get_all_loop_wavefunctions(): 2227 lwf.compute_analytic_information(myAlohaModel) 2228 2229 for diag in self.get_loop_diagrams(): 2230 for amp in diag.get_loop_amplitudes(): 2231 amp.compute_analytic_information(myAlohaModel)
2232
2233 - def get_used_lorentz(self):
2234 """Return a list of (lorentz_name, tags, outgoing) with 2235 all lorentz structures used by this LoopHelasMatrixElement.""" 2236 2237 # Loop version of the function which add to the tuple wether it is a loop 2238 # structure or not so that aloha knows if it has to produce the subroutine 2239 # which removes the denominator in the propagator of the wavefunction created. 2240 output = [] 2241 2242 for wa in self.get_all_wavefunctions() + self.get_all_amplitudes(): 2243 if wa.get('interaction_id') in [0,-1]: 2244 continue 2245 output.append(wa.get_aloha_info(self.optimized_output)); 2246 2247 return output
2248
2249 - def get_used_helas_loop_amps(self):
2250 """ Returns the list of the helas loop amplitude of type 2251 CALL LOOP_I_J(_K)(...) used for this matrix element """ 2252 2253 # In the optimized output, we don't care about the number of couplings 2254 # in a given loop. 2255 if self.optimized_output: 2256 last_relevant_index=3 2257 else: 2258 last_relevant_index=4 2259 2260 return list(set([lamp.get_call_key()[1:last_relevant_index] \ 2261 for ldiag in self.get_loop_diagrams() for lamp in \ 2262 ldiag.get_loop_amplitudes()]))
2263
2264 - def get_used_wl_updates(self):
2265 """ Returns a list of the necessary updates of the loop wavefunction 2266 polynomials """ 2267 2268 return list(set([(lwf.get_analytic_info('wavefunction_rank')-\ 2269 lwf.get_analytic_info('interaction_rank'), 2270 lwf.get_analytic_info('interaction_rank')) 2271 for ldiag in self.get_loop_diagrams() 2272 for lwf in ldiag.get('loop_wavefunctions')]))
2273
2274 - def get_used_couplings(self):
2275 """Return a list with all couplings used by this 2276 HelasMatrixElement.""" 2277 2278 answer = super(LoopHelasMatrixElement, self).get_used_couplings() 2279 for diag in self.get_loop_UVCT_diagrams(): 2280 answer.extend([amp.get_used_UVCT_couplings() for amp in \ 2281 diag.get_loop_UVCTamplitudes()]) 2282 return answer
2283
2284 - def get_color_amplitudes(self):
2285 """ Just to forbid the usage of this generic function in a 2286 LoopHelasMatrixElement""" 2287 2288 raise self.PhysicsObjectError, \ 2289 "Usage of get_color_amplitudes is not allowed in a LoopHelasMatrixElement"
2290
2291 - def get_born_color_amplitudes(self):
2292 """Return a list of (coefficient, amplitude number) lists, 2293 corresponding to the JAMPs for this born color basis and the born 2294 diagrams of this LoopMatrixElement. The coefficients are given in the 2295 format (fermion factor, color coeff (frac), imaginary, Nc power).""" 2296 2297 return super(LoopHelasMatrixElement,self).generate_color_amplitudes(\ 2298 self['born_color_basis'],self.get_born_diagrams())
2299
2300 - def get_loop_color_amplitudes(self):
2301 """Return a list of (coefficient, amplitude number) lists, 2302 corresponding to the JAMPs for this loop color basis and the loop 2303 diagrams of this LoopMatrixElement. The coefficients are given in the 2304 format (fermion factor, color coeff (frac), imaginary, Nc power).""" 2305 2306 diagrams=self.get_loop_diagrams() 2307 color_basis=self['loop_color_basis'] 2308 2309 if not color_basis: 2310 # No color, simply add all amplitudes with correct factor 2311 # for first color amplitude 2312 col_amp = [] 2313 for diagram in diagrams: 2314 for amplitude in diagram.get('amplitudes'): 2315 col_amp.append(((amplitude.get('fermionfactor'), 2316 1, False, 0), 2317 amplitude.get('number'))) 2318 return [col_amp] 2319 2320 # There is a color basis - create a list of coefficients and 2321 # amplitude numbers 2322 2323 # Remember that with get_base_amplitude of LoopHelasMatrixElement, 2324 # we get several base_objects.Diagrams for a given LoopHelasDiagram: 2325 # One for the loop and one for each counter-term. 2326 # We should then here associate what are the HelasAmplitudes associated 2327 # to each diagram number using the function 2328 # get_helas_amplitudes_loop_diagrams(). 2329 LoopDiagramsHelasAmplitudeList=self.get_helas_amplitudes_loop_diagrams() 2330 # The HelasLoopAmplitudes should be unfolded to the HelasAmplitudes 2331 # (only one for the current version) they contain. 2332 for i, helas_amp_list in enumerate(LoopDiagramsHelasAmplitudeList): 2333 new_helas_amp_list=helas_objects.HelasAmplitudeList() 2334 for helas_amp in helas_amp_list: 2335 if isinstance(helas_amp,LoopHelasAmplitude): 2336 new_helas_amp_list.extend(helas_amp['amplitudes']) 2337 else: 2338 new_helas_amp_list.append(helas_amp) 2339 LoopDiagramsHelasAmplitudeList[i]=new_helas_amp_list 2340 2341 # print "I get LoopDiagramsHelasAmplitudeList=" 2342 # for i, elem in enumerate(LoopDiagramsHelasAmplitudeList): 2343 # print "LoopDiagramsHelasAmplitudeList[",i,"]=",[amp.get('number') for amp in LoopDiagramsHelasAmplitudeList[i]] 2344 2345 col_amp_list = [] 2346 for i, col_basis_elem in \ 2347 enumerate(sorted(color_basis.keys())): 2348 2349 col_amp = [] 2350 # print "color_basis[col_basis_elem]=",color_basis[col_basis_elem] 2351 for diag_tuple in color_basis[col_basis_elem]: 2352 res_amps = filter(lambda amp: \ 2353 tuple(amp.get('color_indices')) == diag_tuple[1], 2354 LoopDiagramsHelasAmplitudeList[diag_tuple[0]]) 2355 if not res_amps: 2356 raise self.PhysicsObjectError, \ 2357 """No amplitude found for color structure 2358 %s and color index chain (%s) (diagram %i)""" % \ 2359 (col_basis_elem, 2360 str(diag_tuple[1]), 2361 diag_tuple[0]) 2362 2363 for res_amp in res_amps: 2364 col_amp.append(((res_amp.get('fermionfactor'), 2365 diag_tuple[2], 2366 diag_tuple[3], 2367 diag_tuple[4]), 2368 res_amp.get('number'))) 2369 2370 col_amp_list.append(col_amp) 2371 2372 return col_amp_list
2373
2374 - def get_helas_amplitudes_loop_diagrams(self):
2375 """ When creating the base_objects.Diagram in get_base_amplitudes(), 2376 each LoopHelasDiagram will lead to one loop_base_objects.LoopDiagram 2377 for its LoopHelasAmplitude and one other for each of its counter-term 2378 (with different interaction id). This function return a list for which 2379 each element is a HelasAmplitudeList corresponding to the HelasAmplitudes 2380 related to a given loop_base_objects.LoopDiagram generated """ 2381 2382 amplitudes_loop_diagrams=[] 2383 2384 for diag in self.get_loop_diagrams(): 2385 # We start by adding the loop topology 2386 amplitudes_loop_diagrams.append(diag.get_loop_amplitudes()) 2387 # Then add a diagram for each counter-term with a different 2388 # interactions id. (because it involves a different interaction 2389 # which possibly brings new color structures). 2390 # This is strictly speaking not necessary since Counter-Terms 2391 # cannot in principle bring new color structures into play. 2392 # The dictionary ctIDs has the ct interactions ID as keys 2393 # and a HelasAmplitudeList of the corresponding HelasAmplitude as 2394 # values. 2395 ctIDs={} 2396 for ctamp in diag.get_ct_amplitudes(): 2397 try: 2398 ctIDs[ctamp.get('interaction_id')].append(ctamp) 2399 except KeyError: 2400 ctIDs[ctamp.get('interaction_id')]=\ 2401 helas_objects.HelasAmplitudeList([ctamp]) 2402 # To have a canonical order of the CT diagrams, we sort them according 2403 # to their interaction_id value. 2404 keys=ctIDs.keys() 2405 keys.sort() 2406 for key in keys: 2407 amplitudes_loop_diagrams.append(ctIDs[key]) 2408 2409 for diag in self.get_loop_UVCT_diagrams(): 2410 amplitudes_loop_diagrams.append(diag.get_loop_UVCTamplitudes()) 2411 2412 return amplitudes_loop_diagrams
2413
2414 - def get_base_amplitude(self):
2415 """Generate a loop_diagram_generation.LoopAmplitude from a 2416 LoopHelasMatrixElement. This is used to generate both color 2417 amplitudes and diagram drawing.""" 2418 2419 # Need to take care of diagram numbering for decay chains 2420 # before this can be used for those! 2421 2422 optimization = 1 2423 if len(filter(lambda wf: wf.get('number') == 1, 2424 self.get_all_wavefunctions())) > 1: 2425 optimization = 0 2426 2427 model = self.get('processes')[0].get('model') 2428 2429 wf_dict = {} 2430 vx_list = [] 2431 diagrams = base_objects.DiagramList() 2432 2433 # Start with the born 2434 for diag in self.get_born_diagrams(): 2435 newdiag=diag.get('amplitudes')[0].get_base_diagram(\ 2436 wf_dict, vx_list, optimization) 2437 diagrams.append(loop_base_objects.LoopDiagram({ 2438 'vertices':newdiag['vertices'],'type':0})) 2439 2440 # Store here the type of the last LoopDiagram encountered to reuse the 2441 # same value, but negative, for the corresponding counter-terms. 2442 # It is not strictly necessary, it only has to be negative. 2443 type=1 2444 for HelasAmpList in self.get_helas_amplitudes_loop_diagrams(): 2445 # We use uniformly the class LoopDiagram for the diagrams stored 2446 # in LoopAmplitude 2447 if isinstance(HelasAmpList[0],LoopHelasAmplitude): 2448 diagrams.append(HelasAmpList[0].get_base_diagram(\ 2449 wf_dict, vx_list, optimization)) 2450 type=diagrams[-1]['type'] 2451 elif isinstance(HelasAmpList[0],LoopHelasUVCTAmplitude): 2452 diagrams.append(HelasAmpList[0].\ 2453 get_base_diagram(wf_dict, vx_list, optimization)) 2454 else: 2455 newdiag=HelasAmpList[0].get_base_diagram(wf_dict, vx_list, optimization) 2456 diagrams.append(loop_base_objects.LoopDiagram({ 2457 'vertices':newdiag['vertices'],'type':-type})) 2458 2459 2460 for diag in diagrams: 2461 diag.calculate_orders(self.get('processes')[0].get('model')) 2462 2463 return loop_diagram_generation.LoopAmplitude({\ 2464 'process': self.get('processes')[0], 2465 'diagrams': diagrams})
2466
2467 #=============================================================================== 2468 # LoopHelasProcess 2469 #=============================================================================== 2470 -class LoopHelasProcess(helas_objects.HelasMultiProcess):
2471 """LoopHelasProcess: Analogous of HelasMultiProcess except that it is suited 2472 for LoopAmplitude and with the peculiarity that it is always treating only 2473 one loop amplitude. So this LoopHelasProcess correspond to only one single 2474 subprocess without multiparticle labels (contrary to HelasMultiProcess)""" 2475 2476 # Type of HelasMatrixElement to be generated by this class of HelasMultiProcess 2477 matrix_element_class = LoopHelasMatrixElement 2478
2479 - def __init__(self, argument=None, combine_matrix_elements=True, 2480 optimized_output = True):
2481 """ Allow for the initialization of the HelasMultiProcess with the 2482 right argument 'optimized_output' for the helas_matrix_element options.""" 2483 2484 super(LoopHelasProcess, self).__init__(argument, combine_matrix_elements, 2485 matrix_element_opts = {'optimized_output' : optimized_output})
2486 2487 @classmethod
2488 - def process_color(cls,matrix_element,color_information):
2489 """ Process the color information for a given matrix 2490 element made of a loop diagrams. It will create a different 2491 color matrix depending on wether the process has a born or not.""" 2492 2493 # Define the objects stored in the contained color_information 2494 for key in color_information: 2495 exec("%s=color_information['%s']"%(key,key)) 2496 2497 # Now that the Helas Object generation is finished, we must relabel 2498 # the wavefunction and the amplitudes according to what should be 2499 # used for the output. 2500 matrix_element.relabel_helas_objects() 2501 2502 # Always create an empty color basis, and the 2503 # list of raw colorize objects (before 2504 # simplification) associated with amplitude 2505 new_amp = matrix_element.get_base_amplitude() 2506 matrix_element.set('base_amplitude', new_amp) 2507 # Process the loop color basis which is needed anyway 2508 loop_col_basis = loop_color_amp.LoopColorBasis() 2509 loop_colorize_obj = loop_col_basis.create_loop_color_dict_list(\ 2510 matrix_element.get('base_amplitude')) 2511 try: 2512 # If the loop color configuration of the ME has 2513 # already been considered before, recycle 2514 # the information 2515 loop_col_basis_index = list_colorize.index(loop_colorize_obj) 2516 loop_col_basis = list_color_basis[loop_col_basis_index] 2517 except ValueError: 2518 # If not, create color basis accordingly 2519 list_colorize.append(loop_colorize_obj) 2520 loop_col_basis.build() 2521 loop_col_basis_index = len(list_color_basis) 2522 list_color_basis.append(loop_col_basis) 2523 logger.info(\ 2524 "Processing color information for %s" % \ 2525 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 2526 replace('Process', 'loop process')) 2527 else: # Found identical color 2528 logger.info(\ 2529 "Reusing existing color information for %s" % \ 2530 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 2531 replace('Process', 'loop process')) 2532 2533 if new_amp['process']['has_born']: 2534 born_col_basis = loop_color_amp.LoopColorBasis() 2535 born_colorize_obj = born_col_basis.create_born_color_dict_list(\ 2536 matrix_element.get('base_amplitude')) 2537 try: 2538 # If the loop color configuration of the ME has 2539 # already been considered before, recycle 2540 # the information 2541 born_col_basis_index = list_colorize.index(born_colorize_obj) 2542 born_col_basis = list_color_basis[born_col_basis_index] 2543 except ValueError: 2544 # If not, create color basis accordingly 2545 list_colorize.append(born_colorize_obj) 2546 born_col_basis.build() 2547 born_col_basis_index = len(list_color_basis) 2548 list_color_basis.append(born_col_basis) 2549 logger.info(\ 2550 "Processing color information for %s" % \ 2551 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 2552 replace('Process', 'born process')) 2553 else: # Found identical color 2554 logger.info(\ 2555 "Reusing existing color information for %s" % \ 2556 matrix_element.get('processes')[0].nice_string(print_weighted=False).\ 2557 replace('Process', 'born process')) 2558 loopborn_matrices_key=(loop_col_basis_index,born_col_basis_index) 2559 else: 2560 loopborn_matrices_key=(loop_col_basis_index,loop_col_basis_index) 2561 2562 2563 # Now we try to recycle the color matrix 2564 try: 2565 # If the color configuration of the ME has 2566 # already been considered before, recycle 2567 # the information 2568 col_matrix = dict_loopborn_matrices[loopborn_matrices_key] 2569 except KeyError: 2570 # If not, create color matrix accordingly 2571 col_matrix = color_amp.ColorMatrix(\ 2572 list_color_basis[loopborn_matrices_key[0]], 2573 list_color_basis[loopborn_matrices_key[1]]) 2574 dict_loopborn_matrices[loopborn_matrices_key]=col_matrix 2575 logger.info(\ 2576 "Creating color matrix %s" % \ 2577 matrix_element.get('processes')[0].nice_string().\ 2578 replace('Process', 'loop process')) 2579 else: # Found identical color 2580 logger.info(\ 2581 "Reusing existing color matrix for %s" % \ 2582 matrix_element.get('processes')[0].nice_string().\ 2583 replace('Process', 'loop process')) 2584 2585 matrix_element.set('loop_color_basis',loop_col_basis) 2586 if new_amp['process']['has_born']: 2587 matrix_element.set('born_color_basis',born_col_basis) 2588 matrix_element.set('color_matrix',col_matrix)
2589