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