1
2
3
4
5
6
7
8
9
10
11
12
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')
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
66
75
76 - def filter(self, name, value):
77 """Filter for valid LoopHelasAmplitude property values."""
78
79 if name=='UVCT_couplings':
80 if not isinstance(value, list):
81 raise self.PhysicsObjectError, \
82 "%s is not a valid list for UVCT_couplings" % str(value)
83 for id in value:
84 if not isinstance(id, str) and not isinstance(id, int):
85 raise self.PhysicsObjectError, \
86 "%s is not a valid string or integer for UVCT_couplings" % str(value)
87
88 if name == 'UVCT_orders':
89 if not isinstance(value, dict):
90 raise self.PhysicsObjectError, \
91 "%s is not a valid dictionary" % str(value)
92
93 if name == 'type':
94 if not isinstance(value, str):
95 raise self.PhysicsObjectError, \
96 "%s is not a valid string" % str(value)
97
98 else:
99 return super(LoopHelasUVCTAmplitude,self).filter(name, value)
100
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
170
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
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
222
223
224
225
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
266
267
268 - def get(self, name):
275
276 - def filter(self, name, value):
277 """Filter for valid LoopHelasAmplitude property values."""
278
279 if name=='wavefunctions':
280 if not isinstance(value, helas_objects.HelasWavefunctionList):
281 raise self.PhysicsObjectError, \
282 "%s is not a valid list of HelasWaveFunctions" % str(value)
283 for wf in value:
284 if not wf['is_loop']:
285 raise self.PhysicsObjectError, \
286 "Wavefunctions from a LoopHelasAmplitude must be from a loop."
287
288 elif name=='amplitudes':
289 if not isinstance(value, helas_objects.HelasAmplitudeList):
290 raise self.PhysicsObjectError, \
291 "%s is not a valid list of HelasAmplitudes" % str(value)
292
293 elif name in ['type','loop_group_id','multiplier','loopsymmetryfactor']:
294 if not isinstance(value, int):
295 raise self.PhysicsObjectError, \
296 "%s is not a valid integer for the attribute '%s'" %(str(value),name)
297
298 else:
299 return super(LoopHelasAmplitude,self).filter(name, value)
300
301 return True
302
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
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
352
353
354
355
356
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
362
363
364
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
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
398
399
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
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
534
536 """ The fermion factor is not implemented for this object but in the
537 subamplitude"""
538 self['fermion_factor']=0
539 for amp in self.get('amplitudes'):
540 amp.get('fermionfactor')
541
543 """ Calculate the loop symmetry factor. For one-loop matrix elements,
544 it is always 2 for bubble with identical particles and tadpoles with self-conjugated particles
545 and 1 otherwise."""
546
547
548
549
550 self['loopsymmetryfactor']=1
551
552 physical_wfs = [wf for wf in self.get('wavefunctions') if wf.get('interaction_id')!=0]
553 if len(physical_wfs)==1:
554 if physical_wfs[0].get('self_antipart'):
555 self['loopsymmetryfactor']=2
556 elif len(physical_wfs)==2:
557 if physical_wfs[0].get('particle')==physical_wfs[1].get('antiparticle'):
558 self['loopsymmetryfactor']=2
559
564 """LoopHelasDiagram object, behaving exactly as a Diagram except that
565 it has a couple of additional functions which can reconstruct and
566 handle loop amplitudes.
567 """
568
570 """ Quick access to ALL non-loop amplitudes, including those which are
571 inside the LoopAmplitudes defined in this diagram."""
572
573 ampList=helas_objects.HelasAmplitudeList()
574 for loopAmp in self.get_loop_amplitudes():
575 ampList.extend(loopAmp['amplitudes'])
576 ampList.extend(self.get_ct_amplitudes())
577 return ampList
578
580 """ Quick access to the regular amplitudes defined directly in this
581 diagram (not in the LoopAmplitudes). Usually they correspond to the
582 counter-terms. """
583
584 return helas_objects.HelasAmplitudeList([amp for amp in \
585 self['amplitudes'] if not isinstance(amp, LoopHelasAmplitude)])
586
592
598
603 """LoopHelasMatrixElement: list of processes with identical Helas
604 calls, and the list of LoopHelasDiagrams associated with the processes.
605 It works as for the HelasMatrixElement except for the loop-related features
606 which are defined here. """
607
624
625 - def filter(self, name, value):
626 """Filter for valid diagram property values."""
627
628 if name=='born_color_basis' or name=='loop_color_basis':
629 if not isinstance(value,color_amp.ColorBasis):
630 raise self.PhysicsObjectError, \
631 "%s is not a valid color basis" % str(value)
632 elif name=='loop_groups':
633 if not isinstance(value,list):
634 raise self.PhysicsObjectError, \
635 "%s is not a valid list"%str(value)
636 for (dkey, dvalue) in value:
637 if not isinstance(dvalue,helas_objects.HelasAmplitudeList):
638 raise self.PhysicsObjectError, \
639 "%s is not a valid HelasAmplitudeList."%str(dvalue)
640 if not isinstance(dkey,tuple):
641 raise self.PhysicsObjectError, \
642 "%s is not a valid tuple."%str(dkey)
643 else:
644 return super(LoopHelasMatrixElement,self).filter(name, value)
645
646 return True
647
648 - def get(self,name):
649 """Overload in order to return the loop_color_basis when simply asked
650 for color_basis. The setter is not updated to avoid side effects."""
651
652 if name=='color_basis':
653 return self['loop_color_basis']
654 elif name=='loop_groups':
655 if not self['loop_groups']:
656 self.identify_loop_groups()
657 return self['loop_groups']
658 else:
659 return super(LoopHelasMatrixElement,self).get(name)
660
662 """ Identify what are the loops sharing the same denominators and put
663 them together in the 'loop_groups' attribute of this object. """
664
665 identified_denom_structures=[]
666 for lamp in [lamp for ldiag in self.get_loop_diagrams() for lamp in \
667 ldiag.get_loop_amplitudes()]:
668 denom_structure=lamp.get_denominators()
669 try:
670 denom_index=identified_denom_structures.index(denom_structure)
671 self['loop_groups'][denom_index][1].append(lamp)
672 except ValueError:
673 denom_index=len(self['loop_groups'])
674 self['loop_groups'].append((denom_structure,
675 helas_objects.HelasAmplitudeList([lamp,])))
676 identified_denom_structures.append(denom_structure)
677 lamp.set('loop_group_id',denom_index)
678
679
680
681 self['loop_groups']=[(group[0],helas_objects.HelasAmplitudeList(
682 sorted(group[1],key=lambda lamp: \
683 lamp.get_analytic_info('wavefunction_rank'),reverse=True)))
684 for group in self['loop_groups']]
685
686
687 self['loop_groups']=sorted(self['loop_groups'],key=lambda group: \
688 group[1][0].get('number'))
689 self.update_loop_group_ids()
690
692 """ Make sure never to use this optimization in the loop context."""
693
694 for diag in helas_diagrams:
695 for wf in diag['wavefunctions']:
696 wf.set('me_id',wf.get('number'))
697
698 return helas_diagrams
699
701 """ Make sure that the attribute 'loop_group_id' of all loop amplitudes
702 in the 'loop_groups' list is correct given the order of 'loop_groups'"""
703
704 for i, group in enumerate(self['loop_groups']):
705 for lamp in group[1]:
706 lamp.set('loop_group_id',i)
707
709 """ Perform the simple color processing from a single matrix element
710 (without optimization then). This is called from the initialization
711 and overloaded here in order to have the correct treatment """
712
713
714
715 self.relabel_helas_objects()
716 self.get('loop_color_basis').build_loop(self.get('base_amplitude'))
717 if self.get('base_amplitude')['process']['has_born']:
718 self.get('born_color_basis').build_born(self.get('base_amplitude'))
719 self.set('color_matrix',\
720 color_amp.ColorMatrix(self.get('loop_color_basis'),\
721 self.get('born_color_basis')))
722 else:
723 self.set('color_matrix',\
724 color_amp.ColorMatrix(self.get('loop_color_basis')))
725
727 """Return particle property names as a nicely sorted list."""
728
729 return ['processes', 'identical_particle_factor',
730 'diagrams', 'born_color_basis','loop_color_basis',
731 'color_matrix','base_amplitude', 'has_mirror_process',
732 'loop_groups']
733
734
735 - def __init__(self, amplitude=None, optimization=1,
736 decay_ids=[], gen_color=True, optimized_output=False):
737 """Constructor for the LoopHelasMatrixElement. For now, it works exactly
738 as for the HelasMatrixElement one."""
739 self.optimized_output=optimized_output
740 super(LoopHelasMatrixElement, self).__init__(amplitude, optimization,\
741 decay_ids, gen_color)
742
743
744
745
746
747
749 """Comparison between different loop matrix elements. It works exactly as for
750 the HelasMatrixElement for now."""
751
752 return super(LoopHelasMatrixElement,self).__eq__(other)
753
755 """Overloading the nonequality operator, to make comparison easy"""
756 return not self.__eq__(other)
757
760 """Starting from a list of LoopDiagrams from the diagram
761 generation, generate the corresponding LoopHelasDiagrams, i.e.,
762 the wave functions and amplitudes (for the loops and their R2 and UV
763 counterterms). Choose between default optimization (= 1, maximum
764 recycling of wavefunctions) or no optimization (= 0, no recycling of
765 wavefunctions, useful for GPU calculations with very restricted memory).
766
767 Note that we need special treatment for decay chains, since
768 the end product then is a wavefunction, not an amplitude.
769 """
770
771 assert isinstance(amplitude, loop_diagram_generation.LoopAmplitude), \
772 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement"
773 assert isinstance(optimization, int), \
774 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement"
775
776 structures = amplitude.get('structure_repository')
777
778 process = amplitude.get('process')
779 has_born = amplitude.get('has_born')
780
781 model = process.get('model')
782
783
784
785 self.sort_split_orders(self.get('processes')[0].get('split_orders'))
786
787
788
789
790
791
792
793 amplitude.order_diagrams_according_to_split_orders(\
794 self.get('processes')[0].get('split_orders'))
795
796
797 wavefunctions = []
798
799
800
801
802
803
804
805
806 structID_to_infos = {}
807
808
809
810 wf_mother_arrays = []
811
812 wf_number = 0
813
814
815 external_wavefunctions = dict([(leg.get('number'),
816 helas_objects.HelasWavefunction(\
817 leg, 0, model, decay_ids)) \
818 for leg in process.get('legs')])
819
820
821
822 external_loop_wfs_dict={}
823
824
825
826 for key in external_wavefunctions.keys():
827 wf = external_wavefunctions[key]
828 if wf.is_boson() and wf.get('state') == 'initial' and \
829 not wf.get('self_antipart'):
830 wf.set('is_part', not wf.get('is_part'))
831
832
833
834 for key in external_wavefunctions.keys():
835 wf = external_wavefunctions[key]
836 if wf.get('leg_state') == False and \
837 not wf.get('self_antipart'):
838 wf.flip_part_antipart()
839
840
841 wf_number = len(process.get('legs'))
842
843
844
845 helas_diagrams = helas_objects.HelasDiagramList()
846
847
848 amplitude_number = 0
849 diagram_number = 0
850
851 def process_born_diagram(diagram, wfNumber, amplitudeNumber, UVCTdiag=False):
852 """ Helper function to process a born diagrams exactly as it is done in
853 HelasMatrixElement for tree-level diagrams. This routine can also
854 process LoopUVCTDiagrams, and if so the argument UVCTdiag must be set
855 to true"""
856
857
858
859
860 number_to_wavefunctions = [{}]
861
862
863 color_lists = [[]]
864
865
866 diagram_wavefunctions = helas_objects.HelasWavefunctionList()
867
868 vertices = copy.copy(diagram.get('vertices'))
869
870
871 lastvx = vertices.pop()
872
873
874
875 for vertex in vertices:
876
877
878
879
880
881
882
883
884
885
886 new_number_to_wavefunctions = []
887 new_color_lists = []
888 for number_wf_dict, color_list in zip(number_to_wavefunctions,
889 color_lists):
890 legs = copy.copy(vertex.get('legs'))
891 last_leg = legs.pop()
892
893 mothers = self.getmothers(legs, number_wf_dict,
894 external_wavefunctions,
895 wavefunctions,
896 diagram_wavefunctions)
897 inter = model.get('interaction_dict')[vertex.get('id')]
898
899
900
901
902 done_color = {}
903 for coupl_key in sorted(inter.get('couplings').keys()):
904 color = coupl_key[0]
905 if color in done_color:
906 wf = done_color[color]
907 wf.get('coupling').append(inter.get('couplings')[coupl_key])
908 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
909 continue
910 wf = helas_objects.HelasWavefunction(last_leg, \
911 vertex.get('id'), model)
912 wf.set('coupling', [inter.get('couplings')[coupl_key]])
913 if inter.get('color'):
914 wf.set('inter_color', inter.get('color')[coupl_key[0]])
915 done_color[color] = wf
916 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
917 wf.set('color_key', color)
918 wf.set('mothers',mothers)
919
920
921
922 wf.set_state_and_particle(model)
923
924
925
926
927 wf, wfNumber = wf.check_and_fix_fermion_flow(\
928 wavefunctions,
929 diagram_wavefunctions,
930 external_wavefunctions,
931 wfNumber)
932
933 new_number_wf_dict = copy.copy(number_wf_dict)
934
935 try:
936 wf = diagram_wavefunctions[\
937 diagram_wavefunctions.index(wf)]
938 except ValueError:
939
940 wfNumber = wfNumber + 1
941 wf.set('number', wfNumber)
942 try:
943
944
945 wf = wavefunctions[wf_mother_arrays.index(\
946 wf.to_array())]
947
948
949 wfNumber = wfNumber - 1
950 except ValueError:
951 diagram_wavefunctions.append(wf)
952
953 new_number_wf_dict[last_leg.get('number')] = wf
954
955
956 new_number_to_wavefunctions.append(\
957 new_number_wf_dict)
958
959 new_color_list = copy.copy(color_list)
960 new_color_list.append(coupl_key[0])
961 new_color_lists.append(new_color_list)
962
963 number_to_wavefunctions = new_number_to_wavefunctions
964 color_lists = new_color_lists
965
966
967
968 if not UVCTdiag:
969 helas_diagram = helas_objects.HelasDiagram()
970 else:
971 helas_diagram = LoopHelasDiagram()
972
973 for number_wf_dict, color_list in zip(number_to_wavefunctions,
974 color_lists):
975
976
977 if lastvx.get('id'):
978 inter = model.get_interaction(lastvx.get('id'))
979 keys = sorted(inter.get('couplings').keys())
980 pdg_codes = [p.get_pdg_code() for p in \
981 inter.get('particles')]
982 else:
983
984
985 inter = None
986 keys = [(0, 0)]
987 pdg_codes = None
988
989
990 legs = lastvx.get('legs')
991 mothers = self.getmothers(legs, number_wf_dict,
992 external_wavefunctions,
993 wavefunctions,
994 diagram_wavefunctions).\
995 sort_by_pdg_codes(pdg_codes, 0)[0]
996
997
998 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
999 diagram_wavefunctions,
1000 external_wavefunctions,
1001 None,
1002 wfNumber,
1003 False,
1004 number_to_wavefunctions)
1005 done_color = {}
1006 for i, coupl_key in enumerate(keys):
1007 color = coupl_key[0]
1008 if inter and color in done_color.keys():
1009 amp = done_color[color]
1010 amp.get('coupling').append(inter.get('couplings')[coupl_key])
1011 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1012 continue
1013 if not UVCTdiag:
1014 amp = helas_objects.HelasAmplitude(lastvx, model)
1015 else:
1016 amp = LoopHelasUVCTAmplitude(lastvx, model)
1017 amp.set('UVCT_orders',diagram.get('UVCT_orders'))
1018 amp.set('UVCT_couplings',diagram.get('UVCT_couplings'))
1019 amp.set('type',diagram.get('type'))
1020 if inter:
1021 amp.set('coupling', [inter.get('couplings')[coupl_key]])
1022 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1023 if inter.get('color'):
1024 amp.set('inter_color', inter.get('color')[color])
1025 amp.set('color_key', color)
1026 done_color[color] = amp
1027 amp.set('mothers', mothers)
1028 amplitudeNumber = amplitudeNumber + 1
1029 amp.set('number', amplitudeNumber)
1030
1031 new_color_list = copy.copy(color_list)
1032 if inter:
1033 new_color_list.append(color)
1034
1035 amp.set('color_indices', new_color_list)
1036
1037
1038 helas_diagram.get('amplitudes').append(amp)
1039
1040
1041
1042 helas_diagram.set('wavefunctions', diagram_wavefunctions)
1043
1044
1045 diagram_wavefunctions.sort(lambda wf1, wf2: \
1046 wf1.get('number') - wf2.get('number'))
1047
1048 if optimization:
1049 wavefunctions.extend(diagram_wavefunctions)
1050 wf_mother_arrays.extend([wf.to_array() for wf \
1051 in diagram_wavefunctions])
1052 else:
1053 wfNumber = len(process.get('legs'))
1054 if self.optimized_output:
1055
1056
1057 wfNumber = wfNumber+1
1058
1059
1060 return helas_diagram, wfNumber, amplitudeNumber
1061
1062 def process_struct(sID, diag_wfs, wfNumber):
1063 """ Scan a structure, create the necessary wavefunctions, add them
1064 to the diagram wavefunctions list, and return a list of bridge
1065 wavefunctions (i.e. those attached to the loop) with a list, ordered
1066 in the same way, of color lists. Each element of these lists
1067 correspond to one choice of color-lorentz structure of this
1068 tree-structure #sID. """
1069
1070
1071
1072
1073 number_to_wavefunctions = [{}]
1074
1075
1076 color_lists = [[]]
1077
1078
1079 bridge_wfs = helas_objects.HelasWavefunctionList()
1080
1081 vertices = copy.copy(structures[sID].get('vertices'))
1082
1083
1084
1085 if len(vertices)==0:
1086 binding_leg=copy.copy(structures[sID]['binding_leg'])
1087 binding_wf = self.getmothers(base_objects.LegList([binding_leg,]),
1088 {},
1089 external_wavefunctions,
1090 wavefunctions,
1091 diag_wfs)
1092
1093
1094 return [(binding_wf[0],[])] ,wfNumber
1095
1096
1097
1098 for i, vertex in enumerate(vertices):
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109 new_number_to_wavefunctions = []
1110 new_color_lists = []
1111 for number_wf_dict, color_list in zip(number_to_wavefunctions,
1112 color_lists):
1113 legs = copy.copy(vertex.get('legs'))
1114 last_leg = legs.pop()
1115
1116 mothers = self.getmothers(legs, number_wf_dict,
1117 external_wavefunctions,
1118 wavefunctions,
1119 diag_wfs)
1120 inter = model.get('interaction_dict')[vertex.get('id')]
1121
1122
1123
1124
1125
1126 grouped_interaction_keys = {}
1127 colors_order = []
1128 for coupl_key in sorted(inter.get('couplings').keys()):
1129 color = coupl_key[0]
1130 if color not in colors_order:
1131 colors_order.append(color)
1132 grouped_interaction_keys[color] = \
1133 (coupl_key, [inter.get('couplings')[coupl_key]], [inter.get('lorentz')[coupl_key[1]]])
1134 else:
1135 grouped_interaction_keys[color][1].append(inter.get('couplings')[coupl_key])
1136 grouped_interaction_keys[color][2].append(inter.get('lorentz')[coupl_key[1]])
1137
1138 for coupl_key, all_couplings, all_lorentz in [grouped_interaction_keys[color] for color in colors_order]:
1139 color = coupl_key[0]
1140 wf = helas_objects.HelasWavefunction(last_leg, vertex.get('id'), model)
1141 wf.set('coupling', all_couplings)
1142 if inter.get('color'):
1143 wf.set('inter_color', inter.get('color')[coupl_key[0]])
1144 wf.set('lorentz', all_lorentz)
1145 wf.set('color_key', color)
1146 wf.set('mothers',mothers)
1147
1148
1149
1150
1151
1152
1153
1154
1155 wf.set_state_and_particle(model)
1156
1157
1158
1159 wf, wfNumber = wf.check_and_fix_fermion_flow(\
1160 wavefunctions,
1161 diag_wfs,
1162 external_wavefunctions,
1163 wfNumber)
1164
1165 new_number_wf_dict = copy.copy(number_wf_dict)
1166
1167
1168 try:
1169 wf = diag_wfs[\
1170 diag_wfs.index(wf)]
1171 except ValueError:
1172
1173 wfNumber = wfNumber + 1
1174 wf.set('number', wfNumber)
1175 try:
1176
1177
1178 wf = wavefunctions[wf_mother_arrays.index(wf.to_array())]
1179
1180
1181 wfNumber = wfNumber - 1
1182 except ValueError:
1183 diag_wfs.append(wf)
1184
1185 new_number_wf_dict[last_leg.get('number')] = wf
1186 if i==(len(vertices)-1):
1187
1188
1189 bridge_wfs.append(wf)
1190
1191 new_number_to_wavefunctions.append(\
1192 new_number_wf_dict)
1193
1194 new_color_list = copy.copy(color_list)
1195 new_color_list.append(coupl_key[0])
1196 new_color_lists.append(new_color_list)
1197
1198
1199 number_to_wavefunctions = new_number_to_wavefunctions
1200 color_lists = new_color_lists
1201
1202
1203
1204
1205
1206 return zip(bridge_wfs, color_lists), wfNumber
1207
1208 def getloopmothers(loopWfsIn, structIDs, color_list, diag_wfs, wfNumber):
1209 """From the incoming loop leg(s) and the list of structures IDs
1210 connected to the loop at this point, it generates the list of
1211 mothers, a list of colorlist and a number_to_wavefunctions
1212 dictionary list for which each element correspond to one
1213 lorentz-color structure of the tree-structure attached to the loop.
1214 It will launch the reconstruction procedure of the structures
1215 which have not been encountered yet."""
1216
1217
1218
1219
1220
1221 mothers_list = [loopWfsIn,]
1222 color_lists = [color_list,]
1223
1224
1225
1226 for sID in structIDs:
1227 try:
1228 struct_infos = structID_to_infos[sID]
1229 except KeyError:
1230
1231
1232 struct_infos, wfNumber = \
1233 process_struct(sID, diag_wfs, wfNumber)
1234
1235
1236
1237
1238
1239 if optimization and False:
1240
1241
1242
1243
1244 structID_to_infos[sID]=copy.copy(struct_infos)
1245
1246
1247 new_mothers_list = []
1248 new_color_lists = []
1249 for mothers, orig_color_list in zip(mothers_list, color_lists):
1250 for struct_wf, struct_color_list in struct_infos:
1251 new_color_list = copy.copy(orig_color_list)+\
1252 copy.copy(struct_color_list)
1253 new_mothers = copy.copy(mothers)
1254 new_mothers.append(struct_wf)
1255 new_color_lists.append(new_color_list)
1256 new_mothers_list.append(new_mothers)
1257 mothers_list = new_mothers_list
1258 color_lists = new_color_lists
1259
1260
1261
1262
1263
1264
1265 return (mothers_list, color_lists), wfNumber
1266
1267 def process_loop_diagram(diagram, wavefunctionNumber, amplitudeNumber):
1268 """ Helper function to process a the loop diagrams which features
1269 several different aspects compared to the tree born diagrams."""
1270
1271
1272 helas_diagram = LoopHelasDiagram()
1273
1274
1275
1276
1277
1278
1279
1280 last_loop_wfs = helas_objects.HelasWavefunctionList()
1281
1282
1283 color_lists = [[]]
1284
1285
1286 diagram_wavefunctions = helas_objects.HelasWavefunctionList()
1287
1288
1289
1290
1291 tag = copy.deepcopy(diagram.get('tag'))
1292 loop_vertices = copy.deepcopy(diagram.get('vertices'))
1293 for i in range(len(tag)):
1294 tag[i][2]=loop_vertices[i]
1295
1296
1297 ct_vertices = copy.copy(diagram.get('CT_vertices'))
1298
1299
1300 external_loop_wf=helas_objects.HelasWavefunction(\
1301 tag[0][0], 0, model, decay_ids)
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311 if not self.optimized_output:
1312 wavefunctionNumber=wavefunctionNumber+1
1313 external_loop_wf.set('number',wavefunctionNumber)
1314 diagram_wavefunctions.append(external_loop_wf)
1315 else:
1316 try:
1317 external_loop_wf=\
1318 external_loop_wfs_dict[external_loop_wf.get('pdg_code')]
1319 except KeyError:
1320 wavefunctionNumber=wavefunctionNumber+1
1321 external_loop_wf.set('number',wavefunctionNumber)
1322 external_loop_wfs_dict[external_loop_wf.get('pdg_code')]=\
1323 external_loop_wf
1324 diagram_wavefunctions.append(external_loop_wf)
1325
1326
1327 last_loop_wfs.append(external_loop_wf)
1328
1329 def process_tag_elem(tagElem, wfNumber, lastloopwfs, colorlists):
1330 """Treat one tag element of the loop diagram (not the last one
1331 which provides an amplitude)"""
1332
1333
1334
1335
1336
1337 new_color_lists = []
1338 new_last_loop_wfs = helas_objects.HelasWavefunctionList()
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349 vertex=tagElem[2]
1350 structIDs=tagElem[1]
1351 for last_loop_wf, color_list in zip(lastloopwfs,
1352 colorlists):
1353 loopLegOut = copy.copy(vertex.get('legs')[-1])
1354
1355
1356
1357
1358
1359
1360 (motherslist, colorlists), wfNumber = \
1361 getloopmothers(\
1362 helas_objects.HelasWavefunctionList([last_loop_wf,]),
1363 structIDs,\
1364 color_list, diagram_wavefunctions, wfNumber)
1365 inter = model.get('interaction_dict')[vertex.get('id')]
1366
1367
1368
1369 for mothers, structcolorlist in zip(motherslist, colorlists):
1370
1371 done_color = {}
1372 for coupl_key in sorted(inter.get('couplings').keys()):
1373 color = coupl_key[0]
1374 if color in done_color:
1375 wf = done_color[color]
1376 wf.get('coupling').append(inter.get('couplings')[coupl_key])
1377 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1378 continue
1379 wf = helas_objects.HelasWavefunction(loopLegOut, \
1380 vertex.get('id'), model)
1381 wf.set('coupling', [inter.get('couplings')[coupl_key]])
1382 if inter.get('color'):
1383 wf.set('inter_color', inter.get('color')[coupl_key[0]])
1384 done_color[color] = wf
1385 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1386 wf.set('color_key', color)
1387 wf.set('mothers',mothers)
1388
1389
1390
1391 wf.set_state_and_particle(model)
1392
1393
1394
1395 wf, wfNumber = wf.check_and_fix_fermion_flow(\
1396 wavefunctions,
1397 diagram_wavefunctions,
1398 external_wavefunctions,
1399 wfNumber)
1400
1401
1402 try:
1403 wf = diagram_wavefunctions[\
1404 diagram_wavefunctions.index(wf)]
1405 except ValueError:
1406
1407 wfNumber = wfNumber + 1
1408 wf.set('number', wfNumber)
1409
1410
1411
1412 try:
1413 if not self.optimized_output:
1414 raise ValueError
1415
1416
1417 wf = wavefunctions[wf_mother_arrays.index(\
1418 wf.to_array())]
1419
1420
1421 wfNumber = wfNumber - 1
1422
1423
1424 self.lwf_reused += 1
1425 except ValueError:
1426 diagram_wavefunctions.append(wf)
1427
1428
1429
1430 new_last_loop_wfs.append(wf)
1431
1432 new_color_list = copy.copy(structcolorlist)
1433 new_color_list.append(coupl_key[0])
1434 new_color_lists.append(new_color_list)
1435
1436
1437
1438
1439 return wfNumber, new_last_loop_wfs, new_color_lists
1440
1441
1442
1443
1444
1445 def create_amplitudes(lastvx, wfNumber, amplitudeNumber):
1446 """Treat the last tag element of the loop diagram (which
1447 provides an amplitude)"""
1448
1449
1450
1451
1452
1453
1454 other_external_loop_wf=helas_objects.HelasWavefunction()
1455
1456 for leg in [leg for leg in lastvx['legs'] if leg['loop_line']]:
1457 if last_loop_wfs[0]['number_external']!=leg['number']:
1458 other_external_loop_wf=\
1459 helas_objects.HelasWavefunction(leg, 0, model, decay_ids)
1460
1461 break
1462
1463
1464 for last_loop_wf, color_list in zip(last_loop_wfs,color_lists):
1465
1466 if lastvx.get('id')!=-1:
1467 raise self.PhysicsObjectError, \
1468 "The amplitude vertex of a loop diagram must be a "+\
1469 "two point vertex with id=-1"
1470
1471
1472 if other_external_loop_wf.is_majorana():
1473 fix_lcut_majorana_fermion_flow(last_loop_wf,\
1474 other_external_loop_wf)
1475
1476 mothers=helas_objects.HelasWavefunctionList(\
1477 [last_loop_wf,other_external_loop_wf])
1478 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
1479 diagram_wavefunctions,
1480 external_wavefunctions,
1481 None,
1482 wfNumber,
1483 False,
1484 [])
1485 amp = helas_objects.HelasAmplitude(lastvx, model)
1486 amp.set('interaction_id',-1)
1487 amp.set('mothers',mothers)
1488
1489
1490 amp.set('pdg_codes',[last_loop_wf.get_pdg_code(),
1491 other_external_loop_wf.get_pdg_code()])
1492
1493
1494
1495
1496
1497 amp.set('color_indices', copy.copy(color_list))
1498
1499
1500 amplitudeNumber = amplitudeNumber + 1
1501 amp.set('number', amplitudeNumber)
1502 amp.set('type','loop')
1503 loop_amp = LoopHelasAmplitude()
1504 loop_amp.set('amplitudes',\
1505 helas_objects.HelasAmplitudeList([amp,]))
1506
1507
1508
1509
1510 loop_amp_wfs=helas_objects.HelasWavefunctionList(\
1511 [last_loop_wf,])
1512 while loop_amp_wfs[-1].get('mothers'):
1513 loop_amp_wfs.append([lwf for lwf in \
1514 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0])
1515
1516
1517
1518
1519
1520 loop_amp_wfs.append(other_external_loop_wf)
1521
1522
1523 loop_amp_wfs.reverse()
1524 loop_amp.set('wavefunctions',loop_amp_wfs)
1525 loop_amp.set('type',diagram.get('type'))
1526 loop_amp.set('multiplier',diagram.get('multiplier'))
1527
1528 loop_amp.set('number',min([amp.get('number') for amp
1529 in loop_amp.get('amplitudes')]))
1530 loop_amp.set('coupling',loop_amp.get_couplings())
1531 loop_amp.set('orders',loop_amp.get_orders())
1532 helas_diagram.get('amplitudes').append(loop_amp)
1533
1534
1535 check_lcut_fermion_flow_consistency(\
1536 loop_amp_wfs[0],loop_amp_wfs[1])
1537 return wfNumber, amplitudeNumber
1538
1539 def check_lcut_fermion_flow_consistency(lcut_wf1, lcut_wf2):
1540 """Checks that the two L-cut loop helas wavefunctions have
1541 a consistent fermion flow."""
1542 if lcut_wf1.is_boson():
1543 if lcut_wf1.get('state')!='final' or\
1544 lcut_wf2.get('state')!='final':
1545 raise MadGraph5Error,\
1546 "Inconsistent flow in L-cut bosons."
1547 elif not lcut_wf1.is_majorana():
1548 for lcut_wf in [lcut_wf1,lcut_wf2]:
1549 if not ((lcut_wf.get('is_part') and \
1550 lcut_wf.get('state')=='outgoing') or\
1551 (not lcut_wf.get('is_part') and\
1552 lcut_wf.get('state')=='incoming')):
1553 raise MadGraph5Error,\
1554 "Inconsistent flow in L-cut Dirac fermions."
1555 elif lcut_wf1.is_majorana():
1556 if (lcut_wf1.get('state'), lcut_wf2.get('state')) not in \
1557 [('incoming','outgoing'),('outgoing','incoming')]:
1558 raise MadGraph5Error,\
1559 "Inconsistent flow in L-cut Majorana fermions."
1560
1561 def fix_lcut_majorana_fermion_flow(last_loop_wf,\
1562 other_external_loop_wf):
1563 """Fix the fermion flow of the last external Majorana loop
1564 wavefunction through the fermion flow of the first external
1565 Majorana loop wavefunction."""
1566
1567
1568 loop_amp_wfs=helas_objects.HelasWavefunctionList(\
1569 [last_loop_wf,])
1570 while loop_amp_wfs[-1].get('mothers'):
1571 loop_amp_wfs.append([lwf for lwf in \
1572 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0])
1573 loop_amp_wfs.append(other_external_loop_wf)
1574 loop_amp_wfs.reverse()
1575
1576
1577 rep={'incoming':'outgoing','outgoing':'incoming'}
1578
1579 other_external_loop_wf['state']=rep[loop_amp_wfs[1]['state']]
1580 return
1581
1582 def process_counterterms(ct_vertices, wfNumber, amplitudeNumber):
1583 """Process the counterterms vertices defined in this loop
1584 diagram."""
1585
1586 structIDs=[]
1587 for tagElem in tag:
1588 structIDs += tagElem[1]
1589
1590
1591
1592
1593 (motherslist, colorlists), wfNumber = getloopmothers(\
1594 helas_objects.HelasWavefunctionList(), structIDs, \
1595 [], diagram_wavefunctions, wfNumber)
1596
1597 for mothers, structcolorlist in zip(motherslist, colorlists):
1598 for ct_vertex in ct_vertices:
1599
1600 inter = model.get_interaction(ct_vertex.get('id'))
1601 keys = sorted(inter.get('couplings').keys())
1602 pdg_codes = [p.get_pdg_code() for p in \
1603 inter.get('particles')]
1604 mothers = mothers.sort_by_pdg_codes(pdg_codes, 0)[0]
1605
1606
1607 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
1608 diagram_wavefunctions,
1609 external_wavefunctions,
1610 None,
1611 wfNumber,
1612 False,
1613 [])
1614 done_color = {}
1615 for i, coupl_key in enumerate(keys):
1616 color = coupl_key[0]
1617 if color in done_color.keys():
1618 amp = done_color[color]
1619 amp.get('coupling').append(inter.get('couplings')[coupl_key])
1620 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1621 continue
1622 amp = helas_objects.HelasAmplitude(ct_vertex, model)
1623 amp.set('coupling', [inter.get('couplings')[coupl_key]])
1624 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1625 if inter.get('color'):
1626 amp.set('inter_color', inter.get('color')[color])
1627 amp.set('color_key', color)
1628 done_color[color] = amp
1629 amp.set('mothers', mothers)
1630 amplitudeNumber = amplitudeNumber + 1
1631 amp.set('number', amplitudeNumber)
1632
1633 amp_color_list = copy.copy(structcolorlist)
1634 amp_color_list.append(color)
1635 amp.set('color_indices', amp_color_list)
1636 amp.set('type',inter.get('type'))
1637
1638
1639 helas_diagram.get('amplitudes').append(amp)
1640 return wfNumber, amplitudeNumber
1641
1642 for tagElem in tag:
1643 wavefunctionNumber, last_loop_wfs, color_lists = \
1644 process_tag_elem(tagElem, wavefunctionNumber, \
1645 last_loop_wfs, color_lists)
1646
1647
1648
1649 wavefunctionNumber, amplitudeNumber = create_amplitudes(
1650 loop_vertices[-1], wavefunctionNumber, amplitudeNumber)
1651
1652
1653 if ct_vertices:
1654 wavefunctionNumber, amplitudeNumber = process_counterterms(\
1655 ct_vertices, wavefunctionNumber, amplitudeNumber)
1656
1657
1658
1659 struct_wfs=helas_objects.HelasWavefunctionList(\
1660 [wf for wf in diagram_wavefunctions if not wf['is_loop']])
1661 loop_wfs=helas_objects.HelasWavefunctionList(\
1662 [wf for wf in diagram_wavefunctions if wf['is_loop']])
1663
1664
1665 struct_wfs.sort(lambda wf1, wf2: \
1666 wf1.get('number') - wf2.get('number'))
1667
1668
1669
1670 helas_diagram.set('wavefunctions', struct_wfs)
1671
1672
1673
1674
1675 if optimization:
1676 wavefunctions.extend(struct_wfs)
1677 wf_mother_arrays.extend([wf.to_array() for wf in struct_wfs])
1678 if self.optimized_output:
1679 wavefunctions.extend(loop_wfs)
1680 wf_mother_arrays.extend([wf.to_array() for wf in loop_wfs])
1681 else:
1682 wavefunctionNumber = len(process.get('legs'))
1683 if self.optimized_output:
1684
1685
1686 wavefunctionNumber = wavefunctionNumber+1
1687
1688
1689
1690
1691
1692
1693 if self.optimized_output:
1694 loop_wfs = helas_objects.HelasWavefunctionList(
1695 [lwf for lwf in loop_wfs if len(lwf.get('mothers'))>0])
1696 helas_diagram.set('loop_wavefunctions',loop_wfs)
1697
1698
1699 return helas_diagram, wavefunctionNumber, amplitudeNumber
1700
1701
1702 if has_born:
1703 for diagram in amplitude.get('born_diagrams'):
1704 helBornDiag, wf_number, amplitude_number=\
1705 process_born_diagram(diagram, wf_number, amplitude_number)
1706 diagram_number = diagram_number + 1
1707 helBornDiag.set('number', diagram_number)
1708 helas_diagrams.append(helBornDiag)
1709
1710
1711 self.lwf_reused=0
1712 for diagram in amplitude.get('loop_diagrams'):
1713 loopHelDiag, wf_number, amplitude_number=\
1714 process_loop_diagram(diagram, wf_number, amplitude_number)
1715 diagram_number = diagram_number + 1
1716 loopHelDiag.set('number', diagram_number)
1717 helas_diagrams.append(loopHelDiag)
1718
1719
1720 for diagram in amplitude.get('loop_UVCT_diagrams'):
1721 loopHelDiag, wf_number, amplitude_number=\
1722 process_born_diagram(diagram, wf_number, amplitude_number, \
1723 UVCTdiag=True)
1724 diagram_number = diagram_number + 1
1725 loopHelDiag.set('number', diagram_number)
1726
1727
1728 for lamp in loopHelDiag.get_loop_UVCTamplitudes():
1729 new_orders = copy.copy(lamp.get('orders'))
1730 for order, value in lamp.get('UVCT_orders').items():
1731 try:
1732 new_orders[order] = new_orders[order] + value
1733 except KeyError:
1734 new_orders[order] = value
1735 lamp.set('orders', new_orders)
1736 helas_diagrams.append(loopHelDiag)
1737
1738 self.set('diagrams', helas_diagrams)
1739
1740 if __debug__:
1741 for diag in self.get('diagrams'):
1742
1743
1744
1745
1746 diag.get('wavefunctions').check_wavefunction_numbers_order()
1747
1748
1749 if self.optimized_output:
1750 logger.debug('%d loop wavefunctions have been reused'%self.lwf_reused+
1751 ', for a total of %d ones'%sum([len(ldiag.get('loop_wavefunctions'))
1752 for ldiag in self.get_loop_diagrams()]))
1753
1754
1755 for wf in self.get_all_wavefunctions():
1756 wf.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(wf))
1757
1758 for amp in self.get_all_amplitudes():
1759 amp.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(amp))
1760
1761
1762
1763 gen_colors = amp.get('color_indices')
1764 amp.set('color_indices', amp.get_color_indices())
1765 if isinstance(amp,LoopHelasAmplitude):
1766 assert (amp.get('color_indices')==gen_colors), \
1767 "Error in the treatment of color in the loop helas diagram "+\
1768 "generation. It could be harmless, but report this bug to be sure."+\
1769 " The different keys are %s vs %s."%(str(gen_colors),\
1770 str(amp.get('color_indices')))
1771 for loopdiag in self.get_loop_diagrams():
1772 for loopamp in loopdiag.get_loop_amplitudes():
1773 loopamp.set_mothers_and_pairing()
1774
1775
1776
1777
1778
1779
1780
1781
1782
1784 """This function returns a list and a dictionary:
1785 squared_orders, amps_orders
1786 ===
1787 The squared_orders lists all contributing squared_orders as tuple whose
1788 elements are the power at which are elevated the couplings orderered as
1789 in the 'split_orders'.
1790
1791 squared_orders : All possible contributing squared orders among those
1792 specified in the process['split_orders'] argument. The elements of
1793 the list are tuples of the format
1794 ((OrderValue1,OrderValue2,...),
1795 (max_contrib_ct_amp_number,
1796 max_contrib_uvct_amp_number,
1797 max_contrib_loop_amp_number,
1798 max_contrib_group_id))
1799 with OrderValue<i> correspond to the value of the <i>th order in
1800 process['split_orders'] (the others are summed over and therefore
1801 left unspecified).
1802 Ex for dijet with process['split_orders']=['QCD','QED']:
1803 => [((4,0),(8,2,3)),((2,2),(10,3,3)),((0,4),(20,5,4))]
1804
1805 'max_contrib_loop_amp_number': For optimization purposes, it is good to
1806 know what is the maximum loop amplitude number contributing to any given
1807 squared order. The fortran output is structured so that if the user
1808 is interested in a given squared order contribution only, then
1809 all the open loop coefficients for the amplitudes with a number above
1810 this value can be skipped.
1811
1812 'max_contrib_(uv)ct_amp_number': Same as above but for the
1813 (uv)ctamplitude number.
1814
1815 'max_contrib_group_id': The same as above, except this time
1816 it is for the loop group id used for the loop reduction.
1817 ===
1818 The amps_orders is a *dictionary* with keys
1819 'born_amp_orders',
1820 'loop_amp_orders'
1821 with values being the tuples described below.
1822
1823 If process['split_orders'] is empty, all these tuples are set empty.
1824
1825 'born_amp_orders' : Exactly as for squared order except that this list specifies
1826 the contributing order values for the amplitude (i.e. not 'squared').
1827 Also, the tuple describing the amplitude order is nested with a
1828 second one listing all amplitude numbers contributing to this order.
1829 Ex for dijet with process['split_orders']=['QCD','QED']:
1830 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))]
1831 The function returns () if the process has no borns.
1832
1833 'loop_amp_orders' : The same as for born_amp_orders but for the loop
1834 type of amplitudes only.
1835
1836 Keep in mind that the orders of the elements of the outter most list is
1837 important as it dictates the order for the corresponding "order indices"
1838 in the fortran code output by the exporters.
1839 """
1840
1841 split_orders=self.get('processes')[0].get('split_orders')
1842
1843 amps_orders = {'born_amp_orders':[],
1844 'loop_amp_orders':[]}
1845 if len(split_orders)==0:
1846 self.squared_orders = []
1847 return [],amps_orders
1848
1849
1850
1851 self.sort_split_orders(split_orders)
1852
1853 process = self.get('processes')[0]
1854
1855
1856 self.sort_split_orders(split_orders)
1857 loop_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1858 self.get_loop_diagrams(), split_orders,
1859 get_amplitudes_function = lambda diag: diag.get_loop_amplitudes(),
1860
1861
1862
1863 get_amp_number_function = lambda amp:
1864 (amp.get('amplitudes')[0].get('number'),amp.get('loop_group_id')))
1865 ct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1866 self.get_loop_diagrams(), split_orders,
1867 get_amplitudes_function = lambda diag: diag.get_ct_amplitudes())
1868 uvct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1869 self.get_loop_UVCT_diagrams(), split_orders)
1870
1871
1872
1873
1874 amps_orders['loop_amp_orders'] = dict([(lao[0],
1875 [el[0] for el in lao[1]]) for lao in loop_amp_orders])
1876
1877 for ct_amp_order in ct_amp_orders+uvct_amp_orders:
1878 try:
1879 amps_orders['loop_amp_orders'][ct_amp_order[0]].extend(\
1880 list(ct_amp_order[1]))
1881 except KeyError:
1882 amps_orders['loop_amp_orders'][ct_amp_order[0]] = \
1883 list(ct_amp_order[1])
1884
1885 amps_orders['loop_amp_orders'] = [
1886 (key, tuple(sorted(amps_orders['loop_amp_orders'][key])))
1887 for key in amps_orders['loop_amp_orders'].keys()]
1888
1889 order_hierarchy = self.get('processes')[0]\
1890 .get('model').get('order_hierarchy')
1891 if set(order_hierarchy.keys()).union(set(split_orders))==\
1892 set(order_hierarchy.keys()):
1893 amps_orders['loop_amp_orders'].sort(key= lambda so:
1894 sum([order_hierarchy[split_orders[i]]*order_power for \
1895 i, order_power in enumerate(so[0])]))
1896
1897
1898 if process.get('has_born'):
1899 born_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1900 self.get_born_diagrams(),split_orders)
1901
1902 amps_orders['born_amp_orders'] = born_amp_orders
1903
1904
1905
1906
1907
1908 loop_orders = [(lso[0],tuple(zip(*list(lso[1])))) for lso in loop_amp_orders]
1909
1910
1911
1912 if process.get('has_born'):
1913 ref_orders = [bao[0] for bao in born_amp_orders]
1914 else:
1915 ref_orders = [lao[0] for lao in loop_orders+ct_amp_orders]
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927 def smax(AmpNumList):
1928 return -1 if len(AmpNumList)==0 else max(AmpNumList)
1929
1930 squared_orders = {}
1931 for ref_order in ref_orders:
1932 for uvct_order in uvct_amp_orders:
1933 key = tuple([ord1 + ord2 for ord1,ord2 in zip(uvct_order[0],
1934 ref_order)])
1935 try:
1936
1937 squared_orders[key][0] = smax([squared_orders[key][0]]+
1938 list(uvct_order[1]))
1939 except KeyError:
1940 squared_orders[key] = [smax(list(uvct_order[1])),-1,-1,-1]
1941
1942 for ct_order in ct_amp_orders:
1943 key = tuple([ord1 + ord2 for ord1,ord2 in zip(ct_order[0],
1944 ref_order)])
1945 try:
1946
1947 squared_orders[key][1] = smax([squared_orders[key][1]]+
1948 list(ct_order[1]))
1949 except KeyError:
1950 squared_orders[key] = [-1,smax(list(ct_order[1])),-1,-1]
1951
1952 for loop_order in loop_orders:
1953 key = tuple([ord1 + ord2 for ord1,ord2 in zip(loop_order[0],
1954 ref_order)])
1955 try:
1956
1957 squared_orders[key][2] = smax([squared_orders[key][2]]+
1958 list(loop_order[1][0]))
1959
1960 squared_orders[key][3] = smax([squared_orders[key][3]]+
1961 list(loop_order[1][1]))
1962 except KeyError:
1963 squared_orders[key] = [-1,-1,smax(list(loop_order[1][0])),
1964 smax(list(loop_order[1][1]))]
1965
1966
1967
1968
1969
1970
1971 squared_orders = [(sqso[0],tuple(sqso[1])) for sqso in \
1972 squared_orders.items()]
1973
1974 order_hierarchy = self.get('processes')[0].get('model').get('order_hierarchy')
1975 if set(order_hierarchy.keys()).union(set(split_orders))==\
1976 set(order_hierarchy.keys()):
1977 squared_orders.sort(key= lambda so:
1978 sum([order_hierarchy[split_orders[i]]*order_power for \
1979 i, order_power in enumerate(so[0])]))
1980
1981
1982 self.squared_orders = squared_orders
1983
1984 return squared_orders, amps_orders
1985
1987 """Return the squared_order contributions as returned by the function
1988 get_split_orders_mapping. It uses the cached value self.squared_orders
1989 if it was already defined during a previous call to get_split_orders_mapping.
1990 """
1991
1992 if not hasattr(self, "squared_orders"):
1993 self.get_split_orders_mapping()
1994
1995 return self.squared_orders
1996
1998 """ Find the maximum number of loop couplings appearing in any of the
1999 LoopHelasAmplitude in this LoopHelasMatrixElement"""
2000 if len(self.get_loop_diagrams())==0:
2001 return 0
2002 return max([len(amp.get('coupling')) for amp in \
2003 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[])])
2004
2006 """ Returns the maximum power of loop momentum brought by a loop
2007 interaction. For renormalizable theories, it should be no more than one.
2008 """
2009 return max([lwf.get_analytic_info('interaction_rank') for lwf in \
2010 self.get_all_loop_wavefunctions()])
2011
2013 """ Returns the rank of the contributing loop with maximum rank """
2014 r_list = [lamp.get_analytic_info('wavefunction_rank') for ldiag in \
2015 self.get_loop_diagrams() for lamp in ldiag.get_loop_amplitudes()]
2016 if len(r_list)==0:
2017 return 0
2018 else:
2019 return max(r_list)
2020
2022 """Returns the maximum spin that any particle either connected to a loop
2023 or running in it has, among all the loops contributing to this ME"""
2024
2025
2026
2027
2028
2029 return max(
2030 max(l.get('spin') for l in lamp.get('mothers')+
2031 lamp.get('wavefunctions')+d.get('loop_wavefunctions'))
2032 for d in self['diagrams'] if isinstance(d,LoopHelasDiagram)
2033 for lamp in d.get_loop_amplitudes()
2034 )
2035
2037 """ Returns the spin of the loop particle with maximum spin among all
2038 the loop contributing to this ME"""
2039 return max([lwf.get('spin') for lwf in \
2040 self.get_all_loop_wavefunctions()])
2041
2043 """Give a unique number to each non-equivalent (at the level of the output)
2044 LoopHelasAmplitude """
2045
2046 LoopHelasAmplitudeRecognized=[]
2047 for lamp in \
2048 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]):
2049 lamp.set('number',-1)
2050 for lamp2 in LoopHelasAmplitudeRecognized:
2051 if lamp.is_equivalent(lamp2):
2052
2053
2054 lamp.set('number',lamp2.get('number'))
2055 break;
2056 if lamp.get('number')==-1:
2057 lamp.set('number',(len(LoopHelasAmplitudeRecognized)+1))
2058 LoopHelasAmplitudeRecognized.append(lamp)
2059
2061 """Give a unique number to each LoopHelasAmplitude. These will be the
2062 number used for the LOOPCOEF array in the optimized output and the
2063 grouping is done in a further stage by adding all the LOOPCOEF sharing
2064 the same denominator to a given one using the 'loop_group_id' attribute
2065 of the LoopHelasAmplitudes. """
2066
2067 lamp_number=1
2068 for lamp in \
2069 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]):
2070 lamp.set('number',lamp_number)
2071 lamp_number += 1
2072
2074 """ Give the correct number for the default output to the wavefunctions
2075 and amplitudes building the loops """
2076
2077
2078 CT_ampnumber=1
2079 loop_ampnumber=self.get_number_of_CT_amplitudes()+1
2080 loopwfnumber=1
2081
2082 for loopdiag in self.get_loop_diagrams():
2083 for wf in loopdiag.get('wavefunctions'):
2084 wf.set('number',wfnumber)
2085 wfnumber=wfnumber+1
2086 for loopamp in loopdiag.get_loop_amplitudes():
2087 loopwfnumber=1
2088 for loopwf in loopamp['wavefunctions']:
2089 loopwf.set('number',loopwfnumber)
2090 loopwfnumber=loopwfnumber+1
2091 for amp in loopamp['amplitudes']:
2092 amp.set('number',loop_ampnumber)
2093 loop_ampnumber=loop_ampnumber+1
2094 for ctamp in loopdiag.get_ct_amplitudes():
2095 ctamp.set('number',CT_ampnumber)
2096 CT_ampnumber=CT_ampnumber+1
2097
2098 for loopUVCTdiag in self.get_loop_UVCT_diagrams():
2099 for wf in loopUVCTdiag.get('wavefunctions'):
2100 wf.set('number',wfnumber)
2101 wfnumber=wfnumber+1
2102 for amp in loopUVCTdiag.get('amplitudes'):
2103 amp.set('number',CT_ampnumber)
2104 CT_ampnumber=CT_ampnumber+1
2105
2107 """ Give the correct number for the optimized output to the wavefunctions
2108 and amplitudes building the loops """
2109 CT_ampnumber=1
2110 loop_ampnumber=self.get_number_of_CT_amplitudes()+1
2111 loopwfnumber=1
2112
2113 for loopdiag in self.get_loop_diagrams():
2114 for wf in loopdiag.get('wavefunctions'):
2115 wf.set('number',wfnumber)
2116 wfnumber=wfnumber+1
2117 for lwf in loopdiag.get('loop_wavefunctions'):
2118 lwf.set('number',loopwfnumber)
2119 loopwfnumber=loopwfnumber+1
2120 for loopamp in loopdiag.get_loop_amplitudes():
2121
2122
2123 loopamp.get_starting_loop_wavefunction().set('number',0)
2124 for amp in loopamp['amplitudes']:
2125 amp.set('number',loop_ampnumber)
2126 loop_ampnumber=loop_ampnumber+1
2127 for ctamp in loopdiag.get_ct_amplitudes():
2128 ctamp.set('number',CT_ampnumber)
2129 CT_ampnumber=CT_ampnumber+1
2130
2131 for loopUVCTdiag in self.get_loop_UVCT_diagrams():
2132 for wf in loopUVCTdiag.get('wavefunctions'):
2133 wf.set('number',wfnumber)
2134 wfnumber=wfnumber+1
2135 for amp in loopUVCTdiag.get('amplitudes'):
2136 amp.set('number',CT_ampnumber)
2137 CT_ampnumber=CT_ampnumber+1
2138
2140 """After the generation of the helas objects, we can give up on having
2141 a unique number identifying the helas wavefunction and amplitudes and
2142 instead use a labeling which is optimal for the output of the loop process.
2143 Also we tag all the LoopHelasAmplitude which are identical with the same
2144 'number' attribute."""
2145
2146
2147 if self.optimized_output:
2148 self.relabel_loop_amplitudes_optimized()
2149 else:
2150 self.relabel_loop_amplitudes()
2151
2152
2153 wfnumber=1
2154 ampnumber=1
2155 for borndiag in self.get_born_diagrams():
2156 for wf in borndiag.get('wavefunctions'):
2157 wf.set('number',wfnumber)
2158 wfnumber=wfnumber+1
2159 for amp in borndiag.get('amplitudes'):
2160 amp.set('number',ampnumber)
2161 ampnumber=ampnumber+1
2162
2163
2164
2165 if self.optimized_output:
2166 self.relabel_loop_wfs_and_amps_optimized(wfnumber)
2167 for lwf in [lwf for loopdiag in self.get_loop_diagrams() for \
2168 lwf in loopdiag.get('loop_wavefunctions')]:
2169 lwf.set('me_id',lwf.get('number'))
2170 else:
2171 self.relabel_loop_wfs_and_amps(wfnumber)
2172
2173
2174
2175 for wf in self.get_all_wavefunctions():
2176 wf.set('me_id',wf.get('number'))
2177
2178
2180 """Gives the total number of wavefunctions for this ME, including the
2181 loop ones"""
2182
2183 return len(self.get_all_wavefunctions())
2184
2186 """ Gives the total number of loop wavefunctions for this ME."""
2187 return sum([len(ldiag.get('loop_wavefunctions')) for ldiag in \
2188 self.get_loop_diagrams()])
2189
2191 """Gives the total number of wavefunctions for this ME, excluding the
2192 loop ones."""
2193
2194 return sum([ len(d.get('wavefunctions')) for d in self.get('diagrams')])
2195
2197 """Gives a list of all wavefunctions for this ME"""
2198
2199 allwfs=sum([d.get('wavefunctions') for d in self.get('diagrams')], [])
2200 for d in self['diagrams']:
2201 if isinstance(d,LoopHelasDiagram):
2202 for l in d.get_loop_amplitudes():
2203 allwfs += l.get('wavefunctions')
2204
2205 return allwfs
2206
2220
2222 """Gives (number or external particles, number of
2223 incoming particles)"""
2224
2225 external_wfs = filter(lambda wf:
2226 not wf.get('mothers') and not wf.get('is_loop'),
2227 self.get_all_wavefunctions())
2228
2229 return (len(set([wf.get('number_external') for wf in \
2230 external_wfs])),
2231 len(set([wf.get('number_external') for wf in \
2232 filter(lambda wf: wf.get('leg_state') == False,
2233 external_wfs)])))
2234
2236 """Gives the total number of amplitudes for this ME, including the loop
2237 ones."""
2238
2239 return len(self.get_all_amplitudes())
2240
2247
2249 """Gives the total number of amplitudes for this ME, excluding those
2250 inside the loop amplitudes. (So only one is counted per loop amplitude.)
2251 """
2252
2253 return sum([ len(d.get('amplitudes')) for d in \
2254 self.get('diagrams')])
2255
2257 """Gives the total number of helas amplitudes for the loop diagrams of this ME,
2258 excluding those inside the loop amplitudes, but including the CT-terms.
2259 (So only one amplitude is counted per loop amplitude.)
2260 """
2261
2262 return sum([len(d.get('amplitudes')) for d in (self.get_loop_diagrams()+
2263 self.get_loop_UVCT_diagrams())])
2264
2266 """Gives the total number of amplitudes for the born diagrams of this ME
2267 """
2268
2269 return sum([len(d.get('amplitudes')) for d in self.get_born_diagrams()])
2270
2281
2287
2294
2301
2329
2345
2347 """ Returns the list of the helas loop amplitude of type
2348 CALL LOOP_I_J(_K)(...) used for this matrix element """
2349
2350
2351
2352 if self.optimized_output:
2353 last_relevant_index=3
2354 else:
2355 last_relevant_index=4
2356
2357 return list(set([lamp.get_call_key()[1:last_relevant_index] \
2358 for ldiag in self.get_loop_diagrams() for lamp in \
2359 ldiag.get_loop_amplitudes()]))
2360
2370
2380
2382 """ Just to forbid the usage of this generic function in a
2383 LoopHelasMatrixElement"""
2384
2385 raise self.PhysicsObjectError, \
2386 "Usage of get_color_amplitudes is not allowed in a LoopHelasMatrixElement"
2387
2389 """Return a list of (coefficient, amplitude number) lists,
2390 corresponding to the JAMPs for this born color basis and the born
2391 diagrams of this LoopMatrixElement. The coefficients are given in the
2392 format (fermion factor, color coeff (frac), imaginary, Nc power)."""
2393
2394 return super(LoopHelasMatrixElement,self).generate_color_amplitudes(\
2395 self['born_color_basis'],self.get_born_diagrams())
2396
2398 """Return a list of (coefficient, amplitude number) lists,
2399 corresponding to the JAMPs for this loop color basis and the loop
2400 diagrams of this LoopMatrixElement. The coefficients are given in the
2401 format (fermion factor, color coeff (frac), imaginary, Nc power)."""
2402
2403 diagrams=self.get_loop_diagrams()
2404 color_basis=self['loop_color_basis']
2405
2406 if not color_basis:
2407
2408
2409 col_amp = []
2410 for diagram in diagrams:
2411 for amplitude in diagram.get('amplitudes'):
2412 col_amp.append(((amplitude.get('fermionfactor'),
2413 1, False, 0),
2414 amplitude.get('number')))
2415 return [col_amp]
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426 LoopDiagramsHelasAmplitudeList=self.get_helas_amplitudes_loop_diagrams()
2427
2428
2429 for i, helas_amp_list in enumerate(LoopDiagramsHelasAmplitudeList):
2430 new_helas_amp_list=helas_objects.HelasAmplitudeList()
2431 for helas_amp in helas_amp_list:
2432 if isinstance(helas_amp,LoopHelasAmplitude):
2433 new_helas_amp_list.extend(helas_amp['amplitudes'])
2434 else:
2435 new_helas_amp_list.append(helas_amp)
2436 LoopDiagramsHelasAmplitudeList[i]=new_helas_amp_list
2437
2438
2439
2440
2441
2442 col_amp_list = []
2443 for i, col_basis_elem in \
2444 enumerate(sorted(color_basis.keys())):
2445
2446 col_amp = []
2447
2448 for diag_tuple in color_basis[col_basis_elem]:
2449 res_amps = filter(lambda amp: \
2450 tuple(amp.get('color_indices')) == diag_tuple[1],
2451 LoopDiagramsHelasAmplitudeList[diag_tuple[0]])
2452 if not res_amps:
2453 raise self.PhysicsObjectError, \
2454 """No amplitude found for color structure
2455 %s and color index chain (%s) (diagram %i)""" % \
2456 (col_basis_elem,
2457 str(diag_tuple[1]),
2458 diag_tuple[0])
2459
2460 for res_amp in res_amps:
2461 col_amp.append(((res_amp.get('fermionfactor'),
2462 diag_tuple[2],
2463 diag_tuple[3],
2464 diag_tuple[4]),
2465 res_amp.get('number')))
2466
2467 col_amp_list.append(col_amp)
2468
2469 return col_amp_list
2470
2472 """ When creating the base_objects.Diagram in get_base_amplitudes(),
2473 each LoopHelasDiagram will lead to one loop_base_objects.LoopDiagram
2474 for its LoopHelasAmplitude and one other for each of its counter-term
2475 (with different interaction id). This function return a list for which
2476 each element is a HelasAmplitudeList corresponding to the HelasAmplitudes
2477 related to a given loop_base_objects.LoopDiagram generated """
2478
2479 amplitudes_loop_diagrams=[]
2480
2481 for diag in self.get_loop_diagrams():
2482
2483 amplitudes_loop_diagrams.append(diag.get_loop_amplitudes())
2484
2485
2486
2487
2488
2489
2490
2491
2492 ctIDs={}
2493 for ctamp in diag.get_ct_amplitudes():
2494 try:
2495 ctIDs[ctamp.get('interaction_id')].append(ctamp)
2496 except KeyError:
2497 ctIDs[ctamp.get('interaction_id')]=\
2498 helas_objects.HelasAmplitudeList([ctamp])
2499
2500
2501 keys=ctIDs.keys()
2502 keys.sort()
2503 for key in keys:
2504 amplitudes_loop_diagrams.append(ctIDs[key])
2505
2506 for diag in self.get_loop_UVCT_diagrams():
2507 amplitudes_loop_diagrams.append(diag.get_loop_UVCTamplitudes())
2508
2509 return amplitudes_loop_diagrams
2510
2512 """Generate a loop_diagram_generation.LoopAmplitude from a
2513 LoopHelasMatrixElement. This is used to generate both color
2514 amplitudes and diagram drawing."""
2515
2516
2517
2518
2519 optimization = 1
2520 if len(filter(lambda wf: wf.get('number') == 1,
2521 self.get_all_wavefunctions())) > 1:
2522 optimization = 0
2523
2524 model = self.get('processes')[0].get('model')
2525
2526 wf_dict = {}
2527 vx_list = []
2528 diagrams = base_objects.DiagramList()
2529
2530
2531 for diag in self.get_born_diagrams():
2532 newdiag=diag.get('amplitudes')[0].get_base_diagram(\
2533 wf_dict, vx_list, optimization)
2534 diagrams.append(loop_base_objects.LoopDiagram({
2535 'vertices':newdiag['vertices'],'type':0}))
2536
2537
2538
2539
2540 dtype=1
2541 for HelasAmpList in self.get_helas_amplitudes_loop_diagrams():
2542
2543
2544 if isinstance(HelasAmpList[0],LoopHelasAmplitude):
2545 diagrams.append(HelasAmpList[0].get_base_diagram(\
2546 wf_dict, vx_list, optimization))
2547 dtype=diagrams[-1]['type']
2548 elif isinstance(HelasAmpList[0],LoopHelasUVCTAmplitude):
2549 diagrams.append(HelasAmpList[0].\
2550 get_base_diagram(wf_dict, vx_list, optimization))
2551 else:
2552 newdiag=HelasAmpList[0].get_base_diagram(wf_dict, vx_list, optimization)
2553 diagrams.append(loop_base_objects.LoopDiagram({
2554 'vertices':newdiag['vertices'],'type':-dtype}))
2555
2556
2557 for diag in diagrams:
2558 diag.calculate_orders(self.get('processes')[0].get('model'))
2559
2560 return loop_diagram_generation.LoopAmplitude({\
2561 'process': self.get('processes')[0],
2562 'diagrams': diagrams})
2563
2568 """LoopHelasProcess: Analogous of HelasMultiProcess except that it is suited
2569 for LoopAmplitude and with the peculiarity that it is always treating only
2570 one loop amplitude. So this LoopHelasProcess correspond to only one single
2571 subprocess without multiparticle labels (contrary to HelasMultiProcess)."""
2572
2573
2574 matrix_element_class = LoopHelasMatrixElement
2575
2576 - def __init__(self, argument=None, combine_matrix_elements=True,
2577 optimized_output = True, compute_loop_nc = False, matrix_element_opts={}):
2578 """ Allow for the initialization of the HelasMultiProcess with the
2579 right argument 'optimized_output' for the helas_matrix_element options.
2580 """
2581
2582 matrix_element_opts = dict(matrix_element_opts)
2583 matrix_element_opts.update({'optimized_output' : optimized_output})
2584
2585 super(LoopHelasProcess, self).__init__(argument, combine_matrix_elements,
2586 compute_loop_nc = compute_loop_nc,
2587 matrix_element_opts = matrix_element_opts)
2588
2589 @classmethod
2590 - def process_color(cls,matrix_element,color_information,compute_loop_nc=False):
2591 """ Process the color information for a given matrix
2592 element made of a loop diagrams. It will create a different
2593 color matrix depending on wether the process has a born or not.
2594 The compute_loop_nc sets wheter independent tracking of Nc power coming
2595 from the color loop trace is necessary or not (it is time consuming).
2596 """
2597 if matrix_element.get('processes')[0]['has_born']:
2598 logger.debug('Computing the loop and Born color basis')
2599 else:
2600 logger.debug('Computing the loop color basis')
2601
2602
2603 for key in color_information:
2604 exec("%s=color_information['%s']"%(key,key))
2605
2606
2607
2608
2609 matrix_element.relabel_helas_objects()
2610
2611
2612
2613
2614 new_amp = matrix_element.get_base_amplitude()
2615 matrix_element.set('base_amplitude', new_amp)
2616
2617 loop_col_basis = loop_color_amp.LoopColorBasis(
2618 compute_loop_nc = compute_loop_nc)
2619 loop_colorize_obj = loop_col_basis.create_loop_color_dict_list(\
2620 matrix_element.get('base_amplitude'),
2621 )
2622 try:
2623
2624
2625
2626 loop_col_basis_index = list_colorize.index(loop_colorize_obj)
2627 loop_col_basis = list_color_basis[loop_col_basis_index]
2628 except ValueError:
2629
2630 list_colorize.append(loop_colorize_obj)
2631 loop_col_basis.build()
2632 loop_col_basis_index = len(list_color_basis)
2633 list_color_basis.append(loop_col_basis)
2634 logger.info(\
2635 "Processing color information for %s" % \
2636 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2637 replace('Process', 'loop process'))
2638 else:
2639 logger.info(\
2640 "Reusing existing color information for %s" % \
2641 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2642 replace('Process', 'loop process'))
2643
2644 if new_amp['process']['has_born']:
2645 born_col_basis = loop_color_amp.LoopColorBasis()
2646 born_colorize_obj = born_col_basis.create_born_color_dict_list(\
2647 matrix_element.get('base_amplitude'))
2648 try:
2649
2650
2651
2652 born_col_basis_index = list_colorize.index(born_colorize_obj)
2653 born_col_basis = list_color_basis[born_col_basis_index]
2654 except ValueError:
2655
2656 list_colorize.append(born_colorize_obj)
2657 born_col_basis.build()
2658 born_col_basis_index = len(list_color_basis)
2659 list_color_basis.append(born_col_basis)
2660 logger.info(\
2661 "Processing color information for %s" % \
2662 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2663 replace('Process', 'born process'))
2664 else:
2665 logger.info(\
2666 "Reusing existing color information for %s" % \
2667 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2668 replace('Process', 'born process'))
2669 loopborn_matrices_key=(loop_col_basis_index,born_col_basis_index)
2670 else:
2671 loopborn_matrices_key=(loop_col_basis_index,loop_col_basis_index)
2672
2673
2674
2675 try:
2676
2677
2678
2679 col_matrix = dict_loopborn_matrices[loopborn_matrices_key]
2680 except KeyError:
2681
2682 col_matrix = color_amp.ColorMatrix(\
2683 list_color_basis[loopborn_matrices_key[0]],
2684 list_color_basis[loopborn_matrices_key[1]])
2685 dict_loopborn_matrices[loopborn_matrices_key]=col_matrix
2686 logger.info(\
2687 "Creating color matrix %s" % \
2688 matrix_element.get('processes')[0].nice_string().\
2689 replace('Process', 'loop process'))
2690 else:
2691 logger.info(\
2692 "Reusing existing color matrix for %s" % \
2693 matrix_element.get('processes')[0].nice_string().\
2694 replace('Process', 'loop process'))
2695
2696 matrix_element.set('loop_color_basis',loop_col_basis)
2697 if new_amp['process']['has_born']:
2698 matrix_element.set('born_color_basis',born_col_basis)
2699 matrix_element.set('color_matrix',col_matrix)
2700