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