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