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