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

Source Code for Module madgraph.loop.loop_helas_objects

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