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

Source Code for Module madgraph.core.base_objects

   1  ################################################################################ 
   2  # 
   3  # Copyright (c) 2009 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  """Definitions of all basic objects used in the core code: particle,  
  16  interaction, model, leg, vertex, process, ...""" 
  17   
  18  import copy 
  19  import itertools 
  20  import logging 
  21  import math 
  22  import numbers 
  23  import os 
  24  import re 
  25  import StringIO 
  26  import madgraph.core.color_algebra as color 
  27  from madgraph import MadGraph5Error, MG5DIR 
  28  import madgraph.various.misc as misc  
  29   
  30  logger = logging.getLogger('madgraph.base_objects') 
  31  pjoin = os.path.join 
32 33 #=============================================================================== 34 # PhysicsObject 35 #=============================================================================== 36 -class PhysicsObject(dict):
37 """A parent class for all physics objects.""" 38
39 - class PhysicsObjectError(Exception):
40 """Exception raised if an error occurs in the definition 41 or the execution of a physics object.""" 42 pass
43
44 - def __init__(self, init_dict={}):
45 """Creates a new particle object. If a dictionary is given, tries to 46 use it to give values to properties.""" 47 48 dict.__init__(self) 49 self.default_setup() 50 51 assert isinstance(init_dict, dict), \ 52 "Argument %s is not a dictionary" % repr(init_dict) 53 54 55 for item in init_dict.keys(): 56 self.set(item, init_dict[item])
57 58
59 - def __getitem__(self, name):
60 """ force the check that the property exist before returning the 61 value associated to value. This ensure that the correct error 62 is always raise 63 """ 64 65 try: 66 return dict.__getitem__(self, name) 67 except KeyError: 68 self.is_valid_prop(name) #raise the correct error
69 70
71 - def default_setup(self):
72 """Function called to create and setup default values for all object 73 properties""" 74 pass
75
76 - def is_valid_prop(self, name):
77 """Check if a given property name is valid""" 78 79 assert isinstance(name, str), \ 80 "Property name %s is not a string" % repr(name) 81 82 if name not in self.keys(): 83 raise self.PhysicsObjectError, \ 84 """%s is not a valid property for this object: %s\n 85 Valid property are %s""" % (name,self.__class__.__name__, self.keys()) 86 return True
87
88 - def get(self, name):
89 """Get the value of the property name.""" 90 91 return self[name]
92
93 - def set(self, name, value, force=False):
94 """Set the value of the property name. First check if value 95 is a valid value for the considered property. Return True if the 96 value has been correctly set, False otherwise.""" 97 if not __debug__ or force: 98 self[name] = value 99 return True 100 101 if self.is_valid_prop(name): 102 try: 103 self.filter(name, value) 104 self[name] = value 105 return True 106 except self.PhysicsObjectError, why: 107 logger.warning("Property " + name + " cannot be changed:" + \ 108 str(why)) 109 return False
110
111 - def filter(self, name, value):
112 """Checks if the proposed value is valid for a given property 113 name. Returns True if OK. Raises an error otherwise.""" 114 115 return True
116
117 - def get_sorted_keys(self):
118 """Returns the object keys sorted in a certain way. By default, 119 alphabetical.""" 120 121 return self.keys().sort()
122
123 - def __str__(self):
124 """String representation of the object. Outputs valid Python 125 with improved format.""" 126 127 mystr = '{\n' 128 for prop in self.get_sorted_keys(): 129 if isinstance(self[prop], str): 130 mystr = mystr + ' \'' + prop + '\': \'' + \ 131 self[prop] + '\',\n' 132 elif isinstance(self[prop], float): 133 mystr = mystr + ' \'' + prop + '\': %.2f,\n' % self[prop] 134 else: 135 mystr = mystr + ' \'' + prop + '\': ' + \ 136 repr(self[prop]) + ',\n' 137 mystr = mystr.rstrip(',\n') 138 mystr = mystr + '\n}' 139 140 return mystr
141 142 __repr__ = __str__
143
144 145 #=============================================================================== 146 # PhysicsObjectList 147 #=============================================================================== 148 -class PhysicsObjectList(list):
149 """A class to store lists of physics object.""" 150
151 - class PhysicsObjectListError(Exception):
152 """Exception raised if an error occurs in the definition 153 or execution of a physics object list.""" 154 pass
155
156 - def __init__(self, init_list=None):
157 """Creates a new particle list object. If a list of physics 158 object is given, add them.""" 159 160 list.__init__(self) 161 162 if init_list is not None: 163 for object in init_list: 164 self.append(object)
165
166 - def append(self, object):
167 """Appends an element, but test if valid before.""" 168 169 assert self.is_valid_element(object), \ 170 "Object %s is not a valid object for the current list" % repr(object) 171 172 list.append(self, object)
173 174
175 - def is_valid_element(self, obj):
176 """Test if object obj is a valid element for the list.""" 177 return True
178
179 - def __str__(self):
180 """String representation of the physics object list object. 181 Outputs valid Python with improved format.""" 182 183 mystr = '[' 184 185 for obj in self: 186 mystr = mystr + str(obj) + ',\n' 187 188 mystr = mystr.rstrip(',\n') 189 190 return mystr + ']'
191
192 #=============================================================================== 193 # Particle 194 #=============================================================================== 195 -class Particle(PhysicsObject):
196 """The particle object containing the whole set of information required to 197 univocally characterize a given type of physical particle: name, spin, 198 color, mass, width, charge,... The is_part flag tells if the considered 199 particle object is a particle or an antiparticle. The self_antipart flag 200 tells if the particle is its own antiparticle.""" 201 202 sorted_keys = ['name', 'antiname', 'spin', 'color', 203 'charge', 'mass', 'width', 'pdg_code', 204 'texname', 'antitexname', 'line', 'propagating', 'propagator', 205 'is_part', 'self_antipart', 'ghost', 'counterterm'] 206
207 - def default_setup(self):
208 """Default values for all properties""" 209 210 self['name'] = 'none' 211 self['antiname'] = 'none' 212 self['spin'] = 1 213 self['color'] = 1 214 self['charge'] = 1. 215 self['mass'] = 'ZERO' 216 self['width'] = 'ZERO' 217 self['pdg_code'] = 0 218 self['texname'] = 'none' 219 self['antitexname'] = 'none' 220 self['line'] = 'dashed' 221 self['propagating'] = True 222 self['propagator'] = '' 223 self['is_part'] = True 224 self['self_antipart'] = False 225 # True if ghost, False otherwise 226 self['ghost'] = False 227 # Counterterm defined as a dictionary with format: 228 # ('ORDER_OF_COUNTERTERM',((Particle_list_PDG))):{laurent_order:CTCouplingName} 229 self['counterterm'] = {}
230
231 - def filter(self, name, value):
232 """Filter for valid particle property values.""" 233 234 if name in ['name', 'antiname']: 235 # Forbid special character but +-~_ 236 p=re.compile('''^[\w\-\+~_]+$''') 237 if not p.match(value): 238 raise self.PhysicsObjectError, \ 239 "%s is not a valid particle name" % value 240 241 if name is 'ghost': 242 if not isinstance(value,bool): 243 raise self.PhysicsObjectError, \ 244 "%s is not a valid bool for the 'ghost' attribute" % str(value) 245 246 if name is 'counterterm': 247 if not isinstance(value,dict): 248 raise self.PhysicsObjectError, \ 249 "counterterm %s is not a valid dictionary" % repr(value) 250 for key, val in value.items(): 251 if not isinstance(key,tuple): 252 raise self.PhysicsObjectError, \ 253 "key %s is not a valid tuple for counterterm key" % repr(key) 254 if not isinstance(key[0],str): 255 raise self.PhysicsObjectError, \ 256 "%s is not a valid string" % repr(key[0]) 257 if not isinstance(key[1],tuple): 258 raise self.PhysicsObjectError, \ 259 "%s is not a valid list" % repr(key[1]) 260 for elem in key[1]: 261 if not isinstance(elem,tuple): 262 raise self.PhysicsObjectError, \ 263 "%s is not a valid list" % repr(elem) 264 for partPDG in elem: 265 if not isinstance(partPDG,int): 266 raise self.PhysicsObjectError, \ 267 "%s is not a valid integer for PDG" % repr(partPDG) 268 if partPDG<=0: 269 raise self.PhysicsObjectError, \ 270 "%s is not a valid positive PDG" % repr(partPDG) 271 if not isinstance(val,dict): 272 raise self.PhysicsObjectError, \ 273 "value %s is not a valid dictionary for counterterm value" % repr(val) 274 for vkey, vvalue in val.items(): 275 if vkey not in [0,-1,-2]: 276 raise self.PhysicsObjectError, \ 277 "Key %s is not a valid laurent serie order" % repr(vkey) 278 if not isinstance(vvalue,str): 279 raise self.PhysicsObjectError, \ 280 "Coupling %s is not a valid string" % repr(vvalue) 281 if name is 'spin': 282 if not isinstance(value, int): 283 raise self.PhysicsObjectError, \ 284 "Spin %s is not an integer" % repr(value) 285 if (value < 1 or value > 5) and value != 99: 286 raise self.PhysicsObjectError, \ 287 "Spin %i not valid" % value 288 289 if name is 'color': 290 if not isinstance(value, int): 291 raise self.PhysicsObjectError, \ 292 "Color %s is not an integer" % repr(value) 293 if value not in [1, 3, 6, 8]: 294 raise self.PhysicsObjectError, \ 295 "Color %i is not valid" % value 296 297 if name in ['mass', 'width']: 298 # Must start with a letter, followed by letters, digits or _ 299 p = re.compile('\A[a-zA-Z]+[\w\_]*\Z') 300 if not p.match(value): 301 raise self.PhysicsObjectError, \ 302 "%s is not a valid name for mass/width variable" % \ 303 value 304 305 if name is 'pdg_code': 306 if not isinstance(value, int): 307 raise self.PhysicsObjectError, \ 308 "PDG code %s is not an integer" % repr(value) 309 310 if name is 'line': 311 if not isinstance(value, str): 312 raise self.PhysicsObjectError, \ 313 "Line type %s is not a string" % repr(value) 314 if value not in ['dashed', 'straight', 'wavy', 'curly', 'double','swavy','scurly','dotted']: 315 raise self.PhysicsObjectError, \ 316 "Line type %s is unknown" % value 317 318 if name is 'charge': 319 if not isinstance(value, float): 320 raise self.PhysicsObjectError, \ 321 "Charge %s is not a float" % repr(value) 322 323 if name is 'propagating': 324 if not isinstance(value, bool): 325 raise self.PhysicsObjectError, \ 326 "Propagating tag %s is not a boolean" % repr(value) 327 328 if name in ['is_part', 'self_antipart']: 329 if not isinstance(value, bool): 330 raise self.PhysicsObjectError, \ 331 "%s tag %s is not a boolean" % (name, repr(value)) 332 333 return True
334
335 - def get_sorted_keys(self):
336 """Return particle property names as a nicely sorted list.""" 337 338 return self.sorted_keys
339 340 # Helper functions 341
342 - def is_perturbating(self,order,model):
343 """Returns wether this particle contributes in perturbation of the order passed 344 in argument given the model specified. It is very fast for usual models""" 345 346 for int in model['interactions'].get_type('base'): 347 # We discard the interactions with more than one type of orders 348 # contributing because it then doesn't necessarly mean that this 349 # particle (self) is charged under the group corresponding to the 350 # coupling order 'order'. The typical example is in SUSY which 351 # features a ' photon-gluon-squark-antisquark ' interaction which 352 # has coupling orders QED=1, QCD=1 and would induce the photon 353 # to be considered as a valid particle to circulate in a loop of 354 # type "QCD". 355 if len(int.get('orders'))>1: 356 continue 357 if order in int.get('orders').keys() and self.get('pdg_code') in \ 358 [part.get('pdg_code') for part in int.get('particles')]: 359 return True 360 361 return False
362
363 - def get_pdg_code(self):
364 """Return the PDG code with a correct minus sign if the particle is its 365 own antiparticle""" 366 367 if not self['is_part'] and not self['self_antipart']: 368 return - self['pdg_code'] 369 else: 370 return self['pdg_code']
371
372 - def get_anti_pdg_code(self):
373 """Return the PDG code of the antiparticle with a correct minus sign 374 if the particle is its own antiparticle""" 375 376 if not self['self_antipart']: 377 return - self.get_pdg_code() 378 else: 379 return self['pdg_code']
380
381 - def get_color(self):
382 """Return the color code with a correct minus sign""" 383 384 if not self['is_part'] and abs(self['color']) in [3, 6]: 385 return - self['color'] 386 else: 387 return self['color']
388
389 - def get_anti_color(self):
390 """Return the color code of the antiparticle with a correct minus sign 391 """ 392 393 if self['is_part'] and self['color'] not in [1, 8]: 394 return - self['color'] 395 else: 396 return self['color']
397
398 - def get_charge(self):
399 """Return the charge code with a correct minus sign""" 400 401 if not self['is_part']: 402 return - self['charge'] 403 else: 404 return self['charge']
405
406 - def get_anti_charge(self):
407 """Return the charge code of the antiparticle with a correct minus sign 408 """ 409 410 if self['is_part']: 411 return - self['charge'] 412 else: 413 return self['charge']
414
415 - def get_name(self):
416 """Return the name if particle, antiname if antiparticle""" 417 418 if not self['is_part'] and not self['self_antipart']: 419 return self['antiname'] 420 else: 421 return self['name']
422
423 - def get_helicity_states(self):
424 """Return a list of the helicity states for the onshell particle""" 425 426 spin = self.get('spin') 427 if spin ==1: 428 # Scalar 429 return [ 0 ] 430 elif spin == 2: 431 # Spinor 432 return [ -1, 1 ] 433 elif spin == 3 and self.get('mass').lower() == 'zero': 434 # Massless vector 435 return [ -1, 1 ] 436 elif spin == 3: 437 # Massive vector 438 return [ -1, 0, 1 ] 439 elif spin == 4 and self.get('mass').lower() == 'zero': 440 # Massless tensor 441 return [-3, 3] 442 elif spin == 4: 443 # Massive tensor 444 return [-3, -1, 1, 3] 445 446 elif spin == 5 and self.get('mass').lower() == 'zero': 447 # Massless tensor 448 return [-2, -1, 1, 2] 449 elif spin in [5, 99]: 450 # Massive tensor 451 return [-2, -1, 0, 1, 2] 452 453 raise self.PhysicsObjectError, \ 454 "No helicity state assignment for spin %d particles" % spin
455
456 - def is_fermion(self):
457 """Returns True if this is a fermion, False if boson""" 458 459 return self['spin'] % 2 == 0
460
461 - def is_boson(self):
462 """Returns True if this is a boson, False if fermion""" 463 464 return self['spin'] % 2 == 1
465
466 #=============================================================================== 467 # ParticleList 468 #=============================================================================== 469 -class ParticleList(PhysicsObjectList):
470 """A class to store lists of particles.""" 471
472 - def is_valid_element(self, obj):
473 """Test if object obj is a valid Particle for the list.""" 474 return isinstance(obj, Particle)
475
476 - def get_copy(self, name):
477 """Try to find a particle with the given name. Check both name 478 and antiname. If a match is found, return the a copy of the 479 corresponding particle (first one in the list), with the 480 is_part flag set accordingly. None otherwise.""" 481 482 assert isinstance(name, str) 483 484 part = self.find_name(name) 485 if not part: 486 # Then try to look for a particle with that PDG 487 try: 488 pdg = int(name) 489 except ValueError: 490 return None 491 492 for p in self: 493 if p.get_pdg_code()==pdg: 494 part = copy.copy(p) 495 part.set('is_part', True) 496 return part 497 elif p.get_anti_pdg_code()==pdg: 498 part = copy.copy(p) 499 part.set('is_part', False) 500 return part 501 502 return None 503 part = copy.copy(part) 504 505 if part.get('name') == name: 506 part.set('is_part', True) 507 return part 508 elif part.get('antiname') == name: 509 part.set('is_part', False) 510 return part 511 return None
512
513 - def find_name(self, name):
514 """Try to find a particle with the given name. Check both name 515 and antiname. If a match is found, return the a copy of the 516 corresponding particle (first one in the list), with the 517 is_part flag set accordingly. None otherwise.""" 518 519 assert isinstance(name, str), "%s is not a valid string" % str(name) 520 521 for part in self: 522 if part.get('name') == name: 523 return part 524 elif part.get('antiname') == name: 525 return part 526 527 return None
528
529 - def generate_ref_dict(self):
530 """Generate a dictionary of part/antipart pairs (as keys) and 531 0 (as value)""" 532 533 ref_dict_to0 = {} 534 535 for part in self: 536 ref_dict_to0[(part.get_pdg_code(), part.get_anti_pdg_code())] = [0] 537 ref_dict_to0[(part.get_anti_pdg_code(), part.get_pdg_code())] = [0] 538 539 return ref_dict_to0
540
541 - def generate_dict(self):
542 """Generate a dictionary from particle id to particle. 543 Include antiparticles. 544 """ 545 546 particle_dict = {} 547 548 for particle in self: 549 particle_dict[particle.get('pdg_code')] = particle 550 if not particle.get('self_antipart'): 551 antipart = copy.deepcopy(particle) 552 antipart.set('is_part', False) 553 particle_dict[antipart.get_pdg_code()] = antipart 554 555 return particle_dict
556
557 558 #=============================================================================== 559 # Interaction 560 #=============================================================================== 561 -class Interaction(PhysicsObject):
562 """The interaction object containing the whole set of information 563 required to univocally characterize a given type of physical interaction: 564 565 particles: a list of particle ids 566 color: a list of string describing all the color structures involved 567 lorentz: a list of variable names describing all the Lorentz structure 568 involved 569 couplings: dictionary listing coupling variable names. The key is a 570 2-tuple of integers referring to color and Lorentz structures 571 orders: dictionary listing order names (as keys) with their value 572 """ 573 574 sorted_keys = ['id', 'particles', 'color', 'lorentz', 'couplings', 575 'orders','loop_particles','type','perturbation_type'] 576
577 - def default_setup(self):
578 """Default values for all properties""" 579 580 self['id'] = 0 581 self['particles'] = [] 582 self['color'] = [] 583 self['lorentz'] = [] 584 self['couplings'] = { (0, 0):'none'} 585 self['orders'] = {} 586 # The type of interactions can be 'base', 'UV' or 'R2'. 587 # For 'UV' or 'R2', one can always specify the loop it corresponds 588 # to by a tag in the second element of the list. If the tag is an 589 # empty list, then the R2/UV interaction will be recognized only 590 # based on the nature of the identity of the particles branching 591 # off the loop and the loop orders. 592 # Otherwise, the tag can be specified and it will be used when 593 # identifying the R2/UV interaction corresponding to a given loop 594 # generated. 595 # The format is [(lp1ID,int1ID),(lp1ID,int1ID),(lp1ID,int1ID),etc...] 596 # Example of a tag for the following loop 597 # 598 # ___34_____ The ';' line is a gluon with ID 21 599 # 45/ ; The '|' line is a d-quark with ID 1 600 # ------< ; The numbers are the interactions ID 601 # \___;______ The tag for this loop would be: 602 # 12 ((21,34),(1,45),(1,12)) 603 # 604 # This tag is equivalent to all its cyclic permutations. This is why 605 # it must be specified in the canonical order which is defined with 606 # by putting in front of the tag the lowest 2-tuple it contains. 607 # (the order relation is defined by comparing the particle ID first 608 # and the interaction ID after in case the particle ID are the same). 609 # In case there are two identical lowest 2-tuple in the tag, the 610 # tag chosen is such that it has the lowest second 2-tuple. The procedure 611 # is repeated again with the subsequent 2-tuple until there is only 612 # one cyclic permutation remaining and the ambiguity is resolved. 613 # This insures to have one unique unambiguous canonical tag chosen. 614 # In the example above, it would be: 615 # ((1,12),(21,34),(1,45)) 616 # PS: Notice that in the UFO model, the tag-information is limited to 617 # the minimally relevant one which are the loop particles specified in 618 # in the attribute below. In this case, 'loop_particles' is the list of 619 # all the loops giving this same counterterm contribution. 620 # Each loop being represented by a set of the PDG of the particles 621 # (not repeated) constituting it. In the example above, it would simply 622 # be (1,21). In the UFO, if the loop particles are not specified then 623 # MG5 will account for this counterterm only once per concerned vertex. 624 # Taking the example of the three gluon vertex counterterm, one can 625 # possibly have in the ufo: 626 # VertexB = blabla, loop_particles = (b) 627 # VertexT = blabla, loop_particles = (t) 628 # or 629 # VertexALL = blabla, loop_particles = () 630 # In the first case UFO specifies the specific counterterm to the three- 631 # gluon loop with the bottom running in (VertexB) and with the top running 632 # in (VertexT). So MG5 will associate these counterterm vertices once to 633 # each of the two loop. 634 # In the case where UFO defined VertexALL, then whenever MG5 encounters 635 # a triangle three-gluon loop (say the bottom one), it will associate to 636 # it the vertex VertexALL but will not do so again when encountering the 637 # same loop with the top quark running in. This, because it assumes that 638 # the UFO vertexALL comprises all contributions already. 639 640 self['loop_particles']=[[]] 641 self['type'] = 'base' 642 self['perturbation_type'] = None
643
644 - def filter(self, name, value):
645 """Filter for valid interaction property values.""" 646 647 if name == 'id': 648 #Should be an integer 649 if not isinstance(value, int): 650 raise self.PhysicsObjectError, \ 651 "%s is not a valid integer" % str(value) 652 653 if name == 'particles': 654 #Should be a list of valid particle names 655 if not isinstance(value, ParticleList): 656 raise self.PhysicsObjectError, \ 657 "%s is not a valid list of particles" % str(value) 658 659 if name == 'perturbation_type': 660 if value!=None and not isinstance(value, str): 661 raise self.PhysicsObjectError, \ 662 "%s is not a valid string" % str(value) 663 664 if name == 'type': 665 #Should be a string 666 if not isinstance(value, str): 667 raise self.PhysicsObjectError, \ 668 "%s is not a valid string" % str(value) 669 if name == 'loop_particles': 670 if isinstance(value,list): 671 for l in value: 672 if isinstance(l,list): 673 for part in l: 674 if not isinstance(part,int): 675 raise self.PhysicsObjectError, \ 676 "%s is not a valid integer" % str(part) 677 if part<0: 678 raise self.PhysicsObjectError, \ 679 "%s is not a valid positive integer" % str(part) 680 681 if name == 'orders': 682 #Should be a dict with valid order names ask keys and int as values 683 if not isinstance(value, dict): 684 raise self.PhysicsObjectError, \ 685 "%s is not a valid dict for coupling orders" % \ 686 str(value) 687 for order in value.keys(): 688 if not isinstance(order, str): 689 raise self.PhysicsObjectError, \ 690 "%s is not a valid string" % str(order) 691 if not isinstance(value[order], int): 692 raise self.PhysicsObjectError, \ 693 "%s is not a valid integer" % str(value[order]) 694 695 if name in ['color']: 696 #Should be a list of list strings 697 if not isinstance(value, list): 698 raise self.PhysicsObjectError, \ 699 "%s is not a valid list of Color Strings" % str(value) 700 for mycolstring in value: 701 if not isinstance(mycolstring, color.ColorString): 702 raise self.PhysicsObjectError, \ 703 "%s is not a valid list of Color Strings" % str(value) 704 705 if name in ['lorentz']: 706 #Should be a list of list strings 707 if not isinstance(value, list): 708 raise self.PhysicsObjectError, \ 709 "%s is not a valid list of strings" % str(value) 710 for mystr in value: 711 if not isinstance(mystr, str): 712 raise self.PhysicsObjectError, \ 713 "%s is not a valid string" % str(mystr) 714 715 if name == 'couplings': 716 #Should be a dictionary of strings with (i,j) keys 717 if not isinstance(value, dict): 718 raise self.PhysicsObjectError, \ 719 "%s is not a valid dictionary for couplings" % \ 720 str(value) 721 722 for key in value.keys(): 723 if not isinstance(key, tuple): 724 raise self.PhysicsObjectError, \ 725 "%s is not a valid tuple" % str(key) 726 if len(key) != 2: 727 raise self.PhysicsObjectError, \ 728 "%s is not a valid tuple with 2 elements" % str(key) 729 if not isinstance(key[0], int) or not isinstance(key[1], int): 730 raise self.PhysicsObjectError, \ 731 "%s is not a valid tuple of integer" % str(key) 732 if not isinstance(value[key], str): 733 raise self.PhysicsObjectError, \ 734 "%s is not a valid string" % value[key] 735 736 return True
737
738 - def get_sorted_keys(self):
739 """Return particle property names as a nicely sorted list.""" 740 741 return self.sorted_keys
742
743 - def is_perturbating(self, orders_considered):
744 """ Returns if this interaction comes from the perturbation of one of 745 the order listed in the argument """ 746 747 if self['perturbation_type']==None: 748 return True 749 else: 750 return (self['perturbation_type'] in orders_considered)
751
752 - def is_R2(self):
753 """ Returns if the interaction is of R2 type.""" 754 755 # Precaution only useful because some tests have a predefined model 756 # bypassing the default_setup and for which type was not defined. 757 if 'type' in self.keys(): 758 return (len(self['type'])>=2 and self['type'][:2]=='R2') 759 else: 760 return False
761
762 - def is_UV(self):
763 """ Returns if the interaction is of UV type.""" 764 765 # Precaution only useful because some tests have a predefined model 766 # bypassing the default_setup and for which type was not defined. 767 if 'type' in self.keys(): 768 return (len(self['type'])>=2 and self['type'][:2]=='UV') 769 else: 770 return False
771
772 - def is_UVmass(self):
773 """ Returns if the interaction is of UVmass type.""" 774 775 # Precaution only useful because some tests have a predefined model 776 # bypassing the default_setup and for which type was not defined. 777 if 'type' in self.keys(): 778 return (len(self['type'])>=6 and self['type'][:6]=='UVmass') 779 else: 780 return False
781
782 - def is_UVloop(self):
783 """ Returns if the interaction is of UVmass type.""" 784 785 # Precaution only useful because some tests have a predefined model 786 # bypassing the default_setup and for which type was not defined. 787 if 'type' in self.keys(): 788 return (len(self['type'])>=6 and self['type'][:6]=='UVloop') 789 else: 790 return False
791
792 - def is_UVtree(self):
793 """ Returns if the interaction is of UVmass type.""" 794 795 # Precaution only useful because some tests have a predefined model 796 # bypassing the default_setup and for which type was not defined. 797 if 'type' in self.keys(): 798 return (len(self['type'])>=6 and self['type'][:6]=='UVtree') 799 else: 800 return False
801
802 - def is_UVCT(self):
803 """ Returns if the interaction is of the UVCT type which means that 804 it has been selected as a possible UV counterterm interaction for this 805 process. Such interactions are marked by having the 'UVCT_SPECIAL' order 806 key in their orders.""" 807 808 # Precaution only useful because some tests have a predefined model 809 # bypassing the default_setup and for which type was not defined. 810 if 'UVCT_SPECIAL' in self['orders'].keys(): 811 return True 812 else: 813 return False
814
815 - def get_epsilon_order(self):
816 """ Returns 0 if this interaction contributes to the finite part of the 817 amplitude and 1 (2) is it contributes to its single (double) pole """ 818 819 if 'type' in self.keys(): 820 if '1eps' in self['type']: 821 return 1 822 elif '2eps' in self['type']: 823 return 2 824 else: 825 return 0 826 else: 827 return 0
828
829 - def generate_dict_entries(self, ref_dict_to0, ref_dict_to1):
830 """Add entries corresponding to the current interactions to 831 the reference dictionaries (for n>0 and n-1>1)""" 832 833 # Create n>0 entries. Format is (p1,p2,p3,...):interaction_id. 834 # We are interested in the unordered list, so use sorted() 835 836 pdg_tuple = tuple(sorted([p.get_pdg_code() for p in self['particles']])) 837 if pdg_tuple not in ref_dict_to0.keys(): 838 ref_dict_to0[pdg_tuple] = [self['id']] 839 else: 840 ref_dict_to0[pdg_tuple].append(self['id']) 841 842 # Create n-1>1 entries. Note that, in the n-1 > 1 dictionary, 843 # the n-1 entries should have opposite sign as compared to 844 # interaction, since the interaction has outgoing particles, 845 # while in the dictionary we treat the n-1 particles as 846 # incoming 847 848 for part in self['particles']: 849 850 # We are interested in the unordered list, so use sorted() 851 pdg_tuple = tuple(sorted([p.get_pdg_code() for (i, p) in \ 852 enumerate(self['particles']) if \ 853 i != self['particles'].index(part)])) 854 pdg_part = part.get_anti_pdg_code() 855 if pdg_tuple in ref_dict_to1.keys(): 856 if (pdg_part, self['id']) not in ref_dict_to1[pdg_tuple]: 857 ref_dict_to1[pdg_tuple].append((pdg_part, self['id'])) 858 else: 859 ref_dict_to1[pdg_tuple] = [(pdg_part, self['id'])]
860
861 - def get_WEIGHTED_order(self, model):
862 """Get the WEIGHTED order for this interaction, for equivalent 863 3-particle vertex. Note that it can be fractional.""" 864 865 return float(sum([model.get('order_hierarchy')[key]*self.get('orders')[key]\ 866 for key in self.get('orders')]))/ \ 867 max((len(self.get('particles'))-2), 1)
868
869 - def __str__(self):
870 """String representation of an interaction. Outputs valid Python 871 with improved format. Overrides the PhysicsObject __str__ to only 872 display PDG code of involved particles.""" 873 874 mystr = '{\n' 875 876 for prop in self.get_sorted_keys(): 877 if isinstance(self[prop], str): 878 mystr = mystr + ' \'' + prop + '\': \'' + \ 879 self[prop] + '\',\n' 880 elif isinstance(self[prop], float): 881 mystr = mystr + ' \'' + prop + '\': %.2f,\n' % self[prop] 882 elif isinstance(self[prop], ParticleList): 883 mystr = mystr + ' \'' + prop + '\': [%s],\n' % \ 884 ','.join([str(part.get_pdg_code()) for part in self[prop]]) 885 else: 886 mystr = mystr + ' \'' + prop + '\': ' + \ 887 repr(self[prop]) + ',\n' 888 mystr = mystr.rstrip(',\n') 889 mystr = mystr + '\n}' 890 891 return mystr
892
893 #=============================================================================== 894 # InteractionList 895 #=============================================================================== 896 -class InteractionList(PhysicsObjectList):
897 """A class to store lists of interactionss.""" 898
899 - def is_valid_element(self, obj):
900 """Test if object obj is a valid Interaction for the list.""" 901 902 return isinstance(obj, Interaction)
903
904 - def generate_ref_dict(self,useR2UV=False, useUVCT=False):
905 """Generate the reference dictionaries from interaction list. 906 Return a list where the first element is the n>0 dictionary and 907 the second one is n-1>1.""" 908 909 ref_dict_to0 = {} 910 ref_dict_to1 = {} 911 buffer = {} 912 913 for inter in self: 914 if useR2UV or (not inter.is_UV() and not inter.is_R2() and \ 915 not inter.is_UVCT()): 916 inter.generate_dict_entries(ref_dict_to0, ref_dict_to1) 917 if useUVCT and inter.is_UVCT(): 918 inter.generate_dict_entries(ref_dict_to0, ref_dict_to1) 919 920 return [ref_dict_to0, ref_dict_to1]
921
922 - def generate_dict(self):
923 """Generate a dictionary from interaction id to interaction. 924 """ 925 926 interaction_dict = {} 927 928 for inter in self: 929 interaction_dict[inter.get('id')] = inter 930 931 return interaction_dict
932
933 - def synchronize_interactions_with_particles(self, particle_dict):
934 """Make sure that the particles in the interactions are those 935 in the particle_dict, and that there are no interactions 936 refering to particles that don't exist. To be called when the 937 particle_dict is updated in a model. 938 """ 939 940 iint = 0 941 while iint < len(self): 942 inter = self[iint] 943 particles = inter.get('particles') 944 try: 945 for ipart, part in enumerate(particles): 946 particles[ipart] = particle_dict[part.get_pdg_code()] 947 iint += 1 948 except KeyError: 949 # This interaction has particles that no longer exist 950 self.pop(iint)
951
952 - def get_type(self, type):
953 """ return all interactions in the list of type 'type' """ 954 return InteractionList([int for int in self if int.get('type')==type])
955
956 - def get_R2(self):
957 """ return all interactions in the list of type R2 """ 958 return InteractionList([int for int in self if int.is_R2()])
959
960 - def get_UV(self):
961 """ return all interactions in the list of type UV """ 962 return InteractionList([int for int in self if int.is_UV()])
963
964 - def get_UVmass(self):
965 """ return all interactions in the list of type UVmass """ 966 return InteractionList([int for int in self if int.is_UVmass()])
967
968 - def get_UVtree(self):
969 """ return all interactions in the list of type UVtree """ 970 return InteractionList([int for int in self if int.is_UVtree()])
971
972 - def get_UVloop(self):
973 """ return all interactions in the list of type UVloop """ 974 return InteractionList([int for int in self if int.is_UVloop()])
975
976 #=============================================================================== 977 # Model 978 #=============================================================================== 979 -class Model(PhysicsObject):
980 """A class to store all the model information.""" 981
982 - def default_setup(self):
983 984 self['name'] = "" 985 self['particles'] = ParticleList() 986 self['interactions'] = InteractionList() 987 self['parameters'] = None 988 self['functions'] = None 989 self['couplings'] = None 990 self['lorentz'] = None 991 self['particle_dict'] = {} 992 self['interaction_dict'] = {} 993 self['ref_dict_to0'] = {} 994 self['ref_dict_to1'] = {} 995 self['got_majoranas'] = None 996 self['order_hierarchy'] = {} 997 self['conserved_charge'] = set() 998 self['coupling_orders'] = None 999 self['expansion_order'] = None 1000 self['version_tag'] = None # position of the directory (for security) 1001 self['gauge'] = [0, 1] 1002 self['case_sensitive'] = True
1003 # attribute which might be define if needed 1004 #self['name2pdg'] = {'name': pdg} 1005 1006
1007 - def filter(self, name, value):
1008 """Filter for model property values""" 1009 1010 if name in ['name']: 1011 if not isinstance(value, str): 1012 raise self.PhysicsObjectError, \ 1013 "Object of type %s is not a string" %type(value) 1014 1015 elif name == 'particles': 1016 if not isinstance(value, ParticleList): 1017 raise self.PhysicsObjectError, \ 1018 "Object of type %s is not a ParticleList object" % \ 1019 type(value) 1020 elif name == 'interactions': 1021 if not isinstance(value, InteractionList): 1022 raise self.PhysicsObjectError, \ 1023 "Object of type %s is not a InteractionList object" % \ 1024 type(value) 1025 elif name == 'particle_dict': 1026 if not isinstance(value, dict): 1027 raise self.PhysicsObjectError, \ 1028 "Object of type %s is not a dictionary" % \ 1029 type(value) 1030 elif name == 'interaction_dict': 1031 if not isinstance(value, dict): 1032 raise self.PhysicsObjectError, \ 1033 "Object of type %s is not a dictionary" % type(value) 1034 1035 elif name == 'ref_dict_to0': 1036 if not isinstance(value, dict): 1037 raise self.PhysicsObjectError, \ 1038 "Object of type %s is not a dictionary" % type(value) 1039 1040 elif name == 'ref_dict_to1': 1041 if not isinstance(value, dict): 1042 raise self.PhysicsObjectError, \ 1043 "Object of type %s is not a dictionary" % type(value) 1044 1045 elif name == 'got_majoranas': 1046 if not (isinstance(value, bool) or value == None): 1047 raise self.PhysicsObjectError, \ 1048 "Object of type %s is not a boolean" % type(value) 1049 1050 elif name == 'conserved_charge': 1051 if not (isinstance(value, set)): 1052 raise self.PhysicsObjectError, \ 1053 "Object of type %s is not a set" % type(value) 1054 1055 elif name == 'version_tag': 1056 if not (isinstance(value, str)): 1057 raise self.PhysicsObjectError, \ 1058 "Object of type %s is not a string" % type(value) 1059 1060 elif name == 'order_hierarchy': 1061 if not isinstance(value, dict): 1062 raise self.PhysicsObjectError, \ 1063 "Object of type %s is not a dictionary" % \ 1064 type(value) 1065 for key in value.keys(): 1066 if not isinstance(value[key],int): 1067 raise self.PhysicsObjectError, \ 1068 "Object of type %s is not an integer" % \ 1069 type(value[key]) 1070 elif name == 'gauge': 1071 if not (isinstance(value, list)): 1072 raise self.PhysicsObjectError, \ 1073 "Object of type %s is not a list" % type(value) 1074 1075 elif name == 'case_sensitive': 1076 if not value in [True ,False]: 1077 raise self.PhysicsObjectError, \ 1078 "Object of type %s is not a boolean" % type(value) 1079 return True
1080
1081 - def get(self, name):
1082 """Get the value of the property name.""" 1083 1084 if (name == 'ref_dict_to0' or name == 'ref_dict_to1') and \ 1085 not self[name]: 1086 if self['interactions']: 1087 [self['ref_dict_to0'], self['ref_dict_to1']] = \ 1088 self['interactions'].generate_ref_dict() 1089 self['ref_dict_to0'].update( 1090 self['particles'].generate_ref_dict()) 1091 1092 if (name == 'particle_dict') and not self[name]: 1093 if self['particles']: 1094 self['particle_dict'] = self['particles'].generate_dict() 1095 if self['interactions']: 1096 self['interactions'].synchronize_interactions_with_particles(\ 1097 self['particle_dict']) 1098 if name == 'modelpath': 1099 modeldir = self.get('version_tag').rsplit('##',1)[0] 1100 if os.path.exists(modeldir): 1101 return modeldir 1102 else: 1103 raise Exception, "path %s not valid anymore." % modeldir 1104 #modeldir = os.path.join(os.path.dirname(modeldir), 1105 # os.path.basename(modeldir).rsplit("-",1)[0]) 1106 #if os.path.exists(modeldir): 1107 # return modeldir 1108 #raise Exception, 'Invalid Path information: %s' % self.get('version_tag') 1109 elif name == 'modelpath+restriction': 1110 modeldir = self.get('version_tag').rsplit('##',1)[0] 1111 modelname = self['name'] 1112 if not os.path.exists(modeldir): 1113 raise Exception, "path %s not valid anymore" % modeldir 1114 modeldir = os.path.dirname(modeldir) 1115 modeldir = pjoin(modeldir, modelname) 1116 return modeldir 1117 elif name == 'restrict_name': 1118 modeldir = self.get('version_tag').rsplit('##',1)[0] 1119 modelname = self['name'] 1120 basename = os.path.basename(modeldir) 1121 restriction = modelname[len(basename)+1:] 1122 return restriction 1123 1124 if (name == 'interaction_dict') and not self[name]: 1125 if self['interactions']: 1126 self['interaction_dict'] = self['interactions'].generate_dict() 1127 1128 if (name == 'got_majoranas') and self[name] == None: 1129 if self['particles']: 1130 self['got_majoranas'] = self.check_majoranas() 1131 1132 if (name == 'coupling_orders') and self[name] == None: 1133 if self['interactions']: 1134 self['coupling_orders'] = self.get_coupling_orders() 1135 1136 if (name == 'order_hierarchy') and not self[name]: 1137 if self['interactions']: 1138 self['order_hierarchy'] = self.get_order_hierarchy() 1139 1140 if (name == 'expansion_order') and self[name] == None: 1141 if self['interactions']: 1142 self['expansion_order'] = \ 1143 dict([(order, -1) for order in self.get('coupling_orders')]) 1144 1145 if (name == 'name2pdg') and 'name2pdg' not in self: 1146 self['name2pdg'] = {} 1147 for p in self.get('particles'): 1148 self['name2pdg'][p.get('antiname')] = -1*p.get('pdg_code') 1149 self['name2pdg'][p.get('name')] = p.get('pdg_code') 1150 1151 return Model.__bases__[0].get(self, name) # call the mother routine
1152
1153 - def set(self, name, value, force = False):
1154 """Special set for particles and interactions - need to 1155 regenerate dictionaries.""" 1156 1157 if name == 'particles': 1158 # Ensure no doublets in particle list 1159 make_unique(value) 1160 # Reset dictionaries 1161 self['particle_dict'] = {} 1162 self['ref_dict_to0'] = {} 1163 self['got_majoranas'] = None 1164 1165 if name == 'interactions': 1166 # Ensure no doublets in interaction list 1167 make_unique(value) 1168 # Reset dictionaries 1169 self['interaction_dict'] = {} 1170 self['ref_dict_to1'] = {} 1171 self['ref_dict_to0'] = {} 1172 self['got_majoranas'] = None 1173 self['coupling_orders'] = None 1174 self['order_hierarchy'] = {} 1175 self['expansion_order'] = None 1176 1177 result = Model.__bases__[0].set(self, name, value, force) # call the mother routine 1178 1179 if name == 'particles': 1180 # Recreate particle_dict 1181 self.get('particle_dict') 1182 1183 return result
1184
1185 - def actualize_dictionaries(self):
1186 """This function actualizes the dictionaries""" 1187 1188 [self['ref_dict_to0'], self['ref_dict_to1']] = \ 1189 self['interactions'].generate_ref_dict() 1190 self['ref_dict_to0'].update( 1191 self['particles'].generate_ref_dict())
1192
1193 - def get_sorted_keys(self):
1194 """Return process property names as a nicely sorted list.""" 1195 1196 return ['name', 'particles', 'parameters', 'interactions', 1197 'couplings','lorentz', 'gauge']
1198
1199 - def get_particle(self, id):
1200 """Return the particle corresponding to the id / name""" 1201 1202 try: 1203 return self["particle_dict"][id] 1204 except Exception: 1205 if isinstance(id, int): 1206 try: 1207 return self.get("particle_dict")[id] 1208 except Exception: 1209 return None 1210 else: 1211 if not hasattr(self, 'name2part'): 1212 self.create_name2part() 1213 try: 1214 return self.name2part[id] 1215 except: 1216 return None
1217
1218 - def create_name2part(self):
1219 """create a dictionary name 2 part""" 1220 1221 self.name2part = {} 1222 for part in self.get("particle_dict").values(): 1223 self.name2part[part.get('name')] = part
1224 1225 1226
1227 - def get_lorentz(self, name):
1228 """return the lorentz object from the associate name""" 1229 if hasattr(self, 'lorentz_name2obj'): 1230 return self.lorentz_name2obj[name] 1231 else: 1232 self.create_lorentz_dict() 1233 return self.lorentz_name2obj[name]
1234
1235 - def create_lorentz_dict(self):
1236 """create the dictionary linked to the lorentz structure""" 1237 self.lorentz_name2obj = {} 1238 self.lorentz_expr2name = {} 1239 if not self.get('lorentz'): 1240 return 1241 for lor in self.get('lorentz'): 1242 self.lorentz_name2obj[lor.name] = lor 1243 self.lorentz_expr2name[lor.structure] = lor.name
1244
1245 - def get_interaction(self, id):
1246 """Return the interaction corresponding to the id""" 1247 1248 try: 1249 return self.get("interaction_dict")[id] 1250 except Exception: 1251 return None
1252
1253 - def get_parameter(self, name):
1254 """Return the parameter associated to the name NAME""" 1255 1256 # If information is saved 1257 if hasattr(self, 'parameters_dict') and self.parameters_dict: 1258 try: 1259 return self.parameters_dict[name] 1260 except Exception: 1261 # try to reload it before crashing 1262 pass 1263 1264 # Else first build the dictionary 1265 self.parameters_dict = {} 1266 for data in self['parameters'].values(): 1267 [self.parameters_dict.__setitem__(p.name,p) for p in data] 1268 1269 return self.parameters_dict[name]
1270
1271 - def get_coupling_orders(self):
1272 """Determine the coupling orders of the model""" 1273 return set(sum([i.get('orders').keys() for i in \ 1274 self.get('interactions')], []))
1275
1276 - def get_order_hierarchy(self):
1277 """Set a default order hierarchy for the model if not set by the UFO.""" 1278 # Set coupling hierachy 1279 hierarchy = dict([(order, 1) for order in self.get('coupling_orders')]) 1280 # Special case for only QCD and QED couplings, unless already set 1281 if self.get('coupling_orders') == set(['QCD', 'QED']): 1282 hierarchy['QED'] = 2 1283 return hierarchy
1284 1285
1286 - def get_nflav(self):
1287 """returns the number of light quark flavours in the model.""" 1288 return len([p for p in self.get('particles') \ 1289 if p['spin'] == 2 and p['is_part'] and \ 1290 p ['color'] != 1 and p['mass'].lower() == 'zero'])
1291 1292
1293 - def get_particles_hierarchy(self):
1294 """Returns the order hierarchies of the model and the 1295 particles which have interactions in at least this hierarchy 1296 (used in find_optimal_process_orders in MultiProcess diagram 1297 generation): 1298 1299 Check the coupling hierarchy of the model. Assign all 1300 particles to the different coupling hierarchies so that a 1301 particle is considered to be in the highest hierarchy (i.e., 1302 with lowest value) where it has an interaction. 1303 """ 1304 1305 # Find coupling orders in model 1306 coupling_orders = self.get('coupling_orders') 1307 # Loop through the different coupling hierarchy values, so we 1308 # start with the most dominant and proceed to the least dominant 1309 hierarchy = sorted(list(set([self.get('order_hierarchy')[k] for \ 1310 k in coupling_orders]))) 1311 1312 # orders is a rising list of the lists of orders with a given hierarchy 1313 orders = [] 1314 for value in hierarchy: 1315 orders.append([ k for (k, v) in \ 1316 self.get('order_hierarchy').items() if \ 1317 v == value ]) 1318 1319 # Extract the interaction that correspond to the different 1320 # coupling hierarchies, and the corresponding particles 1321 interactions = [] 1322 particles = [] 1323 for iorder, order in enumerate(orders): 1324 sum_orders = sum(orders[:iorder+1], []) 1325 sum_interactions = sum(interactions[:iorder], []) 1326 sum_particles = sum([list(p) for p in particles[:iorder]], []) 1327 # Append all interactions that have only orders with at least 1328 # this hierarchy 1329 interactions.append([i for i in self.get('interactions') if \ 1330 not i in sum_interactions and \ 1331 not any([k not in sum_orders for k in \ 1332 i.get('orders').keys()])]) 1333 # Append the corresponding particles, excluding the 1334 # particles that have already been added 1335 particles.append(set(sum([[p.get_pdg_code() for p in \ 1336 inter.get('particles') if \ 1337 p.get_pdg_code() not in sum_particles] \ 1338 for inter in interactions[-1]], []))) 1339 1340 return particles, hierarchy
1341
1342 - def get_max_WEIGHTED(self):
1343 """Return the maximum WEIGHTED order for any interaction in the model, 1344 for equivalent 3-particle vertices. Note that it can be fractional.""" 1345 1346 return max([inter.get_WEIGHTED_order(self) for inter in \ 1347 self.get('interactions')])
1348 1349
1350 - def check_majoranas(self):
1351 """Return True if there is fermion flow violation, False otherwise""" 1352 1353 if any([part.is_fermion() and part.get('self_antipart') \ 1354 for part in self.get('particles')]): 1355 return True 1356 1357 # No Majorana particles, but may still be fermion flow 1358 # violating interactions 1359 for inter in self.get('interactions'): 1360 # Do not look at UV Wfct renormalization counterterms 1361 if len(inter.get('particles'))==1: 1362 continue 1363 fermions = [p for p in inter.get('particles') if p.is_fermion()] 1364 for i in range(0, len(fermions), 2): 1365 if fermions[i].get('is_part') == \ 1366 fermions[i+1].get('is_part'): 1367 # This is a fermion flow violating interaction 1368 return True 1369 # No fermion flow violations 1370 return False
1371
1372 - def reset_dictionaries(self):
1373 """Reset all dictionaries and got_majoranas. This is necessary 1374 whenever the particle or interaction content has changed. If 1375 particles or interactions are set using the set routine, this 1376 is done automatically.""" 1377 1378 self['particle_dict'] = {} 1379 self['ref_dict_to0'] = {} 1380 self['got_majoranas'] = None 1381 self['interaction_dict'] = {} 1382 self['ref_dict_to1'] = {} 1383 self['ref_dict_to0'] = {}
1384
1386 """Change the name of the particles such that all SM and MSSM particles 1387 follows the MG convention""" 1388 1389 # Check that default name/antiname is not already use 1390 def check_name_free(self, name): 1391 """ check if name is not use for a particle in the model if it is 1392 raise an MadGraph5error""" 1393 part = self['particles'].find_name(name) 1394 if part: 1395 error_text = \ 1396 '%s particles with pdg code %s is in conflict with MG ' + \ 1397 'convention name for particle %s.\n Use -modelname in order ' + \ 1398 'to use the particles name defined in the model and not the ' + \ 1399 'MadGraph5_aMC@NLO convention' 1400 1401 raise MadGraph5Error, error_text % \ 1402 (part.get_name(), part.get_pdg_code(), pdg)
1403 1404 default = self.load_default_name() 1405 1406 for pdg in default.keys(): 1407 part = self.get_particle(pdg) 1408 if not part: 1409 continue 1410 antipart = self.get_particle(-pdg) 1411 name = part.get_name() 1412 if name != default[pdg]: 1413 check_name_free(self, default[pdg]) 1414 if part.get('is_part'): 1415 part.set('name', default[pdg]) 1416 if antipart: 1417 antipart.set('name', default[pdg]) 1418 else: 1419 part.set('antiname', default[pdg]) 1420 else: 1421 part.set('antiname', default[pdg]) 1422 if antipart: 1423 antipart.set('antiname', default[pdg]) 1424 1425 #additional check for the Higgs in the mssm 1426 if self.get('name') == 'mssm' or self.get('name').startswith('mssm-'): 1427 part = self.get_particle(25) 1428 part.set('name', 'h1') 1429 part.set('antiname', 'h1')
1430 1431 1432
1433 - def change_parameter_name_with_prefix(self, prefix='mdl_'):
1434 """ Change all model parameter by a given prefix. 1435 Modify the parameter if some of them are identical up to the case""" 1436 1437 lower_dict={} 1438 duplicate = set() 1439 keys = self.get('parameters').keys() 1440 for key in keys: 1441 for param in self['parameters'][key]: 1442 lower_name = param.name.lower() 1443 if not lower_name: 1444 continue 1445 try: 1446 lower_dict[lower_name].append(param) 1447 except KeyError: 1448 lower_dict[lower_name] = [param] 1449 else: 1450 duplicate.add(lower_name) 1451 logger.debug('%s is define both as lower case and upper case.' 1452 % lower_name) 1453 1454 if prefix == '' and not duplicate: 1455 return 1456 1457 re_expr = r'''\b(%s)\b''' 1458 to_change = [] 1459 change={} 1460 # recast all parameter in prefix_XX 1461 for key in keys: 1462 for param in self['parameters'][key]: 1463 value = param.name.lower() 1464 if value in ['as','mu_r', 'zero','aewm1','g']: 1465 continue 1466 elif value.startswith(prefix): 1467 continue 1468 elif value in duplicate: 1469 continue # handle later 1470 elif value: 1471 change[param.name] = '%s%s' % (prefix,param.name) 1472 to_change.append(param.name) 1473 param.name = change[param.name] 1474 1475 for value in duplicate: 1476 for i, var in enumerate(lower_dict[value][1:]): 1477 to_change.append(var.name) 1478 change[var.name] = '%s%s__%s' % (prefix, var.name.lower(), i+2) 1479 var.name = '%s%s__%s' %(prefix, var.name.lower(), i+2) 1480 to_change.append(var.name) 1481 assert 'zero' not in to_change 1482 replace = lambda match_pattern: change[match_pattern.groups()[0]] 1483 1484 if not to_change: 1485 return 1486 1487 if 'parameter_dict' in self: 1488 new_dict = dict( (change[name] if (name in change) else name, value) for 1489 name, value in self['parameter_dict'].items()) 1490 self['parameter_dict'] = new_dict 1491 1492 i=0 1493 while i*1000 <= len(to_change): 1494 one_change = to_change[i*1000: min((i+1)*1000,len(to_change))] 1495 i+=1 1496 rep_pattern = re.compile('\\b%s\\b'% (re_expr % ('\\b|\\b'.join(one_change)))) 1497 1498 # change parameters 1499 for key in keys: 1500 if key == ('external',): 1501 continue 1502 for param in self['parameters'][key]: 1503 param.expr = rep_pattern.sub(replace, param.expr) 1504 # change couplings 1505 for key in self['couplings'].keys(): 1506 for coup in self['couplings'][key]: 1507 coup.expr = rep_pattern.sub(replace, coup.expr) 1508 1509 # change mass/width 1510 for part in self['particles']: 1511 if str(part.get('mass')) in one_change: 1512 part.set('mass', rep_pattern.sub(replace, str(part.get('mass')))) 1513 if str(part.get('width')) in one_change: 1514 part.set('width', rep_pattern.sub(replace, str(part.get('width')))) 1515 if hasattr(part, 'partial_widths'): 1516 for key, value in part.partial_widths.items(): 1517 part.partial_widths[key] = rep_pattern.sub(replace, value) 1518 1519 #ensure that the particle_dict is up-to-date 1520 self['particle_dict'] ='' 1521 self.get('particle_dict')
1522 1523 1524
1525 - def get_first_non_pdg(self):
1526 """Return the first positive number that is not a valid PDG code""" 1527 return [c for c in range(1, len(self.get('particles')) + 1) if \ 1528 c not in self.get('particle_dict').keys()][0]
1529
1530 - def write_param_card(self):
1531 """Write out the param_card, and return as string.""" 1532 1533 import models.write_param_card as writter 1534 out = StringIO.StringIO() # it's suppose to be written in a file 1535 param = writter.ParamCardWriter(self) 1536 param.define_output_file(out) 1537 param.write_card() 1538 return out.getvalue()
1539 1540 @ staticmethod
1541 - def load_default_name():
1542 """ load the default for name convention """ 1543 1544 logger.info('Change particles name to pass to MG5 convention') 1545 default = {} 1546 for line in open(os.path.join(MG5DIR, 'input', \ 1547 'particles_name_default.txt')): 1548 line = line.lstrip() 1549 if line.startswith('#'): 1550 continue 1551 1552 args = line.split() 1553 if len(args) != 2: 1554 logger.warning('Invalid syntax in interface/default_name:\n %s' % line) 1555 continue 1556 default[int(args[0])] = args[1].lower() 1557 1558 return default
1559
1560 - def change_mass_to_complex_scheme(self):
1561 """modify the expression changing the mass to complex mass scheme""" 1562 1563 # 1) Find All input parameter mass and width associated 1564 # Add a internal parameter and replace mass with that param 1565 # 2) Find All mass fixed by the model and width associated 1566 # -> Both need to be fixed with a real() /Imag() 1567 # 3) Find All width fixed by the model 1568 # -> Need to be fixed with a real() 1569 # 4) Fix the Yukawa mass to the value of the complex mass/ real mass 1570 # 5) Loop through all expression and modify those accordingly 1571 # Including all parameter expression as complex 1572 1573 to_change = {} 1574 mass_widths = [] # parameter which should stay real 1575 for particle in self.get('particles'): 1576 m = particle.get('width') 1577 if m in mass_widths: 1578 continue 1579 mass_widths.append(particle.get('width')) 1580 mass_widths.append(particle.get('mass')) 1581 if particle.get('width') == 'ZERO': 1582 #everything is fine since the width is zero 1583 continue 1584 width = self.get_parameter(particle.get('width')) 1585 if not isinstance(width, ParamCardVariable): 1586 width.expr = 're(%s)' % width.expr 1587 if particle.get('mass') != 'ZERO': 1588 mass = self.get_parameter(particle.get('mass')) 1589 # special SM treatment to change the gauge scheme automatically. 1590 if particle.get('pdg_code') == 24: 1591 if hasattr(mass, 'expr') and mass.expr == 'cmath.sqrt(MZ__exp__2/2. + cmath.sqrt(MZ__exp__4/4. - (aEW*cmath.pi*MZ__exp__2)/(Gf*sqrt__2)))': 1592 # Make MW an external parameter 1593 MW = ParamCardVariable(mass.name, mass.value, 'MASS', [24]) 1594 if not MW.value: 1595 MW.value = 80.385 1596 self.get('parameters')[('external',)].append(MW) 1597 self.get('parameters')[mass.depend].remove(mass) 1598 # Make Gf an internal parameter 1599 new_param = ModelVariable('Gf', 1600 '-aEW*MZ**2*cmath.pi/(cmath.sqrt(2)*%(MW)s**2*(%(MW)s**2 - MZ**2))' %\ 1601 {'MW': mass.name}, 'complex', mass.depend) 1602 Gf = self.get_parameter('Gf') 1603 self.get('parameters')[('external',)].remove(Gf) 1604 self.add_param(new_param, ['aEW']) 1605 # Use the new mass for the future modification 1606 mass = MW 1607 #option with prefixing 1608 elif hasattr(mass, 'expr') and mass.expr == 'cmath.sqrt(mdl_MZ__exp__2/2. + cmath.sqrt(mdl_MZ__exp__4/4. - (mdl_aEW*cmath.pi*mdl_MZ__exp__2)/(mdl_Gf*mdl_sqrt__2)))': 1609 # Make MW an external parameter 1610 MW = ParamCardVariable(mass.name, mass.value, 'MASS', [24]) 1611 if not MW.value: 1612 MW.value = 80.385 1613 self.get('parameters')[('external',)].append(MW) 1614 self.get('parameters')[mass.depend].remove(mass) 1615 # Make Gf an internal parameter 1616 new_param = ModelVariable('mdl_Gf', 1617 '-mdl_aEW*mdl_MZ**2*cmath.pi/(cmath.sqrt(2)*%(MW)s**2*(%(MW)s**2 - mdl_MZ**2))' %\ 1618 {'MW': mass.name}, 'complex', mass.depend) 1619 Gf = self.get_parameter('mdl_Gf') 1620 self.get('parameters')[('external',)].remove(Gf) 1621 self.add_param(new_param, ['mdl_aEW']) 1622 # Use the new mass for the future modification 1623 mass = MW 1624 elif isinstance(mass, ModelVariable): 1625 logger.warning('W mass is not an external parameter. This is not adviced for the complex mass scheme.') 1626 1627 # Add A new parameter CMASS 1628 #first compute the dependencies (as,...) 1629 depend = list(set(mass.depend + width.depend)) 1630 if len(depend)>1 and 'external' in depend: 1631 depend.remove('external') 1632 depend = tuple(depend) 1633 if depend == ('external',): 1634 depend = () 1635 1636 # Create the new parameter 1637 if isinstance(mass, ParamCardVariable): 1638 New_param = ModelVariable('CMASS_'+mass.name, 1639 'cmath.sqrt(%(mass)s**2 - complex(0,1) * %(mass)s * %(width)s)' \ 1640 % {'mass': mass.name, 'width': width.name}, 1641 'complex', depend) 1642 else: 1643 New_param = ModelVariable('CMASS_'+mass.name, 1644 mass.expr, 'complex', depend) 1645 # Modify the treatment of the width in this case 1646 if not isinstance(width, ParamCardVariable): 1647 width.expr = '- im(%s**2) / cmath.sqrt(re(%s**2))' % (mass.expr, mass.expr) 1648 else: 1649 # Remove external parameter from the param_card 1650 New_width = ModelVariable(width.name, 1651 '-1 * im(CMASS_%s**2) / %s' % (mass.name, mass.name), 'real', mass.depend) 1652 self.get('parameters')[('external',)].remove(width) 1653 self.add_param(New_param, (mass,)) 1654 self.add_param(New_width, (New_param,)) 1655 mass.expr = 'cmath.sqrt(re(%s**2))' % mass.expr 1656 to_change[mass.name] = New_param.name 1657 continue 1658 1659 mass.expr = 're(%s)' % mass.expr 1660 self.add_param(New_param, (mass, width)) 1661 to_change[mass.name] = New_param.name 1662 1663 # Remove the Yukawa and fix those accordingly to the mass/complex mass 1664 yukawas = [p for p in self.get('parameters')[('external',)] 1665 if p.lhablock.lower() == 'yukawa'] 1666 for yukawa in yukawas: 1667 # clean the pevious parameter 1668 self.get('parameters')[('external',)].remove(yukawa) 1669 1670 particle = self.get_particle(yukawa.lhacode[0]) 1671 mass = self.get_parameter(particle.get('mass')) 1672 1673 # add the new parameter in the correct category 1674 if mass.depend == ('external',): 1675 depend = () 1676 else: 1677 depend = mass.depend 1678 1679 New_param = ModelVariable(yukawa.name, mass.name, 'real', depend) 1680 1681 # Add it in the model at the correct place (for the dependences) 1682 if mass.name in to_change: 1683 expr = 'CMASS_%s' % mass.name 1684 else: 1685 expr = mass.name 1686 param_depend = self.get_parameter(expr) 1687 self.add_param(New_param, [param_depend]) 1688 1689 if not to_change: 1690 return 1691 1692 1693 # So at this stage we still need to modify all parameters depending of 1694 # particle's mass. In addition all parameter (but mass/width/external 1695 # parameter) should be pass in complex mode. 1696 pat = '|'.join(to_change.keys()) 1697 pat = r'(%s)\b' % pat 1698 pat = re.compile(pat) 1699 def replace(match): 1700 return to_change[match.group()]
1701 1702 # Modify the parameters 1703 for dep, list_param in self['parameters'].items(): 1704 for param in list_param: 1705 if param.name.startswith('CMASS_') or param.name in mass_widths or\ 1706 isinstance(param, ParamCardVariable): 1707 continue 1708 param.type = 'complex' 1709 # print param.expr, to_change 1710 1711 param.expr = pat.sub(replace, param.expr) 1712 1713 # Modify the couplings 1714 for dep, list_coup in self['couplings'].items(): 1715 for coup in list_coup: 1716 coup.expr = pat.sub(replace, coup.expr) 1717
1718 - def add_param(self, new_param, depend_param):
1719 """add the parameter in the list of parameter in a correct position""" 1720 1721 pos = 0 1722 for i,param in enumerate(self.get('parameters')[new_param.depend]): 1723 if param.name in depend_param: 1724 pos = i + 1 1725 self.get('parameters')[new_param.depend].insert(pos, new_param)
1726
1727 1728 #def __repr__(self): 1729 # """ """ 1730 # raise Exception 1731 # return "Model(%s)" % self.get_name() 1732 #__str__ = __repr__ 1733 ################################################################################ 1734 # Class for Parameter / Coupling 1735 ################################################################################ 1736 -class ModelVariable(object):
1737 """A Class for storing the information about coupling/ parameter""" 1738
1739 - def __init__(self, name, expression, type, depend=()):
1740 """Initialize a new parameter/coupling""" 1741 1742 self.name = name 1743 self.expr = expression # python expression 1744 self.type = type # real/complex 1745 self.depend = depend # depend on some other parameter -tuple- 1746 self.value = None
1747
1748 - def __eq__(self, other):
1749 """Object with same name are identical, If the object is a string we check 1750 if the attribute name is equal to this string""" 1751 1752 try: 1753 return other.name == self.name 1754 except Exception: 1755 return other == self.name
1756
1757 -class ParamCardVariable(ModelVariable):
1758 """ A class for storing the information linked to all the parameter 1759 which should be define in the param_card.dat""" 1760 1761 depend = ('external',) 1762 type = 'real' 1763
1764 - def __init__(self, name, value, lhablock, lhacode):
1765 """Initialize a new ParamCardVariable 1766 name: name of the variable 1767 value: default numerical value 1768 lhablock: name of the block in the param_card.dat 1769 lhacode: code associate to the variable 1770 """ 1771 self.name = name 1772 self.value = value 1773 self.lhablock = lhablock 1774 self.lhacode = lhacode
1775
1776 1777 #=============================================================================== 1778 # Classes used in diagram generation and process definition: 1779 # Leg, Vertex, Diagram, Process 1780 #=============================================================================== 1781 1782 #=============================================================================== 1783 # Leg 1784 #=============================================================================== 1785 -class Leg(PhysicsObject):
1786 """Leg object: id (Particle), number, I/F state, flag from_group 1787 """ 1788
1789 - def default_setup(self):
1790 """Default values for all properties""" 1791 1792 self['id'] = 0 1793 self['number'] = 0 1794 # state: True = final, False = initial (boolean to save memory) 1795 self['state'] = True 1796 #self['loop_line'] = False 1797 self['loop_line'] = False 1798 # from_group: Used in diagram generation 1799 self['from_group'] = True 1800 # onshell: decaying leg (True), forbidden s-channel (False), none (None) 1801 self['onshell'] = None
1802
1803 - def filter(self, name, value):
1804 """Filter for valid leg property values.""" 1805 1806 if name in ['id', 'number']: 1807 if not isinstance(value, int): 1808 raise self.PhysicsObjectError, \ 1809 "%s is not a valid integer for leg id" % str(value) 1810 1811 if name == 'state': 1812 if not isinstance(value, bool): 1813 raise self.PhysicsObjectError, \ 1814 "%s is not a valid leg state (True|False)" % \ 1815 str(value) 1816 1817 if name == 'from_group': 1818 if not isinstance(value, bool) and value != None: 1819 raise self.PhysicsObjectError, \ 1820 "%s is not a valid boolean for leg flag from_group" % \ 1821 str(value) 1822 1823 if name == 'loop_line': 1824 if not isinstance(value, bool) and value != None: 1825 raise self.PhysicsObjectError, \ 1826 "%s is not a valid boolean for leg flag loop_line" % \ 1827 str(value) 1828 1829 if name == 'onshell': 1830 if not isinstance(value, bool) and value != None: 1831 raise self.PhysicsObjectError, \ 1832 "%s is not a valid boolean for leg flag onshell" % \ 1833 str(value) 1834 return True
1835
1836 - def get_sorted_keys(self):
1837 """Return particle property names as a nicely sorted list.""" 1838 1839 return ['id', 'number', 'state', 'from_group', 'loop_line', 'onshell']
1840
1841 - def is_fermion(self, model):
1842 """Returns True if the particle corresponding to the leg is a 1843 fermion""" 1844 1845 assert isinstance(model, Model), "%s is not a model" % str(model) 1846 1847 return model.get('particle_dict')[self['id']].is_fermion()
1848
1849 - def is_incoming_fermion(self, model):
1850 """Returns True if leg is an incoming fermion, i.e., initial 1851 particle or final antiparticle""" 1852 1853 assert isinstance(model, Model), "%s is not a model" % str(model) 1854 1855 part = model.get('particle_dict')[self['id']] 1856 return part.is_fermion() and \ 1857 (self.get('state') == False and part.get('is_part') or \ 1858 self.get('state') == True and not part.get('is_part'))
1859
1860 - def is_outgoing_fermion(self, model):
1861 """Returns True if leg is an outgoing fermion, i.e., initial 1862 antiparticle or final particle""" 1863 1864 assert isinstance(model, Model), "%s is not a model" % str(model) 1865 1866 part = model.get('particle_dict')[self['id']] 1867 return part.is_fermion() and \ 1868 (self.get('state') == True and part.get('is_part') or \ 1869 self.get('state') == False and not part.get('is_part'))
1870 1871 # Helper function. We don't overload the == operator because it might be useful 1872 # to define it differently than that later. 1873
1874 - def same(self, leg):
1875 """ Returns true if the leg in argument has the same ID and the same numer """ 1876 1877 # In case we want to check this leg with an integer in the tagging procedure, 1878 # then it only has to match the leg number. 1879 if isinstance(leg,int): 1880 if self['number']==leg: 1881 return True 1882 else: 1883 return False 1884 1885 # If using a Leg object instead, we also want to compare the other relevant 1886 # properties. 1887 elif isinstance(leg, Leg): 1888 if self['id']==leg.get('id') and \ 1889 self['number']==leg.get('number') and \ 1890 self['loop_line']==leg.get('loop_line') : 1891 return True 1892 else: 1893 return False 1894 1895 else : 1896 return False
1897 1898 # Make sure sort() sorts lists of legs according to 'number'
1899 - def __lt__(self, other):
1900 return self['number'] < other['number']
1901
1902 #=============================================================================== 1903 # LegList 1904 #=============================================================================== 1905 -class LegList(PhysicsObjectList):
1906 """List of Leg objects 1907 """ 1908
1909 - def is_valid_element(self, obj):
1910 """Test if object obj is a valid Leg for the list.""" 1911 1912 return isinstance(obj, Leg)
1913 1914 # Helper methods for diagram generation 1915
1916 - def from_group_elements(self):
1917 """Return all elements which have 'from_group' True""" 1918 1919 return filter(lambda leg: leg.get('from_group'), self)
1920
1921 - def minimum_one_from_group(self):
1922 """Return True if at least one element has 'from_group' True""" 1923 1924 return len(self.from_group_elements()) > 0
1925
1926 - def minimum_two_from_group(self):
1927 """Return True if at least two elements have 'from_group' True""" 1928 1929 return len(self.from_group_elements()) > 1
1930
1931 - def can_combine_to_1(self, ref_dict_to1):
1932 """If has at least one 'from_group' True and in ref_dict_to1, 1933 return the return list from ref_dict_to1, otherwise return False""" 1934 if self.minimum_one_from_group(): 1935 return ref_dict_to1.has_key(tuple(sorted([leg.get('id') for leg in self]))) 1936 else: 1937 return False
1938
1939 - def can_combine_to_0(self, ref_dict_to0, is_decay_chain=False):
1940 """If has at least two 'from_group' True and in ref_dict_to0, 1941 1942 return the vertex (with id from ref_dict_to0), otherwise return None 1943 1944 If is_decay_chain = True, we only allow clustering of the 1945 initial leg, since we want this to be the last wavefunction to 1946 be evaluated. 1947 """ 1948 if is_decay_chain: 1949 # Special treatment - here we only allow combination to 0 1950 # if the initial leg (marked by from_group = None) is 1951 # unclustered, since we want this to stay until the very 1952 # end. 1953 return any(leg.get('from_group') == None for leg in self) and \ 1954 ref_dict_to0.has_key(tuple(sorted([leg.get('id') \ 1955 for leg in self]))) 1956 1957 if self.minimum_two_from_group(): 1958 return ref_dict_to0.has_key(tuple(sorted([leg.get('id') for leg in self]))) 1959 else: 1960 return False
1961
1962 - def get_outgoing_id_list(self, model):
1963 """Returns the list of ids corresponding to the leglist with 1964 all particles outgoing""" 1965 1966 res = [] 1967 1968 assert isinstance(model, Model), "Error! model not model" 1969 1970 1971 for leg in self: 1972 if leg.get('state') == False: 1973 res.append(model.get('particle_dict')[leg.get('id')].get_anti_pdg_code()) 1974 else: 1975 res.append(leg.get('id')) 1976 1977 return res
1978
1979 - def sort(self,*args, **opts):
1980 """Match with FKSLegList""" 1981 Opts=copy.copy(opts) 1982 if 'pert' in Opts.keys(): 1983 del Opts['pert'] 1984 return super(LegList,self).sort(*args, **Opts)
1985
1986 1987 #=============================================================================== 1988 # MultiLeg 1989 #=============================================================================== 1990 -class MultiLeg(PhysicsObject):
1991 """MultiLeg object: ids (Particle or particles), I/F state 1992 """ 1993
1994 - def default_setup(self):
1995 """Default values for all properties""" 1996 1997 self['ids'] = [] 1998 self['state'] = True
1999
2000 - def filter(self, name, value):
2001 """Filter for valid multileg property values.""" 2002 2003 if name == 'ids': 2004 if not isinstance(value, list): 2005 raise self.PhysicsObjectError, \ 2006 "%s is not a valid list" % str(value) 2007 for i in value: 2008 if not isinstance(i, int): 2009 raise self.PhysicsObjectError, \ 2010 "%s is not a valid list of integers" % str(value) 2011 2012 if name == 'state': 2013 if not isinstance(value, bool): 2014 raise self.PhysicsObjectError, \ 2015 "%s is not a valid leg state (initial|final)" % \ 2016 str(value) 2017 2018 return True
2019
2020 - def get_sorted_keys(self):
2021 """Return particle property names as a nicely sorted list.""" 2022 2023 return ['ids', 'state']
2024
2025 #=============================================================================== 2026 # LegList 2027 #=============================================================================== 2028 -class MultiLegList(PhysicsObjectList):
2029 """List of MultiLeg objects 2030 """ 2031
2032 - def is_valid_element(self, obj):
2033 """Test if object obj is a valid MultiLeg for the list.""" 2034 2035 return isinstance(obj, MultiLeg)
2036
2037 #=============================================================================== 2038 # Vertex 2039 #=============================================================================== 2040 -class Vertex(PhysicsObject):
2041 """Vertex: list of legs (ordered), id (Interaction) 2042 """ 2043 2044 sorted_keys = ['id', 'legs'] 2045
2046 - def default_setup(self):
2047 """Default values for all properties""" 2048 2049 self['id'] = 0 2050 self['legs'] = LegList()
2051
2052 - def filter(self, name, value):
2053 """Filter for valid vertex property values.""" 2054 2055 if name == 'id': 2056 if not isinstance(value, int): 2057 raise self.PhysicsObjectError, \ 2058 "%s is not a valid integer for vertex id" % str(value) 2059 2060 if name == 'legs': 2061 if not isinstance(value, LegList): 2062 raise self.PhysicsObjectError, \ 2063 "%s is not a valid LegList object" % str(value) 2064 2065 return True
2066
2067 - def get_sorted_keys(self):
2068 """Return particle property names as a nicely sorted list.""" 2069 2070 return self.sorted_keys #['id', 'legs']
2071
2072 - def get_s_channel_id(self, model, ninitial):
2073 """Returns the id for the last leg as an outgoing 2074 s-channel. Returns 0 if leg is t-channel, or if identity 2075 vertex. Used to check for required and forbidden s-channel 2076 particles.""" 2077 2078 leg = self.get('legs')[-1] 2079 2080 if ninitial == 1: 2081 # For one initial particle, all legs are s-channel 2082 # Only need to flip particle id if state is False 2083 if leg.get('state') == True: 2084 return leg.get('id') 2085 else: 2086 return model.get('particle_dict')[leg.get('id')].\ 2087 get_anti_pdg_code() 2088 2089 # Number of initial particles is at least 2 2090 if self.get('id') == 0 or \ 2091 leg.get('state') == False: 2092 # identity vertex or t-channel particle 2093 return 0 2094 2095 if leg.get('loop_line'): 2096 # Loop lines never count as s-channel 2097 return 0 2098 2099 # Check if the particle number is <= ninitial 2100 # In that case it comes from initial and we should switch direction 2101 if leg.get('number') > ninitial: 2102 return leg.get('id') 2103 else: 2104 return model.get('particle_dict')[leg.get('id')].\ 2105 get_anti_pdg_code()
2106
2107 ## Check if the other legs are initial or final. 2108 ## If the latter, return leg id, if the former, return -leg id 2109 #if self.get('legs')[0].get('state') == True: 2110 # return leg.get('id') 2111 #else: 2112 # return model.get('particle_dict')[leg.get('id')].\ 2113 # get_anti_pdg_code() 2114 2115 #=============================================================================== 2116 # VertexList 2117 #=============================================================================== 2118 -class VertexList(PhysicsObjectList):
2119 """List of Vertex objects 2120 """ 2121 2122 orders = {} 2123
2124 - def is_valid_element(self, obj):
2125 """Test if object obj is a valid Vertex for the list.""" 2126 2127 return isinstance(obj, Vertex)
2128
2129 - def __init__(self, init_list=None, orders=None):
2130 """Creates a new list object, with an optional dictionary of 2131 coupling orders.""" 2132 2133 list.__init__(self) 2134 2135 if init_list is not None: 2136 for object in init_list: 2137 self.append(object) 2138 2139 if isinstance(orders, dict): 2140 self.orders = orders
2141
2142 2143 #=============================================================================== 2144 # Diagram 2145 #=============================================================================== 2146 -class Diagram(PhysicsObject):
2147 """Diagram: list of vertices (ordered) 2148 """ 2149
2150 - def default_setup(self):
2151 """Default values for all properties""" 2152 2153 self['vertices'] = VertexList() 2154 self['orders'] = {}
2155
2156 - def filter(self, name, value):
2157 """Filter for valid diagram property values.""" 2158 2159 if name == 'vertices': 2160 if not isinstance(value, VertexList): 2161 raise self.PhysicsObjectError, \ 2162 "%s is not a valid VertexList object" % str(value) 2163 2164 if name == 'orders': 2165 Interaction.filter(Interaction(), 'orders', value) 2166 2167 return True
2168
2169 - def get_sorted_keys(self):
2170 """Return particle property names as a nicely sorted list.""" 2171 2172 return ['vertices', 'orders']
2173
2174 - def nice_string(self):
2175 """Returns a nicely formatted string of the diagram content.""" 2176 2177 if self['vertices']: 2178 mystr = '(' 2179 for vert in self['vertices']: 2180 mystr = mystr + '(' 2181 for leg in vert['legs'][:-1]: 2182 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) + ',' 2183 2184 if self['vertices'].index(vert) < len(self['vertices']) - 1: 2185 # Do not want ">" in the last vertex 2186 mystr = mystr[:-1] + '>' 2187 mystr = mystr + str(vert['legs'][-1]['number']) + '(%s)' % str(vert['legs'][-1]['id']) + ',' 2188 mystr = mystr + 'id:' + str(vert['id']) + '),' 2189 mystr = mystr[:-1] + ')' 2190 mystr += " (%s)" % ",".join(["%s=%d" % (key, self['orders'][key]) \ 2191 for key in sorted(self['orders'].keys())]) 2192 return mystr 2193 else: 2194 return '()'
2195
2196 - def calculate_orders(self, model):
2197 """Calculate the actual coupling orders of this diagram. Note 2198 that the special order WEIGTHED corresponds to the sum of 2199 hierarchys for the couplings.""" 2200 2201 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')]) 2202 weight = 0 2203 for vertex in self['vertices']: 2204 if vertex.get('id') in [0,-1]: continue 2205 couplings = model.get('interaction_dict')[vertex.get('id')].\ 2206 get('orders') 2207 for coupling in couplings: 2208 coupling_orders[coupling] += couplings[coupling] 2209 weight += sum([model.get('order_hierarchy')[c]*n for \ 2210 (c,n) in couplings.items()]) 2211 coupling_orders['WEIGHTED'] = weight 2212 self.set('orders', coupling_orders)
2213
2214 - def pass_squared_order_constraints(self, diag_multiplier, squared_orders, 2215 sq_orders_types):
2216 """ Returns wether the contributiong consisting in the current diagram 2217 multiplied by diag_multiplier passes the *positive* squared_orders 2218 specified ( a dictionary ) of types sq_order_types (a dictionary whose 2219 values are the relational operator used to define the constraint of the 2220 order in key).""" 2221 2222 for order, value in squared_orders.items(): 2223 if value<0: 2224 continue 2225 combined_order = self.get_order(order) + \ 2226 diag_multiplier.get_order(order) 2227 if ( sq_orders_types[order]=='==' and combined_order != value ) or \ 2228 ( sq_orders_types[order] in ['=', '<='] and combined_order > value) or \ 2229 ( sq_orders_types[order]=='>' and combined_order <= value) : 2230 return False 2231 return True
2232
2233 - def get_order(self, order):
2234 """Return the order of this diagram. It returns 0 if it is not present.""" 2235 2236 try: 2237 return self['orders'][order] 2238 except Exception: 2239 return 0
2240
2241 - def renumber_legs(self, perm_map, leg_list):
2242 """Renumber legs in all vertices according to perm_map""" 2243 vertices = VertexList() 2244 min_dict = copy.copy(perm_map) 2245 # Dictionary from leg number to state 2246 state_dict = dict([(l.get('number'), l.get('state')) for l in leg_list]) 2247 # First renumber all legs in the n-1->1 vertices 2248 for vertex in self.get('vertices')[:-1]: 2249 vertex = copy.copy(vertex) 2250 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2251 for leg in leg_list[:-1]: 2252 leg.set('number', min_dict[leg.get('number')]) 2253 leg.set('state', state_dict[leg.get('number')]) 2254 min_number = min([leg.get('number') for leg in leg_list[:-1]]) 2255 leg = leg_list[-1] 2256 min_dict[leg.get('number')] = min_number 2257 # resulting leg is initial state if there is exactly one 2258 # initial state leg among the incoming legs 2259 state_dict[min_number] = len([l for l in leg_list[:-1] if \ 2260 not l.get('state')]) != 1 2261 leg.set('number', min_number) 2262 leg.set('state', state_dict[min_number]) 2263 vertex.set('legs', leg_list) 2264 vertices.append(vertex) 2265 # Now renumber the legs in final vertex 2266 vertex = copy.copy(self.get('vertices')[-1]) 2267 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2268 for leg in leg_list: 2269 leg.set('number', min_dict[leg.get('number')]) 2270 leg.set('state', state_dict[leg.get('number')]) 2271 vertex.set('legs', leg_list) 2272 vertices.append(vertex) 2273 # Finally create new diagram 2274 new_diag = copy.copy(self) 2275 new_diag.set('vertices', vertices) 2276 state_dict = {True:'T',False:'F'} 2277 return new_diag
2278
2279 - def get_vertex_leg_numbers(self):
2280 """Return a list of the number of legs in the vertices for 2281 this diagram""" 2282 2283 return [len(v.get('legs')) for v in self.get('vertices')]
2284
2285 - def get_num_configs(self, model, ninitial):
2286 """Return the maximum number of configs from this diagram, 2287 given by 2^(number of non-zero width s-channel propagators)""" 2288 2289 s_channels = [v.get_s_channel_id(model,ninitial) for v in \ 2290 self.get('vertices')[:-1]] 2291 num_props = len([i for i in s_channels if i != 0 and \ 2292 model.get_particle(i).get('width').lower() != 'zero']) 2293 2294 if num_props < 1: 2295 return 1 2296 else: 2297 return 2**num_props
2298
2299 - def get_flow_charge_diff(self, model):
2300 """return the difference of total diff of charge occuring on the 2301 lofw of the initial parton. return [None,None] if the two initial parton 2302 are connected and the (partial) value if None if the initial parton is 2303 not a fermiom""" 2304 2305 import madgraph.core.drawing as drawing 2306 drawdiag = drawing.FeynmanDiagram(self, model) 2307 drawdiag.load_diagram() 2308 out = [] 2309 2310 for v in drawdiag.initial_vertex: 2311 init_part = v.lines[0] 2312 if not init_part.is_fermion(): 2313 out.append(None) 2314 continue 2315 2316 init_charge = model.get_particle(init_part.id).get('charge') 2317 2318 l_last = init_part 2319 v_last = v 2320 vcurrent = l_last.end 2321 if vcurrent == v: 2322 vcurrent = l_last.begin 2323 security =0 2324 while not vcurrent.is_external(): 2325 if security > 1000: 2326 raise Exception, 'wrong diagram' 2327 next_l = [l for l in vcurrent.lines if l is not l_last and l.is_fermion()][0] 2328 next_v = next_l.end 2329 if next_v == vcurrent: 2330 next_v = next_l.begin 2331 l_last, vcurrent = next_l, next_v 2332 if vcurrent in drawdiag.initial_vertex: 2333 return [None, None] 2334 2335 out.append(model.get_particle(l_last.id).get('charge') - init_charge) 2336 return out
2337
2338 2339 #=============================================================================== 2340 # DiagramList 2341 #=============================================================================== 2342 -class DiagramList(PhysicsObjectList):
2343 """List of Diagram objects 2344 """ 2345
2346 - def is_valid_element(self, obj):
2347 """Test if object obj is a valid Diagram for the list.""" 2348 2349 return isinstance(obj, Diagram)
2350
2351 - def nice_string(self, indent=0):
2352 """Returns a nicely formatted string""" 2353 mystr = " " * indent + str(len(self)) + ' diagrams:\n' 2354 for i, diag in enumerate(self): 2355 mystr = mystr + " " * indent + str(i+1) + " " + \ 2356 diag.nice_string() + '\n' 2357 return mystr[:-1]
2358 2359 # Helper function 2360
2361 - def get_max_order(self,order):
2362 """ Return the order of the diagram in the list with the maximum coupling 2363 order for the coupling specified """ 2364 max_order=-1 2365 2366 for diag in self: 2367 if order in diag['orders'].keys(): 2368 if max_order==-1 or diag['orders'][order] > max_order: 2369 max_order = diag['orders'][order] 2370 2371 return max_order
2372
2373 - def apply_negative_sq_order(self, ref_diag_list, order, value, order_type):
2374 """ This function returns a fitlered version of the diagram list self 2375 which satisfy the negative squared_order constraint 'order' with negative 2376 value 'value' and of type 'order_type', assuming that the diagram_list 2377 it must be squared against is 'reg_diag_list'. It also returns the 2378 new postive target squared order which correspond to this negative order 2379 constraint. Example: u u~ > d d~ QED^2<=-2 means that one wants to 2380 pick terms only up to the the next-to-leading order contributiong in QED, 2381 which is QED=2 in this case, so that target_order=4 is returned.""" 2382 2383 # First we must compute all contributions to that order 2384 target_order = min(ref_diag_list.get_order_values(order))+\ 2385 min(self.get_order_values(order))+2*(-value-1) 2386 2387 new_list = self.apply_positive_sq_orders(ref_diag_list, 2388 {order:target_order}, {order:order_type}) 2389 2390 return new_list, target_order
2391
2392 - def apply_positive_sq_orders(self, ref_diag_list, sq_orders, sq_order_types):
2393 """ This function returns a filtered version of self which contain 2394 only the diagram which satisfy the positive squared order constraints 2395 sq_orders of type sq_order_types and assuming that the diagrams are 2396 multiplied with those of the reference diagram list ref_diag_list.""" 2397 2398 new_diag_list = DiagramList() 2399 for tested_diag in self: 2400 for ref_diag in ref_diag_list: 2401 if tested_diag.pass_squared_order_constraints(ref_diag, 2402 sq_orders,sq_order_types): 2403 new_diag_list.append(tested_diag) 2404 break 2405 return new_diag_list
2406
2407 - def get_min_order(self,order):
2408 """ Return the order of the diagram in the list with the mimimum coupling 2409 order for the coupling specified """ 2410 min_order=-1 2411 for diag in self: 2412 if order in diag['orders'].keys(): 2413 if min_order==-1 or diag['orders'][order] < min_order: 2414 min_order = diag['orders'][order] 2415 else: 2416 return 0 2417 2418 return min_order
2419
2420 - def get_order_values(self, order):
2421 """ Return the list of possible values appearing in the diagrams of this 2422 list for the order given in argument """ 2423 2424 values=set([]) 2425 for diag in self: 2426 if order in diag['orders'].keys(): 2427 values.add(diag['orders'][order]) 2428 else: 2429 values.add(0) 2430 2431 return list(values)
2432
2433 #=============================================================================== 2434 # Process 2435 #=============================================================================== 2436 -class Process(PhysicsObject):
2437 """Process: list of legs (ordered) 2438 dictionary of orders 2439 model 2440 process id 2441 """ 2442
2443 - def default_setup(self):
2444 """Default values for all properties""" 2445 2446 self['legs'] = LegList() 2447 # These define the orders restrict the born and loop amplitudes. 2448 self['orders'] = {} 2449 self['model'] = Model() 2450 # Optional number to identify the process 2451 self['id'] = 0 2452 self['uid'] = 0 # should be a uniq id number 2453 # Required s-channels are given as a list of id lists. Only 2454 # diagrams with all s-channels in any of the lists are 2455 # allowed. This enables generating e.g. Z/gamma as s-channel 2456 # propagators. 2457 self['required_s_channels'] = [] 2458 self['forbidden_onsh_s_channels'] = [] 2459 self['forbidden_s_channels'] = [] 2460 self['forbidden_particles'] = [] 2461 self['is_decay_chain'] = False 2462 self['overall_orders'] = {} 2463 # Decay chain processes associated with this process 2464 self['decay_chains'] = ProcessList() 2465 # Legs with decay chains substituted in 2466 self['legs_with_decays'] = LegList() 2467 # Loop particles if the process is to be computed at NLO 2468 self['perturbation_couplings']=[] 2469 # These orders restrict the order of the squared amplitude. 2470 # This dictionary possibly contains a key "WEIGHTED" which 2471 # gives the upper bound for the total weighted order of the 2472 # squared amplitude. 2473 self['squared_orders'] = {} 2474 # The squared order (sqorders) constraints above can either be upper 2475 # bound (<=) or exact match (==) depending on how they were specified 2476 # in the user input. This choice is stored in the dictionary below. 2477 # Notice that the upper bound is the default 2478 self['sqorders_types'] = {} 2479 self['has_born'] = True 2480 # The NLO_mode is always None for a tree-level process and can be 2481 # 'all', 'real', 'virt' for a loop process. 2482 self['NLO_mode'] = 'tree' 2483 # The user might want to have the individual matrix element evaluations 2484 # for specific values of the coupling orders. The list below specifies 2485 # what are the coupling names which need be individually treated. 2486 # For example, for the process p p > j j [] QED=2 (QED=2 is 2487 # then a squared order constraint), then QED will appear in the 2488 # 'split_orders' list so that the subroutine in matrix.f return the 2489 # evaluation of the matrix element individually for the pure QCD 2490 # contribution 'QCD=4 QED=0', the pure interference 'QCD=2 QED=2' and 2491 # the pure QED contribution of order 'QCD=0 QED=4'. 2492 self['split_orders'] = []
2493
2494 - def filter(self, name, value):
2495 """Filter for valid process property values.""" 2496 2497 if name in ['legs', 'legs_with_decays'] : 2498 if not isinstance(value, LegList): 2499 raise self.PhysicsObjectError, \ 2500 "%s is not a valid LegList object" % str(value) 2501 2502 if name in ['orders', 'overall_orders','squared_orders']: 2503 Interaction.filter(Interaction(), 'orders', value) 2504 2505 if name == 'sqorders_types': 2506 if not isinstance(value, dict): 2507 raise self.PhysicsObjectError, \ 2508 "%s is not a valid dictionary" % str(value) 2509 for order in value.keys()+value.values(): 2510 if not isinstance(order, str): 2511 raise self.PhysicsObjectError, \ 2512 "%s is not a valid string" % str(value) 2513 2514 if name == 'split_orders': 2515 if not isinstance(value, list): 2516 raise self.PhysicsObjectError, \ 2517 "%s is not a valid list" % str(value) 2518 for order in value: 2519 if not isinstance(order, str): 2520 raise self.PhysicsObjectError, \ 2521 "%s is not a valid string" % str(value) 2522 2523 if name == 'model': 2524 if not isinstance(value, Model): 2525 raise self.PhysicsObjectError, \ 2526 "%s is not a valid Model object" % str(value) 2527 if name in ['id', 'uid']: 2528 if not isinstance(value, int): 2529 raise self.PhysicsObjectError, \ 2530 "Process %s %s is not an integer" % (name, repr(value)) 2531 2532 if name == 'required_s_channels': 2533 if not isinstance(value, list): 2534 raise self.PhysicsObjectError, \ 2535 "%s is not a valid list" % str(value) 2536 for l in value: 2537 if not isinstance(l, list): 2538 raise self.PhysicsObjectError, \ 2539 "%s is not a valid list of lists" % str(value) 2540 for i in l: 2541 if not isinstance(i, int): 2542 raise self.PhysicsObjectError, \ 2543 "%s is not a valid list of integers" % str(l) 2544 if i == 0: 2545 raise self.PhysicsObjectError, \ 2546 "Not valid PDG code %d for s-channel particle" % i 2547 2548 if name in ['forbidden_onsh_s_channels', 'forbidden_s_channels']: 2549 if not isinstance(value, list): 2550 raise self.PhysicsObjectError, \ 2551 "%s is not a valid list" % str(value) 2552 for i in value: 2553 if not isinstance(i, int): 2554 raise self.PhysicsObjectError, \ 2555 "%s is not a valid list of integers" % str(value) 2556 if i == 0: 2557 raise self.PhysicsObjectError, \ 2558 "Not valid PDG code %d for s-channel particle" % str(value) 2559 2560 if name == 'forbidden_particles': 2561 if not isinstance(value, list): 2562 raise self.PhysicsObjectError, \ 2563 "%s is not a valid list" % str(value) 2564 for i in value: 2565 if not isinstance(i, int): 2566 raise self.PhysicsObjectError, \ 2567 "%s is not a valid list of integers" % str(value) 2568 if i <= 0: 2569 raise self.PhysicsObjectError, \ 2570 "Forbidden particles should have a positive PDG code" % str(value) 2571 2572 if name == 'perturbation_couplings': 2573 if not isinstance(value, list): 2574 raise self.PhysicsObjectError, \ 2575 "%s is not a valid list" % str(value) 2576 for order in value: 2577 if not isinstance(order, str): 2578 raise self.PhysicsObjectError, \ 2579 "%s is not a valid string" % str(value) 2580 2581 if name == 'is_decay_chain': 2582 if not isinstance(value, bool): 2583 raise self.PhysicsObjectError, \ 2584 "%s is not a valid bool" % str(value) 2585 2586 if name == 'has_born': 2587 if not isinstance(value, bool): 2588 raise self.PhysicsObjectError, \ 2589 "%s is not a valid bool" % str(value) 2590 2591 if name == 'decay_chains': 2592 if not isinstance(value, ProcessList): 2593 raise self.PhysicsObjectError, \ 2594 "%s is not a valid ProcessList" % str(value) 2595 2596 if name == 'NLO_mode': 2597 if value not in ['real','all','virt','tree']: 2598 raise self.PhysicsObjectError, \ 2599 "%s is not a valid NLO_mode" % str(value) 2600 return True
2601
2602 - def has_multiparticle_label(self):
2603 """ A process, not being a ProcessDefinition never carries multiple 2604 particles labels""" 2605 2606 return False
2607
2608 - def set(self, name, value):
2609 """Special set for forbidden particles - set to abs value.""" 2610 2611 if name == 'forbidden_particles': 2612 try: 2613 value = [abs(i) for i in value] 2614 except Exception: 2615 pass 2616 2617 if name == 'required_s_channels': 2618 # Required s-channels need to be a list of lists of ids 2619 if value and isinstance(value, list) and \ 2620 not isinstance(value[0], list): 2621 value = [value] 2622 2623 return super(Process, self).set(name, value) # call the mother routine
2624
2625 - def get_squared_order_type(self, order):
2626 """ Return what kind of squared order constraint was specified for the 2627 order 'order'.""" 2628 2629 if order in self['sqorders_types'].keys(): 2630 return self['sqorders_types'][order] 2631 else: 2632 # Default behavior '=' is interpreted as upper bound '<=' 2633 return '='
2634
2635 - def get(self, name):
2636 """Special get for legs_with_decays""" 2637 2638 if name == 'legs_with_decays': 2639 self.get_legs_with_decays() 2640 2641 if name == 'sqorders_types': 2642 # We must make sure that there is a type for each sqorder defined 2643 for order in self['squared_orders'].keys(): 2644 if order not in self['sqorders_types']: 2645 # Then assign its type to the default '=' 2646 self['sqorders_types'][order]='=' 2647 2648 return super(Process, self).get(name) # call the mother routine
2649
2650 - def get_sorted_keys(self):
2651 """Return process property names as a nicely sorted list.""" 2652 2653 return ['legs', 'orders', 'overall_orders', 'squared_orders', 2654 'model', 'id', 'required_s_channels', 2655 'forbidden_onsh_s_channels', 'forbidden_s_channels', 2656 'forbidden_particles', 'is_decay_chain', 'decay_chains', 2657 'legs_with_decays', 'perturbation_couplings', 'has_born', 2658 'NLO_mode','split_orders']
2659
2660 - def nice_string(self, indent=0, print_weighted = True):
2661 """Returns a nicely formated string about current process 2662 content. Since the WEIGHTED order is automatically set and added to 2663 the user-defined list of orders, it can be ommitted for some info 2664 displays.""" 2665 2666 mystr = " " * indent + "Process: " 2667 prevleg = None 2668 for leg in self['legs']: 2669 mypart = self['model'].get('particle_dict')[leg['id']] 2670 if prevleg and prevleg['state'] == False \ 2671 and leg['state'] == True: 2672 # Separate initial and final legs by > 2673 mystr = mystr + '> ' 2674 # Add required s-channels 2675 if self['required_s_channels'] and \ 2676 self['required_s_channels'][0]: 2677 mystr += "|".join([" ".join([self['model'].\ 2678 get('particle_dict')[req_id].get_name() \ 2679 for req_id in id_list]) \ 2680 for id_list in self['required_s_channels']]) 2681 mystr = mystr + ' > ' 2682 2683 mystr = mystr + mypart.get_name() + ' ' 2684 #mystr = mystr + '(%i) ' % leg['number'] 2685 prevleg = leg 2686 2687 # Add orders 2688 if self['orders']: 2689 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 2690 for key in self['orders'] if (print_weighted or key!='WEIGHTED') \ 2691 and not key in self['squared_orders'].keys()]) + ' ' 2692 2693 # Add perturbation_couplings 2694 if self['perturbation_couplings']: 2695 mystr = mystr + '[ ' 2696 if self['NLO_mode']!='tree': 2697 mystr = mystr + self['NLO_mode'] + ' = ' 2698 for order in self['perturbation_couplings']: 2699 mystr = mystr + order + ' ' 2700 mystr = mystr + '] ' 2701 2702 # Add squared orders 2703 if self['squared_orders']: 2704 mystr = mystr + " ".join([key + '^2%s%d'%\ 2705 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 2706 for key in self['squared_orders'].keys() \ 2707 if print_weighted or key!='WEIGHTED']) + ' ' 2708 2709 # Add forbidden s-channels 2710 if self['forbidden_onsh_s_channels']: 2711 mystr = mystr + '$ ' 2712 for forb_id in self['forbidden_onsh_s_channels']: 2713 forbpart = self['model'].get('particle_dict')[forb_id] 2714 mystr = mystr + forbpart.get_name() + ' ' 2715 2716 # Add double forbidden s-channels 2717 if self['forbidden_s_channels']: 2718 mystr = mystr + '$$ ' 2719 for forb_id in self['forbidden_s_channels']: 2720 forbpart = self['model'].get('particle_dict')[forb_id] 2721 mystr = mystr + forbpart.get_name() + ' ' 2722 2723 # Add forbidden particles 2724 if self['forbidden_particles']: 2725 mystr = mystr + '/ ' 2726 for forb_id in self['forbidden_particles']: 2727 forbpart = self['model'].get('particle_dict')[forb_id] 2728 mystr = mystr + forbpart.get_name() + ' ' 2729 2730 # Remove last space 2731 mystr = mystr[:-1] 2732 2733 if self.get('id') or self.get('overall_orders'): 2734 mystr += " @%d" % self.get('id') 2735 if self.get('overall_orders'): 2736 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 2737 for key in sorted(self['orders'])]) + ' ' 2738 2739 if not self.get('decay_chains'): 2740 return mystr 2741 2742 for decay in self['decay_chains']: 2743 mystr = mystr + '\n' + \ 2744 decay.nice_string(indent + 2).replace('Process', 'Decay') 2745 2746 return mystr
2747
2748 - def input_string(self):
2749 """Returns a process string corresponding to the input string 2750 in the command line interface.""" 2751 2752 mystr = "" 2753 prevleg = None 2754 2755 for leg in self['legs']: 2756 mypart = self['model'].get('particle_dict')[leg['id']] 2757 if prevleg and prevleg['state'] == False \ 2758 and leg['state'] == True: 2759 # Separate initial and final legs by ">" 2760 mystr = mystr + '> ' 2761 # Add required s-channels 2762 if self['required_s_channels'] and \ 2763 self['required_s_channels'][0]: 2764 mystr += "|".join([" ".join([self['model'].\ 2765 get('particle_dict')[req_id].get_name() \ 2766 for req_id in id_list]) \ 2767 for id_list in self['required_s_channels']]) 2768 mystr = mystr + '> ' 2769 2770 mystr = mystr + mypart.get_name() + ' ' 2771 #mystr = mystr + '(%i) ' % leg['number'] 2772 prevleg = leg 2773 2774 if self['orders']: 2775 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 2776 for key in self['orders']]) + ' ' 2777 2778 # Add perturbation orders 2779 if self['perturbation_couplings']: 2780 mystr = mystr + '[ ' 2781 if self['NLO_mode']: 2782 mystr = mystr + self['NLO_mode'] 2783 if not self['has_born']: 2784 mystr = mystr + '^2' 2785 mystr = mystr + '= ' 2786 2787 for order in self['perturbation_couplings']: 2788 mystr = mystr + order + ' ' 2789 mystr = mystr + '] ' 2790 2791 # Add squared orders 2792 if self['perturbation_couplings'] and self['squared_orders']: 2793 mystr = mystr + " ".join([key + '=' + repr(self['squared_orders'][key]) \ 2794 for key in self['squared_orders']]) + ' ' 2795 2796 # Add forbidden s-channels 2797 if self['forbidden_onsh_s_channels']: 2798 mystr = mystr + '$ ' 2799 for forb_id in self['forbidden_onsh_s_channels']: 2800 forbpart = self['model'].get('particle_dict')[forb_id] 2801 mystr = mystr + forbpart.get_name() + ' ' 2802 2803 # Add double forbidden s-channels 2804 if self['forbidden_s_channels']: 2805 mystr = mystr + '$$ ' 2806 for forb_id in self['forbidden_s_channels']: 2807 forbpart = self['model'].get('particle_dict')[forb_id] 2808 mystr = mystr + forbpart.get_name() + ' ' 2809 2810 # Add forbidden particles 2811 if self['forbidden_particles']: 2812 mystr = mystr + '/ ' 2813 for forb_id in self['forbidden_particles']: 2814 forbpart = self['model'].get('particle_dict')[forb_id] 2815 mystr = mystr + forbpart.get_name() + ' ' 2816 2817 # Remove last space 2818 mystr = mystr[:-1] 2819 2820 if self.get('overall_orders'): 2821 mystr += " @%d" % self.get('id') 2822 if self.get('overall_orders'): 2823 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 2824 for key in sorted(self['orders'])]) + ' ' 2825 2826 if not self.get('decay_chains'): 2827 return mystr 2828 2829 for decay in self['decay_chains']: 2830 paren1 = '' 2831 paren2 = '' 2832 if decay.get('decay_chains'): 2833 paren1 = '(' 2834 paren2 = ')' 2835 mystr += ', ' + paren1 + decay.input_string() + paren2 2836 2837 return mystr
2838
2839 - def base_string(self):
2840 """Returns a string containing only the basic process (w/o decays).""" 2841 2842 mystr = "" 2843 prevleg = None 2844 for leg in self.get_legs_with_decays(): 2845 mypart = self['model'].get('particle_dict')[leg['id']] 2846 if prevleg and prevleg['state'] == False \ 2847 and leg['state'] == True: 2848 # Separate initial and final legs by ">" 2849 mystr = mystr + '> ' 2850 mystr = mystr + mypart.get_name() + ' ' 2851 prevleg = leg 2852 2853 # Remove last space 2854 return mystr[:-1]
2855
2856 - def shell_string(self, schannel=True, forbid=True, main=True, pdg_order=False):
2857 """Returns process as string with '~' -> 'x', '>' -> '_', 2858 '+' -> 'p' and '-' -> 'm', including process number, 2859 intermediate s-channels and forbidden particles, 2860 pdg_order allow to order to leg order by pid.""" 2861 2862 mystr = "" 2863 if not self.get('is_decay_chain'): 2864 mystr += "%d_" % self['id'] 2865 2866 prevleg = None 2867 if pdg_order: 2868 legs = [l for l in self['legs'][1:]] 2869 def order_leg(l1,l2): 2870 id1 = l1.get('id') 2871 id2 = l2.get('id') 2872 return id2-id1
2873 legs.sort(cmp=order_leg) 2874 legs.insert(0, self['legs'][0]) 2875 else: 2876 legs = self['legs'] 2877 2878 2879 for leg in legs: 2880 mypart = self['model'].get('particle_dict')[leg['id']] 2881 if prevleg and prevleg['state'] == False \ 2882 and leg['state'] == True: 2883 # Separate initial and final legs by ">" 2884 mystr = mystr + '_' 2885 # Add required s-channels 2886 if self['required_s_channels'] and \ 2887 self['required_s_channels'][0] and schannel: 2888 mystr += "_or_".join(["".join([self['model'].\ 2889 get('particle_dict')[req_id].get_name() \ 2890 for req_id in id_list]) \ 2891 for id_list in self['required_s_channels']]) 2892 mystr = mystr + '_' 2893 if mypart['is_part']: 2894 mystr = mystr + mypart['name'] 2895 else: 2896 mystr = mystr + mypart['antiname'] 2897 prevleg = leg 2898 2899 # Check for forbidden particles 2900 if self['forbidden_particles'] and forbid: 2901 mystr = mystr + '_no_' 2902 for forb_id in self['forbidden_particles']: 2903 forbpart = self['model'].get('particle_dict')[forb_id] 2904 mystr = mystr + forbpart.get_name() 2905 2906 # Replace '~' with 'x' 2907 mystr = mystr.replace('~', 'x') 2908 # Replace '+' with 'p' 2909 mystr = mystr.replace('+', 'p') 2910 # Replace '-' with 'm' 2911 mystr = mystr.replace('-', 'm') 2912 # Just to be safe, remove all spaces 2913 mystr = mystr.replace(' ', '') 2914 2915 for decay in self.get('decay_chains'): 2916 mystr = mystr + "_" + decay.shell_string(schannel,forbid, main=False, 2917 pdg_order=pdg_order) 2918 2919 # Too long name are problematic so restrict them to a maximal of 70 char 2920 if len(mystr) > 64 and main: 2921 if schannel and forbid: 2922 out = self.shell_string(True, False, True, pdg_order) 2923 elif schannel: 2924 out = self.shell_string(False, False, True, pdg_order) 2925 else: 2926 out = mystr[:64] 2927 if not out.endswith('_%s' % self['uid']): 2928 out += '_%s' % self['uid'] 2929 return out 2930 2931 return mystr
2932
2933 - def shell_string_v4(self):
2934 """Returns process as v4-compliant string with '~' -> 'x' and 2935 '>' -> '_'""" 2936 2937 mystr = "%d_" % self['id'] 2938 prevleg = None 2939 for leg in self.get_legs_with_decays(): 2940 mypart = self['model'].get('particle_dict')[leg['id']] 2941 if prevleg and prevleg['state'] == False \ 2942 and leg['state'] == True: 2943 # Separate initial and final legs by ">" 2944 mystr = mystr + '_' 2945 if mypart['is_part']: 2946 mystr = mystr + mypart['name'] 2947 else: 2948 mystr = mystr + mypart['antiname'] 2949 prevleg = leg 2950 2951 # Replace '~' with 'x' 2952 mystr = mystr.replace('~', 'x') 2953 # Just to be safe, remove all spaces 2954 mystr = mystr.replace(' ', '') 2955 2956 return mystr
2957 2958 # Helper functions 2959
2960 - def are_negative_orders_present(self):
2961 """ Check iteratively that no coupling order constraint include negative 2962 values.""" 2963 2964 if any(val<0 for val in self.get('orders').values()+\ 2965 self.get('squared_orders').values()): 2966 return True 2967 2968 for procdef in self['decay_chains']: 2969 if procdef.are_negative_orders_present(): 2970 return True 2971 2972 return False
2973
2974 - def are_decays_perturbed(self):
2975 """ Check iteratively that the decayed processes are not perturbed """ 2976 2977 for procdef in self['decay_chains']: 2978 if procdef['perturbation_couplings'] or procdef.are_decays_perturbed(): 2979 return True 2980 return False
2981
2982 - def decays_have_squared_orders(self):
2983 """ Check iteratively that the decayed processes are not perturbed """ 2984 2985 for procdef in self['decay_chains']: 2986 if procdef['squared_orders']!={} or procdef.decays_have_squared_orders(): 2987 return True 2988 return False
2989
2990 - def get_ninitial(self):
2991 """Gives number of initial state particles""" 2992 2993 return len(filter(lambda leg: leg.get('state') == False, 2994 self.get('legs')))
2995
2996 - def get_initial_ids(self):
2997 """Gives the pdg codes for initial state particles""" 2998 2999 return [leg.get('id') for leg in \ 3000 filter(lambda leg: leg.get('state') == False, 3001 self.get('legs'))]
3002
3003 - def get_initial_pdg(self, number):
3004 """Return the pdg codes for initial state particles for beam number""" 3005 3006 return filter(lambda leg: leg.get('state') == False and\ 3007 leg.get('number') == number, 3008 self.get('legs'))[0].get('id')
3009
3010 - def get_initial_final_ids(self):
3011 """return a tuple of two tuple containing the id of the initial/final 3012 state particles. Each list is ordered""" 3013 3014 initial = [] 3015 final = [l.get('id') for l in self.get('legs')\ 3016 if l.get('state') or initial.append(l.get('id'))] 3017 initial.sort() 3018 final.sort() 3019 return (tuple(initial), tuple(final))
3020
3021 - def get_final_ids_after_decay(self):
3022 """Give the pdg code of the process including decay""" 3023 3024 finals = self.get_final_ids() 3025 for proc in self.get('decay_chains'): 3026 init = proc.get_initial_ids()[0] 3027 #while 1: 3028 try: 3029 pos = finals.index(init) 3030 except: 3031 break 3032 finals[pos] = proc.get_final_ids_after_decay() 3033 output = [] 3034 for d in finals: 3035 if isinstance(d, list): 3036 output += d 3037 else: 3038 output.append(d) 3039 3040 return output
3041 3042
3043 - def get_final_legs(self):
3044 """Gives the final state legs""" 3045 3046 return filter(lambda leg: leg.get('state') == True, 3047 self.get('legs'))
3048
3049 - def get_final_ids(self):
3050 """Gives the pdg codes for final state particles""" 3051 3052 return [l.get('id') for l in self.get_final_legs()]
3053 3054
3055 - def get_legs_with_decays(self):
3056 """Return process with all decay chains substituted in.""" 3057 3058 if self['legs_with_decays']: 3059 return self['legs_with_decays'] 3060 3061 legs = copy.deepcopy(self.get('legs')) 3062 org_decay_chains = copy.copy(self.get('decay_chains')) 3063 sorted_decay_chains = [] 3064 # Sort decay chains according to leg order 3065 for leg in legs: 3066 if not leg.get('state'): continue 3067 org_ids = [l.get('legs')[0].get('id') for l in \ 3068 org_decay_chains] 3069 if leg.get('id') in org_ids: 3070 sorted_decay_chains.append(org_decay_chains.pop(\ 3071 org_ids.index(leg.get('id')))) 3072 assert not org_decay_chains 3073 ileg = 0 3074 for decay in sorted_decay_chains: 3075 while legs[ileg].get('state') == False or \ 3076 legs[ileg].get('id') != decay.get('legs')[0].get('id'): 3077 ileg = ileg + 1 3078 decay_legs = decay.get_legs_with_decays() 3079 legs = legs[:ileg] + decay_legs[1:] + legs[ileg+1:] 3080 ileg = ileg + len(decay_legs) - 1 3081 3082 # Replace legs with copies 3083 legs = [copy.copy(l) for l in legs] 3084 3085 for ileg, leg in enumerate(legs): 3086 leg.set('number', ileg + 1) 3087 3088 self['legs_with_decays'] = LegList(legs) 3089 3090 return self['legs_with_decays']
3091
3092 - def list_for_sort(self):
3093 """Output a list that can be compared to other processes as: 3094 [id, sorted(initial leg ids), sorted(final leg ids), 3095 sorted(decay list_for_sorts)]""" 3096 3097 sorted_list = [self.get('id'), 3098 sorted(self.get_initial_ids()), 3099 sorted(self.get_final_ids())] 3100 3101 if self.get('decay_chains'): 3102 sorted_list.extend(sorted([d.list_for_sort() for d in \ 3103 self.get('decay_chains')])) 3104 3105 return sorted_list
3106
3107 - def compare_for_sort(self, other):
3108 """Sorting routine which allows to sort processes for 3109 comparison. Compare only process id and legs.""" 3110 3111 if self.list_for_sort() > other.list_for_sort(): 3112 return 1 3113 if self.list_for_sort() < other.list_for_sort(): 3114 return -1 3115 return 0
3116
3117 - def identical_particle_factor(self):
3118 """Calculate the denominator factor for identical final state particles 3119 """ 3120 3121 final_legs = filter(lambda leg: leg.get('state') == True, \ 3122 self.get_legs_with_decays()) 3123 3124 identical_indices = {} 3125 for leg in final_legs: 3126 if leg.get('id') in identical_indices: 3127 identical_indices[leg.get('id')] = \ 3128 identical_indices[leg.get('id')] + 1 3129 else: 3130 identical_indices[leg.get('id')] = 1 3131 return reduce(lambda x, y: x * y, [ math.factorial(val) for val in \ 3132 identical_indices.values() ], 1)
3133
3134 - def check_expansion_orders(self):
3135 """Ensure that maximum expansion orders from the model are 3136 properly taken into account in the process""" 3137 3138 # Ensure that expansion orders are taken into account 3139 expansion_orders = self.get('model').get('expansion_order') 3140 orders = self.get('orders') 3141 sq_orders = self.get('squared_orders') 3142 3143 tmp = [(k,v) for (k,v) in expansion_orders.items() if 0 < v < 99] 3144 for (k,v) in tmp: 3145 if k in orders: 3146 if v < orders[k]: 3147 if k in sq_orders.keys() and \ 3148 (sq_orders[k]>v or sq_orders[k]<0): 3149 logger.warning( 3150 '''The process with the squared coupling order (%s^2%s%s) specified can potentially 3151 recieve contributions with powers of the coupling %s larger than the maximal 3152 value allowed by the model builder (%s). Hence, MG5_aMC sets the amplitude order 3153 for that coupling to be this maximal one. '''%(k,self.get('sqorders_types')[k], 3154 self.get('squared_orders')[k],k,v)) 3155 else: 3156 logger.warning( 3157 '''The coupling order (%s=%s) specified is larger than the one allowed 3158 by the model builder. The maximal value allowed is %s. 3159 We set the %s order to this value''' % (k,orders[k],v,k)) 3160 orders[k] = v 3161 else: 3162 orders[k] = v
3163
3164 - def __eq__(self, other):
3165 """Overloading the equality operator, so that only comparison 3166 of process id and legs is being done, using compare_for_sort.""" 3167 3168 if not isinstance(other, Process): 3169 return False 3170 3171 return self.compare_for_sort(other) == 0
3172
3173 - def __ne__(self, other):
3174 return not self.__eq__(other)
3175
3176 #=============================================================================== 3177 # ProcessList 3178 #=============================================================================== 3179 -class ProcessList(PhysicsObjectList):
3180 """List of Process objects 3181 """ 3182
3183 - def is_valid_element(self, obj):
3184 """Test if object obj is a valid Process for the list.""" 3185 3186 return isinstance(obj, Process)
3187
3188 - def nice_string(self, indent = 0):
3189 """Returns a nicely formatted string of the matrix element processes.""" 3190 3191 mystr = "\n".join([p.nice_string(indent) for p in self]) 3192 3193 return mystr
3194
3195 #=============================================================================== 3196 # ProcessDefinition 3197 #=============================================================================== 3198 -class ProcessDefinition(Process):
3199 """ProcessDefinition: list of multilegs (ordered) 3200 dictionary of orders 3201 model 3202 process id 3203 """ 3204
3205 - def default_setup(self):
3206 """Default values for all properties""" 3207 3208 super(ProcessDefinition, self).default_setup() 3209 3210 self['legs'] = MultiLegList() 3211 # Decay chain processes associated with this process 3212 self['decay_chains'] = ProcessDefinitionList() 3213 if 'legs_with_decays' in self: del self['legs_with_decays']
3214
3215 - def filter(self, name, value):
3216 """Filter for valid process property values.""" 3217 3218 if name == 'legs': 3219 if not isinstance(value, MultiLegList): 3220 raise self.PhysicsObjectError, \ 3221 "%s is not a valid MultiLegList object" % str(value) 3222 elif name == 'decay_chains': 3223 if not isinstance(value, ProcessDefinitionList): 3224 raise self.PhysicsObjectError, \ 3225 "%s is not a valid ProcessDefinitionList" % str(value) 3226 3227 else: 3228 return super(ProcessDefinition, self).filter(name, value) 3229 3230 return True
3231
3232 - def has_multiparticle_label(self):
3233 """ Check that this process definition will yield a single process, as 3234 each multileg only has one leg""" 3235 3236 for mleg in self['legs']: 3237 if len(mleg['ids'])>1: 3238 return True 3239 3240 return False
3241
3242 - def get_sorted_keys(self):
3243 """Return process property names as a nicely sorted list.""" 3244 3245 keys = super(ProcessDefinition, self).get_sorted_keys() 3246 keys.remove('legs_with_decays') 3247 3248 return keys
3249
3250 - def get_minimum_WEIGHTED(self):
3251 """Retrieve the minimum starting guess for WEIGHTED order, to 3252 use in find_optimal_process_orders in MultiProcess diagram 3253 generation (as well as particles and hierarchy). The algorithm: 3254 3255 1) Pick out the legs in the multiprocess according to the 3256 highest hierarchy represented (so don't mix particles from 3257 different hierarchy classes in the same multiparticles!) 3258 3259 2) Find the starting maximum WEIGHTED order as the sum of the 3260 highest n-2 weighted orders 3261 3262 3) Pick out required s-channel particle hierarchies, and use 3263 the highest of the maximum WEIGHTED order from the legs and 3264 the minimum WEIGHTED order extracted from 2*s-channel 3265 hierarchys plus the n-2-2*(number of s-channels) lowest 3266 leg weighted orders. 3267 """ 3268 3269 model = self.get('model') 3270 3271 # Extract hierarchy and particles corresponding to the 3272 # different hierarchy levels from the model 3273 particles, hierarchy = model.get_particles_hierarchy() 3274 3275 # Find legs corresponding to the different orders 3276 # making sure we look at lowest hierarchy first for each leg 3277 max_order_now = [] 3278 new_legs = copy.copy(self.get('legs')) 3279 for parts, value in zip(particles, hierarchy): 3280 ileg = 0 3281 while ileg < len(new_legs): 3282 if any([id in parts for id in new_legs[ileg].get('ids')]): 3283 max_order_now.append(value) 3284 new_legs.pop(ileg) 3285 else: 3286 ileg += 1 3287 3288 # Now remove the two lowest orders to get maximum (since the 3289 # number of interactions is n-2) 3290 max_order_now = sorted(max_order_now)[2:] 3291 3292 # Find s-channel propagators corresponding to the different orders 3293 max_order_prop = [] 3294 for idlist in self.get('required_s_channels'): 3295 max_order_prop.append([0,0]) 3296 for id in idlist: 3297 for parts, value in zip(particles, hierarchy): 3298 if id in parts: 3299 max_order_prop[-1][0] += 2*value 3300 max_order_prop[-1][1] += 1 3301 break 3302 3303 if max_order_prop: 3304 if len(max_order_prop) >1: 3305 max_order_prop = min(*max_order_prop, key=lambda x:x[0]) 3306 else: 3307 max_order_prop = max_order_prop[0] 3308 3309 # Use either the max_order from the external legs or 3310 # the maximum order from the s-channel propagators, plus 3311 # the appropriate lowest orders from max_order_now 3312 max_order_now = max(sum(max_order_now), 3313 max_order_prop[0] + \ 3314 sum(max_order_now[:-2 * max_order_prop[1]])) 3315 else: 3316 max_order_now = sum(max_order_now) 3317 3318 return max_order_now, particles, hierarchy
3319
3320 - def nice_string(self, indent=0, print_weighted=False):
3321 """Returns a nicely formated string about current process 3322 content""" 3323 3324 mystr = " " * indent + "Process: " 3325 prevleg = None 3326 for leg in self['legs']: 3327 myparts = \ 3328 "/".join([self['model'].get('particle_dict')[id].get_name() \ 3329 for id in leg.get('ids')]) 3330 if prevleg and prevleg['state'] == False \ 3331 and leg['state'] == True: 3332 # Separate initial and final legs by ">" 3333 mystr = mystr + '> ' 3334 # Add required s-channels 3335 if self['required_s_channels'] and \ 3336 self['required_s_channels'][0]: 3337 mystr += "|".join([" ".join([self['model'].\ 3338 get('particle_dict')[req_id].get_name() \ 3339 for req_id in id_list]) \ 3340 for id_list in self['required_s_channels']]) 3341 mystr = mystr + '> ' 3342 3343 mystr = mystr + myparts + ' ' 3344 #mystr = mystr + '(%i) ' % leg['number'] 3345 prevleg = leg 3346 3347 # Add forbidden s-channels 3348 if self['forbidden_onsh_s_channels']: 3349 mystr = mystr + '$ ' 3350 for forb_id in self['forbidden_onsh_s_channels']: 3351 forbpart = self['model'].get('particle_dict')[forb_id] 3352 mystr = mystr + forbpart.get_name() + ' ' 3353 3354 # Add double forbidden s-channels 3355 if self['forbidden_s_channels']: 3356 mystr = mystr + '$$ ' 3357 for forb_id in self['forbidden_s_channels']: 3358 forbpart = self['model'].get('particle_dict')[forb_id] 3359 mystr = mystr + forbpart.get_name() + ' ' 3360 3361 # Add forbidden particles 3362 if self['forbidden_particles']: 3363 mystr = mystr + '/ ' 3364 for forb_id in self['forbidden_particles']: 3365 forbpart = self['model'].get('particle_dict')[forb_id] 3366 mystr = mystr + forbpart.get_name() + ' ' 3367 3368 if self['orders']: 3369 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3370 for key in sorted(self['orders'])]) + ' ' 3371 3372 # Add perturbation_couplings 3373 if self['perturbation_couplings']: 3374 mystr = mystr + '[ ' 3375 if self['NLO_mode']: 3376 mystr = mystr + self['NLO_mode'] + ' = ' 3377 for order in self['perturbation_couplings']: 3378 mystr = mystr + order + ' ' 3379 mystr = mystr + '] ' 3380 3381 if self['squared_orders']: 3382 mystr = mystr + " ".join([key + '^2%s%d'%\ 3383 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 3384 for key in self['squared_orders'].keys() \ 3385 if print_weighted or key!='WEIGHTED']) + ' ' 3386 3387 # Remove last space 3388 mystr = mystr[:-1] 3389 3390 if self.get('id') or self.get('overall_orders'): 3391 mystr += " @%d" % self.get('id') 3392 if self.get('overall_orders'): 3393 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3394 for key in sorted(self['orders'])]) + ' ' 3395 3396 if not self.get('decay_chains'): 3397 return mystr 3398 3399 for decay in self['decay_chains']: 3400 mystr = mystr + '\n' + \ 3401 decay.nice_string(indent + 2).replace('Process', 'Decay') 3402 3403 return mystr
3404
3405 - def get_process_with_legs(self, LegList):
3406 """ Return a Process object which has the same properties of this 3407 ProcessDefinition but with the specified LegList as legs attribute. 3408 """ 3409 3410 return Process({\ 3411 'legs': LegList, 3412 'model':self.get('model'), 3413 'id': self.get('id'), 3414 'orders': self.get('orders'), 3415 'sqorders_types': self.get('sqorders_types'), 3416 'squared_orders': self.get('squared_orders'), 3417 'has_born': self.get('has_born'), 3418 'required_s_channels': self.get('required_s_channels'), 3419 'forbidden_onsh_s_channels': self.get('forbidden_onsh_s_channels'), 3420 'forbidden_s_channels': self.get('forbidden_s_channels'), 3421 'forbidden_particles': self.get('forbidden_particles'), 3422 'perturbation_couplings': self.get('perturbation_couplings'), 3423 'is_decay_chain': self.get('is_decay_chain'), 3424 'overall_orders': self.get('overall_orders'), 3425 'split_orders': self.get('split_orders'), 3426 'NLO_mode': self.get('NLO_mode') 3427 })
3428
3429 - def get_process(self, initial_state_ids, final_state_ids):
3430 """ Return a Process object which has the same properties of this 3431 ProcessDefinition but with the specified given leg ids. """ 3432 3433 # First make sure that the desired particle ids belong to those defined 3434 # in this process definition. 3435 my_isids = [leg.get('ids') for leg in self.get('legs') \ 3436 if not leg.get('state')] 3437 my_fsids = [leg.get('ids') for leg in self.get('legs') \ 3438 if leg.get('state')] 3439 for i, is_id in enumerate(initial_state_ids): 3440 assert is_id in my_isids[i] 3441 for i, fs_id in enumerate(final_state_ids): 3442 assert fs_id in my_fsids[i] 3443 3444 return self.get_process_with_legs(LegList(\ 3445 [Leg({'id': id, 'state':False}) for id in initial_state_ids] + \ 3446 [Leg({'id': id, 'state':True}) for id in final_state_ids]))
3447
3448 - def __eq__(self, other):
3449 """Overloading the equality operator, so that only comparison 3450 of process id and legs is being done, using compare_for_sort.""" 3451 3452 return super(Process, self).__eq__(other)
3453
3454 #=============================================================================== 3455 # ProcessDefinitionList 3456 #=============================================================================== 3457 -class ProcessDefinitionList(PhysicsObjectList):
3458 """List of ProcessDefinition objects 3459 """ 3460
3461 - def is_valid_element(self, obj):
3462 """Test if object obj is a valid ProcessDefinition for the list.""" 3463 3464 return isinstance(obj, ProcessDefinition)
3465
3466 #=============================================================================== 3467 # Global helper functions 3468 #=============================================================================== 3469 3470 -def make_unique(doubletlist):
3471 """Make sure there are no doublets in the list doubletlist. 3472 Note that this is a slow implementation, so don't use if speed 3473 is needed""" 3474 3475 assert isinstance(doubletlist, list), \ 3476 "Argument to make_unique must be list" 3477 3478 3479 uniquelist = [] 3480 for elem in doubletlist: 3481 if elem not in uniquelist: 3482 uniquelist.append(elem) 3483 3484 doubletlist[:] = uniquelist[:]
3485