1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """This files contains class for creating files or object representing a
17 diagram or a set of diagrams.
18
19 class structure:
20
21 DrawDiagram:
22 In principle ALL routines representing a diagram in Any format SHOULD derive
23 from this class. This is a (nearly empty) frameworks to draw a diagram
24 in any type format.
25
26 This frameworks defines in particular
27 - function to convert the input diagram in the correct object.
28 [convert_diagram]
29 - main loop to draw a diagram in a line-by-line method
30 [draw - draw_diagram]
31
32 DrawDiagramEPS:
33 This contains all the routines to represent one diagram in Encapsuled
34 PostScript (EPS)
35
36 DrawDiagramsEPS:
37 This contains all the routines to represent a set of diagrams in Encapsuled
38 PostScript (EPS)."""
39
40 from __future__ import division
41
42 import os
43 import math
44 import madgraph.core.drawing as draw
45 import madgraph.core.base_objects as base_objects
46 import madgraph.loop.loop_base_objects as loop_objects
47 import logging
48
49 logger = logging.getLogger('madgraph.drawing_eps')
50
51 _file_path = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + '/'
52
53
54
55
57 """Class to write a EPS file containing the asked diagram
58 This class follows the DrawDiagram Frameworks.
59
60 The main routine to draw a diagram is 'draw' which call
61 1) initialize: setup things for the diagram (usually open a file)
62 2) convert_diagram : Update the diagram in the correct format if needed
63 3) draw_diagram : Perform diagram dependent operation
64 4) conclude : finish the operation.
65 """
66
67
68 width = 450
69 height = 450
70 npage = 1
71
72
73
74 x_min = 150
75 y_min = 450
76 x_max = 450
77 y_max = 750
78
79 blob_size = 1.5
80
82 """Operation done before starting to create diagram specific EPS content
83 First open the file in write mode then write in it the header and the
84 library of particle type."""
85
86
87 super(EpsDiagramDrawer, self).initialize()
88
89
90 text = "%!PS-Adobe-2.0\n"
91 text += "%%" + "BoundingBox: -20 -20 %s %s \n" % \
92 (self.width, self.height)
93 text += "%%DocumentFonts: Helvetica\n"
94 text += "%%" + "Pages: %s \n" % self.npage
95 self.file.writelines(text)
96
97
98 self.file.writelines(open(os.path.join(_file_path, \
99 'iolibs/template_files/drawing_eps_header.inc')).read())
100
101
103 """Operation to perform when all code related to a specific diagram are
104 finish. Operation :
105 - Add the 'end of page' code
106 - write unwritten text and close the file. [DrawDiagram.conclude]"""
107
108
109 self.text = 'showpage\n'
110 self.text += '%%trailer\n'
111
112
113 super(EpsDiagramDrawer, self).conclude()
114
115
117 """All coordinates belongs to [0,1]. So that in order to have a visible
118 graph we need to re-scale the graph. This method distort the square in
119 a oblong. Deformation are linear."""
120
121
122
123 x = self.x_min + (self.x_max - self.x_min) * x
124 y = self.y_min + (self.y_max - self.y_min) * y
125
126 return x, y
127
128
141
142 - def draw_vertex(self, vertex, bypass = ['QED','QCD'] ):
143 """Add blob in case on non QED-QCD information"""
144
145 interaction = self.model.get_interaction(vertex.id)
146 if interaction:
147 order = interaction.get('orders')
148 order = [key for key in order.keys() if order[key] and \
149 key not in bypass]
150
151 if order:
152 x1, y1 = self.rescale(vertex.pos_x, vertex.pos_y)
153 self.text += " %s %s %s 1.0 Fblob \n" % (x1, y1, self.blob_size)
154
155
156
158 """ADD the EPS code for this fermion line."""
159
160
161 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
162 line.end.pos_x, line.end.pos_y, 'Ffermion')
163
165 """ADD the EPS code for this fermion line."""
166
167 if not cercle:
168 curvature = 0.4
169 else:
170 curvature = 1
171
172 if (line.begin.pos_x, line.begin.pos_y) == self.curved_part_start:
173 curvature *= -1
174
175
176 x1, y1 = self.rescale(line.begin.pos_x, line.begin.pos_y)
177 self.text += ' %s %s moveto \n' % (x1, y1)
178 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
179 line.end.pos_x, line.end.pos_y, '%s Fhiggsl' %\
180 curvature)
181
183 """ADD the EPS code for this fermion line."""
184
185 if not cercle:
186 curvature = 0.4
187 else:
188 curvature = 1
189
190 if (line.begin.pos_x, line.begin.pos_y) == self.curved_part_start:
191 curvature *= -1
192
193
194 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
195 line.end.pos_x, line.end.pos_y, '%s Ffermionl' %\
196 curvature)
197
199 """ADD the EPS code for this fermion line."""
200
201 if not cercle:
202 curvature = 4
203 else:
204 curvature = 5
205
206 is_tadpole = line.begin.pos_x==line.end.pos_x and \
207 line.begin.pos_y==line.end.pos_y
208
209 if is_tadpole:
210
211 direction = None
212 for l in line.begin.lines:
213 new_direction = (l.end.pos_x-l.begin.pos_x, l.end.pos_y-l.begin.pos_y)
214 if new_direction==(0.0,0.0):
215 continue
216 norm = math.sqrt(new_direction[0]**2+new_direction[1]**2)
217 new_direction = (new_direction[0]/norm, new_direction[1]/norm)
218
219 if not direction:
220 direction = new_direction
221 else:
222 if direction not in \
223 [new_direction, (-new_direction[0],-new_direction[1])]:
224 pass
225
226
227 continue
228
229
230 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
231 line.end.pos_x+0.01*direction[0], line.end.pos_y+0.01*direction[1],
232 '%s Ffermionl' % (curvature*7))
233 else:
234
235 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
236 line.end.pos_x+0.01, line.end.pos_y+0.01, '%s Ffermionl' %\
237 curvature)
239 """ADD the EPS code for this Higgs line."""
240
241
242 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
243 line.end.pos_x, line.end.pos_y, 'Fhiggs')
244
245
247 """ADD the EPS code for this Higgs line."""
248 if not cercle:
249 curvature = 4
250 else:
251 curvature = 5
252
253 is_tadpole = line.begin.pos_x==line.end.pos_x and \
254 line.begin.pos_y==line.end.pos_y
255
256 if is_tadpole:
257
258 direction = None
259 for l in line.begin.lines:
260 new_direction = (l.end.pos_x-l.begin.pos_x, l.end.pos_y-l.begin.pos_y)
261 if new_direction==(0.0,0.0):
262 continue
263 norm = math.sqrt(new_direction[0]**2+new_direction[1]**2)
264 new_direction = (new_direction[0]/norm, new_direction[1]/norm)
265
266 if not direction:
267 direction = new_direction
268 else:
269 if direction not in \
270 [new_direction, (-new_direction[0],-new_direction[1])]:
271
272
273 pass
274
275
276 x, y = self.rescale(line.begin.pos_x, line.begin.pos_y)
277 self.text += '%s %s moveto'%(x, y)
278 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
279 line.end.pos_x+0.01*direction[0], line.end.pos_y+0.01*direction[1],
280 '%s Fhiggsl' % (curvature*7))
281 else:
282
283 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
284 line.end.pos_x+0.01, line.end.pos_y+0.01, '%s Fhiggsl'% curvature)
285
287 """ADD the EPS code for the ghost line."""
288
289
290 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,\
291 line.end.pos_x, line.end.pos_y, 'Fghost')
292
294 """ADD the EPS code for the ghost line."""
295 if not cercle:
296 curvature = 0.4
297 else:
298 curvature = 1
299
300 if (line.begin.pos_x, line.begin.pos_y) == self.curved_part_start:
301 curvature *= -1
302
303 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,\
304 line.end.pos_x, line.end.pos_y, '%s Fghostl'% curvature)
305
307 """ADD the EPS code for the ghost line."""
308 if not cercle:
309 curvature = 4
310 else:
311 curvature = 5
312
313 is_tadpole = line.begin.pos_x==line.end.pos_x and \
314 line.begin.pos_y==line.end.pos_y
315
316 if is_tadpole:
317
318 direction = None
319 for l in line.begin.lines:
320 new_direction = (l.end.pos_x-l.begin.pos_x, l.end.pos_y-l.begin.pos_y)
321 if new_direction==(0.0,0.0):
322 continue
323 norm = math.sqrt(new_direction[0]**2+new_direction[1]**2)
324 new_direction = (new_direction[0]/norm, new_direction[1]/norm)
325
326 if not direction:
327 direction = new_direction
328 else:
329 if direction not in \
330 [new_direction, (-new_direction[0],-new_direction[1])]:
331
332
333 pass
334
335
336 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
337 line.end.pos_x+0.01*direction[0], line.end.pos_y+0.01*direction[1],
338 '%s Fghostl' % (curvature*7))
339 else:
340
341 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,\
342 line.end.pos_x+0.01, line.end.pos_y+0.01, '%s Fghostl'% curvature)
343
345 """ADD the EPS code for this photon line."""
346
347
348 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
349 line.end.pos_x, line.end.pos_y, '%d Fphoton%s' % (opt,type))
350
352 """ADD the EPS code for this photon line."""
353 if not cercle:
354 curvature = 0.4
355 else:
356 curvature = 1
357 if (line.begin.pos_x, line.begin.pos_y) == self.curved_part_start:
358 curvature *= -1
359
360 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
361 line.end.pos_x, line.end.pos_y, '%d %s Fphotonl%s' % (opt,curvature,type))
362
364 """ADD the EPS code for this photon line."""
365
366 if not cercle:
367 curvature = 4
368 else:
369 curvature = 5
370
371 is_tadpole = line.begin.pos_x==line.end.pos_x and \
372 line.begin.pos_y==line.end.pos_y
373
374 if is_tadpole:
375
376 direction = None
377 for l in line.begin.lines:
378 new_direction = (l.end.pos_x-l.begin.pos_x, l.end.pos_y-l.begin.pos_y)
379 if new_direction==(0.0,0.0):
380 continue
381 norm = math.sqrt(new_direction[0]**2+new_direction[1]**2)
382 new_direction = (new_direction[0]/norm, new_direction[1]/norm)
383
384 if not direction:
385 direction = new_direction
386 else:
387 if direction not in \
388 [new_direction, (-new_direction[0],-new_direction[1])]:
389
390
391 pass
392
393
394 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
395 line.end.pos_x+0.01*direction[0], line.end.pos_y+0.01*direction[1],
396 '%d %s Fphotonl%s' % (opt,curvature*7,type))
397 else:
398
399 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
400 line.end.pos_x+0.01, line.end.pos_y+0.01,
401 '%d %s Fphotonl%s' % (opt,curvature,type))
402
404 """ADD the EPS code for this gluon line."""
405
406
407
408
409 if (line.begin.pos_x < line.end.pos_x) or \
410 (line.begin.pos_x == line.end.pos_x and \
411 line.begin.pos_y > line.end.pos_y):
412 self.text += self.line_format(line.begin.pos_x,
413 line.begin.pos_y, line.end.pos_x,
414 line.end.pos_y, '0 Fgluon%s' % type)
415 else:
416 self.text += self.line_format(line.end.pos_x,
417 line.end.pos_y, line.begin.pos_x,
418 line.begin.pos_y, '0 Fgluon%s' % type)
419
421 """ADD the EPS code for this gluon line."""
422
423 dist = math.sqrt((line.begin.pos_x-line.end.pos_x)**2 + \
424 (line.begin.pos_y-line.end.pos_y)**2)
425 if not cercle or dist > 0.3:
426 curvature = 0.4
427 else:
428 curvature = 1
429
430
431
432
433
434
435 if (line.begin.pos_x, line.begin.pos_y) == self.curved_part_start:
436 curvature *= -1
437
438 self.text += self.line_format(line.end.pos_x,
439 line.end.pos_y, line.begin.pos_x,
440 line.begin.pos_y, '0 %s Fgluonl%s' % (-1*curvature, type))
441
442
443
449
454
456 """ADD the EPS code for this neutralino line."""
457
458
459 length = math.sqrt((line.end.pos_y - line.begin.pos_y)**2 + (line.end.pos_x - line.begin.pos_x) **2)
460 c1 = (line.end.pos_x - line.begin.pos_x)/length
461 c2 = (line.end.pos_y - line.begin.pos_y)/length
462
463 gap = 0.013
464 start2_x = line.begin.pos_x + gap * c1
465 start2_y = line.begin.pos_y + gap * c2
466 stop1_x = line.end.pos_x - gap * c1
467 stop1_y = line.end.pos_y - gap * c2
468
469
470 self.text += self.line_format(line.begin.pos_x, line.begin.pos_y,
471 stop1_x, stop1_y, '0 Fphoton%s' % (type))
472
473 self.text += self.line_format(start2_x, start2_y,
474 line.end.pos_x, line.end.pos_y, '0 Fphoton%s' % (type))
475
476
478 """ADD the comment 'diagram [number]' just below the diagram."""
479
480
481 x = 0.2
482 y = -0.17
483
484 x, y = self.rescale(x, y)
485
486 self.text += ' %s %s moveto \n' % (x, y)
487
488 if hasattr(self, 'diagram_type'):
489 self.text += '(%s diagram %s ) show\n' % (self.diagram_type, number + 1)
490
491
492 else:
493 self.text += '(diagram %s ) show\n' % (number + 1)
494
495
496
497 mystr = " (%s)" % ", ".join(["%s=%d" % (key, self.diagram.diagram['orders'][key]) \
498 for key in sorted(self.diagram.diagram['orders'].keys()) \
499 if key != 'WEIGHTED'])
500
501 x = 0.6
502 y = -0.17
503 x, y = self.rescale(x, y)
504
505 self.text += ' %s %s moveto \n' % (x, y)
506 self.text += '%s show\n' % (mystr)
507
508
509
511 """Write in the EPS figure the MadGraph5_aMC@NLO number associate to the line.
512 Note that this routine is called only for external particle."""
513
514
515 if line.begin.is_external():
516 vertex = line.begin
517 else:
518 vertex = line.end
519
520
521 x = vertex.pos_x
522 y = vertex.pos_y
523
524
525 if x == 0:
526 x = -0.04
527 else:
528 x += 0.04
529 y = line._has_ordinate(x)
530
531
532 x, y = self.rescale(x, y)
533
534 self.text += ' %s %s moveto \n' % (x, y)
535 self.text += '(%s) show\n' % (number)
536
538 """ADD the EPS code associate to the name of the particle. Place it near
539 to the center of the line.
540 """
541
542 is_tadpole = line.begin.pos_x==line.end.pos_x and \
543 line.begin.pos_y==line.end.pos_y
544 if is_tadpole:
545
546 direction = None
547 for l in line.begin.lines:
548 new_direction = (l.end.pos_x-l.begin.pos_x, l.end.pos_y-l.begin.pos_y)
549 if new_direction==(0.0,0.0):
550 continue
551 norm = math.sqrt(new_direction[0]**2+new_direction[1]**2)
552 new_direction = (new_direction[0]/norm, new_direction[1]/norm)
553
554 if not direction:
555 direction = new_direction
556 else:
557 if direction not in \
558 [new_direction, (-new_direction[0],-new_direction[1])]:
559
560
561 pass
562
563
564 orthogonal = (-direction[1],direction[0])
565
566
567 x1, y1 = line.begin.pos_x, line.begin.pos_y
568 x2, y2 = line.end.pos_x, line.end.pos_y
569
570 d = line.get_length()
571 if is_tadpole:
572 scale = 0.08
573 dx, dy = scale*orthogonal[0], scale*orthogonal[1]
574
575 elif abs(x1 - x2) < 1e-3:
576 dx = 0.015
577 dy = -0.01
578 elif abs(y1 - y2) < 1e-3:
579 dx = -0.01
580 dy = 0.025
581 elif ((x1 < x2) == (y1 < y2)):
582 dx = -0.03 * len(name)
583 dy = 0.02 * len(name)
584 else:
585 dx = 0.01
586 dy = 0.02
587 if loop:
588 dx, dy = 1.5* dx, dy
589 if x1 == x2:
590 if y1 < y2:
591 dx, dy = -dx, -dy
592 elif y1 == y2:
593 if x1 >x2:
594 dx, dy = -dx, -dy
595 elif x1 < x2:
596 dx, dy = -dx, -dy
597 if reverse:
598 dx, dy = -dx, -dy
599
600
601
602
603 x_pos = (x1 + x2) / 2 + dx
604 y_pos = (y1 + y2) / 2 + dy
605
606
607 x_pos, y_pos = self.rescale(x_pos, y_pos)
608
609 self.text += ' %s %s moveto \n' % (x_pos, y_pos)
610 self.text += '(' + name + ') show\n'
611
612
613
614
615
617 """Class to write a EPS file containing the asked set of diagram
618 This class follows the DrawDiagram Frameworks.
619
620 The main routine to draw a diagram is 'draw' which call
621 1) initialize: setup things for the diagram (usually open a file)
622 2) convert_diagram : Update the diagram in the correct format if needed
623 3) draw_diagram : Perform diagram dependent operation
624 4) conclude : finish the operation.
625 """
626
627
628
629
630
631 x_min = 75
632 x_size = 200
633 y_min = 560
634 y_size = 150
635
636 x_gap = 75
637 y_gap = 70
638
639
640 font=9
641
642
643 nb_line = 3
644 nb_col = 2
645
646 blob_size = 1.5
647
648 lower_scale = 5
649 second_scale ={'x_min': 40, 'x_size':150,'y_min':620,'y_size':100,
650 'x_gap':42,'y_gap':30,'font':6,'nb_line':5,'nb_col':3,
651 'blob_size':0.9}
652
653 - def __init__(self, diagramlist=None, filename='diagram.eps', \
654 model=None, amplitude=None, legend='',diagram_type=''):
655 """Define basic variable and store some global information
656 all argument are optional
657 diagramlist : are the list of object to draw. item should inherit
658 from either base_objects.Diagram or drawing_lib.FeynmanDiagram
659 filename: filename of the file to write
660 model: model associate to the diagram. In principle use only if diagram
661 inherit from base_objects.Diagram
662 amplitude: amplitude associate to the diagram. NOT USE for the moment.
663 In future you could pass the amplitude associate to the object in
664 order to adjust fermion flow in case of Majorana fermion."""
665
666
667 super(MultiEpsDiagramDrawer, self).__init__(None, filename , model, \
668 amplitude)
669 self.legend = legend
670
671 self.block_nb = 0
672 self.curr_page = 0
673 self.block_in_page = 0
674
675 self.npage = 1
676 self.diagram_type = diagram_type
677
678 diagramlist = [d for d in diagramlist if not (isinstance(d, loop_objects.LoopUVCTDiagram) or \
679 (isinstance(d, loop_objects.LoopDiagram) and d.get('type') < 0))]
680 diagramlist = base_objects.DiagramList(diagramlist)
681
682 limit = self.lower_scale * self.nb_col * self.nb_line
683 if len(diagramlist) < limit:
684 self.npage += (len(diagramlist)-1) // (self.nb_col * self.nb_line)
685 else:
686 add = (len(diagramlist) - limit -1) // \
687 (self.second_scale['nb_col'] * self.second_scale['nb_line'])
688 self.npage += self.lower_scale + add
689
690 if diagramlist:
691
692 assert(isinstance(diagramlist, base_objects.DiagramList))
693 self.diagramlist = diagramlist
694 else:
695 self.diagramlist = None
696
698 """All coordinates belongs to [0,1]. So that in order to have a visible
699 graph we need to re-scale the graph. This method distort the square in
700 a oblong. Deformation are linear."""
701
702
703 block_pos = self.block_in_page
704 line_pos = block_pos // self.nb_col
705 col_pos = block_pos % self.nb_col
706
707
708
709 x_min = self.x_min + (self.x_size + self.x_gap) * col_pos
710 x_max = self.x_min + self.x_gap * (col_pos) + self.x_size * \
711 (col_pos + 1)
712 y_min = self.y_min - (self.y_size + self.y_gap) * line_pos
713 y_max = self.y_min - self.y_gap * (line_pos) - self.y_size * \
714 (line_pos - 1)
715
716
717 x = x_min + (x_max - x_min) * x
718 y = y_min + (y_max - y_min) * y
719
720 return x, y
721
723 """Creates the representation in EPS format associate to a specific
724 diagram."""
725
726
727 super(MultiEpsDiagramDrawer, self).draw_diagram(diagram, self.block_nb)
728
729
730 self.block_nb += 1
731 self.block_in_page +=1
732
733
734 - def draw(self, diagramlist='', opt=None):
735 """Creates the representation in EPS format associate to a specific
736 diagram. 'opt' keeps track of possible option of drawing. Those option
737 are used if we need to convert diagram to Drawing Object.
738 opt is an DrawOption object containing all the possible option on how
739 draw a diagram."""
740
741 if diagramlist == '':
742 diagramlist = self.diagramlist
743
744
745
746 self.initialize()
747 self.text += '/Helvetica findfont %s scalefont setfont\n' % self.font
748 self.text += ' 50 770 moveto\n'
749 self.text += ' (%s) show\n' % self.legend
750 self.text += ' 525 770 moveto\n'
751 self.text += ' (page %s/%s) show\n' % (self.curr_page + 1, self.npage)
752 self.text += ' 260 50 moveto\n'
753 self.text += ' (Diagrams made by MadGraph5_aMC@NLO) show\n'
754
755 for i,diagram in enumerate(diagramlist):
756
757 diagram = self.convert_diagram(diagram, self.model, self.amplitude, opt)
758 if diagram==None:
759 continue
760
761 self.draw_diagram(diagram)
762
763
764 if self.block_in_page % (self.nb_col * self.nb_line) == 0:
765
766 self.pass_to_next_page()
767
768
769 self.conclude()
770
772 """Insert text in order to pass to next EPS page."""
773
774 self.curr_page += 1
775 self.block_in_page = 0
776 if self.curr_page == self.lower_scale:
777 for key, value in self.second_scale.items():
778 setattr(self, key, value)
779
780
781 self.text += 'showpage\n'
782 self.text += '%%' + 'Page: %s %s \n' % (self.curr_page+1, self.curr_page+1)
783 self.text += '%%PageBoundingBox:-20 -20 600 800\n'
784 self.text += '%%PageFonts: Helvetica\n'
785 self.text += '/Helvetica findfont %s scalefont setfont\n' % self.font
786 self.text += ' 50 770 moveto\n'
787 self.text += ' (%s) show\n' % self.legend
788 self.text += ' 525 770 moveto\n'
789 self.text += ' (page %s/%s) show\n' % (self.curr_page + 1, self.npage)
790 self.text += ' 260 40 moveto\n'
791 self.text += ' (Diagrams made by MadGraph5_aMC@NLO) show\n'
792