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