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

Source Code for Module madgraph.core.drawing

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