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