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