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