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