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

Source Code for Module madgraph.core.drawing

   1  ################################################################################ 
   2  # 
   3  # Copyright (c) 2011 The MadGraph5_aMC@NLO Development team and Contributors 
   4  # 
   5  # This file is a part of the MadGraph5_aMC@NLO 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 MadGraph5_aMC@NLO license which should accompany this  
  10  # distribution. 
  11  # 
  12  # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 
  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  # FeynmanLine 
  53  #=============================================================================== 
54 -class FeynmanLine(object):
55 """All the information about a line in a Feynman diagram 56 i.e. begin-end/type/tag.""" 57
58 - class FeynmanLineError(Exception):
59 """Exception raised if an error occurs in the definition 60 or the execution of a Feynam_line."""
61
62 - def __init__(self, init_dict={}):
63 """Initialize the FeynmanLine content.""" 64 65 # Add this attribute by default to have some tests with a hard-coded 66 # dictionary for initialization passing. 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
73 - def __str__(self):
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 # def is_valid_prop(self, name): 77 # """Check if a given property name is valid.""" 78 # 79 # if name == 'pid': 80 # return True 81 # else: 82 # return super(FeynmanLine, self).is_valid_prop(name) 83
84 - def def_model(self, model):
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
93 - def def_begin_point(self, vertex):
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
103 - def def_end_point(self, vertex):
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
113 - def add_vertex(self, vertex):
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 # Look if we already resolve this problem 132 if self.begin: 133 self.def_end_point(vertex) 134 elif self.end: 135 self.def_begin_point(vertex) 136 #If not: resolve it. Treat external particle separately 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
144 - def define_line_orientation(self):
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
152 - def inverse_pid_for_type(self, inversetype='straight'):
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
160 - def inverse_part_antipart(self):
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
167 - def inverse_begin_end(self):
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
173 - def get_info(self, name):
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 # particle is self anti particle 208 return self.model.get_particle(-1 * pid).get(name)
209
210 - def get_length(self):
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
217 - def is_fermion(self):
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
224 - def is_external(self):
225 """Check if this line represent an external particles or not.""" 226 227 return self.end.is_external() or self.begin.is_external()
228
229 - def __eq__(self, other):
230 """Define that two line are equal when they have the same pointer""" 231 232 return self is other
233
234 - def __ne__(self, other):
235 """Define that two line are different when they have different 236 pointer.""" 237 238 return self is not other
239 240 241 # Debugging Routines linked to FeynmanLine --------------------------------- 242
243 - def has_intersection(self, line):
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
255 - def _has_intersection(self, line):
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 #tolerance for equality of two number 262 sys_error = 1e-7 263 264 # Find the x-range where both line are defined 265 min, max = self._domain_intersection(line) 266 267 # No x-value where both line are defined => No possible intersection 268 if min == None: 269 return False 270 271 # Only one x value is common for both line 272 if min == max : 273 # Check if self is normal line (not vertical) 274 if abs(self.begin.pos_x - self.end.pos_x) > sys_error: 275 # Check if line is normal line (not vertical) 276 if abs(line.begin.pos_x - line.end.pos_x) > sys_error: 277 # No vertical line => one vertex in common 278 return False 279 # line is vertical but not self: 280 return self._intersection_with_vertical_line(line) 281 282 # Check if line is not vertical) 283 elif (abs(line.begin.pos_x - line.end.pos_x) > sys_error): 284 # self is vertical but not line 285 return line._intersection_with_vertical_line(self) 286 287 # both vertical case 288 else: 289 # Find the y-range where both line are defined 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 # No vertical line -> resolve angular coefficient 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 # Check if the line are parallel 311 if abs(coef1 - coef2) < sys_error: 312 # Check if one point in common in the domain 313 if abs(line._has_ordinate(min) - self._has_ordinate(min)) < \ 314 sys_error: 315 return True 316 else: 317 return False 318 319 # Intersecting line -> find point of intersection (commonX, commonY) 320 commonX = (yS0 - yL0 - coef1 * xS0 + coef2 * xL0) / (coef2 - coef1) 321 322 #check if the intersection is in the x-domain 323 if (commonX >= min) == (commonX >= max): 324 return False 325 326 commonY = self._has_ordinate(commonX) 327 328 #check if intersection is a common vertex 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
337 - def is_end_point(self, x, y):
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 #authorize error machine 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 # Check consistency 363 self.check_position_exist() 364 line.check_position_exist() 365 366 # Launch routine 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 #find domain for each line 377 min_self, max_self = self.border_on_axis(axis) 378 min_line, max_line = line.border_on_axis(axis) 379 380 #find intersection 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
388 - def border_on_axis(self, axis='x'):
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
398 - def _intersection_with_vertical_line(self, line):
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 # Find the y coordinate for the x-value corresponding to line x-position 405 y_self = self._has_ordinate(line.begin.pos_x) 406 407 # Find the y range for line. This is done in order to check that the 408 #intersection point is not a common vertex 409 ymin, ymax = line.border_on_axis('y') 410 411 # Search intersection status 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
422 - def check_position_exist(self):
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
435 - def has_ordinate(self, x):
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
457 - def _has_ordinate(self, x):
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 #calculate the angular coefficient 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) #x1 always diff of x0 470 471 472 ordinate_fct = lambda X: y_0 + alpha * (X - x_0) 473 return ordinate_fct(x)
474 475 476 477 #=============================================================================== 478 # VertexPoint 479 #===============================================================================
480 -class VertexPoint(object):
481 """Extension of the class Vertex in order to store the information 482 linked to the display of a FeynmanDiagram, as position 483 """ 484
485 - class VertexPointError(Exception):
486 """Exception raised if an error occurs in the definition 487 or the execution of a VertexPoint."""
488 489
490 - def __init__(self, vertex):
491 """Update a vertex to a VertexPoint with additional information about 492 positioning and link with other vertex/line of the diagram.""" 493 494 # Check the validity of the parameter should be Vertex 495 assert(isinstance(vertex, base_objects.Vertex)) 496 497 # Copy data and add new entry 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
505 - def def_position(self, x, y):
506 """-Re-Define the position of the vertex in a square [0, 1]^2""" 507 508 # Coordinate should b 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
516 - def fuse_vertex(self, vertex, common_line=''):
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 # Remove common line. They are shrink to a single point 525 if line is common_line: 526 self.lines.remove(line) 527 continue 528 529 # Re-define the begin-end vertex of the line to point on this vertex 530 #and not on the old one. self.lines is automatically updated. 531 if line.begin is vertex: 532 line.def_begin_point(self) 533 else: 534 line.def_end_point(self) 535 return
536 537
538 - def add_line(self, line):
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
551 - def remove_line(self, line_to_del):
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 # Find the first item in the list and remove it. note that we cann't use 561 #standard delete as remove because it's use '==' and not 'is'. 562 for i, line in enumerate(self.lines): 563 if line is line_to_del: 564 del self.lines[i] 565 return # only one data to remove! 566 567 raise self.VertexPointError, 'trying to remove in a ' + \ 568 'Vertex_Point a non present Feynman_Line'
569 570
571 - def def_level(self, level):
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
581 - def is_external(self):
582 """Check if this vertex is external , i.e is related to a single 583 (external) particles.""" 584 585 #the termination has only one line. 586 if len(self.lines) == 1: 587 return True 588 else: 589 return False
590
591 - def has_the_same_line_content(self, other):
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 # Check the number of line 599 if len(self.lines) != len(other.lines): 600 return False 601 602 # Store the information of the pid content of the vertex other 603 other_line_pid = [line.id for line in other.lines] 604 # Sort the information of the number content for external particle 605 other_line_number = [line.number for line in other.lines if \ 606 line.is_external()] 607 608 # Now look at the self vertex and destroy the information store in the 609 #two above variable. If an error raise, this means that the content is 610 #not the same. 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 # The lines have all their equivalent, so returns True 623 return True
624
625 - def get_uid(self):
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
634 - def __eq__(self, other):
635 """Define equality with pointeur equality.""" 636 637 return self is other
638 639 #=============================================================================== 640 # FeynmanDiagram 641 #===============================================================================
642 -class FeynmanDiagram(object):
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
678 - class FeynamDiagramError(Exception):
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 # Check if input are what we are expecting 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 # Initialize other value to void. 708 self.vertexList = [] # List of vertex associate to the diagram 709 self.initial_vertex = [] # vertex associate to initial particles 710 self.lineList = [] # List of line present in the diagram 711 self.min_level = 0 712 self.max_level = 1 713 714 #internal parameter 715 self._treated_legs = [] # List of leg, in the same order as lineList 716 self._available_legs = {} # List of line which can/should be reuse. 717 self._ext_distance_up = self.opt.external 718 self._ext_distance_down = self.opt.external
719 720
721 - def main(self):
722 """This routine will compute all the vertex position and line 723 orientation needed to draw the diagram.""" 724 725 # Define all the vertex/line 726 # Define self.vertexList,self.lineList 727 self.load_diagram(contract=self.opt.contract_non_propagating) 728 #misc.sprint(self._debug_load_diagram()) 729 # Define the level of each vertex 730 self.define_level() 731 #misc.sprint(self._debug_level()) 732 self._debug_level() 733 # Define position for each vertex 734 self.find_initial_vertex_position() 735 #misc.sprint(self._debug_position()) 736 # Adjust some 'not beautifull' position 737 self.adjust_position() 738 # Flip the particle orientation such that fermion-flow is correct 739 self.solve_line_direction()
740 741 #fake vertex for end point particle use in load_diagram 742 fake_vertex = base_objects.Vertex({'id':0, 'legs':base_objects.LegList([])}) 743
744 - def load_diagram(self, contract=True):
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 # The last vertex is particular 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 # Contract the non propagating particle and fuse vertex associated 760 self._fuse_non_propa_particule() 761 762 # External particles have only one vertex attach to the line. (by 763 #construction). So we will add a new vertex object in order that all 764 #line are associated to two vertex. Those additional vertex will be 765 #place, later, at the border of the square. 766 for line in self.lineList: 767 if line.end == 0 or line.begin == 0: 768 # Create a new vertex. update the list, assign at the line. 769 vertex_point = VertexPoint(self.fake_vertex) 770 self.vertexList.append(vertex_point) 771 # If initial state particle, we will need to flip begin-end 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 # Remove wrongly define T-channel 788 self.remove_t_channel() 789 790 return
791
792 - def find_leg_id(self, leg, equal=0, end=0):
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
815 - def find_leg_id2(self, leg, end=0):
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
823 - def find_leg_id3(self, gen_id):
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
832 - def load_vertex(self, vertex):
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 #1) Extend to a vertex point 845 vertex_point = VertexPoint(vertex) 846 847 #2) Add to the vertexList of the diagram 848 self.vertexList.append(vertex_point) 849 850 # Loop over the leg associate to the diagram 851 for i, leg in enumerate(vertex.get('legs')): 852 gen_id = leg.get('number') 853 # Search if leg exist: two case exist corresponding if it is the 854 #line of vertex or not. Corresponding to that change mode to find 855 #if the leg exist or not. 856 mg_id = self.find_leg_id3(gen_id) 857 858 # Define-recover the line associate to this leg 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 # Associate the vertex to the line at the correct place 868 line.add_vertex(vertex_point) 869 870 # Change particule to anti-particule for last entry of vertex.lines 871 #doing this modification only if the vertex is the type 1 X....Z>1 872 #since in this case either the last particles will be a T-channel 873 #and will be resolve latter (so we don't care) or we have to flip 874 #particle to antiparticle. 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
882 - def load_leg(self, leg):
883 """Extend the leg to Feynman line. Associate the line to the diagram. 884 """ 885 886 # Extend the leg to FeynmanLine Object 887 line = FeynmanLine(leg) 888 line.def_model(self.model) 889 890 # Assign line and leg to the diagram. Storing leg is done in order to be 891 #able to check if a leg was already treated or not. 892 self._treated_legs.append(leg) 893 self.lineList.append(line) 894 895 return line
896
897 - def deal_last_line(self, last_line):
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 # Check if the line has two vertex associate to it, if not correct. 905 if last_line.end == 0 or last_line.begin == 0: 906 # Find the position of the line in self._treated_legs 907 id1 = self.find_leg_id(self._treated_legs[-1]) 908 # Find if they are a second call to this line 909 id2 = self.find_leg_id(self._treated_legs[-1], end=len(self._treated_legs) - id1) 910 if id2 is not None: 911 # Line is duplicate in linelist => remove this duplication 912 line = self.lineList[id2] 913 # Connect correctly the lines. The two lines are a continuation 914 #one of the other with a common vertex. We want to delete 915 #last_line. In consequence we replace in line the common vertex 916 #by the second vertex present in last_line, such that the new 917 #line is the sum of the two lines. 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 # Remove last_line from the vertex 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 # Remove last_line from the vertex 931 last_line.begin.remove_line(last_line) 932 933 # Remove last_line 934 self.lineList.remove(last_line) 935 else: 936 return #this is an external line => everything is ok
937
938 - def _fuse_non_propa_particule(self):
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 # Look for all line in backward mode in order to delete entry in the 948 #same time (as making th loop) without creating trouble 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
959 - def define_level(self):
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) #auto recursive operation 969 970 self.nb_level = self.max_level - self.min_level
971
972 - def def_next_level_from(self, vertex):
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 # Check if T-channel or not. Note that T-channel tag is wrongly 987 #define if only one particle in initial state. 988 if line.state == False: 989 # This is T vertex. => level is 1 990 line.end.def_level(1) 991 else: 992 # Define level 993 line.end.def_level(level + 1) 994 # Check for update in self.max_level 995 self.max_level = max(self.max_level, level + 1) 996 # Launch the recursion 997 self.def_next_level_from(line.end)
998 999
1000 - def find_t_channel_vertex(self):
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 [] #only one particle in initial state => no T-channel 1010 1011 while 1: 1012 # search next vertex and the connection line leading to this vertex 1013 t_vertex = self.find_next_t_channel_vertex(t_vertex) 1014 1015 #add it to the list 1016 if t_vertex: 1017 vertex_at_level.append(t_vertex) 1018 else: 1019 return vertex_at_level
1020
1021 - def find_next_t_channel_vertex(self, t_vertex):
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
1030 - def find_vertex_at_level(self, previous_level, level):
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 # Move external vertex from one level to avoid external 1040 #particles finishing inside the square. 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
1052 """Find a position to each vertex. All the vertex with the same level 1053 will have the same x coordinate. All external particles will be on the 1054 border of the square.""" 1055 1056 if len(self.initial_vertex) == 2: 1057 self.initial_vertex[0].def_position(0, 0) 1058 self.initial_vertex[1].def_position(0, 1) 1059 # Initial state are wrongly consider as outgoing-> solve: 1060 self.initial_vertex[0].lines[0].inverse_part_antipart() 1061 self.initial_vertex[1].lines[0].inverse_part_antipart() 1062 # Associate position to T-vertex 1063 t_vertex = self.find_vertex_position_tchannel() 1064 # Associatie position to level 2 and following (auto-recursive fct) 1065 self.find_vertex_position_at_level(t_vertex, 2) 1066 self.initial_vertex[0].def_position(0, 0) 1067 self.initial_vertex[1].def_position(0, 1) 1068 elif len(self.initial_vertex) == 1: 1069 #No T-Channel 1070 self.initial_vertex[0].def_position(0, 0.5) 1071 #initial state are wrongly consider as outgoing -> solve: 1072 init_line = self.initial_vertex[0].lines[0] 1073 init_line.inverse_part_antipart() 1074 # Associate position to level 1 1075 init_line.end.def_position(1 / self.nb_level, 0.5) 1076 # Associate position to level 2 and following (auto-recursive fct) 1077 self.find_vertex_position_at_level([init_line.end], 2) 1078 else: 1079 raise self.FeynamDiagramError, \ 1080 'only for one or two initial particles not %s' \ 1081 % (len(self.initial_vertex))
1082 1083
1085 """Finds the vertex position for level one, T channel are authorize""" 1086 1087 # Find the T-vertex in correct order 1088 t_vertex = self.find_t_channel_vertex() 1089 # Assign position at those vertex 1090 self.assign_pos(t_vertex, 1) 1091 return t_vertex
1092 1093
1094 - def find_vertex_position_at_level(self, vertexlist, level, direction=1):
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 # Find the order of vertex at next-level. if some external particle 1103 #are in vertexlist. They are replace in vertex_at_level. Such case 1104 #happen if the options forbids to an external particles to end at x!=1 1105 #coordinates or if it's not possible to put the vertex on the border. 1106 vertex_at_level = self.find_vertex_at_level(vertexlist, level) 1107 1108 if not vertex_at_level: 1109 return 1110 # Assign position to vertex_at_level. In order to deal with external 1111 #particles the vertex_at_level is modify. If an external vertex has 1112 #position on border it will be remove of vertex_at_level. 1113 self.assign_pos(vertex_at_level, level) 1114 1115 # Recursive mode 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 # At final level we should authorize min=0 and max=1 position 1162 if level == self.max_level: 1163 ext_dist_up = 1 1164 ext_dist_down = 1 1165 # Treat special 2 > 1 case 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 # else follow option 1171 ext_dist_up = self._ext_distance_up 1172 ext_dist_down = self._ext_distance_down 1173 # Set default gap in dist unity 1174 begin_gap, end_gap = 1, 1 1175 # Check the special case when min is 0 -> border 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 # Assign position at the border and update option 1181 self.define_vertex_at_border(vertex_at_level[0], level, 0) 1182 # Remove the vertex to avoid that it will pass to next level 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 # Check the special case when max is 1 -> border 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 # Assign position at the border 1198 self.define_vertex_at_border(vertex_at_level[-1], level, 1) 1199 # Remove the vertex to avoid that it will pass to next level 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 # Compute the distance between two vertex 1209 dist = (max - min) / (begin_gap + end_gap + len(vertex_at_level) - 1) 1210 1211 # Assign position to each vertex 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
1218 - def define_vertex_at_border(self, vertex, level, pos_y):
1219 """Define the position of the vertex considering the distance required 1220 in the Drawing Options. Update the option if needed.""" 1221 1222 # find the minimal x distance and update this distance for the future 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 # Find the position and switch integer and not integer case 1231 if dist % 1: 1232 # Check that we have to move forward the line 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
1245 - def remove_t_channel(self):
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
1254 - def solve_line_direction(self):
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 # Use the basic rules. Assigns correctly but for T-channel 1262 # This methods fails if the creation of wavefunctions modify the 1263 # particle content. 1264 1265 for line in self.lineList: 1266 if line.state == True: 1267 line.define_line_orientation() 1268 1269 # The define line orientation use level information and in consequence 1270 #fails on T-Channel. So in consequence we still have to fix T-channel 1271 #line. 1272 1273 # Make a loop on T-channel particles 1274 try: 1275 t_vertex = self.initial_vertex[-2] 1276 except Exception: 1277 return # No T-channel for 1 > X diagram 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 # Look the total flow of the vertex the other 1285 ver_flow = 0 # Current flow status for the vertex 1286 t_next = None # Next T-channel line. with unfix fermion flow 1287 for line in t_vertex.lines: 1288 1289 # Identify the next T-channel particles 1290 if line.state == False and t_old is not line and \ 1291 line.begin is t_vertex: 1292 t_next = line 1293 1294 #import sys 1295 #sys.exit() 1296 1297 # If not fermion, no update of the fermion flow 1298 if not line.is_fermion(): 1299 continue 1300 1301 # Update the fermion_flow 1302 if (line.begin is t_vertex): 1303 ver_flow += 1 1304 elif line.end is t_vertex: 1305 ver_flow -= 1 1306 1307 # End of the loop on the line of the vertex. 1308 if t_next: 1309 t_old = t_next 1310 t_vertex = t_next.end 1311 # Check the vertex_flow=0, we were lucky, else correct the flow. 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
1320 - def adjust_position(self):
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 # Check if we need to do something 1333 if not finalsize: 1334 return 1335 1336 # Select all external line 1337 for line in self.lineList: 1338 if line.is_external(): 1339 # Check the size of final particles to restrict to the max_size 1340 #constraints. 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
1352 - def _debug_load_diagram(self):
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
1387 - def _debug_level(self, text=1):
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
1408 - def _debug_position(self):
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
1422 - def _debug_has_intersection(self):
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 #loop on all pair combination 1428 for i, line in enumerate(self.lineList): 1429 for j in range(i + 1, len(self.lineList)): 1430 line2 = self.lineList[j] 1431 #check if they are a unvalid intersection 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
1444 - def __eq__(self, other):
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 # Check basic globals (this is done to fastenize the check 1457 if self.max_level != other.max_level: 1458 return False 1459 elif len(self.lineList) != len(other.lineList): 1460 return False 1461 1462 # Then compare vertex by vertex. As we didn't want to use order 1463 #information, we first select two vertex with the same position and then 1464 #compare then. 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 # This vertex doesn't have equivalent => They are different. 1471 return False 1472 else: 1473 vertex_other = other.vertexList[i] 1474 1475 # So now we have the 'vertex_self' and 'vertex_other' which are 1476 #vertex at the same position. Now we check if they have the same 1477 #line content. 1478 if not vertex_self.has_the_same_line_content(vertex_other): 1479 return False 1480 1481 # All the vertex and the associate line are equivalent. So the two 1482 #diagrams are consider as identical. 1483 return True
1484 1485 1486 #=============================================================================== 1487 # FeynmanDiagramHorizontal 1488 #===============================================================================
1489 -class FeynmanDiagramHorizontal(FeynmanDiagram):
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
1499 - def find_vertex_position_at_level(self, vertexlist, level, direction=1):
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 # If only final-initial particles no S-channel to fix => old routine 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 # Find the order of vertex at next-level. if some external particle 1520 #are in vertexlist. They are replace in vertex_at_level. Such case 1521 #happen if the options forbids to an external particles to end at x!=1 1522 #coordinates or if it's not possible to put the vertex on the border 1523 #of a previous level. 1524 vertex_at_level = self.find_vertex_at_level(vertexlist, level) 1525 vertex_at_level2 = [] # Will be the same list as vertex_at level but 1526 #with a potential different order and whitout some 1527 #(already fixed) external particles 1528 1529 min_pos = 0 # Starting of the current interval 1530 list_unforce_vertex = [] # Vertex which fit in this interval 1531 1532 # Loop at level-1 in order to check the number of S-channel going from 1533 #level-1 to level. 1534 for vertex in vertexlist: 1535 1536 s_vertex = [] # List of s vertex going to level 1537 ext_vertex = [] # List of external particle vertex 1538 v_pos = vertex.pos_y 1539 1540 # Assign the vertex linked to current vertex in the associate 1541 #category (S-channel or external) 1542 for line in vertex.lines: 1543 1544 # Find the vertex 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 # The line goes to level-2 1551 continue 1552 1553 # Assign in the correct list (external/s-channel) 1554 if line.is_external(): 1555 ext_vertex.append(new_vertex) 1556 else: 1557 s_vertex.append(new_vertex) 1558 1559 # Check the number of S-channel 1560 if len(s_vertex) != 1: 1561 # Udate the list_unforce_vertex. The complex way to do is a 1562 #naive attempt of improving the look of the diagram. 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 # Only One S-Channel => force to be horizontal 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 # Assign position to unforce list with some naive try of improvement 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 # Update value for the next interval 1591 min_pos = v_pos 1592 vertex_at_level2.append(force_vertex) 1593 1594 # End of the loop assign the position of unforce vertex remaining 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 # DiagramDrawer 1606 #===============================================================================
1607 -class DiagramDrawer(object):
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
1640 - class DrawDiagramError(Exception):
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 # Check the parameter value 1658 #No need to test Diagram class, it will be tested before using it anyway 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 # A Test of the Amplitude should be added when this one will be 1670 #use. 1671 1672 # Store the parameter in the object variable 1673 self.diagram = diagram 1674 self.filename = filename 1675 self.model = model # use for automatic conversion of graph 1676 self.amplitude = amplitude # will be use for conversion of graph 1677 self.opt = opt 1678 1679 # Set variable for storing text 1680 self.text = '' 1681 # Do we have to write a file? -> store in self.file 1682 if filename: 1683 self.file = True # Note that this variable will be overwritten. THis 1684 #will be the object file. [initialize] 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 # Check if we need to upgrade the diagram. 1695 self.convert_diagram(amplitude=self.amplitude, opt=opt) 1696 # Initialize some variable before starting to draw the diagram 1697 # This is just for frameworks capabilities (default: open file in 1698 #write mode if a filename was provide. 1699 self.initialize() 1700 # Call the instruction to draw the diagram line by line. 1701 self.draw_diagram(self.diagram) 1702 # Finish the creation of the file/object (default: write object if a 1703 #filename was provide). 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 #if already a valid diagram. nothing to do 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 # assign default for model and check validity (if not default) 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 # Test on Amplitude should be enter here, when we will use this 1749 #information 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 # Upgrade diagram to FeynmanDiagram or FeynmanDiagramHorizontal 1760 1761 #following option choice type is zero for the born and negative for R2 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 #following option choice 1780 # if opt.horizontal: 1781 # diagram = FeynmanDiagramHorizontal(diagram, model, \ 1782 # amplitude=amplitude, opt=opt) 1783 #else: 1784 # diagram = FeynmanDiagram(diagram, model, \ 1785 # amplitude=amplitude, opt=opt) 1786 1787 # Find the position of all vertex and all line orientation 1788 assert isinstance(diagram, FeynmanDiagram) 1789 diagram.main() 1790 1791 # Store-return information 1792 self.diagram = diagram 1793 return diagram
1794
1795 - def initialize(self):
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 # self.file is set on True/False in __init__. This defines if a filename 1801 #was provide in the __init__ step. 1802 if self.file: 1803 self.file = open(self.filename, 'w')
1804 1805
1806 - def draw_diagram(self, diagram=None, number=0):
1807 """Building the diagram Line after Line. 1808 This is the key routine of 'draw'.""" 1809 1810 # If No diagram set, use the one use at init 1811 if diagram is None: 1812 diagram = self.diagram 1813 1814 # drawing the vertex 1815 [self.draw_vertex(vertex) for vertex in diagram.vertexList] 1816 1817 # check if we need curved loop particles 1818 curved_for_loop = False 1819 circled_for_loop = False 1820 1821 if isinstance(diagram, LoopFeynmanDiagram): 1822 # If only 2 particle in the loop require that those lines are 1823 # curved 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 # for tadpole DOES NOT CRASH BUT STILL NEED FIXING 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 # drawing the particles 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 # Finalize information related to the graph. First, associate a diagram 1843 #position to the diagram representation. 1844 self.put_diagram_number(number) 1845 1846 1847 # Then If a file exist write the text in it 1848 if self.file: 1849 self.file.writelines(self.text) 1850 self.text = ""
1851
1852 - def conclude(self):
1853 """Final operation of the draw method. By default, this end to write the 1854 1855 file (if this one exist) 1856 """ 1857 1858 # self.file is set on True/False in __init__. If it is on True 1859 #the Initialize method change it in a file object 1860 if self.file: 1861 self.file.writelines(self.text) 1862 self.file.close() 1863 return
1864
1865 - def draw_line(self, line):
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 # Find the type line of the particle [straight, wavy, ...] 1872 line_type = line.get_info('line') 1873 # Call the routine associate to this type [self.draw_straight, ...] 1874 if hasattr(self, 'draw_' + line_type): 1875 getattr(self, 'draw_' + line_type)(line) 1876 else: 1877 self.draw_straight(line) 1878 1879 # Finalize the line representation with adding the name of the particle 1880 name = line.get_name() 1881 self.associate_name(line, name) 1882 # And associate the MadGraph5_aMC@NLO Number if it is an external particle 1883 if line.is_external(): 1884 number = line.number 1885 self.associate_number(line, number)
1886 1887 # To draw tadpole
1888 - def draw_circled_line(self, line):
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 # if 4 point (or more interaction at the beginning/end of the loop 1894 # need to reduce curvature 1895 if len(line.begin.lines) > 3 or len(line.end.lines) > 3 : 1896 cercle = False 1897 else: 1898 cercle = True 1899 1900 # Find the type line of the particle [straight, wavy, ...] 1901 line_type = line.get_info('line') 1902 # Call the routine associate to this type [self.draw_straight, ...] 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 # Finalize the line representation with adding the name of the particle 1909 name = line.get_name() 1910 self.associate_name(line, name) 1911 1912 #store begin for helping future curving 1913 self.curved_part_start = (line.begin.pos_x, line.begin.pos_y*1.2)
1914
1915 - def draw_curved_line(self, line):
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 # if 4 point (or more interaction at the beginning/end of the loop 1922 # need to reduce curvature 1923 if len(line.begin.lines) > 3 or len(line.end.lines) > 3 : 1924 cercle = False 1925 else: 1926 cercle = True 1927 1928 # Find the type line of the particle [straight, wavy, ...] 1929 line_type = line.get_info('line') 1930 # Call the routine associate to this type [self.draw_straight, ...] 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 # Finalize the line representation with adding the name of the particle 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 #store begin for helping future curving 1944 self.curved_part_start = (line.begin.pos_x, line.begin.pos_y)
1945
1946 - def draw_vertex(self, vertex):
1947 """default vertex style""" 1948 pass
1949 1950
1951 - def draw_straight(self, line):
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
1961 - def associate_name(self, line, name):
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
1967 - def associate_number(self, line, number):
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
1973 -class DrawOption(object):
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
1985 - class DrawingOptionError(Exception):
1986 """Error raising if an invalid entry is set in a option."""
1987
1988 - def __init__(self, opt=''):
1989 """Fullfill option with standard value.""" 1990 1991 #define default 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
2025 - def pass_to_logical(self, value):
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
2033 - def pass_to_number(self, value):
2034 """Convert the value in a number""" 2035 2036 return float(value)
2037 2038 #=============================================================================== 2039 # FeynmanDiagramLoop 2040 #===============================================================================
2041 -class LoopFeynmanDiagram(FeynmanDiagram):
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 # call the mother initialization 2090 super(LoopFeynmanDiagram, self).__init__(diagram, model, opt) 2091 self.fdstructures = fdstructures
2092 2093
2094 - def load_diagram(self, contract=True):
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 # select the lines present in the loop 2111 loop_line = [line for line in self.lineList if line.loop_line] 2112 2113 2114 # Fuse the cutted particles (the first and the last but one of the list) 2115 fake_line = loop_line[-1] 2116 self.fuse_line(loop_line[0], loop_line[-2]) 2117 # delete the fake line: 2118 self.vertexList.remove(fake_line.end) 2119 self.lineList.remove(fake_line)
2120
2121 - def find_vertex_at_level(self, previous_level, level):
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 # Move external vertex from one level to avoid external 2132 #particles finishing inside the square. 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 #order tmp to put external on the bottom/top 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
2167 - def find_vertex_position_at_level(self, vertexlist, level, direction=1):
2168 """Finds the vertex position for the particle at 'level' given the 2169 ordering at previous level given by the vertexlist. 2170 if direction !=0 pass in auto-recursive mode.""" 2171 2172 if level == 2: 2173 self.find_vertex_position_at_level(vertexlist, 0, -1) 2174 2175 super(LoopFeynmanDiagram, self).find_vertex_position_at_level( \ 2176 vertexlist, level, direction)
2177
2178 - def find_all_loop_vertex(self, init_loop):
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
2188 - def find_next_loop_channel_vertex(self, loop_vertex, forbiden=[]):
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
2205 - def fuse_line(self, line1, line2):
2206 """ make two lines to fuse in a single one. The final line will connect 2207 the two begin.""" 2208 2209 # remove line2 from lineList 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 # connect the line 2216 line1.def_end_point(line2.begin)
2217
2218 - def define_level(self):
2219 """ define level in a recursive way """ 2220 2221 #check what side of loop should be put on right side 2222 if self.need_to_flip(): 2223 self.loop_flip() 2224 2225 #add special attribute 2226 self.start_level_loop = None 2227 2228 super(LoopFeynmanDiagram, self).define_level()
2229
2230 - def need_to_flip(self):
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 #if not any([True for l in self.lineList if l.loop_line and l.state == False]): 2237 2238 # return False 2239 2240 left_side = 0 2241 right_side = 0 2242 side_weight = 0 # if side is positive need to switch 2243 nb_T_channel = 0 2244 nb_S_channel = 0 2245 2246 2247 binding_side = {} 2248 2249 # Count the number of T-channel propagator 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 # Note that the number of T_channel/S_channel has a factor 2 compare to 2284 # the number of particles in the loop. 2285 2286 # Ensure that the triangle are always correct: 2287 if nb_S_channel == 2: 2288 return True 2289 elif nb_T_channel == 2: 2290 return False 2291 2292 # See the depth of each side 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 # connecting to initial particles 2299 #compute the number of vertex in the structure 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
2314 - def loop_flip(self):
2315 """ switch t-channel information for the particle in the loop """ 2316 2317 #for vertex in self.diagram.get('vertices'): 2318 # leg = vertex['legs'][-1] 2319 # if leg.get('loop_line'): 2320 # leg.set('state', not leg.get('state')) 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
2327 - def remove_t_channel(self):
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
2341 - def def_next_level_from(self, vertex, direction=1):
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 # everything correctly define 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 # Check if T-channel or not. Note that T-channel tag is wrongly 2387 #define if only one particle in initial state. 2388 if line.state == False and len(self.initial_vertex)==2: 2389 # This is T vertex. => level is 1 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 # Define level 2408 next.def_level(level + direction) 2409 # Check for update in self.max_level 2410 self.max_level = max(self.max_level, level + direction) 2411 self.min_level = min(self.min_level, level + direction) 2412 # Launch the recursion 2413 self.def_next_level_from(next, direction)
2414