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