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
3164 """Calculate the actual coupling orders of this diagram"""
3165
3166 wavefunctions = HelasWavefunctionList.extract_wavefunctions(\
3167 self.get('amplitudes')[0].get('mothers'))
3168
3169 coupling_orders = {}
3170 for wf in wavefunctions + [self.get('amplitudes')[0]]:
3171 if not wf.get('orders'): continue
3172 for order in wf.get('orders').keys():
3173 try:
3174 coupling_orders[order] += wf.get('orders')[order]
3175 except Exception:
3176 coupling_orders[order] = wf.get('orders')[order]
3177
3178 return coupling_orders
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:
4613 Averaging initial state color and spin, and
4614 identical final state particles"""
4615
4616 model = self.get('processes')[0].get('model')
4617
4618 initial_legs = filter(lambda leg: leg.get('state') == False, \
4619 self.get('processes')[0].get('legs'))
4620
4621 color_factor = reduce(lambda x, y: x * y,
4622 [ model.get('particle_dict')[leg.get('id')].\
4623 get('color')\
4624 for leg in initial_legs ])
4625
4626 spin_factor = reduce(lambda x, y: x * y,
4627 [ len(model.get('particle_dict')[leg.get('id')].\
4628 get_helicity_states())\
4629 for leg in initial_legs ])
4630
4631 return spin_factor * color_factor * self['identical_particle_factor']
4632
4634 """ Return a list of (coefficient, amplitude number) lists,
4635 corresponding to the JAMPs for the HelasDiagrams and color basis passed
4636 in argument. The coefficients are given in the format (fermion factor,
4637 colorcoeff (frac), imaginary, Nc power). """
4638
4639 if not color_basis:
4640
4641
4642 col_amp = []
4643 for diagram in diagrams:
4644 for amplitude in diagram.get('amplitudes'):
4645 col_amp.append(((amplitude.get('fermionfactor'),
4646 1, False, 0),
4647 amplitude.get('number')))
4648 return [col_amp]
4649
4650
4651
4652
4653 col_amp_list = []
4654 for i, col_basis_elem in \
4655 enumerate(sorted(color_basis.keys())):
4656
4657 col_amp = []
4658 for diag_tuple in color_basis[col_basis_elem]:
4659 res_amps = filter(lambda amp: \
4660 tuple(amp.get('color_indices')) == diag_tuple[1],
4661 diagrams[diag_tuple[0]].get('amplitudes'))
4662 if not res_amps:
4663 raise self.PhysicsObjectError, \
4664 """No amplitude found for color structure
4665 %s and color index chain (%s) (diagram %i)""" % \
4666 (col_basis_elem,
4667 str(diag_tuple[1]),
4668 diag_tuple[0])
4669
4670 for res_amp in res_amps:
4671 col_amp.append(((res_amp.get('fermionfactor'),
4672 diag_tuple[2],
4673 diag_tuple[3],
4674 diag_tuple[4]),
4675 res_amp.get('number')))
4676
4677 col_amp_list.append(col_amp)
4678
4679 return col_amp_list
4680
4682 """Return a list of (coefficient, amplitude number) lists,
4683 corresponding to the JAMPs for this matrix element. The
4684 coefficients are given in the format (fermion factor, color
4685 coeff (frac), imaginary, Nc power)."""
4686
4687 return self.generate_color_amplitudes(self['color_basis'],self['diagrams'])
4688
4690 """ Sort the 'split_orders' list given in argument so that the orders of
4691 smaller weights appear first. Do nothing if not all split orders have
4692 a hierarchy defined."""
4693 order_hierarchy=\
4694 self.get('processes')[0].get('model').get('order_hierarchy')
4695 if set(order_hierarchy.keys()).union(set(split_orders))==\
4696 set(order_hierarchy.keys()):
4697 split_orders.sort(key=lambda order: order_hierarchy[order])
4698
4702 """ This a helper function for get_split_orders_mapping to return, for
4703 the HelasDiagram list given in argument, the list amp_orders detailed in
4704 the description of the 'get_split_orders_mapping' function.
4705 """
4706
4707 order_hierarchy=\
4708 self.get('processes')[0].get('model').get('order_hierarchy')
4709
4710
4711
4712 amp_orders={}
4713 for diag in diag_list:
4714 diag_orders=diag.calculate_orders()
4715
4716 diag_orders['WEIGHTED']=sum(order_hierarchy[order]*value for \
4717 order, value in diag_orders.items())
4718
4719 for order in split_orders:
4720 if not order in diag_orders.keys():
4721 diag_orders[order]=0
4722 key = tuple([diag_orders[order] for order in split_orders])
4723 try:
4724 amp_orders[key].extend([get_amp_number_function(amp) for amp in \
4725 get_amplitudes_function(diag)])
4726 except KeyError:
4727 amp_orders[key] = [get_amp_number_function(amp) for amp in \
4728 get_amplitudes_function(diag)]
4729 amp_orders=[(order[0],tuple(order[1])) for order in amp_orders.items()]
4730
4731
4732 if set(order_hierarchy.keys()).union(set(split_orders))==\
4733 set(order_hierarchy.keys()):
4734
4735 amp_orders.sort(key= lambda elem:
4736 sum([order_hierarchy[split_orders[i]]*order_power for \
4737 i, order_power in enumerate(elem[0])]))
4738
4739 return amp_orders
4740
4742 """This function returns two lists, squared_orders, amp_orders.
4743 If process['split_orders'] is empty, the function returns two empty lists.
4744
4745 squared_orders : All possible contributing squared orders among those
4746 specified in the process['split_orders'] argument. The elements of
4747 the list are tuples of the format format (OrderValue1,OrderValue2,...)
4748 with OrderValue<i> correspond to the value of the <i>th order in
4749 process['split_orders'] (the others are summed over and therefore
4750 left unspecified).
4751 Ex for dijet with process['split_orders']=['QCD','QED']:
4752 => [(4,0),(2,2),(0,4)]
4753
4754 amp_orders : Exactly as for squared order except that this list specifies
4755 the contributing order values for the amplitude (i.e. not 'squared').
4756 Also, the tuple describing the amplitude order is nested with a
4757 second one listing all amplitude numbers contributing to this order.
4758 Ex for dijet with process['split_orders']=['QCD','QED']:
4759 => [((2, 0), (2,)), ((0, 2), (1, 3, 4))]
4760
4761 Keep in mind that the orders of the element of the list is important as
4762 it dicatates the order of the corresponding "order indices" in the
4763 code output by the exporters.
4764 """
4765
4766 split_orders=self.get('processes')[0].get('split_orders')
4767
4768 if len(split_orders)==0:
4769 return (),()
4770
4771
4772
4773 self.sort_split_orders(split_orders)
4774
4775 amp_orders = self.get_split_orders_mapping_for_diagram_list(\
4776 self.get('diagrams'),split_orders)
4777
4778
4779
4780 squared_orders = []
4781 for i, amp_order in enumerate(amp_orders):
4782 for j in range(0,i+1):
4783 key = tuple([ord1 + ord2 for ord1,ord2 in \
4784 zip(amp_order[0],amp_orders[j][0])])
4785
4786
4787
4788 if not key in squared_orders:
4789 squared_orders.append(key)
4790
4791 return squared_orders, amp_orders
4792
4793
4794
4806
4808 """Return a list with all couplings used by this
4809 HelasMatrixElement."""
4810
4811 tmp = [wa.get('coupling') for wa in \
4812 self.get_all_wavefunctions() + self.get_all_amplitudes() \
4813 if wa.get('interaction_id') not in [0,-1]]
4814
4815 return [ [t] if not t.startswith('-') else [t[1:]] for t2 in tmp for t in t2]
4816
4817
4819 """Return a list of processes with initial states interchanged
4820 if has mirror processes"""
4821
4822 if not self.get('has_mirror_process'):
4823 return []
4824 processes = base_objects.ProcessList()
4825 for proc in self.get('processes'):
4826 legs = copy.copy(proc.get('legs'))
4827 legs[0:2] = [legs[1],legs[0]]
4828 decay_legs = copy.copy(proc.get('legs_with_decays'))
4829 decay_legs[0:2] = [decay_legs[1],decay_legs[0]]
4830 process = copy.copy(proc)
4831 process.set('legs', legs)
4832 process.set('legs_with_decays', decay_legs)
4833 processes.append(process)
4834 return processes
4835
4836 @staticmethod
4838 """Check if two single-sided decay processes
4839 (HelasMatrixElements) are equal.
4840
4841 Note that this has to be called before any combination of
4842 processes has occured.
4843
4844 Since a decay processes for a decay chain is always generated
4845 such that all final state legs are completely contracted
4846 before the initial state leg is included, all the diagrams
4847 will have identical wave function, independently of the order
4848 of final state particles.
4849
4850 Note that we assume that the process definitions have all
4851 external particles, corresponding to the external
4852 wavefunctions.
4853 """
4854
4855 assert len(decay1.get('processes')) == 1 == len(decay2.get('processes')), \
4856 "Can compare only single process HelasMatrixElements"
4857
4858 assert len(filter(lambda leg: leg.get('state') == False, \
4859 decay1.get('processes')[0].get('legs'))) == 1 and \
4860 len(filter(lambda leg: leg.get('state') == False, \
4861 decay2.get('processes')[0].get('legs'))) == 1, \
4862 "Call to check_decay_processes_equal requires " + \
4863 "both processes to be unique"
4864
4865
4866
4867
4868 if len(decay1.get('processes')[0].get("legs")) != \
4869 len(decay2.get('processes')[0].get("legs")) or \
4870 len(decay1.get('diagrams')) != len(decay2.get('diagrams')) or \
4871 decay1.get('identical_particle_factor') != \
4872 decay2.get('identical_particle_factor') or \
4873 sum(len(d.get('wavefunctions')) for d in \
4874 decay1.get('diagrams')) != \
4875 sum(len(d.get('wavefunctions')) for d in \
4876 decay2.get('diagrams')) or \
4877 decay1.get('processes')[0].get('legs')[0].get('id') != \
4878 decay2.get('processes')[0].get('legs')[0].get('id') or \
4879 sorted([leg.get('id') for leg in \
4880 decay1.get('processes')[0].get('legs')[1:]]) != \
4881 sorted([leg.get('id') for leg in \
4882 decay2.get('processes')[0].get('legs')[1:]]):
4883 return False
4884
4885
4886
4887 if [leg.get('id') for leg in \
4888 decay1.get('processes')[0].get('legs')] == \
4889 [leg.get('id') for leg in \
4890 decay2.get('processes')[0].get('legs')] and \
4891 decay1 == decay2:
4892 return True
4893
4894
4895
4896
4897
4898
4899 amplitudes2 = copy.copy(reduce(lambda a1, d2: a1 + \
4900 d2.get('amplitudes'),
4901 decay2.get('diagrams'), []))
4902
4903 for amplitude1 in reduce(lambda a1, d2: a1 + d2.get('amplitudes'),
4904 decay1.get('diagrams'), []):
4905 foundamplitude = False
4906 for amplitude2 in amplitudes2:
4907 if HelasMatrixElement.check_equal_wavefunctions(\
4908 amplitude1.get('mothers')[-1],
4909 amplitude2.get('mothers')[-1]):
4910 foundamplitude = True
4911
4912 amplitudes2.remove(amplitude2)
4913 break
4914 if not foundamplitude:
4915 return False
4916
4917 return True
4918
4919 @staticmethod
4921 """Recursive function to check if two wavefunctions are equal.
4922 First check that mothers have identical pdg codes, then repeat for
4923 all mothers with identical pdg codes."""
4924
4925
4926
4927 if sorted([wf.get('pdg_code') for wf in wf1.get('mothers')]) != \
4928 sorted([wf.get('pdg_code') for wf in wf2.get('mothers')]):
4929 return False
4930
4931
4932
4933
4934 if not wf1.get('mothers') and not wf2.get('mothers'):
4935 return True
4936
4937 mothers2 = copy.copy(wf2.get('mothers'))
4938
4939 for mother1 in wf1.get('mothers'):
4940
4941
4942 equalmothers = filter(lambda wf: wf.get('pdg_code') == \
4943 mother1.get('pdg_code'),
4944 mothers2)
4945 foundmother = False
4946 for mother2 in equalmothers:
4947 if HelasMatrixElement.check_equal_wavefunctions(\
4948 mother1, mother2):
4949 foundmother = True
4950
4951 mothers2.remove(mother2)
4952 break
4953 if not foundmother:
4954 return False
4955
4956 return True
4957
4958 @staticmethod
4960 """Gives a list of mother wavefunctions sorted according to
4961 1. The order of the particles in the interaction
4962 2. Cyclic reordering of particles in same spin group
4963 3. Fermions ordered IOIOIO... according to the pairs in
4964 the interaction."""
4965
4966 assert isinstance(arg, (HelasWavefunction, HelasAmplitude)), \
4967 "%s is not a valid HelasWavefunction or HelasAmplitude" % repr(arg)
4968
4969 if not arg.get('interaction_id'):
4970 return arg.get('mothers')
4971
4972 my_pdg_code = 0
4973 my_spin = 0
4974 if isinstance(arg, HelasWavefunction):
4975 my_pdg_code = arg.get_anti_pdg_code()
4976 my_spin = arg.get_spin_state_number()
4977
4978 sorted_mothers, my_index = arg.get('mothers').sort_by_pdg_codes(\
4979 arg.get('pdg_codes'), my_pdg_code)
4980
4981
4982 partner = None
4983 if isinstance(arg, HelasWavefunction) and arg.is_fermion():
4984
4985 if my_index % 2 == 0:
4986
4987 partner_index = my_index
4988 else:
4989
4990 partner_index = my_index - 1
4991 partner = sorted_mothers.pop(partner_index)
4992
4993 if partner.get_spin_state_number() > 0:
4994 my_index = partner_index
4995 else:
4996 my_index = partner_index + 1
4997
4998
4999 for i in range(0, len(sorted_mothers), 2):
5000 if sorted_mothers[i].is_fermion():
5001
5002 if sorted_mothers[i].get_spin_state_number() > 0 and \
5003 sorted_mothers[i + 1].get_spin_state_number() < 0:
5004
5005 sorted_mothers = sorted_mothers[:i] + \
5006 [sorted_mothers[i+1], sorted_mothers[i]] + \
5007 sorted_mothers[i+2:]
5008 elif sorted_mothers[i].get_spin_state_number() < 0 and \
5009 sorted_mothers[i + 1].get_spin_state_number() > 0:
5010
5011 pass
5012 else:
5013
5014 break
5015
5016
5017 if partner:
5018 sorted_mothers.insert(partner_index, partner)
5019
5020
5021 return HelasWavefunctionList(sorted_mothers)
5022
5027 """List of HelasMatrixElement objects
5028 """
5029
5031 """Test if object obj is a valid HelasMatrixElement for the list."""
5032
5033 return isinstance(obj, HelasMatrixElement)
5034
5036 pos = (i for i in xrange(len(self)) if self[i] is obj)
5037 for i in pos:
5038 del self[i]
5039 break
5040
5045 """HelasDecayChainProcess: If initiated with a DecayChainAmplitude
5046 object, generates the HelasMatrixElements for the core process(es)
5047 and decay chains. Then call combine_decay_chain_processes in order
5048 to generate the matrix elements for all combined processes."""
5049
5055
5056 - def filter(self, name, value):
5057 """Filter for valid process property values."""
5058
5059 if name == 'core_processes':
5060 if not isinstance(value, HelasMatrixElementList):
5061 raise self.PhysicsObjectError, \
5062 "%s is not a valid HelasMatrixElementList object" % \
5063 str(value)
5064
5065 if name == 'decay_chains':
5066 if not isinstance(value, HelasDecayChainProcessList):
5067 raise self.PhysicsObjectError, \
5068 "%s is not a valid HelasDecayChainProcessList object" % \
5069 str(value)
5070
5071 return True
5072
5074 """Return process property names as a nicely sorted list."""
5075
5076 return ['core_processes', 'decay_chains']
5077
5090
5092 """Returns a nicely formatted string of the matrix element processes."""
5093
5094 mystr = ""
5095
5096 for process in self.get('core_processes'):
5097 mystr += process.get('processes')[0].nice_string(indent) + "\n"
5098
5099 if self.get('decay_chains'):
5100 mystr += " " * indent + "Decays:\n"
5101 for dec in self.get('decay_chains'):
5102 mystr += dec.nice_string(indent + 2) + "\n"
5103
5104 return mystr[:-1]
5105
5107 """Generate the HelasMatrixElements for the core processes and
5108 decay processes (separately)"""
5109
5110 assert isinstance(dc_amplitude, diagram_generation.DecayChainAmplitude), \
5111 "%s is not a valid DecayChainAmplitude" % dc_amplitude
5112
5113
5114
5115
5116 decay_ids = dc_amplitude.get_decay_ids()
5117
5118 matrix_elements = HelasMultiProcess.generate_matrix_elements(\
5119 dc_amplitude.get('amplitudes'),
5120 False,
5121 decay_ids)
5122
5123 self.set('core_processes', matrix_elements)
5124
5125 while dc_amplitude.get('decay_chains'):
5126
5127 decay_chain = dc_amplitude.get('decay_chains').pop(0)
5128 self['decay_chains'].append(HelasDecayChainProcess(\
5129 decay_chain))
5130
5131
5133 """Recursive function to generate complete
5134 HelasMatrixElements, combining the core process with the decay
5135 chains.
5136
5137 * If the number of decay chains is the same as the number of
5138 decaying particles, apply each decay chain to the corresponding
5139 final state particle.
5140 * If the number of decay chains and decaying final state particles
5141 don't correspond, all decays applying to a given particle type are
5142 combined (without double counting).
5143 * combine allow to merge identical ME
5144 """
5145
5146
5147 if not self['decay_chains']:
5148
5149 return self['core_processes']
5150
5151
5152
5153 decay_elements = []
5154
5155 for decay_chain in self['decay_chains']:
5156
5157 decay_elements.append(decay_chain.combine_decay_chain_processes(combine))
5158
5159
5160 matrix_elements = HelasMatrixElementList()
5161
5162 me_tags = []
5163
5164 permutations = []
5165
5166
5167
5168 decay_is_ids = [[element.get('processes')[0].get_initial_ids()[0] \
5169 for element in elements]
5170 for elements in decay_elements]
5171
5172 while self['core_processes']:
5173
5174 core_process = self['core_processes'].pop(0)
5175
5176 fs_legs = filter(lambda leg: any([any([id == leg.get('id') for id \
5177 in is_ids]) for is_ids in decay_is_ids]),
5178 core_process.get('processes')[0].get_final_legs())
5179
5180 fs_ids = [leg.get('id') for leg in fs_legs]
5181
5182 fs_numbers = {}
5183 fs_indices = {}
5184 for i, leg in enumerate(fs_legs):
5185 fs_numbers[leg.get('id')] = \
5186 fs_numbers.setdefault(leg.get('id'), []) + \
5187 [leg.get('number')]
5188 fs_indices[leg.get('id')] = \
5189 fs_indices.setdefault(leg.get('id'), []) + \
5190 [i]
5191
5192 decay_lists = []
5193
5194 for fs_id in set(fs_ids):
5195
5196
5197
5198
5199 decay_list = []
5200
5201
5202
5203
5204
5205
5206
5207 chains = []
5208 if len(fs_legs) == len(decay_elements) and \
5209 all([fs in ids for (fs, ids) in \
5210 zip(fs_ids, decay_is_ids)]):
5211
5212
5213
5214 for index in fs_indices[fs_id]:
5215 chains.append(filter(lambda me: \
5216 me.get('processes')[0].\
5217 get_initial_ids()[0] == fs_id,
5218 decay_elements[index]))
5219
5220 if len(fs_legs) != len(decay_elements) or not chains or not chains[0]:
5221
5222
5223
5224 chain = sum([filter(lambda me: \
5225 me.get('processes')[0].\
5226 get_initial_ids()[0] == fs_id,
5227 decay_chain) for decay_chain in \
5228 decay_elements], [])
5229
5230 chains = [chain] * len(fs_numbers[fs_id])
5231
5232 red_decay_chains = []
5233 for prod in itertools.product(*chains):
5234
5235
5236
5237
5238
5239
5240 if sorted([p.get('processes')[0] for p in prod],
5241 lambda x1, x2: x1.compare_for_sort(x2)) \
5242 in red_decay_chains:
5243 continue
5244
5245
5246 red_decay_chains.append(\
5247 sorted([p.get('processes')[0] for p in prod],
5248 lambda x1, x2: x1.compare_for_sort(x2)))
5249
5250
5251 decay_list.append(zip(fs_numbers[fs_id], prod))
5252
5253 decay_lists.append(decay_list)
5254
5255
5256
5257 for decays in itertools.product(*decay_lists):
5258
5259
5260 decay_dict = dict(sum(decays, []))
5261
5262
5263 model_bk = core_process.get('processes')[0].get('model')
5264
5265 for i, process in enumerate(core_process.get('processes')):
5266 process.set('model',base_objects.Model())
5267 matrix_element = copy.deepcopy(core_process)
5268
5269 for i, process in enumerate(matrix_element.get('processes')):
5270 process.set('model', model_bk)
5271 core_process.get('processes')[i].set('model', model_bk)
5272
5273
5274 org_wfs = core_process.get_all_wavefunctions()
5275 for i, wf in enumerate(matrix_element.get_all_wavefunctions()):
5276 wf.set('particle', org_wfs[i].get('particle'))
5277 wf.set('antiparticle', org_wfs[i].get('antiparticle'))
5278
5279
5280 logger.info("Combine %s with decays %s" % \
5281 (core_process.get('processes')[0].nice_string().\
5282 replace('Process: ', ''), \
5283 ", ".join([d.get('processes')[0].nice_string().\
5284 replace('Process: ', '') \
5285 for d in decay_dict.values()])))
5286
5287 matrix_element.insert_decay_chains(decay_dict)
5288
5289 if combine:
5290 me_tag = IdentifyMETag.create_tag(\
5291 matrix_element.get_base_amplitude(),
5292 matrix_element.get('identical_particle_factor'))
5293 try:
5294 if not combine:
5295 raise ValueError
5296
5297
5298
5299 me_index = me_tags.index(me_tag)
5300 except ValueError:
5301
5302
5303 if matrix_element.get('processes') and \
5304 matrix_element.get('diagrams'):
5305 matrix_elements.append(matrix_element)
5306 if combine:
5307 me_tags.append(me_tag)
5308 permutations.append(me_tag[-1][0].\
5309 get_external_numbers())
5310 else:
5311 other_processes = matrix_elements[me_index].get('processes')
5312 logger.info("Combining process with %s" % \
5313 other_processes[0].nice_string().replace('Process: ', ''))
5314 for proc in matrix_element.get('processes'):
5315 other_processes.append(HelasMultiProcess.\
5316 reorder_process(proc,
5317 permutations[me_index],
5318 me_tag[-1][0].get_external_numbers()))
5319
5320 return matrix_elements
5321
5326 """List of HelasDecayChainProcess objects
5327 """
5328
5330 """Test if object obj is a valid HelasDecayChainProcess for the list."""
5331
5332 return isinstance(obj, HelasDecayChainProcess)
5333
5338 """HelasMultiProcess: If initiated with an AmplitudeList,
5339 generates the HelasMatrixElements for the Amplitudes, identifying
5340 processes with identical matrix elements"""
5341
5346
5347 - def filter(self, name, value):
5348 """Filter for valid process property values."""
5349
5350 if name == 'matrix_elements':
5351 if not isinstance(value, HelasMatrixElementList):
5352 raise self.PhysicsObjectError, \
5353 "%s is not a valid HelasMatrixElementList object" % str(value)
5354 return True
5355
5357 """Return process property names as a nicely sorted list."""
5358
5359 return ['matrix_elements']
5360
5361 - def __init__(self, argument=None, combine_matrix_elements=True,
5362 matrix_element_opts={}, compute_loop_nc = False):
5363 """Allow initialization with AmplitudeList. Matrix_element_opts are
5364 potential options to be passed to the constructor of the
5365 HelasMatrixElements created. By default it is none, but when called from
5366 LoopHelasProcess, this options will contain 'optimized_output'."""
5367
5368
5369 if isinstance(argument, diagram_generation.AmplitudeList):
5370 super(HelasMultiProcess, self).__init__()
5371 self.set('matrix_elements', self.generate_matrix_elements(argument,
5372 combine_matrix_elements = combine_matrix_elements,
5373 matrix_element_opts=matrix_element_opts,
5374 compute_loop_nc = compute_loop_nc))
5375 elif isinstance(argument, diagram_generation.MultiProcess):
5376 super(HelasMultiProcess, self).__init__()
5377 self.set('matrix_elements',
5378 self.generate_matrix_elements(argument.get('amplitudes'),
5379 combine_matrix_elements = combine_matrix_elements,
5380 matrix_element_opts = matrix_element_opts,
5381 compute_loop_nc = compute_loop_nc))
5382 elif isinstance(argument, diagram_generation.Amplitude):
5383 super(HelasMultiProcess, self).__init__()
5384 self.set('matrix_elements', self.generate_matrix_elements(\
5385 diagram_generation.AmplitudeList([argument]),
5386 combine_matrix_elements = combine_matrix_elements,
5387 matrix_element_opts = matrix_element_opts,
5388 compute_loop_nc = compute_loop_nc))
5389 elif argument:
5390
5391 super(HelasMultiProcess, self).__init__(argument)
5392 else:
5393
5394 super(HelasMultiProcess, self).__init__()
5395
5397 """Return a list of (lorentz_name, conjugate, outgoing) with
5398 all lorentz structures used by this HelasMultiProcess."""
5399 helas_list = []
5400
5401 for me in self.get('matrix_elements'):
5402 helas_list.extend(me.get_used_lorentz())
5403
5404 return list(set(helas_list))
5405
5407 """Return a list with all couplings used by this
5408 HelasMatrixElement."""
5409
5410 coupling_list = []
5411
5412 for me in self.get('matrix_elements'):
5413 coupling_list.extend([c for l in me.get_used_couplings() for c in l])
5414
5415 return list(set(coupling_list))
5416
5418 """Extract the list of matrix elements"""
5419
5420 return self.get('matrix_elements')
5421
5422
5423
5424
5425
5426 @classmethod
5427 - def process_color(cls,matrix_element, color_information, compute_loop_nc=None):
5428 """ Process the color information for a given matrix
5429 element made of a tree diagram. compute_loop_nc is dummy here for the
5430 tree-level Nc and present for structural reasons only."""
5431
5432 if compute_loop_nc:
5433 raise MadGraph5Error, "The tree-level function 'process_color' "+\
5434 " of class HelasMultiProcess cannot be called with a value for compute_loop_nc"
5435
5436
5437 for key in color_information:
5438 exec("%s=color_information['%s']"%(key,key))
5439
5440
5441
5442
5443 col_basis = color_amp.ColorBasis()
5444 new_amp = matrix_element.get_base_amplitude()
5445 matrix_element.set('base_amplitude', new_amp)
5446
5447 colorize_obj = col_basis.create_color_dict_list(\
5448 matrix_element.get('base_amplitude'))
5449
5450
5451
5452
5453 try:
5454
5455
5456
5457 col_index = list_colorize.index(colorize_obj)
5458 except ValueError:
5459
5460
5461 list_colorize.append(colorize_obj)
5462 col_basis.build()
5463 list_color_basis.append(col_basis)
5464 col_matrix = color_amp.ColorMatrix(col_basis)
5465 list_color_matrices.append(col_matrix)
5466 col_index = -1
5467 logger.info(\
5468 "Processing color information for %s" % \
5469 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
5470 replace('Process', 'process'))
5471 else:
5472 logger.info(\
5473 "Reusing existing color information for %s" % \
5474 matrix_element.get('processes')[0].nice_string(print_weighted=False).\
5475 replace('Process', 'process'))
5476
5477 matrix_element.set('color_basis',
5478 list_color_basis[col_index])
5479 matrix_element.set('color_matrix',
5480 list_color_matrices[col_index])
5481
5482
5483
5484 matrix_element_class = HelasMatrixElement
5485
5486 @classmethod
5487 - def generate_matrix_elements(cls, amplitudes, gen_color = True,
5488 decay_ids = [], combine_matrix_elements = True,
5489 compute_loop_nc = False, matrix_element_opts = {}):
5490 """Generate the HelasMatrixElements for the amplitudes,
5491 identifying processes with identical matrix elements, as
5492 defined by HelasMatrixElement.__eq__. Returns a
5493 HelasMatrixElementList and an amplitude map (used by the
5494 SubprocessGroup functionality). decay_ids is a list of decayed
5495 particle ids, since those should not be combined even if
5496 matrix element is identical.
5497 The compute_loop_nc sets wheter independent tracking of Nc power coming
5498 from the color loop trace is necessary or not (it is time consuming).
5499 Matrix_element_opts are potential additional options to be passed to
5500 the HelasMatrixElements constructed."""
5501
5502 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \
5503 "%s is not valid AmplitudeList" % type(amplitudes)
5504
5505 combine = combine_matrix_elements
5506 if 'mode' in matrix_element_opts and matrix_element_opts['mode']=='MadSpin':
5507 combine = False
5508 del matrix_element_opts['mode']
5509
5510
5511
5512 list_colorize = []
5513 list_color_basis = []
5514 list_color_matrices = []
5515
5516
5517
5518
5519 dict_loopborn_matrices = {}
5520
5521
5522
5523 color_information = { 'list_colorize' : list_colorize,
5524 'list_color_basis' : list_color_basis,
5525 'list_color_matrices' : list_color_matrices,
5526 'dict_loopborn_matrices' : dict_loopborn_matrices}
5527
5528
5529 matrix_elements = HelasMatrixElementList()
5530
5531 identified_matrix_elements = []
5532
5533 amplitude_tags = []
5534
5535
5536
5537 permutations = []
5538 for amplitude in amplitudes:
5539 if isinstance(amplitude, diagram_generation.DecayChainAmplitude):
5540
5541 tmp_matrix_element_list = HelasDecayChainProcess(amplitude).\
5542 combine_decay_chain_processes(combine)
5543
5544 matrix_element_list = []
5545 for matrix_element in tmp_matrix_element_list:
5546 assert isinstance(matrix_element, HelasMatrixElement), \
5547 "Not a HelasMatrixElement: %s" % matrix_element
5548
5549
5550
5551 if not matrix_element.get('processes') or \
5552 not matrix_element.get('diagrams'):
5553 continue
5554
5555
5556 amplitude_tag = IdentifyMETag.create_tag(\
5557 matrix_element.get_base_amplitude())
5558 try:
5559 if not combine:
5560 raise ValueError
5561 me_index = amplitude_tags.index(amplitude_tag)
5562 except ValueError:
5563
5564 matrix_element_list.append(matrix_element)
5565 if combine_matrix_elements:
5566 amplitude_tags.append(amplitude_tag)
5567 identified_matrix_elements.append(matrix_element)
5568 permutations.append(amplitude_tag[-1][0].\
5569 get_external_numbers())
5570 else:
5571
5572 other_processes = identified_matrix_elements[me_index].\
5573 get('processes')
5574
5575
5576 for proc in matrix_element.get('processes'):
5577 other_processes.append(cls.reorder_process(\
5578 proc,
5579 permutations[me_index],
5580 amplitude_tag[-1][0].get_external_numbers()))
5581 logger.info("Combined %s with %s" % \
5582 (matrix_element.get('processes')[0].\
5583 nice_string().\
5584 replace('Process: ', 'process '),
5585 other_processes[0].nice_string().\
5586 replace('Process: ', 'process ')))
5587
5588 continue
5589 else:
5590
5591
5592
5593 amplitude_tag = IdentifyMETag.create_tag(amplitude)
5594 try:
5595 me_index = amplitude_tags.index(amplitude_tag)
5596 except ValueError:
5597
5598 logger.info("Generating Helas calls for %s" % \
5599 amplitude.get('process').nice_string().\
5600 replace('Process', 'process'))
5601
5602
5603
5604 matrix_element_list = [cls.matrix_element_class(amplitude,
5605 decay_ids=decay_ids,
5606 gen_color=False,
5607 **matrix_element_opts)]
5608 me = matrix_element_list[0]
5609 if me.get('processes') and me.get('diagrams'):
5610
5611 if combine_matrix_elements:
5612 amplitude_tags.append(amplitude_tag)
5613 identified_matrix_elements.append(me)
5614 permutations.append(amplitude_tag[-1][0].\
5615 get_external_numbers())
5616 else:
5617 matrix_element_list = []
5618 else:
5619
5620 other_processes = identified_matrix_elements[me_index].\
5621 get('processes')
5622 other_processes.append(cls.reorder_process(\
5623 amplitude.get('process'),
5624 permutations[me_index],
5625 amplitude_tag[-1][0].get_external_numbers()))
5626 logger.info("Combined %s with %s" % \
5627 (other_processes[-1].nice_string().\
5628 replace('Process: ', 'process '),
5629 other_processes[0].nice_string().\
5630 replace('Process: ', 'process ')))
5631
5632 continue
5633
5634
5635 for matrix_element in copy.copy(matrix_element_list):
5636 assert isinstance(matrix_element, HelasMatrixElement), \
5637 "Not a HelasMatrixElement: %s" % matrix_element
5638
5639
5640 matrix_elements.append(matrix_element)
5641
5642 if not gen_color:
5643 continue
5644
5645
5646
5647
5648 cls.process_color(matrix_element,color_information,\
5649 compute_loop_nc=compute_loop_nc)
5650
5651 if not matrix_elements:
5652 raise InvalidCmd, \
5653 "No matrix elements generated, check overall coupling orders"
5654
5655 return matrix_elements
5656
5657 @staticmethod
5659 """Reorder the legs in the process according to the difference
5660 between org_perm and proc_perm"""
5661
5662 leglist = base_objects.LegList(\
5663 [copy.copy(process.get('legs_with_decays')[i]) for i in \
5664 diagram_generation.DiagramTag.reorder_permutation(\
5665 proc_perm, org_perm)])
5666 new_proc = copy.copy(process)
5667 new_proc.set('legs_with_decays', leglist)
5668
5669 if not new_proc.get('decay_chains'):
5670 new_proc.set('legs', leglist)
5671
5672 return new_proc
5673