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