1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Classes for diagram generation. Amplitude performs the diagram
16 generation, DecayChainAmplitude keeps track of processes with decay
17 chains, and MultiProcess allows generation of processes with
18 multiparticle definitions. DiagramTag allows to identify diagrams
19 based on relevant properties.
20 """
21
22 import array
23 import copy
24 import itertools
25 import logging
26
27 import madgraph.core.base_objects as base_objects
28 import madgraph.various.misc as misc
29 from madgraph import InvalidCmd, MadGraph5Error
30
31 logger = logging.getLogger('madgraph.diagram_generation')
35
41 """Class to tag diagrams based on objects with some __lt__ measure, e.g.
42 PDG code/interaction id (for comparing diagrams from the same amplitude),
43 or Lorentz/coupling/mass/width (for comparing AMPs from different MEs).
44 Algorithm: Create chains starting from external particles:
45 1 \ / 6
46 2 /\______/\ 7
47 3_ / | \_ 8
48 4 / 5 \_ 9
49 \ 10
50 gives ((((9,10,id910),8,id9108),(6,7,id67),id910867)
51 (((1,2,id12),(3,4,id34)),id1234),
52 5,id91086712345)
53 where idN is the id of the corresponding interaction. The ordering within
54 chains is based on chain length (depth; here, 1234 has depth 3, 910867 has
55 depth 4, 5 has depht 0), and if equal on the ordering of the chain elements.
56 The determination of central vertex is based on minimizing the chain length
57 for the longest subchain.
58 This gives a unique tag which can be used to identify diagrams
59 (instead of symmetry), as well as identify identical matrix elements from
60 different processes."""
61
63 """Exception for any problems in DiagramTags"""
64 pass
65
66 - def __init__(self, diagram, model=None, ninitial=2):
67 """Initialize with a diagram. Create DiagramTagChainLinks according to
68 the diagram, and figure out if we need to shift the central vertex."""
69
70
71 leg_dict = {}
72
73 for vertex in diagram.get('vertices'):
74
75 legs = vertex.get('legs')[:-1]
76 lastvx = vertex == diagram.get('vertices')[-1]
77 if lastvx:
78
79 legs = vertex.get('legs')
80
81 link = DiagramTagChainLink([leg_dict.setdefault(leg.get('number'),
82 DiagramTagChainLink(self.link_from_leg(leg, model))) \
83 for leg in legs],
84 self.vertex_id_from_vertex(vertex,
85 lastvx,
86 model,
87 ninitial))
88
89 if not lastvx:
90 leg_dict[vertex.get('legs')[-1].get('number')] = link
91
92
93 self.tag = link
94
95
96
97 done = max([l.depth for l in self.tag.links]) == 0
98 while not done:
99
100 longest_chain = self.tag.links[0]
101
102 new_link = DiagramTagChainLink(self.tag.links[1:],
103 self.flip_vertex(\
104 self.tag.vertex_id,
105 longest_chain.vertex_id,
106 self.tag.links[1:]))
107
108 other_links = list(longest_chain.links) + [new_link]
109 other_link = DiagramTagChainLink(other_links,
110 self.flip_vertex(\
111 longest_chain.vertex_id,
112 self.tag.vertex_id,
113 other_links))
114
115 if other_link.links[0] < self.tag.links[0]:
116
117 self.tag = other_link
118 else:
119
120 done = True
121
126
128 """Output a diagram from a DiagramTag. Note that each daughter
129 class must implement the static functions id_from_vertex_id
130 (if the vertex id is something else than an integer) and
131 leg_from_link (to pass the correct info from an end link to a
132 leg)."""
133
134
135 diagram = base_objects.Diagram({'vertices': \
136 self.vertices_from_link(self.tag,
137 model,
138 True)})
139 diagram.calculate_orders(model)
140 return diagram
141
142 @classmethod
144 """Recursively return the leg corresponding to this link and
145 the list of all vertices from all previous links"""
146
147 if link.end_link:
148
149 return cls.leg_from_link(link), []
150
151
152 leg_vertices = [cls.vertices_from_link(l, model) for l in link.links]
153
154 legs = base_objects.LegList(sorted([l for l,v in leg_vertices],
155 lambda l1,l2: l2.get('number') - \
156 l1.get('number')))
157
158 vertices = base_objects.VertexList(sum([v for l, v in leg_vertices],
159 []))
160
161 if not first_vertex:
162
163
164 last_leg = cls.leg_from_legs(legs,link.vertex_id,model)
165 legs.append(last_leg)
166
167
168 vertices.append(cls.vertex_from_link(legs,
169 link.vertex_id,
170 model))
171 if first_vertex:
172
173 return vertices
174 else:
175
176 return last_leg, vertices
177
178 @classmethod
180 """Returns the list of external PDGs of the interaction corresponding
181 to this vertex_id."""
182
183
184
185
186 if (len(vertex_id)>=3 and 'PDGs' in vertex_id[2]):
187 return vertex_id[2]['PDGs']
188 else:
189 return [part.get_pdg_code() for part in model.get_interaction(
190 cls.id_from_vertex_id(vertex_id)).get('particles')]
191
192 @classmethod
194 """Return a leg from a leg list and the model info"""
195
196 pdgs = list(cls.legPDGs_from_vertex_id(vertex_id, model))
197
198
199 for pdg in [leg.get('id') for leg in legs]:
200 pdgs.remove(pdg)
201
202 assert len(pdgs) == 1
203
204 pdg = model.get_particle(pdgs[0]).get_anti_pdg_code()
205 number = min([l.get('number') for l in legs])
206
207 state = (len([l for l in legs if l.get('state') == False]) != 1)
208
209 onshell= False
210
211 return base_objects.Leg({'id': pdg,
212 'number': number,
213 'state': state,
214 'onshell': onshell})
215
216 @classmethod
229
230 @staticmethod
232 """Return a leg from a link"""
233
234 if link.end_link:
235
236 return base_objects.Leg({'number':link.links[0][1],
237 'id':link.links[0][0][0],
238 'state':(link.links[0][0][1] == 0),
239 'onshell':False})
240
241
242 assert False
243
244 @staticmethod
246 """Return the numerical vertex id from a link.vertex_id"""
247
248 return vertex_id[0][0]
249
250 @staticmethod
252 """Return the loop_info stored in this vertex id. Notice that the
253 IdentifyME tag does not store the loop_info, but should normally never
254 need access to it."""
255
256 return vertex_id[2]
257
258 @staticmethod
260 """Reorder a permutation with respect to start_perm. Note that
261 both need to start from 1."""
262 if perm == start_perm:
263 return range(len(perm))
264 order = [i for (p,i) in \
265 sorted([(p,i) for (i,p) in enumerate(perm)])]
266 return [start_perm[i]-1 for i in order]
267
268 @staticmethod
270 """Returns the default end link for a leg: ((id, state), number).
271 Note that the number is not taken into account if tag comparison,
272 but is used only to extract leg permutations."""
273 if leg.get('state'):
274
275 return [((leg.get('id'), 0), leg.get('number'))]
276 else:
277
278 return [((leg.get('id'), leg.get('number')), leg.get('number'))]
279
280 @staticmethod
282 """Returns the default vertex id: just the interaction id
283 Note that in the vertex id, like the leg, only the first entry is
284 taken into account in the tag comparison, while the second is for
285 storing information that is not to be used in comparisons and the
286 third for additional info regarding the shrunk loop vertex."""
287
288 if isinstance(vertex,base_objects.ContractedVertex):
289
290 return ((vertex.get('id'),vertex.get('loop_tag')),(),
291 {'PDGs':vertex.get('PDGs')})
292 else:
293 return ((vertex.get('id'),()),(),{})
294
295 @staticmethod
297 """Returns the default vertex flip: just the new_vertex"""
298 return new_vertex
299
301 """Equal if same tag"""
302 if type(self) != type(other):
303 return False
304 return self.tag == other.tag
305
307 return not self.__eq__(other)
308
311
313 return self.tag < other.tag
314
316 return self.tag > other.tag
317
318 __repr__ = __str__
319
321 """Chain link for a DiagramTag. A link is a tuple + vertex id + depth,
322 with a comparison operator defined"""
323
324 - def __init__(self, objects, vertex_id = None):
325 """Initialize, either with a tuple of DiagramTagChainLinks and
326 a vertex_id (defined by DiagramTag.vertex_id_from_vertex), or
327 with an external leg object (end link) defined by
328 DiagramTag.link_from_leg"""
329
330 if vertex_id == None:
331
332 self.links = tuple(objects)
333 self.vertex_id = (0,)
334 self.depth = 0
335 self.end_link = True
336 return
337
338 self.links = tuple(sorted(list(tuple(objects)), reverse=True))
339 self.vertex_id = vertex_id
340
341
342 self.depth = sum([l.depth for l in self.links],
343 max(1, len(self.links)-1))
344 self.end_link = False
345
347 """Get the permutation of external numbers (assumed to be the
348 second entry in the end link tuples)"""
349
350 if self.end_link:
351 return [self.links[0][1]]
352
353 return sum([l.get_external_numbers() for l in self.links], [])
354
356 """Compare self with other in the order:
357 1. depth 2. len(links) 3. vertex id 4. measure of links"""
358
359 if self == other:
360 return False
361
362 if self.depth != other.depth:
363 return self.depth < other.depth
364
365 if len(self.links) != len(other.links):
366 return len(self.links) < len(other.links)
367
368 if self.vertex_id[0] != other.vertex_id[0]:
369 return self.vertex_id[0] < other.vertex_id[0]
370
371 for i, link in enumerate(self.links):
372 if i > len(other.links) - 1:
373 return False
374 if link != other.links[i]:
375 return link < other.links[i]
376
378 return self != other and not self.__lt__(other)
379
381 """For end link,
382 consider equal if self.links[0][0] == other.links[0][0],
383 i.e., ignore the leg number (in links[0][1])."""
384
385 if self.end_link and other.end_link and self.depth == other.depth \
386 and self.vertex_id == other.vertex_id:
387 return self.links[0][0] == other.links[0][0]
388
389 return self.end_link == other.end_link and self.depth == other.depth \
390 and self.vertex_id[0] == other.vertex_id[0] \
391 and self.links == other.links
392
394 return not self.__eq__(other)
395
396
398 if self.end_link:
399 return str(self.links)
400 return "%s, %s; %d" % (str(self.links),
401 str(self.vertex_id),
402 self.depth)
403
404 __repr__ = __str__
405
406
407
408
409 -class Amplitude(base_objects.PhysicsObject):
410 """Amplitude: process + list of diagrams (ordered)
411 Initialize with a process, then call generate_diagrams() to
412 generate the diagrams for the amplitude
413 """
414
416 """Default values for all properties"""
417
418 self['process'] = base_objects.Process()
419 self['diagrams'] = None
420
421
422 self['has_mirror_process'] = False
423
436
437 - def filter(self, name, value):
453
454 - def get(self, name):
463
464
465
467 """Return diagram property names as a nicely sorted list."""
468
469 return ['process', 'diagrams', 'has_mirror_process']
470
472 """Returns number of diagrams for this amplitude"""
473 return len(self.get('diagrams'))
474
476 """Return an AmplitudeList with just this amplitude.
477 Needed for DecayChainAmplitude."""
478
479 return AmplitudeList([self])
480
482 """Returns a nicely formatted string of the amplitude content."""
483 return self.get('process').nice_string(indent) + "\n" + \
484 self.get('diagrams').nice_string(indent)
485
487 """Returns a nicely formatted string of the amplitude process."""
488 return self.get('process').nice_string(indent)
489
491 """Returns the number of initial state particles in the process."""
492 return self.get('process').get_ninitial()
493
495 """ Returns wether this amplitude has a loop process."""
496
497 return self.get('process').get('perturbation_couplings')
498
500 """Generate diagrams. Algorithm:
501
502 1. Define interaction dictionaries:
503 * 2->0 (identity), 3->0, 4->0, ... , maxlegs->0
504 * 2 -> 1, 3 -> 1, ..., maxlegs-1 -> 1
505
506 2. Set flag from_group=true for all external particles.
507 Flip particle/anti particle for incoming particles.
508
509 3. If there is a dictionary n->0 with n=number of external
510 particles, create if possible the combination [(1,2,3,4,...)]
511 with *at least two* from_group==true. This will give a
512 finished (set of) diagram(s) (done by reduce_leglist)
513
514 4. Create all allowed groupings of particles with at least one
515 from_group==true (according to dictionaries n->1):
516 [(1,2),3,4...],[1,(2,3),4,...],...,
517 [(1,2),(3,4),...],...,[(1,2,3),4,...],...
518 (done by combine_legs)
519
520 5. Replace each group with a (list of) new particle(s) with number
521 n = min(group numbers). Set from_group true for these
522 particles and false for all other particles. Store vertex info.
523 (done by merge_comb_legs)
524
525 6. Stop algorithm when at most 2 particles remain.
526 Return all diagrams (lists of vertices).
527
528 7. Repeat from 3 (recursion done by reduce_leglist)
529
530 8. Replace final p=p vertex
531
532 Be aware that the resulting vertices have all particles outgoing,
533 so need to flip for incoming particles when used.
534
535 SPECIAL CASE: For A>BC... processes which are legs in decay
536 chains, we need to ensure that BC... combine first, giving A=A
537 as a final vertex. This case is defined by the Process
538 property is_decay_chain = True.
539 This function can also be called by the generate_diagram function
540 of LoopAmplitudes, in which case the generated diagrams here must not
541 be directly assigned to the 'diagrams' attributed but returned as a
542 DiagramList by the function. This is controlled by the argument
543 returndiag.
544 """
545
546 process = self.get('process')
547 model = process.get('model')
548 legs = process.get('legs')
549
550 for key in process.get('overall_orders').keys():
551 try:
552 process.get('orders')[key] = \
553 min(process.get('orders')[key],
554 process.get('overall_orders')[key])
555 except KeyError:
556 process.get('orders')[key] = process.get('overall_orders')[key]
557
558 assert model.get('particles'), \
559 "particles are missing in model: %s" % model.get('particles')
560
561 assert model.get('interactions'), \
562 "interactions are missing in model"
563
564
565 res = base_objects.DiagramList()
566
567 if len(filter(lambda leg: model.get('particle_dict')[\
568 leg.get('id')].is_fermion(), legs)) % 2 == 1:
569 if not returndiag:
570 self['diagrams'] = res
571 raise InvalidCmd, 'The number of fermion is odd'
572 else:
573 return False, res
574
575
576
577 if not model.get('got_majoranas') and \
578 len(filter(lambda leg: leg.is_incoming_fermion(model), legs)) != \
579 len(filter(lambda leg: leg.is_outgoing_fermion(model), legs)):
580 if not returndiag:
581 self['diagrams'] = res
582 raise InvalidCmd, 'The number of of incoming/outcoming fermions are different'
583 else:
584 return False, res
585
586
587
588 for charge in model.get('conserved_charge'):
589 total = 0
590 for leg in legs:
591 part = model.get('particle_dict')[leg.get('id')]
592 try:
593 value = part.get(charge)
594 except (AttributeError, base_objects.PhysicsObject.PhysicsObjectError):
595 try:
596 value = getattr(part, charge)
597 except AttributeError:
598 value = 0
599
600 if (leg.get('id') != part['pdg_code']) != leg['state']:
601 total -= value
602 else:
603 total += value
604
605 if abs(total) > 1e-10:
606 if not returndiag:
607 self['diagrams'] = res
608 raise InvalidCmd, 'No %s conservation for this process ' % charge
609 return res
610 else:
611 raise InvalidCmd, 'No %s conservation for this process ' % charge
612 return res, res
613
614 if not returndiag:
615 logger.info("Trying %s " % process.nice_string().replace('Process', 'process'))
616
617
618 for i in range(0, len(process.get('legs'))):
619
620 leg = copy.copy(process.get('legs')[i])
621 process.get('legs')[i] = leg
622 if leg.get('number') == 0:
623 leg.set('number', i + 1)
624
625
626
627 leglist = self.copy_leglist(process.get('legs'))
628
629 for leg in leglist:
630
631
632 leg.set('from_group', True)
633
634
635
636 if leg.get('state') == False:
637 part = model.get('particle_dict')[leg.get('id')]
638 leg.set('id', part.get_anti_pdg_code())
639
640
641
642 max_multi_to1 = max([len(key) for key in \
643 model.get('ref_dict_to1').keys()])
644
645
646
647
648
649
650
651
652 is_decay_proc = process.get_ninitial() == 1
653 if is_decay_proc:
654 part = model.get('particle_dict')[leglist[0].get('id')]
655
656
657
658 ref_dict_to0 = {(part.get_pdg_code(),part.get_anti_pdg_code()):[0],
659 (part.get_anti_pdg_code(),part.get_pdg_code()):[0]}
660
661
662 leglist[0].set('from_group', None)
663 reduced_leglist = self.reduce_leglist(leglist,
664 max_multi_to1,
665 ref_dict_to0,
666 is_decay_proc,
667 process.get('orders'))
668 else:
669 reduced_leglist = self.reduce_leglist(leglist,
670 max_multi_to1,
671 model.get('ref_dict_to0'),
672 is_decay_proc,
673 process.get('orders'))
674
675
676
677
678 self.convert_dgleg_to_leg(reduced_leglist)
679
680 if reduced_leglist:
681 for vertex_list in reduced_leglist:
682 res.append(self.create_diagram(base_objects.VertexList(vertex_list)))
683
684
685
686 failed_crossing = not res
687
688
689
690
691
692
693 if process.get('required_s_channels') and \
694 process.get('required_s_channels')[0]:
695
696
697 lastvx = -1
698
699
700
701 if is_decay_proc: lastvx = -2
702 ninitial = len(filter(lambda leg: leg.get('state') == False,
703 process.get('legs')))
704
705 old_res = res
706 res = base_objects.DiagramList()
707 for id_list in process.get('required_s_channels'):
708 res_diags = filter(lambda diagram: \
709 all([req_s_channel in \
710 [vertex.get_s_channel_id(\
711 process.get('model'), ninitial) \
712 for vertex in diagram.get('vertices')[:lastvx]] \
713 for req_s_channel in \
714 id_list]), old_res)
715
716 res.extend([diag for diag in res_diags if diag not in res])
717
718
719
720
721
722 if process.get('forbidden_s_channels'):
723 ninitial = len(filter(lambda leg: leg.get('state') == False,
724 process.get('legs')))
725 if ninitial == 2:
726 res = base_objects.DiagramList(\
727 filter(lambda diagram: \
728 not any([vertex.get_s_channel_id(\
729 process.get('model'), ninitial) \
730 in process.get('forbidden_s_channels')
731 for vertex in diagram.get('vertices')[:-1]]),
732 res))
733 else:
734
735
736 newres= []
737 for diagram in res:
738 leg1 = 1
739
740
741
742 vertex = diagram.get('vertices')[-1]
743 if any([l['number'] ==1 for l in vertex.get('legs')]):
744 leg1 = [l['number'] for l in vertex.get('legs') if l['number'] !=1][0]
745 to_loop = range(len(diagram.get('vertices'))-1)
746 if leg1 >1:
747 to_loop.reverse()
748 for i in to_loop:
749 vertex = diagram.get('vertices')[i]
750 if leg1:
751 if any([l['number'] ==leg1 for l in vertex.get('legs')]):
752 leg1 = 0
753 continue
754 if vertex.get_s_channel_id(process.get('model'), ninitial)\
755 in process.get('forbidden_s_channels'):
756 break
757 else:
758 newres.append(diagram)
759 res = base_objects.DiagramList(newres)
760
761
762
763
764 if process.get('forbidden_onsh_s_channels'):
765 ninitial = len(filter(lambda leg: leg.get('state') == False,
766 process.get('legs')))
767
768 verts = base_objects.VertexList(sum([[vertex for vertex \
769 in diagram.get('vertices')[:-1]
770 if vertex.get_s_channel_id(\
771 process.get('model'), ninitial) \
772 in process.get('forbidden_onsh_s_channels')] \
773 for diagram in res], []))
774 for vert in verts:
775
776 newleg = copy.copy(vert.get('legs').pop(-1))
777 newleg.set('onshell', False)
778 vert.get('legs').append(newleg)
779
780
781 for diagram in res:
782 diagram.calculate_orders(model)
783
784
785
786
787
788
789
790
791 if not returndiag and len(res)>0:
792 res = self.apply_squared_order_constraints(res)
793
794 if diagram_filter:
795 res = self.apply_user_filter(res)
796
797
798 if not process.get('is_decay_chain'):
799 for diagram in res:
800 vertices = diagram.get('vertices')
801 if len(vertices) > 1 and vertices[-1].get('id') == 0:
802
803
804
805
806 vertices = copy.copy(vertices)
807 lastvx = vertices.pop()
808 nexttolastvertex = copy.copy(vertices.pop())
809 legs = copy.copy(nexttolastvertex.get('legs'))
810 ntlnumber = legs[-1].get('number')
811 lastleg = filter(lambda leg: leg.get('number') != ntlnumber,
812 lastvx.get('legs'))[0]
813
814 if lastleg.get('onshell') == False:
815 lastleg.set('onshell', None)
816
817 legs[-1] = lastleg
818 nexttolastvertex.set('legs', legs)
819 vertices.append(nexttolastvertex)
820 diagram.set('vertices', vertices)
821
822 if res and not returndiag:
823 logger.info("Process has %d diagrams" % len(res))
824
825
826 self.trim_diagrams(diaglist=res)
827
828
829 pertur = 'QCD'
830 if self.get('process')['perturbation_couplings']:
831 pertur = sorted(self.get('process')['perturbation_couplings'])[0]
832 self.get('process').get('legs').sort(pert=pertur)
833
834
835 if not returndiag:
836 self['diagrams'] = res
837 return not failed_crossing
838 else:
839 return not failed_crossing, res
840
842 """Applies the user specified squared order constraints on the diagram
843 list in argument."""
844
845 res = copy.copy(diag_list)
846
847
848
849 for name, (value, operator) in self['process'].get('constrained_orders').items():
850 res.filter_constrained_orders(name, value, operator)
851
852
853
854
855 while True:
856 new_res = res.apply_positive_sq_orders(res,
857 self['process'].get('squared_orders'),
858 self['process']['sqorders_types'])
859
860 if len(res)==len(new_res):
861 break
862 elif (len(new_res)>len(res)):
863 raise MadGraph5Error(
864 'Inconsistency in function apply_squared_order_constraints().')
865
866 res = new_res
867
868
869
870
871 neg_orders = [(order, value) for order, value in \
872 self['process'].get('squared_orders').items() if value<0]
873 if len(neg_orders)==1:
874 neg_order, neg_value = neg_orders[0]
875
876 res, target_order = res.apply_negative_sq_order(res, neg_order,\
877 neg_value, self['process']['sqorders_types'][neg_order])
878
879
880
881
882 self['process']['squared_orders'][neg_order]=target_order
883 elif len(neg_orders)>1:
884 raise InvalidCmd('At most one negative squared order constraint'+\
885 ' can be specified, not %s.'%str(neg_orders))
886
887 return res
888
890 """Applies the user specified squared order constraints on the diagram
891 list in argument."""
892
893 if True:
894 remove_diag = misc.plugin_import('user_filter',
895 'user filter required to be defined in PLUGIN/user_filter.py with the function remove_diag(ONEDIAG) which returns True if the diagram has to be removed',
896 fcts=['remove_diag'])
897 else:
898
899 def remove_diag(diag, model=None):
900 for vertex in diag['vertices']:
901 if vertex['id'] == 0:
902 continue
903 if vertex['legs'][-1]['number'] < 3:
904 if abs(vertex['legs'][-1]['id']) <6:
905 return True
906 return False
907
908 res = diag_list.__class__()
909 nb_removed = 0
910 model = self['process']['model']
911 for diag in diag_list:
912 if remove_diag(diag, model):
913 nb_removed +=1
914 else:
915 res.append(diag)
916
917 if nb_removed:
918 logger.warning('Diagram filter is ON and removed %s diagrams for this subprocess.' % nb_removed)
919
920 return res
921
922
923
925 """ Return a Diagram created from the vertex list. This function can be
926 overloaded by daughter classes."""
927 return base_objects.Diagram({'vertices':vertexlist})
928
930 """ In LoopAmplitude, it converts back all DGLoopLegs into Legs.
931 In Amplitude, there is nothing to do. """
932
933 return True
934
936 """ Simply returns a copy of the leg list. This function is
937 overloaded in LoopAmplitude so that a DGLoopLeg list is returned.
938 The DGLoopLeg has some additional parameters only useful during
939 loop diagram generation"""
940
941 return base_objects.LegList(\
942 [ copy.copy(leg) for leg in legs ])
943
944 - def reduce_leglist(self, curr_leglist, max_multi_to1, ref_dict_to0,
945 is_decay_proc = False, coupling_orders = None):
946 """Recursive function to reduce N LegList to N-1
947 For algorithm, see doc for generate_diagrams.
948 """
949
950
951
952 res = []
953
954
955
956 if curr_leglist is None:
957 return None
958
959
960 model = self.get('process').get('model')
961 ref_dict_to1 = self.get('process').get('model').get('ref_dict_to1')
962
963
964
965
966
967
968 if curr_leglist.can_combine_to_0(ref_dict_to0, is_decay_proc):
969
970
971 vertex_ids = self.get_combined_vertices(curr_leglist,
972 copy.copy(ref_dict_to0[tuple(sorted([leg.get('id') for \
973 leg in curr_leglist]))]))
974
975 final_vertices = [base_objects.Vertex({'legs':curr_leglist,
976 'id':vertex_id}) for \
977 vertex_id in vertex_ids]
978
979 for final_vertex in final_vertices:
980 if self.reduce_orders(coupling_orders, model,
981 [final_vertex.get('id')]) != False:
982 res.append([final_vertex])
983
984
985 if len(curr_leglist) == 2:
986 if res:
987 return res
988 else:
989 return None
990
991
992 comb_lists = self.combine_legs(curr_leglist,
993 ref_dict_to1, max_multi_to1)
994
995
996 leg_vertex_list = self.merge_comb_legs(comb_lists, ref_dict_to1)
997
998
999 for leg_vertex_tuple in leg_vertex_list:
1000
1001
1002 if self.get('process').get('forbidden_particles') and \
1003 any([abs(vertex.get('legs')[-1].get('id')) in \
1004 self.get('process').get('forbidden_particles') \
1005 for vertex in leg_vertex_tuple[1]]):
1006 continue
1007
1008
1009 new_coupling_orders = self.reduce_orders(coupling_orders,
1010 model,
1011 [vertex.get('id') for vertex in \
1012 leg_vertex_tuple[1]])
1013 if new_coupling_orders == False:
1014
1015 continue
1016
1017
1018
1019 reduced_diagram = self.reduce_leglist(leg_vertex_tuple[0],
1020 max_multi_to1,
1021 ref_dict_to0,
1022 is_decay_proc,
1023 new_coupling_orders)
1024
1025 if reduced_diagram:
1026 vertex_list_list = [list(leg_vertex_tuple[1])]
1027 vertex_list_list.append(reduced_diagram)
1028 expanded_list = expand_list_list(vertex_list_list)
1029 res.extend(expanded_list)
1030
1031 return res
1032
1033 - def reduce_orders(self, coupling_orders, model, vertex_id_list):
1034 """Return False if the coupling orders for any coupling is <
1035 0, otherwise return the new coupling orders with the vertex
1036 orders subtracted. If coupling_orders is not given, return
1037 None (which counts as success).
1038 WEIGHTED is a special order, which corresponds to the sum of
1039 order hierarchies for the couplings.
1040 We ignore negative constraints as these cannot be taken into
1041 account on the fly but only after generation."""
1042
1043 if not coupling_orders:
1044 return None
1045
1046 present_couplings = copy.copy(coupling_orders)
1047 for id in vertex_id_list:
1048
1049 if not id:
1050 continue
1051 inter = model.get("interaction_dict")[id]
1052 for coupling in inter.get('orders').keys():
1053
1054
1055 if coupling in present_couplings and \
1056 present_couplings[coupling]>=0:
1057
1058 present_couplings[coupling] -= \
1059 inter.get('orders')[coupling]
1060 if present_couplings[coupling] < 0:
1061
1062 return False
1063
1064 if 'WEIGHTED' in present_couplings and \
1065 present_couplings['WEIGHTED']>=0:
1066 weight = sum([model.get('order_hierarchy')[c]*n for \
1067 (c,n) in inter.get('orders').items()])
1068 present_couplings['WEIGHTED'] -= weight
1069 if present_couplings['WEIGHTED'] < 0:
1070
1071 return False
1072
1073 return present_couplings
1074
1075 - def combine_legs(self, list_legs, ref_dict_to1, max_multi_to1):
1076 """Recursive function. Take a list of legs as an input, with
1077 the reference dictionary n-1->1, and output a list of list of
1078 tuples of Legs (allowed combinations) and Legs (rest). Algorithm:
1079
1080 1. Get all n-combinations from list [123456]: [12],..,[23],..,[123],..
1081
1082 2. For each combination, say [34]. Check if combination is valid.
1083 If so:
1084
1085 a. Append [12[34]56] to result array
1086
1087 b. Split [123456] at index(first element in combination+1),
1088 i.e. [12],[456] and subtract combination from second half,
1089 i.e.: [456]-[34]=[56]. Repeat from 1. with this array
1090
1091 3. Take result array from call to 1. (here, [[56]]) and append
1092 (first half in step b - combination) + combination + (result
1093 from 1.) = [12[34][56]] to result array
1094
1095 4. After appending results from all n-combinations, return
1096 resulting array. Example, if [13] and [45] are valid
1097 combinations:
1098 [[[13]2456],[[13]2[45]6],[123[45]6]]
1099 """
1100
1101 res = []
1102
1103
1104 for comb_length in range(2, max_multi_to1 + 1):
1105
1106
1107 if comb_length > len(list_legs):
1108 return res
1109
1110
1111
1112 for comb in itertools.combinations(list_legs, comb_length):
1113
1114
1115 if base_objects.LegList(comb).can_combine_to_1(ref_dict_to1):
1116
1117
1118
1119 res_list = copy.copy(list_legs)
1120 for leg in comb:
1121 res_list.remove(leg)
1122 res_list.insert(list_legs.index(comb[0]), comb)
1123 res.append(res_list)
1124
1125
1126
1127
1128
1129
1130 res_list1 = list_legs[0:list_legs.index(comb[0])]
1131 res_list2 = list_legs[list_legs.index(comb[0]) + 1:]
1132 for leg in comb[1:]:
1133 res_list2.remove(leg)
1134
1135
1136 res_list = res_list1
1137 res_list.append(comb)
1138
1139
1140 for item in self.combine_legs(res_list2,
1141 ref_dict_to1,
1142 max_multi_to1):
1143 final_res_list = copy.copy(res_list)
1144 final_res_list.extend(item)
1145 res.append(final_res_list)
1146
1147 return res
1148
1149
1151 """Takes a list of allowed leg combinations as an input and returns
1152 a set of lists where combinations have been properly replaced
1153 (one list per element in the ref_dict, so that all possible intermediate
1154 particles are included). For each list, give the list of vertices
1155 corresponding to the executed merging, group the two as a tuple.
1156 """
1157
1158 res = []
1159
1160 for comb_list in comb_lists:
1161
1162 reduced_list = []
1163 vertex_list = []
1164
1165 for entry in comb_list:
1166
1167
1168 if isinstance(entry, tuple):
1169
1170
1171
1172 leg_vert_ids = copy.copy(ref_dict_to1[\
1173 tuple(sorted([leg.get('id') for leg in entry]))])
1174
1175
1176 number = min([leg.get('number') for leg in entry])
1177
1178
1179 if len(filter(lambda leg: leg.get('state') == False,
1180 entry)) == 1:
1181 state = False
1182 else:
1183 state = True
1184
1185
1186
1187
1188
1189 new_leg_vert_ids = []
1190 if leg_vert_ids:
1191 new_leg_vert_ids = self.get_combined_legs(entry,
1192 leg_vert_ids,
1193 number,
1194 state)
1195
1196 reduced_list.append([l[0] for l in new_leg_vert_ids])
1197
1198
1199
1200
1201
1202 vlist = base_objects.VertexList()
1203 for (myleg, vert_id) in new_leg_vert_ids:
1204
1205 myleglist = base_objects.LegList(list(entry))
1206
1207 myleglist.append(myleg)
1208
1209 vlist.append(base_objects.Vertex(
1210 {'legs':myleglist,
1211 'id':vert_id}))
1212
1213 vertex_list.append(vlist)
1214
1215
1216
1217 else:
1218 cp_entry = copy.copy(entry)
1219
1220
1221
1222 if cp_entry.get('from_group') != None:
1223 cp_entry.set('from_group', False)
1224 reduced_list.append(cp_entry)
1225
1226
1227 flat_red_lists = expand_list(reduced_list)
1228 flat_vx_lists = expand_list(vertex_list)
1229
1230
1231 for i in range(0, len(flat_vx_lists)):
1232 res.append((base_objects.LegList(flat_red_lists[i]), \
1233 base_objects.VertexList(flat_vx_lists[i])))
1234
1235 return res
1236
1238 """Create a set of new legs from the info given. This can be
1239 overloaded by daughter classes."""
1240
1241 mylegs = [(base_objects.Leg({'id':leg_id,
1242 'number':number,
1243 'state':state,
1244 'from_group':True}),
1245 vert_id)\
1246 for leg_id, vert_id in leg_vert_ids]
1247
1248 return mylegs
1249
1251 """Allow for selection of vertex ids. This can be
1252 overloaded by daughter classes."""
1253
1254 return vert_ids
1255
1257 """Reduce the number of legs and vertices used in memory.
1258 When called by a diagram generation initiated by LoopAmplitude,
1259 this function should not trim the diagrams in the attribute 'diagrams'
1260 but rather a given list in the 'diaglist' argument."""
1261
1262 legs = []
1263 vertices = []
1264
1265 if diaglist is None:
1266 diaglist=self.get('diagrams')
1267
1268
1269 process = self.get('process')
1270 for leg in process.get('legs'):
1271 if leg.get('state') and leg.get('id') in decay_ids:
1272 leg.set('onshell', True)
1273
1274 for diagram in diaglist:
1275
1276 leg_external = set()
1277 for ivx, vertex in enumerate(diagram.get('vertices')):
1278 for ileg, leg in enumerate(vertex.get('legs')):
1279
1280 if leg.get('state') and leg.get('id') in decay_ids and \
1281 leg.get('number') not in leg_external:
1282
1283
1284 leg = copy.copy(leg)
1285 leg.set('onshell', True)
1286 try:
1287 index = legs.index(leg)
1288 except ValueError:
1289 vertex.get('legs')[ileg] = leg
1290 legs.append(leg)
1291 else:
1292 vertex.get('legs')[ileg] = legs[index]
1293 leg_external.add(leg.get('number'))
1294 try:
1295 index = vertices.index(vertex)
1296 diagram.get('vertices')[ivx] = vertices[index]
1297 except ValueError:
1298 vertices.append(vertex)
1299
1300
1301
1302
1303 -class AmplitudeList(base_objects.PhysicsObjectList):
1304 """List of Amplitude objects
1305 """
1306
1308 """ Check the content of all processes of the amplitudes in this list to
1309 see if there is any which defines perturbation couplings. """
1310
1311 for amp in self:
1312 if amp.has_loop_process():
1313 return True
1314
1316 """Test if object obj is a valid Amplitude for the list."""
1317
1318 return isinstance(obj, Amplitude)
1319
1324 """A list of amplitudes + a list of decay chain amplitude lists;
1325 corresponding to a ProcessDefinition with a list of decay chains
1326 """
1327
1333
1334 - def __init__(self, argument = None, collect_mirror_procs = False,
1335 ignore_six_quark_processes = False, loop_filter=None, diagram_filter=False):
1336 """Allow initialization with Process and with ProcessDefinition"""
1337
1338 if isinstance(argument, base_objects.Process):
1339 super(DecayChainAmplitude, self).__init__()
1340 from madgraph.loop.loop_diagram_generation import LoopMultiProcess
1341 if argument['perturbation_couplings']:
1342 MultiProcessClass=LoopMultiProcess
1343 else:
1344 MultiProcessClass=MultiProcess
1345 if isinstance(argument, base_objects.ProcessDefinition):
1346 self['amplitudes'].extend(\
1347 MultiProcessClass.generate_multi_amplitudes(argument,
1348 collect_mirror_procs,
1349 ignore_six_quark_processes,
1350 loop_filter=loop_filter,
1351 diagram_filter=diagram_filter))
1352 else:
1353 self['amplitudes'].append(\
1354 MultiProcessClass.get_amplitude_from_proc(argument,
1355 loop_filter=loop_filter,
1356 diagram_filter=diagram_filter))
1357
1358
1359 process = copy.copy(self.get('amplitudes')[0].get('process'))
1360 process.set('decay_chains', base_objects.ProcessList())
1361 self['amplitudes'][0].set('process', process)
1362
1363 for process in argument.get('decay_chains'):
1364 if process.get('perturbation_couplings'):
1365 raise MadGraph5Error,\
1366 "Decay processes can not be perturbed"
1367 process.set('overall_orders', argument.get('overall_orders'))
1368 if not process.get('is_decay_chain'):
1369 process.set('is_decay_chain',True)
1370 if not process.get_ninitial() == 1:
1371 raise InvalidCmd,\
1372 "Decay chain process must have exactly one" + \
1373 " incoming particle"
1374 self['decay_chains'].append(\
1375 DecayChainAmplitude(process, collect_mirror_procs,
1376 ignore_six_quark_processes,
1377 diagram_filter=diagram_filter))
1378
1379
1380 decay_ids = sum([[a.get('process').get('legs')[0].get('id') \
1381 for a in dec.get('amplitudes')] for dec in \
1382 self['decay_chains']], [])
1383 decay_ids = set(decay_ids)
1384 for amp in self['amplitudes']:
1385 amp.trim_diagrams(decay_ids)
1386
1387
1388 for amp in self['amplitudes']:
1389 for l in amp.get('process').get('legs'):
1390 if l.get('id') in decay_ids:
1391 decay_ids.remove(l.get('id'))
1392
1393 if decay_ids:
1394 model = amp.get('process').get('model')
1395 names = [model.get_particle(id).get('name') for id in decay_ids]
1396
1397 logger.warning(
1398 "$RED Decay without corresponding particle in core process found.\n" + \
1399 "Decay information for particle(s) %s is discarded.\n" % ','.join(names) + \
1400 "Please check your process definition carefully. \n" + \
1401 "This warning usually means that you forgot parentheses in presence of subdecay.\n" + \
1402 "Example of correct syntax: p p > t t~, ( t > w+ b, w+ > l+ vl)")
1403
1404
1405 for dc in reversed(self['decay_chains']):
1406 for a in reversed(dc.get('amplitudes')):
1407
1408 if a.get('process').get('legs')[0].get('id') in decay_ids:
1409 dc.get('amplitudes').remove(a)
1410 if not dc.get('amplitudes'):
1411
1412 self['decay_chains'].remove(dc)
1413
1414
1415
1416 bad_procs = []
1417 for dc in self['decay_chains']:
1418 for amp in dc.get('amplitudes'):
1419 legs = amp.get('process').get('legs')
1420 fs_parts = [abs(l.get('id')) for l in legs if
1421 l.get('state')]
1422 is_part = [l.get('id') for l in legs if not
1423 l.get('state')][0]
1424 if abs(is_part) in fs_parts:
1425 bad_procs.append(amp.get('process'))
1426
1427 if bad_procs:
1428 logger.warning(
1429 "$RED Decay(s) with particle decaying to itself:\n" + \
1430 '\n'.join([p.nice_string() for p in bad_procs]) + \
1431 "\nPlease check your process definition carefully. \n")
1432
1433
1434 elif argument != None:
1435
1436 super(DecayChainAmplitude, self).__init__(argument)
1437 else:
1438
1439 super(DecayChainAmplitude, self).__init__()
1440
1441 - def filter(self, name, value):
1442 """Filter for valid amplitude property values."""
1443
1444 if name == 'amplitudes':
1445 if not isinstance(value, AmplitudeList):
1446 raise self.PhysicsObjectError, \
1447 "%s is not a valid AmplitudeList" % str(value)
1448 if name == 'decay_chains':
1449 if not isinstance(value, DecayChainAmplitudeList):
1450 raise self.PhysicsObjectError, \
1451 "%s is not a valid DecayChainAmplitudeList object" % \
1452 str(value)
1453 return True
1454
1456 """Return diagram property names as a nicely sorted list."""
1457
1458 return ['amplitudes', 'decay_chains']
1459
1460
1461
1463 """Returns number of diagrams for this amplitude"""
1464 return sum(len(a.get('diagrams')) for a in self.get('amplitudes')) \
1465 + sum(d.get_number_of_diagrams() for d in \
1466 self.get('decay_chains'))
1467
1469 """Returns a nicely formatted string of the amplitude content."""
1470 mystr = ""
1471 for amplitude in self.get('amplitudes'):
1472 mystr = mystr + amplitude.nice_string(indent) + "\n"
1473
1474 if self.get('decay_chains'):
1475 mystr = mystr + " " * indent + "Decays:\n"
1476 for dec in self.get('decay_chains'):
1477 mystr = mystr + dec.nice_string(indent + 2) + "\n"
1478
1479 return mystr[:-1]
1480
1482 """Returns a nicely formatted string of the amplitude processes."""
1483 mystr = ""
1484 for amplitude in self.get('amplitudes'):
1485 mystr = mystr + amplitude.nice_string_processes(indent) + "\n"
1486
1487 if self.get('decay_chains'):
1488 mystr = mystr + " " * indent + "Decays:\n"
1489 for dec in self.get('decay_chains'):
1490 mystr = mystr + dec.nice_string_processes(indent + 2) + "\n"
1491
1492 return mystr[:-1]
1493
1495 """Returns the number of initial state particles in the process."""
1496 return self.get('amplitudes')[0].get('process').get_ninitial()
1497
1499 """Returns a set of all particle ids for which a decay is defined"""
1500
1501 decay_ids = []
1502
1503
1504 for amp in sum([dc.get('amplitudes') for dc \
1505 in self['decay_chains']], []):
1506
1507 decay_ids.append(amp.get('process').get_initial_ids()[0])
1508
1509
1510 return list(set(decay_ids))
1511
1513 """ Returns wether this amplitude has a loop process."""
1514 return self['amplitudes'].has_any_loop_process()
1515
1517 """Recursive function to extract all amplitudes for this process"""
1518
1519 amplitudes = AmplitudeList()
1520
1521 amplitudes.extend(self.get('amplitudes'))
1522 for decay in self.get('decay_chains'):
1523 amplitudes.extend(decay.get_amplitudes())
1524
1525 return amplitudes
1526
1532 """List of DecayChainAmplitude objects
1533 """
1534
1536 """Test if object obj is a valid DecayChainAmplitude for the list."""
1537
1538 return isinstance(obj, DecayChainAmplitude)
1539
1540
1541
1542
1543
1544 -class MultiProcess(base_objects.PhysicsObject):
1545 """MultiProcess: list of process definitions
1546 list of processes (after cleaning)
1547 list of amplitudes (after generation)
1548 """
1549
1551 """Default values for all properties"""
1552
1553 self['process_definitions'] = base_objects.ProcessDefinitionList()
1554
1555
1556
1557 self['amplitudes'] = AmplitudeList()
1558
1559 self['collect_mirror_procs'] = False
1560
1561
1562 self['ignore_six_quark_processes'] = []
1563
1564
1565 self['use_numerical'] = False
1566
1567 - def __init__(self, argument=None, collect_mirror_procs = False,
1568 ignore_six_quark_processes = [], optimize=False,
1569 loop_filter=None, diagram_filter=None):
1597
1598
1599 - def filter(self, name, value):
1600 """Filter for valid process property values."""
1601
1602 if name == 'process_definitions':
1603 if not isinstance(value, base_objects.ProcessDefinitionList):
1604 raise self.PhysicsObjectError, \
1605 "%s is not a valid ProcessDefinitionList object" % str(value)
1606
1607 if name == 'amplitudes':
1608 if not isinstance(value, AmplitudeList):
1609 raise self.PhysicsObjectError, \
1610 "%s is not a valid AmplitudeList object" % str(value)
1611
1612 if name in ['collect_mirror_procs']:
1613 if not isinstance(value, bool):
1614 raise self.PhysicsObjectError, \
1615 "%s is not a valid boolean" % str(value)
1616
1617 if name == 'ignore_six_quark_processes':
1618 if not isinstance(value, list):
1619 raise self.PhysicsObjectError, \
1620 "%s is not a valid list" % str(value)
1621
1622 return True
1623
1624 - def get(self, name):
1625 """Get the value of the property name."""
1626
1627 if (name == 'amplitudes') and not self[name]:
1628 for process_def in self.get('process_definitions'):
1629 if process_def.get('decay_chains'):
1630
1631
1632 self['amplitudes'].append(\
1633 DecayChainAmplitude(process_def,
1634 self.get('collect_mirror_procs'),
1635 self.get('ignore_six_quark_processes'),
1636 diagram_filter=self['diagram_filter']))
1637 else:
1638 self['amplitudes'].extend(\
1639 self.generate_multi_amplitudes(process_def,
1640 self.get('collect_mirror_procs'),
1641 self.get('ignore_six_quark_processes'),
1642 self['use_numerical'],
1643 loop_filter=self['loop_filter'],
1644 diagram_filter=self['diagram_filter']))
1645
1646 return MultiProcess.__bases__[0].get(self, name)
1647
1649 """Return process property names as a nicely sorted list."""
1650
1651 return ['process_definitions', 'amplitudes']
1652
1653 @classmethod
1654 - def generate_multi_amplitudes(cls,process_definition,
1655 collect_mirror_procs = False,
1656 ignore_six_quark_processes = [],
1657 use_numerical=False,
1658 loop_filter=None,
1659 diagram_filter=False):
1660 """Generate amplitudes in a semi-efficient way.
1661 Make use of crossing symmetry for processes that fail diagram
1662 generation, but not for processes that succeed diagram
1663 generation. Doing so will risk making it impossible to
1664 identify processes with identical amplitudes.
1665 """
1666 assert isinstance(process_definition, base_objects.ProcessDefinition), \
1667 "%s not valid ProcessDefinition object" % \
1668 repr(process_definition)
1669
1670
1671 process_definition.set('orders', MultiProcess.\
1672 find_optimal_process_orders(process_definition,
1673 diagram_filter))
1674
1675 process_definition.check_expansion_orders()
1676
1677 processes = base_objects.ProcessList()
1678 amplitudes = AmplitudeList()
1679
1680
1681
1682 failed_procs = []
1683 success_procs = []
1684
1685 non_permuted_procs = []
1686
1687 permutations = []
1688
1689
1690
1691 model = process_definition['model']
1692
1693 islegs = [leg for leg in process_definition['legs'] \
1694 if leg['state'] == False]
1695 fslegs = [leg for leg in process_definition['legs'] \
1696 if leg['state'] == True]
1697
1698 isids = [leg['ids'] for leg in process_definition['legs'] \
1699 if leg['state'] == False]
1700 fsids = [leg['ids'] for leg in process_definition['legs'] \
1701 if leg['state'] == True]
1702 polids = [tuple(leg['polarization']) for leg in process_definition['legs'] \
1703 if leg['state'] == True]
1704
1705 for prod in itertools.product(*isids):
1706 islegs = [\
1707 base_objects.Leg({'id':id, 'state': False,
1708 'polarization': islegs[i]['polarization']})
1709 for i,id in enumerate(prod)]
1710
1711
1712
1713
1714 red_fsidlist = set()
1715
1716 for prod in itertools.product(*fsids):
1717 tag = zip(prod, polids)
1718 tag = sorted(tag)
1719
1720 if tuple(tag) in red_fsidlist:
1721 continue
1722
1723 red_fsidlist.add(tuple(tag))
1724
1725 leg_list = [copy.copy(leg) for leg in islegs]
1726 leg_list.extend([\
1727 base_objects.Leg({'id':id, 'state': True, 'polarization': fslegs[i]['polarization']}) \
1728 for i,id in enumerate(prod)])
1729
1730 legs = base_objects.LegList(leg_list)
1731
1732
1733 sorted_legs = sorted([(l,i+1) for (i,l) in \
1734 enumerate(legs.get_outgoing_id_list(model))])
1735 permutation = [l[1] for l in sorted_legs]
1736
1737 sorted_legs = array.array('i', [l[0] for l in sorted_legs])
1738
1739
1740 if ignore_six_quark_processes and \
1741 len([i for i in sorted_legs if abs(i) in \
1742 ignore_six_quark_processes]) >= 6:
1743 continue
1744
1745
1746
1747 if sorted_legs in failed_procs:
1748 continue
1749
1750
1751 if use_numerical:
1752
1753 initial_mass = abs(model['parameter_dict'][model.get_particle(legs[0].get('id')).get('mass')])
1754 if initial_mass == 0:
1755 continue
1756 for leg in legs[1:]:
1757 m = model['parameter_dict'][model.get_particle(leg.get('id')).get('mass')]
1758 initial_mass -= abs(m)
1759 if initial_mass.real <= 0:
1760 continue
1761
1762
1763 process = process_definition.get_process_with_legs(legs)
1764
1765 fast_proc = \
1766 array.array('i',[leg.get('id') for leg in legs])
1767 if collect_mirror_procs and \
1768 process_definition.get_ninitial() == 2:
1769
1770 mirror_proc = \
1771 array.array('i', [fast_proc[1], fast_proc[0]] + \
1772 list(fast_proc[2:]))
1773 try:
1774 mirror_amp = \
1775 amplitudes[non_permuted_procs.index(mirror_proc)]
1776 except Exception:
1777
1778 pass
1779 else:
1780
1781 mirror_amp.set('has_mirror_process', True)
1782 logger.info("Process %s added to mirror process %s" % \
1783 (process.base_string(),
1784 mirror_amp.get('process').base_string()))
1785 continue
1786
1787
1788
1789 if not process.get('required_s_channels') and \
1790 not process.get('forbidden_onsh_s_channels') and \
1791 not process.get('forbidden_s_channels') and \
1792 not process.get('is_decay_chain') and not diagram_filter:
1793 try:
1794 crossed_index = success_procs.index(sorted_legs)
1795
1796
1797
1798
1799 if 'loop_diagrams' in amplitudes[crossed_index]:
1800 raise ValueError
1801 except ValueError:
1802
1803 pass
1804 else:
1805
1806 amplitude = MultiProcess.cross_amplitude(\
1807 amplitudes[crossed_index],
1808 process,
1809 permutations[crossed_index],
1810 permutation)
1811 amplitudes.append(amplitude)
1812 success_procs.append(sorted_legs)
1813 permutations.append(permutation)
1814 non_permuted_procs.append(fast_proc)
1815 logger.info("Crossed process found for %s, reuse diagrams." % \
1816 process.base_string())
1817 continue
1818
1819
1820 amplitude = cls.get_amplitude_from_proc(process,
1821 loop_filter=loop_filter)
1822
1823 try:
1824 result = amplitude.generate_diagrams(diagram_filter=diagram_filter)
1825 except InvalidCmd as error:
1826 failed_procs.append(sorted_legs)
1827 else:
1828
1829 if amplitude.get('diagrams'):
1830 amplitudes.append(amplitude)
1831 success_procs.append(sorted_legs)
1832 permutations.append(permutation)
1833 non_permuted_procs.append(fast_proc)
1834 elif not result:
1835
1836 failed_procs.append(sorted_legs)
1837
1838
1839 if not amplitudes:
1840 if len(failed_procs) == 1 and 'error' in locals():
1841 raise error
1842 else:
1843 raise NoDiagramException, \
1844 "No amplitudes generated from process %s. Please enter a valid process" % \
1845 process_definition.nice_string()
1846
1847
1848
1849 return amplitudes
1850
1851 @classmethod
1853 """ Return the correct amplitude type according to the characteristics of
1854 the process proc. The only option that could be specified here is
1855 loop_filter and it is of course not relevant for a tree amplitude."""
1856
1857 return Amplitude({"process": proc})
1858
1859
1860 @staticmethod
1862 """Find the minimal WEIGHTED order for this set of processes.
1863
1864 The algorithm:
1865
1866 1) Check the coupling hierarchy of the model. Assign all
1867 particles to the different coupling hierarchies so that a
1868 particle is considered to be in the highest hierarchy (i.e.,
1869 with lowest value) where it has an interaction.
1870
1871 2) Pick out the legs in the multiprocess according to the
1872 highest hierarchy represented (so don't mix particles from
1873 different hierarchy classes in the same multiparticles!)
1874
1875 3) Find the starting maximum WEIGHTED order as the sum of the
1876 highest n-2 weighted orders
1877
1878 4) Pick out required s-channel particle hierarchies, and use
1879 the highest of the maximum WEIGHTED order from the legs and
1880 the minimum WEIGHTED order extracted from 2*s-channel
1881 hierarchys plus the n-2-2*(number of s-channels) lowest
1882 leg weighted orders.
1883
1884 5) Run process generation with the WEIGHTED order determined
1885 in 3)-4) - # final state gluons, with all gluons removed from
1886 the final state
1887
1888 6) If no process is found, increase WEIGHTED order by 1 and go
1889 back to 5), until we find a process which passes. Return that
1890 order.
1891
1892 7) Continue 5)-6) until we reach (n-2)*(highest hierarchy)-1.
1893 If still no process has passed, return
1894 WEIGHTED = (n-2)*(highest hierarchy)
1895 """
1896
1897 assert isinstance(process_definition, base_objects.ProcessDefinition), \
1898 "%s not valid ProcessDefinition object" % \
1899 repr(process_definition)
1900
1901 processes = base_objects.ProcessList()
1902 amplitudes = AmplitudeList()
1903
1904
1905 if process_definition.get('orders') or \
1906 process_definition.get('overall_orders') or \
1907 process_definition.get('NLO_mode')=='virt':
1908 return process_definition.get('orders')
1909
1910
1911 if process_definition.get_ninitial() == 1 and not \
1912 process_definition.get('is_decay_chain'):
1913 return process_definition.get('orders')
1914
1915 logger.info("Checking for minimal orders which gives processes.")
1916 logger.info("Please specify coupling orders to bypass this step.")
1917
1918
1919 max_order_now, particles, hierarchy = \
1920 process_definition.get_minimum_WEIGHTED()
1921 coupling = 'WEIGHTED'
1922
1923 model = process_definition.get('model')
1924
1925
1926 isids = [leg['ids'] for leg in \
1927 filter(lambda leg: leg['state'] == False, process_definition['legs'])]
1928 fsids = [leg['ids'] for leg in \
1929 filter(lambda leg: leg['state'] == True, process_definition['legs'])]
1930
1931 max_WEIGHTED_order = \
1932 (len(fsids + isids) - 2)*int(model.get_max_WEIGHTED())
1933
1934 hierarchydef = process_definition['model'].get('order_hierarchy')
1935 tmp = []
1936 hierarchy = hierarchydef.items()
1937 hierarchy.sort()
1938 for key, value in hierarchydef.items():
1939 if value>1:
1940 tmp.append('%s*%s' % (value,key))
1941 else:
1942 tmp.append('%s' % key)
1943 wgtdef = '+'.join(tmp)
1944
1945
1946 while max_order_now < max_WEIGHTED_order:
1947 logger.info("Trying coupling order WEIGHTED<=%d: WEIGTHED IS %s" % (max_order_now, wgtdef))
1948
1949 oldloglevel = logger.level
1950 logger.setLevel(logging.WARNING)
1951
1952
1953
1954 failed_procs = []
1955
1956 for prod in apply(itertools.product, isids):
1957 islegs = [ base_objects.Leg({'id':id, 'state': False}) \
1958 for id in prod]
1959
1960
1961
1962
1963 red_fsidlist = []
1964
1965 for prod in apply(itertools.product, fsids):
1966
1967
1968 if tuple(sorted(prod)) in red_fsidlist:
1969 continue
1970
1971 red_fsidlist.append(tuple(sorted(prod)));
1972
1973
1974
1975 nglue = 0
1976 if 21 in particles[0]:
1977 nglue = len([id for id in prod if id == 21])
1978 prod = [id for id in prod if id != 21]
1979
1980
1981 leg_list = [copy.copy(leg) for leg in islegs]
1982
1983 leg_list.extend([\
1984 base_objects.Leg({'id':id, 'state': True}) \
1985 for id in prod])
1986
1987 legs = base_objects.LegList(leg_list)
1988
1989
1990
1991 coupling_orders_now = {coupling: max_order_now - \
1992 nglue * model['order_hierarchy']['QCD']}
1993
1994
1995 process = base_objects.Process({\
1996 'legs':legs,
1997 'model':model,
1998 'id': process_definition.get('id'),
1999 'orders': coupling_orders_now,
2000 'required_s_channels': \
2001 process_definition.get('required_s_channels'),
2002 'forbidden_onsh_s_channels': \
2003 process_definition.get('forbidden_onsh_s_channels'),
2004 'sqorders_types': \
2005 process_definition.get('sqorders_types'),
2006 'squared_orders': \
2007 process_definition.get('squared_orders'),
2008 'split_orders': \
2009 process_definition.get('split_orders'),
2010 'forbidden_s_channels': \
2011 process_definition.get('forbidden_s_channels'),
2012 'forbidden_particles': \
2013 process_definition.get('forbidden_particles'),
2014 'is_decay_chain': \
2015 process_definition.get('is_decay_chain'),
2016 'overall_orders': \
2017 process_definition.get('overall_orders'),
2018 'split_orders': \
2019 process_definition.get('split_orders')})
2020
2021
2022 process.check_expansion_orders()
2023
2024
2025 sorted_legs = sorted(legs.get_outgoing_id_list(model))
2026
2027
2028 if tuple(sorted_legs) in failed_procs and not process_definition.get('forbidden_s_channels'):
2029 continue
2030
2031 amplitude = Amplitude({'process': process})
2032 try:
2033 amplitude.generate_diagrams(diagram_filter=diagram_filter)
2034 except InvalidCmd, error:
2035 failed_procs.append(tuple(sorted_legs))
2036 else:
2037 if amplitude.get('diagrams'):
2038
2039 logger.setLevel(oldloglevel)
2040 return {coupling: max_order_now}
2041 else:
2042 failed_procs.append(tuple(sorted_legs))
2043
2044 max_order_now += 1
2045 logger.setLevel(oldloglevel)
2046
2047
2048 return {coupling: max_order_now}
2049
2050 @staticmethod
2052 """Return the amplitude crossed with the permutation new_perm"""
2053
2054 perm_map = dict(zip(org_perm, new_perm))
2055
2056 new_amp = copy.copy(amplitude)
2057
2058 for i, leg in enumerate(process.get('legs')):
2059 leg.set('number', i+1)
2060
2061 new_amp.set('process', process)
2062
2063 diagrams = base_objects.DiagramList([d.renumber_legs(perm_map,
2064 process.get('legs'),) for \
2065 d in new_amp.get('diagrams')])
2066 new_amp.set('diagrams', diagrams)
2067 new_amp.trim_diagrams()
2068
2069
2070 new_amp.set('has_mirror_process', False)
2071
2072 return new_amp
2073
2079 """Takes a list of lists and elements and returns a list of flat lists.
2080 Example: [[1,2], 3, [4,5]] -> [[1,3,4], [1,3,5], [2,3,4], [2,3,5]]
2081 """
2082
2083
2084 assert isinstance(mylist, list), "Expand_list argument must be a list"
2085
2086 res = []
2087
2088 tmplist = []
2089 for item in mylist:
2090 if isinstance(item, list):
2091 tmplist.append(item)
2092 else:
2093 tmplist.append([item])
2094
2095 for item in apply(itertools.product, tmplist):
2096 res.append(list(item))
2097
2098 return res
2099
2101 """Recursive function. Takes a list of lists and lists of lists
2102 and returns a list of flat lists.
2103 Example: [[1,2],[[4,5],[6,7]]] -> [[1,2,4,5], [1,2,6,7]]
2104 """
2105
2106 res = []
2107
2108 if not mylist or len(mylist) == 1 and not mylist[0]:
2109 return [[]]
2110
2111
2112 assert isinstance(mylist[0], list), \
2113 "Expand_list_list needs a list of lists and lists of lists"
2114
2115
2116 if len(mylist) == 1:
2117 if isinstance(mylist[0][0], list):
2118 return mylist[0]
2119 else:
2120 return mylist
2121
2122 if isinstance(mylist[0][0], list):
2123 for item in mylist[0]:
2124
2125
2126
2127 for rest in expand_list_list(mylist[1:]):
2128 reslist = copy.copy(item)
2129 reslist.extend(rest)
2130 res.append(reslist)
2131 else:
2132 for rest in expand_list_list(mylist[1:]):
2133 reslist = copy.copy(mylist[0])
2134 reslist.extend(rest)
2135 res.append(reslist)
2136
2137
2138 return res
2139