Package madgraph :: Package core :: Module drawing
[hide private]
[frames] | no frames]

Source Code for Module madgraph.core.drawing

   1  ################################################################################ 
   2  # 
   3  # Copyright (c) 2009 The MadGraph Development team and Contributors 
   4  # 
   5  # This file is a part of the MadGraph 5 project, an application which  
   6  # automatically generates Feynman diagrams and matrix elements for arbitrary 
   7  # high-energy processes in the Standard Model and beyond. 
   8  # 
   9  # It is subject to the MadGraph license which should accompany this  
  10  # distribution. 
  11  # 
  12  # For more information, please visit: http://madgraph.phys.ucl.ac.be 
  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   
  49   
  50  #=============================================================================== 
  51  # FeynmanLine 
  52  #=============================================================================== 
53 -class FeynmanLine(object):
54 """All the information about a line in a Feynman diagram 55 i.e. begin-end/type/tag.""" 56
57 - class FeynmanLineError(Exception):
58 """Exception raised if an error occurs in the definition 59 or the execution of a Feynam_line."""
60
61 - def __init__(self, pid, init_dict={}):
62 """Initialize the FeynmanLine content.""" 63 64 for key, value in init_dict.items(): 65 setattr(self, key, value) 66 self.pid = pid 67 self.start = 0 68 self.end = 0
69 70 # def is_valid_prop(self, name): 71 # """Check if a given property name is valid.""" 72 # 73 # if name == 'pid': 74 # return True 75 # else: 76 # return super(FeynmanLine, self).is_valid_prop(name) 77
78 - def def_model(self, model):
79 """ 80 make a link between the present object and the associate model 81 """ 82 83 assert isinstance(model, base_objects.Model), ' try to assign a non model obect' 84 85 self.model = model
86
87 - def def_begin_point(self, vertex):
88 """-Re-Define the starting point of the line.""" 89 90 assert isinstance(vertex, VertexPoint), 'The begin point should be a ' + \ 91 'Vertex_Point object' 92 93 self.start = vertex 94 vertex.add_line(self) 95 return
96
97 - def def_end_point(self, vertex):
98 """-Re-Define the starting point of the line. with check""" 99 100 assert isinstance(vertex, VertexPoint), 'The end point should be a ' + \ 101 'Vertex_Point object' 102 103 self.end = vertex 104 vertex.add_line(self) 105 return
106
107 - def add_vertex(self, vertex):
108 """Associate the vertex to the line at the correct position. 109 line.start should be closer of the lower right corner than line.end. 110 111 This is achieved in the following way: 112 * We don't care about external particles.Those one will be perform 113 easily in a second step. In the mean time we apply this method anyway. 114 Internal particles are created from a combination of particles. 115 * S-channel either are create from number [X,Y,Z are strictly bigger 116 than two and A,B,C are strictly bigger than one). 117 (1 A [X Y]> 1) =>forward 118 (X Y [Z]> X) => backward 119 * T-channel are also produce either by 120 (1 X> 1) =>forward 121 (2 X >2) => backward 122 So the common rule is to check if the number is one or not. 123 """ 124 # Look if we already resolve this problem 125 if self.start: 126 self.def_end_point(vertex) 127 elif self.end: 128 self.def_begin_point(vertex) 129 #If not: resolve it. Treat external particle separately 130 else: 131 number = self.number 132 if number == 1: 133 self.def_begin_point(vertex) 134 else: 135 self.def_end_point(vertex)
136
137 - def define_line_orientation(self):
138 """Define the line orientation. Use the following rules: 139 Particles move timelike when anti-particles move anti-timelike. 140 """ 141 142 if (self.pid < 0): 143 self.inverse_begin_end()
144
145 - def inverse_pid_for_type(self, inversetype='straight'):
146 """Change the particle in his anti-particle if this type is 147 equal to 'inversetype'.""" 148 149 drawtype = self.get_info('line') 150 if drawtype == inversetype: 151 self.inverse_part_antipart()
152
153 - def inverse_part_antipart(self):
154 """Pass particle into an anti-particle. This is needed for initial state 155 particles (usually wrongly defined) and for some fermion flow resolution 156 problem.""" 157 158 self.pid = -1 * self.pid
159
160 - def inverse_begin_end(self):
161 """Invert the orientation of the line. This is needed to have correct 162 fermion flow.""" 163 164 self.start, self.end = self.end, self.start
165
166 - def get_info(self, name):
167 """Return the model information 'name' associated to the line.""" 168 169 pid = abs(self.pid) 170 return self.model.get_particle(pid).get(name)
171 172
173 - def get_name(self, name='name'):
174 """Return the name associate to the particle.""" 175 176 pid = self.pid 177 model_info = self.model.get_particle(pid) 178 if model_info is None: 179 model_info = self.model.get_particle(-pid) 180 return model_info.get_name()
181
182 - def get_length(self):
183 """ return the length of the line """ 184 185 return math.sqrt((self.end.pos_x - self.start.pos_x) ** 2 + \ 186 (self.end.pos_y - self.start.pos_y) ** 2)
187 188
189 - def is_fermion(self):
190 """Returns True if the particle is a fermion.""" 191 192 model_info = self.model.get_particle(abs(self.pid)) 193 if model_info.get('line') == 'straight': 194 return True
195
196 - def is_external(self):
197 """Check if this line represent an external particles or not.""" 198 199 return self.end.is_external() or self.start.is_external()
200
201 - def __eq__(self, other):
202 """Define that two line are equal when they have the same pointer""" 203 204 return self is other
205
206 - def __ne__(self, other):
207 """Define that two line are different when they have different 208 pointer.""" 209 210 return self is not other
211 212 213 # Debugging Routines linked to FeynmanLine --------------------------------- 214
215 - def has_intersection(self, line):
216 """Check if the two line intersects and returns status. A common vertex 217 is not consider as an intersection. 218 This routine first check input validity. 219 220 At current status this is use for test/debugging only.""" 221 222 assert self.check_position_exist() 223 assert line.check_position_exist() 224 225 return self._has_intersection(line)
226
227 - def _has_intersection(self, line):
228 """Check if the two line intersects and returns status. A common vertex 229 is not consider as an intersection. 230 231 At current status this is only use for test/debugging only.""" 232 233 #tolerance for equality of two number 234 sys_error = 1e-7 235 236 # Find the x-range where both line are defined 237 min, max = self._domain_intersection(line) 238 239 # No x-value where both line are defined => No possible intersection 240 if min == None: 241 return False 242 243 # Only one x value is common for both line 244 if min == max : 245 # Check if self is normal line (not vertical) 246 if abs(self.start.pos_x - self.end.pos_x) > sys_error: 247 # Check if line is normal line (not vertical) 248 if abs(line.start.pos_x - line.end.pos_x) > sys_error: 249 # No vertical line => one vertex in common 250 return False 251 # line is vertical but not self: 252 return self._intersection_with_vertical_line(line) 253 254 # Check if line is not vertical) 255 elif (abs(line.start.pos_x - line.end.pos_x) > sys_error): 256 # self is vertical but not line 257 return line._intersection_with_vertical_line(self) 258 259 # both vertical case 260 else: 261 # Find the y-range where both line are defined 262 min, max = self._domain_intersection(line, 'y') 263 if min == None or min == max: 264 return False 265 else: 266 return True 267 268 # No vertical line -> resolve angular coefficient 269 xS0 = self.start.pos_x 270 yS0 = self.start.pos_y 271 xS1 = self.end.pos_x 272 yS1 = self.end.pos_y 273 274 xL0 = line.start.pos_x 275 yL0 = line.start.pos_y 276 xL1 = line.end.pos_x 277 yL1 = line.end.pos_y 278 279 coef1 = (yS1 - yS0) / (xS1 - xS0) 280 coef2 = (yL1 - yL0) / (xL1 - xL0) 281 282 # Check if the line are parallel 283 if abs(coef1 - coef2) < sys_error: 284 # Check if one point in common in the domain 285 if abs(line._has_ordinate(min) - self._has_ordinate(min)) < \ 286 sys_error: 287 return True 288 else: 289 return False 290 291 # Intersecting line -> find point of intersection (commonX, commonY) 292 commonX = (yS0 - yL0 - coef1 * xS0 + coef2 * xL0) / (coef2 - coef1) 293 294 #check if the intersection is in the x-domain 295 if (commonX >= min) == (commonX >= max): 296 return False 297 298 commonY = self._has_ordinate(commonX) 299 300 #check if intersection is a common vertex 301 if self.is_end_point(commonX, commonY): 302 if line.is_end_point(commonX, commonY): 303 return False 304 else: 305 return True 306 else: 307 return True
308
309 - def is_end_point(self, x, y):
310 """Check if 'x','y' are one of the end point coordinates of the line. 311 312 At current status this is use for test/debugging only.""" 313 314 #authorize error machine 315 gap = 1e-9 316 317 if abs(x - self.start.pos_x) < gap and abs(y - self.start.pos_y) < gap: 318 return True 319 elif abs(x - self.end.pos_x) < gap and abs(y - self.end.pos_y) < gap: 320 return True 321 else: 322 return False
323
324 - def domain_intersection(self, line, axis='x'):
325 """Returns x1,x2 where both line and self are defined. 326 Returns None, None if this domain is empty. 327 This routine contains self consistency check 328 329 At current status this is use for test/debugging only.""" 330 331 assert isinstance(line, FeynmanLine), ' domain intersection are between ' + \ 332 'Feynman_line object only and not {0} object'.format(type(line)) 333 334 # Check consistency 335 self.check_position_exist() 336 line.check_position_exist() 337 338 # Launch routine 339 return self._domain_intersection(line, axis)
340
341 - def _domain_intersection(self, line, axis='x'):
342 """Returns x1,x2 where both line and self are defined. 343 Returns None, None if this domain is empty. 344 This routine doesn't contain self consistency check. 345 346 At current status this is use for debugging only.""" 347 348 #find domain for each line 349 min_self, max_self = self.border_on_axis(axis) 350 min_line, max_line = line.border_on_axis(axis) 351 352 #find intersection 353 start = max(min_self, min_line) 354 end = min(max_self, max_line) 355 if start <= end: 356 return start, end 357 else: 358 return None, None
359
360 - def border_on_axis(self, axis='x'):
361 """ Returns the two value of the domain interval for the given axis. 362 363 At current status this is use for test/debugging only.""" 364 365 data = [getattr(self.start, 'pos_' + axis), \ 366 getattr(self.end, 'pos_' + axis)] 367 data.sort() 368 return data
369
370 - def _intersection_with_vertical_line(self, line):
371 """Checks if line intersect self. Line SHOULD be a vertical line and 372 self COULDN'T. No test are done to check those conditions. 373 374 At current status this is use for test/debugging only.""" 375 376 # Find the y coordinate for the x-value corresponding to line x-position 377 y_self = self._has_ordinate(line.start.pos_x) 378 379 # Find the y range for line. This is done in order to check that the 380 #intersection point is not a common vertex 381 ymin, ymax = line.border_on_axis('y') 382 383 # Search intersection status 384 if (ymin == y_self or ymax == y_self): 385 if self.is_end_point(line.start.pos_x, y_self): 386 return False 387 else: 388 return True 389 elif (y_self > ymin) and (y_self < ymax): 390 return True 391 else: 392 return False
393
394 - def check_position_exist(self):
395 """Check that the begin-end position are defined. 396 397 At current status this is use for debugging only.""" 398 399 try: 400 self.start.pos_x 401 self.end.pos_y 402 except: 403 raise self.FeynmanLineError, 'No vertex in begin-end position ' + \ 404 ' or no position attach at one of those vertex ' 405 return True
406
407 - def has_ordinate(self, x):
408 """Returns the y associate to the x value in the line 409 Raises FeynmanLineError if point outside interval or result not unique. 410 This routines contains check consistency. 411 412 At current status this is use for debugging only.""" 413 414 if __debug__: 415 self.check_position_exist() 416 min = self.start.pos_x 417 max = self.end.pos_x 418 if max < min: 419 min, max = max, min 420 421 if min == max: 422 raise self.FeynmanLineError, 'Vertical line: no unique solution' 423 if(not(min <= x <= max)): 424 raise self.FeynmanLineError, 'point outside interval invalid ' + \ 425 'invalid order {0:3}<={1:3}<={2:3}'.format(min, x, max) 426 427 return self._has_ordinate(x)
428
429 - def _has_ordinate(self, x):
430 """Returns the y associate to the x value in the line 431 This routines doesn't contain check consistency. 432 433 At current status this is use for debugging only.""" 434 435 #calculate the angular coefficient 436 x_0 = self.start.pos_x 437 y_0 = self.start.pos_y 438 x_1 = self.end.pos_x 439 y_1 = self.end.pos_y 440 441 alpha = (y_1 - y_0) / (x_1 - x_0) #x1 always diff of x0 442 443 444 ordinate_fct = lambda X: y_0 + alpha * (X - x_0) 445 return ordinate_fct(x)
446 447 448 449 #=============================================================================== 450 # VertexPoint 451 #===============================================================================
452 -class VertexPoint(object):
453 """Extension of the class Vertex in order to store the information 454 linked to the display of a FeynmanDiagram, as position 455 """ 456
457 - class VertexPointError(Exception):
458 """Exception raised if an error occurs in the definition 459 or the execution of a VertexPoint."""
460 461
462 - def __init__(self, vertex):
463 """Update a vertex to a VertexPoint with additional information about 464 positioning and link with other vertex/line of the diagram.""" 465 466 # Check the validity of the parameter should be Vertex 467 assert(isinstance(vertex, base_objects.Vertex)) 468 469 # Copy data and add new entry 470 for key, value in vertex.items(): 471 setattr(self, key, value) 472 self.lines = [] 473 self.level = -1 474 self.pos_x = 0 475 self.pos_y = 0
476
477 - def def_position(self, x, y):
478 """-Re-Define the position of the vertex in a square [0, 1]^2""" 479 480 # Coordinate should b 481 assert 0 <= x <= 1 and 0 <= y <= 1 , 'vertex coordinate should be' + \ 482 ' in 0,1 interval introduce value ({0},{1})'.format(x, y) 483 484 self.pos_x = x 485 self.pos_y = y 486 return
487
488 - def fuse_vertex(self, vertex, common_line=''):
489 """Import the line of the second vertex in the first one 490 this means 491 A) change the 'line' of this vertex 492 B) change the start-end position of line to point on this vertex 493 C) remove common_line (if defined).""" 494 495 for line in vertex.lines: 496 # Remove common line. They are shrink to a single point 497 if line is common_line: 498 self.lines.remove(line) 499 continue 500 501 # Re-define the begin-end vertex of the line to point on this vertex 502 #and not on the old one. self.lines is automatically updated. 503 if line.start is vertex: 504 line.def_begin_point(self) 505 else: 506 line.def_end_point(self) 507 return
508 509
510 - def add_line(self, line):
511 """Add the line in the list keeping line connected to this vertex : 512 self.lines. This routine avoid duplication of entry.""" 513 514 assert isinstance(line, FeynmanLine), \ 515 'Trying to add in a Vertex a non FeynmanLine Object' 516 517 for oldline in self.lines: 518 if oldline is line: 519 return 520 521 self.lines.append(line)
522
523 - def remove_line(self, line_to_del):
524 """Remove the line from the lineList. Didn't touch to vertex associate 525 to begin/end point. This happens only if we fuse two vertex together. 526 (Then the line will be completely drop out, such that we dont't care 527 about those vertex point.""" 528 529 assert isinstance(line_to_del, FeynmanLine), \ 530 'trying to remove in a Vertex_Point a non FeynmanLine Object' 531 532 # Find the first item in the list and remove it. note that we cann't use 533 #standard delete as remove because it's use '==' and not 'is'. 534 for i, line in enumerate(self.lines): 535 if line is line_to_del: 536 del self.lines[i] 537 return # only one data to remove! 538 539 raise self.VertexPointError, 'trying to remove in a ' + \ 540 'Vertex_Point a non present Feynman_Line'
541 542
543 - def def_level(self, level):
544 """Define the Vertex level at 'level'. The level represents the 545 distance between the initial vertex and the current vertex. This 546 distance is define has the number of non T-channel particles needed to 547 connect this particle to initial states starting point.""" 548 549 assert isinstance(level, int), 'Trying to attribute non integer level' 550 551 self.level = level
552
553 - def is_external(self):
554 """Check if this vertex is external , i.e is related to a single 555 (external) particles.""" 556 557 #the termination has only one line. 558 if len(self.lines) == 1: 559 return True 560 else: 561 return False
562
563 - def has_the_same_line_content(self, other):
564 """Check if the line associate to the two vertex are equivalent. 565 This means that they have the same number of particles with the same pid 566 and that the external particles have the same number. 567 568 This is a backup function, this is not use for the moment.""" 569 570 # Check the number of line 571 if len(self.lines) != len(other.lines): 572 return False 573 574 # Store the information of the pid content of the vertex other 575 other_line_pid = [line.pid for line in other.lines] 576 # Sort the information of the number content for external particle 577 other_line_number = [line.number for line in other.lines if \ 578 line.is_external()] 579 580 # Now look at the self vertex and destroy the information store in the 581 #two above variable. If an error raise, this means that the content is 582 #not the same. 583 for s_line in self.lines: 584 try: 585 other_line_pid.remove(s_line.pid) 586 except ValueError: 587 return False 588 if s_line.is_external(): 589 try: 590 other_line_number.remove(s_line.number) 591 except ValueError: 592 return False 593 594 # The lines have all their equivalent, so returns True 595 return True
596
597 - def get_uid(self):
598 """Provide a unique id for the vertex""" 599 600 tag = 0 601 for i, line in enumerate(self.lines): 602 tag += line.number / 10 ** (-i) 603 tag = tag * 10 ** (len(self.lines) + 1) 604 return tag
605
606 - def __eq__(self, other):
607 """Define equality with pointeur equality.""" 608 609 return self is other
610 611 #=============================================================================== 612 # FeynmanDiagram 613 #===============================================================================
614 -class FeynmanDiagram:
615 """Object to compute the position of the different Vertex and Line associate 616 to a diagram object. 617 618 This is the standard way to doing it [main] 619 1) Creates the new structure needed for the diagram generation [load_diagram] 620 This defines self.vertexList and self.lineList which are the list of 621 respectively all the vertex and all the line include in the diagram. 622 Each line is associated to two vertex, so we have added new vertex 623 compare to the diagram object (base_objects.Diagram). The two vertex are 624 named begin/end and represent the line direction. at this stage all line 625 are going timelike. T-channel are going from particle 1 to particle 2 626 2) Associate to each vertex a level. [define_level] 627 The level represents the distance between the initial vertex and the 628 current vertex. This distance is define has the number of non T-channel 629 particles needed to connect this particles to a initial state starting 630 point. 631 3) Compute the position of each vertex [find_initial_vertex_position] 632 The x-coordinate will proportional to the level. The vertex at level=0. 633 will have x=0 coordinate (vertex associate with initial state particle) 634 The vertex with the highest level value should be at x=1. 635 636 If an external particles cann't be place at the border at the current 637 level. we will try to place it one level later, potentially up to last 638 level. A option can force to place all external particles at x=1. 639 640 the y-coordinate are chosen such that 641 - external particles try to have (y=0 or y=1) coordinates 642 (if not move those vertex to next level) 643 - other particles maximizes distances between themselves. 644 4) Solve Fermion-flow and (anti)particle type [self.solve_line_direction] 645 the way to solve the fermion-flow is basic and fail in general for 646 majorana fermion. The basic idea is "particles are going timelike". 647 This is sufficient in all cases but T-channel particles which are solve 648 separately.""" 649
650 - class FeynamDiagramError(Exception):
651 """Class for internal error."""
652
653 - def __init__(self, diagram, model, amplitude=False, opt=None):
654 """Store the information concerning this diagram. This routines didn't 655 perform any action at all. 656 diagram: The diagram object to draw 657 model: The model associate to the diagram 658 amplitude: tell if the diagram has already fixed the I/O state of the fermion 659 opt: A DrawingOpt instance with all options for drawing the diagram.""" 660 661 # Check if input are what we are expecting 662 assert isinstance(diagram, base_objects.Diagram), \ 663 'first argument should derivate from Diagram object' 664 assert isinstance(model, base_objects.Model), \ 665 'second argument should derivate from Model object' 666 667 668 self.diagram = diagram 669 self.model = model 670 self.amplitude = amplitude 671 672 if opt is None: 673 self.opt = DrawOption() 674 else: 675 assert isinstance(opt, DrawOption), 'third argument should derivates' + \ 676 ' from DrawOption object' 677 self.opt = opt 678 679 # Initialize other value to void. 680 self.vertexList = [] # List of vertex associate to the diagram 681 self.initial_vertex = [] # vertex associate to initial particles 682 self.lineList = [] # List of line present in the diagram 683 self.max_level = 0 684 685 #internal parameter 686 self._treated_legs = [] # List of leg, in the same order as lineList 687 self._available_legs = {} # List of line which can/should be reuse. 688 self._ext_distance_up = self.opt.external 689 self._ext_distance_down = self.opt.external
690 691
692 - def main(self):
693 """This routine will compute all the vertex position and line 694 orientation needed to draw the diagram.""" 695 696 # Define all the vertex/line 697 # Define self.vertexList,self.lineList 698 self.load_diagram(contract=self.opt.contract_non_propagating) 699 # Define the level of each vertex 700 self.define_level() 701 # Define position for each vertex 702 self.find_initial_vertex_position() 703 # Adjust some 'not beautifull' position 704 self.adjust_position() 705 # Flip the particle orientation such that fermion-flow is correct 706 self.solve_line_direction()
707 708 #fake vertex for end point particle use in load_diagram 709 fake_vertex = base_objects.Vertex({'id':0, 'legs':base_objects.LegList([])}) 710
711 - def load_diagram(self, contract=True):
712 """Define all the object for the Feynman Diagram Drawing (Vertex and 713 Line) following the data include in 'self.diagram' 714 'contract' defines if we contract to one point the non propagating line. 715 """ 716 717 for vertex in self.diagram.get('vertices'): 718 self.load_vertex(vertex) 719 720 # The last vertex is particular 721 last_vertex = self.vertexList[-1] 722 723 for line in last_vertex.lines: 724 self.deal_last_line(line) 725 726 if contract: 727 # Contract the non propagating particle and fuse vertex associated 728 self._fuse_non_propa_particule() 729 730 # External particles have only one vertex attach to the line. (by 731 #construction). So we will add a new vertex object in order that all 732 #line are associated to two vertex. Those additional vertex will be 733 #place, later, at the border of the square. 734 for line in self.lineList: 735 if line.end == 0 or line.start == 0: 736 # Create a new vertex. update the list, assign at the line. 737 vertex_point = VertexPoint(self.fake_vertex) 738 self.vertexList.append(vertex_point) 739 # If initial state particle, we will need to flip begin-end 740 if line.state== False: 741 if line.start: 742 line.inverse_begin_end() 743 line.def_begin_point(vertex_point) 744 vertex_point.def_level(0) 745 self.initial_vertex.append(vertex_point) 746 else: 747 if line.end: 748 line.inverse_begin_end() 749 line.def_end_point(vertex_point) 750 751 if len(self.initial_vertex) == 2: 752 if self.initial_vertex[0].lines[0].number == 2: 753 self.initial_vertex.reverse() 754 else: 755 # Remove wrongly define T-channel 756 self.remove_t_channel() 757 758 return
759
760 - def find_leg_id(self, leg, equal=0, end=0):
761 """Find the position of leg in self._treated_legs 762 763 if equal=0 returns the last position of number in the list 764 otherwise check that leg is the item in self._treated_legs 765 766 the two methods provides the same result if they provide a result. 767 But some times equal=0 mode provides result when equal=1 doesn't. 768 To my understanding equal=1 is suppose to be sufficient in all cases 769 but gg> 7x( g ) fails with using equal=1 only. 770 771 'end' removes the last 'end' element of the list, before looking at 772 the id in the list. (the list is not modify)""" 773 774 if equal: 775 return self.find_leg_id2(leg, end=end) 776 777 for i in range(len(self.lineList) - 1 - end, -1, -1): 778 if leg.get('number') == self.lineList[i].number: 779 return i 780 781 return None
782
783 - def find_leg_id2(self, leg, end=0):
784 """Find the position of leg in self._treated_legs. Use object equality 785 to find the position.""" 786 787 for i in range(len(self.lineList) - 1 - end, -1, -1): 788 if (self._treated_legs[i] is leg): 789 return i
790
791 - def find_leg_id3(self, gen_id):
792 """Find the position of leg in self._treated_legs but only if this b 793 belongs to an available particles""" 794 795 try: 796 return self._available_legs[gen_id] 797 except: 798 return None
799
800 - def load_vertex(self, vertex):
801 """1) Extend the vertex to a VertexPoint. 802 2) Add this vertex in vertexList of the diagram 803 3) Update vertex.lines list. (first update the leg into line if needed) 804 4) assign line.start[end] to this vertex. (in end if start is already 805 assigned to another vertex). the start-end will be flip later 806 if needed. 807 5) if the fermion flow is correctly set by the diagram (amplitude=True) 808 Then change the particles/anti-particles states accordingly. 809 """ 810 811 #1) Extend to a vertex point 812 vertex_point = VertexPoint(vertex) 813 814 #2) Add to the vertexList of the diagram 815 self.vertexList.append(vertex_point) 816 817 # Loop over the leg associate to the diagram 818 for i, leg in enumerate(vertex.get('legs')): 819 820 gen_id = leg.get('number') 821 # Search if leg exist: two case exist corresponding if it is the 822 #line of vertex or not. Corresponding to that change mode to find 823 #if the leg exist or not. 824 mg_id = self.find_leg_id3(gen_id) 825 826 #if i + 1 == len(vertex.get('legs')): 827 # # Find if leg is in self._treated_legs and returns the position 828 # #in that list 829 # mg_id = self.find_leg_id2(leg, len(self._treated_legs) - \ 830 # previous_len) 831 #else: 832 # # Find thelast item in self._treated_legs with same number and 833 # #returns the position in that list 834 # mg_id = self.find_leg_id(leg) 835 836 # Define-recover the line associate to this leg 837 if mg_id: 838 del self._available_legs[gen_id] 839 line = self.lineList[mg_id] 840 else: 841 line = self.load_leg(leg) 842 if i + 1 == len(vertex.get('legs')): 843 self._available_legs[gen_id] = len(self._treated_legs) - 1 844 845 # Associate the vertex to the line at the correct place 846 line.add_vertex(vertex_point) 847 848 # Change particule to anti-particule for last entry of vertex.lines 849 #doing this modification only if the vertex is the type 1 X....Z>1 850 #since in this case either the last particles will be a T-channel 851 #and will be resolve latter (so we don't care) or we have to flip 852 #particle to antiparticle. 853 if line.number == 1 == vertex.get('legs')[0].get('number'): 854 line.inverse_part_antipart() 855 elif self.amplitude and line.number == 1: 856 nb = [l.get('number') for l in vertex.get('legs')] 857 if nb.count(1) == 2: 858 line.inverse_part_antipart()
859
860 - def load_leg(self, leg):
861 """Extend the leg to Feynman line. Associate the line to the diagram. 862 """ 863 864 # Extend the leg to FeynmanLine Object 865 line = FeynmanLine(leg.get('id'), leg) 866 line.def_model(self.model) 867 868 # Assign line and leg to the diagram. Storing leg is done in order to be 869 #able to check if a leg was already treated or not. 870 self._treated_legs.append(leg) 871 self.lineList.append(line) 872 873 return line
874
875 - def deal_last_line(self, last_line):
876 """The line of the last vertex breaks the rules that line before 877 '>' exist previously and the one after don't. The last one can also 878 already exist and for the one before the '>' sometimes they arrive 879 with a second object which is equivalent to another one but not 880 the same object. discover those case and treat this properly.""" 881 882 # Check if the line has two vertex associate to it, if not correct. 883 if last_line.end == 0 or last_line.start == 0: 884 # Find the position of the line in self._treated_legs 885 id1 = self.find_leg_id(self._treated_legs[-1]) 886 # Find if they are a second call to this line 887 id2 = self.find_leg_id(self._treated_legs[-1], end=len(self._treated_legs) - id1) 888 if id2 is not None: 889 # Line is duplicate in linelist => remove this duplication 890 line = self.lineList[id2] 891 # Connect correctly the lines. The two lines are a continuation 892 #one of the other with a common vertex. We want to delete 893 #last_line. In consequence we replace in line the common vertex 894 #by the second vertex present in last_line, such that the new 895 #line is the sum of the two lines. 896 if last_line.start == 0: 897 if line.end == 0 : 898 line.def_end_point(last_line.end) 899 else: 900 line.def_begin_point(last_line.end) 901 # Remove last_line from the vertex 902 last_line.end.remove_line(last_line) 903 else: 904 if line.end == 0 : 905 line.def_end_point(last_line.start) 906 else: 907 line.def_begin_point(last_line.start) 908 # Remove last_line from the vertex 909 last_line.start.remove_line(last_line) 910 911 # Remove last_line 912 self.lineList.remove(last_line) 913 else: 914 return #this is an external line => everything is ok
915
916 - def _fuse_non_propa_particule(self):
917 """Fuse all the non propagating line 918 step: 919 1) find those line 920 2) fuse the vertex 921 3) remove one vertex from self.vertexList 922 4) remove the line/leg from self.lineList/self._treated_leg 923 """ 924 925 # Look for all line in backward mode in order to delete entry in the 926 #same time (as making th loop) without creating trouble 927 for i in range(len(self.lineList)).__reversed__(): 928 if self.lineList[i].get_info('propagating'): 929 continue 930 else: 931 line = self.lineList[i] 932 line.start.fuse_vertex(line.end, common_line=line) 933 self.vertexList.remove(line.end) 934 del self._treated_legs[i] 935 del self.lineList[i]
936
937 - def define_level(self):
938 """Assign to each vertex a level: 939 the level correspond to the number of visible particles and S-channel 940 needed in order to reach the initial particles vertex. 941 942 This is computing by search level by level starting at level 0. 943 """ 944 945 for vertex in self.initial_vertex: 946 self.def_next_level_from(vertex) #auto recursive operation
947 948
949 - def def_next_level_from(self, vertex):
950 """Define level for adjacent vertex. 951 If those vertex is already defined do nothing 952 Otherwise define as level+1 (at level 1 if T-channel) 953 954 This routine defines also self.max_level. 955 956 This routine is foreseen for an auto-recursive mode. So as soon as a 957 vertex have his level defined. We launch this routine for this vertex. 958 """ 959 960 level = vertex.level 961 for line in vertex.lines: 962 if line.end.level != -1: 963 continue 964 # Check if T-channel or not. Note that T-channel tag is wrongly 965 #define if only one particle in initial state. 966 if line.state == False: 967 # This is T vertex. => level is 1 968 line.end.def_level(1) 969 else: 970 # Define level 971 line.end.def_level(level + 1) 972 # Check for update in self.max_level 973 self.max_level = max(self.max_level, level + 1) 974 # Launch the recursion 975 self.def_next_level_from(line.end)
976 977
978 - def find_t_channel_vertex(self):
979 """Returns the vertex (T-vertex authorize) associate to level 1. 980 We start with the vertex associate to first entry of previous_level 981 and then following the T-line.""" 982 983 vertex_at_level = [] 984 try: 985 t_vertex = self.initial_vertex[-2] 986 except: 987 return [] #only one particle in initial state => no T-channel 988 989 while 1: 990 # search next vertex and the connection line leading to this vertex 991 t_vertex = self.find_next_t_channel_vertex(t_vertex) 992 993 #add it to the list 994 if t_vertex: 995 vertex_at_level.append(t_vertex) 996 else: 997 return vertex_at_level
998
999 - def find_next_t_channel_vertex(self, t_vertex):
1000 """Returns the next t_vertex. i.e. the vertex following t_vertex. t_line 1001 indicates the 'wrong' T-direction. This routines returns also the 'good' 1002 evolution direction (which will be the wrong one at the next step).""" 1003 1004 for line in t_vertex.lines: 1005 if line.state == False and line.start is t_vertex: 1006 return line.end
1007
1008 - def find_vertex_at_level(self, previous_level):
1009 """Returns a list of vertex such that all those vertex are one level 1010 after the level of vertexlist and sorted in such way that the list 1011 start with vertex connected with the first vertex of 'vertexlist' then 1012 those connected to the second and so on.""" 1013 1014 vertex_at_level = [] 1015 for vertex in previous_level: 1016 if vertex.is_external() and vertex.pos_y not in [0, 1]: 1017 # Move external vertex from one level to avoid external 1018 #particles finishing inside the square. 1019 vertex.def_level(vertex.level + 1) 1020 vertex_at_level.append(vertex) 1021 continue 1022 1023 for line in vertex.lines: 1024 if line.start is vertex and line.state == True: 1025 vertex_at_level.append(line.end) 1026 1027 return vertex_at_level
1028
1030 """Find a position to each vertex. All the vertex with the same level 1031 will have the same x coordinate. All external particles will be on the 1032 border of the square.""" 1033 1034 if len(self.initial_vertex) == 2: 1035 self.initial_vertex[0].def_position(0, 0) 1036 self.initial_vertex[1].def_position(0, 1) 1037 # Initial state are wrongly consider as outgoing-> solve: 1038 self.initial_vertex[0].lines[0].inverse_part_antipart() 1039 self.initial_vertex[1].lines[0].inverse_part_antipart() 1040 # Associate position to T-vertex 1041 t_vertex = self.find_vertex_position_tchannel() 1042 # Associatie position to level 2 and following (auto-recursive fct) 1043 self.find_vertex_position_at_level(t_vertex, 2) 1044 elif len(self.initial_vertex) == 1: 1045 #No T-Channel 1046 self.initial_vertex[0].def_position(0, 0.5) 1047 #initial state are wrongly consider as outgoing -> solve: 1048 init_line = self.initial_vertex[0].lines[0] 1049 init_line.inverse_part_antipart() 1050 # Associate position to level 1 1051 init_line.end.def_position(1 / self.max_level, 0.5) 1052 # Associatie position to level 2 and following (auto-recursive fct) 1053 self.find_vertex_position_at_level([init_line.end], 2) 1054 else: 1055 raise self.FeynamDiagramError, \ 1056 'only for one or two initial particles not %s' \ 1057 % (len(self.initial_vertex))
1058 1059
1061 """Finds the vertex position for level one, T channel are authorize""" 1062 1063 # Find the T-vertex in correct order 1064 t_vertex = self.find_t_channel_vertex() 1065 # Assign position at those vertex 1066 self.assign_pos(t_vertex, 1) 1067 return t_vertex
1068 1069
1070 - def find_vertex_position_at_level(self, vertexlist, level, auto=True):
1071 """Finds the vertex position for the particle at 'level' given the 1072 ordering at previous level given by the vertexlist. 1073 if auto equals True then pass in auto-recursive mode.""" 1074 1075 if level > self.max_level: 1076 return 1077 1078 # Find the order of vertex at next-level. if some external particle 1079 #are in vertexlist. They are replace in vertex_at_level. Such case 1080 #happen if the options forbids to an external particles to end at x!=1 1081 #coordinates or if it's not possible to put the vertex on the border. 1082 vertex_at_level = self.find_vertex_at_level(vertexlist) 1083 1084 if not vertex_at_level: 1085 return 1086 # Assign position to vertex_at_level. In order to deal with external 1087 #particles the vertex_at_level is modify. If an external vertex has 1088 #position on border it will be remove of vertex_at_level. 1089 self.assign_pos(vertex_at_level, level) 1090 1091 # Recursive mode 1092 if auto and vertex_at_level: 1093 self.find_vertex_position_at_level(vertex_at_level, level + 1)
1094 1095
1096 - def assign_pos(self, vertex_at_level, level, min=0, max=1):
1097 """Assign the position to each vertex of vertex_at_level. 1098 1099 The x-coordinate will the ratio of the current level with the maximum 1100 level of the diagram. 1101 1102 If the first_vertex of vertex_at_level is an outgoing particle. Put it 1103 at y=0 if possible (this could be prevented by min>0 or by drawing 1104 option). if you put it at y=0 delete the vertex of the list to avoid 1105 duplications. 1106 1107 Do the symmetric case for the last entry of vertex_at_level. 1108 1109 The y-value for the other point is computed such that the distance 1110 between two vertex of the list are the same. the distance between min 1111 (resp. max) and the first vertex is also equal but if min=0 (resp. 1112 max=1) then this distance counts half. 1113 1114 the option self.opt.external is used 1115 if equals 0, the external lines are authorizes to end only 1116 at the end of the diagram (in x=1 axis) so this will forbid 1117 to put any vertex at y=0-1 (except if x=1) 1118 if bigger than 0, minimal distance in before putting a external line 1119 on the border of the diagram. 1120 1121 1122 The computation of y is done in this way 1123 first compute the distance [dist] between two vertex and assign the point. 1124 begin_gap and end_gap are the ratio of the compute distance to put 1125 between min and first vertex. 1126 """ 1127 1128 if not vertex_at_level: 1129 return [] 1130 1131 if level > self.max_level: 1132 raise self.FeynamDiagramError, 'self.max_level not correctly ' + \ 1133 'assigned level %s is bigger than max_level %s' % \ 1134 (level, self.max_level) 1135 1136 1137 # At final level we should authorize min=0 and max=1 position 1138 if level == self.max_level: 1139 ext_dist_up = 1 1140 ext_dist_down = 1 1141 # Treat special 2 > 1 case 1142 if len(vertex_at_level) == 1 and min ==0 and max == 1: 1143 vertex_at_level[0].def_position(1, 0.5) 1144 return [] 1145 else: 1146 # else follow option 1147 ext_dist_up = self._ext_distance_up 1148 ext_dist_down = self._ext_distance_down 1149 # Set default gap in dist unity 1150 begin_gap, end_gap = 1, 1 1151 # Check the special case when min is 0 -> border 1152 if min == 0: 1153 if ext_dist_down and vertex_at_level[0].is_external(): 1154 line = vertex_at_level[0].lines[0] 1155 if line.end.level - line.start.level >= ext_dist_down: 1156 # Assign position at the border and update option 1157 self.define_vertex_at_border(vertex_at_level[0], level, 0) 1158 # Remove the vertex to avoid that it will pass to next level 1159 del vertex_at_level[0] 1160 # 1161 if not vertex_at_level: 1162 return [] 1163 else: 1164 begin_gap = 0.5 1165 else: 1166 begin_gap = 0.5 1167 1168 # Check the special case when max is 1 -> border 1169 if max == 1: 1170 if ext_dist_up and vertex_at_level[-1].is_external(): 1171 line = vertex_at_level[-1].lines[0] 1172 if line.end.level - line.start.level >= ext_dist_up: 1173 # Assign position at the border 1174 self.define_vertex_at_border(vertex_at_level[-1], level, 1) 1175 # Remove the vertex to avoid that it will pass to next level 1176 del vertex_at_level[-1] 1177 if not vertex_at_level: 1178 return [] 1179 else: 1180 end_gap = 0.5 1181 else: 1182 end_gap = 0.5 1183 1184 # Compute the distance between two vertex 1185 dist = (max - min) / (begin_gap + end_gap + len(vertex_at_level) - 1) 1186 1187 # Assign position to each vertex 1188 for i, vertex in enumerate(vertex_at_level): 1189 vertex.def_position(level / self.max_level, min + dist * \ 1190 (begin_gap + i)) 1191 1192 return vertex_at_level
1193
1194 - def define_vertex_at_border(self, vertex, level, pos_y):
1195 """Define the position of the vertex considering the distance required 1196 in the Drawing Options. Update the option if needed.""" 1197 1198 # find the minimal x distance and update this distance for the future 1199 if pos_y == 1: 1200 dist = self._ext_distance_up 1201 self._ext_distance_up += self.opt.add_gap 1202 else: 1203 dist = self._ext_distance_down 1204 self._ext_distance_down += self.opt.add_gap 1205 1206 # Find the position and switch integer and not integer case 1207 if dist % 1: 1208 # Check that we have to move forward the line 1209 if level < self.max_level: 1210 pos_x = (level - 1 + (dist % 1)) / self.max_level 1211 elif (1 - vertex.lines[0].start.pos_x) * self.max_level > dist: 1212 pos_x = (level - 1 + (dist % 1)) / self.max_level 1213 else: 1214 pos_x = 1 1215 else: 1216 pos_x = level / self.max_level 1217 1218 vertex.def_position(pos_x, pos_y)
1219 1220
1221 - def remove_t_channel(self):
1222 """Removes all T-channel in a diagram and convert those in S-channel. 1223 This occur for 1>X diagram where T-channel are wrongly define.""" 1224 1225 for line in self.lineList: 1226 if line.state == False: 1227 line.state = True
1228 1229
1230 - def solve_line_direction(self):
1231 """Computes the directions of the lines of the diagrams. 1232 first use simple rules as particles move in time directions (to right). 1233 - define_line_orientation -. Then flip T-channel particles to 1234 correct fermion flow in T-channel. Majorana case not deal correctly 1235 at this stage.""" 1236 1237 # Use the basic rules. Assigns correctly but for T-channel 1238 # This methods fails if the creation of wavefunctions modify the 1239 # particle content. 1240 1241 for line in self.lineList: 1242 if line.state == True: 1243 line.define_line_orientation() 1244 1245 # The define line orientation use level information and in consequence 1246 #fails on T-Channel. So in consequence we still have to fix T-channel 1247 #line. 1248 1249 # Make a loop on T-channel particles 1250 try: 1251 t_vertex = self.initial_vertex[-2] 1252 except: 1253 return # No T-channel for 1 > X diagram 1254 1255 t_vertex = self.find_next_t_channel_vertex(t_vertex) 1256 self.initial_vertex[0].lines[0].define_line_orientation() 1257 1258 t_old = self.initial_vertex[0].lines[0] 1259 while 1: 1260 # Look the total flow of the vertex the other 1261 ver_flow = 0 # Current flow status for the vertex 1262 t_next = None # Next T-channel line. with unfix fermion flow 1263 for line in t_vertex.lines: 1264 1265 # Identify the next T-channel particles 1266 if line.state == False and t_old is not line and \ 1267 line.start is t_vertex: 1268 t_next = line 1269 1270 #import sys 1271 #sys.exit() 1272 1273 # If not fermion, no update of the fermion flow 1274 if not line.is_fermion(): 1275 continue 1276 1277 # Update the fermion_flow 1278 if (line.start is t_vertex): 1279 ver_flow += 1 1280 elif line.end is t_vertex: 1281 ver_flow -= 1 1282 1283 # End of the loop on the line of the vertex. 1284 if t_next: 1285 t_old = t_next 1286 t_vertex = t_next.end 1287 # Check the vertex_flow=0, we were lucky, else correct the flow. 1288 if ver_flow: 1289 t_next.inverse_begin_end() 1290 else: 1291 if ver_flow: 1292 self.initial_vertex[1].lines[0].inverse_begin_end() 1293 return
1294 1295
1296 - def adjust_position(self):
1297 """Modify the position of some particles in order to improve the final 1298 diagram look. This routines use one option 1299 1) max_size which forbids external particles to be longer than max_size. 1300 This is in level unit. If a line is too long we contract it to 1301 max_size preserving the orientation. 1302 2) external indicating the minimal x-gap for an external line. This 1303 constraints is already take into account in previous stage. But that 1304 stage cann't do non integer gap. So this routines correct this.""" 1305 1306 finalsize = self.opt.max_size 1307 1308 # Check if we need to do something 1309 if not finalsize: 1310 return 1311 1312 # Select all external line 1313 for line in self.lineList: 1314 if line.is_external(): 1315 # Check the size of final particles to restrict to the max_size 1316 #constraints. 1317 if line.state == False or not line.is_external(): 1318 continue 1319 size = line.get_length() * self.max_level 1320 if size > finalsize: 1321 ratio = finalsize / size 1322 new_x = line.start.pos_x + ratio * (line.end.pos_x - 1323 line.start.pos_x) 1324 new_y = line.start.pos_y + ratio * (line.end.pos_y - 1325 line.start.pos_y) 1326 line.end.def_position(new_x, new_y)
1327
1328 - def _debug_load_diagram(self):
1329 """Return a string to check to conversion of format for the diagram. 1330 1331 This is a debug function.""" 1332 1333 text = 'line content :\n' 1334 for i in range(0, len(self.lineList)): 1335 line = self.lineList[i] 1336 try: 1337 begin = self.vertexList.index(line.start) 1338 except: 1339 begin = -1 1340 try: 1341 end = self.vertexList.index(line.end) 1342 except: 1343 end = -1 1344 try: 1345 external = line.is_external() 1346 except: 1347 external = '?' 1348 text += 'pos, %s ,id: %s, number: %s, external: %s, \ 1349 begin at %s, end at %s \n' % (i, line.pid, \ 1350 line.number, external, begin, end) 1351 text += 'vertex content : \n' 1352 for i in range(0, len(self.vertexList)): 1353 vertex = self.vertexList[i] 1354 text += 'pos, %s, id: %s, external: %s, uid: %s ' % \ 1355 (i, vertex.id, vertex.is_external(), \ 1356 vertex.get_uid()) 1357 text += 'line: ' + ','.join([str(self.lineList.index(line)) \ 1358 for line in vertex.lines]) + '\n' 1359 return text
1360 1361
1362 - def _debug_level(self, text=1):
1363 """Returns a string to check the level of each vertex. 1364 1365 This is a debug function.""" 1366 1367 for line in self.lineList: 1368 if line.start.level > line.end.level: 1369 if text == 0: 1370 raise self.FeynamDiagramError('invalid level order') 1371 1372 1373 text = '' 1374 for vertex in self.vertexList: 1375 text += 'line : ' 1376 text += ','.join([str(line['id']) for line in vertex.legs]) 1377 text += ' level : ' + str(vertex.level) 1378 text += '\n' 1379 if text: 1380 return text
1381
1382 - def _debug_position(self):
1383 """Returns a string to check the position of each vertex. 1384 1385 This is a debug function.""" 1386 1387 text = '' 1388 for vertex in self.vertexList: 1389 text += 'line : ' 1390 text += ','.join([str(line.id) for line in vertex.lines]) 1391 text += ' level : ' + str(vertex.level) 1392 text += ' pos : ' + str((vertex.pos_x, vertex.pos_y)) 1393 text += '\n' 1394 return text
1395
1396 - def _debug_has_intersection(self):
1397 """Returns if some line cross are crossing each other. 1398 1399 This is a debug Function and is used for the test routine.""" 1400 1401 #loop on all pair combination 1402 for i, line in enumerate(self.lineList): 1403 for j in range(i + 1, len(self.lineList)): 1404 line2 = self.lineList[j] 1405 #check if they are a unvalid intersection 1406 if line.has_intersection(line2): 1407 print 'intersection for %s %s' % (i, j) 1408 print 'line', i, '(', line.start.pos_x, line.start.pos_y, \ 1409 ') (', line.end.pos_x, line.end.pos_y, ')' 1410 print 'line', j, '(', line2.start.pos_x, line2.start.pos_y, \ 1411 ') (', line2.end.pos_x, line2.end.pos_y, ')' 1412 return True 1413 return False
1414
1415 - def __eq__(self, other):
1416 """Check if two diagrams are equivalent. (same structure-same particle) 1417 1418 This function is not used for the moment. The initial purpose was the 1419 avoid duplication of identical diagram in the output (these could happen 1420 if we contract non propagating line). But the number of such comparaison 1421 rise as the number of diagram square such that the total time needed for 1422 this feature was consider as too (time-)expansive.""" 1423 1424 # Check basic globals (this is done to fastenize the check 1425 if self.max_level != other.max_level: 1426 return False 1427 elif len(self.lineList) != len(other.lineList): 1428 return False 1429 1430 # Then compare vertex by vertex. As we didn't want to use order 1431 #information, we first select two vertex with the same position and then 1432 #compare then. 1433 other_pos = [(vertex.pos_x, vertex.pos_y) for vertex in other.vertexList] 1434 for vertex_self in self.vertexList: 1435 try: 1436 i = other_pos.index((vertex_self.pos_x, vertex_self.pos_y)) 1437 except: 1438 # This vertex doesn't have equivalent => They are different. 1439 return False 1440 else: 1441 vertex_other = other.vertexList[i] 1442 1443 # So now we have the 'vertex_self' and 'vertex_other' which are 1444 #vertex at the same position. Now we check if they have the same 1445 #line content. 1446 if not vertex_self.has_the_same_line_content(vertex_other): 1447 return False 1448 1449 # All the vertex and the associate line are equivalent. So the two 1450 #diagrams are consider as identical. 1451 return True
1452 1453 1454 #=============================================================================== 1455 # FeynmanDiagramHorizontal 1456 #===============================================================================
1457 -class FeynmanDiagramHorizontal(FeynmanDiagram):
1458 """Object to compute the position of the different Vertex and Line associate 1459 to a diagram object. This routines is quite similar to FeynmanDiagram. 1460 The only differences concerns the rules for the y-coordinate of each vertex. 1461 1462 In case of vertex with one and only one S-channel going to the next level. 1463 Then force this line to be horizontal. This creates sub-interval where other 1464 vertex can be place following the same rule as before (equal distance 1465 between vertex) but this time sub-interval by sub-interval.""" 1466
1467 - def find_vertex_position_at_level(self, vertexlist, level, auto=True):
1468 """Finds the vertex position for the particle at 'level' given the 1469 ordering at previous level given by the vertexlist. 1470 if auto=True pass in autorecursive mode. 1471 1472 Compare to the function of FeynmanDiagram, this check the number of 1473 S-channel particles going out of each vertex. If the result is one: 1474 1) Fix the associate vertex at the same y as the original vertex 1475 -> horizontal line 1476 2) Assign non fix vertex below the fix one in the current interval. 1477 3) Continue to the next vertex.""" 1478 1479 # If only final-initial particles no S-channel to fix => old routine 1480 if level == 1 or level == self.max_level : 1481 FeynmanDiagram.find_vertex_position_at_level(self, vertexlist, \ 1482 level, auto) 1483 return 1484 elif level > self.max_level: 1485 return 1486 1487 # Find the order of vertex at next-level. if some external particle 1488 #are in vertexlist. They are replace in vertex_at_level. Such case 1489 #happen if the options forbids to an external particles to end at x!=1 1490 #coordinates or if it's not possible to put the vertex on the border 1491 #of a previous level. 1492 vertex_at_level = self.find_vertex_at_level(vertexlist) 1493 vertex_at_level2 = [] # Will be the same list as vertex_at level but 1494 #with a potential different order and whitout some 1495 #(already fixed) external particles 1496 1497 min_pos = 0 # Starting of the current interval 1498 list_unforce_vertex = [] # Vertex which fit in this interval 1499 1500 # Loop at level-1 in order to check the number of S-channel going from 1501 #level-1 to level. 1502 for vertex in vertexlist: 1503 1504 s_vertex = [] # List of s vertex going to level 1505 ext_vertex = [] # List of external particle vertex 1506 v_pos = vertex.pos_y 1507 1508 # Assign the vertex linked to current vertex in the associate 1509 #category (S-channel or external) 1510 for line in vertex.lines: 1511 1512 # Find the vertex 1513 if line.end in vertex_at_level: 1514 new_vertex = line.end 1515 elif line.start in vertex_at_level: 1516 new_vertex = line.start 1517 else: 1518 # The line goes to level-2 1519 continue 1520 1521 # Assign in the correct list (external/s-channel) 1522 if line.is_external(): 1523 ext_vertex.append(new_vertex) 1524 else: 1525 s_vertex.append(new_vertex) 1526 1527 # Check the number of S-channel 1528 if len(s_vertex) != 1: 1529 # Udate the list_unforce_vertex. The complex way to do is a 1530 #naive attempt of improving the look of the diagram. 1531 if len(ext_vertex) <= 1: 1532 if vertex.pos_y >= 0.5: 1533 list_unforce_vertex += (s_vertex + ext_vertex) 1534 else: 1535 list_unforce_vertex += (ext_vertex + s_vertex) 1536 else: 1537 list_unforce_vertex += ext_vertex[:-1] + s_vertex + \ 1538 ext_vertex[-1:] 1539 continue 1540 1541 # Only One S-Channel => force to be horizontal 1542 force_vertex = s_vertex[0] 1543 force_vertex.def_position(level / self.max_level, v_pos) 1544 1545 list_unforce_vertex += ext_vertex 1546 1547 # Assign position to unforce list with some naive try of improvement 1548 if (len(ext_vertex) == 1 and v_pos >= 0.5) or len(ext_vertex) > 1: 1549 vertex_at_level2 += self.assign_pos(list_unforce_vertex[:-1], \ 1550 level, min_pos, v_pos) 1551 1552 list_unforce_vertex = [list_unforce_vertex[-1]] 1553 else: 1554 vertex_at_level2 += self.assign_pos(list_unforce_vertex, level, \ 1555 min_pos, v_pos) 1556 list_unforce_vertex = [] 1557 1558 # Update value for the next interval 1559 min_pos = v_pos 1560 vertex_at_level2.append(force_vertex) 1561 1562 # End of the loop assign the position of unforce vertex remaining 1563 if list_unforce_vertex: 1564 vertex_at_level2 += self.assign_pos(list_unforce_vertex, level, \ 1565 min_pos, 1) 1566 1567 if auto and vertex_at_level2: 1568 self.find_vertex_position_at_level(vertex_at_level2, level + 1)
1569 1570 #=============================================================================== 1571 # DiagramDrawer 1572 #===============================================================================
1573 -class DiagramDrawer(object):
1574 """In principle ALL routines representing diagram in ANY format SHOULD 1575 derive from this class. 1576 1577 This is a (nearly empty) frameworks to draw a diagram in any type format 1578 1579 This frameworks defines in particular 1580 - function to convert the input diagram (create by the generation step) 1581 in the correct object. [convert_diagram] 1582 - main loop to draw a diagram in a line-by-line method 1583 [draw - draw_diagram - draw_line] 1584 - name of routine (routine are empty) in order to fit with the framework 1585 [ associate_name - associate_number - draw_straight ] 1586 - some basic definition of routines 1587 [conclude - initialize] 1588 1589 This framework is base on the idea that we can create the diagram line after 1590 line. Indeed all line object (FeynmanLine) contains the full information 1591 needed to be drawed independently of the rest of the diagram. 1592 1593 In order to create a class with this framework you should start to write the 1594 draw_straight, draw_curly, ... method which are called by the framework. 1595 1596 If you want to write a file, you can store his content in self.text variable 1597 the routine conclude will then automatically write the file. 1598 1599 The main routine to draw a diagram is 'draw' which call 1600 1) initialize: setup things for the diagram (usually open a file). 1601 2) convert_diagram : Update the diagram in the correct format if needed. 1602 3) draw_diagram : Build the diagram line after line. 1603 4) conclude : finish the operation. 1604 """ 1605
1606 - class DrawDiagramError(Exception):
1607 """Standard error for error occuring to create output of a Diagram."""
1608
1609 - def __init__(self, diagram=None, filename=None, model=None, amplitude=None, \ 1610 opt=None):
1611 """Define basic variables and store some global information. 1612 All argument are optional: 1613 diagram : is the object to 'diagram' should inherit from either 1614 base_objects.Diagram or drawing_lib.FeynmanDiagram. 1615 filename: file's name of the file to write. 1616 model: model associate to the diagram. In principle use only if diagram 1617 inherit from base_objects.Diagram (for conversion). 1618 amplitude: amplitude associates to the diagram. NOT USE for the moment. 1619 In future you could pass the amplitude associate to the object in 1620 order to adjust fermion flow in case of Majorana fermion. 1621 opt: should be a valid DrawOption object.""" 1622 1623 # Check the parameter value 1624 #No need to test Diagram class, it will be tested before using it anyway 1625 try: 1626 assert(not model or isinstance(model, base_objects.Model)) 1627 assert(not filename or isinstance(filename, basestring)) 1628 except AssertionError: 1629 raise self.DrawDiagramError('No valid model provide to convert ' + \ 1630 'diagram in appropriate format') 1631 1632 assert opt is None or isinstance(opt, DrawOption) , \ 1633 'The Option to draw the diagram are in a invalid format' 1634 1635 # A Test of the Amplitude should be added when this one will be 1636 #use. 1637 1638 # Store the parameter in the object variable 1639 self.diagram = diagram 1640 self.filename = filename 1641 self.model = model # use for automatic conversion of graph 1642 self.amplitude = amplitude # will be use for conversion of graph 1643 self.opt = opt 1644 1645 # Set variable for storing text 1646 self.text = '' 1647 # Do we have to write a file? -> store in self.file 1648 if file: 1649 self.file = True # Note that this variable will be overwritten. THis 1650 #will be the object file. [initialize] 1651 else: 1652 self.file = False
1653
1654 - def draw(self, opt=None):
1655 """Main routine to draw a single diagram. 1656 opt is DrawOption object use for the conversion of the 1657 base_objects.Diagram in one of the Diagram object.""" 1658 1659 # Check if we need to upgrade the diagram. 1660 self.convert_diagram(amplitude=self.amplitude, opt=opt) 1661 # Initialize some variable before starting to draw the diagram 1662 # This is just for frameworks capabilities (default: open file in 1663 #write mode if a filename was provide. 1664 self.initialize() 1665 # Call the instruction to draw the diagram line by line. 1666 self.draw_diagram(self.diagram) 1667 # Finish the creation of the file/object (default: write object if a 1668 #filename was provide). 1669 self.conclude()
1670 1671
1672 - def convert_diagram(self, diagram=None, model=None, amplitude=None, \ 1673 opt=None):
1674 """If diagram is a basic diagram (inherit from base_objects.Diagram) 1675 convert him to a FeynmanDiagram one. 'opt' keeps track of possible 1676 option of drawing. 'amplitude' is not use for the moment. But, later, 1677 if defined will authorize to adjust the fermion-flow of Majorana 1678 particles. opt is a DrawOption object containing all option on the way 1679 to draw the diagram (see this class for more details) 1680 1681 1682 This is the list of recognize options: 1683 external [True] : authorizes external particles to finish on 1684 horizontal limit of the square 1685 horizontal [True]: if on true use FeynmanDiagramHorizontal to 1686 convert the diagram. otherwise use FeynmanDiagram (Horizontal 1687 forces S-channel to be horizontal) 1688 non_propagating [True] : removes the non propagating particles 1689 present in the diagram.""" 1690 1691 if diagram is None: 1692 diagram = self.diagram 1693 1694 #if already a valid diagram. nothing to do 1695 if isinstance(diagram, FeynmanDiagram): 1696 return 1697 1698 # assign default for model and check validity (if not default) 1699 if model is None: 1700 model = self.model 1701 elif not isinstance(model, base_objects.Model): 1702 raise self.DrawDiagramError('No valid model provide to convert ' + \ 1703 'diagram in appropriate format') 1704 1705 # Test on Amplitude should be enter here, when we will use this 1706 #information 1707 if opt is None: 1708 if self.opt: 1709 opt = self.opt 1710 else: 1711 opt = DrawOption() 1712 elif not isinstance(opt, DrawOption): 1713 raise self.DrawDiagramError('The Option to draw the diagram are' + \ 1714 ' in a invalid format') 1715 1716 # Upgrade diagram to FeynmanDiagram or FeynmanDiagramHorizontal 1717 #following option choice 1718 if opt.horizontal: 1719 diagram = FeynmanDiagramHorizontal(diagram, model, \ 1720 amplitude=amplitude, opt=opt) 1721 else: 1722 diagram = FeynmanDiagram(diagram, model, \ 1723 amplitude=amplitude, opt=opt) 1724 1725 # Find the position of all vertex and all line orientation 1726 diagram.main() 1727 1728 # Store-return information 1729 self.diagram = diagram 1730 return diagram
1731
1732 - def initialize(self):
1733 """Initialization of object-file before starting in order to draw the 1734 diagram correctly. By default, we just check if we are in writing mode. 1735 And open the output file if we are.""" 1736 1737 # self.file is set on True/False in __init__. This defines if a filename 1738 #was provide in the __init__ step. 1739 if self.file: 1740 self.file = open(self.filename, 'w')
1741 1742
1743 - def draw_diagram(self, diagram='', number=0):
1744 """Building the diagram Line after Line. 1745 This is the key routine of 'draw'.""" 1746 1747 for vertex in self.diagram.vertexList: 1748 self.draw_vertex(vertex) 1749 1750 for line in self.diagram.lineList: 1751 self.draw_line(line) 1752 1753 # Finalize information related to the graph. First, associate a diagram 1754 #position to the diagram representation. 1755 self.put_diagram_number(number) 1756 1757 # Then If a file exist write the text in it 1758 if self.file: 1759 self.file.writelines(self.text) 1760 self.text = ""
1761
1762 - def conclude(self):
1763 """Final operation of the draw method. By default, this end to write the 1764 1765 file (if this one exist) 1766 """ 1767 1768 # self.file is set on True/False in __init__. If it is on True 1769 #the Initialize method change it in a file object 1770 if self.file: 1771 self.file.writelines(self.text) 1772 self.file.close() 1773 return
1774
1775 - def draw_line(self, line):
1776 """Draw the line information. 1777 First, call the method associate the line type [draw_XXXXXX] 1778 Then finalize line representation by adding his name and, if it's an 1779 external particle, the MadGraph number associate to it.""" 1780 1781 # Find the type line of the particle [straight, wavy, ...] 1782 line_type = line.get_info('line') 1783 # Call the routine associate to this type [self.draw_straight, ...] 1784 if hasattr(self, 'draw_' + line_type): 1785 getattr(self, 'draw_' + line_type)(line) 1786 else: 1787 self.draw_straight(line) 1788 1789 # Finalize the line representation with adding the name of the particle 1790 name = line.get_name() 1791 self.associate_name(line, name) 1792 # And associate the MadGraph Number if it is an external particle 1793 if line.is_external(): 1794 number = line.number 1795 self.associate_number(line, number)
1796
1797 - def draw_vertex(self, vertex):
1798 """default vertex style""" 1799 pass
1800 1801
1802 - def draw_straight(self, line):
1803 """Example of routine for drawing the line 'line' in a specific format. 1804 straight is an example and can be replace by other type of line as 1805 dashed, wavy, curly, ...""" 1806 1807 raise self.DrawDiagramError, 'DrawDiagram.draw_straight should be ' + \ 1808 'overwritten by Inherited Class'
1809
1810 - def associate_name(self, line, name):
1811 """Method to associate a name to a the given line. 1812 The default action of this framework doesn't do anything""" 1813 pass
1814 1815
1816 - def associate_number(self, line, number):
1817 """Method to associate a number to 'line'. By default this method is 1818 call only for external particles and the number is the MadGraph number 1819 associate to the particle. The default routine doesn't do anything""" 1820 pass
1821
1822 -class DrawOption(object):
1823 """Dealing with the different option of the drawing method. 1824 This is the list of recognize attributes: 1825 horizontal [False]: force S-channel to be horizontal 1826 external [0]: authorizes external particles to end 1827 at top or bottom of diagram. If bigger than zero 1828 this tune the length of those line. 1829 add_gap [0]: make external rising after each positioning. 1830 max_size [0]: this forbids external line bigger than 1831 max_size. 1832 non_propagating [True]:contracts non propagating lines""" 1833
1834 - class DrawingOptionError(Exception):
1835 """Error raising if an invalid entry is set in a option."""
1836
1837 - def __init__(self, opt=''):
1838 """Fullfill option with standard value.""" 1839 1840 #define default 1841 self.external = 0 1842 self.add_gap = 0 1843 self.horizontal = False 1844 self.max_size = 1.5 1845 self.contract_non_propagating = True 1846 1847 if isinstance(opt, dict): 1848 for key, value in opt.items(): 1849 self.set(key, value) 1850 else: 1851 for value in ['external','add_gap','horizontal','max_size', 1852 'contract_non_propagating']: 1853 if hasattr(opt, value): 1854 self.set(value, getattr(opt, value))
1855
1856 - def set(self, key, value):
1857 """Check and attribute the given value.""" 1858 1859 if key in ['horizontal', 'contract_non_propagating']: 1860 value = self.pass_to_logical(value) 1861 setattr(self, key, value) 1862 elif(key in ['external', 'max_size', 'add_gap']): 1863 try: 1864 value = self.pass_to_number(value) 1865 except: 1866 raise self.DrawingOptionError('%s is not a numerical when %s \ 1867 requires one' % (value, key)) 1868 setattr(self, key, value) 1869 1870 else: 1871 raise self.DrawingOptionError('%s is not a valid property for \ 1872 drawing object' % key)
1873
1874 - def pass_to_logical(self, value):
1875 """convert the value in a logical""" 1876 1877 if value in [0, False, '0', 'False', 'false']: 1878 return False 1879 else: 1880 return True
1881
1882 - def pass_to_number(self, value):
1883 """Convert the value in a number""" 1884 1885 return float(value)
1886