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