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