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