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