1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Definitions of objects used to generate language-independent Helas
17 calls: HelasWavefunction, HelasAmplitude, HelasDiagram for the
18 generation of wavefunctions and amplitudes, HelasMatrixElement and
19 HelasMultiProcess for generation of complete matrix elements for
20 single and multiple processes; and HelasModel, which is the
21 language-independent base class for the language-specific classes for
22 writing Helas calls, found in the iolibs directory"""
23
24 import array
25 import copy
26 import collections
27 import logging
28 import itertools
29 import math
30
31 import aloha
32
33 import madgraph.core.base_objects as base_objects
34 import madgraph.core.diagram_generation as diagram_generation
35 import madgraph.core.color_amp as color_amp
36 import madgraph.loop.loop_diagram_generation as loop_diagram_generation
37 import madgraph.loop.loop_color_amp as loop_color_amp
38 import madgraph.core.color_algebra as color
39 import madgraph.various.misc as misc
40
41 from madgraph import InvalidCmd, MadGraph5Error
42
43
44
45
46
47 logger = logging.getLogger('madgraph.helas_objects')
225
238
258
265 """DiagramTag daughter class to create canonical order of
266 config. Need to compare leg number, mass, width, and color.
267 Also implement find s- and t-channels from the tag.
268 Warning! The sorting in this tag must be identical to that of
269 IdentifySGConfigTag in diagram_symmetry.py (apart from leg number)
270 to make sure symmetry works!"""
271
272
274 """Get s and t channels from the tag, as two lists of vertices
275 ordered from the outermost s-channel and in/down towards the highest
276 number initial state leg.
277 Algorithm: Start from the final tag. Check for final leg number for
278 all links and move in the direction towards leg 2 (or 1, if 1 and 2
279 are in the same direction).
280 """
281
282 final_leg = min(ninitial, max_final_leg)
283
284
285 done = [l for l in self.tag.links if \
286 l.end_link and l.links[0][1][0] == final_leg]
287 while not done:
288
289 right_num = -1
290 for num, link in enumerate(self.tag.links):
291 if len(link.vertex_id) == 3 and \
292 link.vertex_id[1][-1] == final_leg:
293 right_num = num
294 if right_num == -1:
295
296 for num, link in enumerate(self.tag.links):
297 if len(link.vertex_id) == 3 and \
298 link.vertex_id[1][-1] == 1:
299 right_num = num
300 if right_num == -1:
301
302 raise diagram_generation.DiagramTag.DiagramTagError, \
303 "Error in CanonicalConfigTag, no link with number 1 or 2."
304
305
306 right_link = self.tag.links[right_num]
307
308 new_links = list(self.tag.links[:right_num]) + \
309 list(self.tag.links[right_num + 1:])
310
311 new_link = diagram_generation.DiagramTagChainLink(\
312 new_links,
313 self.flip_vertex(\
314 self.tag.vertex_id,
315 right_link.vertex_id,
316 new_links))
317
318
319 other_links = list(right_link.links) + [new_link]
320 other_link = diagram_generation.DiagramTagChainLink(\
321 other_links,
322 self.flip_vertex(\
323 right_link.vertex_id,
324 self.tag.vertex_id,
325 other_links))
326
327 self.tag = other_link
328 done = [l for l in self.tag.links if \
329 l.end_link and l.links[0][1][0] == final_leg]
330
331
332 diagram = self.diagram_from_tag(model)
333
334
335 schannels = base_objects.VertexList()
336 tchannels = base_objects.VertexList()
337
338 for vert in diagram.get('vertices')[:-1]:
339 if vert.get('legs')[-1].get('number') > ninitial:
340 schannels.append(vert)
341 else:
342 tchannels.append(vert)
343
344
345 lastvertex = diagram.get('vertices')[-1]
346 legs = lastvertex.get('legs')
347 leg2 = [l.get('number') for l in legs].index(final_leg)
348 legs.append(legs.pop(leg2))
349 if ninitial == 2:
350
351 tchannels.append(lastvertex)
352 else:
353 legs[-1].set('id',
354 model.get_particle(legs[-1].get('id')).get_anti_pdg_code())
355 schannels.append(lastvertex)
356
357
358 multischannels = [(i, v) for (i, v) in enumerate(schannels) \
359 if len(v.get('legs')) > 3]
360 multitchannels = [(i, v) for (i, v) in enumerate(tchannels) \
361 if len(v.get('legs')) > 3]
362
363 increase = 0
364 for channel in multischannels + multitchannels:
365 newschannels = []
366 vertex = channel[1]
367 while len(vertex.get('legs')) > 3:
368
369
370 popped_legs = \
371 base_objects.LegList([vertex.get('legs').pop(0) \
372 for i in [0,1]])
373 popped_legs.append(base_objects.Leg({\
374 'id': new_pdg,
375 'number': min([l.get('number') for l in popped_legs]),
376 'state': True,
377 'onshell': None}))
378
379 new_vertex = base_objects.Vertex({
380 'id': vertex.get('id'),
381 'legs': popped_legs})
382
383
384 if channel in multischannels:
385 schannels.insert(channel[0]+increase, new_vertex)
386
387 increase += 1
388 else:
389 schannels.append(new_vertex)
390 legs = vertex.get('legs')
391
392 legs.insert(0, copy.copy(popped_legs[-1]))
393
394 legs[-1].set('number', min([l.get('number') for l in legs[:-1]]))
395
396
397
398 number_dict = {}
399 nprop = 0
400 for vertex in schannels + tchannels:
401
402 legs = vertex.get('legs')[:-1]
403 if vertex in schannels:
404 legs.sort(lambda l1, l2: l2.get('number') - \
405 l1.get('number'))
406 else:
407 legs.sort(lambda l1, l2: l1.get('number') - \
408 l2.get('number'))
409 for ileg,leg in enumerate(legs):
410 newleg = copy.copy(leg)
411 try:
412 newleg.set('number', number_dict[leg.get('number')])
413 except KeyError:
414 pass
415 else:
416 legs[ileg] = newleg
417 nprop = nprop - 1
418 last_leg = copy.copy(vertex.get('legs')[-1])
419 number_dict[last_leg.get('number')] = nprop
420 last_leg.set('number', nprop)
421 legs.append(last_leg)
422 vertex.set('legs', base_objects.LegList(legs))
423
424 return schannels, tchannels
425
426 @staticmethod
428 """Returns the end link for a leg needed to identify configs:
429 ((leg numer, mass, width, color), number)."""
430
431 part = model.get_particle(leg.get('id'))
432
433
434
435 if part.get('color') != 1:
436 charge = 0
437 else:
438 charge = abs(part.get('charge'))
439
440 return [((leg.get('number'), part.get('spin'), part.get('color'), charge,
441 part.get('mass'), part.get('width')),
442 (leg.get('number'),leg.get('id'),leg.get('state')))]
443
444 @staticmethod
446 """Returns the info needed to identify configs:
447 interaction color, mass, width. Also provide propagator PDG code.
448 The third element of the tuple vertex_id serves to store potential
449 necessary information regarding the shrunk loop."""
450
451 if isinstance(vertex,base_objects.ContractedVertex):
452 inter = None
453
454
455
456 loop_info = {'PDGs':vertex.get('PDGs'),
457 'loop_orders':vertex.get('loop_orders')}
458 else:
459
460 inter = model.get_interaction(vertex.get('id'))
461 loop_info = {}
462
463 if last_vertex:
464 return ((0,),
465 (vertex.get('id'),
466 min([l.get('number') for l in vertex.get('legs')])),
467 loop_info)
468 else:
469 part = model.get_particle(vertex.get('legs')[-1].get('id'))
470 return ((part.get('color'),
471 part.get('mass'), part.get('width')),
472 (vertex.get('id'),
473 vertex.get('legs')[-1].get('onshell'),
474 vertex.get('legs')[-1].get('number')),
475 loop_info)
476
477 @staticmethod
479 """Move the wavefunction part of propagator id appropriately"""
480
481
482 min_number = min([l.vertex_id[1][-1] for l in links if not l.end_link]\
483 + [l.links[0][1][0] for l in links if l.end_link])
484
485 if len(new_vertex[0]) == 1 and len(old_vertex[0]) > 1:
486
487 return (old_vertex[0],
488 (new_vertex[1][0], old_vertex[1][1], min_number), new_vertex[2])
489 elif len(new_vertex[0]) > 1 and len(old_vertex[0]) == 1:
490
491 return (old_vertex[0], (new_vertex[1][0], min_number), new_vertex[2])
492
493
494 raise diagram_generation.DiagramTag.DiagramTagError, \
495 "Error in CanonicalConfigTag, wrong setup of vertices in link."
496
497 @staticmethod
499 """Return a leg from a link"""
500
501 if link.end_link:
502
503 leg = base_objects.Leg({'number':link.links[0][1][0],
504 'id':link.links[0][1][1],
505 'state':link.links[0][1][2],
506 'onshell':None})
507 return leg
508
509 assert False
510
511 @classmethod
530
531 @staticmethod
533 """Return the numerical vertex id from a link.vertex_id"""
534
535 return vertex_id[1][0]
536
542 """HelasWavefunction object, has the information necessary for
543 writing a call to a HELAS wavefunction routine: the PDG number,
544 all relevant particle information, a list of mother wavefunctions,
545 interaction id, all relevant interaction information, fermion flow
546 state, wavefunction number
547 """
548
549 supported_analytical_info = ['wavefunction_rank','interaction_rank']
550
551
552 @staticmethod
554 """ Returns the size of a wavefunction (i.e. number of element) carrying
555 a particle with spin 'spin' """
556
557 sizes = {1:1,2:4,3:4,4:16,5:16}
558 try:
559 return sizes[abs(spin)]
560 except KeyError:
561 raise MadGraph5Error, "L-cut particle has spin %d which is not supported."%spin
562
564 """Default values for all properties"""
565
566
567
568
569
570
571
572
573
574
575
576
577 self['particle'] = base_objects.Particle()
578 self['antiparticle'] = base_objects.Particle()
579 self['is_part'] = True
580
581
582
583
584
585
586
587
588 self['interaction_id'] = 0
589 self['pdg_codes'] = []
590 self['orders'] = {}
591 self['inter_color'] = None
592 self['lorentz'] = []
593 self['coupling'] = ['none']
594
595 self['color_key'] = 0
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 self['state'] = 'initial'
611 self['leg_state'] = True
612 self['mothers'] = HelasWavefunctionList()
613 self['number_external'] = 0
614 self['number'] = 0
615 self['me_id'] = 0
616 self['fermionflow'] = 1
617 self['is_loop'] = False
618
619
620
621
622
623
624
625 self['analytic_info'] = {}
626
627
628 self['lcut_size']=None
629
630
631 self['decay'] = False
632
633
634
635
636 self['onshell'] = None
637
638
639 self['conjugate_indices'] = None
640
641
643 """Allow generating a HelasWavefunction from a Leg
644 """
645
646 if len(arguments) > 2:
647 if isinstance(arguments[0], base_objects.Leg) and \
648 isinstance(arguments[1], int) and \
649 isinstance(arguments[2], base_objects.Model):
650 super(HelasWavefunction, self).__init__()
651 leg = arguments[0]
652 interaction_id = arguments[1]
653 model = arguments[2]
654
655
656 decay_ids = []
657 if len(arguments) > 3:
658 decay_ids = arguments[3]
659 self.set('particle', leg.get('id'), model)
660 self.set('number_external', leg.get('number'))
661 self.set('number', leg.get('number'))
662 self.set('is_loop', leg.get('loop_line'))
663 self.set('state', {False: 'initial', True: 'final'}[leg.get('state')])
664 if leg.get('onshell') == False:
665
666 self.set('onshell', leg.get('onshell'))
667 self.set('leg_state', leg.get('state'))
668
669
670
671
672 if self['state'] == 'final' and self.get('pdg_code') in decay_ids:
673 self.set('decay', True)
674
675
676
677
678 if self.is_fermion():
679 if leg.get('state') == False and \
680 self.get('is_part') or \
681 leg.get('state') == True and \
682 not self.get('is_part'):
683 self.set('state', 'incoming')
684 else:
685 self.set('state', 'outgoing')
686 self.set('interaction_id', interaction_id, model)
687 elif arguments:
688 super(HelasWavefunction, self).__init__(arguments[0])
689 else:
690 super(HelasWavefunction, self).__init__()
691
692 - def filter(self, name, value):
693 """Filter for valid wavefunction property values."""
694
695 if name in ['particle', 'antiparticle']:
696 if not isinstance(value, base_objects.Particle):
697 raise self.PhysicsObjectError, \
698 "%s tag %s is not a particle" % (name, repr(value))
699
700 if name == 'is_part':
701 if not isinstance(value, bool):
702 raise self.PhysicsObjectError, \
703 "%s tag %s is not a boolean" % (name, repr(value))
704
705 if name == 'interaction_id':
706 if not isinstance(value, int):
707 raise self.PhysicsObjectError, \
708 "%s is not a valid integer " % str(value) + \
709 " for wavefunction interaction id"
710
711 if name == 'pdg_codes':
712
713 if not isinstance(value, list):
714 raise self.PhysicsObjectError, \
715 "%s is not a valid list of integers" % str(value)
716 for mystr in value:
717 if not isinstance(mystr, int):
718 raise self.PhysicsObjectError, \
719 "%s is not a valid integer" % str(mystr)
720
721 if name == 'orders':
722
723 if not isinstance(value, dict):
724 raise self.PhysicsObjectError, \
725 "%s is not a valid dict for coupling orders" % \
726 str(value)
727 for order in value.keys():
728 if not isinstance(order, str):
729 raise self.PhysicsObjectError, \
730 "%s is not a valid string" % str(order)
731 if not isinstance(value[order], int):
732 raise self.PhysicsObjectError, \
733 "%s is not a valid integer" % str(value[order])
734
735
736 if name == 'inter_color':
737
738 if value and not isinstance(value, color.ColorString):
739 raise self.PhysicsObjectError, \
740 "%s is not a valid Color String" % str(value)
741
742 if name == 'lorentz':
743
744 if not isinstance(value, list):
745 raise self.PhysicsObjectError, \
746 "%s is not a valid list" % str(value)
747 for name in value:
748 if not isinstance(name, str):
749 raise self.PhysicsObjectError, \
750 "%s doesn't contain only string" % str(value)
751
752 if name == 'coupling':
753
754 if not isinstance(value, list):
755 raise self.PhysicsObjectError, \
756 "%s is not a valid coupling string" % str(value)
757 for name in value:
758 if not isinstance(name, str):
759 raise self.PhysicsObjectError, \
760 "%s doesn't contain only string" % str(value)
761 if len(value) == 0:
762 raise self.PhysicsObjectError, \
763 "%s should have at least one value" % str(value)
764
765 if name == 'color_key':
766 if value and not isinstance(value, int):
767 raise self.PhysicsObjectError, \
768 "%s is not a valid integer" % str(value)
769
770 if name == 'state':
771 if not isinstance(value, str):
772 raise self.PhysicsObjectError, \
773 "%s is not a valid string for wavefunction state" % \
774 str(value)
775 if value not in ['incoming', 'outgoing',
776 'intermediate', 'initial', 'final']:
777 raise self.PhysicsObjectError, \
778 "%s is not a valid wavefunction " % str(value) + \
779 "state (incoming|outgoing|intermediate)"
780 if name == 'leg_state':
781 if value not in [False, True]:
782 raise self.PhysicsObjectError, \
783 "%s is not a valid wavefunction " % str(value) + \
784 "state (incoming|outgoing|intermediate)"
785 if name in ['fermionflow']:
786 if not isinstance(value, int):
787 raise self.PhysicsObjectError, \
788 "%s is not a valid integer" % str(value)
789 if not value in [-1, 1]:
790 raise self.PhysicsObjectError, \
791 "%s is not a valid sign (must be -1 or 1)" % str(value)
792
793 if name in ['number_external', 'number']:
794 if not isinstance(value, int):
795 raise self.PhysicsObjectError, \
796 "%s is not a valid integer" % str(value) + \
797 " for wavefunction number"
798
799 if name == 'mothers':
800 if not isinstance(value, HelasWavefunctionList):
801 raise self.PhysicsObjectError, \
802 "%s is not a valid list of mothers for wavefunction" % \
803 str(value)
804
805 if name in ['decay']:
806 if not isinstance(value, bool):
807 raise self.PhysicsObjectError, \
808 "%s is not a valid bool" % str(value) + \
809 " for decay"
810
811 if name in ['onshell']:
812 if not isinstance(value, bool):
813 raise self.PhysicsObjectError, \
814 "%s is not a valid bool" % str(value) + \
815 " for onshell"
816
817 if name in ['is_loop']:
818 if not isinstance(value, bool):
819 raise self.PhysicsObjectError, \
820 "%s is not a valid bool" % str(value) + \
821 " for is_loop"
822
823 if name == 'conjugate_indices':
824 if not isinstance(value, tuple) and value != None:
825 raise self.PhysicsObjectError, \
826 "%s is not a valid tuple" % str(value) + \
827 " for conjugate_indices"
828
829 if name == 'rank':
830 if not isinstance(value, int) and value != None:
831 raise self.PhysicsObjectError, \
832 "%s is not a valid int" % str(value) + \
833 " for the rank"
834
835 if name == 'lcut_size':
836 if not isinstance(value, int) and value != None:
837 raise self.PhysicsObjectError, \
838 "%s is not a valid int" % str(value) + \
839 " for the lcut_size"
840
841 return True
842
843
844 - def get(self, name):
845 """When calling any property related to the particle,
846 automatically call the corresponding property of the particle."""
847
848
849 if name == 'conjugate_indices' and self[name] == None:
850 self['conjugate_indices'] = self.get_conjugate_index()
851
852 if name == 'lcut_size' and self[name] == None:
853 self['lcut_size'] = self.get_lcut_size()
854
855 if name in ['spin', 'mass', 'width', 'self_antipart']:
856 return self['particle'].get(name)
857 elif name == 'pdg_code':
858 return self['particle'].get_pdg_code()
859 elif name == 'color':
860 return self['particle'].get_color()
861 elif name == 'name':
862 return self['particle'].get_name()
863 elif name == 'antiname':
864 return self['particle'].get_anti_name()
865 elif name == 'me_id':
866 out = super(HelasWavefunction, self).get(name)
867 if out:
868 return out
869 else:
870 return super(HelasWavefunction, self).get('number')
871 else:
872 return super(HelasWavefunction, self).get(name)
873
874
875
876 - def set(self, *arguments):
877 """When setting interaction_id, if model is given (in tuple),
878 set all other interaction properties. When setting pdg_code,
879 if model is given, set all other particle properties."""
880
881 assert len(arguments) >1, "Too few arguments for set"
882
883 name = arguments[0]
884 value = arguments[1]
885
886 if len(arguments) > 2 and \
887 isinstance(value, int) and \
888 isinstance(arguments[2], base_objects.Model):
889 model = arguments[2]
890 if name == 'interaction_id':
891 self.set('interaction_id', value)
892 if value > 0:
893 inter = model.get('interaction_dict')[value]
894 self.set('pdg_codes',
895 [part.get_pdg_code() for part in \
896 inter.get('particles')])
897 self.set('orders', inter.get('orders'))
898
899
900 if inter.get('color'):
901 self.set('inter_color', inter.get('color')[0])
902 if inter.get('lorentz'):
903 self.set('lorentz', [inter.get('lorentz')[0]])
904 if inter.get('couplings'):
905 self.set('coupling', [inter.get('couplings').values()[0]])
906 return True
907 elif name == 'particle':
908 self.set('particle', model.get('particle_dict')[value])
909 self.set('is_part', self['particle'].get('is_part'))
910 if self['particle'].get('self_antipart'):
911 self.set('antiparticle', self['particle'])
912 else:
913 self.set('antiparticle', model.get('particle_dict')[-value])
914 return True
915 else:
916 raise self.PhysicsObjectError, \
917 "%s not allowed name for 3-argument set", name
918 else:
919 return super(HelasWavefunction, self).set(name, value)
920
922 """Return particle property names as a nicely sorted list."""
923
924 return ['particle', 'antiparticle', 'is_part',
925 'interaction_id', 'pdg_codes', 'orders', 'inter_color',
926 'lorentz', 'coupling', 'color_key', 'state', 'number_external',
927 'number', 'fermionflow', 'mothers', 'is_loop']
928
929
930
932 """Flip between particle and antiparticle."""
933 part = self.get('particle')
934 self.set('particle', self.get('antiparticle'))
935 self.set('antiparticle', part)
936
938 """ Return True if the particle of this wavefunction is a ghost"""
939 return self.get('particle').get('ghost')
940
942 return self.get('spin') % 2 == 0
943
946
949
951 """ Returns a given analytic information about this loop wavefunction or
952 its characterizing interaction. The list of available information is in
953 the HelasWavefunction class variable 'supported_analytical_info'. An
954 example of analytic information is the 'interaction_rank', corresponding
955 to the power of the loop momentum q brought by the interaction
956 and propagator from which this loop wavefunction originates. This
957 is done in a general way by having aloha analyzing the lorentz structure
958 used.
959 Notice that if one knows that this analytic information has already been
960 computed before (for example because a call to compute_analytic_information
961 has been performed before, then alohaModel is not required since
962 the result can be recycled."""
963
964
965 assert(self.get('is_loop'))
966
967 assert(info in self.supported_analytical_info)
968
969
970 try:
971 return self['analytic_info'][info]
972 except KeyError:
973
974 if alohaModel is None:
975 raise MadGraph5Error,"The analytic information %s has"%info+\
976 " not been computed yet for this wavefunction and an"+\
977 " alohaModel was not specified, so that the information"+\
978 " cannot be retrieved."
979
980 result = None
981
982 if info=="interaction_rank" and len(self['mothers'])==0:
983
984 result = 0
985
986 elif info=="interaction_rank":
987
988
989
990
991
992
993 aloha_info = self.get_aloha_info(True)
994
995
996 max_rank = max([ alohaModel.get_info('rank', lorentz,
997 aloha_info[2], aloha_info[1], cached=True)
998 for lorentz in aloha_info[0] ])
999 result = max_rank
1000
1001 elif info=="wavefunction_rank":
1002
1003
1004 loop_mothers=[wf for wf in self['mothers'] if wf['is_loop']]
1005 if len(loop_mothers)==0:
1006
1007 result = 0
1008 elif len(loop_mothers)==1:
1009 result=loop_mothers[0].get_analytic_info('wavefunction_rank',
1010 alohaModel)
1011 result = result+self.get_analytic_info('interaction_rank',
1012 alohaModel)
1013 else:
1014 raise MadGraph5Error, "A loop wavefunction has more than one loop"+\
1015 " mothers."
1016
1017
1018 self['analytic_info'][info] = result
1019
1020 return result
1021
1029
1031 """Generate an array with the information needed to uniquely
1032 determine if a wavefunction has been used before: interaction
1033 id and mother wavefunction numbers."""
1034
1035
1036 array_rep = array.array('i', [self['interaction_id']])
1037
1038
1039
1040 array_rep.append(self['color_key'])
1041
1042 array_rep.append(int(self['is_loop']))
1043
1044
1045 array_rep.extend([mother['number'] for \
1046 mother in self['mothers']])
1047
1048 return array_rep
1049
1051 """Generate the corresponding pdg_code for an outgoing particle,
1052 taking into account fermion flow, for mother wavefunctions"""
1053
1054 return self.get('pdg_code')
1055
1057 """Generate the corresponding pdg_code for an incoming particle,
1058 taking into account fermion flow, for mother wavefunctions"""
1059
1060 if self.get('self_antipart'):
1061
1062 return self.get('pdg_code')
1063
1064 return - self.get('pdg_code')
1065
1067 """Check if we need to add a minus sign due to non-identical
1068 bosons in HVS type couplings"""
1069
1070 inter = model.get('interaction_dict')[self.get('interaction_id')]
1071 if [p.get('spin') for p in \
1072 inter.get('particles')] == [3, 1, 1]:
1073 particles = inter.get('particles')
1074
1075 if particles[1].get_pdg_code() != particles[2].get_pdg_code() \
1076 and self.get('pdg_code') == \
1077 particles[1].get_anti_pdg_code()\
1078 and not self.get('coupling')[0].startswith('-'):
1079
1080 self.set('coupling', ['-%s'%c for c in self.get('coupling')])
1081
1083 """For octet Majorana fermions, need an extra minus sign in
1084 the FVI (and FSI?) wavefunction in UFO models."""
1085
1086
1087
1088 if self.get('color') == 8 and \
1089 self.get_spin_state_number() == -2 and \
1090 self.get('self_antipart') and \
1091 [m.get('color') for m in self.get('mothers')] == [8, 8] and \
1092 not self.get('coupling')[0].startswith('-'):
1093 self.set('coupling', ['-%s' % c for c in self.get('coupling')])
1094
1095 - def set_state_and_particle(self, model):
1096 """Set incoming/outgoing state according to mother states and
1097 Lorentz structure of the interaction, and set PDG code
1098 according to the particles in the interaction"""
1099
1100 assert isinstance(model, base_objects.Model), \
1101 "%s is not a valid model for call to set_state_and_particle" \
1102 % repr(model)
1103
1104
1105
1106 if len(filter(lambda mother: mother.get('leg_state') == False,
1107 self.get('mothers'))) == 1:
1108 leg_state = False
1109 else:
1110 leg_state = True
1111 self.set('leg_state', leg_state)
1112
1113
1114 if self.is_boson():
1115
1116 self.set('state', 'intermediate')
1117 else:
1118
1119
1120 mother = self.find_mother_fermion()
1121
1122 if self.get('self_antipart'):
1123 self.set('state', mother.get_with_flow('state'))
1124 self.set('is_part', mother.get_with_flow('is_part'))
1125 else:
1126 self.set('state', mother.get('state'))
1127 self.set('fermionflow', mother.get('fermionflow'))
1128
1129 if self.get('is_part') and self.get('state') == 'incoming' or \
1130 not self.get('is_part') and self.get('state') == 'outgoing':
1131 self.set('state', {'incoming':'outgoing',
1132 'outgoing':'incoming'}[self.get('state')])
1133 self.set('fermionflow', -self.get('fermionflow'))
1134 return True
1135
1136 - def check_and_fix_fermion_flow(self,
1137 wavefunctions,
1138 diagram_wavefunctions,
1139 external_wavefunctions,
1140 wf_number):
1141 """Check for clashing fermion flow (N(incoming) !=
1142 N(outgoing)) in mothers. This can happen when there is a
1143 Majorana particle in the diagram, which can flip the fermion
1144 flow. This is detected either by a wavefunctions or an
1145 amplitude, with 2 fermion mothers with same state.
1146
1147 In this case, we need to follow the fermion lines of the
1148 mother wavefunctions until we find the outermost Majorana
1149 fermion. For all fermions along the line up to (but not
1150 including) the Majorana fermion, we need to flip incoming <->
1151 outgoing and particle id. For all fermions after the Majorana
1152 fermion, we need to flip the fermionflow property (1 <-> -1).
1153
1154 The reason for this is that in the Helas calls, we need to
1155 keep track of where the actual fermion flow clash happens
1156 (i.e., at the outermost Majorana), as well as having the
1157 correct fermion flow for all particles along the fermion line.
1158
1159 This is done by the mothers using
1160 HelasWavefunctionList.check_and_fix_fermion_flow, which in
1161 turn calls the recursive function
1162 check_majorana_and_flip_flow to trace the fermion lines.
1163 """
1164
1165
1166
1167
1168 self.set('mothers', self.get('mothers').sort_by_pdg_codes(\
1169 self.get('pdg_codes'), self.get_anti_pdg_code())[0])
1170
1171 wf_number = self.get('mothers').\
1172 check_and_fix_fermion_flow(wavefunctions,
1173 diagram_wavefunctions,
1174 external_wavefunctions,
1175 self,
1176 wf_number)
1177
1178 return self, wf_number
1179
1180 - def check_majorana_and_flip_flow(self, found_majorana,
1181 wavefunctions,
1182 diagram_wavefunctions,
1183 external_wavefunctions,
1184 wf_number, force_flip_flow=False,
1185 number_to_wavefunctions=[]):
1186 """Recursive function. Check for Majorana fermion. If found,
1187 continue down to external leg, then flip all the fermion flows
1188 on the way back up, in the correct way:
1189 Only flip fermionflow after the last Majorana fermion; for
1190 wavefunctions before the last Majorana fermion, instead flip
1191 particle identities and state. Return the new (or old)
1192 wavefunction, and the present wavefunction number.
1193
1194 Arguments:
1195 found_majorana: boolean
1196 wavefunctions: HelasWavefunctionList with previously
1197 defined wavefunctions
1198 diagram_wavefunctions: HelasWavefunctionList with the wavefunctions
1199 already defined in this diagram
1200 external_wavefunctions: dictionary from legnumber to external wf
1201 wf_number: The present wavefunction number
1202 """
1203
1204 if not found_majorana:
1205 found_majorana = self.get('self_antipart')
1206
1207 new_wf = self
1208 flip_flow = False
1209 flip_sign = False
1210
1211
1212 mothers = copy.copy(self.get('mothers'))
1213 if not mothers:
1214 if force_flip_flow:
1215 flip_flow = True
1216 elif not self.get('self_antipart'):
1217 flip_flow = found_majorana
1218 else:
1219 flip_sign = found_majorana
1220 else:
1221
1222 fermion_mother = self.find_mother_fermion()
1223
1224 if fermion_mother.get_with_flow('state') != \
1225 self.get_with_flow('state'):
1226 new_mother = fermion_mother
1227 else:
1228
1229 new_mother, wf_number = fermion_mother.\
1230 check_majorana_and_flip_flow(\
1231 found_majorana,
1232 wavefunctions,
1233 diagram_wavefunctions,
1234 external_wavefunctions,
1235 wf_number,
1236 force_flip_flow)
1237
1238
1239
1240
1241
1242 flip_sign = new_mother.get_with_flow('state') != \
1243 self.get_with_flow('state') and \
1244 self.get('self_antipart')
1245 flip_flow = new_mother.get_with_flow('state') != \
1246 self.get_with_flow('state') and \
1247 not self.get('self_antipart')
1248
1249
1250 mothers[mothers.index(fermion_mother)] = new_mother
1251
1252
1253 if flip_flow or flip_sign:
1254 if self in wavefunctions:
1255
1256
1257 new_wf = copy.copy(self)
1258
1259 wf_number = wf_number + 1
1260 new_wf.set('number', wf_number)
1261 try:
1262
1263
1264 old_wf_index = diagram_wavefunctions.index(self)
1265 old_wf = diagram_wavefunctions[old_wf_index]
1266 if self.get('number') == old_wf.get('number'):
1267
1268
1269 wf_number -= 1
1270 new_wf.set('number', old_wf.get('number'))
1271 diagram_wavefunctions[old_wf_index] = new_wf
1272 except ValueError:
1273
1274
1275 if len(self['mothers']) == 0:
1276
1277 if diagram_wavefunctions:
1278 wf_nb = diagram_wavefunctions[0].get('number')
1279 for w in diagram_wavefunctions:
1280 w.set('number', w.get('number') + 1)
1281 new_wf.set('number', wf_nb)
1282 diagram_wavefunctions.insert(0, new_wf)
1283 else:
1284 diagram_wavefunctions.insert(0, new_wf)
1285 else:
1286 for i, wf in enumerate(diagram_wavefunctions):
1287 if self in wf.get('mothers'):
1288
1289 new_wf.set('number', wf.get('number'))
1290 for w in diagram_wavefunctions[i:]:
1291 w.set('number', w.get('number') + 1)
1292
1293 diagram_wavefunctions.insert(i, new_wf)
1294 break
1295 else:
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306 max_mother_index = max([-1]+
1307 [diagram_wavefunctions.index(wf) for wf in
1308 mothers if wf in diagram_wavefunctions])
1309
1310
1311
1312
1313
1314
1315 if max_mother_index<len(diagram_wavefunctions)-1:
1316 new_wf.set('number',diagram_wavefunctions[
1317 max_mother_index+1].get('number'))
1318 for wf in diagram_wavefunctions[max_mother_index+1:]:
1319 wf.set('number',wf.get('number')+1)
1320 diagram_wavefunctions.insert(max_mother_index+1,
1321 new_wf)
1322
1323
1324 new_wf.set('mothers', mothers)
1325
1326
1327 if flip_flow:
1328
1329 new_wf.set('fermionflow', -new_wf.get('fermionflow'))
1330
1331 if flip_sign:
1332
1333
1334 new_wf.set('state', filter(lambda state: \
1335 state != new_wf.get('state'),
1336 ['incoming', 'outgoing'])[0])
1337 new_wf.set('is_part', not new_wf.get('is_part'))
1338 try:
1339
1340
1341 new_wf_number = new_wf.get('number')
1342 new_wf = wavefunctions[wavefunctions.index(new_wf)]
1343 diagram_wf_numbers = [w.get('number') for w in \
1344 diagram_wavefunctions]
1345 index = diagram_wf_numbers.index(new_wf_number)
1346 diagram_wavefunctions.pop(index)
1347
1348
1349 for wf in diagram_wavefunctions:
1350 if wf.get('number') > new_wf_number:
1351 wf.set('number', wf.get('number') - 1)
1352
1353 wf_number = wf_number - 1
1354
1355
1356
1357 for n_to_wf_dict in number_to_wavefunctions:
1358 if new_wf in n_to_wf_dict.values():
1359 for key in n_to_wf_dict.keys():
1360 if n_to_wf_dict[key] == new_wf:
1361 n_to_wf_dict[key] = new_wf
1362
1363 if self.get('is_loop'):
1364
1365
1366
1367
1368
1369
1370 for wf in diagram_wavefunctions:
1371 for i,mother_wf in enumerate(wf.get('mothers')):
1372 if mother_wf.get('number')==new_wf_number:
1373 wf.get('mothers')[i]=new_wf
1374
1375 except ValueError:
1376 pass
1377
1378
1379
1380 return new_wf, wf_number
1381
1383 """Recursive function to get a list of fermion numbers
1384 corresponding to the order of fermions along fermion lines
1385 connected to this wavefunction, in the form [n1,n2,...] for a
1386 boson, and [N,[n1,n2,...]] for a fermion line"""
1387
1388
1389 if not self.get('mothers'):
1390 if self.is_fermion():
1391 return [self.get('number_external'), []]
1392 else:
1393 return []
1394
1395
1396 fermion_mother = None
1397 if self.is_fermion():
1398 fermion_mother = self.find_mother_fermion()
1399
1400 other_fermions = [wf for wf in self.get('mothers') if \
1401 wf.is_fermion() and wf != fermion_mother]
1402
1403
1404 bosons = filter(lambda wf: wf.is_boson(), self.get('mothers'))
1405
1406 fermion_number_list = []
1407
1408 if self.is_fermion():
1409
1410
1411 mother_list = fermion_mother.get_fermion_order()
1412 fermion_number_list.extend(mother_list[1])
1413
1414
1415
1416 fermion_numbers = [f.get_fermion_order() for f in other_fermions]
1417 for iferm in range(0, len(fermion_numbers), 2):
1418 fermion_number_list.append(fermion_numbers[iferm][0])
1419 fermion_number_list.append(fermion_numbers[iferm+1][0])
1420 fermion_number_list.extend(fermion_numbers[iferm][1])
1421 fermion_number_list.extend(fermion_numbers[iferm+1][1])
1422
1423 for boson in bosons:
1424
1425 fermion_number_list.extend(boson.get_fermion_order())
1426
1427 if self.is_fermion():
1428 return [mother_list[0], fermion_number_list]
1429
1430 return fermion_number_list
1431
1433 """Returns true if any of the mothers have negative
1434 fermionflow"""
1435
1436 return self.get('conjugate_indices') != ()
1437
1439 """Generate the is_part and state needed for writing out
1440 wavefunctions, taking into account the fermion flow"""
1441
1442 if self.get('fermionflow') > 0:
1443
1444 return self.get(name)
1445
1446
1447 if name == 'is_part':
1448 return not self.get('is_part')
1449 if name == 'state':
1450 return filter(lambda state: state != self.get('state'),
1451 ['incoming', 'outgoing'])[0]
1452 return self.get(name)
1453
1455 """ Returns a dictionary for formatting this external wavefunction
1456 helas call """
1457
1458 if self['mothers']:
1459 raise MadGraph5Error, "This function should be called only for"+\
1460 " external wavefunctions."
1461 return_dict = {}
1462 if self.get('is_loop'):
1463 return_dict['conjugate'] = ('C' if self.needs_hermitian_conjugate() \
1464 else '')
1465 return_dict['lcutspinletter'] = self.get_lcutspinletter()
1466 return_dict['number'] = self.get('number')
1467 return_dict['me_id'] = self.get('me_id')
1468 return_dict['number_external'] = self.get('number_external')
1469 return_dict['mass'] = self.get('mass')
1470 if self.is_boson():
1471 return_dict['state_id'] = (-1) ** (self.get('state') == 'initial')
1472 else:
1473 return_dict['state_id'] = -(-1) ** self.get_with_flow('is_part')
1474 return_dict['number_external'] = self.get('number_external')
1475
1476 return return_dict
1477
1480 """ return a dictionary to be used for formatting
1481 HELAS call. The argument index sets the flipping while optimized output
1482 changes the wavefunction specification in the arguments."""
1483
1484 if index == 1:
1485 flip = 0
1486 else:
1487 flip = 1
1488
1489 output = {}
1490 if self.get('is_loop') and OptimizedOutput:
1491 output['vertex_rank']=self.get_analytic_info('interaction_rank')
1492 output['lcut_size']=self.get('lcut_size')
1493 output['out_size']=self.spin_to_size(self.get('spin'))
1494
1495 loop_mother_found=False
1496 for ind, mother in enumerate(self.get('mothers')):
1497
1498
1499
1500
1501
1502
1503 if OptimizedOutput and self.get('is_loop'):
1504 if mother.get('is_loop'):
1505 i=0
1506 else:
1507 if loop_mother_found:
1508 i=ind
1509 else:
1510 i=ind+1
1511 else:
1512 i=ind
1513
1514 nb = mother.get('me_id') - flip
1515 output[str(i)] = nb
1516 if not OptimizedOutput:
1517 if mother.get('is_loop'):
1518 output['WF%d'%i] = 'L(1,%d)'%nb
1519 else:
1520 output['WF%d'%i] = '(1,WE(%d)'%nb
1521 else:
1522 if mother.get('is_loop'):
1523 output['loop_mother_number']=nb
1524 output['loop_mother_rank']=\
1525 mother.get_analytic_info('wavefunction_rank')
1526 output['in_size']=self.spin_to_size(mother.get('spin'))
1527 output['WF%d'%i] = 'PL(0,%d)'%nb
1528 loop_mother_found=True
1529 else:
1530 output['WF%d'%i] = 'W(1,%d'%nb
1531 if not mother.get('is_loop'):
1532 if specifyHel:
1533 output['WF%d'%i]=output['WF%d'%i]+',H)'
1534 else:
1535 output['WF%d'%i]=output['WF%d'%i]+')'
1536
1537
1538 for i, coup in enumerate(self.get_with_flow('coupling')):
1539
1540
1541
1542
1543 if not OptimizedOutput and self.get('is_loop'):
1544 output['coup%d'%i] = coup[1:] if coup.startswith('-') else coup
1545 else:
1546 output['coup%d'%i] = coup
1547
1548 output['out'] = self.get('me_id') - flip
1549 output['M'] = self.get('mass')
1550 output['W'] = self.get('width')
1551 output['propa'] = self.get('particle').get('propagator')
1552 if output['propa'] not in ['', None]:
1553 output['propa'] = 'P%s' % output['propa']
1554
1555 if aloha.complex_mass:
1556 if (self.get('width') == 'ZERO' or self.get('mass') == 'ZERO'):
1557
1558 output['CM'] = '%s' % self.get('mass')
1559 else:
1560 output['CM'] ='CMASS_%s' % self.get('mass')
1561 output.update(opt)
1562 return output
1563
1565 """Returns the number corresponding to the spin state, with a
1566 minus sign for incoming fermions. For flip=True, this
1567 spin_state_number is suited for find the index in the interaction
1568 of a MOTHER wavefunction. """
1569
1570 state_number = {'incoming':-1 if not flip else 1,
1571 'outgoing': 1 if not flip else -1,
1572 'intermediate': 1, 'initial': 1, 'final': 1}
1573 return self.get('fermionflow') * \
1574 state_number[self.get('state')] * \
1575 self.get('spin')
1576
1588
1598
1600 """ Find the place in the interaction list of the given particle with
1601 pdg 'pdg_code' and spin 'spin_stat'. For interactions with several identical particles (or
1602 fermion pairs) the outgoing index is always the first occurence.
1603 """
1604 wf_indices = self.get('pdg_codes')
1605 wf_index = wf_indices.index(pdg_code)
1606
1607
1608 if spin_state % 2 == 0:
1609 if wf_index % 2 == 0 and spin_state < 0:
1610
1611 wf_index += 1
1612 elif wf_index % 2 == 1 and spin_state > 0:
1613
1614 wf_index -= 1
1615 return wf_index + 1
1616
1640
1642 """Recursive method to get a base_objects.VertexList
1643 corresponding to this wavefunction and its mothers."""
1644
1645 vertices = base_objects.VertexList()
1646
1647 mothers = self.get('mothers')
1648
1649 if not mothers:
1650 return vertices
1651
1652
1653 for mother in mothers:
1654
1655 vertices.extend(mother.get_base_vertices(\
1656 wf_dict, vx_list,optimization))
1657
1658 vertex = self.get_base_vertex(wf_dict, vx_list, optimization)
1659
1660 try:
1661 index = vx_list.index(vertex)
1662 vertex = vx_list[index]
1663 except ValueError:
1664 pass
1665
1666 vertices.append(vertex)
1667
1668 return vertices
1669
1671 """Get a base_objects.Vertex corresponding to this
1672 wavefunction."""
1673
1674
1675 legs = base_objects.LegList()
1676
1677
1678
1679
1680 try:
1681 if self.get('is_loop'):
1682
1683 raise KeyError
1684 lastleg = wf_dict[(self.get('number'),self.get('onshell'))]
1685 except KeyError:
1686 lastleg = base_objects.Leg({
1687 'id': self.get_pdg_code(),
1688 'number': self.get('number_external'),
1689 'state': self.get('leg_state'),
1690 'onshell': self.get('onshell'),
1691 'loop_line':self.get('is_loop')
1692 })
1693
1694 if optimization != 0 and not self.get('is_loop'):
1695 wf_dict[(self.get('number'),self.get('onshell'))] = lastleg
1696
1697 for mother in self.get('mothers'):
1698 try:
1699 if mother.get('is_loop'):
1700
1701 raise KeyError
1702 leg = wf_dict[(mother.get('number'),False)]
1703 except KeyError:
1704 leg = base_objects.Leg({
1705 'id': mother.get_pdg_code(),
1706 'number': mother.get('number_external'),
1707 'state': mother.get('leg_state'),
1708 'onshell': None,
1709 'loop_line':mother.get('is_loop'),
1710 'onshell': None
1711 })
1712 if optimization != 0 and not mother.get('is_loop'):
1713 wf_dict[(mother.get('number'),False)] = leg
1714 legs.append(leg)
1715
1716 legs.append(lastleg)
1717
1718 vertex = base_objects.Vertex({
1719 'id': self.get('interaction_id'),
1720 'legs': legs})
1721
1722 return vertex
1723
1725 """Recursive method to get the color indices corresponding to
1726 this wavefunction and its mothers."""
1727
1728 if not self.get('mothers'):
1729 return []
1730
1731 color_indices = []
1732
1733
1734 for mother in self.get('mothers'):
1735
1736 color_indices.extend(mother.get_color_indices())
1737
1738 color_indices.append(self.get('color_key'))
1739
1740 return color_indices
1741
1743 """Returns the tuple (lorentz_name, tag, outgoing_number) providing
1744 the necessary information to compute_subset of create_aloha to write
1745 out the HELAS-like routines."""
1746
1747
1748
1749 if self.get('interaction_id') in [0,-1]:
1750 return None
1751
1752 tags = ['C%s' % w for w in self.get_conjugate_index()]
1753 if self.get('is_loop'):
1754 if not optimized_output:
1755 tags.append('L')
1756 else:
1757 tags.append('L%d'%self.get_loop_index())
1758
1759 if self.get('particle').get('propagator') not in ['', None]:
1760 tags.append('P%s' % str(self.get('particle').get('propagator')))
1761
1762 return (tuple(self.get('lorentz')),tuple(tags),self.find_outgoing_number())
1763
1765 """Returns S,V or F depending on the spin of the mother loop particle.
1766 Return '' otherwise."""
1767
1768 if self['is_loop'] and not self.get('mothers'):
1769 if self.get('spin') == 1:
1770 if self.get('particle').get('is_part'):
1771 return 'S'
1772 else:
1773 return 'AS'
1774 if self.get('spin') == 2:
1775 if self.get('particle').get('is_part'):
1776 return 'F'
1777 else:
1778 return 'AF'
1779 if self.get('spin') == 3:
1780 return 'V'
1781 else:
1782 raise MadGraph5Error,'L-cut particle type not supported'
1783 else:
1784 return ''
1785
1787 """Returns two lists of vertices corresponding to the s- and
1788 t-channels that can be traced from this wavefunction, ordered
1789 from the outermost s-channel and in/down towards the highest
1790 (if not reverse_t_ch) or lowest (if reverse_t_ch) number initial
1791 state leg. mother_leg corresponds to self but with
1792 correct leg number = min(final state mothers)."""
1793
1794 schannels = base_objects.VertexList()
1795 tchannels = base_objects.VertexList()
1796
1797 mother_leg = copy.copy(mother_leg)
1798
1799 (startleg, finalleg) = (1,2)
1800 if reverse_t_ch: (startleg, finalleg) = (2,1)
1801
1802
1803 final_mothers = filter(lambda wf: wf.get('number_external') > ninitial,
1804 self.get('mothers'))
1805
1806 for mother in final_mothers:
1807 schannels.extend(mother.get_base_vertices({}, optimization = 0))
1808
1809
1810 init_mothers = filter(lambda wf: wf.get('number_external') <= ninitial,
1811 self.get('mothers'))
1812
1813 assert len(init_mothers) < 3 , \
1814 "get_s_and_t_channels can only handle up to 2 initial states"
1815
1816 if len(init_mothers) == 1:
1817
1818
1819
1820 legs = base_objects.LegList()
1821 mothers = final_mothers + init_mothers
1822
1823 for mother in mothers:
1824 legs.append(base_objects.Leg({
1825 'id': mother.get_pdg_code(),
1826 'number': mother.get('number_external'),
1827 'state': mother.get('leg_state'),
1828 'onshell': mother.get('onshell')
1829 }))
1830
1831 if init_mothers[0].get('number_external') == startleg and \
1832 not init_mothers[0].get('leg_state') and ninitial > 1:
1833
1834
1835 legs.append(mother_leg)
1836 else:
1837
1838
1839
1840 legs.insert(-1, mother_leg)
1841
1842 legs[-1].set('id', init_mothers[0].get_anti_pdg_code())
1843
1844
1845 legs[-1].set('number', min([l.get('number') for l in legs[:-1]]))
1846
1847 vertex = base_objects.Vertex({
1848 'id': self.get('interaction_id'),
1849 'legs': legs})
1850
1851
1852 new_mother_leg = legs[-1]
1853 if init_mothers[0].get('number_external') == startleg and \
1854 not init_mothers[0].get('leg_state') and \
1855 ninitial > 1:
1856
1857
1858 new_mother_leg = legs[-2]
1859
1860 mother_s, tchannels = \
1861 init_mothers[0].get_s_and_t_channels(ninitial,
1862 new_mother_leg,
1863 reverse_t_ch)
1864 if ninitial == 1 or init_mothers[0].get('leg_state') == True:
1865
1866 schannels.append(vertex)
1867 elif init_mothers[0].get('number_external') == startleg:
1868
1869
1870 tchannels.append(vertex)
1871 else:
1872
1873
1874 tchannels.insert(0, vertex)
1875
1876 schannels.extend(mother_s)
1877
1878 elif len(init_mothers) == 2:
1879
1880
1881
1882 init_mothers1 = filter(lambda wf: wf.get('number_external') == \
1883 startleg,
1884 init_mothers)[0]
1885 init_mothers2 = filter(lambda wf: wf.get('number_external') == \
1886 finalleg,
1887 init_mothers)[0]
1888
1889
1890 legs = base_objects.LegList()
1891 for mother in final_mothers + [init_mothers1, init_mothers2]:
1892 legs.append(base_objects.Leg({
1893 'id': mother.get_pdg_code(),
1894 'number': mother.get('number_external'),
1895 'state': mother.get('leg_state'),
1896 'onshell': mother.get('onshell')
1897 }))
1898 legs.insert(0, mother_leg)
1899
1900
1901 legs[-1].set('number', min([l.get('number') for l in legs[:-1]]))
1902
1903 vertex = base_objects.Vertex({
1904 'id': self.get('interaction_id'),
1905 'legs': legs})
1906
1907
1908 mother_s, tchannels = \
1909 init_mothers1.get_s_and_t_channels(ninitial, legs[-2],
1910 reverse_t_ch)
1911 schannels.extend(mother_s)
1912
1913
1914 tchannels.append(vertex)
1915
1916
1917 mother_s, mother_t = \
1918 init_mothers2.get_s_and_t_channels(ninitial, legs[-1],
1919 reverse_t_ch)
1920 schannels.extend(mother_s)
1921 tchannels.extend(mother_t)
1922
1923
1924 schannels.sort(lambda x1,x2: x2.get('legs')[-1].get('number') - \
1925 x1.get('legs')[-1].get('number'))
1926
1927 return schannels, tchannels
1928
1930 """ Return a set containing the ids of all the non-loop outter-most
1931 external legs attached to the loop at the interaction point of this
1932 loop wavefunction """
1933
1934 if not self.get('mothers'):
1935 return set([self.get('number_external'),])
1936
1937 res=set([])
1938 for wf in self.get('mothers'):
1939 if not wf['is_loop']:
1940 res=res.union(wf.get_struct_external_leg_ids())
1941 return res
1942
1944 """Return the index of the wavefunction in the mothers which is the
1945 loop one"""
1946
1947 if not self.get('mothers'):
1948 return 0
1949
1950 try:
1951 loop_wf_index=\
1952 [wf['is_loop'] for wf in self.get('mothers')].index(True)
1953 except ValueError:
1954 raise MadGraph5Error, "The loop wavefunctions should have exactly"+\
1955 " one loop wavefunction mother."
1956
1957 if self.find_outgoing_number()-1<=loop_wf_index:
1958
1959
1960
1961 return loop_wf_index+2
1962 else:
1963
1964
1965 return loop_wf_index+1
1966
1968 """ Return the size (i.e number of elements) of the L-Cut wavefunction
1969 this loop wavefunction originates from. """
1970
1971 if not self['is_loop']:
1972 return 0
1973
1974
1975
1976
1977 last_loop_wf=self
1978 last_loop_wf_loop_mother=last_loop_wf.get_loop_mother()
1979 while last_loop_wf_loop_mother:
1980 last_loop_wf=last_loop_wf_loop_mother
1981 last_loop_wf_loop_mother=last_loop_wf_loop_mother.get_loop_mother()
1982
1983
1984 return self.spin_to_size(last_loop_wf.get('spin'))
1985
1987 """ Return the mother of type 'loop', if any. """
1988
1989 if not self.get('mothers'):
1990 return None
1991 loop_wfs=[wf for wf in self.get('mothers') if wf['is_loop']]
1992 if loop_wfs:
1993 if len(loop_wfs)==1:
1994 return loop_wfs[0]
1995 else:
1996 raise MadGraph5Error, "The loop wavefunction must have either"+\
1997 " no mothers, or exactly one mother with type 'loop'."
1998 else:
1999 return None
2000
2002 """Return the index of the particle that should be conjugated."""
2003
2004 if not any([(wf.get('fermionflow') < 0 or wf.is_majorana()) for wf in \
2005 self.get('mothers')]) and \
2006 (not self.get('interaction_id') or \
2007 self.get('fermionflow') >= 0):
2008 return ()
2009
2010
2011 mothers, self_index = \
2012 self.get('mothers').sort_by_pdg_codes(self.get('pdg_codes'),
2013 self.get_anti_pdg_code())
2014 fermions = HelasWavefunctionList([wf for wf in mothers if wf.is_fermion()])
2015
2016
2017 if self.is_fermion():
2018 me = copy.copy(self)
2019
2020
2021 me.set('state', [state for state in ['incoming', 'outgoing'] \
2022 if state != me.get('state')][0])
2023 fermions.insert(self_index, me)
2024
2025
2026 indices = fermions.majorana_conjugates()
2027
2028
2029 for i in range(0,len(fermions), 2):
2030 if fermions[i].get('fermionflow') < 0 or \
2031 fermions[i+1].get('fermionflow') < 0:
2032 indices.append(i/2 + 1)
2033
2034 return tuple(sorted(indices))
2035
2039 """Get a list of the number of legs in vertices in this diagram"""
2040
2041 if not self.get('mothers'):
2042 return []
2043
2044 if max_n_loop == 0:
2045 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling
2046
2047 vertex_leg_numbers = [len(self.get('mothers')) + 1] if \
2048 (self.get('interaction_id') not in veto_inter_id) or\
2049 (self.get('interaction_id')==-2 and len(self.get('mothers'))+1 >
2050 max_n_loop) else []
2051 for mother in self.get('mothers'):
2052 vertex_leg_numbers.extend(mother.get_vertex_leg_numbers(
2053 veto_inter_id = veto_inter_id))
2054
2055 return vertex_leg_numbers
2056
2057
2058
2060 """Overloading the equality operator, to make comparison easy
2061 when checking if wavefunction is already written, or when
2062 checking for identical processes. Note that the number for
2063 this wavefunction, the pdg code, and the interaction id are
2064 irrelevant, while the numbers for the mothers are important.
2065 """
2066
2067 if not isinstance(other, HelasWavefunction):
2068 return False
2069
2070
2071 if self['number_external'] != other['number_external'] or \
2072 self['fermionflow'] != other['fermionflow'] or \
2073 self['color_key'] != other['color_key'] or \
2074 self['lorentz'] != other['lorentz'] or \
2075 self['coupling'] != other['coupling'] or \
2076 self['state'] != other['state'] or \
2077 self['onshell'] != other['onshell'] or \
2078 self.get('spin') != other.get('spin') or \
2079 self.get('self_antipart') != other.get('self_antipart') or \
2080 self.get('mass') != other.get('mass') or \
2081 self.get('width') != other.get('width') or \
2082 self.get('color') != other.get('color') or \
2083 self['decay'] != other['decay'] or \
2084 self['decay'] and self['particle'] != other['particle']:
2085 return False
2086
2087
2088 return sorted([mother['number'] for mother in self['mothers']]) == \
2089 sorted([mother['number'] for mother in other['mothers']])
2090
2092 """Overloading the nonequality operator, to make comparison easy"""
2093 return not self.__eq__(other)
2094
2095
2096
2097
2098
2100 """ Returns the power of the loop momentum q brought by the interaction
2101 and propagator from which this loop wavefunction originates. This
2102 is done in a SM ad-hoc way, but it should be promoted to be general in
2103 the future, by reading the lorentz structure of the interaction.
2104 This function is now rendered obsolete by the use of the function
2105 get_analytical_info. It is however kept for legacy."""
2106 rank=0
2107
2108
2109
2110 if self.get('spin')==2:
2111 rank=rank+1
2112
2113
2114 spin_cols = [(self.get('spin'),abs(self.get('color')))]+\
2115 [(w.get('spin'),abs(w.get('color'))) for w in self.get('mothers')]
2116
2117 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8)]):
2118 return rank+2
2119
2120 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8),(3,8)]):
2121 return rank+1
2122
2123 if sorted(spin_cols) == sorted([(1,1),(3,8),(3,8),(3,8),(3,8)]):
2124 return rank
2125
2126
2127
2128
2129
2130
2131 if self.is_boson() and len([w for w in self.get('mothers') \
2132 if w.is_boson()])==2:
2133 rank=rank+1
2134 return rank
2135
2144 """List of HelasWavefunction objects. This class has the routine
2145 check_and_fix_fermion_flow, which checks for fermion flow clashes
2146 among the mothers of an amplitude or wavefunction.
2147 """
2148
2150 """Test if object obj is a valid HelasWavefunction for the list."""
2151
2152 return isinstance(obj, HelasWavefunction)
2153
2154
2155
2157 return array.array('i', [w['number'] for w in self])
2158
2159 - def check_and_fix_fermion_flow(self,
2160 wavefunctions,
2161 diagram_wavefunctions,
2162 external_wavefunctions,
2163 my_wf,
2164 wf_number,
2165 force_flip_flow=False,
2166 number_to_wavefunctions=[]):
2167
2168 """Check for clashing fermion flow (N(incoming) !=
2169 N(outgoing)). If found, we need to trace back through the
2170 mother structure (only looking at fermions), until we find a
2171 Majorana fermion. Then flip fermion flow along this line all
2172 the way from the initial clash to the external fermion (in the
2173 right way, see check_majorana_and_flip_flow), and consider an
2174 incoming particle with fermionflow -1 as outgoing (and vice
2175 versa). Continue until we have N(incoming) = N(outgoing).
2176
2177 Since the wavefunction number might get updated, return new
2178 wavefunction number.
2179 """
2180
2181
2182 fermion_mother = None
2183
2184
2185 clashes = []
2186
2187
2188 if my_wf and my_wf.is_fermion():
2189 fermion_mother = my_wf.find_mother_fermion()
2190 if my_wf.get_with_flow('state') != \
2191 fermion_mother.get_with_flow('state'):
2192 clashes.append([fermion_mother])
2193
2194
2195 other_fermions = [w for w in self if \
2196 w.is_fermion() and w != fermion_mother]
2197
2198 for iferm in range(0, len(other_fermions), 2):
2199 if other_fermions[iferm].get_with_flow('state') == \
2200 other_fermions[iferm+1].get_with_flow('state'):
2201 clashes.append([other_fermions[iferm],
2202 other_fermions[iferm+1]])
2203
2204 if not clashes:
2205 return wf_number
2206
2207
2208
2209 for clash in clashes:
2210 neg_fermionflow_mothers = [m for m in clash if \
2211 m.get('fermionflow') < 0]
2212
2213 if not neg_fermionflow_mothers:
2214 neg_fermionflow_mothers = clash
2215
2216 for mother in neg_fermionflow_mothers:
2217
2218
2219
2220
2221 found_majorana = False
2222 state_before = mother.get_with_flow('state')
2223 new_mother, wf_number = mother.check_majorana_and_flip_flow(\
2224 found_majorana,
2225 wavefunctions,
2226 diagram_wavefunctions,
2227 external_wavefunctions,
2228 wf_number,
2229 force_flip_flow,
2230 number_to_wavefunctions)
2231
2232 if new_mother.get_with_flow('state') == state_before:
2233
2234 continue
2235
2236
2237 mother_index = self.index(mother)
2238 self[self.index(mother)] = new_mother
2239 clash_index = clash.index(mother)
2240 clash[clash.index(mother)] = new_mother
2241
2242
2243 break
2244
2245 if len(clash) == 1 and clash[0].get_with_flow('state') != \
2246 my_wf.get_with_flow('state') or \
2247 len(clash) == 2 and clash[0].get_with_flow('state') == \
2248 clash[1].get_with_flow('state'):
2249
2250
2251 force_flip_flow = True
2252 wf_number = self.check_and_fix_fermion_flow(\
2253 wavefunctions,
2254 diagram_wavefunctions,
2255 external_wavefunctions,
2256 my_wf,
2257 wf_number,
2258 force_flip_flow,
2259 number_to_wavefunctions)
2260
2261 break
2262
2263 return wf_number
2264
2266 """Recursively go through a wavefunction list and insert the
2267 mothers of all wavefunctions, return the result.
2268 Assumes that all wavefunctions have unique numbers."""
2269
2270 res = copy.copy(self)
2271
2272 for wf in self:
2273 index = res.index(wf)
2274 res = res[:index] + wf.get('mothers').insert_own_mothers() \
2275 + res[index:]
2276
2277
2278
2279 i = len(res) - 1
2280 while res[:i]:
2281 if res[i].get('number') in [w.get('number') for w in res[:i]]:
2282 res.pop(i)
2283 i = i - 1
2284
2285 return res
2286
2288 """Sort this HelasWavefunctionList according to the cyclic
2289 order of the pdg codes given. my_pdg_code is the pdg code of
2290 the daughter wavefunction (or 0 if daughter is amplitude)."""
2291
2292 if not pdg_codes:
2293 return self, 0
2294
2295 pdg_codes = copy.copy(pdg_codes)
2296
2297
2298
2299 my_index = -1
2300 if my_pdg_code:
2301
2302 my_index = pdg_codes.index(my_pdg_code)
2303 pdg_codes.pop(my_index)
2304
2305 mothers = copy.copy(self)
2306
2307
2308 mother_codes = [ wf.get_pdg_code() for wf \
2309 in mothers ]
2310 if pdg_codes == mother_codes:
2311
2312 return mothers, my_index
2313
2314 sorted_mothers = []
2315 for i, code in enumerate(pdg_codes):
2316 index = mother_codes.index(code)
2317 mother_codes.pop(index)
2318 mother = mothers.pop(index)
2319 sorted_mothers.append(mother)
2320
2321 if mothers:
2322 raise base_objects.PhysicsObject.PhysicsObjectError
2323
2324 return HelasWavefunctionList(sorted_mothers), my_index
2325
2327 """Returns a list [1,2,...] of fermion lines that need
2328 conjugate wfs due to wrong order of I/O Majorana particles
2329 compared to interaction order (or empty list if no Majorana
2330 particles). This is crucial if the Lorentz structure depends
2331 on the direction of the Majorana particles, as in MSSM with
2332 goldstinos."""
2333
2334 if len([m for m in self if m.is_majorana()]) < 2:
2335 return []
2336
2337 conjugates = []
2338
2339
2340 for i in range(0, len(self), 2):
2341 if self[i].is_majorana() and self[i+1].is_majorana() \
2342 and self[i].get_pdg_code() != \
2343 self[i+1].get_pdg_code():
2344
2345 if self[i].get_spin_state_number() > 0 and \
2346 self[i + 1].get_spin_state_number() < 0:
2347
2348 conjugates.append(True)
2349 else:
2350 conjugates.append(False)
2351 elif self[i].is_fermion():
2352
2353 conjugates.append(False)
2354
2355
2356 conjugates = [i+1 for (i,c) in enumerate(conjugates) if c]
2357
2358 return conjugates
2359
2360
2362 """ This function only serves as an internal consistency check to
2363 make sure that when setting the 'wavefunctions' attribute of the
2364 diagram, their order is consistent, in the sense that all mothers
2365 of any given wavefunction appear before that wavefunction.
2366 This function returns True if there was no change and the original
2367 wavefunction list was consistent and False otherwise.
2368 The option 'applyChanges' controls whether the function should substitute
2369 the original list (self) with the new corrected one. For now, this function
2370 is only used for self-consistency checks and the changes are not applied."""
2371
2372 if len(self)<2:
2373 return True
2374
2375 def RaiseError():
2376 raise self.PhysicsObjectListError, \
2377 "This wavefunction list does not have a consistent wavefunction ordering."+\
2378 "\n Wf numbers: %s"%str([wf['number'] for wf in diag_wfs])+\
2379 "\n Wf mothers: %s"%str([[mother['number'] for mother in wf['mothers']] \
2380 for wf in diag_wfs])
2381
2382
2383 diag_wfs = copy.copy(self)
2384
2385
2386
2387
2388 wfNumbers = [wf['number'] for wf in self]
2389
2390 exitLoop=False
2391 while not exitLoop:
2392 for i, wf in enumerate(diag_wfs):
2393 if i==len(diag_wfs)-1:
2394 exitLoop=True
2395 break
2396 found=False
2397
2398
2399 for w in diag_wfs[i+1:]:
2400 if w['number'] in [mwf['number'] for mwf in wf.get('mothers')]:
2401
2402
2403 diag_wfs.remove(w)
2404 diag_wfs.insert(i,w)
2405 found=True
2406 if raiseError: RaiseError()
2407 if not applyChanges:
2408 return False
2409 break
2410 if found:
2411 break
2412
2413 if diag_wfs!=self:
2414
2415
2416
2417 for i,wf in enumerate(diag_wfs):
2418 wf.set('number', wfNumbers[i])
2419
2420
2421 del self[:]
2422 self.extend(diag_wfs)
2423
2424
2425 return False
2426
2427
2428 return True
2429
2430 @staticmethod
2440
2445 """HelasAmplitude object, has the information necessary for
2446 writing a call to a HELAS amplitude routine:a list of mother wavefunctions,
2447 interaction id, amplitude number
2448 """
2449
2451 """Default values for all properties"""
2452
2453
2454 self['interaction_id'] = 0
2455
2456
2457 self['type'] = 'base'
2458 self['pdg_codes'] = []
2459 self['orders'] = {}
2460 self['inter_color'] = None
2461 self['lorentz'] = []
2462 self['coupling'] = ['none']
2463
2464 self['color_key'] = 0
2465
2466 self['number'] = 0
2467 self['fermionfactor'] = 0
2468 self['color_indices'] = []
2469 self['mothers'] = HelasWavefunctionList()
2470
2471
2472 self['conjugate_indices'] = None
2473
2474
2489
2490 - def filter(self, name, value):
2491 """Filter for valid property values."""
2492
2493 if name == 'interaction_id':
2494 if not isinstance(value, int):
2495 raise self.PhysicsObjectError, \
2496 "%s is not a valid integer for interaction id" % \
2497 str(value)
2498
2499 if name == 'pdg_codes':
2500
2501 if not isinstance(value, list):
2502 raise self.PhysicsObjectError, \
2503 "%s is not a valid list of integers" % str(value)
2504 for mystr in value:
2505 if not isinstance(mystr, int):
2506 raise self.PhysicsObjectError, \
2507 "%s is not a valid integer" % str(mystr)
2508
2509 if name == 'orders':
2510
2511 if not isinstance(value, dict):
2512 raise self.PhysicsObjectError, \
2513 "%s is not a valid dict for coupling orders" % \
2514 str(value)
2515 for order in value.keys():
2516 if not isinstance(order, str):
2517 raise self.PhysicsObjectError, \
2518 "%s is not a valid string" % str(order)
2519 if not isinstance(value[order], int):
2520 raise self.PhysicsObjectError, \
2521 "%s is not a valid integer" % str(value[order])
2522
2523 if name == 'inter_color':
2524
2525 if value and not isinstance(value, color.ColorString):
2526 raise self.PhysicsObjectError, \
2527 "%s is not a valid Color String" % str(value)
2528
2529 if name == 'lorentz':
2530
2531 if not isinstance(value, list):
2532 raise self.PhysicsObjectError, \
2533 "%s is not a valid list of string" % str(value)
2534 for name in value:
2535 if not isinstance(name, str):
2536 raise self.PhysicsObjectError, \
2537 "%s doesn't contain only string" % str(value)
2538
2539 if name == 'coupling':
2540
2541 if not isinstance(value, list):
2542 raise self.PhysicsObjectError, \
2543 "%s is not a valid coupling (list of string)" % str(value)
2544
2545 for name in value:
2546 if not isinstance(name, str):
2547 raise self.PhysicsObjectError, \
2548 "%s doesn't contain only string" % str(value)
2549 if not len(value):
2550 raise self.PhysicsObjectError, \
2551 'coupling should have at least one value'
2552
2553 if name == 'color_key':
2554 if value and not isinstance(value, int):
2555 raise self.PhysicsObjectError, \
2556 "%s is not a valid integer" % str(value)
2557
2558 if name == 'number':
2559 if not isinstance(value, int):
2560 raise self.PhysicsObjectError, \
2561 "%s is not a valid integer for amplitude number" % \
2562 str(value)
2563
2564 if name == 'fermionfactor':
2565 if not isinstance(value, int):
2566 raise self.PhysicsObjectError, \
2567 "%s is not a valid integer for fermionfactor" % \
2568 str(value)
2569 if not value in [-1, 0, 1]:
2570 raise self.PhysicsObjectError, \
2571 "%s is not a valid fermion factor (-1, 0 or 1)" % \
2572 str(value)
2573
2574 if name == 'color_indices':
2575
2576 if not isinstance(value, list):
2577 raise self.PhysicsObjectError, \
2578 "%s is not a valid list of integers" % str(value)
2579 for mystr in value:
2580 if not isinstance(mystr, int):
2581 raise self.PhysicsObjectError, \
2582 "%s is not a valid integer" % str(mystr)
2583
2584 if name == 'mothers':
2585 if not isinstance(value, HelasWavefunctionList):
2586 raise self.PhysicsObjectError, \
2587 "%s is not a valid list of mothers for amplitude" % \
2588 str(value)
2589
2590 if name == 'conjugate_indices':
2591 if not isinstance(value, tuple) and value != None:
2592 raise self.PhysicsObjectError, \
2593 "%s is not a valid tuple" % str(value) + \
2594 " for conjugate_indices"
2595
2596 return True
2597
2599 """ practicle way to represent an HelasAmplitude"""
2600
2601 mystr = '{\n'
2602 for prop in self.get_sorted_keys():
2603 if isinstance(self[prop], str):
2604 mystr = mystr + ' \'' + prop + '\': \'' + \
2605 self[prop] + '\',\n'
2606 elif isinstance(self[prop], float):
2607 mystr = mystr + ' \'' + prop + '\': %.2f,\n' % self[prop]
2608 elif isinstance(self[prop], int):
2609 mystr = mystr + ' \'' + prop + '\': %s,\n' % self[prop]
2610 elif prop != 'mothers':
2611 mystr = mystr + ' \'' + prop + '\': ' + \
2612 str(self[prop]) + ',\n'
2613 else:
2614 info = [m.get('pdg_code') for m in self['mothers']]
2615 mystr += ' \'%s\': %s,\n' % (prop, info)
2616
2617 mystr = mystr.rstrip(',\n')
2618 mystr = mystr + '\n}'
2619
2620 return mystr
2621
2622
2623 - def get(self, name):
2634
2635
2636
2637 - def set(self, *arguments):
2638 """When setting interaction_id, if model is given (in tuple),
2639 set all other interaction properties. When setting pdg_code,
2640 if model is given, set all other particle properties."""
2641
2642 assert len(arguments) > 1, "Too few arguments for set"
2643
2644 name = arguments[0]
2645 value = arguments[1]
2646
2647 if len(arguments) > 2 and \
2648 isinstance(value, int) and \
2649 isinstance(arguments[2], base_objects.Model):
2650 if name == 'interaction_id':
2651 self.set('interaction_id', value)
2652 if value > 0:
2653 inter = arguments[2].get('interaction_dict')[value]
2654 self.set('pdg_codes',
2655 [part.get_pdg_code() for part in \
2656 inter.get('particles')])
2657 self.set('orders', inter.get('orders'))
2658
2659
2660 if inter.get('type'):
2661 self.set('type', inter.get('type'))
2662 if inter.get('color'):
2663 self.set('inter_color', inter.get('color')[0])
2664 if inter.get('lorentz'):
2665 self.set('lorentz', [inter.get('lorentz')[0]])
2666 if inter.get('couplings'):
2667 self.set('coupling', [inter.get('couplings').values()[0]])
2668 return True
2669 else:
2670 raise self.PhysicsObjectError, \
2671 "%s not allowed name for 3-argument set", name
2672 else:
2673 return super(HelasAmplitude, self).set(name, value)
2674
2676 """Return particle property names as a nicely sorted list."""
2677
2678 return ['interaction_id', 'pdg_codes', 'orders', 'inter_color',
2679 'lorentz', 'coupling', 'color_key', 'number', 'color_indices',
2680 'fermionfactor', 'mothers']
2681
2682
2683
2684 - def check_and_fix_fermion_flow(self,
2685 wavefunctions,
2686 diagram_wavefunctions,
2687 external_wavefunctions,
2688 wf_number):
2689 """Check for clashing fermion flow (N(incoming) !=
2690 N(outgoing)) in mothers. For documentation, check
2691 HelasWavefunction.check_and_fix_fermion_flow.
2692 """
2693
2694 self.set('mothers', self.get('mothers').sort_by_pdg_codes(\
2695 self.get('pdg_codes'), 0)[0])
2696
2697 return self.get('mothers').check_and_fix_fermion_flow(\
2698 wavefunctions,
2699 diagram_wavefunctions,
2700 external_wavefunctions,
2701 None,
2702 wf_number)
2703
2704
2706 """Returns true if any of the mothers have negative
2707 fermionflow"""
2708
2709 return self.get('conjugate_indices') != ()
2710
2712 """Based on the type of the amplitude, determines to which epsilon
2713 order it contributes"""
2714
2715 if '1eps' in self['type']:
2716 return 1
2717 elif '2eps' in self['type']:
2718 return 2
2719 else:
2720 return 0
2721
2723 """Generate the (spin, state) tuples used as key for the helas call
2724 dictionaries in HelasModel"""
2725
2726 res = []
2727 for mother in self.get('mothers'):
2728 res.append(mother.get_spin_state_number())
2729
2730
2731 res.sort()
2732
2733
2734
2735
2736
2737 if self['type']!='base':
2738 res.append(self['type'])
2739
2740
2741 if self.needs_hermitian_conjugate():
2742 res.append(self.get('conjugate_indices'))
2743
2744 return (tuple(res), tuple(self.get('lorentz')))
2745
2747 """Calculate the fermion factor for the diagram corresponding
2748 to this amplitude"""
2749
2750
2751 fermions = [wf for wf in self.get('mothers') if wf.is_fermion()]
2752 assert len(fermions) % 2 == 0
2753
2754
2755 bosons = filter(lambda wf: wf.is_boson(), self.get('mothers'))
2756
2757 fermion_number_list = []
2758
2759
2760
2761 fermion_numbers = [f.get_fermion_order() for f in fermions]
2762
2763
2764 if self.get('type')=='loop':
2765
2766 lcuf_wf_2=[m for m in self.get('mothers') if m['is_loop'] and \
2767 len(m.get('mothers'))==0][0]
2768 ghost_factor = -1 if lcuf_wf_2.is_anticommutating_ghost() else 1
2769 else:
2770
2771 ghost_factor = 1
2772
2773 fermion_loop_factor = 1
2774
2775
2776 if self.get('type')=='loop' and len(fermion_numbers)>0:
2777
2778
2779
2780 lcut_wf2_number = lcuf_wf_2.get('number_external')
2781 assert len(fermion_numbers)==2, "Incorrect number of fermions"+\
2782 " (%d) for the amp. closing the loop."%len(fermion_numbers)
2783
2784 lcuf_wf_1=[m for m in self.get('mothers') if m['is_loop'] and \
2785 len(m.get('mothers'))>0][0]
2786 while len(lcuf_wf_1.get('mothers'))>0:
2787 lcuf_wf_1 = lcuf_wf_1.get_loop_mother()
2788 lcut_wf1_number = lcuf_wf_1.get('number_external')
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802 iferm_to_replace = (fermion_numbers.index([lcut_wf2_number,[]])+1)%2
2803
2804 if fermion_numbers[iferm_to_replace][0]==lcut_wf1_number:
2805
2806
2807
2808
2809 fermion_number_list.extend(fermion_numbers[iferm_to_replace][1])
2810 fermion_loop_factor = -1
2811 else:
2812
2813 fermion_number_list = \
2814 copy.copy(fermion_numbers[iferm_to_replace][1])
2815
2816
2817
2818 i_connected_fermion = fermion_number_list.index(lcut_wf1_number)
2819 fermion_number_list[i_connected_fermion] = \
2820 fermion_numbers[iferm_to_replace][0]
2821 else:
2822 for iferm in range(0, len(fermion_numbers), 2):
2823 fermion_number_list.append(fermion_numbers[iferm][0])
2824 fermion_number_list.append(fermion_numbers[iferm+1][0])
2825 fermion_number_list.extend(fermion_numbers[iferm][1])
2826 fermion_number_list.extend(fermion_numbers[iferm+1][1])
2827
2828
2829
2830
2831 for boson in bosons:
2832
2833 fermion_number_list.extend(boson.get_fermion_order())
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852 fermion_factor = HelasAmplitude.sign_flips_to_order(fermion_number_list)
2853
2854 self['fermionfactor'] = fermion_factor*ghost_factor*fermion_loop_factor
2855
2856
2857 @staticmethod
2859 """Gives the sign corresponding to the number of flips needed
2860 to place the fermion numbers in order"""
2861
2862
2863
2864
2865 nflips = 0
2866
2867 for i in range(len(fermions) - 1):
2868 for j in range(i + 1, len(fermions)):
2869 if fermions[j] < fermions[i]:
2870 fermions[i], fermions[j] = fermions[j], fermions[i]
2871 nflips = nflips + 1
2872
2873 return (-1) ** nflips
2874
2876 """Returns the tuple (lorentz_name, tag, outgoing_number) providing
2877 the necessary information to compute_subset of create_aloha to write
2878 out the HELAS-like routines."""
2879
2880
2881
2882 if self.get('interaction_id') in [0,-1]:
2883 return None
2884
2885 tags = ['C%s' % w for w in self.get_conjugate_index()]
2886
2887 return (tuple(self.get('lorentz')),tuple(tags),self.find_outgoing_number())
2888
2890 """Return the base_objects.Diagram which corresponds to this
2891 amplitude, using a recursive method for the wavefunctions."""
2892
2893 vertices = base_objects.VertexList()
2894
2895
2896 for mother in self.get('mothers'):
2897 vertices.extend(mother.get_base_vertices(wf_dict, vx_list,
2898 optimization))
2899
2900 vertex = self.get_base_vertex(wf_dict, vx_list, optimization)
2901
2902 vertices.append(vertex)
2903
2904 return base_objects.Diagram({'vertices': vertices})
2905
2907 """Get a base_objects.Vertex corresponding to this amplitude."""
2908
2909
2910 legs = base_objects.LegList()
2911 for mother in self.get('mothers'):
2912 try:
2913 if mother.get('is_loop'):
2914
2915 raise KeyError
2916 leg = wf_dict[(mother.get('number'),False)]
2917 except KeyError:
2918 leg = base_objects.Leg({
2919 'id': mother.get_pdg_code(),
2920 'number': mother.get('number_external'),
2921 'state': mother.get('leg_state'),
2922 'onshell': None,
2923 'loop_line':mother.get('is_loop')
2924 })
2925 if optimization != 0 and not mother.get('is_loop'):
2926 wf_dict[(mother.get('number'),False)] = leg
2927 legs.append(leg)
2928
2929 return base_objects.Vertex({
2930 'id': self.get('interaction_id'),
2931 'legs': legs})
2932
2934 """Returns two lists of vertices corresponding to the s- and
2935 t-channels of this amplitude/diagram, ordered from the outermost
2936 s-channel and in/down towards the highest number initial state
2937 leg."""
2938
2939
2940
2941 wf_dict = {}
2942 max_final_leg = 2
2943 if reverse_t_ch:
2944 max_final_leg = 1
2945
2946
2947
2948 tag = CanonicalConfigTag(self.get_base_diagram(wf_dict).
2949 get_contracted_loop_diagram(model), model)
2950
2951 return tag.get_s_and_t_channels(ninitial, model, new_pdg, max_final_leg)
2952
2953
2955 """Get the color indices corresponding to
2956 this amplitude and its mothers, using a recursive function."""
2957
2958 if not self.get('mothers'):
2959 return []
2960
2961 color_indices = []
2962
2963
2964 for mother in self.get('mothers'):
2965
2966 color_indices.extend(mother.get_color_indices())
2967
2968
2969 if self.get('interaction_id') not in [0,-1]:
2970 color_indices.append(self.get('color_key'))
2971
2972 return color_indices
2973
2975 """Return 0. Needed to treat HelasAmplitudes and
2976 HelasWavefunctions on same footing."""
2977
2978 return 0
2979
2981 """Return the index of the particle that should be conjugated."""
2982
2983 if not any([(wf.get('fermionflow') < 0 or wf.is_majorana()) for wf in \
2984 self.get('mothers')]):
2985 return ()
2986
2987
2988 mothers, self_index = \
2989 self.get('mothers').sort_by_pdg_codes(self.get('pdg_codes'))
2990 fermions = HelasWavefunctionList([wf for wf in mothers if wf.is_fermion()])
2991
2992
2993 indices = fermions.majorana_conjugates()
2994
2995
2996 for i in range(0,len(fermions), 2):
2997 if fermions[i].get('fermionflow') < 0 or \
2998 fermions[i+1].get('fermionflow') < 0:
2999 indices.append(i/2 + 1)
3000
3001 return tuple(sorted(indices))
3002
3006 """Get a list of the number of legs in vertices in this diagram,
3007 This function is only used for establishing the multi-channeling, so that
3008 we exclude from it all the fake vertices and the vertices resulting from
3009 shrunk loops (id=-2)"""
3010
3011 if max_n_loop == 0:
3012 max_n_loop = base_objects.Vertex.max_n_loop_for_multichanneling
3013
3014 vertex_leg_numbers = [len(self.get('mothers'))] if \
3015 (self['interaction_id'] not in veto_inter_id) or \
3016 (self['interaction_id']==-2 and len(self.get('mothers'))>max_n_loop) \
3017 else []
3018 for mother in self.get('mothers'):
3019 vertex_leg_numbers.extend(mother.get_vertex_leg_numbers(
3020 veto_inter_id = veto_inter_id))
3021
3022 return vertex_leg_numbers
3023
3026 """ return a dictionary to be used for formatting
3027 HELAS call."""
3028
3029 if index == 1:
3030 flip = 0
3031 else:
3032 flip = 1
3033
3034 output = {}
3035 for i, mother in enumerate(self.get('mothers')):
3036 nb = mother.get('me_id') - flip
3037 output[str(i)] = nb
3038 if mother.get('is_loop'):
3039 output['WF%d' % i ] = 'L(1,%d)'%nb
3040 else:
3041 if specifyHel:
3042 output['WF%d' % i ] = '(1,WE(%d),H)'%nb
3043 else:
3044 output['WF%d' % i ] = '(1,WE(%d))'%nb
3045
3046
3047 for i, coup in enumerate(self.get('coupling')):
3048 output['coup%d'%i] = str(coup)
3049
3050 output['out'] = self.get('number') - flip
3051 output['propa'] = ''
3052 output.update(opt)
3053 return output
3054
3055
3056
3058 """Check if there is a mismatch between order of fermions
3059 w.r.t. color"""
3060 mothers = self.get('mothers')
3061
3062
3063
3064
3065 for imo in range(len(mothers)-1):
3066 if mothers[imo].get('color') != 1 and \
3067 mothers[imo].is_fermion() and \
3068 mothers[imo].get('color') == mothers[imo+1].get('color') and \
3069 mothers[imo].get('spin') == mothers[imo+1].get('spin') and \
3070 mothers[imo].get('pdg_code') != mothers[imo+1].get('pdg_code'):
3071 mothers, my_index = \
3072 mothers.sort_by_pdg_codes(self.get('pdg_codes'))
3073 break
3074
3075 if mothers != self.get('mothers') and \
3076 not self.get('coupling').startswith('-'):
3077
3078 self.set('coupling', '-'+self.get('coupling'))
3079
3080
3081
3082
3083
3085 """Comparison between different amplitudes, to allow check for
3086 identical processes.
3087 """
3088
3089 if not isinstance(other, HelasAmplitude):
3090 return False
3091
3092
3093 if self['lorentz'] != other['lorentz'] or \
3094 self['coupling'] != other['coupling'] or \
3095 self['number'] != other['number']:
3096 return False
3097
3098
3099 return sorted([mother['number'] for mother in self['mothers']]) == \
3100 sorted([mother['number'] for mother in other['mothers']])
3101
3103 """Overloading the nonequality operator, to make comparison easy"""
3104 return not self.__eq__(other)
3105
3110 """List of HelasAmplitude objects
3111 """
3112
3114 """Test if object obj is a valid HelasAmplitude for the list."""
3115
3116 return isinstance(obj, HelasAmplitude)
3117
3118
3119
3120
3121
3122 -class HelasDiagram(base_objects.PhysicsObject):
3123 """HelasDiagram: list of HelasWavefunctions and a HelasAmplitude,
3124 plus the fermion factor associated with the corresponding diagram.
3125 """
3126
3140
3141 - def filter(self, name, value):
3142 """Filter for valid diagram property values."""
3143
3144 if name == 'wavefunctions' or name == 'loop_wavefunctions':
3145 if not isinstance(value, HelasWavefunctionList):
3146 raise self.PhysicsObjectError, \
3147 "%s is not a valid HelasWavefunctionList object" % \
3148 str(value)
3149
3150 if name == 'amplitudes':
3151 if not isinstance(value, HelasAmplitudeList):
3152 raise self.PhysicsObjectError, \
3153 "%s is not a valid HelasAmplitudeList object" % \
3154 str(value)
3155
3156 return True
3157
3159 """Return particle property names as a nicely sorted list."""
3160
3161 return ['wavefunctions', 'loop_wavefunctions', 'amplitudes']
3162
3179
3190
3192 """ For regular HelasDiagrams, it is simply all amplitudes.
3193 It is overloaded in LoopHelasDiagram"""
3194
3195 return self['amplitudes']
3196
3201 """List of HelasDiagram objects
3202 """
3203
3205 """Test if object obj is a valid HelasDiagram for the list."""
3206
3207 return isinstance(obj, HelasDiagram)
3208
3213 """HelasMatrixElement: list of processes with identical Helas
3214 calls, and the list of HelasDiagrams associated with the processes.
3215
3216 If initiated with an Amplitude, HelasMatrixElement calls
3217 generate_helas_diagrams, which goes through the diagrams of the
3218 Amplitude and generates the corresponding Helas calls, taking into
3219 account possible fermion flow clashes due to Majorana
3220 particles. The optional optimization argument determines whether
3221 optimization is used (optimization = 1, default), for maximum
3222 recycling of wavefunctions, or no optimization (optimization = 0)
3223 when each diagram is written independently of all previous
3224 diagrams (this is useful for running with restricted memory,
3225 e.g. on a GPU). For processes with many diagrams, the total number
3226 or wavefunctions after optimization is ~15% of the number of
3227 amplitudes (diagrams).
3228
3229 By default, it will also generate the color information (color
3230 basis and color matrix) corresponding to the Amplitude.
3231 """
3232
3248
3249 - def filter(self, name, value):
3250 """Filter for valid diagram property values."""
3251
3252 if name == 'processes':
3253 if not isinstance(value, base_objects.ProcessList):
3254 raise self.PhysicsObjectError, \
3255 "%s is not a valid ProcessList object" % str(value)
3256 if name == 'diagrams':
3257 if not isinstance(value, HelasDiagramList):
3258 raise self.PhysicsObjectError, \
3259 "%s is not a valid HelasDiagramList object" % str(value)
3260 if name == 'identical_particle_factor':
3261 if not isinstance(value, int):
3262 raise self.PhysicsObjectError, \
3263 "%s is not a valid int object" % str(value)
3264 if name == 'color_basis':
3265 if not isinstance(value, color_amp.ColorBasis):
3266 raise self.PhysicsObjectError, \
3267 "%s is not a valid ColorBasis object" % str(value)
3268 if name == 'color_matrix':
3269 if not isinstance(value, color_amp.ColorMatrix):
3270 raise self.PhysicsObjectError, \
3271 "%s is not a valid ColorMatrix object" % str(value)
3272 if name == 'base_amplitude':
3273 if value != None and not \
3274 isinstance(value, diagram_generation.Amplitude):
3275 raise self.PhysicsObjectError, \
3276 "%s is not a valid Amplitude object" % str(value)
3277 if name == 'has_mirror_process':
3278 if not isinstance(value, bool):
3279 raise self.PhysicsObjectError, \
3280 "%s is not a valid boolean" % str(value)
3281 return True
3282
3284 """Return particle property names as a nicely sorted list."""
3285
3286 return ['processes', 'identical_particle_factor',
3287 'diagrams', 'color_basis', 'color_matrix',
3288 'base_amplitude', 'has_mirror_process']
3289
3290
3291 - def get(self, name):
3298
3299
3300 - def __init__(self, amplitude=None, optimization=1,
3301 decay_ids=[], gen_color=True):
3323
3324
3325
3326
3328 """Comparison between different matrix elements, to allow check for
3329 identical processes.
3330 """
3331
3332 if not isinstance(other, HelasMatrixElement):
3333 return False
3334
3335
3336 if not self['processes'] and not other['processes']:
3337 return True
3338
3339
3340
3341
3342 if self['processes'] and not other['processes'] or \
3343 self['has_mirror_process'] != other['has_mirror_process'] or \
3344 self['processes'] and \
3345 self['processes'][0]['id'] != other['processes'][0]['id'] or \
3346 self['processes'][0]['is_decay_chain'] or \
3347 other['processes'][0]['is_decay_chain'] or \
3348 self['identical_particle_factor'] != \
3349 other['identical_particle_factor'] or \
3350 self['diagrams'] != other['diagrams']:
3351 return False
3352 return True
3353
3355 """Overloading the nonequality operator, to make comparison easy"""
3356 return not self.__eq__(other)
3357
3359 """ Perform the simple color processing from a single matrix element
3360 (without optimization then). This is called from the initialization
3361 and pulled out here in order to have the correct treatment in daughter
3362 classes."""
3363 logger.debug('Computing the color basis')
3364 self.get('color_basis').build(self.get('base_amplitude'))
3365 self.set('color_matrix',
3366 color_amp.ColorMatrix(self.get('color_basis')))
3367
3369 """Starting from a list of Diagrams from the diagram
3370 generation, generate the corresponding HelasDiagrams, i.e.,
3371 the wave functions and amplitudes. Choose between default
3372 optimization (= 1, maximum recycling of wavefunctions) or no
3373 optimization (= 0, no recycling of wavefunctions, useful for
3374 GPU calculations with very restricted memory).
3375
3376 Note that we need special treatment for decay chains, since
3377 the end product then is a wavefunction, not an amplitude.
3378 """
3379
3380 assert isinstance(amplitude, diagram_generation.Amplitude), \
3381 "Missing or erraneous arguments for generate_helas_diagrams"
3382 assert isinstance(optimization, int), \
3383 "Missing or erraneous arguments for generate_helas_diagrams"
3384 self.optimization = optimization
3385
3386 diagram_list = amplitude.get('diagrams')
3387 process = amplitude.get('process')
3388
3389 model = process.get('model')
3390 if not diagram_list:
3391 return
3392
3393
3394 wavefunctions = []
3395
3396
3397 wf_mother_arrays = []
3398
3399 wf_number = 0
3400
3401
3402 external_wavefunctions = dict([(leg.get('number'),
3403 HelasWavefunction(leg, 0, model,
3404 decay_ids)) \
3405 for leg in process.get('legs')])
3406
3407
3408 wf_number = len(process.get('legs'))
3409
3410
3411
3412 for key in external_wavefunctions.keys():
3413 wf = external_wavefunctions[key]
3414 if wf.is_boson() and wf.get('state') == 'initial' and \
3415 not wf.get('self_antipart'):
3416 wf.set('is_part', not wf.get('is_part'))
3417
3418
3419
3420 for key in external_wavefunctions.keys():
3421 wf = external_wavefunctions[key]
3422 if wf.get('leg_state') == False and \
3423 not wf.get('self_antipart'):
3424 wf.flip_part_antipart()
3425
3426
3427
3428 helas_diagrams = HelasDiagramList()
3429
3430
3431 amplitude_number = 0
3432 diagram_number = 0
3433
3434 for diagram in diagram_list:
3435
3436
3437
3438
3439 number_to_wavefunctions = [{}]
3440
3441
3442 color_lists = [[]]
3443
3444
3445 diagram_wavefunctions = HelasWavefunctionList()
3446
3447 vertices = copy.copy(diagram.get('vertices'))
3448
3449
3450 lastvx = vertices.pop()
3451
3452
3453
3454 for vertex in vertices:
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465 new_number_to_wavefunctions = []
3466 new_color_lists = []
3467 for number_wf_dict, color_list in zip(number_to_wavefunctions,
3468 color_lists):
3469 legs = copy.copy(vertex.get('legs'))
3470 last_leg = legs.pop()
3471
3472 mothers = self.getmothers(legs, number_wf_dict,
3473 external_wavefunctions,
3474 wavefunctions,
3475 diagram_wavefunctions)
3476 inter = model.get('interaction_dict')[vertex.get('id')]
3477
3478
3479
3480
3481 done_color = {}
3482 for coupl_key in sorted(inter.get('couplings').keys()):
3483 color = coupl_key[0]
3484 if color in done_color:
3485 wf = done_color[color]
3486 wf.get('coupling').append(inter.get('couplings')[coupl_key])
3487 wf.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
3488 continue
3489 wf = HelasWavefunction(last_leg, vertex.get('id'), model)
3490 wf.set('coupling', [inter.get('couplings')[coupl_key]])
3491 if inter.get('color'):
3492 wf.set('inter_color', inter.get('color')[coupl_key[0]])
3493 done_color[color] = wf
3494 wf.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
3495 wf.set('color_key', color)
3496 wf.set('mothers', mothers)
3497
3498
3499
3500 wf.set_state_and_particle(model)
3501
3502
3503
3504 wf, wf_number = wf.check_and_fix_fermion_flow(\
3505 wavefunctions,
3506 diagram_wavefunctions,
3507 external_wavefunctions,
3508 wf_number)
3509
3510 new_number_wf_dict = copy.copy(number_wf_dict)
3511
3512
3513 try:
3514 wf = diagram_wavefunctions[\
3515 diagram_wavefunctions.index(wf)]
3516 except ValueError, error:
3517
3518 wf_number = wf_number + 1
3519 wf.set('number', wf_number)
3520 try:
3521
3522
3523 wf = wavefunctions[wf_mother_arrays.index(\
3524 wf.to_array())]
3525
3526
3527 wf_number = wf_number - 1
3528 except ValueError:
3529 diagram_wavefunctions.append(wf)
3530
3531 new_number_wf_dict[last_leg.get('number')] = wf
3532
3533
3534 new_number_to_wavefunctions.append(\
3535 new_number_wf_dict)
3536
3537 new_color_list = copy.copy(color_list)
3538 new_color_list.append(coupl_key[0])
3539 new_color_lists.append(new_color_list)
3540
3541 number_to_wavefunctions = new_number_to_wavefunctions
3542 color_lists = new_color_lists
3543
3544
3545
3546 helas_diagram = HelasDiagram()
3547 diagram_number = diagram_number + 1
3548 helas_diagram.set('number', diagram_number)
3549 for number_wf_dict, color_list in zip(number_to_wavefunctions,
3550 color_lists):
3551
3552 if lastvx.get('id'):
3553 inter = model.get_interaction(lastvx.get('id'))
3554 keys = sorted(inter.get('couplings').keys())
3555 pdg_codes = [p.get_pdg_code() for p in \
3556 inter.get('particles')]
3557 else:
3558
3559
3560 inter = None
3561 keys = [(0, 0)]
3562 pdg_codes = None
3563
3564
3565 legs = lastvx.get('legs')
3566 mothers = self.getmothers(legs, number_wf_dict,
3567 external_wavefunctions,
3568 wavefunctions,
3569 diagram_wavefunctions).\
3570 sort_by_pdg_codes(pdg_codes, 0)[0]
3571
3572
3573 wf_number = mothers.check_and_fix_fermion_flow(wavefunctions,
3574 diagram_wavefunctions,
3575 external_wavefunctions,
3576 None,
3577 wf_number,
3578 False,
3579 number_to_wavefunctions)
3580 done_color = {}
3581 for i, coupl_key in enumerate(keys):
3582 color = coupl_key[0]
3583 if inter and color in done_color.keys():
3584 amp = done_color[color]
3585 amp.get('coupling').append(inter.get('couplings')[coupl_key])
3586 amp.get('lorentz').append(inter.get('lorentz')[coupl_key[1]])
3587 continue
3588 amp = HelasAmplitude(lastvx, model)
3589 if inter:
3590 amp.set('coupling', [inter.get('couplings')[coupl_key]])
3591 amp.set('lorentz', [inter.get('lorentz')[coupl_key[1]]])
3592 if inter.get('color'):
3593 amp.set('inter_color', inter.get('color')[color])
3594 amp.set('color_key', color)
3595 done_color[color] = amp
3596 amp.set('mothers', mothers)
3597 amplitude_number = amplitude_number + 1
3598 amp.set('number', amplitude_number)
3599
3600 new_color_list = copy.copy(color_list)
3601 if inter:
3602 new_color_list.append(color)
3603
3604 amp.set('color_indices', new_color_list)
3605
3606
3607 helas_diagram.get('amplitudes').append(amp)
3608
3609
3610
3611 diagram_wavefunctions.sort(lambda wf1, wf2: \
3612 wf1.get('number') - wf2.get('number'))
3613
3614
3615 iwf = len(diagram_wavefunctions) - 1
3616 while iwf > 0:
3617 this_wf = diagram_wavefunctions[iwf]
3618 moved = False
3619 for i,wf in enumerate(diagram_wavefunctions[:iwf]):
3620 if this_wf in wf.get('mothers'):
3621 diagram_wavefunctions.pop(iwf)
3622 diagram_wavefunctions.insert(i, this_wf)
3623 this_wf.set('number', wf.get('number'))
3624 for w in diagram_wavefunctions[i+1:]:
3625 w.set('number',w.get('number')+1)
3626 moved = True
3627 break
3628 if not moved: iwf -= 1
3629
3630
3631 helas_diagram.set('wavefunctions', diagram_wavefunctions)
3632
3633 if optimization:
3634 wavefunctions.extend(diagram_wavefunctions)
3635 wf_mother_arrays.extend([wf.to_array() for wf \
3636 in diagram_wavefunctions])
3637 else:
3638 wf_number = len(process.get('legs'))
3639
3640
3641 helas_diagrams.append(helas_diagram)
3642
3643
3644 self.set('diagrams', helas_diagrams)
3645
3646
3647 for wf in self.get_all_wavefunctions():
3648 wf.set('mothers', HelasMatrixElement.sorted_mothers(wf))
3649
3650 for amp in self.get_all_amplitudes():
3651 amp.set('mothers', HelasMatrixElement.sorted_mothers(amp))
3652 amp.set('color_indices', amp.get_color_indices())
3653
3654
3656 """change the wavefunctions id used in the writer to minimize the
3657 memory used by the wavefunctions."""
3658
3659 if not self.optimization:
3660 for diag in helas_diagrams:
3661 for wf in diag['wavefunctions']:
3662 wf.set('me_id',wf.get('number'))
3663 return helas_diagrams
3664
3665
3666
3667
3668 last_lign={}
3669 first={}
3670 pos=0
3671 for diag in helas_diagrams:
3672 for wf in diag['wavefunctions']:
3673 pos+=1
3674 for wfin in wf.get('mothers'):
3675 last_lign[wfin.get('number')] = pos
3676 assert wfin.get('number') in first.values()
3677 first[pos] = wf.get('number')
3678 for amp in diag['amplitudes']:
3679 pos+=1
3680 for wfin in amp.get('mothers'):
3681 last_lign[wfin.get('number')] = pos
3682
3683
3684
3685 last=collections.defaultdict(list)
3686 for nb, pos in last_lign.items():
3687 last[pos].append(nb)
3688 tag = list(set(last.keys()+first.keys()))
3689 tag.sort()
3690
3691
3692 outdated = []
3693 replace = {}
3694 max_wf = 0
3695 for nb in tag:
3696 if outdated and nb in first:
3697 replace[first[nb]] = outdated.pop()
3698 elif nb in first:
3699 assert first[nb] not in replace, '%s already assigned' % first[nb]
3700 max_wf += 1
3701 replace[first[nb]] = max_wf
3702 if nb in last:
3703 for value in last[nb]:
3704 outdated.append(replace[value])
3705
3706
3707
3708 for diag in helas_diagrams:
3709 for wf in diag['wavefunctions']:
3710 wf.set('me_id', replace[wf.get('number')])
3711
3712 return helas_diagrams
3713
3715 """This restore the original memory print and revert
3716 change the wavefunctions id used in the writer to minimize the
3717 memory used by the wavefunctions."""
3718
3719 helas_diagrams = self.get('diagrams')
3720
3721 for diag in helas_diagrams:
3722 for wf in diag['wavefunctions']:
3723 wf.set('me_id',wf.get('number'))
3724
3725 return helas_diagrams
3726
3727
3729 """Iteratively insert decay chains decays into this matrix
3730 element.
3731 * decay_dict: a dictionary from external leg number
3732 to decay matrix element.
3733 """
3734
3735
3736 for proc in self.get('processes'):
3737 proc.set('legs_with_decays', base_objects.LegList())
3738
3739
3740
3741 replace_dict = {}
3742 for number in decay_dict.keys():
3743
3744
3745 replace_dict[number] = [wf for wf in \
3746 filter(lambda wf: not wf.get('mothers') and \
3747 wf.get('number_external') == number,
3748 self.get_all_wavefunctions())]
3749
3750
3751
3752 numbers = [self.get_all_wavefunctions()[-1].get('number'),
3753 self.get_all_amplitudes()[-1].get('number')]
3754
3755
3756 got_majoranas = False
3757 for wf in self.get_all_wavefunctions() + \
3758 sum([d.get_all_wavefunctions() for d in \
3759 decay_dict.values()], []):
3760 if wf.get('fermionflow') < 0 or \
3761 wf.get('self_antipart') and wf.is_fermion():
3762 got_majoranas = True
3763
3764
3765 for number in decay_dict.keys():
3766
3767 self.insert_decay(replace_dict[number],
3768 decay_dict[number],
3769 numbers,
3770 got_majoranas)
3771
3772
3773 overall_orders = self.get('processes')[0].get('overall_orders')
3774 if overall_orders:
3775 ndiag = len(self.get('diagrams'))
3776 idiag = 0
3777 while self.get('diagrams')[idiag:]:
3778 diagram = self.get('diagrams')[idiag]
3779 orders = diagram.calculate_orders()
3780 remove_diagram = False
3781 for order in orders.keys():
3782 try:
3783 if orders[order] > \
3784 overall_orders[order]:
3785 remove_diagram = True
3786 except KeyError:
3787 pass
3788 if remove_diagram:
3789 self.get('diagrams').pop(idiag)
3790 else:
3791 idiag += 1
3792
3793 if len(self.get('diagrams')) < ndiag:
3794
3795
3796 wf_numbers = []
3797 ndiagrams = 0
3798 for diagram in self.get('diagrams'):
3799 ndiagrams += 1
3800 diagram.set('number', ndiagrams)
3801
3802 diagram_wfs = HelasWavefunctionList()
3803 for amplitude in diagram.get('amplitudes'):
3804 wavefunctions = \
3805 sorted(HelasWavefunctionList.\
3806 extract_wavefunctions(amplitude.get('mothers')),
3807 lambda wf1, wf2: wf1.get('number') - \
3808 wf2.get('number'))
3809 for wf in wavefunctions:
3810
3811 if wf.get('number') not in wf_numbers and \
3812 wf not in diagram_wfs:
3813 diagram_wfs.append(wf)
3814 wf_numbers.append(wf.get('number'))
3815 diagram.set('wavefunctions', diagram_wfs)
3816
3817
3818
3819
3820 flows = reduce(lambda i1, i2: i1 * i2,
3821 [len(replace_dict[i]) for i in decay_dict.keys()], 1)
3822 diagrams = reduce(lambda i1, i2: i1 * i2,
3823 [len(decay_dict[i].get('diagrams')) for i in \
3824 decay_dict.keys()], 1)
3825
3826 if flows > 1 or (diagrams > 1 and got_majoranas):
3827
3828
3829
3830 earlier_wfs = []
3831
3832 earlier_wf_arrays = []
3833
3834 mothers = self.get_all_wavefunctions() + self.get_all_amplitudes()
3835 mother_arrays = [w['mothers'].to_array() \
3836 for w in mothers]
3837
3838 for diagram in self.get('diagrams'):
3839
3840 if diagram.get('number') > 1:
3841 earlier_wfs.extend(self.get('diagrams')[\
3842 diagram.get('number') - 2].get('wavefunctions'))
3843
3844 i = 0
3845 diag_wfs = diagram.get('wavefunctions')
3846
3847
3848
3849 while diag_wfs[i:]:
3850 try:
3851 new_wf = earlier_wfs[\
3852 earlier_wfs.index(diag_wfs[i])]
3853 wf = diag_wfs.pop(i)
3854
3855 self.update_later_mothers(wf, new_wf, mothers,
3856 mother_arrays)
3857 except ValueError:
3858 i = i + 1
3859
3860
3861
3862 for i, wf in enumerate(self.get_all_wavefunctions()):
3863 wf.set('number', i + 1)
3864 for i, amp in enumerate(self.get_all_amplitudes()):
3865 amp.set('number', i + 1)
3866
3867 amp.calculate_fermionfactor()
3868
3869 amp.set('color_indices', amp.get_color_indices())
3870
3871
3872
3873 self.identical_decay_chain_factor(decay_dict.values())
3874
3875
3876 - def insert_decay(self, old_wfs, decay, numbers, got_majoranas):
3877 """Insert a decay chain matrix element into the matrix element.
3878 * old_wfs: the wavefunctions to be replaced.
3879 They all correspond to the same external particle, but might
3880 have different fermion flow directions
3881 * decay: the matrix element for the decay chain
3882 * numbers: the present wavefunction and amplitude number,
3883 to allow for unique numbering
3884
3885 Note that:
3886 1) All amplitudes and all wavefunctions using the decaying wf
3887 must be copied as many times as there are amplitudes in the
3888 decay matrix element
3889 2) In the presence of Majorana particles, we must make sure
3890 to flip fermion flow for the decay process if needed.
3891
3892 The algorithm is the following:
3893 1) Multiply the diagrams with the number of diagrams Ndiag in
3894 the decay element
3895 2) For each diagram in the decay element, work on the diagrams
3896 which corresponds to it
3897 3) Flip fermion flow for the decay wavefunctions if needed
3898 4) Insert all auxiliary wavefunctions into the diagram (i.e., all
3899 except the final wavefunctions, which directly replace the
3900 original final state wavefunctions)
3901 4) Replace the wavefunctions recursively, so that we always replace
3902 each old wavefunctions with Namp new ones, where Namp is
3903 the number of amplitudes in this decay element
3904 diagram. Do recursion for wavefunctions which have this
3905 wavefunction as mother. Simultaneously replace any
3906 amplitudes which have this wavefunction as mother.
3907 """
3908
3909 len_decay = len(decay.get('diagrams'))
3910
3911 number_external = old_wfs[0].get('number_external')
3912
3913
3914 for process in self.get('processes'):
3915 process.get('decay_chains').append(\
3916 decay.get('processes')[0])
3917
3918
3919
3920
3921 decay_elements = [copy.deepcopy(d) for d in \
3922 [ decay.get('diagrams') ] * len(old_wfs)]
3923
3924
3925
3926 for decay_element in decay_elements:
3927 for idiag, diagram in enumerate(decay.get('diagrams')):
3928 wfs = diagram.get('wavefunctions')
3929 decay_diag = decay_element[idiag]
3930 for i, wf in enumerate(decay_diag.get('wavefunctions')):
3931 wf.set('particle', wfs[i].get('particle'))
3932 wf.set('antiparticle', wfs[i].get('antiparticle'))
3933
3934 for decay_element in decay_elements:
3935
3936
3937 for decay_diag in decay_element:
3938 for wf in filter(lambda wf: wf.get('number_external') == 1,
3939 decay_diag.get('wavefunctions')):
3940 decay_diag.get('wavefunctions').remove(wf)
3941
3942 decay_wfs = sum([d.get('wavefunctions') for d in decay_element], [])
3943
3944
3945 incr_new = number_external - \
3946 decay_wfs[0].get('number_external')
3947
3948 for wf in decay_wfs:
3949
3950 wf.set('number_external', wf.get('number_external') + incr_new)
3951
3952 numbers[0] = numbers[0] + 1
3953 wf.set('number', numbers[0])
3954
3955
3956
3957 (nex, nin) = decay.get_nexternal_ninitial()
3958 incr_old = nex - 2
3959 wavefunctions = self.get_all_wavefunctions()
3960 for wf in wavefunctions:
3961
3962 if wf.get('number_external') > number_external:
3963 wf.set('number_external',
3964 wf.get('number_external') + incr_old)
3965
3966
3967
3968 diagrams = HelasDiagramList()
3969 for diagram in self.get('diagrams'):
3970 new_diagrams = [copy.copy(diag) for diag in \
3971 [ diagram ] * (len_decay - 1)]
3972
3973 diagram.set('number', (diagram.get('number') - 1) * \
3974 len_decay + 1)
3975
3976 for i, diag in enumerate(new_diagrams):
3977
3978 diag.set('number', diagram.get('number') + i + 1)
3979
3980 diag.set('wavefunctions',
3981 copy.copy(diagram.get('wavefunctions')))
3982
3983 amplitudes = HelasAmplitudeList(\
3984 [copy.copy(amp) for amp in \
3985 diag.get('amplitudes')])
3986
3987 for amp in amplitudes:
3988 numbers[1] = numbers[1] + 1
3989 amp.set('number', numbers[1])
3990 diag.set('amplitudes', amplitudes)
3991
3992 diagrams.append(diagram)
3993 diagrams.extend(new_diagrams)
3994
3995 self.set('diagrams', diagrams)
3996
3997
3998 for numdecay in range(len_decay):
3999
4000
4001 diagrams = [self.get('diagrams')[i] for i in \
4002 range(numdecay, len(self.get('diagrams')), len_decay)]
4003
4004
4005 for decay_element, old_wf in zip(decay_elements, old_wfs):
4006
4007 decay_diag = decay_element[numdecay]
4008
4009
4010 my_diagrams = filter(lambda diag: (old_wf.get('number') in \
4011 [wf.get('number') for wf in \
4012 diag.get('wavefunctions')]),
4013 diagrams)
4014
4015
4016 if len(my_diagrams) > 1:
4017 raise self.PhysicsObjectError, \
4018 "Decay chains not yet prepared for GPU"
4019
4020 for diagram in my_diagrams:
4021
4022 if got_majoranas:
4023
4024
4025
4026
4027
4028 index = [d.get('number') for d in diagrams].\
4029 index(diagram.get('number'))
4030 earlier_wavefunctions = \
4031 sum([d.get('wavefunctions') for d in \
4032 diagrams[:index]], [])
4033
4034
4035
4036 decay_diag_wfs = copy.deepcopy(\
4037 decay_diag.get('wavefunctions'))
4038
4039
4040 for i, wf in enumerate(decay_diag.get('wavefunctions')):
4041 decay_diag_wfs[i].set('particle', \
4042 wf.get('particle'))
4043 decay_diag_wfs[i].set('antiparticle', \
4044 wf.get('antiparticle'))
4045
4046
4047
4048 decay_diag_wfs = decay_diag_wfs.insert_own_mothers()
4049
4050
4051 final_decay_wfs = [amp.get('mothers')[1] for amp in \
4052 decay_diag.get('amplitudes')]
4053
4054
4055 for i, wf in enumerate(final_decay_wfs):
4056 final_decay_wfs[i] = \
4057 decay_diag_wfs[decay_diag_wfs.index(wf)]
4058
4059
4060
4061
4062 for wf in final_decay_wfs:
4063 decay_diag_wfs.remove(wf)
4064
4065
4066 if old_wf.is_fermion() and \
4067 old_wf.get_with_flow('state') != \
4068 final_decay_wfs[0].get_with_flow('state'):
4069
4070
4071
4072 for i, wf in enumerate(final_decay_wfs):
4073
4074
4075
4076
4077
4078
4079
4080 final_decay_wfs[i], numbers[0] = \
4081 wf.check_majorana_and_flip_flow(\
4082 True,
4083 earlier_wavefunctions,
4084 decay_diag_wfs,
4085 {},
4086 numbers[0])
4087
4088
4089
4090 i = 0
4091 earlier_wavefunctions = \
4092 sum([d.get('wavefunctions') for d in \
4093 self.get('diagrams')[:diagram.get('number') - 1]], \
4094 [])
4095 earlier_wf_numbers = [wf.get('number') for wf in \
4096 earlier_wavefunctions]
4097
4098 i = 0
4099 mother_arrays = [w.get('mothers').to_array() for \
4100 w in final_decay_wfs]
4101 while decay_diag_wfs[i:]:
4102 wf = decay_diag_wfs[i]
4103 try:
4104 new_wf = earlier_wavefunctions[\
4105 earlier_wf_numbers.index(wf.get('number'))]
4106
4107
4108
4109 if wf != new_wf:
4110 numbers[0] = numbers[0] + 1
4111 wf.set('number', numbers[0])
4112 continue
4113 decay_diag_wfs.pop(i)
4114 pres_mother_arrays = [w.get('mothers').to_array() for \
4115 w in decay_diag_wfs[i:]] + \
4116 mother_arrays
4117 self.update_later_mothers(wf, new_wf,
4118 decay_diag_wfs[i:] + \
4119 final_decay_wfs,
4120 pres_mother_arrays)
4121 except ValueError:
4122 i = i + 1
4123
4124
4125
4126 for decay_wf in decay_diag_wfs + final_decay_wfs:
4127 mothers = decay_wf.get('mothers')
4128 for i, wf in enumerate(mothers):
4129 try:
4130 mothers[i] = earlier_wavefunctions[\
4131 earlier_wf_numbers.index(wf.get('number'))]
4132 except ValueError:
4133 pass
4134 else:
4135
4136
4137 decay_diag_wfs = \
4138 copy.copy(decay_diag.get('wavefunctions'))
4139
4140
4141
4142 final_decay_wfs = [amp.get('mothers')[1] for amp in \
4143 decay_diag.get('amplitudes')]
4144
4145
4146
4147
4148 for wf in final_decay_wfs:
4149 decay_diag_wfs.remove(wf)
4150
4151
4152 diagram_wfs = diagram.get('wavefunctions')
4153
4154 old_wf_index = [wf.get('number') for wf in \
4155 diagram_wfs].index(old_wf.get('number'))
4156
4157 diagram_wfs = diagram_wfs[0:old_wf_index] + \
4158 decay_diag_wfs + diagram_wfs[old_wf_index:]
4159
4160 diagram.set('wavefunctions', HelasWavefunctionList(diagram_wfs))
4161
4162
4163
4164
4165 for wf in final_decay_wfs:
4166 wf.set('onshell', True)
4167
4168 if len_decay == 1 and len(final_decay_wfs) == 1:
4169
4170 self.replace_single_wavefunction(old_wf,
4171 final_decay_wfs[0])
4172 else:
4173
4174
4175 self.replace_wavefunctions(old_wf,
4176 final_decay_wfs,
4177 diagrams,
4178 numbers)
4179
4180
4181
4182
4183 for diagram in diagrams:
4184
4185
4186
4187 earlier_wfs = sum([d.get('wavefunctions') for d in \
4188 self.get('diagrams')[\
4189 diagram.get('number') - numdecay - 1:\
4190 diagram.get('number') - 1]], [])
4191
4192 later_wfs = sum([d.get('wavefunctions') for d in \
4193 self.get('diagrams')[\
4194 diagram.get('number'):]], [])
4195
4196 later_amps = sum([d.get('amplitudes') for d in \
4197 self.get('diagrams')[\
4198 diagram.get('number') - 1:]], [])
4199
4200 i = 0
4201 diag_wfs = diagram.get('wavefunctions')
4202
4203
4204
4205
4206
4207 mother_arrays = [w.get('mothers').to_array() for \
4208 w in later_wfs + later_amps]
4209
4210 while diag_wfs[i:]:
4211 try:
4212 index = [w.get('number') for w in earlier_wfs].\
4213 index(diag_wfs[i].get('number'))
4214 wf = diag_wfs.pop(i)
4215 pres_mother_arrays = [w.get('mothers').to_array() for \
4216 w in diag_wfs[i:]] + \
4217 mother_arrays
4218 self.update_later_mothers(wf, earlier_wfs[index],
4219 diag_wfs[i:] + later_wfs + later_amps,
4220 pres_mother_arrays)
4221 except ValueError:
4222 i = i + 1
4223
4225 """Update mothers for all later wavefunctions"""
4226
4227 daughters = filter(lambda tup: wf.get('number') in tup[1],
4228 enumerate(later_wf_arrays))
4229
4230 for (index, mothers) in daughters:
4231 try:
4232
4233 later_wfs[index].get('mothers')[\
4234 mothers.index(wf.get('number'))] = new_wf
4235 except ValueError:
4236 pass
4237
4240 """Recursive function to replace old_wf with new_wfs, and
4241 multiply all wavefunctions or amplitudes that use old_wf
4242
4243 * old_wf: The wavefunction to be replaced
4244 * new_wfs: The replacing wavefunction
4245 * diagrams - the diagrams that are relevant for these new
4246 wavefunctions.
4247 * numbers: the present wavefunction and amplitude number,
4248 to allow for unique numbering
4249 """
4250
4251
4252 my_diagrams = filter(lambda diag: old_wf.get('number') in \
4253 [wf.get('number') for wf in diag.get('wavefunctions')],
4254 diagrams)
4255
4256
4257 for diagram in my_diagrams:
4258
4259 diagram_wfs = diagram.get('wavefunctions')
4260
4261 old_wf_index = [wf.get('number') for wf in \
4262 diagram.get('wavefunctions')].index(old_wf.get('number'))
4263 diagram_wfs = diagram_wfs[:old_wf_index] + \
4264 new_wfs + diagram_wfs[old_wf_index + 1:]
4265
4266 diagram.set('wavefunctions', HelasWavefunctionList(diagram_wfs))
4267
4268
4269
4270
4271
4272 amp_diagrams = filter(lambda diag: old_wf.get('number') in \
4273 sum([[wf.get('number') for wf in amp.get('mothers')] \
4274 for amp in diag.get('amplitudes')], []),
4275 diagrams)
4276
4277 for diagram in amp_diagrams:
4278
4279
4280 daughter_amps = filter(lambda amp: old_wf.get('number') in \
4281 [wf.get('number') for wf in amp.get('mothers')],
4282 diagram.get('amplitudes'))
4283
4284 new_amplitudes = copy.copy(diagram.get('amplitudes'))
4285
4286
4287
4288 for old_amp in daughter_amps:
4289
4290 new_amps = [copy.copy(amp) for amp in \
4291 [ old_amp ] * len(new_wfs)]
4292
4293 for i, (new_amp, new_wf) in enumerate(zip(new_amps, new_wfs)):
4294 mothers = copy.copy(new_amp.get('mothers'))
4295 old_wf_index = [wf.get('number') for wf in mothers].index(\
4296 old_wf.get('number'))
4297
4298 mothers[old_wf_index] = new_wf
4299 new_amp.set('mothers', mothers)
4300
4301 numbers[1] = numbers[1] + 1
4302 new_amp.set('number', numbers[1])
4303
4304
4305 index = [a.get('number') for a in new_amplitudes].\
4306 index(old_amp.get('number'))
4307 new_amplitudes = new_amplitudes[:index] + \
4308 new_amps + new_amplitudes[index + 1:]
4309
4310
4311 diagram.set('amplitudes', HelasAmplitudeList(new_amplitudes))
4312
4313
4314 daughter_wfs = filter(lambda wf: old_wf.get('number') in \
4315 [wf1.get('number') for wf1 in wf.get('mothers')],
4316 sum([diag.get('wavefunctions') for diag in \
4317 diagrams], []))
4318
4319
4320 for daughter_wf in daughter_wfs:
4321
4322
4323 wf_diagrams = filter(lambda diag: daughter_wf.get('number') in \
4324 [wf.get('number') for wf in \
4325 diag.get('wavefunctions')],
4326 diagrams)
4327
4328 if len(wf_diagrams) > 1:
4329 raise self.PhysicsObjectError, \
4330 "Decay chains not yet prepared for GPU"
4331
4332 for diagram in wf_diagrams:
4333
4334
4335 replace_daughters = [ copy.copy(wf) for wf in \
4336 [daughter_wf] * len(new_wfs) ]
4337
4338 index = [wf.get('number') for wf in \
4339 daughter_wf.get('mothers')].index(old_wf.get('number'))
4340
4341
4342 for i, (new_daughter, new_wf) in \
4343 enumerate(zip(replace_daughters, new_wfs)):
4344 mothers = copy.copy(new_daughter.get('mothers'))
4345 mothers[index] = new_wf
4346 new_daughter.set('mothers', mothers)
4347 numbers[0] = numbers[0] + 1
4348 new_daughter.set('number', numbers[0])
4349
4350
4351
4352
4353
4354 self.replace_wavefunctions(daughter_wf,
4355 replace_daughters,
4356 diagrams,
4357 numbers)
4358
4360 """Insert decay chain by simply modifying wavefunction. This
4361 is possible only if there is only one diagram in the decay."""
4362
4363 for key in old_wf.keys():
4364 old_wf.set(key, new_wf[key])
4365
4367 """Calculate the denominator factor from identical decay chains"""
4368
4369 final_legs = [leg.get('id') for leg in \
4370 filter(lambda leg: leg.get('state') == True, \
4371 self.get('processes')[0].get('legs'))]
4372
4373
4374 decay_ids = [decay.get('legs')[0].get('id') for decay in \
4375 self.get('processes')[0].get('decay_chains')]
4376
4377
4378 non_decay_legs = filter(lambda id: id not in decay_ids,
4379 final_legs)
4380
4381
4382 identical_indices = {}
4383 for id in non_decay_legs:
4384 if id in identical_indices:
4385 identical_indices[id] = \
4386 identical_indices[id] + 1
4387 else:
4388 identical_indices[id] = 1
4389 non_chain_factor = reduce(lambda x, y: x * y,
4390 [ math.factorial(val) for val in \
4391 identical_indices.values() ], 1)
4392
4393
4394
4395 chains = copy.copy(decay_chains)
4396 iden_chains_factor = 1
4397 while chains:
4398 ident_copies = 1
4399 first_chain = chains.pop(0)
4400 i = 0
4401 while i < len(chains):
4402 chain = chains[i]
4403 if HelasMatrixElement.check_equal_decay_processes(\
4404 first_chain, chain):
4405 ident_copies = ident_copies + 1
4406 chains.pop(i)
4407 else:
4408 i = i + 1
4409 iden_chains_factor = iden_chains_factor * \
4410 math.factorial(ident_copies)
4411
4412 self['identical_particle_factor'] = non_chain_factor * \
4413 iden_chains_factor * \
4414 reduce(lambda x1, x2: x1 * x2,
4415 [me.get('identical_particle_factor') \
4416 for me in decay_chains], 1)
4417
4419 """Generate the fermion factors for all diagrams in the matrix element
4420 """
4421 for diagram in self.get('diagrams'):
4422 for amplitude in diagram.get('amplitudes'):
4423 amplitude.get('fermionfactor')
4424
4426 """Calculate the denominator factor for identical final state particles
4427 """
4428
4429 self["identical_particle_factor"] = self.get('processes')[0].\
4430 identical_particle_factor()
4431
4433 """Generate a diagram_generation.Amplitude from a
4434 HelasMatrixElement. This is used to generate both color
4435 amplitudes and diagram drawing."""
4436
4437
4438
4439
4440 optimization = 1
4441 if len(filter(lambda wf: wf.get('number') == 1,
4442 self.get_all_wavefunctions())) > 1:
4443 optimization = 0
4444
4445 model = self.get('processes')[0].get('model')
4446
4447 wf_dict = {}
4448 vx_list = []
4449 diagrams = base_objects.DiagramList()
4450 for diag in self.get('diagrams'):
4451 diagrams.append(diag.get('amplitudes')[0].get_base_diagram(\
4452 wf_dict, vx_list, optimization))
4453
4454 for diag in diagrams:
4455 diag.calculate_orders(self.get('processes')[0].get('model'))
4456
4457 return diagram_generation.Amplitude({\
4458 'process': self.get('processes')[0],
4459 'diagrams': diagrams})
4460
4461
4462
4463 - def getmothers(self, legs, number_to_wavefunctions,
4464 external_wavefunctions, wavefunctions,
4465 diagram_wavefunctions):
4466 """Generate list of mothers from number_to_wavefunctions and
4467 external_wavefunctions"""
4468
4469 mothers = HelasWavefunctionList()
4470
4471 for leg in legs:
4472 try:
4473
4474 wf = number_to_wavefunctions[leg.get('number')]
4475 except KeyError:
4476
4477 wf = external_wavefunctions[leg.get('number')]
4478 number_to_wavefunctions[leg.get('number')] = wf
4479 if not wf in wavefunctions and not wf in diagram_wavefunctions:
4480 diagram_wavefunctions.append(wf)
4481 mothers.append(wf)
4482
4483 return mothers
4484
4486 """Get number of diagrams, which is always more than number of
4487 configs"""
4488
4489 model = self.get('processes')[0].\
4490 get('model')
4491
4492 next, nini = self.get_nexternal_ninitial()
4493 return sum([d.get_num_configs(model, nini) for d in \
4494 self.get('base_amplitude').get('diagrams')])
4495
4497 """Gives the total number of wavefunctions for this ME"""
4498
4499 out = max([wf.get('me_id') for wfs in self.get('diagrams')
4500 for wf in wfs.get('wavefunctions')])
4501 if out:
4502 return out
4503 return sum([ len(d.get('wavefunctions')) for d in \
4504 self.get('diagrams')])
4505
4507 """Gives a list of all wavefunctions for this ME"""
4508
4509 return sum([d.get('wavefunctions') for d in \
4510 self.get('diagrams')], [])
4511
4513 """Gives a list of all amplitudes for this ME"""
4514
4515 return sum([d.get('amplitudes') for d in \
4516 self.get('diagrams')], [])
4517
4519 """Gives the external wavefunctions for this ME"""
4520
4521 external_wfs = filter(lambda wf: not wf.get('mothers'),
4522 self.get('diagrams')[0].get('wavefunctions'))
4523
4524 external_wfs.sort(lambda w1, w2: w1.get('number_external') - \
4525 w2.get('number_external'))
4526
4527 i = 1
4528 while i < len(external_wfs):
4529 if external_wfs[i].get('number_external') == \
4530 external_wfs[i - 1].get('number_external'):
4531 external_wfs.pop(i)
4532 else:
4533 i = i + 1
4534 return external_wfs
4535
4537 """Gives the total number of amplitudes for this ME"""
4538
4539 return sum([ len(d.get('amplitudes')) for d in \
4540 self.get('diagrams')])
4541
4543 """Gives (number or external particles, number of
4544 incoming particles)"""
4545
4546 external_wfs = filter(lambda wf: not wf.get('mothers'),
4547 self.get_all_wavefunctions())
4548
4549 return (len(set([wf.get('number_external') for wf in \
4550 external_wfs])),
4551 len(set([wf.get('number_external') for wf in \
4552 filter(lambda wf: wf.get('leg_state') == False,
4553 external_wfs)])))
4554
4556 """Gives the list of the strings corresponding to the masses of the
4557 external particles."""
4558
4559 mass_list=[]
4560 external_wfs = sorted(filter(lambda wf: wf.get('leg_state') != \
4561 'intermediate', self.get_all_wavefunctions()),\
4562 key=lambda w: w['number_external'])
4563 external_number=1
4564 for wf in external_wfs:
4565 if wf.get('number_external')==external_number:
4566 external_number=external_number+1
4567 mass_list.append(wf.get('particle').get('mass'))
4568
4569 return mass_list
4570
4572 """Gives the number of helicity combinations for external
4573 wavefunctions"""
4574
4575 if not self.get('processes'):
4576 return None
4577
4578 model = self.get('processes')[0].get('model')
4579
4580 return reduce(lambda x, y: x * y,
4581 [ len(model.get('particle_dict')[wf.get('pdg_code')].\
4582 get_helicity_states())\
4583 for wf in self.get_external_wavefunctions() ], 1)
4584
4586 """Gives the helicity matrix for external wavefunctions"""
4587
4588 if not self.get('processes'):
4589 return None
4590
4591 process = self.get('processes')[0]
4592 model = process.get('model')
4593
4594 return apply(itertools.product, [ model.get('particle_dict')[\
4595 wf.get('pdg_code')].get_helicity_states(allow_reverse)\
4596 for wf in self.get_external_wavefunctions()])
4597
4599 """ Calculate the denominator factor due to the average over initial
4600 state spin only """
4601
4602 model = self.get('processes')[0].get('model')
4603 initial_legs = filter(lambda leg: leg.get('state') == False, \
4604 self.get('processes')[0].get('legs'))
4605
4606 return reduce(lambda x, y: x * y,
4607 [ len(model.get('particle_dict')[leg.get('id')].\
4608 get_helicity_states())\
4609 for leg in initial_legs ])
4610
4612 """ Calculate the denominator factor due to the average over initial
4613 state spin only. Returns the result for beam one and two separately
4614 so that the averaging can be done correctly for partial polarization."""
4615
4616 model = self.get('processes')[0].get('model')
4617 initial_legs = filter(lambda leg: leg.get('state') == False, \
4618 self.get('processes')[0].get('legs'))
4619
4620 beam_avg_factors = [ len(model.get('particle_dict')[leg.get('id')].\
4621 get_helicity_states()) for leg in initial_legs ]
4622 if len(beam_avg_factors)==1:
4623
4624 return beam_avg_factors[0],1
4625 else:
4626 return beam_avg_factors[0],beam_avg_factors[1]
4627
4629 """Calculate the denominator factor due to:
4630 Averaging initial state color and spin, and
4631 identical final state particles"""
4632
4633 model = self.get('processes')[0].get('model')
4634
4635 initial_legs = filter(lambda leg: leg.get('state') == False, \
4636 self.get('processes')[0].get('legs'))
4637
4638 color_factor = reduce(lambda x, y: x * y,
4639 [ model.get('particle_dict')[leg.get('id')].\
4640 get('color')\
4641 for leg in initial_legs ])
4642
4643 spin_factor = reduce(lambda x, y: x * y,
4644 [ len(model.get('particle_dict')[leg.get('id')].\
4645 get_helicity_states())\
4646 for leg in initial_legs ])
4647
4648 return spin_factor * color_factor * self['identical_particle_factor']
4649
4651 """ Return a list of (coefficient, amplitude number) lists,
4652 corresponding to the JAMPs for the HelasDiagrams and color basis passed
4653 in argument. The coefficients are given in the format (fermion factor,
4654 colorcoeff (frac), imaginary, Nc power). """
4655
4656 if not color_basis:
4657
4658
4659 col_amp = []
4660 for diagram in diagrams:
4661 for amplitude in diagram.get('amplitudes'):
4662 col_amp.append(((amplitude.get('fermionfactor'),
4663 1, False, 0),
4664 amplitude.get('number')))
4665 return [col_amp]
4666
4667
4668
4669
4670 col_amp_list = []
4671 for i, col_basis_elem in \
4672 enumerate(sorted(color_basis.keys())):
4673
4674 col_amp = []
4675 for diag_tuple in color_basis[col_basis_elem]:
4676 res_amps = filter(lambda amp: \
4677 tuple(amp.get('color_indices')) == diag_tuple[1],
4678 diagrams[diag_tuple[0]].get('amplitudes'))
4679 if not res_amps:
4680 raise self.PhysicsObjectError, \
4681 """No amplitude found for color structure
4682 %s and color index chain (%s) (diagram %i)""" % \
4683 (col_basis_elem,
4684 str(diag_tuple[1]),
4685 diag_tuple[0])
4686
4687 for res_amp in res_amps:
4688 col_amp.append(((res_amp.get('fermionfactor'),
4689 diag_tuple[2],
4690 diag_tuple[3],
4691 diag_tuple[4]),
4692 res_amp.get('number')))
4693
4694 col_amp_list.append(col_amp)
4695
4696 return col_amp_list
4697
4699 """Return a list of (coefficient, amplitude number) lists,
4700 corresponding to the JAMPs for this matrix element. The
4701 coefficients are given in the format (fermion factor, color
4702 coeff (frac), imaginary, Nc power)."""
4703
4704 return self.generate_color_amplitudes(self['color_basis'],self['diagrams'])
4705
4707 """ Sort the 'split_orders' list given in argument so that the orders of
4708 smaller weights appear first. Do nothing if not all split orders have
4709 a hierarchy defined."""
4710 order_hierarchy=\
4711 self.get('processes')[0].get('model').get('order_hierarchy')
4712 if set(order_hierarchy.keys()).union(set(split_orders))==\
4713 set(order_hierarchy.keys()):
4714 split_orders.sort(key=lambda order: order_hierarchy[order])
4715
4719 """ This a helper function for get_split_orders_mapping to return, for
4720 the HelasDiagram list given in argument, the list amp_orders detailed in
4721 the description of the 'get_split_orders_mapping' function.
4722 """
4723
4724 order_hierarchy=\
4725 self.get('processes')[0].get('model').get('order_hierarchy')
4726
4727
4728
4729 amp_orders={}
4730 for diag in diag_list:
4731 diag_orders=diag.calculate_orders()
4732
4733 diag_orders['WEIGHTED']=sum(order_hierarchy[order]*value for \
4734 order, value in diag_orders.items())
4735
4736 for order in split_orders:
4737 if not order in diag_orders.keys():
4738 diag_orders[order]=0
4739 key = tuple([diag_orders[order] for order in split_orders])
4740 try:
4741 amp_orders[key].extend([get_amp_number_function(amp) for amp in \
4742 get_amplitudes_function(diag)])
4743 except KeyError:
4744 amp_orders[key] = [get_amp_number_function(amp) for amp in \
4745 get_amplitudes_function(diag)]
4746 amp_orders=[(order[0],tuple(order[1])) for order in amp_orders.items()]
4747
4748
4749 if set(order_hierarchy.keys()).union(set(split_orders))==\
4750 set(order_hierarchy.keys()):
4751
4752 amp_orders.sort(key= lambda elem:
4753 sum([order_hierarchy[split_orders[i]]*order_power for \
4754 i, order_power in enumerate(elem[0])]))
4755
4756 return amp_orders
4757
4759 """This function returns two lists, squared_orders, amp_orders.
4760 If process['split_orders'] is empty, the function returns two empty lists.
4761
4762 squared_orders : All possible contributing squared orders among those
4763 specified in the process['split_orders'] argument. The elements of
4764 the list are tuples of the format format (OrderValue1,OrderValue2,...)
4765 with OrderValue<i> correspond to the value of the <i>th order in
4766 process['split_orders'] (the others are summed over and therefore
4767 left unspecified).
4768 Ex for dijet with process['split_orders']=['QCD','QED']:
4769 => [(4,0),(2,2),(0,4)]
4770
4771 amp_orders : Exactly as for squared order except that this list specifies
4772 the contributing order values for the amplitude (i.e. not 'squared').
4773 Also, the tuple describing the amplitude order is nested with a
4774 second one listing all amplitude numbers contributing to this order.
4775 Ex for dijet with process['split_orders']=['QCD','QED']:
4776 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))]
4777
4778 Keep in mind that the orders of the element of the list is important as
4779 it dicatates the order of the corresponding "order indices" in the
4780 code output by the exporters.
4781 """
4782
4783 split_orders=self.get('processes')[0].get('split_orders')
4784
4785 if len(split_orders)==0:
4786 return (),()
4787
4788
4789
4790 self.sort_split_orders(split_orders)
4791
4792 amp_orders = self.get_split_orders_mapping_for_diagram_list(\
4793 self.get('diagrams'),split_orders)
4794
4795
4796
4797 squared_orders = []
4798 for i, amp_order in enumerate(amp_orders):
4799 for j in range(0,i+1):
4800 key = tuple([ord1 + ord2 for ord1,ord2 in \
4801 zip(amp_order[0],amp_orders[j][0])])
4802
4803
4804
4805 if not key in squared_orders:
4806 squared_orders.append(key)
4807
4808 return squared_orders, amp_orders
4809
4810
4811
4823
4825 """Return a list with all couplings used by this
4826 HelasMatrixElement."""
4827
4828 tmp = [wa.get('coupling') for wa in \
4829 self.get_all_wavefunctions() + self.get_all_amplitudes() \
4830 if wa.get('interaction_id') not in [0,-1]]
4831
4832 return [ [t] if not t.startswith('-') else [t[1:]] for t2 in tmp for t in t2]
4833
4834
4836 """Return a list of processes with initial states interchanged
4837 if has mirror processes"""
4838
4839 if not self.get('has_mirror_process'):
4840 return []
4841 processes = base_objects.ProcessList()
4842 for proc in self.get('processes'):
4843 legs = copy.copy(proc.get('legs'))
4844 legs[0:2] = [legs[1],legs[0]]
4845 decay_legs = copy.copy(proc.get('legs_with_decays'))
4846 decay_legs[0:2] = [decay_legs[1],decay_legs[0]]
4847 process = copy.copy(proc)
4848 process.set('legs', legs)
4849 process.set('legs_with_decays', decay_legs)
4850 processes.append(process)
4851 return processes
4852
4853 @staticmethod
4855 """Check if two single-sided decay processes
4856 (HelasMatrixElements) are equal.
4857
4858 Note that this has to be called before any combination of
4859 processes has occured.
4860
4861 Since a decay processes for a decay chain is always generated
4862 such that all final state legs are completely contracted
4863 before the initial state leg is included, all the diagrams
4864 will have identical wave function, independently of the order
4865 of final state particles.
4866
4867 Note that we assume that the process definitions have all
4868 external particles, corresponding to the external
4869 wavefunctions.
4870 """
4871
4872 assert len(decay1.get('processes')) == 1 == len(decay2.get('processes')), \
4873 "Can compare only single process HelasMatrixElements"
4874
4875 assert len(filter(lambda leg: leg.get('state') == False, \
4876 decay1.get('processes')[0].get('legs'))) == 1 and \
4877 len(filter(lambda leg: leg.get('state') == False, \
4878 decay2.get('processes')[0].get('legs'))) == 1, \
4879 "Call to check_decay_processes_equal requires " + \
4880 "both processes to be unique"
4881
4882
4883
4884
4885 if len(decay1.get('processes')[0].get("legs")) != \
4886 len(decay2.get('processes')[0].get("legs")) or \
4887 len(decay1.get('diagrams')) != len(decay2.get('diagrams')) or \
4888 decay1.get('identical_particle_factor') != \
4889 decay2.get('identical_particle_factor') or \
4890 sum(len(d.get('wavefunctions')) for d in \
4891 decay1.get('diagrams')) != \
4892 sum(len(d.get('wavefunctions')) for d in \
4893 decay2.get('diagrams')) or \
4894 decay1.get('processes')[0].get('legs')[0].get('id') != \
4895 decay2.get('processes')[0].get('legs')[0].get('id') or \
4896 sorted([leg.get('id') for leg in \
4897 decay1.get('processes')[0].get('legs')[1:]]) != \
4898 sorted([leg.get('id') for leg in \
4899 decay2.get('processes')[0].get('legs')[1:]]):
4900 return False
4901
4902
4903
4904 if [leg.get('id') for leg in \
4905 decay1.get('processes')[0].get('legs')] == \
4906 [leg.get('id') for leg in \
4907 decay2.get('processes')[0].get('legs')] and \
4908 decay1 == decay2:
4909 return True
4910
4911
4912
4913
4914
4915
4916 amplitudes2 = copy.copy(reduce(lambda a1, d2: a1 + \
4917 d2.get('amplitudes'),
4918 decay2.get('diagrams'), []))
4919
4920 for amplitude1 in reduce(lambda a1, d2: a1 + d2.get('amplitudes'),
4921 decay1.get('diagrams'), []):
4922 foundamplitude = False
4923 for amplitude2 in amplitudes2:
4924 if HelasMatrixElement.check_equal_wavefunctions(\
4925 amplitude1.get('mothers')[-1],
4926 amplitude2.get('mothers')[-1]):
4927 foundamplitude = True
4928
4929 amplitudes2.remove(amplitude2)
4930 break
4931 if not foundamplitude:
4932 return False
4933
4934 return True
4935
4936 @staticmethod
4938 """Recursive function to check if two wavefunctions are equal.
4939 First check that mothers have identical pdg codes, then repeat for
4940 all mothers with identical pdg codes."""
4941
4942
4943
4944 if sorted([wf.get('pdg_code') for wf in wf1.get('mothers')]) != \
4945 sorted([wf.get('pdg_code') for wf in wf2.get('mothers')]):
4946 return False
4947
4948
4949
4950
4951 if not wf1.get('mothers') and not wf2.get('mothers'):
4952 return True
4953
4954 mothers2 = copy.copy(wf2.get('mothers'))
4955
4956 for mother1 in wf1.get('mothers'):
4957
4958
4959 equalmothers = filter(lambda wf: wf.get('pdg_code') == \
4960 mother1.get('pdg_code'),
4961 mothers2)
4962 foundmother = False
4963 for mother2 in equalmothers:
4964 if HelasMatrixElement.check_equal_wavefunctions(\
4965 mother1, mother2):
4966 foundmother = True
4967
4968 mothers2.remove(mother2)
4969 break
4970 if not foundmother:
4971 return False
4972
4973 return True
4974
4975 @staticmethod
4977 """Gives a list of mother wavefunctions sorted according to
4978 1. The order of the particles in the interaction
4979 2. Cyclic reordering of particles in same spin group
4980 3. Fermions ordered IOIOIO... according to the pairs in
4981 the interaction."""
4982
4983 assert isinstance(arg, (HelasWavefunction, HelasAmplitude)), \
4984 "%s is not a valid HelasWavefunction or HelasAmplitude" % repr(arg)
4985
4986 if not arg.get('interaction_id'):
4987 return arg.get('mothers')
4988
4989 my_pdg_code = 0
4990 my_spin = 0
4991 if isinstance(arg, HelasWavefunction):
4992 my_pdg_code = arg.get_anti_pdg_code()
4993 my_spin = arg.get_spin_state_number()
4994
4995 sorted_mothers, my_index = arg.get('mothers').sort_by_pdg_codes(\
4996 arg.get('pdg_codes'), my_pdg_code)
4997
4998
4999 partner = None
5000 if isinstance(arg, HelasWavefunction) and arg.is_fermion():
5001
5002 if my_index % 2 == 0:
5003
5004 partner_index = my_index
5005 else:
5006
5007 partner_index = my_index - 1
5008 partner = sorted_mothers.pop(partner_index)
5009
5010 if partner.get_spin_state_number() > 0:
5011 my_index = partner_index
5012 else:
5013 my_index = partner_index + 1
5014
5015
5016 for i in range(0, len(sorted_mothers), 2):
5017 if sorted_mothers[i].is_fermion():
5018
5019 if sorted_mothers[i].get_spin_state_number() > 0 and \
5020 sorted_mothers[i + 1].get_spin_state_number() < 0:
5021
5022 sorted_mothers = sorted_mothers[:i] + \
5023 [sorted_mothers[i+1], sorted_mothers[i]] + \
5024 sorted_mothers[i+2:]
5025 elif sorted_mothers[i].get_spin_state_number() < 0 and \
5026 sorted_mothers[i + 1].get_spin_state_number() > 0:
5027
5028 pass
5029 else:
5030
5031 break
5032
5033
5034 if partner:
5035 sorted_mothers.insert(partner_index, partner)
5036
5037
5038 return HelasWavefunctionList(sorted_mothers)
5039
5044 """List of HelasMatrixElement objects
5045 """
5046
5048 """Test if object obj is a valid HelasMatrixElement for the list."""
5049
5050 return isinstance(obj, HelasMatrixElement)
5051
5053 pos = (i for i in xrange(len(self)) if self[i] is obj)
5054 for i in pos:
5055 del self[i]
5056 break
5057
5062 """HelasDecayChainProcess: If initiated with a DecayChainAmplitude
5063 object, generates the HelasMatrixElements for the core process(es)
5064 and decay chains. Then call combine_decay_chain_processes in order
5065 to generate the matrix elements for all combined processes."""
5066
5072
5073 - def filter(self, name, value):
5074 """Filter for valid process property values."""
5075
5076 if name == 'core_processes':
5077 if not isinstance(value, HelasMatrixElementList):
5078 raise self.PhysicsObjectError, \
5079 "%s is not a valid HelasMatrixElementList object" % \
5080 str(value)
5081
5082 if name == 'decay_chains':
5083 if not isinstance(value, HelasDecayChainProcessList):
5084 raise self.PhysicsObjectError, \
5085 "%s is not a valid HelasDecayChainProcessList object" % \
5086 str(value)
5087
5088 return True
5089
5091 """Return process property names as a nicely sorted list."""
5092
5093 return ['core_processes', 'decay_chains']
5094
5107
5109 """Returns a nicely formatted string of the matrix element processes."""
5110
5111 mystr = ""
5112
5113 for process in self.get('core_processes'):
5114 mystr += process.get('processes')[0].nice_string(indent) + "\n"
5115
5116 if self.get('decay_chains'):
5117 mystr += " " * indent + "Decays:\n"
5118 for dec in self.get('decay_chains'):
5119 mystr += dec.nice_string(indent + 2) + "\n"
5120
5121 return mystr[:-1]
5122
5124 """Generate the HelasMatrixElements for the core processes and
5125 decay processes (separately)"""
5126
5127 assert isinstance(dc_amplitude, diagram_generation.DecayChainAmplitude), \
5128 "%s is not a valid DecayChainAmplitude" % dc_amplitude
5129
5130
5131
5132
5133 decay_ids = dc_amplitude.get_decay_ids()
5134
5135 matrix_elements = HelasMultiProcess.generate_matrix_elements(\
5136 dc_amplitude.get('amplitudes'),
5137 False,
5138 decay_ids)
5139
5140 self.set('core_processes', matrix_elements)
5141
5142 while dc_amplitude.get('decay_chains'):
5143
5144 decay_chain = dc_amplitude.get('decay_chains').pop(0)
5145 self['decay_chains'].append(HelasDecayChainProcess(\
5146 decay_chain))
5147
5148
5150 """Recursive function to generate complete
5151 HelasMatrixElements, combining the core process with the decay
5152 chains.
5153
5154 * If the number of decay chains is the same as the number of
5155 decaying particles, apply each decay chain to the corresponding
5156 final state particle.
5157 * If the number of decay chains and decaying final state particles
5158 don't correspond, all decays applying to a given particle type are
5159 combined (without double counting).
5160 * combine allow to merge identical ME
5161 """
5162
5163
5164 if not self['decay_chains']:
5165
5166 return self['core_processes']
5167
5168
5169
5170 decay_elements = []
5171
5172 for decay_chain in self['decay_chains']:
5173
5174 decay_elements.append(decay_chain.combine_decay_chain_processes(combine))
5175
5176
5177 matrix_elements = HelasMatrixElementList()
5178
5179 me_tags = []
5180
5181 permutations = []
5182
5183
5184
5185 decay_is_ids = [[element.get('processes')[0].get_initial_ids()[0] \
5186 for element in elements]
5187 for elements in decay_elements]
5188
5189 while self['core_processes']:
5190
5191 core_process = self['core_processes'].pop(0)
5192
5193 fs_legs = filter(lambda leg: any([any([id == leg.get('id') for id \
5194 in is_ids]) for is_ids in decay_is_ids]),
5195 core_process.get('processes')[0].get_final_legs())
5196
5197 fs_ids = [leg.get('id') for leg in fs_legs]
5198
5199 fs_numbers = {}
5200 fs_indices = {}
5201 for i, leg in enumerate(fs_legs):
5202 fs_numbers[leg.get('id')] = \
5203 fs_numbers.setdefault(leg.get('id'), []) + \
5204 [leg.get('number')]
5205 fs_indices[leg.get('id')] = \
5206 fs_indices.setdefault(leg.get('id'), []) + \
5207 [i]
5208
5209 decay_lists = []
5210
5211 for fs_id in set(fs_ids):
5212
5213
5214
5215
5216 decay_list = []
5217
5218
5219
5220
5221
5222
5223
5224 chains = []
5225 if len(fs_legs) == len(decay_elements) and \
5226 all([fs in ids for (fs, ids) in \
5227 zip(fs_ids, decay_is_ids)]):
5228
5229
5230
5231 for index in fs_indices[fs_id]:
5232 chains.append(filter(lambda me: \
5233 me.get('processes')[0].\
5234 get_initial_ids()[0] == fs_id,
5235 decay_elements[index]))
5236
5237 if len(fs_legs) != len(decay_elements) or not chains or not chains[0]:
5238
5239
5240
5241 chain = sum([filter(lambda me: \
5242 me.get('processes')[0].\
5243 get_initial_ids()[0] == fs_id,
5244 decay_chain) for decay_chain in \
5245 decay_elements], [])
5246
5247 chains = [chain] * len(fs_numbers[fs_id])
5248
5249 red_decay_chains = []
5250 for prod in itertools.product(*chains):
5251
5252
5253
5254
5255
5256
5257 if sorted([p.get('processes')[0] for p in prod],
5258 lambda x1, x2: x1.compare_for_sort(x2)) \
5259 in red_decay_chains:
5260 continue
5261
5262
5263 red_decay_chains.append(\
5264 sorted([p.get('processes')[0] for p in prod],
5265 lambda x1, x2: x1.compare_for_sort(x2)))
5266
5267
5268 decay_list.append(zip(fs_numbers[fs_id], prod))
5269
5270 decay_lists.append(decay_list)
5271
5272
5273
5274 for decays in itertools.product(*decay_lists):
5275
5276
5277 decay_dict = dict(sum(decays, []))
5278
5279
5280 model_bk = core_process.get('processes')[0].get('model')
5281
5282 for i, process in enumerate(core_process.get('processes')):
5283 process.set('model',base_objects.Model())
5284 matrix_element = copy.deepcopy(core_process)
5285
5286 for i, process in enumerate(matrix_element.get('processes')):
5287 process.set('model', model_bk)
5288 core_process.get('processes')[i].set('model', model_bk)
5289
5290
5291 org_wfs = core_process.get_all_wavefunctions()
5292 for i, wf in enumerate(matrix_element.get_all_wavefunctions()):
5293 wf.set('particle', org_wfs[i].get('particle'))
5294 wf.set('antiparticle', org_wfs[i].get('antiparticle'))
5295
5296
5297 logger.info("Combine %s with decays %s" % \
5298 (core_process.get('processes')[0].nice_string().\
5299 replace('Process: ', ''), \
5300 ", ".join([d.get('processes')[0].nice_string().\
5301 replace('Process: ', '') \
5302 for d in decay_dict.values()])))
5303
5304 matrix_element.insert_decay_chains(decay_dict)
5305
5306 if combine:
5307 me_tag = IdentifyMETag.create_tag(\
5308 matrix_element.get_base_amplitude(),
5309 matrix_element.get('identical_particle_factor'))
5310 try:
5311 if not combine:
5312 raise ValueError
5313
5314
5315
5316 me_index = me_tags.index(me_tag)
5317 except ValueError:
5318
5319
5320 if matrix_element.get('processes') and \
5321 matrix_element.get('diagrams'):
5322 matrix_elements.append(matrix_element)
5323 if combine:
5324 me_tags.append(me_tag)
5325 permutations.append(me_tag[-1][0].\
5326 get_external_numbers())
5327 else:
5328 other_processes = matrix_elements[me_index].get('processes')
5329 logger.info("Combining process with %s" % \
5330 other_processes[0].nice_string().replace('Process: ', ''))
5331 for proc in matrix_element.get('processes'):
5332 other_processes.append(HelasMultiProcess.\
5333 reorder_process(proc,
5334 permutations[me_index],
5335 me_tag[-1][0].get_external_numbers()))
5336
5337 return matrix_elements
5338
5343 """List of HelasDecayChainProcess objects
5344 """
5345
5347 """Test if object obj is a valid HelasDecayChainProcess for the list."""
5348
5349 return isinstance(obj, HelasDecayChainProcess)
5350
5355 """HelasMultiProcess: If initiated with an AmplitudeList,
5356 generates the HelasMatrixElements for the Amplitudes, identifying
5357 processes with identical matrix elements"""
5358
5363
5364 - def filter(self, name, value):
5365 """Filter for valid process property values."""
5366
5367 if name == 'matrix_elements':
5368 if not isinstance(value, HelasMatrixElementList):
5369 raise self.PhysicsObjectError, \
5370 "%s is not a valid HelasMatrixElementList object" % str(value)
5371 return True
5372
5374 """Return process property names as a nicely sorted list."""
5375
5376 return ['matrix_elements']
5377
5378 - def __init__(self, argument=None, combine_matrix_elements=True,
5379 matrix_element_opts={}, compute_loop_nc = False):
5380 """Allow initialization with AmplitudeList. Matrix_element_opts are
5381 potential options to be passed to the constructor of the
5382 HelasMatrixElements created. By default it is none, but when called from
5383 LoopHelasProcess, this options will contain 'optimized_output'."""
5384
5385
5386 if isinstance(argument, diagram_generation.AmplitudeList):
5387 super(HelasMultiProcess, self).__init__()
5388 self.set('matrix_elements', self.generate_matrix_elements(argument,
5389 combine_matrix_elements = combine_matrix_elements,
5390 matrix_element_opts=matrix_element_opts,
5391 compute_loop_nc = compute_loop_nc))
5392 elif isinstance(argument, diagram_generation.MultiProcess):
5393 super(HelasMultiProcess, self).__init__()
5394 self.set('matrix_elements',
5395 self.generate_matrix_elements(argument.get('amplitudes'),
5396 combine_matrix_elements = combine_matrix_elements,
5397 matrix_element_opts = matrix_element_opts,
5398 compute_loop_nc = compute_loop_nc))
5399 elif isinstance(argument, diagram_generation.Amplitude):
5400 super(HelasMultiProcess, self).__init__()
5401 self.set('matrix_elements', self.generate_matrix_elements(\
5402 diagram_generation.AmplitudeList([argument]),
5403 combine_matrix_elements = combine_matrix_elements,
5404 matrix_element_opts = matrix_element_opts,
5405 compute_loop_nc = compute_loop_nc))
5406 elif argument:
5407
5408 super(HelasMultiProcess, self).__init__(argument)
5409 else:
5410
5411 super(HelasMultiProcess, self).__init__()
5412
5414 """Return a list of (lorentz_name, conjugate, outgoing) with
5415 all lorentz structures used by this HelasMultiProcess."""
5416 helas_list = []
5417
5418 for me in self.get('matrix_elements'):
5419 helas_list.extend(me.get_used_lorentz())
5420
5421 return list(set(helas_list))
5422
5424 """Return a list with all couplings used by this
5425 HelasMatrixElement."""
5426
5427 coupling_list = []
5428
5429 for me in self.get('matrix_elements'):
5430 coupling_list.extend([c for l in me.get_used_couplings() for c in l])
5431
5432 return list(set(coupling_list))
5433
5435 """Extract the list of matrix elements"""
5436
5437 return self.get('matrix_elements')
5438
5439
5440
5441
5442
5443 @classmethod
5444 - def process_color(cls,matrix_element, color_information, compute_loop_nc=None):
5445 """ Process the color information for a given matrix
5446 element made of a tree diagram. compute_loop_nc is dummy here for the
5447 tree-level Nc and present for structural reasons only."""
5448
5449 if compute_loop_nc:
5450 raise MadGraph5Error, "The tree-level function 'process_color' "+\
5451 " of class HelasMultiProcess cannot be called with a value for compute_loop_nc"
5452
5453
5454 for key in color_information:
5455 exec("%s=color_information['%s']"%(key,key))
5456
5457
5458
5459
5460 col_basis = color_amp.ColorBasis()
5461 new_amp = matrix_element.get_base_amplitude()
5462 matrix_element.set('base_amplitude', new_amp)
5463
5464 colorize_obj = col_basis.create_color_dict_list(\
5465 matrix_element.get('base_amplitude'))
5466
5467
5468
5469
5470 try:
5471
5472
5473
5474 col_index = list_colorize.index(colorize_obj)
5475 except ValueError:
5476
5477
5478 list_colorize.append(colorize_obj)
5479 col_basis.build()
5480 list_color_basis.append(col_basis)
5481 col_matrix = color_amp.ColorMatrix(col_basis)
5482 list_color_matrices.append(col_matrix)
5483 col_index = -1
5484 logger.info(\
5485 "Processing color information for %s" % \
5486 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
5487 replace('Process', 'process'))
5488 else:
5489 logger.info(\
5490 "Reusing existing color information for %s" % \
5491 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
5492 replace('Process', 'process'))
5493
5494 matrix_element.set('color_basis',
5495 list_color_basis[col_index])
5496 matrix_element.set('color_matrix',
5497 list_color_matrices[col_index])
5498
5499
5500
5501 matrix_element_class = HelasMatrixElement
5502
5503 @classmethod
5504 - def generate_matrix_elements(cls, amplitudes, gen_color = True,
5505 decay_ids = [], combine_matrix_elements = True,
5506 compute_loop_nc = False, matrix_element_opts = {}):
5507 """Generate the HelasMatrixElements for the amplitudes,
5508 identifying processes with identical matrix elements, as
5509 defined by HelasMatrixElement.__eq__. Returns a
5510 HelasMatrixElementList and an amplitude map (used by the
5511 SubprocessGroup functionality). decay_ids is a list of decayed
5512 particle ids, since those should not be combined even if
5513 matrix element is identical.
5514 The compute_loop_nc sets wheter independent tracking of Nc power coming
5515 from the color loop trace is necessary or not (it is time consuming).
5516 Matrix_element_opts are potential additional options to be passed to
5517 the HelasMatrixElements constructed."""
5518
5519 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \
5520 "%s is not valid AmplitudeList" % type(amplitudes)
5521
5522 combine = combine_matrix_elements
5523 if 'mode' in matrix_element_opts and matrix_element_opts['mode']=='MadSpin':
5524 combine = False
5525 del matrix_element_opts['mode']
5526
5527
5528
5529 list_colorize = []
5530 list_color_basis = []
5531 list_color_matrices = []
5532
5533
5534
5535
5536 dict_loopborn_matrices = {}
5537
5538
5539
5540 color_information = { 'list_colorize' : list_colorize,
5541 'list_color_basis' : list_color_basis,
5542 'list_color_matrices' : list_color_matrices,
5543 'dict_loopborn_matrices' : dict_loopborn_matrices}
5544
5545
5546 matrix_elements = HelasMatrixElementList()
5547
5548 identified_matrix_elements = []
5549
5550 amplitude_tags = []
5551
5552
5553
5554 permutations = []
5555 for amplitude in amplitudes:
5556 if isinstance(amplitude, diagram_generation.DecayChainAmplitude):
5557
5558 tmp_matrix_element_list = HelasDecayChainProcess(amplitude).\
5559 combine_decay_chain_processes(combine)
5560
5561 matrix_element_list = []
5562 for matrix_element in tmp_matrix_element_list:
5563 assert isinstance(matrix_element, HelasMatrixElement), \
5564 "Not a HelasMatrixElement: %s" % matrix_element
5565
5566
5567
5568 if not matrix_element.get('processes') or \
5569 not matrix_element.get('diagrams'):
5570 continue
5571
5572
5573 amplitude_tag = IdentifyMETag.create_tag(\
5574 matrix_element.get_base_amplitude())
5575 try:
5576 if not combine:
5577 raise ValueError
5578 me_index = amplitude_tags.index(amplitude_tag)
5579 except ValueError:
5580
5581 matrix_element_list.append(matrix_element)
5582 if combine_matrix_elements:
5583 amplitude_tags.append(amplitude_tag)
5584 identified_matrix_elements.append(matrix_element)
5585 permutations.append(amplitude_tag[-1][0].\
5586 get_external_numbers())
5587 else:
5588
5589 other_processes = identified_matrix_elements[me_index].\
5590 get('processes')
5591
5592
5593 for proc in matrix_element.get('processes'):
5594 other_processes.append(cls.reorder_process(\
5595 proc,
5596 permutations[me_index],
5597 amplitude_tag[-1][0].get_external_numbers()))
5598 logger.info("Combined %s with %s" % \
5599 (matrix_element.get('processes')[0].\
5600 nice_string().\
5601 replace('Process: ', 'process '),
5602 other_processes[0].nice_string().\
5603 replace('Process: ', 'process ')))
5604
5605 continue
5606 else:
5607
5608
5609
5610 amplitude_tag = IdentifyMETag.create_tag(amplitude)
5611 try:
5612 me_index = amplitude_tags.index(amplitude_tag)
5613 except ValueError:
5614
5615 logger.info("Generating Helas calls for %s" % \
5616 amplitude.get('process').nice_string().\
5617 replace('Process', 'process'))
5618
5619
5620
5621 matrix_element_list = [cls.matrix_element_class(amplitude,
5622 decay_ids=decay_ids,
5623 gen_color=False,
5624 **matrix_element_opts)]
5625 me = matrix_element_list[0]
5626 if me.get('processes') and me.get('diagrams'):
5627
5628 if combine_matrix_elements:
5629 amplitude_tags.append(amplitude_tag)
5630 identified_matrix_elements.append(me)
5631 permutations.append(amplitude_tag[-1][0].\
5632 get_external_numbers())
5633 else:
5634 matrix_element_list = []
5635 else:
5636
5637 other_processes = identified_matrix_elements[me_index].\
5638 get('processes')
5639 other_processes.append(cls.reorder_process(\
5640 amplitude.get('process'),
5641 permutations[me_index],
5642 amplitude_tag[-1][0].get_external_numbers()))
5643 logger.info("Combined %s with %s" % \
5644 (other_processes[-1].nice_string().\
5645 replace('Process: ', 'process '),
5646 other_processes[0].nice_string().\
5647 replace('Process: ', 'process ')))
5648
5649 continue
5650
5651
5652 for matrix_element in copy.copy(matrix_element_list):
5653 assert isinstance(matrix_element, HelasMatrixElement), \
5654 "Not a HelasMatrixElement: %s" % matrix_element
5655
5656
5657 matrix_elements.append(matrix_element)
5658
5659 if not gen_color:
5660 continue
5661
5662
5663
5664
5665 cls.process_color(matrix_element,color_information,\
5666 compute_loop_nc=compute_loop_nc)
5667
5668 if not matrix_elements:
5669 raise InvalidCmd, \
5670 "No matrix elements generated, check overall coupling orders"
5671
5672 return matrix_elements
5673
5674 @staticmethod
5676 """Reorder the legs in the process according to the difference
5677 between org_perm and proc_perm"""
5678
5679 leglist = base_objects.LegList(\
5680 [copy.copy(process.get('legs_with_decays')[i]) for i in \
5681 diagram_generation.DiagramTag.reorder_permutation(\
5682 proc_perm, org_perm)])
5683 new_proc = copy.copy(process)
5684 new_proc.set('legs_with_decays', leglist)
5685
5686 if not new_proc.get('decay_chains'):
5687 new_proc.set('legs', leglist)
5688
5689 return new_proc
5690