1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Definitions of objects inheriting from the classes defined in
16 helas_objects.py and which have special attributes and function
17 devoted to the treatment of Loop processes"""
18
19 from __future__ import absolute_import
20 import array
21 import copy
22 import logging
23 import itertools
24 import math
25 import os
26
27 import aloha
28 import aloha.create_aloha as create_aloha
29
30 from madgraph import MadGraph5Error
31 import madgraph.core.base_objects as base_objects
32 import madgraph.loop.loop_base_objects as loop_base_objects
33 import madgraph.core.diagram_generation as diagram_generation
34 import madgraph.loop.loop_diagram_generation as loop_diagram_generation
35 import madgraph.core.color_amp as color_amp
36 import madgraph.loop.loop_color_amp as loop_color_amp
37 import madgraph.core.color_algebra as color
38 import madgraph.core.helas_objects as helas_objects
39 import madgraph.various.misc as misc
40 from six.moves import range
41 from six.moves import zip
42
43
44
45
46
47 logger = logging.getLogger('madgraph.helas_objects')
53 """LoopHelasUVCTAmplitude object, behaving exactly as an amplitude except that
54 it also contains additional vertices with coupling constants corresponding
55 to the 'UVCTVertices' defined in the 'UVCTVertices ' of the
56 loop_base_objects.LoopUVCTDiagram of the LoopAmplitude. These are stored
57 in the additional attribute 'UVCT_interaction_ids' of this class.
58 """
59
60
69
78
79 - def filter(self, name, value):
80 """Filter for valid LoopHelasAmplitude property values."""
81
82 if name=='UVCT_couplings':
83 if not isinstance(value, list):
84 raise self.PhysicsObjectError("%s is not a valid list for UVCT_couplings" % str(value))
85 for id in value:
86 if not isinstance(id, str) and not isinstance(id, int):
87 raise self.PhysicsObjectError("%s is not a valid string or integer for UVCT_couplings" % str(value))
88
89 if name == 'UVCT_orders':
90 if not isinstance(value, dict):
91 raise self.PhysicsObjectError("%s is not a valid dictionary" % str(value))
92
93 if name == 'type':
94 if not isinstance(value, str):
95 raise self.PhysicsObjectError("%s is not a valid string" % str(value))
96
97 else:
98 return super(LoopHelasUVCTAmplitude,self).filter(name, value)
99
101 """Return LoopHelasAmplitude property names as a nicely sorted list."""
102
103 return super(LoopHelasUVCTAmplitude,self).get_sorted_keys()+\
104 ['UVCT_couplings','UVCT_orders','type']
105
106 return True
107
109 """ Exactly as a regular HelasAmplitude except that here we must add
110 an entry to mutliply the final result by the coupling constants of the
111 interaction in UVCT_couplings if there are any"""
112 original_call_key = super(LoopHelasUVCTAmplitude,self).get_call_key()
113
114 if self.get_UVCT_couplings()=='1.0d0':
115 return original_call_key
116 else:
117 return (original_call_key[0],original_call_key[1],'UVCT')
118
120 """ Returns a list of the string UVCT_couplings defined for this
121 amplitudes. """
122 return [coupl for coupl in self['UVCT_couplings'] if \
123 isinstance(coupl,str)]
124
126 """ Returns the string corresponding to the overall UVCT coupling which
127 factorize this amplitude """
128 if self['UVCT_couplings']==[]:
129 return '1.0d0'
130
131 answer=[]
132 integer_sum=0
133 for coupl in list(set(self['UVCT_couplings'])):
134 if isinstance(coupl,int):
135 integer_sum+=coupl
136 else:
137 answer.append(str(len([1 for c in self['UVCT_couplings'] if \
138 c==coupl]))+'.0d0*'+coupl)
139 if integer_sum!=0:
140 answer.append(str(integer_sum)+'.0d0')
141 if answer==[] and (integer_sum==0 or integer_sum==1):
142 return '1.0d0'
143 else:
144 return '+'.join(answer)
145
147 """Return the loop_base_objects.LoopUVCTDiagram which corresponds to this
148 amplitude, using a recursive method for the wavefunctions."""
149
150 vertices = super(LoopHelasUVCTAmplitude,self).get_base_diagram(\
151 wf_dict, vx_list, optimization)['vertices']
152
153 return loop_base_objects.LoopUVCTDiagram({'vertices': vertices, \
154 'UVCT_couplings': self['UVCT_couplings'], \
155 'UVCT_orders': self['UVCT_orders'], \
156 'type': self['type']})
157
169
174 """LoopHelasAmplitude object, behaving exactly as an amplitude except that
175 it also contains loop wave-functions closed on themselves, building an
176 amplitude corresponding to the closed loop.
177 """
178
179
188
190 """Comparison between different LoopHelasAmplitude in order to recognize
191 which ones are equivalent at the level of the file output.
192 I decided not to overload the operator __eq__ to be sure not to interfere
193 with other functionalities of the code."""
194
195 if(len(self.get('wavefunctions'))!=len(other.get('wavefunctions')) or
196 len(self.get('amplitudes'))!=len(other.get('amplitudes')) or
197 [len(wf.get('coupling')) for wf in self.get('wavefunctions')]!=
198 [len(wf.get('coupling')) for wf in other.get('wavefunctions')] or
199 [len(amp.get('coupling')) for amp in self.get('amplitudes')]!=
200 [len(amp.get('coupling')) for amp in other.get('amplitudes')]):
201 return False
202
203 wfArgsToCheck = ['fermionflow','lorentz','state','onshell','spin',\
204 'is_part','self_antipart','color']
205 for arg in wfArgsToCheck:
206 if [wf.get(arg) for wf in self.get('wavefunctions')]!=\
207 [wf.get(arg) for wf in other.get('wavefunctions')]:
208 return False
209
210 if [wf.find_outgoing_number() for wf in self.get('wavefunctions')]!=\
211 [wf.find_outgoing_number() for wf in other.get('wavefunctions')]:
212 return False
213
214 ampArgsToCheck = ['lorentz',]
215 for arg in ampArgsToCheck:
216 if [amp.get(arg) for amp in self.get('amplitudes')]!=\
217 [amp.get(arg) for amp in other.get('amplitudes')]:
218 return False
219
220
221
222
223
224
225 if [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in self.get('wavefunctions')]!=\
226 [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in other.get('wavefunctions')]:
227 return False
228 if [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in self.get('amplitudes')]!=\
229 [[m.get('is_loop') for m in lwf.get('mothers')] for lwf in other.get('amplitudes')]:
230 return False
231
232 return True
233
265
266
267 - def get(self, name):
274
275 - def filter(self, name, value):
297
299 """Return LoopHelasAmplitude property names as a nicely sorted list."""
300
301 return super(LoopHelasAmplitude,self).get_sorted_keys()+\
302 ['wavefunctions', 'amplitudes','loop_group_id']
303
310
312 """ Return the starting external loop mother of this loop helas amplitude.
313 It is the loop wavefunction of the l-cut leg one."""
314
315 loop_wf=self.get_final_loop_wavefunction()
316 loop_wf_mother=loop_wf.get_loop_mother()
317 while loop_wf_mother:
318 loop_wf=loop_wf_mother
319 loop_wf_mother=loop_wf.get_loop_mother()
320 return loop_wf
321
323 """Return the non-external loop mother of the helas amplitude building
324 this loop amplitude"""
325
326 final_lwf=[lwf for lwf in self.get('amplitudes')[0].get('mothers') if \
327 lwf.get('mothers')]
328 if len(final_lwf)!=1:
329 raise MadGraph5Error('The helas amplitude building the helas loop'+\
330 ' amplitude should be made of exactly one loop wavefunctions'+\
331 ' with mothers.')
332 return final_lwf[0]
333
335 """Return the loop_base_objects.LoopDiagram which corresponds to this
336 amplitude, using a recursive method for the wavefunctions.
337 Remember that this diagram is not tagged and structures are not
338 recognized."""
339
340 vertices = self['amplitudes'][0].get_base_diagram(\
341 wf_dict, vx_list, optimization)['vertices']
342
343 out = loop_base_objects.LoopDiagram({'vertices': vertices,\
344 'type':self['type']})
345
346
347
348
349
350
351
352
353 starting_loop_line = out.get_starting_loop_line()
354 finishing_loop_line = out.get_finishing_loop_line()
355 if starting_loop_line['number'] == finishing_loop_line['number']:
356
357
358
359
360 nb_external = len(out.get_external_legs()) +1
361 if nb_external == starting_loop_line['number']:
362 starting_loop_line.set('number', nb_external -1)
363 else:
364 starting_loop_line.set('number', nb_external)
365
366
367 return out
368
370 """ Sets the mothers of this amplitude in the same order as they will
371 be used in the arguments of the helas calls building this loop"""
372
373 if len(self.get('amplitudes'))!=1:
374 self.PhysicsObjectError, \
375 "HelasLoopAmplitude is for now designed to contain only one \
376 HelasAmplitude"
377
378 self.set('mothers',helas_objects.HelasWavefunctionList())
379 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]:
380 mothersList=[wf for wf in lwf.get('mothers') if not wf['is_loop']]
381 self['mothers'].extend(mothersList)
382 self['pairing'].append(len(mothersList))
383
387 """Get a list of the number of legs in vertices in this diagram"""
388
389 if max_n_loop == 0:
390 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling
391
392
393
394
395 vertex_leg_numbers = [len(self.get('mothers'))] if \
396 (self.get('interaction_id') not in veto_inter_id) or \
397 len(self.get('mothers'))>max_n_loop else []
398 for mother in self.get('mothers'):
399 vertex_leg_numbers.extend(mother.get_vertex_leg_numbers(
400 veto_inter_id=veto_inter_id, max_n_loop=max_n_loop))
401
402 return vertex_leg_numbers
403
405 """ Returns the denominator structure as a tuple (tupleA, tupleB) whose
406 elements are of this form ((external_part_ids),mass) where
407 external_part_ids are all the leg id building the momentum flowing in
408 the loop, i.e:
409 D_i=(q+Sum(p_j,j))^2 - m^2
410 """
411
412 denoms=[]
413 last_loop_wf=self.get_final_loop_wavefunction()
414 last_loop_wf_mother=last_loop_wf.get_loop_mother()
415 while last_loop_wf_mother:
416 denoms.append((tuple(last_loop_wf.get_struct_external_leg_ids()),
417 last_loop_wf.get('mass')))
418 last_loop_wf=last_loop_wf_mother
419 last_loop_wf_mother=last_loop_wf.get_loop_mother()
420 denoms.reverse()
421
422 return tuple(denoms)
423
425 """ Returns the list of the masses of the loop particles as they should
426 appear for cuttools (L-cut particles specified last) """
427
428 masses=[]
429 if not aloha.complex_mass:
430 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]:
431 masses.append(lwf.get('mass'))
432 else:
433 for lwf in [wf for wf in self.get('wavefunctions') if wf.get('mothers')]:
434 if (lwf.get('width') == 'ZERO' or lwf.get('mass') == 'ZERO'):
435 masses.append(lwf.get('mass'))
436 else:
437 masses.append('CMASS_%s' % lwf.get('mass'))
438 return masses
439
441 """ Returns the list of the couplings of the different helas objects
442 building this HelasLoopAmplitude. They are ordered as they will appear
443 in the helas calls."""
444
445 return (sum([wf.get('coupling') for wf in self.get('wavefunctions') \
446 if wf.get('coupling')!=['none']],[])\
447 +sum([amp.get('coupling') for amp in self.get('amplitudes') if \
448 amp.get('coupling')!=['none']],[]))
449
451 """ return a dictionary to be used for formatting
452 HELAS call. """
453 output = {}
454 output['numLoopLines']='_%d'%(len(self.get('wavefunctions'))-2)
455
456 output['loop_group_id']=self.get('loop_group_id')+1
457 output['ampNumber']=self.get('amplitudes')[0].get('number')
458 if len(self.get('mothers'))!=len(self.get('pairing')):
459 output['numMotherWfs']='_%d'%len(self.get('mothers'))
460 else:
461 output['numMotherWfs']=''
462 for i, pairing in enumerate(self.get('pairing')):
463 output["Pairing%d"%i]=pairing
464 output['numCouplings']='_%d'%len(self.get('coupling'))
465 output['numeratorNumber']=self.get('number')
466 output["LoopRank"]=self.get_analytic_info('wavefunction_rank')
467 if OptimizedOutput:
468 if self.get('loop_group_id')==-1:
469 output['loopNumber']=self.get('number')
470 else:
471 output['loopNumber']=self.get('loop_group_id')+1
472 else:
473 output['loopNumber']=self.get('amplitudes')[0].get('number')
474 for i , wf in enumerate(self.get('mothers')):
475 output["MotherID%d"%(i+1)]=wf.get('number')
476 for i , mass in enumerate(self.get_masses()):
477 output["LoopMass%d"%(i+1)]=mass
478 for i , coupling in enumerate(self.get('coupling')):
479 output["LoopCoupling%d"%(i+1)]=coupling
480 output["LoopSymmetryFactor"] = self.get('loopsymmetryfactor')
481 output["LoopMultiplier"] = self.get('multiplier')
482 output.update(opt)
483
484 return output
485
487 """ The helas call to a loop is simple and only depends on the number
488 of loop lines and mothers. This how it is reflected in the call key. """
489
490 return ("LOOP",len(self.get('wavefunctions'))-2,\
491 len(self.get('mothers')),len(self.get('coupling')))
492
494 """ Compute the orders building this loop amplitude only (not from the
495 struct wavefunctions. Uses the cached result if available."""
496
497 if self.get('orders') != {}:
498 return self.get('orders')
499 else:
500 coupling_orders = {}
501 last_wf = self.get_final_loop_wavefunction()
502 while last_wf.get_loop_mother()!=None:
503 for order in last_wf.get('orders').keys():
504 try:
505 coupling_orders[order] += last_wf.get('orders')[order]
506 except Exception:
507 coupling_orders[order] = last_wf.get('orders')[order]
508 last_wf = last_wf.get_loop_mother()
509 return coupling_orders
510
512 """ Returns an analytic information of the loop numerator, for example
513 the 'wavefunction_rank' i.e. the maximum power to which the loop momentum
514 is elevated in the loop numerator. All analytic pieces of information
515 are for now identical to the one retrieved from the final_loop_wavefunction."""
516
517 return self.get_final_loop_wavefunction().\
518 get_analytic_info(info, alohaModel)
519
529
531 """ The fermion factor is not implemented for this object but in the
532 subamplitude"""
533 self['fermion_factor']=0
534 for amp in self.get('amplitudes'):
535 amp.get('fermionfactor')
536
538 """ Calculate the loop symmetry factor. For one-loop matrix elements,
539 it is always 2 for bubble with identical particles and tadpoles with self-conjugated particles
540 and 1 otherwise."""
541
542
543
544
545 self['loopsymmetryfactor']=1
546
547 physical_wfs = [wf for wf in self.get('wavefunctions') if wf.get('interaction_id')!=0]
548 if len(physical_wfs)==1:
549 if physical_wfs[0].get('self_antipart'):
550 self['loopsymmetryfactor']=2
551 elif len(physical_wfs)==2:
552 if physical_wfs[0].get('particle')==physical_wfs[1].get('antiparticle'):
553 self['loopsymmetryfactor']=2
554
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
587
593
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
619
620 - def filter(self, name, value):
638
639 - def get(self,name):
640 """Overload in order to return the loop_color_basis when simply asked
641 for color_basis. The setter is not updated to avoid side effects."""
642
643 if name=='color_basis':
644 return self['loop_color_basis']
645 elif name=='loop_groups':
646 if not self['loop_groups']:
647 self.identify_loop_groups()
648 return self['loop_groups']
649 else:
650 return super(LoopHelasMatrixElement,self).get(name)
651
653 """ Identify what are the loops sharing the same denominators and put
654 them together in the 'loop_groups' attribute of this object. """
655
656 identified_denom_structures=[]
657 for lamp in [lamp for ldiag in self.get_loop_diagrams() for lamp in \
658 ldiag.get_loop_amplitudes()]:
659 denom_structure=lamp.get_denominators()
660 try:
661 denom_index=identified_denom_structures.index(denom_structure)
662 self['loop_groups'][denom_index][1].append(lamp)
663 except ValueError:
664 denom_index=len(self['loop_groups'])
665 self['loop_groups'].append((denom_structure,
666 helas_objects.HelasAmplitudeList([lamp,])))
667 identified_denom_structures.append(denom_structure)
668 lamp.set('loop_group_id',denom_index)
669
670
671
672 self['loop_groups']=[(group[0],helas_objects.HelasAmplitudeList(
673 sorted(group[1],key=lambda lamp: \
674 lamp.get_analytic_info('wavefunction_rank'),reverse=True)))
675 for group in self['loop_groups']]
676
677
678 self['loop_groups']=sorted(self['loop_groups'],key=lambda group: \
679 group[1][0].get('number'))
680 self.update_loop_group_ids()
681
683 """ Make sure never to use this optimization in the loop context."""
684
685 for diag in helas_diagrams:
686 for wf in diag['wavefunctions']:
687 wf.set('me_id',wf.get('number'))
688
689 return helas_diagrams
690
692 """ Make sure that the attribute 'loop_group_id' of all loop amplitudes
693 in the 'loop_groups' list is correct given the order of 'loop_groups'"""
694
695 for i, group in enumerate(self['loop_groups']):
696 for lamp in group[1]:
697 lamp.set('loop_group_id',i)
698
700 """ Perform the simple color processing from a single matrix element
701 (without optimization then). This is called from the initialization
702 and overloaded here in order to have the correct treatment """
703
704
705
706 self.relabel_helas_objects()
707 self.get('loop_color_basis').build_loop(self.get('base_amplitude'))
708 if self.get('base_amplitude')['process']['has_born']:
709 self.get('born_color_basis').build_born(self.get('base_amplitude'))
710 self.set('color_matrix',\
711 color_amp.ColorMatrix(self.get('loop_color_basis'),\
712 self.get('born_color_basis')))
713 else:
714 self.set('color_matrix',\
715 color_amp.ColorMatrix(self.get('loop_color_basis')))
716
718 """Return particle property names as a nicely sorted list."""
719
720 return ['processes', 'identical_particle_factor',
721 'diagrams', 'born_color_basis','loop_color_basis',
722 'color_matrix','base_amplitude', 'has_mirror_process',
723 'loop_groups']
724
725
726 - def __init__(self, amplitude=None, optimization=1,
727 decay_ids=[], gen_color=True, optimized_output=False):
728 """Constructor for the LoopHelasMatrixElement. For now, it works exactly
729 as for the HelasMatrixElement one."""
730 self.optimized_output=optimized_output
731 super(LoopHelasMatrixElement, self).__init__(amplitude, optimization,\
732 decay_ids, gen_color)
733
734
735
736
737
738
740 """Comparison between different loop matrix elements. It works exactly as for
741 the HelasMatrixElement for now."""
742
743 return super(LoopHelasMatrixElement,self).__eq__(other)
744
746 """Overloading the nonequality operator, to make comparison easy"""
747 return not self.__eq__(other)
748
751 """Starting from a list of LoopDiagrams from the diagram
752 generation, generate the corresponding LoopHelasDiagrams, i.e.,
753 the wave functions and amplitudes (for the loops and their R2 and UV
754 counterterms). Choose between default optimization (= 1, maximum
755 recycling of wavefunctions) or no optimization (= 0, no recycling of
756 wavefunctions, useful for GPU calculations with very restricted memory).
757
758 Note that we need special treatment for decay chains, since
759 the end product then is a wavefunction, not an amplitude.
760 """
761
762 assert isinstance(amplitude, loop_diagram_generation.LoopAmplitude), \
763 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement"
764 assert isinstance(optimization, int), \
765 "Bad arguments for generate_helas_diagrams in LoopHelasMatrixElement"
766
767 structures = amplitude.get('structure_repository')
768
769 process = amplitude.get('process')
770 has_born = amplitude.get('has_born')
771
772 model = process.get('model')
773
774
775
776 self.sort_split_orders(self.get('processes')[0].get('split_orders'))
777
778
779
780
781
782
783
784 amplitude.order_diagrams_according_to_split_orders(\
785 self.get('processes')[0].get('split_orders'))
786
787
788 wavefunctions = []
789
790
791
792
793
794
795
796
797 structID_to_infos = {}
798
799
800
801 wf_mother_arrays = []
802
803 wf_number = 0
804
805
806 external_wavefunctions = dict([(leg.get('number'),
807 helas_objects.HelasWavefunction(\
808 leg, 0, model, decay_ids)) \
809 for leg in process.get('legs')])
810
811
812
813 external_loop_wfs_dict={}
814
815
816
817 for key in external_wavefunctions.keys():
818 wf = external_wavefunctions[key]
819 if wf.is_boson() and wf.get('state') == 'initial' and \
820 not wf.get('self_antipart'):
821 wf.set('is_part', not wf.get('is_part'))
822
823
824
825 for key in external_wavefunctions.keys():
826 wf = external_wavefunctions[key]
827 if wf.get('leg_state') == False and \
828 not wf.get('self_antipart'):
829 wf.flip_part_antipart()
830
831
832 wf_number = len(process.get('legs'))
833
834
835
836 helas_diagrams = helas_objects.HelasDiagramList()
837
838
839 amplitude_number = 0
840 diagram_number = 0
841
842 def process_born_diagram(diagram, wfNumber, amplitudeNumber, UVCTdiag=False):
843 """ Helper function to process a born diagrams exactly as it is done in
844 HelasMatrixElement for tree-level diagrams. This routine can also
845 process LoopUVCTDiagrams, and if so the argument UVCTdiag must be set
846 to true"""
847
848
849
850
851 number_to_wavefunctions = [{}]
852
853
854 color_lists = [[]]
855
856
857 diagram_wavefunctions = helas_objects.HelasWavefunctionList()
858
859 vertices = copy.copy(diagram.get('vertices'))
860
861
862 lastvx = vertices.pop()
863
864
865
866 for vertex in vertices:
867
868
869
870
871
872
873
874
875
876
877 new_number_to_wavefunctions = []
878 new_color_lists = []
879 for number_wf_dict, color_list in zip(number_to_wavefunctions,
880 color_lists):
881 legs = copy.copy(vertex.get('legs'))
882 last_leg = legs.pop()
883
884 mothers = self.getmothers(legs, number_wf_dict,
885 external_wavefunctions,
886 wavefunctions,
887 diagram_wavefunctions)
888 inter = model.get('interaction_dict')[vertex.get('id')]
889
890
891
892
893 done_color = {}
894 for coupl_key in sorted(inter.get('couplings').keys()):
895 color = coupl_key[0]
896 if color in done_color:
897 wf = done_color[color]
898 wf.get('coupling').append(inter.get('couplings')[coupl_key])
899 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
900 continue
901 wf = helas_objects.HelasWavefunction(last_leg, \
902 vertex.get('id'), model)
903 wf.set('coupling', [inter.get('couplings')[coupl_key]])
904 if inter.get('color'):
905 wf.set('inter_color', inter.get('color')[coupl_key[0]])
906 done_color[color] = wf
907 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
908 wf.set('color_key', color)
909 wf.set('mothers',mothers)
910
911
912
913 wf.set_state_and_particle(model)
914
915
916
917
918 wf, wfNumber = wf.check_and_fix_fermion_flow(\
919 wavefunctions,
920 diagram_wavefunctions,
921 external_wavefunctions,
922 wfNumber)
923
924 new_number_wf_dict = copy.copy(number_wf_dict)
925
926 try:
927 wf = diagram_wavefunctions[\
928 diagram_wavefunctions.index(wf)]
929 except ValueError:
930
931 wfNumber = wfNumber + 1
932 wf.set('number', wfNumber)
933 try:
934
935
936 wf = wavefunctions[wf_mother_arrays.index(\
937 wf.to_array())]
938
939
940 wfNumber = wfNumber - 1
941 except ValueError:
942 diagram_wavefunctions.append(wf)
943
944 new_number_wf_dict[last_leg.get('number')] = wf
945
946
947 new_number_to_wavefunctions.append(\
948 new_number_wf_dict)
949
950 new_color_list = copy.copy(color_list)
951 new_color_list.append(coupl_key[0])
952 new_color_lists.append(new_color_list)
953
954 number_to_wavefunctions = new_number_to_wavefunctions
955 color_lists = new_color_lists
956
957
958
959 if not UVCTdiag:
960 helas_diagram = helas_objects.HelasDiagram()
961 else:
962 helas_diagram = LoopHelasDiagram()
963
964 for number_wf_dict, color_list in zip(number_to_wavefunctions,
965 color_lists):
966
967
968 if lastvx.get('id'):
969 inter = model.get_interaction(lastvx.get('id'))
970 keys = sorted(inter.get('couplings').keys())
971 pdg_codes = [p.get_pdg_code() for p in \
972 inter.get('particles')]
973 else:
974
975
976 inter = None
977 keys = [(0, 0)]
978 pdg_codes = None
979
980
981 legs = lastvx.get('legs')
982 mothers = self.getmothers(legs, number_wf_dict,
983 external_wavefunctions,
984 wavefunctions,
985 diagram_wavefunctions).\
986 sort_by_pdg_codes(pdg_codes, 0)[0]
987
988
989 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
990 diagram_wavefunctions,
991 external_wavefunctions,
992 None,
993 wfNumber,
994 False,
995 number_to_wavefunctions)
996 done_color = {}
997 for i, coupl_key in enumerate(keys):
998 color = coupl_key[0]
999 if inter and color in list(done_color.keys()):
1000 amp = done_color[color]
1001 amp.get('coupling').append(inter.get('couplings')[coupl_key])
1002 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1003 continue
1004 if not UVCTdiag:
1005 amp = helas_objects.HelasAmplitude(lastvx, model)
1006 else:
1007 amp = LoopHelasUVCTAmplitude(lastvx, model)
1008 amp.set('UVCT_orders',diagram.get('UVCT_orders'))
1009 amp.set('UVCT_couplings',diagram.get('UVCT_couplings'))
1010 amp.set('type',diagram.get('type'))
1011 if inter:
1012 amp.set('coupling', [inter.get('couplings')[coupl_key]])
1013 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1014 if inter.get('color'):
1015 amp.set('inter_color', inter.get('color')[color])
1016 amp.set('color_key', color)
1017 done_color[color] = amp
1018 amp.set('mothers', mothers)
1019 amplitudeNumber = amplitudeNumber + 1
1020 amp.set('number', amplitudeNumber)
1021
1022 new_color_list = copy.copy(color_list)
1023 if inter:
1024 new_color_list.append(color)
1025
1026 amp.set('color_indices', new_color_list)
1027
1028
1029 helas_diagram.get('amplitudes').append(amp)
1030
1031
1032
1033 helas_diagram.set('wavefunctions', diagram_wavefunctions)
1034
1035
1036 diagram_wavefunctions.sort(key=lambda wf: wf.get('number'))
1037
1038
1039 if optimization:
1040 wavefunctions.extend(diagram_wavefunctions)
1041 wf_mother_arrays.extend([wf.to_array() for wf \
1042 in diagram_wavefunctions])
1043 else:
1044 wfNumber = len(process.get('legs'))
1045 if self.optimized_output:
1046
1047
1048 wfNumber = wfNumber+1
1049
1050
1051 return helas_diagram, wfNumber, amplitudeNumber
1052
1053 def process_struct(sID, diag_wfs, wfNumber):
1054 """ Scan a structure, create the necessary wavefunctions, add them
1055 to the diagram wavefunctions list, and return a list of bridge
1056 wavefunctions (i.e. those attached to the loop) with a list, ordered
1057 in the same way, of color lists. Each element of these lists
1058 correspond to one choice of color-lorentz structure of this
1059 tree-structure #sID. """
1060
1061
1062
1063
1064 number_to_wavefunctions = [{}]
1065
1066
1067 color_lists = [[]]
1068
1069
1070 bridge_wfs = helas_objects.HelasWavefunctionList()
1071
1072 vertices = copy.copy(structures[sID].get('vertices'))
1073
1074
1075
1076 if len(vertices)==0:
1077 binding_leg=copy.copy(structures[sID]['binding_leg'])
1078 binding_wf = self.getmothers(base_objects.LegList([binding_leg,]),
1079 {},
1080 external_wavefunctions,
1081 wavefunctions,
1082 diag_wfs)
1083
1084
1085 return [(binding_wf[0],[])] ,wfNumber
1086
1087
1088
1089 for i, vertex in enumerate(vertices):
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100 new_number_to_wavefunctions = []
1101 new_color_lists = []
1102 for number_wf_dict, color_list in zip(number_to_wavefunctions,
1103 color_lists):
1104 legs = copy.copy(vertex.get('legs'))
1105 last_leg = legs.pop()
1106
1107 mothers = self.getmothers(legs, number_wf_dict,
1108 external_wavefunctions,
1109 wavefunctions,
1110 diag_wfs)
1111 inter = model.get('interaction_dict')[vertex.get('id')]
1112
1113
1114
1115
1116
1117 grouped_interaction_keys = {}
1118 colors_order = []
1119 for coupl_key in sorted(inter.get('couplings').keys()):
1120 color = coupl_key[0]
1121 if color not in colors_order:
1122 colors_order.append(color)
1123 grouped_interaction_keys[color] = \
1124 (coupl_key, [inter.get('couplings')[coupl_key]], [inter.get('lorentz')[coupl_key[1]]])
1125 else:
1126 grouped_interaction_keys[color][1].append(inter.get('couplings')[coupl_key])
1127 grouped_interaction_keys[color][2].append(inter.get('lorentz')[coupl_key[1]])
1128
1129 for coupl_key, all_couplings, all_lorentz in [grouped_interaction_keys[color] for color in colors_order]:
1130 color = coupl_key[0]
1131 wf = helas_objects.HelasWavefunction(last_leg, vertex.get('id'), model)
1132 wf.set('coupling', all_couplings)
1133 if inter.get('color'):
1134 wf.set('inter_color', inter.get('color')[coupl_key[0]])
1135 wf.set('lorentz', all_lorentz)
1136 wf.set('color_key', color)
1137 wf.set('mothers',mothers)
1138
1139
1140
1141
1142
1143
1144
1145
1146 wf.set_state_and_particle(model)
1147
1148
1149
1150 wf, wfNumber = wf.check_and_fix_fermion_flow(\
1151 wavefunctions,
1152 diag_wfs,
1153 external_wavefunctions,
1154 wfNumber)
1155
1156 new_number_wf_dict = copy.copy(number_wf_dict)
1157
1158
1159 try:
1160 wf = diag_wfs[\
1161 diag_wfs.index(wf)]
1162 except ValueError:
1163
1164 wfNumber = wfNumber + 1
1165 wf.set('number', wfNumber)
1166 try:
1167
1168
1169 wf = wavefunctions[wf_mother_arrays.index(wf.to_array())]
1170
1171
1172 wfNumber = wfNumber - 1
1173 except ValueError:
1174 diag_wfs.append(wf)
1175
1176 new_number_wf_dict[last_leg.get('number')] = wf
1177 if i==(len(vertices)-1):
1178
1179
1180 bridge_wfs.append(wf)
1181
1182 new_number_to_wavefunctions.append(\
1183 new_number_wf_dict)
1184
1185 new_color_list = copy.copy(color_list)
1186 new_color_list.append(coupl_key[0])
1187 new_color_lists.append(new_color_list)
1188
1189
1190 number_to_wavefunctions = new_number_to_wavefunctions
1191 color_lists = new_color_lists
1192
1193
1194
1195
1196
1197 return list(zip(bridge_wfs, color_lists)), wfNumber
1198
1199 def getloopmothers(loopWfsIn, structIDs, color_list, diag_wfs, wfNumber):
1200 """From the incoming loop leg(s) and the list of structures IDs
1201 connected to the loop at this point, it generates the list of
1202 mothers, a list of colorlist and a number_to_wavefunctions
1203 dictionary list for which each element correspond to one
1204 lorentz-color structure of the tree-structure attached to the loop.
1205 It will launch the reconstruction procedure of the structures
1206 which have not been encountered yet."""
1207
1208
1209
1210
1211
1212 mothers_list = [loopWfsIn,]
1213 color_lists = [color_list,]
1214
1215
1216
1217 for sID in structIDs:
1218 try:
1219 struct_infos = structID_to_infos[sID]
1220 except KeyError:
1221
1222
1223 struct_infos, wfNumber = \
1224 process_struct(sID, diag_wfs, wfNumber)
1225
1226
1227
1228
1229
1230 if optimization and False:
1231
1232
1233
1234
1235 structID_to_infos[sID]=copy.copy(struct_infos)
1236
1237
1238 new_mothers_list = []
1239 new_color_lists = []
1240 for mothers, orig_color_list in zip(mothers_list, color_lists):
1241 for struct_wf, struct_color_list in struct_infos:
1242 new_color_list = copy.copy(orig_color_list)+\
1243 copy.copy(struct_color_list)
1244 new_mothers = copy.copy(mothers)
1245 new_mothers.append(struct_wf)
1246 new_color_lists.append(new_color_list)
1247 new_mothers_list.append(new_mothers)
1248 mothers_list = new_mothers_list
1249 color_lists = new_color_lists
1250
1251
1252
1253
1254
1255
1256 return (mothers_list, color_lists), wfNumber
1257
1258 def process_loop_diagram(diagram, wavefunctionNumber, amplitudeNumber):
1259 """ Helper function to process a the loop diagrams which features
1260 several different aspects compared to the tree born diagrams."""
1261
1262
1263 helas_diagram = LoopHelasDiagram()
1264
1265
1266
1267
1268
1269
1270
1271 last_loop_wfs = helas_objects.HelasWavefunctionList()
1272
1273
1274 color_lists = [[]]
1275
1276
1277 diagram_wavefunctions = helas_objects.HelasWavefunctionList()
1278
1279
1280
1281
1282 tag = copy.deepcopy(diagram.get('tag'))
1283 loop_vertices = copy.deepcopy(diagram.get('vertices'))
1284 for i in range(len(tag)):
1285 tag[i][2]=loop_vertices[i]
1286
1287
1288 ct_vertices = copy.copy(diagram.get('CT_vertices'))
1289
1290
1291 external_loop_wf=helas_objects.HelasWavefunction(\
1292 tag[0][0], 0, model, decay_ids)
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302 if not self.optimized_output:
1303 wavefunctionNumber=wavefunctionNumber+1
1304 external_loop_wf.set('number',wavefunctionNumber)
1305 diagram_wavefunctions.append(external_loop_wf)
1306 else:
1307 try:
1308 external_loop_wf=\
1309 external_loop_wfs_dict[external_loop_wf.get('pdg_code')]
1310 except KeyError:
1311 wavefunctionNumber=wavefunctionNumber+1
1312 external_loop_wf.set('number',wavefunctionNumber)
1313 external_loop_wfs_dict[external_loop_wf.get('pdg_code')]=\
1314 external_loop_wf
1315 diagram_wavefunctions.append(external_loop_wf)
1316
1317
1318 last_loop_wfs.append(external_loop_wf)
1319
1320 def process_tag_elem(tagElem, wfNumber, lastloopwfs, colorlists):
1321 """Treat one tag element of the loop diagram (not the last one
1322 which provides an amplitude)"""
1323
1324
1325
1326
1327
1328 new_color_lists = []
1329 new_last_loop_wfs = helas_objects.HelasWavefunctionList()
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340 vertex=tagElem[2]
1341 structIDs=tagElem[1]
1342 for last_loop_wf, color_list in zip(lastloopwfs,
1343 colorlists):
1344 loopLegOut = copy.copy(vertex.get('legs')[-1])
1345
1346
1347
1348
1349
1350
1351 (motherslist, colorlists), wfNumber = \
1352 getloopmothers(\
1353 helas_objects.HelasWavefunctionList([last_loop_wf,]),
1354 structIDs,\
1355 color_list, diagram_wavefunctions, wfNumber)
1356 inter = model.get('interaction_dict')[vertex.get('id')]
1357
1358
1359
1360 for mothers, structcolorlist in zip(motherslist, colorlists):
1361
1362 done_color = {}
1363 for coupl_key in sorted(inter.get('couplings').keys()):
1364 color = coupl_key[0]
1365 if color in done_color:
1366 wf = done_color[color]
1367 wf.get('coupling').append(inter.get('couplings')[coupl_key])
1368 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1369 continue
1370 wf = helas_objects.HelasWavefunction(loopLegOut, \
1371 vertex.get('id'), model)
1372 wf.set('coupling', [inter.get('couplings')[coupl_key]])
1373 if inter.get('color'):
1374 wf.set('inter_color', inter.get('color')[coupl_key[0]])
1375 done_color[color] = wf
1376 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1377 wf.set('color_key', color)
1378 wf.set('mothers',mothers)
1379
1380
1381
1382 wf.set_state_and_particle(model)
1383
1384
1385
1386 wf, wfNumber = wf.check_and_fix_fermion_flow(\
1387 wavefunctions,
1388 diagram_wavefunctions,
1389 external_wavefunctions,
1390 wfNumber)
1391
1392
1393 try:
1394 wf = diagram_wavefunctions[\
1395 diagram_wavefunctions.index(wf)]
1396 except ValueError:
1397
1398 wfNumber = wfNumber + 1
1399 wf.set('number', wfNumber)
1400
1401
1402
1403 try:
1404 if not self.optimized_output:
1405 raise ValueError
1406
1407
1408 wf = wavefunctions[wf_mother_arrays.index(\
1409 wf.to_array())]
1410
1411
1412 wfNumber = wfNumber - 1
1413
1414
1415 self.lwf_reused += 1
1416 except ValueError:
1417 diagram_wavefunctions.append(wf)
1418
1419
1420
1421 new_last_loop_wfs.append(wf)
1422
1423 new_color_list = copy.copy(structcolorlist)
1424 new_color_list.append(coupl_key[0])
1425 new_color_lists.append(new_color_list)
1426
1427
1428
1429
1430 return wfNumber, new_last_loop_wfs, new_color_lists
1431
1432
1433
1434
1435
1436 def create_amplitudes(lastvx, wfNumber, amplitudeNumber):
1437 """Treat the last tag element of the loop diagram (which
1438 provides an amplitude)"""
1439
1440
1441
1442
1443
1444
1445 other_external_loop_wf=helas_objects.HelasWavefunction()
1446
1447 for leg in [leg for leg in lastvx['legs'] if leg['loop_line']]:
1448 if last_loop_wfs[0]['number_external']!=leg['number']:
1449 other_external_loop_wf=\
1450 helas_objects.HelasWavefunction(leg, 0, model, decay_ids)
1451
1452 break
1453
1454
1455 for last_loop_wf, color_list in zip(last_loop_wfs,color_lists):
1456
1457 if lastvx.get('id')!=-1:
1458 raise self.PhysicsObjectError("The amplitude vertex of a loop diagram must be a "+\
1459 "two point vertex with id=-1")
1460
1461
1462 if other_external_loop_wf.is_majorana():
1463 fix_lcut_majorana_fermion_flow(last_loop_wf,\
1464 other_external_loop_wf)
1465
1466 mothers=helas_objects.HelasWavefunctionList(\
1467 [last_loop_wf,other_external_loop_wf])
1468 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
1469 diagram_wavefunctions,
1470 external_wavefunctions,
1471 None,
1472 wfNumber,
1473 False,
1474 [])
1475 amp = helas_objects.HelasAmplitude(lastvx, model)
1476 amp.set('interaction_id',-1)
1477 amp.set('mothers',mothers)
1478
1479
1480 amp.set('pdg_codes',[last_loop_wf.get_pdg_code(),
1481 other_external_loop_wf.get_pdg_code()])
1482
1483
1484
1485
1486
1487 amp.set('color_indices', copy.copy(color_list))
1488
1489
1490 amplitudeNumber = amplitudeNumber + 1
1491 amp.set('number', amplitudeNumber)
1492 amp.set('type','loop')
1493 loop_amp = LoopHelasAmplitude()
1494 loop_amp.set('amplitudes',\
1495 helas_objects.HelasAmplitudeList([amp,]))
1496
1497
1498
1499
1500 loop_amp_wfs=helas_objects.HelasWavefunctionList(\
1501 [last_loop_wf,])
1502 while loop_amp_wfs[-1].get('mothers'):
1503 loop_amp_wfs.append([lwf for lwf in \
1504 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0])
1505
1506
1507
1508
1509
1510 loop_amp_wfs.append(other_external_loop_wf)
1511
1512
1513 loop_amp_wfs.reverse()
1514 loop_amp.set('wavefunctions',loop_amp_wfs)
1515 loop_amp.set('type',diagram.get('type'))
1516 loop_amp.set('multiplier',diagram.get('multiplier'))
1517
1518 loop_amp.set('number',min([amp.get('number') for amp
1519 in loop_amp.get('amplitudes')]))
1520 loop_amp.set('coupling',loop_amp.get_couplings())
1521 loop_amp.set('orders',loop_amp.get_orders())
1522 helas_diagram.get('amplitudes').append(loop_amp)
1523
1524
1525 check_lcut_fermion_flow_consistency(\
1526 loop_amp_wfs[0],loop_amp_wfs[1])
1527 return wfNumber, amplitudeNumber
1528
1529 def check_lcut_fermion_flow_consistency(lcut_wf1, lcut_wf2):
1530 """Checks that the two L-cut loop helas wavefunctions have
1531 a consistent fermion flow."""
1532 if lcut_wf1.is_boson():
1533 if lcut_wf1.get('state')!='final' or\
1534 lcut_wf2.get('state')!='final':
1535 raise MadGraph5Error("Inconsistent flow in L-cut bosons.")
1536 elif not lcut_wf1.is_majorana():
1537 for lcut_wf in [lcut_wf1,lcut_wf2]:
1538 if not ((lcut_wf.get('is_part') and \
1539 lcut_wf.get('state')=='outgoing') or\
1540 (not lcut_wf.get('is_part') and\
1541 lcut_wf.get('state')=='incoming')):
1542 raise MadGraph5Error("Inconsistent flow in L-cut Dirac fermions.")
1543 elif lcut_wf1.is_majorana():
1544 if (lcut_wf1.get('state'), lcut_wf2.get('state')) not in \
1545 [('incoming','outgoing'),('outgoing','incoming')]:
1546 raise MadGraph5Error("Inconsistent flow in L-cut Majorana fermions.")
1547
1548 def fix_lcut_majorana_fermion_flow(last_loop_wf,\
1549 other_external_loop_wf):
1550 """Fix the fermion flow of the last external Majorana loop
1551 wavefunction through the fermion flow of the first external
1552 Majorana loop wavefunction."""
1553
1554
1555 loop_amp_wfs=helas_objects.HelasWavefunctionList(\
1556 [last_loop_wf,])
1557 while loop_amp_wfs[-1].get('mothers'):
1558 loop_amp_wfs.append([lwf for lwf in \
1559 loop_amp_wfs[-1].get('mothers') if lwf['is_loop']][0])
1560 loop_amp_wfs.append(other_external_loop_wf)
1561 loop_amp_wfs.reverse()
1562
1563
1564 rep={'incoming':'outgoing','outgoing':'incoming'}
1565
1566 other_external_loop_wf['state']=rep[loop_amp_wfs[1]['state']]
1567 return
1568
1569 def process_counterterms(ct_vertices, wfNumber, amplitudeNumber):
1570 """Process the counterterms vertices defined in this loop
1571 diagram."""
1572
1573 structIDs=[]
1574 for tagElem in tag:
1575 structIDs += tagElem[1]
1576
1577
1578
1579
1580 (motherslist, colorlists), wfNumber = getloopmothers(\
1581 helas_objects.HelasWavefunctionList(), structIDs, \
1582 [], diagram_wavefunctions, wfNumber)
1583
1584 for mothers, structcolorlist in zip(motherslist, colorlists):
1585 for ct_vertex in ct_vertices:
1586
1587 inter = model.get_interaction(ct_vertex.get('id'))
1588 keys = sorted(inter.get('couplings').keys())
1589 pdg_codes = [p.get_pdg_code() for p in \
1590 inter.get('particles')]
1591 mothers = mothers.sort_by_pdg_codes(pdg_codes, 0)[0]
1592
1593
1594 wfNumber = mothers.check_and_fix_fermion_flow(wavefunctions,
1595 diagram_wavefunctions,
1596 external_wavefunctions,
1597 None,
1598 wfNumber,
1599 False,
1600 [])
1601 done_color = {}
1602 for i, coupl_key in enumerate(keys):
1603 color = coupl_key[0]
1604 if color in list(done_color.keys()):
1605 amp = done_color[color]
1606 amp.get('coupling').append(inter.get('couplings')[coupl_key])
1607 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
1608 continue
1609 amp = helas_objects.HelasAmplitude(ct_vertex, model)
1610 amp.set('coupling', [inter.get('couplings')[coupl_key]])
1611 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
1612 if inter.get('color'):
1613 amp.set('inter_color', inter.get('color')[color])
1614 amp.set('color_key', color)
1615 done_color[color] = amp
1616 amp.set('mothers', mothers)
1617 amplitudeNumber = amplitudeNumber + 1
1618 amp.set('number', amplitudeNumber)
1619
1620 amp_color_list = copy.copy(structcolorlist)
1621 amp_color_list.append(color)
1622 amp.set('color_indices', amp_color_list)
1623 amp.set('type',inter.get('type'))
1624
1625
1626 helas_diagram.get('amplitudes').append(amp)
1627 return wfNumber, amplitudeNumber
1628
1629 for tagElem in tag:
1630 wavefunctionNumber, last_loop_wfs, color_lists = \
1631 process_tag_elem(tagElem, wavefunctionNumber, \
1632 last_loop_wfs, color_lists)
1633
1634
1635
1636 wavefunctionNumber, amplitudeNumber = create_amplitudes(
1637 loop_vertices[-1], wavefunctionNumber, amplitudeNumber)
1638
1639
1640 if ct_vertices:
1641 wavefunctionNumber, amplitudeNumber = process_counterterms(\
1642 ct_vertices, wavefunctionNumber, amplitudeNumber)
1643
1644
1645
1646 struct_wfs=helas_objects.HelasWavefunctionList(\
1647 [wf for wf in diagram_wavefunctions if not wf['is_loop']])
1648 loop_wfs=helas_objects.HelasWavefunctionList(\
1649 [wf for wf in diagram_wavefunctions if wf['is_loop']])
1650
1651
1652 struct_wfs.sort(key = lambda wf: wf.get('number'))
1653
1654
1655
1656 helas_diagram.set('wavefunctions', struct_wfs)
1657
1658
1659
1660
1661 if optimization:
1662 wavefunctions.extend(struct_wfs)
1663 wf_mother_arrays.extend([wf.to_array() for wf in struct_wfs])
1664 if self.optimized_output:
1665 wavefunctions.extend(loop_wfs)
1666 wf_mother_arrays.extend([wf.to_array() for wf in loop_wfs])
1667 else:
1668 wavefunctionNumber = len(process.get('legs'))
1669 if self.optimized_output:
1670
1671
1672 wavefunctionNumber = wavefunctionNumber+1
1673
1674
1675
1676
1677
1678
1679 if self.optimized_output:
1680 loop_wfs = helas_objects.HelasWavefunctionList(
1681 [lwf for lwf in loop_wfs if len(lwf.get('mothers'))>0])
1682 helas_diagram.set('loop_wavefunctions',loop_wfs)
1683
1684
1685 return helas_diagram, wavefunctionNumber, amplitudeNumber
1686
1687
1688 if has_born:
1689 for diagram in amplitude.get('born_diagrams'):
1690 helBornDiag, wf_number, amplitude_number=\
1691 process_born_diagram(diagram, wf_number, amplitude_number)
1692 diagram_number = diagram_number + 1
1693 helBornDiag.set('number', diagram_number)
1694 helas_diagrams.append(helBornDiag)
1695
1696
1697 self.lwf_reused=0
1698 for diagram in amplitude.get('loop_diagrams'):
1699 loopHelDiag, wf_number, amplitude_number=\
1700 process_loop_diagram(diagram, wf_number, amplitude_number)
1701 diagram_number = diagram_number + 1
1702 loopHelDiag.set('number', diagram_number)
1703 helas_diagrams.append(loopHelDiag)
1704
1705
1706 for diagram in amplitude.get('loop_UVCT_diagrams'):
1707 loopHelDiag, wf_number, amplitude_number=\
1708 process_born_diagram(diagram, wf_number, amplitude_number, \
1709 UVCTdiag=True)
1710 diagram_number = diagram_number + 1
1711 loopHelDiag.set('number', diagram_number)
1712
1713
1714 for lamp in loopHelDiag.get_loop_UVCTamplitudes():
1715 new_orders = copy.copy(lamp.get('orders'))
1716 for order, value in lamp.get('UVCT_orders').items():
1717 try:
1718 new_orders[order] = new_orders[order] + value
1719 except KeyError:
1720 new_orders[order] = value
1721 lamp.set('orders', new_orders)
1722 helas_diagrams.append(loopHelDiag)
1723
1724 self.set('diagrams', helas_diagrams)
1725
1726 if __debug__:
1727 for diag in self.get('diagrams'):
1728
1729
1730
1731
1732 diag.get('wavefunctions').check_wavefunction_numbers_order()
1733
1734
1735 if self.optimized_output:
1736 logger.debug('%d loop wavefunctions have been reused'%self.lwf_reused+
1737 ', for a total of %d ones'%sum([len(ldiag.get('loop_wavefunctions'))
1738 for ldiag in self.get_loop_diagrams()]))
1739
1740
1741 for wf in self.get_all_wavefunctions():
1742 wf.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(wf))
1743
1744 for amp in self.get_all_amplitudes():
1745 amp.set('mothers', helas_objects.HelasMatrixElement.sorted_mothers(amp))
1746
1747
1748
1749 gen_colors = amp.get('color_indices')
1750 amp.set('color_indices', amp.get_color_indices())
1751 if isinstance(amp,LoopHelasAmplitude):
1752 assert (amp.get('color_indices')==gen_colors), \
1753 "Error in the treatment of color in the loop helas diagram "+\
1754 "generation. It could be harmless, but report this bug to be sure."+\
1755 " The different keys are %s vs %s."%(str(gen_colors),\
1756 str(amp.get('color_indices')))
1757 for loopdiag in self.get_loop_diagrams():
1758 for loopamp in loopdiag.get_loop_amplitudes():
1759 loopamp.set_mothers_and_pairing()
1760
1761
1762
1763
1764
1765
1766
1767
1768
1770 """This function returns a list and a dictionary:
1771 squared_orders, amps_orders
1772 ===
1773 The squared_orders lists all contributing squared_orders as tuple whose
1774 elements are the power at which are elevated the couplings orderered as
1775 in the 'split_orders'.
1776
1777 squared_orders : All possible contributing squared orders among those
1778 specified in the process['split_orders'] argument. The elements of
1779 the list are tuples of the format
1780 ((OrderValue1,OrderValue2,...),
1781 (max_contrib_ct_amp_number,
1782 max_contrib_uvct_amp_number,
1783 max_contrib_loop_amp_number,
1784 max_contrib_group_id))
1785 with OrderValue<i> correspond to the value of the <i>th order in
1786 process['split_orders'] (the others are summed over and therefore
1787 left unspecified).
1788 Ex for dijet with process['split_orders']=['QCD','QED']:
1789 => [((4,0),(8,2,3)),((2,2),(10,3,3)),((0,4),(20,5,4))]
1790
1791 'max_contrib_loop_amp_number': For optimization purposes, it is good to
1792 know what is the maximum loop amplitude number contributing to any given
1793 squared order. The fortran output is structured so that if the user
1794 is interested in a given squared order contribution only, then
1795 all the open loop coefficients for the amplitudes with a number above
1796 this value can be skipped.
1797
1798 'max_contrib_(uv)ct_amp_number': Same as above but for the
1799 (uv)ctamplitude number.
1800
1801 'max_contrib_group_id': The same as above, except this time
1802 it is for the loop group id used for the loop reduction.
1803 ===
1804 The amps_orders is a *dictionary* with keys
1805 'born_amp_orders',
1806 'loop_amp_orders'
1807 with values being the tuples described below.
1808
1809 If process['split_orders'] is empty, all these tuples are set empty.
1810
1811 'born_amp_orders' : Exactly as for squared order except that this list specifies
1812 the contributing order values for the amplitude (i.e. not 'squared').
1813 Also, the tuple describing the amplitude order is nested with a
1814 second one listing all amplitude numbers contributing to this order.
1815 Ex for dijet with process['split_orders']=['QCD','QED']:
1816 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))]
1817 The function returns () if the process has no borns.
1818
1819 'loop_amp_orders' : The same as for born_amp_orders but for the loop
1820 type of amplitudes only.
1821
1822 Keep in mind that the orders of the elements of the outter most list is
1823 important as it dictates the order for the corresponding "order indices"
1824 in the fortran code output by the exporters.
1825 """
1826
1827 split_orders=self.get('processes')[0].get('split_orders')
1828
1829 amps_orders = {'born_amp_orders':[],
1830 'loop_amp_orders':[]}
1831 if len(split_orders)==0:
1832 self.squared_orders = []
1833 return [],amps_orders
1834
1835
1836
1837 self.sort_split_orders(split_orders)
1838
1839 process = self.get('processes')[0]
1840
1841
1842 self.sort_split_orders(split_orders)
1843 loop_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1844 self.get_loop_diagrams(), split_orders,
1845 get_amplitudes_function = lambda diag: diag.get_loop_amplitudes(),
1846
1847
1848
1849 get_amp_number_function = lambda amp:
1850 (amp.get('amplitudes')[0].get('number'),amp.get('loop_group_id')))
1851 ct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1852 self.get_loop_diagrams(), split_orders,
1853 get_amplitudes_function = lambda diag: diag.get_ct_amplitudes())
1854 uvct_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1855 self.get_loop_UVCT_diagrams(), split_orders)
1856
1857
1858
1859
1860 amps_orders['loop_amp_orders'] = dict([(lao[0],
1861 [el[0] for el in lao[1]]) for lao in loop_amp_orders])
1862
1863 for ct_amp_order in ct_amp_orders+uvct_amp_orders:
1864 try:
1865 amps_orders['loop_amp_orders'][ct_amp_order[0]].extend(\
1866 list(ct_amp_order[1]))
1867 except KeyError:
1868 amps_orders['loop_amp_orders'][ct_amp_order[0]] = \
1869 list(ct_amp_order[1])
1870
1871 amps_orders['loop_amp_orders'] = [
1872 (key, tuple(sorted(amps_orders['loop_amp_orders'][key])))
1873 for key in amps_orders['loop_amp_orders'].keys()]
1874
1875 order_hierarchy = self.get('processes')[0]\
1876 .get('model').get('order_hierarchy')
1877 if set(order_hierarchy.keys()).union(set(split_orders))==\
1878 set(order_hierarchy.keys()):
1879 amps_orders['loop_amp_orders'].sort(key= lambda so:
1880 sum([order_hierarchy[split_orders[i]]*order_power for \
1881 i, order_power in enumerate(so[0])]))
1882
1883
1884 if process.get('has_born'):
1885 born_amp_orders = self.get_split_orders_mapping_for_diagram_list(\
1886 self.get_born_diagrams(),split_orders)
1887
1888 amps_orders['born_amp_orders'] = born_amp_orders
1889
1890
1891
1892
1893
1894 loop_orders = [(lso[0],tuple(zip(*list(lso[1])))) for lso in loop_amp_orders]
1895
1896
1897
1898 if process.get('has_born'):
1899 ref_orders = [bao[0] for bao in born_amp_orders]
1900 else:
1901 ref_orders = [lao[0] for lao in loop_orders+ct_amp_orders]
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913 def smax(AmpNumList):
1914 return -1 if len(AmpNumList)==0 else max(AmpNumList)
1915
1916 squared_orders = {}
1917 for ref_order in ref_orders:
1918 for uvct_order in uvct_amp_orders:
1919 key = tuple([ord1 + ord2 for ord1,ord2 in zip(uvct_order[0],
1920 ref_order)])
1921 try:
1922
1923 squared_orders[key][0] = smax([squared_orders[key][0]]+
1924 list(uvct_order[1]))
1925 except KeyError:
1926 squared_orders[key] = [smax(list(uvct_order[1])),-1,-1,-1]
1927
1928 for ct_order in ct_amp_orders:
1929 key = tuple([ord1 + ord2 for ord1,ord2 in zip(ct_order[0],
1930 ref_order)])
1931 try:
1932
1933 squared_orders[key][1] = smax([squared_orders[key][1]]+
1934 list(ct_order[1]))
1935 except KeyError:
1936 squared_orders[key] = [-1,smax(list(ct_order[1])),-1,-1]
1937
1938 for loop_order in loop_orders:
1939 key = tuple([ord1 + ord2 for ord1,ord2 in zip(loop_order[0],
1940 ref_order)])
1941 try:
1942
1943 squared_orders[key][2] = smax([squared_orders[key][2]]+
1944 list(loop_order[1][0]))
1945
1946 squared_orders[key][3] = smax([squared_orders[key][3]]+
1947 list(loop_order[1][1]))
1948 except KeyError:
1949 squared_orders[key] = [-1,-1,smax(list(loop_order[1][0])),
1950 smax(list(loop_order[1][1]))]
1951
1952
1953
1954
1955
1956
1957 squared_orders = [(sqso[0],tuple(sqso[1])) for sqso in \
1958 squared_orders.items()]
1959
1960 order_hierarchy = self.get('processes')[0].get('model').get('order_hierarchy')
1961 if set(order_hierarchy.keys()).union(set(split_orders))==\
1962 set(order_hierarchy.keys()):
1963 squared_orders.sort(key= lambda so:
1964 sum([order_hierarchy[split_orders[i]]*order_power for \
1965 i, order_power in enumerate(so[0])]))
1966
1967
1968 self.squared_orders = squared_orders
1969
1970 return squared_orders, amps_orders
1971
1973 """Return the squared_order contributions as returned by the function
1974 get_split_orders_mapping. It uses the cached value self.squared_orders
1975 if it was already defined during a previous call to get_split_orders_mapping.
1976 """
1977
1978 if not hasattr(self, "squared_orders"):
1979 self.get_split_orders_mapping()
1980
1981 return self.squared_orders
1982
1984 """ Find the maximum number of loop couplings appearing in any of the
1985 LoopHelasAmplitude in this LoopHelasMatrixElement"""
1986 if len(self.get_loop_diagrams())==0:
1987 return 0
1988 return max([len(amp.get('coupling')) for amp in \
1989 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[])])
1990
1992 """ Returns the maximum power of loop momentum brought by a loop
1993 interaction. For renormalizable theories, it should be no more than one.
1994 """
1995 return max([lwf.get_analytic_info('interaction_rank') for lwf in \
1996 self.get_all_loop_wavefunctions()])
1997
1999 """ Returns the rank of the contributing loop with maximum rank """
2000 r_list = [lamp.get_analytic_info('wavefunction_rank') for ldiag in \
2001 self.get_loop_diagrams() for lamp in ldiag.get_loop_amplitudes()]
2002 if len(r_list)==0:
2003 return 0
2004 else:
2005 return max(r_list)
2006
2008 """Returns the maximum spin that any particle either connected to a loop
2009 or running in it has, among all the loops contributing to this ME"""
2010
2011
2012
2013
2014
2015 return max(
2016 max(l.get('spin') for l in lamp.get('mothers')+
2017 lamp.get('wavefunctions')+d.get('loop_wavefunctions'))
2018 for d in self['diagrams'] if isinstance(d,LoopHelasDiagram)
2019 for lamp in d.get_loop_amplitudes()
2020 )
2021
2023 """ Returns the spin of the loop particle with maximum spin among all
2024 the loop contributing to this ME"""
2025 return max([lwf.get('spin') for lwf in \
2026 self.get_all_loop_wavefunctions()])
2027
2029 """Give a unique number to each non-equivalent (at the level of the output)
2030 LoopHelasAmplitude """
2031
2032 LoopHelasAmplitudeRecognized=[]
2033 for lamp in \
2034 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]):
2035 lamp.set('number',-1)
2036 for lamp2 in LoopHelasAmplitudeRecognized:
2037 if lamp.is_equivalent(lamp2):
2038
2039
2040 lamp.set('number',lamp2.get('number'))
2041 break;
2042 if lamp.get('number')==-1:
2043 lamp.set('number',(len(LoopHelasAmplitudeRecognized)+1))
2044 LoopHelasAmplitudeRecognized.append(lamp)
2045
2047 """Give a unique number to each LoopHelasAmplitude. These will be the
2048 number used for the LOOPCOEF array in the optimized output and the
2049 grouping is done in a further stage by adding all the LOOPCOEF sharing
2050 the same denominator to a given one using the 'loop_group_id' attribute
2051 of the LoopHelasAmplitudes. """
2052
2053 lamp_number=1
2054 for lamp in \
2055 sum([d.get_loop_amplitudes() for d in self.get_loop_diagrams()],[]):
2056 lamp.set('number',lamp_number)
2057 lamp_number += 1
2058
2060 """ Give the correct number for the default output to the wavefunctions
2061 and amplitudes building the loops """
2062
2063
2064 CT_ampnumber=1
2065 loop_ampnumber=self.get_number_of_CT_amplitudes()+1
2066 loopwfnumber=1
2067
2068 for loopdiag in self.get_loop_diagrams():
2069 for wf in loopdiag.get('wavefunctions'):
2070 wf.set('number',wfnumber)
2071 wfnumber=wfnumber+1
2072 for loopamp in loopdiag.get_loop_amplitudes():
2073 loopwfnumber=1
2074 for loopwf in loopamp['wavefunctions']:
2075 loopwf.set('number',loopwfnumber)
2076 loopwfnumber=loopwfnumber+1
2077 for amp in loopamp['amplitudes']:
2078 amp.set('number',loop_ampnumber)
2079 loop_ampnumber=loop_ampnumber+1
2080 for ctamp in loopdiag.get_ct_amplitudes():
2081 ctamp.set('number',CT_ampnumber)
2082 CT_ampnumber=CT_ampnumber+1
2083
2084 for loopUVCTdiag in self.get_loop_UVCT_diagrams():
2085 for wf in loopUVCTdiag.get('wavefunctions'):
2086 wf.set('number',wfnumber)
2087 wfnumber=wfnumber+1
2088 for amp in loopUVCTdiag.get('amplitudes'):
2089 amp.set('number',CT_ampnumber)
2090 CT_ampnumber=CT_ampnumber+1
2091
2093 """ Give the correct number for the optimized output to the wavefunctions
2094 and amplitudes building the loops """
2095 CT_ampnumber=1
2096 loop_ampnumber=self.get_number_of_CT_amplitudes()+1
2097 loopwfnumber=1
2098
2099 for loopdiag in self.get_loop_diagrams():
2100 for wf in loopdiag.get('wavefunctions'):
2101 wf.set('number',wfnumber)
2102 wfnumber=wfnumber+1
2103 for lwf in loopdiag.get('loop_wavefunctions'):
2104 lwf.set('number',loopwfnumber)
2105 loopwfnumber=loopwfnumber+1
2106 for loopamp in loopdiag.get_loop_amplitudes():
2107
2108
2109 start_loop_wf = loopamp.get_starting_loop_wavefunction()
2110 if start_loop_wf.get('fermionflow')==1:
2111 start_loop_wf.set('number',0)
2112 else:
2113
2114 start_loop_wf.set('number',-1)
2115 for amp in loopamp['amplitudes']:
2116 amp.set('number',loop_ampnumber)
2117 loop_ampnumber=loop_ampnumber+1
2118 for ctamp in loopdiag.get_ct_amplitudes():
2119 ctamp.set('number',CT_ampnumber)
2120 CT_ampnumber=CT_ampnumber+1
2121
2122 for loopUVCTdiag in self.get_loop_UVCT_diagrams():
2123 for wf in loopUVCTdiag.get('wavefunctions'):
2124 wf.set('number',wfnumber)
2125 wfnumber=wfnumber+1
2126 for amp in loopUVCTdiag.get('amplitudes'):
2127 amp.set('number',CT_ampnumber)
2128 CT_ampnumber=CT_ampnumber+1
2129
2131 """After the generation of the helas objects, we can give up on having
2132 a unique number identifying the helas wavefunction and amplitudes and
2133 instead use a labeling which is optimal for the output of the loop process.
2134 Also we tag all the LoopHelasAmplitude which are identical with the same
2135 'number' attribute."""
2136
2137
2138 if self.optimized_output:
2139 self.relabel_loop_amplitudes_optimized()
2140 else:
2141 self.relabel_loop_amplitudes()
2142
2143
2144 wfnumber=1
2145 ampnumber=1
2146 for borndiag in self.get_born_diagrams():
2147 for wf in borndiag.get('wavefunctions'):
2148 wf.set('number',wfnumber)
2149 wfnumber=wfnumber+1
2150 for amp in borndiag.get('amplitudes'):
2151 amp.set('number',ampnumber)
2152 ampnumber=ampnumber+1
2153
2154
2155
2156 if self.optimized_output:
2157 self.relabel_loop_wfs_and_amps_optimized(wfnumber)
2158 for lwf in [lwf for loopdiag in self.get_loop_diagrams() for \
2159 lwf in loopdiag.get('loop_wavefunctions')]:
2160 lwf.set('me_id',lwf.get('number'))
2161 else:
2162 self.relabel_loop_wfs_and_amps(wfnumber)
2163
2164
2165
2166 for wf in self.get_all_wavefunctions():
2167 wf.set('me_id',wf.get('number'))
2168
2169
2171 """Gives the total number of wavefunctions for this ME, including the
2172 loop ones"""
2173
2174 return len(self.get_all_wavefunctions())
2175
2177 """ Gives the total number of loop wavefunctions for this ME."""
2178 return sum([len(ldiag.get('loop_wavefunctions')) for ldiag in \
2179 self.get_loop_diagrams()])
2180
2182 """Gives the total number of wavefunctions for this ME, excluding the
2183 loop ones."""
2184
2185 return sum([ len(d.get('wavefunctions')) for d in self.get('diagrams')])
2186
2188 """Gives a list of all wavefunctions for this ME"""
2189
2190 allwfs=sum([d.get('wavefunctions') for d in self.get('diagrams')], [])
2191 for d in self['diagrams']:
2192 if isinstance(d,LoopHelasDiagram):
2193 for l in d.get_loop_amplitudes():
2194 allwfs += l.get('wavefunctions')
2195
2196 return allwfs
2197
2211
2213 """Gives (number or external particles, number of
2214 incoming particles)"""
2215
2216 external_wfs = [wf for wf in self.get_all_wavefunctions() if not wf.get('mothers') and not wf.get('is_loop')]
2217
2218 return (len(set([wf.get('number_external') for wf in \
2219 external_wfs])),
2220 len(set([wf.get('number_external') for wf in \
2221 [wf for wf in external_wfs if wf.get('leg_state') == False]])))
2222
2224 """Gives the total number of amplitudes for this ME, including the loop
2225 ones."""
2226
2227 return len(self.get_all_amplitudes())
2228
2235
2237 """Gives the total number of amplitudes for this ME, excluding those
2238 inside the loop amplitudes. (So only one is counted per loop amplitude.)
2239 """
2240
2241 return sum([ len(d.get('amplitudes')) for d in \
2242 self.get('diagrams')])
2243
2245 """Gives the total number of helas amplitudes for the loop diagrams of this ME,
2246 excluding those inside the loop amplitudes, but including the CT-terms.
2247 (So only one amplitude is counted per loop amplitude.)
2248 """
2249
2250 return sum([len(d.get('amplitudes')) for d in (self.get_loop_diagrams()+
2251 self.get_loop_UVCT_diagrams())])
2252
2254 """Gives the total number of amplitudes for the born diagrams of this ME
2255 """
2256
2257 return sum([len(d.get('amplitudes')) for d in self.get_born_diagrams()])
2258
2269
2275
2282
2289
2318
2334
2336 """ Returns the list of the helas loop amplitude of type
2337 CALL LOOP_I_J(_K)(...) used for this matrix element """
2338
2339
2340
2341 if self.optimized_output:
2342 last_relevant_index=3
2343 else:
2344 last_relevant_index=4
2345
2346 return list(set([lamp.get_call_key()[1:last_relevant_index] \
2347 for ldiag in self.get_loop_diagrams() for lamp in \
2348 ldiag.get_loop_amplitudes()]))
2349
2359
2369
2371 """ Just to forbid the usage of this generic function in a
2372 LoopHelasMatrixElement"""
2373
2374 raise self.PhysicsObjectError("Usage of get_color_amplitudes is not allowed in a LoopHelasMatrixElement")
2375
2377 """Return a list of (coefficient, amplitude number) lists,
2378 corresponding to the JAMPs for this born color basis and the born
2379 diagrams of this LoopMatrixElement. The coefficients are given in the
2380 format (fermion factor, color coeff (frac), imaginary, Nc power)."""
2381
2382 return super(LoopHelasMatrixElement,self).generate_color_amplitudes(\
2383 self['born_color_basis'],self.get_born_diagrams())
2384
2386 """Return a list of (coefficient, amplitude number) lists,
2387 corresponding to the JAMPs for this loop color basis and the loop
2388 diagrams of this LoopMatrixElement. The coefficients are given in the
2389 format (fermion factor, color coeff (frac), imaginary, Nc power)."""
2390
2391 diagrams=self.get_loop_diagrams()
2392 color_basis=self['loop_color_basis']
2393
2394 if not color_basis:
2395
2396
2397 col_amp = []
2398 for diagram in diagrams:
2399 for amplitude in diagram.get('amplitudes'):
2400 col_amp.append(((amplitude.get('fermionfactor'),
2401 1, False, 0),
2402 amplitude.get('number')))
2403 return [col_amp]
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414 LoopDiagramsHelasAmplitudeList=self.get_helas_amplitudes_loop_diagrams()
2415
2416
2417 for i, helas_amp_list in enumerate(LoopDiagramsHelasAmplitudeList):
2418 new_helas_amp_list=helas_objects.HelasAmplitudeList()
2419 for helas_amp in helas_amp_list:
2420 if isinstance(helas_amp,LoopHelasAmplitude):
2421 new_helas_amp_list.extend(helas_amp['amplitudes'])
2422 else:
2423 new_helas_amp_list.append(helas_amp)
2424 LoopDiagramsHelasAmplitudeList[i]=new_helas_amp_list
2425
2426
2427
2428
2429
2430 col_amp_list = []
2431 for i, col_basis_elem in \
2432 enumerate(sorted(color_basis.keys())):
2433
2434 col_amp = []
2435
2436 for diag_tuple in color_basis[col_basis_elem]:
2437 res_amps = [amp for amp in LoopDiagramsHelasAmplitudeList[diag_tuple[0]] if tuple(amp.get('color_indices')) == diag_tuple[1]]
2438 if not res_amps:
2439 raise self.PhysicsObjectError("""No amplitude found for color structure
2440 %s and color index chain (%s) (diagram %i)""" % \
2441 (col_basis_elem,
2442 str(diag_tuple[1]),
2443 diag_tuple[0]))
2444
2445 for res_amp in res_amps:
2446 col_amp.append(((res_amp.get('fermionfactor'),
2447 diag_tuple[2],
2448 diag_tuple[3],
2449 diag_tuple[4]),
2450 res_amp.get('number')))
2451
2452 col_amp_list.append(col_amp)
2453
2454 return col_amp_list
2455
2457 """ When creating the base_objects.Diagram in get_base_amplitudes(),
2458 each LoopHelasDiagram will lead to one loop_base_objects.LoopDiagram
2459 for its LoopHelasAmplitude and one other for each of its counter-term
2460 (with different interaction id). This function return a list for which
2461 each element is a HelasAmplitudeList corresponding to the HelasAmplitudes
2462 related to a given loop_base_objects.LoopDiagram generated """
2463
2464 amplitudes_loop_diagrams=[]
2465
2466 for diag in self.get_loop_diagrams():
2467
2468 amplitudes_loop_diagrams.append(diag.get_loop_amplitudes())
2469
2470
2471
2472
2473
2474
2475
2476
2477 ctIDs={}
2478 for ctamp in diag.get_ct_amplitudes():
2479 try:
2480 ctIDs[ctamp.get('interaction_id')].append(ctamp)
2481 except KeyError:
2482 ctIDs[ctamp.get('interaction_id')]=\
2483 helas_objects.HelasAmplitudeList([ctamp])
2484
2485
2486 keys=list(ctIDs.keys())
2487 keys.sort()
2488 for key in keys:
2489 amplitudes_loop_diagrams.append(ctIDs[key])
2490
2491 for diag in self.get_loop_UVCT_diagrams():
2492 amplitudes_loop_diagrams.append(diag.get_loop_UVCTamplitudes())
2493
2494 return amplitudes_loop_diagrams
2495
2497 """Generate a loop_diagram_generation.LoopAmplitude from a
2498 LoopHelasMatrixElement. This is used to generate both color
2499 amplitudes and diagram drawing."""
2500
2501
2502
2503
2504 optimization = 1
2505 if len([wf for wf in self.get_all_wavefunctions() if wf.get('number') == 1]) > 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
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
2522
2523
2524 dtype=1
2525 for HelasAmpList in self.get_helas_amplitudes_loop_diagrams():
2526
2527
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
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
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
2574 - def process_color(cls,matrix_element,color_information,compute_loop_nc=False):
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
2587
2588
2589 list_colorize = color_information['list_colorize']
2590 list_color_basis= color_information['list_color_basis']
2591 list_color_matrices =color_information['list_color_matrices']
2592 dict_loopborn_matrices =color_information['dict_loopborn_matrices']
2593
2594
2595
2596
2597 matrix_element.relabel_helas_objects()
2598
2599
2600
2601
2602 new_amp = matrix_element.get_base_amplitude()
2603 matrix_element.set('base_amplitude', new_amp)
2604
2605 loop_col_basis = loop_color_amp.LoopColorBasis(
2606 compute_loop_nc = compute_loop_nc)
2607 loop_colorize_obj = loop_col_basis.create_loop_color_dict_list(\
2608 matrix_element.get('base_amplitude'),
2609 )
2610 list_colorize = []
2611 list_color_basis = []
2612
2613 try:
2614
2615
2616
2617 loop_col_basis_index = list_colorize.index(loop_colorize_obj)
2618 loop_col_basis = list_color_basis[loop_col_basis_index]
2619 except ValueError as error:
2620
2621 list_colorize.append(loop_colorize_obj)
2622 loop_col_basis.build()
2623 loop_col_basis_index = len(list_color_basis)
2624 list_color_basis.append(loop_col_basis)
2625 logger.info(\
2626 "Processing color information for %s" % \
2627 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2628 replace('Process', 'loop process'))
2629 else:
2630 logger.info(\
2631 "Reusing existing color information for %s" % \
2632 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2633 replace('Process', 'loop process'))
2634
2635 if new_amp['process']['has_born']:
2636 born_col_basis = loop_color_amp.LoopColorBasis()
2637 born_colorize_obj = born_col_basis.create_born_color_dict_list(\
2638 matrix_element.get('base_amplitude'))
2639 try:
2640
2641
2642
2643 born_col_basis_index = list_colorize.index(born_colorize_obj)
2644 born_col_basis = list_color_basis[born_col_basis_index]
2645 except ValueError:
2646
2647 list_colorize.append(born_colorize_obj)
2648 born_col_basis.build()
2649 born_col_basis_index = len(list_color_basis)
2650 list_color_basis.append(born_col_basis)
2651 logger.info(\
2652 "Processing color information for %s" % \
2653 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2654 replace('Process', 'born process'))
2655 else:
2656 logger.info(\
2657 "Reusing existing color information for %s" % \
2658 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
2659 replace('Process', 'born process'))
2660 loopborn_matrices_key=(loop_col_basis_index,born_col_basis_index)
2661 else:
2662 loopborn_matrices_key=(loop_col_basis_index,loop_col_basis_index)
2663
2664 dict_loopborn_matrices = {}
2665
2666 try:
2667
2668
2669
2670 col_matrix = dict_loopborn_matrices[loopborn_matrices_key]
2671 except KeyError:
2672
2673 col_matrix = color_amp.ColorMatrix(\
2674 list_color_basis[loopborn_matrices_key[0]],
2675 list_color_basis[loopborn_matrices_key[1]])
2676 dict_loopborn_matrices[loopborn_matrices_key]=col_matrix
2677 logger.info(\
2678 "Creating color matrix %s" % \
2679 matrix_element.get('processes')[0].nice_string().\
2680 replace('Process', 'loop process'))
2681 else:
2682 logger.info(\
2683 "Reusing existing color matrix for %s" % \
2684 matrix_element.get('processes')[0].nice_string().\
2685 replace('Process', 'loop process'))
2686
2687 matrix_element.set('loop_color_basis',loop_col_basis)
2688 if new_amp['process']['has_born']:
2689 matrix_element.set('born_color_basis',born_col_basis)
2690 matrix_element.set('color_matrix',col_matrix)
2691