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