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