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