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