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