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