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