1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """All the routines to choose the position to each vertex and the
16 direction for particles. All those class are not related to any output class.
17
18 This file contains 4 class:
19 * FeynmanLine which extend the Leg with positioning information
20 * VertexPoint which extend the vertex with position and line information.
21 Coordinates belongs to [0,1] interval
22 * FeynmanDiagram which
23 1) Extends a diagram to have position information - load_diagram
24 2) Is able to structure the vertex in level - define_level
25 level are the number of s_channel line-initial particles
26 separating the vertex from the initial particles starting point.
27 3) Attributes position to each vertex - find_initial_vertex_position
28 * FeynmanDiagramHorizontal
29 is a child of FeynmanDiagram which assign position in a different way.
30
31 The x-coordinate will proportional to the level, both in FeynmanDiagram and
32 in FeynmanDiagramHorizontal
33
34 In FeynmanDiagram, the y-coordinate of external particles are put (if
35 possible and if option authorizes) to 0,1. all other y-coordinate are
36 assign such that the distance between two neighbor of the same level
37 are always the same.
38
39 In FeynmanDiagramHorizontal, an additional rules apply: if only one
40 S-channel is going from level to the next, then this S-channel should be
41 horizontal."""
42
43 from __future__ import division
44
45 import math
46
47 import madgraph.core.base_objects as base_objects
48 import madgraph.loop.loop_base_objects as loop_objects
49
50
51
52
54 """All the information about a line in a Feynman diagram
55 i.e. begin-end/type/tag."""
56
58 """Exception raised if an error occurs in the definition
59 or the execution of a Feynam_line."""
60
62 """Initialize the FeynmanLine content."""
63
64
65
66 self.loop_line = False
67 for key, value in init_dict.items():
68 setattr(self, key, value)
69 self.begin = 0
70 self.end = 0
71
72
73
74
75
76
77
78
79
81 """
82 make a link between the present object and the associate model
83 """
84
85 assert isinstance(model, base_objects.Model), ' try to assign a non model obect'
86
87 self.model = model
88
90 """-Re-Define the starting point of the line."""
91
92 assert isinstance(vertex, VertexPoint), 'The begin point should be a ' + \
93 'Vertex_Point object'
94
95 self.begin = vertex
96 vertex.add_line(self)
97 return
98
100 """-Re-Define the starting point of the line. with check"""
101
102 assert isinstance(vertex, VertexPoint), 'The end point should be a ' + \
103 'Vertex_Point object'
104
105 self.end = vertex
106 vertex.add_line(self)
107 return
108
110 """Associate the vertex to the line at the correct position.
111 line.begin should be closer of the lower right corner than line.end.
112
113 This is achieved in the following way:
114 * We don't care about external particles.Those one will be perform
115 easily in a second step. In the mean time we apply this method anyway.
116 Internal particles are created from a combination of particles.
117 * S-channel either are create from number [X,Y,Z are strictly bigger
118 than two and A,B,C are strictly bigger than one).
119 (1 A [X Y]> 1) =>forward
120 (X Y [Z]> X) => backward
121 * T-channel are also produce either by
122 (1 X> 1) =>forward
123 (2 X >2) => backward
124 So the common rule is to check if the number is one or not.
125 """
126
127
128 if self.begin:
129 self.def_end_point(vertex)
130 elif self.end:
131 self.def_begin_point(vertex)
132
133 else:
134 number = self.number
135 if number == 1:
136 self.def_begin_point(vertex)
137 else:
138 self.def_end_point(vertex)
139
141 """Define the line orientation. Use the following rules:
142 Particles move timelike when anti-particles move anti-timelike.
143 """
144
145 if (self.id < 0):
146 self.inverse_begin_end()
147
149 """Change the particle in his anti-particle if this type is
150 equal to 'inversetype'."""
151
152 drawtype = self.get_info('line')
153 if drawtype == inversetype:
154 self.inverse_part_antipart()
155
157 """Pass particle into an anti-particle. This is needed for initial state
158 particles (usually wrongly defined) and for some fermion flow resolution
159 problem."""
160
161 self.id = -1 * self.id
162
164 """Invert the orientation of the line. This is needed to have correct
165 fermion flow."""
166
167 self.begin, self.end = self.end, self.begin
168
170 """Return the model information 'name' associated to the line."""
171
172 pid = abs(self.id)
173 return self.model.get_particle(pid).get(name)
174
175
177 """Return the name associate to the particle."""
178
179 pid = self.id
180 model_info = self.model.get_particle(pid)
181
182 if pid > 0:
183 return model_info.get(name)
184 elif model_info:
185 return model_info.get('anti' + name)
186 else:
187
188 return self.model.get_particle(-1 * pid).get(name)
189
191 """ return the length of the line """
192
193 return math.sqrt((self.end.pos_x - self.begin.pos_x) ** 2 + \
194 (self.end.pos_y - self.begin.pos_y) ** 2)
195
196
198 """Returns True if the particle is a fermion."""
199
200 model_info = self.model.get_particle(abs(self.id))
201 if model_info.get('spin') % 2 == 0:
202 return True
203
205 """Check if this line represent an external particles or not."""
206
207 return self.end.is_external() or self.begin.is_external()
208
210 """Define that two line are equal when they have the same pointer"""
211
212 return self is other
213
215 """Define that two line are different when they have different
216 pointer."""
217
218 return self is not other
219
220
221
222
224 """Check if the two line intersects and returns status. A common vertex
225 is not consider as an intersection.
226 This routine first check input validity.
227
228 At current status this is use for test/debugging only."""
229
230 assert self.check_position_exist()
231 assert line.check_position_exist()
232
233 return self._has_intersection(line)
234
236 """Check if the two line intersects and returns status. A common vertex
237 is not consider as an intersection.
238
239 At current status this is only use for test/debugging only."""
240
241
242 sys_error = 1e-7
243
244
245 min, max = self._domain_intersection(line)
246
247
248 if min == None:
249 return False
250
251
252 if min == max :
253
254 if abs(self.begin.pos_x - self.end.pos_x) > sys_error:
255
256 if abs(line.begin.pos_x - line.end.pos_x) > sys_error:
257
258 return False
259
260 return self._intersection_with_vertical_line(line)
261
262
263 elif (abs(line.begin.pos_x - line.end.pos_x) > sys_error):
264
265 return line._intersection_with_vertical_line(self)
266
267
268 else:
269
270 min, max = self._domain_intersection(line, 'y')
271 if min == None or min == max:
272 return False
273 else:
274 return True
275
276
277 xS0 = self.begin.pos_x
278 yS0 = self.begin.pos_y
279 xS1 = self.end.pos_x
280 yS1 = self.end.pos_y
281
282 xL0 = line.begin.pos_x
283 yL0 = line.begin.pos_y
284 xL1 = line.end.pos_x
285 yL1 = line.end.pos_y
286
287 coef1 = (yS1 - yS0) / (xS1 - xS0)
288 coef2 = (yL1 - yL0) / (xL1 - xL0)
289
290
291 if abs(coef1 - coef2) < sys_error:
292
293 if abs(line._has_ordinate(min) - self._has_ordinate(min)) < \
294 sys_error:
295 return True
296 else:
297 return False
298
299
300 commonX = (yS0 - yL0 - coef1 * xS0 + coef2 * xL0) / (coef2 - coef1)
301
302
303 if (commonX >= min) == (commonX >= max):
304 return False
305
306 commonY = self._has_ordinate(commonX)
307
308
309 if self.is_end_point(commonX, commonY):
310 if line.is_end_point(commonX, commonY):
311 return False
312 else:
313 return True
314 else:
315 return True
316
318 """Check if 'x','y' are one of the end point coordinates of the line.
319
320 At current status this is use for test/debugging only."""
321
322
323 gap = 1e-9
324
325 if abs(x - self.begin.pos_x) < gap and abs(y - self.begin.pos_y) < gap:
326 return True
327 elif abs(x - self.end.pos_x) < gap and abs(y - self.end.pos_y) < gap:
328 return True
329 else:
330 return False
331
332 - def domain_intersection(self, line, axis='x'):
333 """Returns x1,x2 where both line and self are defined.
334 Returns None, None if this domain is empty.
335 This routine contains self consistency check
336
337 At current status this is use for test/debugging only."""
338
339 assert isinstance(line, FeynmanLine), ' domain intersection are between ' + \
340 'Feynman_line object only and not {0} object'.format(type(line))
341
342
343 self.check_position_exist()
344 line.check_position_exist()
345
346
347 return self._domain_intersection(line, axis)
348
349 - def _domain_intersection(self, line, axis='x'):
350 """Returns x1,x2 where both line and self are defined.
351 Returns None, None if this domain is empty.
352 This routine doesn't contain self consistency check.
353
354 At current status this is use for debugging only."""
355
356
357 min_self, max_self = self.border_on_axis(axis)
358 min_line, max_line = line.border_on_axis(axis)
359
360
361 start = max(min_self, min_line)
362 end = min(max_self, max_line)
363 if start <= end:
364 return start, end
365 else:
366 return None, None
367
369 """ Returns the two value of the domain interval for the given axis.
370
371 At current status this is use for test/debugging only."""
372
373 data = [getattr(self.begin, 'pos_' + axis), \
374 getattr(self.end, 'pos_' + axis)]
375 data.sort()
376 return data
377
379 """Checks if line intersect self. Line SHOULD be a vertical line and
380 self COULDN'T. No test are done to check those conditions.
381
382 At current status this is use for test/debugging only."""
383
384
385 y_self = self._has_ordinate(line.begin.pos_x)
386
387
388
389 ymin, ymax = line.border_on_axis('y')
390
391
392 if (ymin == y_self or ymax == y_self):
393 if self.is_end_point(line.begin.pos_x, y_self):
394 return False
395 else:
396 return True
397 elif (y_self > ymin) and (y_self < ymax):
398 return True
399 else:
400 return False
401
403 """Check that the begin-end position are defined.
404
405 At current status this is use for debugging only."""
406
407 try:
408 self.begin.pos_x
409 self.end.pos_y
410 except Exception:
411 raise self.FeynmanLineError, 'No vertex in begin-end position ' + \
412 ' or no position attach at one of those vertex '
413 return True
414
416 """Returns the y associate to the x value in the line
417 Raises FeynmanLineError if point outside interval or result not unique.
418 This routines contains check consistency.
419
420 At current status this is use for debugging only."""
421
422 if __debug__:
423 self.check_position_exist()
424 min = self.begin.pos_x
425 max = self.end.pos_x
426 if max < min:
427 min, max = max, min
428
429 if min == max:
430 raise self.FeynmanLineError, 'Vertical line: no unique solution'
431 if(not(min <= x <= max)):
432 raise self.FeynmanLineError, 'point outside interval invalid ' + \
433 'invalid order {0:3}<={1:3}<={2:3}'.format(min, x, max)
434
435 return self._has_ordinate(x)
436
438 """Returns the y associate to the x value in the line
439 This routines doesn't contain check consistency.
440
441 At current status this is use for debugging only."""
442
443
444 x_0 = self.begin.pos_x
445 y_0 = self.begin.pos_y
446 x_1 = self.end.pos_x
447 y_1 = self.end.pos_y
448
449 alpha = (y_1 - y_0) / (x_1 - x_0)
450
451
452 ordinate_fct = lambda X: y_0 + alpha * (X - x_0)
453 return ordinate_fct(x)
454
455
456
457
458
459
461 """Extension of the class Vertex in order to store the information
462 linked to the display of a FeynmanDiagram, as position
463 """
464
466 """Exception raised if an error occurs in the definition
467 or the execution of a VertexPoint."""
468
469
471 """Update a vertex to a VertexPoint with additional information about
472 positioning and link with other vertex/line of the diagram."""
473
474
475 assert(isinstance(vertex, base_objects.Vertex))
476
477
478 for key, value in vertex.items():
479 setattr(self, key, value)
480 self.lines = []
481 self.level = None
482 self.pos_x = 0
483 self.pos_y = 0
484
486 """-Re-Define the position of the vertex in a square [0, 1]^2"""
487
488
489 assert 0 <= x <= 1 and 0 <= y <= 1 , 'vertex coordinate should be' + \
490 ' in 0,1 interval introduce value ({0},{1})'.format(x, y)
491
492 self.pos_x = x
493 self.pos_y = y
494 return
495
497 """Import the line of the second vertex in the first one
498 this means
499 A) change the 'line' of this vertex
500 B) change the start-end position of line to point on this vertex
501 C) remove common_line (if defined)."""
502
503 for line in vertex.lines:
504
505 if line is common_line:
506 self.lines.remove(line)
507 continue
508
509
510
511 if line.begin is vertex:
512 line.def_begin_point(self)
513 else:
514 line.def_end_point(self)
515 return
516
517
519 """Add the line in the list keeping line connected to this vertex :
520 self.lines. This routine avoid duplication of entry."""
521
522 assert isinstance(line, FeynmanLine), \
523 'Trying to add in a Vertex a non FeynmanLine Object'
524
525 for oldline in self.lines:
526 if oldline is line:
527 return
528
529 self.lines.append(line)
530
532 """Remove the line from the lineList. Didn't touch to vertex associate
533 to begin/end point. This happens only if we fuse two vertex together.
534 (Then the line will be completely drop out, such that we dont't care
535 about those vertex point."""
536
537 assert isinstance(line_to_del, FeynmanLine), \
538 'trying to remove in a Vertex_Point a non FeynmanLine Object'
539
540
541
542 for i, line in enumerate(self.lines):
543 if line is line_to_del:
544 del self.lines[i]
545 return
546
547 raise self.VertexPointError, 'trying to remove in a ' + \
548 'Vertex_Point a non present Feynman_Line'
549
550
552 """Define the Vertex level at 'level'. The level represents the
553 distance between the initial vertex and the current vertex. This
554 distance is define has the number of non T-channel particles needed to
555 connect this particle to initial states starting point."""
556
557 assert isinstance(level, int), 'Trying to attribute non integer level'
558
559 self.level = level
560
562 """Check if this vertex is external , i.e is related to a single
563 (external) particles."""
564
565
566 if len(self.lines) == 1:
567 return True
568 else:
569 return False
570
572 """Check if the line associate to the two vertex are equivalent.
573 This means that they have the same number of particles with the same pid
574 and that the external particles have the same number.
575
576 This is a backup function, this is not use for the moment."""
577
578
579 if len(self.lines) != len(other.lines):
580 return False
581
582
583 other_line_pid = [line.id for line in other.lines]
584
585 other_line_number = [line.number for line in other.lines if \
586 line.is_external()]
587
588
589
590
591 for s_line in self.lines:
592 try:
593 other_line_pid.remove(s_line.id)
594 except ValueError:
595 return False
596 if s_line.is_external():
597 try:
598 other_line_number.remove(s_line.number)
599 except ValueError:
600 return False
601
602
603 return True
604
606 """Provide a unique id for the vertex"""
607
608 tag = 0
609 for i, line in enumerate(self.lines):
610 tag += line.number / 10 ** (-i)
611 tag = tag * 10 ** (len(self.lines) + 1)
612 return tag
613
615 """Define equality with pointeur equality."""
616
617 return self is other
618
619
620
621
623 """Object to compute the position of the different Vertex and Line associate
624 to a diagram object.
625
626 This is the standard way to doing it [main]
627 1) Creates the new structure needed for the diagram generation [load_diagram]
628 This defines self.vertexList and self.lineList which are the list of
629 respectively all the vertex and all the line include in the diagram.
630 Each line is associated to two vertex, so we have added new vertex
631 compare to the diagram object (base_objects.Diagram). The two vertex are
632 named begin/end and represent the line direction. at this stage all line
633 are going timelike. T-channel are going from particle 1 to particle 2
634 2) Associate to each vertex a level. [define_level]
635 The level represents the distance between the initial vertex and the
636 current vertex. This distance is define has the number of non T-channel
637 particles needed to connect this particles to a initial state starting
638 point.
639 3) Compute the position of each vertex [find_initial_vertex_position]
640 The x-coordinate will proportional to the level. The vertex at level=0.
641 will have x=0 coordinate (vertex associate with initial state particle)
642 The vertex with the highest level value should be at x=1.
643
644 If an external particles cann't be place at the border at the current
645 level. we will try to place it one level later, potentially up to last
646 level. A option can force to place all external particles at x=1.
647
648 the y-coordinate are chosen such that
649 - external particles try to have (y=0 or y=1) coordinates
650 (if not move those vertex to next level)
651 - other particles maximizes distances between themselves.
652 4) Solve Fermion-flow and (anti)particle type [self.solve_line_direction]
653 the way to solve the fermion-flow is basic and fail in general for
654 majorana fermion. The basic idea is "particles are going timelike".
655 This is sufficient in all cases but T-channel particles which are solve
656 separately."""
657
659 """Class for internal error."""
660
661 - def __init__(self, diagram, model, amplitude=False, opt=None):
662 """Store the information concerning this diagram. This routines didn't
663 perform any action at all.
664 diagram: The diagram object to draw
665 model: The model associate to the diagram
666 amplitude: tell if the diagram has already fixed the I/O state of the fermion
667 opt: A DrawingOpt instance with all options for drawing the diagram."""
668
669
670 assert isinstance(diagram, base_objects.Diagram), \
671 'first argument should derivate from Diagram object'
672 assert isinstance(model, base_objects.Model), \
673 'second argument should derivate from Model object, get %s' % type(model)
674
675
676 self.diagram = diagram
677 self.model = model
678 self.amplitude = amplitude
679
680 if opt is None:
681 self.opt = DrawOption()
682 else:
683 assert isinstance(opt, DrawOption), 'third argument should derivates' + \
684 ' from DrawOption object'
685 self.opt = opt
686
687
688 self.vertexList = []
689 self.initial_vertex = []
690 self.lineList = []
691 self.min_level = 0
692 self.max_level = 1
693
694
695 self._treated_legs = []
696 self._available_legs = {}
697 self._ext_distance_up = self.opt.external
698 self._ext_distance_down = self.opt.external
699
700
702 """This routine will compute all the vertex position and line
703 orientation needed to draw the diagram."""
704
705
706
707 self.load_diagram(contract=self.opt.contract_non_propagating)
708
709 self.define_level()
710
711 self.find_initial_vertex_position()
712
713 self.adjust_position()
714
715 self.solve_line_direction()
716
717
718 fake_vertex = base_objects.Vertex({'id':0, 'legs':base_objects.LegList([])})
719
721 """Define all the object for the Feynman Diagram Drawing (Vertex and
722 Line) following the data include in 'self.diagram'
723 'contract' defines if we contract to one point the non propagating line.
724 """
725
726 for vertex in self.diagram.get('vertices'):
727 self.load_vertex(vertex)
728
729 last_vertex = self.vertexList[-1]
730
731 for line in last_vertex.lines:
732 self.deal_last_line(line)
733
734 if contract:
735
736 self._fuse_non_propa_particule()
737
738
739
740
741
742 for line in self.lineList:
743 if line.end == 0 or line.begin == 0:
744
745 vertex_point = VertexPoint(self.fake_vertex)
746 self.vertexList.append(vertex_point)
747
748 if line.state== False:
749 if line.begin:
750 line.inverse_begin_end()
751 line.def_begin_point(vertex_point)
752 vertex_point.def_level(0)
753 self.initial_vertex.append(vertex_point)
754 else:
755 if line.end:
756 line.inverse_begin_end()
757 line.def_end_point(vertex_point)
758
759 if len(self.initial_vertex) == 2:
760 if self.initial_vertex[0].lines[0].number == 2:
761 self.initial_vertex.reverse()
762 else:
763
764 self.remove_t_channel()
765
766 return
767
769 """Find the position of leg in self._treated_legs
770
771 if equal=0 returns the last position of number in the list
772 otherwise check that leg is the item in self._treated_legs
773
774 the two methods provides the same result if they provide a result.
775 But some times equal=0 mode provides result when equal=1 doesn't.
776 To my understanding equal=1 is suppose to be sufficient in all cases
777 but gg> 7x( g ) fails with using equal=1 only.
778
779 'end' removes the last 'end' element of the list, before looking at
780 the id in the list. (the list is not modify)"""
781
782 if equal:
783 return self.find_leg_id2(leg, end=end)
784
785 for i in range(len(self.lineList) - 1 - end, -1, -1):
786 if leg.get('number') == self.lineList[i].number:
787 return i
788
789 return None
790
792 """Find the position of leg in self._treated_legs. Use object equality
793 to find the position."""
794
795 for i in range(len(self.lineList) - 1 - end, -1, -1):
796 if (self._treated_legs[i] is leg):
797 return i
798
800 """Find the position of leg in self._treated_legs but only if this b
801 belongs to an available particles"""
802
803 try:
804 return self._available_legs[gen_id]
805 except Exception:
806 return None
807
809 """1) Extend the vertex to a VertexPoint.
810 2) Add this vertex in vertexList of the diagram
811 3) Update vertex.lines list. (first update the leg into line if needed)
812
813 4) assign line.start[end] to this vertex. (in end if start is already
814 assigned to another vertex). the start-end will be flip later
815 if needed.
816 5) if the fermion flow is correctly set by the diagram (amplitude=True)
817 Then change the particles/anti-particles states accordingly.
818 """
819
820
821 vertex_point = VertexPoint(vertex)
822
823
824 self.vertexList.append(vertex_point)
825
826
827 for i, leg in enumerate(vertex.get('legs')):
828 gen_id = leg.get('number')
829
830
831
832 mg_id = self.find_leg_id3(gen_id)
833
834
835 if mg_id:
836 del self._available_legs[gen_id]
837 line = self.lineList[mg_id]
838 else:
839 line = self.load_leg(leg)
840 if i + 1 == len(vertex.get('legs')):
841 self._available_legs[gen_id] = len(self.lineList) - 1
842
843
844 line.add_vertex(vertex_point)
845
846
847
848
849
850
851 if line.number == 1 == vertex.get('legs')[0].get('number'):
852 line.inverse_part_antipart()
853 elif self.amplitude and line.number == 1:
854 nb = [l.get('number') for l in vertex.get('legs')]
855 if nb.count(1) == 2:
856 line.inverse_part_antipart()
857
859 """Extend the leg to Feynman line. Associate the line to the diagram.
860 """
861
862
863 line = FeynmanLine(leg)
864 line.def_model(self.model)
865
866
867
868 self._treated_legs.append(leg)
869 self.lineList.append(line)
870
871 return line
872
874 """The line of the last vertex breaks the rules that line before
875 '>' exist previously and the one after don't. The last one can also
876 already exist and for the one before the '>' sometimes they arrive
877 with a second object which is equivalent to another one but not
878 the same object. discover those case and treat this properly."""
879
880
881 if last_line.end == 0 or last_line.begin == 0:
882
883 id1 = self.find_leg_id(self._treated_legs[-1])
884
885 id2 = self.find_leg_id(self._treated_legs[-1], end=len(self._treated_legs) - id1)
886 if id2 is not None:
887
888 line = self.lineList[id2]
889
890
891
892
893
894 if last_line.begin == 0:
895 if line.end == 0 :
896 line.def_end_point(last_line.end)
897 else:
898 line.def_begin_point(last_line.end)
899
900 last_line.end.remove_line(last_line)
901 else:
902 if line.end == 0 :
903 line.def_end_point(last_line.begin)
904 else:
905 line.def_begin_point(last_line.begin)
906
907 last_line.begin.remove_line(last_line)
908
909
910 self.lineList.remove(last_line)
911 else:
912 return
913
915 """Fuse all the non propagating line
916 step:
917 1) find those line
918 2) fuse the vertex
919 3) remove one vertex from self.vertexList
920 4) remove the line/leg from self.lineList/self._treated_leg
921 """
922
923
924
925 for i in range(len(self.lineList)).__reversed__():
926 if self.lineList[i].get_info('propagating'):
927 continue
928 else:
929 line = self.lineList[i]
930 line.begin.fuse_vertex(line.end, common_line=line)
931 self.vertexList.remove(line.end)
932 del self._treated_legs[i]
933 del self.lineList[i]
934
936 """Assign to each vertex a level:
937 the level correspond to the number of visible particles and S-channel
938 needed in order to reach the initial particles vertex.
939
940 This is computing by search level by level starting at level 0.
941 """
942
943 for vertex in self.initial_vertex:
944 self.def_next_level_from(vertex)
945
946 self.nb_level = self.max_level - self.min_level
947
949 """Define level for adjacent vertex.
950 If those vertex is already defined do nothing
951 Otherwise define as level+1 (at level 1 if T-channel)
952
953 This routine defines also self.max_level.
954
955 This routine is foreseen for an auto-recursive mode. So as soon as a
956 vertex have his level defined. We launch this routine for this vertex.
957 """
958
959 level = vertex.level
960 for line in vertex.lines:
961 if line.end.level is not None:
962 continue
963
964
965 if line.state == False:
966
967 line.end.def_level(1)
968 else:
969
970 line.end.def_level(level + 1)
971
972 self.max_level = max(self.max_level, level + 1)
973
974 self.def_next_level_from(line.end)
975
976
978 """Returns the vertex (T-vertex authorize) associate to level 1.
979 We start with the vertex associate to first entry of previous_level
980 and then following the T-line."""
981
982 vertex_at_level = []
983 try:
984 t_vertex = self.initial_vertex[-2]
985 except Exception:
986 return []
987
988 while 1:
989
990 t_vertex = self.find_next_t_channel_vertex(t_vertex)
991
992
993 if t_vertex:
994 vertex_at_level.append(t_vertex)
995 else:
996 return vertex_at_level
997
999 """Returns the next t_vertex. i.e. the vertex following t_vertex. t_line
1000 indicates the 'wrong' T-direction. This routines returns also the 'good'
1001 evolution direction (which will be the wrong one at the next step)."""
1002
1003 for line in t_vertex.lines:
1004 if line.state == False and line.begin is t_vertex:
1005 return line.end
1006
1008 """Returns a list of vertex such that all those vertex are one level
1009 after the level of vertexlist and sorted in such way that the list
1010 start with vertex connected with the first vertex of 'vertexlist' then
1011 those connected to the second and so on."""
1012
1013 vertex_at_level = []
1014 for vertex in previous_level:
1015 if vertex.is_external() and vertex.pos_y not in [0, 1]:
1016
1017
1018 vertex.def_level(vertex.level + 1)
1019 vertex_at_level.append(vertex)
1020 continue
1021
1022 for line in vertex.lines:
1023 if line.begin is vertex and line.end.level == level:
1024 vertex_at_level.append(line.end)
1025
1026 return vertex_at_level
1027
1059
1060
1062 """Finds the vertex position for level one, T channel are authorize"""
1063
1064
1065 t_vertex = self.find_t_channel_vertex()
1066
1067 self.assign_pos(t_vertex, 1)
1068 return t_vertex
1069
1070
1072 """Finds the vertex position for the particle at 'level' given the
1073 ordering at previous level given by the vertexlist.
1074 if direction != 0 pass in auto-recursive mode."""
1075
1076 if level > self.max_level or level < self.min_level:
1077 return
1078
1079
1080
1081
1082
1083 vertex_at_level = self.find_vertex_at_level(vertexlist, level)
1084
1085 if not vertex_at_level:
1086 return
1087
1088
1089
1090 self.assign_pos(vertex_at_level, level)
1091
1092
1093 if direction and vertex_at_level:
1094 self.find_vertex_position_at_level(vertex_at_level,
1095 level + direction, direction)
1096
1097
1098 - def assign_pos(self, vertex_at_level, level, min=0, max=1):
1099 """Assign the position to each vertex of vertex_at_level.
1100
1101 The x-coordinate will the ratio of the current level with the maximum
1102 level of the diagram.
1103
1104 If the first_vertex of vertex_at_level is an outgoing particle. Put it
1105 at y=0 if possible (this could be prevented by min>0 or by drawing
1106 option). if you put it at y=0 delete the vertex of the list to avoid
1107 duplications.
1108
1109 Do the symmetric case for the last entry of vertex_at_level.
1110
1111 The y-value for the other point is computed such that the distance
1112 between two vertex of the list are the same. the distance between min
1113 (resp. max) and the first vertex is also equal but if min=0 (resp.
1114 max=1) then this distance counts half.
1115
1116 the option self.opt.external is used
1117 if equals 0, the external lines are authorizes to end only
1118 at the end of the diagram (in x=1 axis) so this will forbid
1119 to put any vertex at y=0-1 (except if x=1)
1120 if bigger than 0, minimal distance in before putting a external line
1121 on the border of the diagram.
1122
1123
1124 The computation of y is done in this way
1125 first compute the distance [dist] between two vertex and assign the point.
1126 begin_gap and end_gap are the ratio of the compute distance to put
1127 between min and first vertex.
1128 """
1129
1130 if not vertex_at_level:
1131 return []
1132
1133 assert self.min_level <= level <= self.max_level , \
1134 'Incorrect value of min/max level: %s <= %s <= %s' % \
1135 (self.min_level, level, self.max_level)
1136
1137
1138
1139 if level == self.max_level:
1140 ext_dist_up = 1
1141 ext_dist_down = 1
1142
1143 if len(vertex_at_level) == 1 and min == 0 and max == 1:
1144 vertex_at_level[0].def_position(1, 0.5)
1145 return []
1146 else:
1147
1148 ext_dist_up = self._ext_distance_up
1149 ext_dist_down = self._ext_distance_down
1150
1151 begin_gap, end_gap = 1, 1
1152
1153 if min == 0:
1154 if ext_dist_down and vertex_at_level[0].is_external():
1155 line = vertex_at_level[0].lines[0]
1156 if line.end.level - line.begin.level >= ext_dist_down:
1157
1158 self.define_vertex_at_border(vertex_at_level[0], level, 0)
1159
1160 del vertex_at_level[0]
1161
1162 if not vertex_at_level:
1163 return []
1164 else:
1165 begin_gap = 0.5
1166 else:
1167 begin_gap = 0.5
1168
1169
1170 if max == 1:
1171 if ext_dist_up and vertex_at_level[-1].is_external():
1172 line = vertex_at_level[-1].lines[0]
1173 if line.end.level - line.begin.level >= ext_dist_up:
1174
1175 self.define_vertex_at_border(vertex_at_level[-1], level, 1)
1176
1177 del vertex_at_level[-1]
1178 if not vertex_at_level:
1179 return []
1180 else:
1181 end_gap = 0.5
1182 else:
1183 end_gap = 0.5
1184
1185
1186 dist = (max - min) / (begin_gap + end_gap + len(vertex_at_level) - 1)
1187
1188
1189 for i, vertex in enumerate(vertex_at_level):
1190 vertex.def_position((level - self.min_level) / self.nb_level,
1191 min + dist * (begin_gap + i))
1192
1193 return vertex_at_level
1194
1196 """Define the position of the vertex considering the distance required
1197 in the Drawing Options. Update the option if needed."""
1198
1199
1200 if pos_y == 1:
1201 dist = self._ext_distance_up
1202 self._ext_distance_up += self.opt.add_gap
1203 else:
1204 dist = self._ext_distance_down
1205 self._ext_distance_down += self.opt.add_gap
1206
1207
1208 if dist % 1:
1209
1210 if level < self.max_level:
1211 pos_x = (level - 1 + (dist % 1)) / self.max_level
1212 elif (1 - vertex.lines[0].begin.pos_x) * self.max_level > dist:
1213 pos_x = (level - 1 + (dist % 1)) / self.max_level
1214 else:
1215 pos_x = 1
1216 else:
1217 pos_x = level / self.max_level
1218
1219 vertex.def_position(pos_x, pos_y)
1220
1221
1223 """Removes all T-channel in a diagram and convert those in S-channel.
1224 This occur for 1>X diagram where T-channel are wrongly define."""
1225
1226 for line in self.lineList:
1227 if line.state == False:
1228 line.state = True
1229
1230
1232 """Computes the directions of the lines of the diagrams.
1233 first use simple rules as particles move in time directions (to right).
1234 - define_line_orientation -. Then flip T-channel particles to
1235 correct fermion flow in T-channel. Majorana case not deal correctly
1236 at this stage."""
1237
1238
1239
1240
1241
1242 for line in self.lineList:
1243 if line.state == True:
1244 line.define_line_orientation()
1245
1246
1247
1248
1249
1250
1251 try:
1252 t_vertex = self.initial_vertex[-2]
1253 except Exception:
1254 return
1255
1256 t_vertex = self.find_next_t_channel_vertex(t_vertex)
1257 self.initial_vertex[0].lines[0].define_line_orientation()
1258
1259 t_old = self.initial_vertex[0].lines[0]
1260 while 1:
1261
1262 ver_flow = 0
1263 t_next = None
1264 for line in t_vertex.lines:
1265
1266
1267 if line.state == False and t_old is not line and \
1268 line.begin is t_vertex:
1269 t_next = line
1270
1271
1272
1273
1274
1275 if not line.is_fermion():
1276 continue
1277
1278
1279 if (line.begin is t_vertex):
1280 ver_flow += 1
1281 elif line.end is t_vertex:
1282 ver_flow -= 1
1283
1284
1285 if t_next:
1286 t_old = t_next
1287 t_vertex = t_next.end
1288
1289 if ver_flow:
1290 t_next.inverse_begin_end()
1291 else:
1292 if ver_flow:
1293 self.initial_vertex[1].lines[0].inverse_begin_end()
1294 return
1295
1296
1298 """Modify the position of some particles in order to improve the final
1299 diagram look. This routines use one option
1300 1) max_size which forbids external particles to be longer than max_size.
1301 This is in level unit. If a line is too long we contract it to
1302 max_size preserving the orientation.
1303 2) external indicating the minimal x-gap for an external line. This
1304 constraints is already take into account in previous stage. But that
1305 stage cann't do non integer gap. So this routines correct this."""
1306
1307 finalsize = self.opt.max_size
1308
1309
1310 if not finalsize:
1311 return
1312
1313
1314 for line in self.lineList:
1315 if line.is_external():
1316
1317
1318 if line.state == False or not line.is_external():
1319 continue
1320 size = line.get_length() * self.max_level
1321 if size > finalsize:
1322 ratio = finalsize / size
1323 new_x = line.begin.pos_x + ratio * (line.end.pos_x -
1324 line.begin.pos_x)
1325 new_y = line.begin.pos_y + ratio * (line.end.pos_y -
1326 line.begin.pos_y)
1327 line.end.def_position(new_x, new_y)
1328
1330 """Return a string to check to conversion of format for the diagram.
1331
1332 This is a debug function."""
1333
1334 text = 'line content :\n'
1335 for i in range(0, len(self.lineList)):
1336 line = self.lineList[i]
1337 try:
1338 begin = self.vertexList.index(line.begin)
1339 except Exception:
1340 begin = -1
1341 try:
1342 end = self.vertexList.index(line.end)
1343 except Exception:
1344 end = -1
1345 try:
1346 external = line.is_external()
1347 except Exception:
1348 external = '?'
1349 text += 'pos, %s ,id: %s, number: %s, external: %s, S-channel: %s, loop : %s \
1350 begin at %s, end at %s \n' % (i, line.id, \
1351 line.number, external, line.state, line.loop_line, begin, end)
1352 text += 'vertex content : \n'
1353 for i in range(0, len(self.vertexList)):
1354 vertex = self.vertexList[i]
1355 text += 'pos, %s, id: %s, external: %s, uid: %s ' % \
1356 (i, vertex.id, vertex.is_external(), \
1357 vertex.get_uid())
1358 text += 'line: ' + ','.join([str(self.lineList.index(line)) \
1359 for line in vertex.lines]) + '\n'
1360 text += '%s' % [(l.number,) for l in self.lineList if l.state==False]
1361 return text
1362
1363
1365 """Returns a string to check the level of each vertex.
1366
1367 This is a debug function."""
1368
1369 for line in self.lineList:
1370 if line.begin.level > line.end.level:
1371 if text == 0:
1372 raise self.FeynamDiagramError('invalid level order')
1373
1374
1375 text = ''
1376 for vertex in self.vertexList:
1377 text += 'vertex : ' + str(int(vertex.get_uid()))
1378 text += 'line : '
1379 text += ','.join([str(line['id']) for line in vertex.legs])
1380 text += ' level : ' + str(vertex.level)
1381 text += '\n'
1382 if text:
1383 return text
1384
1386 """Returns a string to check the position of each vertex.
1387
1388 This is a debug function."""
1389
1390 text = ''
1391 for vertex in self.vertexList:
1392 text += 'line : '
1393 text += ','.join([str(line.id) for line in vertex.lines])
1394 text += ' level : ' + str(vertex.level)
1395 text += ' pos : ' + str((vertex.pos_x, vertex.pos_y))
1396 text += '\n'
1397 return text
1398
1400 """Returns if some line cross are crossing each other.
1401
1402 This is a debug Function and is used for the test routine."""
1403
1404
1405 for i, line in enumerate(self.lineList):
1406 for j in range(i + 1, len(self.lineList)):
1407 line2 = self.lineList[j]
1408
1409 if line.begin == line2.end or line.begin == line2.begin:
1410 continue
1411 elif line.has_intersection(line2):
1412 import logging
1413 logger = logging.getLogger('test')
1414 logger.info('intersection for %s %s' % (i, j))
1415 logger.info('line %s (%s,%s),(%s,%s)' % (i, line.begin.pos_x, line.begin.pos_y,line.end.pos_x, line.end.pos_y))
1416 logger.info('line %s (%s,%s),(%s,%s)' % (j, line2.begin.pos_x, line2.begin.pos_y,line2.end.pos_x, line2.end.pos_y))
1417
1418 return True
1419 return False
1420
1422 """Check if two diagrams are equivalent. (same structure-same particle)
1423
1424 This function is not used for the moment. The initial purpose was the
1425 avoid duplication of identical diagram in the output (these could happen
1426 if we contract non propagating line). But the number of such comparaison
1427 rise as the number of diagram square such that the total time needed for
1428 this feature was consider as too (time-)expansive."""
1429
1430 if other==None:
1431 return self.__class__==type(None)
1432
1433
1434 if self.max_level != other.max_level:
1435 return False
1436 elif len(self.lineList) != len(other.lineList):
1437 return False
1438
1439
1440
1441
1442 other_pos = [(vertex.pos_x, vertex.pos_y) for vertex in other.vertexList]
1443 for vertex_self in self.vertexList:
1444 try:
1445 i = other_pos.index((vertex_self.pos_x, vertex_self.pos_y))
1446 except Exception:
1447
1448 return False
1449 else:
1450 vertex_other = other.vertexList[i]
1451
1452
1453
1454
1455 if not vertex_self.has_the_same_line_content(vertex_other):
1456 return False
1457
1458
1459
1460 return True
1461
1462
1463
1464
1465
1467 """Object to compute the position of the different Vertex and Line associate
1468 to a diagram object. This routines is quite similar to FeynmanDiagram.
1469 The only differences concerns the rules for the y-coordinate of each vertex.
1470
1471 In case of vertex with one and only one S-channel going to the next level.
1472 Then force this line to be horizontal. This creates sub-interval where other
1473 vertex can be place following the same rule as before (equal distance
1474 between vertex) but this time sub-interval by sub-interval."""
1475
1477 """Finds the vertex position for the particle at 'level' given the
1478 ordering at previous level given by the vertexlist.
1479 if auto=True pass in autorecursive mode.
1480
1481 Compare to the function of FeynmanDiagram, this check the number of
1482 S-channel particles going out of each vertex. If the result is one:
1483 1) Fix the associate vertex at the same y as the original vertex
1484 -> horizontal line
1485 2) Assign non fix vertex below the fix one in the current interval.
1486 3) Continue to the next vertex."""
1487
1488
1489 if level == 1 or level == self.max_level :
1490 FeynmanDiagram.find_vertex_position_at_level(self, vertexlist, \
1491 level, direction)
1492 return
1493 elif level > self.max_level or level < self.min_level:
1494 return
1495
1496
1497
1498
1499
1500
1501 vertex_at_level = self.find_vertex_at_level(vertexlist, level)
1502 vertex_at_level2 = []
1503
1504
1505
1506 min_pos = 0
1507 list_unforce_vertex = []
1508
1509
1510
1511 for vertex in vertexlist:
1512
1513 s_vertex = []
1514 ext_vertex = []
1515 v_pos = vertex.pos_y
1516
1517
1518
1519 for line in vertex.lines:
1520
1521
1522 if line.end in vertex_at_level:
1523 new_vertex = line.end
1524 elif line.begin in vertex_at_level:
1525 new_vertex = line.begin
1526 else:
1527
1528 continue
1529
1530
1531 if line.is_external():
1532 ext_vertex.append(new_vertex)
1533 else:
1534 s_vertex.append(new_vertex)
1535
1536
1537 if len(s_vertex) != 1:
1538
1539
1540 if len(ext_vertex) <= 1:
1541 if vertex.pos_y >= 0.5:
1542 list_unforce_vertex += (s_vertex + ext_vertex)
1543 else:
1544 list_unforce_vertex += (ext_vertex + s_vertex)
1545 else:
1546 list_unforce_vertex += ext_vertex[:-1] + s_vertex + \
1547 ext_vertex[-1:]
1548 continue
1549
1550
1551 force_vertex = s_vertex[0]
1552 force_vertex.def_position(level / self.max_level, v_pos)
1553
1554 list_unforce_vertex += ext_vertex
1555
1556
1557 if (len(ext_vertex) == 1 and v_pos >= 0.5) or len(ext_vertex) > 1:
1558 vertex_at_level2 += self.assign_pos(list_unforce_vertex[:-1], \
1559 level, min_pos, v_pos)
1560
1561 list_unforce_vertex = [list_unforce_vertex[-1]]
1562 else:
1563 vertex_at_level2 += self.assign_pos(list_unforce_vertex, level, \
1564 min_pos, v_pos)
1565 list_unforce_vertex = []
1566
1567
1568 min_pos = v_pos
1569 vertex_at_level2.append(force_vertex)
1570
1571
1572 if list_unforce_vertex:
1573 vertex_at_level2 += self.assign_pos(list_unforce_vertex, level, \
1574 min_pos, 1)
1575
1576 if direction and vertex_at_level2:
1577 self.find_vertex_position_at_level(vertex_at_level2,
1578 level + direction, direction)
1579
1580
1581
1582
1583
1585 """In principle ALL routines representing diagram in ANY format SHOULD
1586 derive from this class.
1587
1588 This is a (nearly empty) frameworks to draw a diagram in any type format
1589
1590 This frameworks defines in particular
1591 - function to convert the input diagram (create by the generation step)
1592 in the correct object. [convert_diagram]
1593 - main loop to draw a diagram in a line-by-line method
1594 [draw - draw_diagram - draw_line]
1595 - name of routine (routine are empty) in order to fit with the framework
1596 [ associate_name - associate_number - draw_straight ]
1597 - some basic definition of routines
1598 [conclude - initialize]
1599
1600 This framework is base on the idea that we can create the diagram line after
1601 line. Indeed all line object (FeynmanLine) contains the full information
1602 needed to be drawed independently of the rest of the diagram.
1603
1604 In order to create a class with this framework you should start to write the
1605 draw_straight, draw_curly, ... method which are called by the framework.
1606
1607 If you want to write a file, you can store his content in self.text variable
1608 the routine conclude will then automatically write the file.
1609
1610 The main routine to draw a diagram is 'draw' which call
1611 1) initialize: setup things for the diagram (usually open a file).
1612 2) convert_diagram : Update the diagram in the correct format if needed.
1613 3) draw_diagram : Build the diagram line after line.
1614 4) conclude : finish the operation.
1615 """
1616
1618 """Standard error for error occuring to create output of a Diagram."""
1619
1620 - def __init__(self, diagram=None, filename=None, model=None, amplitude=None, \
1621 opt=None):
1622 """Define basic variables and store some global information.
1623 All argument are optional:
1624 diagram : is the object to 'diagram' should inherit from either
1625 base_objects.Diagram or drawing_lib.FeynmanDiagram.
1626 filename: file's name of the file to write.
1627 model: model associate to the diagram. In principle use only if diagram
1628 inherit from base_objects.Diagram (for conversion).
1629 amplitude: amplitude associates to the diagram. NOT USE for the moment.
1630 In future you could pass the amplitude associate to the object in
1631 order to adjust fermion flow in case of Majorana fermion.
1632 opt: should be a valid DrawOption object."""
1633
1634
1635
1636 try:
1637 assert(not model or isinstance(model, base_objects.Model))
1638 assert(not filename or isinstance(filename, basestring))
1639 except AssertionError:
1640 raise self.DrawDiagramError('No valid model provide to convert ' + \
1641 'diagram in appropriate format')
1642
1643 assert opt is None or isinstance(opt, DrawOption) , \
1644 'The Option to draw the diagram are in a invalid format'
1645
1646
1647
1648
1649
1650 self.diagram = diagram
1651 self.filename = filename
1652 self.model = model
1653 self.amplitude = amplitude
1654 self.opt = opt
1655
1656
1657 self.text = ''
1658
1659 if file:
1660 self.file = True
1661
1662 else:
1663 self.file = False
1664
1665
1666 - def draw(self, opt=None):
1667 """Main routine to draw a single diagram.
1668 opt is DrawOption object use for the conversion of the
1669 base_objects.Diagram in one of the Diagram object."""
1670
1671
1672 self.convert_diagram(amplitude=self.amplitude, opt=opt)
1673
1674
1675
1676 self.initialize()
1677
1678 self.draw_diagram(self.diagram)
1679
1680
1681 self.conclude()
1682
1683
1684 - def convert_diagram(self, diagram=None, model=None, amplitude=None, \
1685 opt=None):
1686 """If diagram is a basic diagram (inherit from base_objects.Diagram)
1687 convert him to a FeynmanDiagram one. 'opt' keeps track of possible
1688 option of drawing. 'amplitude' is not use for the moment. But, later,
1689 if defined will authorize to adjust the fermion-flow of Majorana
1690 particles. opt is a DrawOption object containing all option on the way
1691 to draw the diagram (see this class for more details)
1692
1693
1694 This is the list of recognize options:
1695 external [True] : authorizes external particles to finish on
1696 horizontal limit of the square
1697 horizontal [True]: if on true use FeynmanDiagramHorizontal to
1698 convert the diagram. otherwise use FeynmanDiagram (Horizontal
1699 forces S-channel to be horizontal)
1700 non_propagating [True] : removes the non propagating particles
1701 present in the diagram."""
1702
1703 if diagram is None:
1704 diagram = self.diagram
1705
1706
1707 if isinstance(diagram, FeynmanDiagram):
1708 return diagram
1709
1710 if amplitude is None:
1711 amplitude = self.amplitude
1712
1713 try:
1714 loop_structure = amplitude.get('structure_repository')
1715 except Exception:
1716 loop_structure = None
1717
1718
1719 if model is None:
1720 model = self.model
1721 elif not isinstance(model, base_objects.Model):
1722 raise self.DrawDiagramError('No valid model provide to convert ' + \
1723 'diagram in appropriate format')
1724
1725
1726
1727 if opt is None:
1728 if self.opt:
1729 opt = self.opt
1730 else:
1731 opt = DrawOption()
1732 elif not isinstance(opt, DrawOption):
1733 raise self.DrawDiagramError('The Option to draw the diagram are' + \
1734 ' in a invalid format')
1735
1736
1737
1738
1739 if isinstance(diagram, loop_objects.LoopDiagram) and diagram.get('type') > 0:
1740 diagram = LoopFeynmanDiagram(diagram,
1741 loop_structure,
1742 model,
1743 opt=opt)
1744 elif isinstance(diagram, loop_objects.LoopUVCTDiagram) or \
1745 (isinstance(diagram, loop_objects.LoopDiagram) and \
1746 diagram.get('type') < 0):
1747 return None
1748 else:
1749 if opt.horizontal:
1750 diagram = FeynmanDiagramHorizontal(diagram, model, \
1751 amplitude=amplitude, opt=opt)
1752 else:
1753 diagram = FeynmanDiagram(diagram, model, \
1754 amplitude=amplitude, opt=opt)
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765 assert isinstance(diagram, FeynmanDiagram)
1766 diagram.main()
1767
1768
1769 self.diagram = diagram
1770 return diagram
1771
1773 """Initialization of object-file before starting in order to draw the
1774 diagram correctly. By default, we just check if we are in writing mode.
1775 And open the output file if we are."""
1776
1777
1778
1779 if self.file:
1780 self.file = open(self.filename, 'w')
1781
1782
1784 """Building the diagram Line after Line.
1785 This is the key routine of 'draw'."""
1786
1787
1788 if diagram is None:
1789 diagram = self.diagram
1790
1791
1792 [self.draw_vertex(vertex) for vertex in diagram.vertexList]
1793
1794
1795 curved_for_loop = False
1796 circled_for_loop = False
1797
1798 if isinstance(diagram, LoopFeynmanDiagram):
1799
1800
1801 if len([l for l in diagram.lineList if l.loop_line]) == 2:
1802 curved_for_loop = True
1803 self.curved_part_start = (0, 0)
1804
1805
1806 elif len([l for l in diagram.lineList if l.loop_line]) == 1:
1807 circled_for_loop = True
1808 self.curved_part_start = (0, 0)
1809
1810
1811 for line in diagram.lineList:
1812 if (not curved_for_loop and not circled_for_loop) or not line.loop_line:
1813 self.draw_line(line)
1814 elif circled_for_loop:
1815 self.draw_circled_line(line)
1816 else:
1817 self.draw_curved_line(line)
1818
1819
1820
1821 self.put_diagram_number(number)
1822
1823
1824
1825 if self.file:
1826 self.file.writelines(self.text)
1827 self.text = ""
1828
1830 """Final operation of the draw method. By default, this end to write the
1831
1832 file (if this one exist)
1833 """
1834
1835
1836
1837 if self.file:
1838 self.file.writelines(self.text)
1839 self.file.close()
1840 return
1841
1843 """Draw the line information.
1844 First, call the method associate the line type [draw_XXXXXX]
1845 Then finalize line representation by adding his name and, if it's an
1846 external particle, the MadGraph5_aMC@NLO number associate to it."""
1847
1848
1849 line_type = line.get_info('line')
1850
1851 if hasattr(self, 'draw_' + line_type):
1852 getattr(self, 'draw_' + line_type)(line)
1853 else:
1854 self.draw_straight(line)
1855
1856
1857 name = line.get_name()
1858 self.associate_name(line, name)
1859
1860 if line.is_external():
1861 number = line.number
1862 self.associate_number(line, number)
1863
1864
1866 """Draw the line information.
1867 First, call the method associate the line type [draw_circled_XXXXXX]
1868 Then finalize line representation by adding his name."""
1869
1870
1871
1872 if len(line.begin.lines) > 3 or len(line.end.lines) > 3 :
1873 cercle = False
1874 else:
1875 cercle = True
1876
1877
1878 line_type = line.get_info('line')
1879
1880 if hasattr(self, 'draw_circled_' + line_type):
1881 getattr(self, 'draw_circled_' + line_type)(line, cercle)
1882 else:
1883 self.draw_circled_straight(line, reduce)
1884
1885
1886 name = line.get_name()
1887 self.associate_name(line, name)
1888
1889
1890 self.curved_part_start = (line.begin.pos_x, line.begin.pos_y*1.2)
1891
1893 """Draw the line information.
1894 First, call the method associate the line type [draw_curved_XXXXXX]
1895 Then finalize line representation by adding his name."""
1896
1897
1898
1899
1900 if len(line.begin.lines) > 3 or len(line.end.lines) > 3 :
1901 cercle = False
1902 else:
1903 cercle = True
1904
1905
1906 line_type = line.get_info('line')
1907
1908 if hasattr(self, 'draw_curved_' + line_type):
1909 getattr(self, 'draw_curved_' + line_type)(line, cercle)
1910 else:
1911 self.draw_curved_straight(line, reduce)
1912
1913
1914 name = line.get_name()
1915 if self.curved_part_start == (line.begin.pos_x, line.begin.pos_y):
1916 self.associate_name(line, name, loop=True, reverse=True)
1917 self.curved_part_start = (line.end.pos_x, line.end.pos_y)
1918 else:
1919 self.associate_name(line, name, loop=True)
1920
1921 self.curved_part_start = (line.begin.pos_x, line.begin.pos_y)
1922
1924 """default vertex style"""
1925 pass
1926
1927
1929 """Example of routine for drawing the line 'line' in a specific format.
1930 straight is an example and can be replace by other type of line as
1931 dashed, wavy, curly, ..."""
1932
1933 raise self.DrawDiagramError, 'DrawDiagram.draw_straight should be ' + \
1934 'overwritten by Inherited Class'
1935
1936 draw_curved_straight = draw_straight
1937
1939 """Method to associate a name to a the given line.
1940 The default action of this framework doesn't do anything"""
1941 pass
1942
1943
1945 """Method to associate a number to 'line'. By default this method is
1946 call only for external particles and the number is the MadGraph5_aMC@NLO number
1947 associate to the particle. The default routine doesn't do anything"""
1948 pass
1949
1951 """Dealing with the different option of the drawing method.
1952 This is the list of recognize attributes:
1953 horizontal [False]: force S-channel to be horizontal
1954 external [0]: authorizes external particles to end
1955 at top or bottom of diagram. If bigger than zero
1956 this tune the length of those line.
1957 add_gap [0]: make external rising after each positioning.
1958 max_size [0]: this forbids external line bigger than
1959 max_size.
1960 non_propagating [True]:contracts non propagating lines"""
1961
1963 """Error raising if an invalid entry is set in a option."""
1964
1966 """Fullfill option with standard value."""
1967
1968
1969 self.external = 0
1970 self.add_gap = 0
1971 self.horizontal = False
1972 self.max_size = 1.5
1973 self.contract_non_propagating = True
1974
1975 if isinstance(opt, dict):
1976 for key, value in opt.items():
1977 self.set(key, value)
1978 else:
1979 for value in ['external','add_gap','horizontal','max_size',
1980 'contract_non_propagating']:
1981 if hasattr(opt, value):
1982 self.set(value, getattr(opt, value))
1983
1984 - def set(self, key, value):
1985 """Check and attribute the given value."""
1986
1987 if key in ['horizontal', 'contract_non_propagating']:
1988 value = self.pass_to_logical(value)
1989 setattr(self, key, value)
1990 elif(key in ['external', 'max_size', 'add_gap']):
1991 try:
1992 value = self.pass_to_number(value)
1993 except Exception:
1994 raise self.DrawingOptionError('%s is not a numerical when %s \
1995 requires one' % (value, key))
1996 setattr(self, key, value)
1997
1998 else:
1999 raise self.DrawingOptionError('%s is not a valid property for \
2000 drawing object' % key)
2001
2003 """convert the value in a logical"""
2004
2005 if value in [0, False, '0', 'False', 'false']:
2006 return False
2007 else:
2008 return True
2009
2011 """Convert the value in a number"""
2012
2013 return float(value)
2014
2015
2016
2017
2019 """Object to compute the position of the different Vertex and Line associate
2020 to a diagram object with a presence of a Loop.
2021
2022 This is the standard way to doing it [main]
2023 1) Creates the new structure needed for the diagram generation [load_diagram]
2024 This defines self.vertexList and self.lineList which are the list of
2025 respectively all the vertex and all the line include in the diagram.
2026 Each line is associated to two vertex, so we have added new vertex
2027 compare to the diagram object (base_objects.Diagram). The two vertex are
2028 named begin/end and represent the line direction. at this stage all line
2029 are going timelike. T-channel are going from particle 1 to particle 2
2030 2) Associate to each vertex a level. [define_level]
2031 This level is define has the number of non T-channel
2032 particles needed to connect this particles to a initial state starting
2033 point.
2034 The Loop is dispatched on only two channel. If some T-channel
2035 started between the initial particles those are going in negative
2036 directions (i.e. to negative level)
2037
2038 3) Compute the position of each vertex [find_initial_vertex_position]
2039 The x-coordinate will proportional to the level. The most negative vertex
2040 will have x=0 coordinate (vertex associate with initial state particle)
2041 The vertex with the highest level value should be at x=1.
2042
2043 If an external particles cann't be place at the border at the current
2044 level. we will try to place it one level later, potentially up to last
2045 level. A option can force to place all external particles at x=1.
2046
2047 the y-coordinate are chosen such that
2048 - external particles try to have (y=0 or y=1) coordinates
2049 (if not move those vertex to next level)
2050 - other particles maximizes distances between themselves.
2051 4) Solve Fermion-flow and (anti)particle type [self.solve_line_direction]
2052 the way to solve the fermion-flow is basic and fail in general for
2053 majorana fermion. The basic idea is "particles are going timelike".
2054 This is sufficient in all cases but T-channel particles and Loop particles
2055 which are solve separately."""
2056
2057 - def __init__(self, diagram, fdstructures, model, opt=None):
2058 """Store the information concerning this diagram. This routines didn't
2059 perform any action at all.
2060 diagram: The diagram object to draw
2061 model: The model associate to the diagram
2062 opt: A DrawingOpt instance with all options for drawing the diagram.
2063 fdstructures: list of structure that might be connected to the loop.
2064 """
2065
2066
2067 super(LoopFeynmanDiagram, self).__init__(diagram, model, opt)
2068 self.fdstructures = fdstructures
2069
2070
2072 """Define all the object for the Feynman Diagram Drawing (Vertex and
2073 Line) following the data include in 'self.diagram'
2074 'contract' defines if we contract to one point the non propagating line.
2075 Compare to usual load we glue the cutted propagator of the Loop.
2076 """
2077
2078 if self.diagram['tag'] and not self.fdstructures is None:
2079 for pdg, list_struct_id, vertex_id in self.diagram['tag']:
2080 for structure_id in list_struct_id:
2081 for vertex in self.fdstructures[structure_id]['vertices']:
2082 self.load_vertex(vertex)
2083 super(LoopFeynmanDiagram, self).load_diagram(contract)
2084 else:
2085 super(LoopFeynmanDiagram, self).load_diagram(contract)
2086
2087
2088 loop_line = [line for line in self.lineList if line.loop_line]
2089
2090
2091
2092 fake_line = loop_line[-1]
2093 self.fuse_line(loop_line[0], loop_line[-2])
2094
2095 self.vertexList.remove(fake_line.end)
2096 self.lineList.remove(fake_line)
2097
2099 """Returns a list of vertex such that all those vertex are one level
2100 after the level of vertexlist and sorted in such way that the list
2101 start with vertex connected with the first vertex of 'vertexlist' then
2102 those connected to the second and so on."""
2103 started_loop = False
2104
2105 vertex_at_level = []
2106 for vertex in previous_level:
2107 if vertex.is_external() and vertex.pos_y not in [0, 1]:
2108
2109
2110 vertex.def_level(vertex.level + 1)
2111 vertex_at_level.append(vertex)
2112 continue
2113
2114 tmp = []
2115 loop_tmp = []
2116 for line in vertex.lines:
2117 if line.begin is vertex and line.end.level == level:
2118 if not line.loop_line:
2119 tmp.append(line.end)
2120 elif started_loop:
2121 continue
2122 else:
2123 started_loop = True
2124 loop_tmp = self.find_all_loop_vertex(line.end)
2125 elif line.end is vertex and line.begin.level == level:
2126 if not line.loop_line:
2127 tmp.append(line.begin)
2128 elif started_loop:
2129 continue
2130 else:
2131 started_loop = True
2132 loop_tmp = self.find_all_loop_vertex(line.begin)
2133
2134 if loop_tmp:
2135 vertex_at_level += tmp
2136 vertex_at_level += loop_tmp
2137 else:
2138 vertex_at_level += tmp
2139
2140 return vertex_at_level
2141
2142
2143
2154
2156 """ Returns all the vertex associate at a given level. returns in a
2157 logical ordinate way starting at init_loop """
2158
2159 solution = []
2160 while init_loop:
2161 solution.append(init_loop)
2162 init_loop = self.find_next_loop_channel_vertex(init_loop, solution)
2163 return solution
2164
2166 """Returns the next loop_vertex. i.e. the vertex following loop_vertex.
2167 """
2168
2169 level = loop_vertex.level
2170 for line in loop_vertex.lines:
2171 if line.loop_line == False:
2172 continue
2173
2174 if line.end is loop_vertex:
2175 if line.begin.level == level and line.begin not in forbiden:
2176 return line.begin
2177 else:
2178 assert line.begin is loop_vertex
2179 if line.end.level == level and line.end not in forbiden:
2180 return line.end
2181
2183 """ make two lines to fuse in a single one. The final line will connect
2184 the two begin."""
2185
2186
2187 self.lineList.remove(line2)
2188 self.vertexList.remove(line1.end)
2189 self.vertexList.remove(line2.end)
2190 line2.begin.lines.remove(line2)
2191
2192
2193 line1.def_end_point(line2.begin)
2194
2206
2208 """check if the T-channel of a loop diagram need to be flipped.
2209 This move from left to right the external particles linked to the
2210 loop.
2211 """
2212
2213
2214
2215
2216
2217 left_side = 0
2218 right_side = 0
2219 side_weight = 0
2220 nb_T_channel = 0
2221 nb_S_channel = 0
2222
2223
2224 binding_side = {}
2225
2226
2227 for i,vertex in enumerate(self.diagram.get('vertices')):
2228 if len([l.get('id') for l in vertex.get('legs')]) < 3:
2229 continue
2230 nb_T_channel += len([line for line in vertex.get('legs') if line.get('loop_line')
2231 and line.get('state') == False])
2232
2233
2234
2235 nb_Tloop = len([line for line in vertex.get('legs') if line.get('loop_line')
2236 and line.get('state')])
2237 nb_S_channel += nb_Tloop
2238
2239
2240 line = vertex['legs'][-1]
2241
2242 if nb_Tloop % 2:
2243 continue
2244
2245 if line.get('state'):
2246 right_side += 1
2247 left_direction = False
2248 else:
2249 left_side += 1
2250 left_direction = True
2251
2252
2253 for line in vertex['legs'][:-1]:
2254 if binding_side.has_key(line.get('number')):
2255 pass
2256 binding_side[line.get('number')] = left_direction
2257
2258 if not nb_T_channel:
2259 return False
2260
2261
2262
2263
2264 if nb_S_channel == 2:
2265 return True
2266 elif nb_T_channel == 2:
2267 return False
2268
2269
2270 if not self.fdstructures is None:
2271 for pdg, list_struct_id, vertex_id in self.diagram['tag']:
2272 for structure_id in list_struct_id:
2273 leg = self.fdstructures[structure_id].get('binding_leg')
2274 if leg.get('number') < 3:
2275 continue
2276
2277 nb_vertex = len(self.fdstructures[structure_id].get('vertices'))
2278 if not binding_side.has_key(leg.get('number')):
2279 continue
2280
2281 if binding_side[leg.get('number')]:
2282 side_weight += nb_vertex **2
2283 else:
2284 side_weight -= nb_vertex **2
2285
2286 if side_weight == 0:
2287 return left_side > right_side
2288 else:
2289 return side_weight > 0
2290
2292 """ switch t-channel information for the particle in the loop """
2293
2294
2295
2296
2297
2298
2299 for line in self.lineList:
2300 if not line.is_external() and line.loop_line:
2301 line.state = not line.state
2302
2303
2305 """Remove T-channel information"""
2306 for vertex in self.diagram.get('vertices'):
2307 legs = vertex['legs'][-1]
2308 legs.set('state', True)
2309
2310 for line in self.lineList:
2311 if not line.is_external() and line.loop_line:
2312 line.state = True
2313
2314
2315
2316
2317
2319 """Define level for adjacent vertex.
2320 If those vertex is already defined do nothing
2321 Otherwise define as level+1 (at level 1 if T-channel)
2322
2323 Special case for loop:
2324 1) Loop are on two level max. so this saturates the level
2325 2) If a branch starts from a Loop T-channel pass in negative number
2326 This is set by direction
2327 3) Treat T-channel first to avoid over-saturation of level 2
2328 This routine defines also self.max_level and self.min_level
2329
2330 This routine is foreseen for an auto-recursive mode. So as soon as a
2331 vertex have his level defined. We launch this routine for this vertex.
2332 """
2333
2334 level = vertex.level
2335 if direction == -1:
2336 nb_Tloop = len([line for line in vertex.lines if line.loop_line and \
2337 line.state])
2338 if nb_Tloop % 2:
2339 direction = 1
2340
2341 def order(line1, line2):
2342 """ put T-channel first """
2343 if line1.state == line2.state:
2344 return 0
2345 if line2.state:
2346 return -1
2347 else:
2348 return 1
2349
2350 vertex.lines.sort(order)
2351 for line in vertex.lines:
2352 if line.begin.level is not None and line.end.level is not None:
2353 continue
2354 elif line.end is vertex:
2355 if line.loop_line and not line.state:
2356 line.inverse_begin_end()
2357 next = line.end
2358 else:
2359 continue
2360 else:
2361 next = line.end
2362
2363
2364
2365 if line.state == False:
2366
2367 next.def_level(1)
2368 if line.loop_line:
2369 direction = -1
2370 nb_Tloop = len([l for l in vertex.lines
2371 if l.loop_line and l.state])
2372 if nb_Tloop % 2:
2373 direction = 1
2374
2375 elif line.loop_line:
2376 direction = 1
2377 if self.start_level_loop is None:
2378 next.def_level(level + 1)
2379 self.start_level_loop = level
2380
2381 else:
2382 next.def_level(self.start_level_loop + 1)
2383 else:
2384
2385 next.def_level(level + direction)
2386
2387 self.max_level = max(self.max_level, level + direction)
2388 self.min_level = min(self.min_level, level + direction)
2389
2390 self.def_next_level_from(next, direction)
2391