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(): part.partial_widths[key] = rep_pattern.sub(replace, value) 1517 1518 #ensure that the particle_dict is up-to-date 1519 self['particle_dict'] ='' 1520 self.get('particle_dict')
1521 1522 1523
1524 - def get_first_non_pdg(self):
1525 """Return the first positive number that is not a valid PDG code""" 1526 return [c for c in range(1, len(self.get('particles')) + 1) if \ 1527 c not in self.get('particle_dict').keys()][0]
1528
1529 - def write_param_card(self):
1530 """Write out the param_card, and return as string.""" 1531 1532 import models.write_param_card as writter 1533 out = StringIO.StringIO() # it's suppose to be written in a file 1534 param = writter.ParamCardWriter(self) 1535 param.define_output_file(out) 1536 param.write_card() 1537 return out.getvalue()
1538 1539 @ staticmethod
1540 - def load_default_name():
1541 """ load the default for name convention """ 1542 1543 logger.info('Change particles name to pass to MG5 convention') 1544 default = {} 1545 for line in open(os.path.join(MG5DIR, 'input', \ 1546 'particles_name_default.txt')): 1547 line = line.lstrip() 1548 if line.startswith('#'): 1549 continue 1550 1551 args = line.split() 1552 if len(args) != 2: 1553 logger.warning('Invalid syntax in interface/default_name:\n %s' % line) 1554 continue 1555 default[int(args[0])] = args[1].lower() 1556 1557 return default
1558
1559 - def change_mass_to_complex_scheme(self):
1560 """modify the expression changing the mass to complex mass scheme""" 1561 1562 # 1) Find All input parameter mass and width associated 1563 # Add a internal parameter and replace mass with that param 1564 # 2) Find All mass fixed by the model and width associated 1565 # -> Both need to be fixed with a real() /Imag() 1566 # 3) Find All width fixed by the model 1567 # -> Need to be fixed with a real() 1568 # 4) Fix the Yukawa mass to the value of the complex mass/ real mass 1569 # 5) Loop through all expression and modify those accordingly 1570 # Including all parameter expression as complex 1571 1572 to_change = {} 1573 mass_widths = [] # parameter which should stay real 1574 for particle in self.get('particles'): 1575 m = particle.get('width') 1576 if m in mass_widths: 1577 continue 1578 mass_widths.append(particle.get('width')) 1579 mass_widths.append(particle.get('mass')) 1580 if particle.get('width') == 'ZERO': 1581 #everything is fine since the width is zero 1582 continue 1583 width = self.get_parameter(particle.get('width')) 1584 if not isinstance(width, ParamCardVariable): 1585 width.expr = 're(%s)' % width.expr 1586 if particle.get('mass') != 'ZERO': 1587 mass = self.get_parameter(particle.get('mass')) 1588 # special SM treatment to change the gauge scheme automatically. 1589 if particle.get('pdg_code') == 24: 1590 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)))': 1591 # Make MW an external parameter 1592 MW = ParamCardVariable(mass.name, mass.value, 'MASS', [24]) 1593 if not MW.value: 1594 MW.value = 80.385 1595 self.get('parameters')[('external',)].append(MW) 1596 self.get('parameters')[mass.depend].remove(mass) 1597 # Make Gf an internal parameter 1598 new_param = ModelVariable('Gf', 1599 '-aEW*MZ**2*cmath.pi/(cmath.sqrt(2)*%(MW)s**2*(%(MW)s**2 - MZ**2))' %\ 1600 {'MW': mass.name}, 'complex', mass.depend) 1601 Gf = self.get_parameter('Gf') 1602 self.get('parameters')[('external',)].remove(Gf) 1603 self.add_param(new_param, ['aEW']) 1604 # Use the new mass for the future modification 1605 mass = MW 1606 #option with prefixing 1607 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)))': 1608 # Make MW an external parameter 1609 MW = ParamCardVariable(mass.name, mass.value, 'MASS', [24]) 1610 if not MW.value: 1611 MW.value = 80.385 1612 self.get('parameters')[('external',)].append(MW) 1613 self.get('parameters')[mass.depend].remove(mass) 1614 # Make Gf an internal parameter 1615 new_param = ModelVariable('mdl_Gf', 1616 '-mdl_aEW*mdl_MZ**2*cmath.pi/(cmath.sqrt(2)*%(MW)s**2*(%(MW)s**2 - mdl_MZ**2))' %\ 1617 {'MW': mass.name}, 'complex', mass.depend) 1618 Gf = self.get_parameter('mdl_Gf') 1619 self.get('parameters')[('external',)].remove(Gf) 1620 self.add_param(new_param, ['mdl_aEW']) 1621 # Use the new mass for the future modification 1622 mass = MW 1623 elif isinstance(mass, ModelVariable): 1624 logger.warning('W mass is not an external parameter. This is not adviced for the complex mass scheme.') 1625 1626 # Add A new parameter CMASS 1627 #first compute the dependencies (as,...) 1628 depend = list(set(mass.depend + width.depend)) 1629 if len(depend)>1 and 'external' in depend: 1630 depend.remove('external') 1631 depend = tuple(depend) 1632 if depend == ('external',): 1633 depend = () 1634 1635 # Create the new parameter 1636 if isinstance(mass, ParamCardVariable): 1637 New_param = ModelVariable('CMASS_'+mass.name, 1638 'cmath.sqrt(%(mass)s**2 - complex(0,1) * %(mass)s * %(width)s)' \ 1639 % {'mass': mass.name, 'width': width.name}, 1640 'complex', depend) 1641 else: 1642 New_param = ModelVariable('CMASS_'+mass.name, 1643 mass.expr, 'complex', depend) 1644 # Modify the treatment of the width in this case 1645 if not isinstance(width, ParamCardVariable): 1646 width.expr = '- im(%s**2) / cmath.sqrt(re(%s**2))' % (mass.expr, mass.expr) 1647 else: 1648 # Remove external parameter from the param_card 1649 New_width = ModelVariable(width.name, 1650 '-1 * im(CMASS_%s**2) / %s' % (mass.name, mass.name), 'real', mass.depend) 1651 self.get('parameters')[('external',)].remove(width) 1652 self.add_param(New_param, (mass,)) 1653 self.add_param(New_width, (New_param,)) 1654 mass.expr = 'cmath.sqrt(re(%s**2))' % mass.expr 1655 to_change[mass.name] = New_param.name 1656 continue 1657 1658 mass.expr = 're(%s)' % mass.expr 1659 self.add_param(New_param, (mass, width)) 1660 to_change[mass.name] = New_param.name 1661 1662 # Remove the Yukawa and fix those accordingly to the mass/complex mass 1663 yukawas = [p for p in self.get('parameters')[('external',)] 1664 if p.lhablock.lower() == 'yukawa'] 1665 for yukawa in yukawas: 1666 # clean the pevious parameter 1667 self.get('parameters')[('external',)].remove(yukawa) 1668 1669 particle = self.get_particle(yukawa.lhacode[0]) 1670 mass = self.get_parameter(particle.get('mass')) 1671 1672 # add the new parameter in the correct category 1673 if mass.depend == ('external',): 1674 depend = () 1675 else: 1676 depend = mass.depend 1677 1678 New_param = ModelVariable(yukawa.name, mass.name, 'real', depend) 1679 1680 # Add it in the model at the correct place (for the dependences) 1681 if mass.name in to_change: 1682 expr = 'CMASS_%s' % mass.name 1683 else: 1684 expr = mass.name 1685 param_depend = self.get_parameter(expr) 1686 self.add_param(New_param, [param_depend]) 1687 1688 if not to_change: 1689 return 1690 1691 1692 # So at this stage we still need to modify all parameters depending of 1693 # particle's mass. In addition all parameter (but mass/width/external 1694 # parameter) should be pass in complex mode. 1695 pat = '|'.join(to_change.keys()) 1696 pat = r'(%s)\b' % pat 1697 pat = re.compile(pat) 1698 def replace(match): 1699 return to_change[match.group()]
1700 1701 # Modify the parameters 1702 for dep, list_param in self['parameters'].items(): 1703 for param in list_param: 1704 if param.name.startswith('CMASS_') or param.name in mass_widths or\ 1705 isinstance(param, ParamCardVariable): 1706 continue 1707 param.type = 'complex' 1708 # print param.expr, to_change 1709 1710 param.expr = pat.sub(replace, param.expr) 1711 1712 # Modify the couplings 1713 for dep, list_coup in self['couplings'].items(): 1714 for coup in list_coup: 1715 coup.expr = pat.sub(replace, coup.expr) 1716
1717 - def add_param(self, new_param, depend_param):
1718 """add the parameter in the list of parameter in a correct position""" 1719 1720 pos = 0 1721 for i,param in enumerate(self.get('parameters')[new_param.depend]): 1722 if param.name in depend_param: 1723 pos = i + 1 1724 self.get('parameters')[new_param.depend].insert(pos, new_param)
1725
1726 1727 #def __repr__(self): 1728 # """ """ 1729 # raise Exception 1730 # return "Model(%s)" % self.get_name() 1731 #__str__ = __repr__ 1732 ################################################################################ 1733 # Class for Parameter / Coupling 1734 ################################################################################ 1735 -class ModelVariable(object):
1736 """A Class for storing the information about coupling/ parameter""" 1737
1738 - def __init__(self, name, expression, type, depend=()):
1739 """Initialize a new parameter/coupling""" 1740 1741 self.name = name 1742 self.expr = expression # python expression 1743 self.type = type # real/complex 1744 self.depend = depend # depend on some other parameter -tuple- 1745 self.value = None
1746
1747 - def __eq__(self, other):
1748 """Object with same name are identical, If the object is a string we check 1749 if the attribute name is equal to this string""" 1750 1751 try: 1752 return other.name == self.name 1753 except Exception: 1754 return other == self.name
1755
1756 -class ParamCardVariable(ModelVariable):
1757 """ A class for storing the information linked to all the parameter 1758 which should be define in the param_card.dat""" 1759 1760 depend = ('external',) 1761 type = 'real' 1762
1763 - def __init__(self, name, value, lhablock, lhacode):
1764 """Initialize a new ParamCardVariable 1765 name: name of the variable 1766 value: default numerical value 1767 lhablock: name of the block in the param_card.dat 1768 lhacode: code associate to the variable 1769 """ 1770 self.name = name 1771 self.value = value 1772 self.lhablock = lhablock 1773 self.lhacode = lhacode
1774
1775 1776 #=============================================================================== 1777 # Classes used in diagram generation and process definition: 1778 # Leg, Vertex, Diagram, Process 1779 #=============================================================================== 1780 1781 #=============================================================================== 1782 # Leg 1783 #=============================================================================== 1784 -class Leg(PhysicsObject):
1785 """Leg object: id (Particle), number, I/F state, flag from_group 1786 """ 1787
1788 - def default_setup(self):
1789 """Default values for all properties""" 1790 1791 self['id'] = 0 1792 self['number'] = 0 1793 # state: True = final, False = initial (boolean to save memory) 1794 self['state'] = True 1795 #self['loop_line'] = False 1796 self['loop_line'] = False 1797 # from_group: Used in diagram generation 1798 self['from_group'] = True 1799 # onshell: decaying leg (True), forbidden s-channel (False), none (None) 1800 self['onshell'] = None
1801
1802 - def filter(self, name, value):
1803 """Filter for valid leg property values.""" 1804 1805 if name in ['id', 'number']: 1806 if not isinstance(value, int): 1807 raise self.PhysicsObjectError, \ 1808 "%s is not a valid integer for leg id" % str(value) 1809 1810 if name == 'state': 1811 if not isinstance(value, bool): 1812 raise self.PhysicsObjectError, \ 1813 "%s is not a valid leg state (True|False)" % \ 1814 str(value) 1815 1816 if name == 'from_group': 1817 if not isinstance(value, bool) and value != None: 1818 raise self.PhysicsObjectError, \ 1819 "%s is not a valid boolean for leg flag from_group" % \ 1820 str(value) 1821 1822 if name == 'loop_line': 1823 if not isinstance(value, bool) and value != None: 1824 raise self.PhysicsObjectError, \ 1825 "%s is not a valid boolean for leg flag loop_line" % \ 1826 str(value) 1827 1828 if name == 'onshell': 1829 if not isinstance(value, bool) and value != None: 1830 raise self.PhysicsObjectError, \ 1831 "%s is not a valid boolean for leg flag onshell" % \ 1832 str(value) 1833 return True
1834
1835 - def get_sorted_keys(self):
1836 """Return particle property names as a nicely sorted list.""" 1837 1838 return ['id', 'number', 'state', 'from_group', 'loop_line', 'onshell']
1839
1840 - def is_fermion(self, model):
1841 """Returns True if the particle corresponding to the leg is a 1842 fermion""" 1843 1844 assert isinstance(model, Model), "%s is not a model" % str(model) 1845 1846 return model.get('particle_dict')[self['id']].is_fermion()
1847
1848 - def is_incoming_fermion(self, model):
1849 """Returns True if leg is an incoming fermion, i.e., initial 1850 particle or final antiparticle""" 1851 1852 assert isinstance(model, Model), "%s is not a model" % str(model) 1853 1854 part = model.get('particle_dict')[self['id']] 1855 return part.is_fermion() and \ 1856 (self.get('state') == False and part.get('is_part') or \ 1857 self.get('state') == True and not part.get('is_part'))
1858
1859 - def is_outgoing_fermion(self, model):
1860 """Returns True if leg is an outgoing fermion, i.e., initial 1861 antiparticle or final particle""" 1862 1863 assert isinstance(model, Model), "%s is not a model" % str(model) 1864 1865 part = model.get('particle_dict')[self['id']] 1866 return part.is_fermion() and \ 1867 (self.get('state') == True and part.get('is_part') or \ 1868 self.get('state') == False and not part.get('is_part'))
1869 1870 # Helper function. We don't overload the == operator because it might be useful 1871 # to define it differently than that later. 1872
1873 - def same(self, leg):
1874 """ Returns true if the leg in argument has the same ID and the same numer """ 1875 1876 # In case we want to check this leg with an integer in the tagging procedure, 1877 # then it only has to match the leg number. 1878 if isinstance(leg,int): 1879 if self['number']==leg: 1880 return True 1881 else: 1882 return False 1883 1884 # If using a Leg object instead, we also want to compare the other relevant 1885 # properties. 1886 elif isinstance(leg, Leg): 1887 if self['id']==leg.get('id') and \ 1888 self['number']==leg.get('number') and \ 1889 self['loop_line']==leg.get('loop_line') : 1890 return True 1891 else: 1892 return False 1893 1894 else : 1895 return False
1896 1897 # Make sure sort() sorts lists of legs according to 'number'
1898 - def __lt__(self, other):
1899 return self['number'] < other['number']
1900
1901 #=============================================================================== 1902 # LegList 1903 #=============================================================================== 1904 -class LegList(PhysicsObjectList):
1905 """List of Leg objects 1906 """ 1907
1908 - def is_valid_element(self, obj):
1909 """Test if object obj is a valid Leg for the list.""" 1910 1911 return isinstance(obj, Leg)
1912 1913 # Helper methods for diagram generation 1914
1915 - def from_group_elements(self):
1916 """Return all elements which have 'from_group' True""" 1917 1918 return filter(lambda leg: leg.get('from_group'), self)
1919
1920 - def minimum_one_from_group(self):
1921 """Return True if at least one element has 'from_group' True""" 1922 1923 return len(self.from_group_elements()) > 0
1924
1925 - def minimum_two_from_group(self):
1926 """Return True if at least two elements have 'from_group' True""" 1927 1928 return len(self.from_group_elements()) > 1
1929
1930 - def can_combine_to_1(self, ref_dict_to1):
1931 """If has at least one 'from_group' True and in ref_dict_to1, 1932 return the return list from ref_dict_to1, otherwise return False""" 1933 if self.minimum_one_from_group(): 1934 return ref_dict_to1.has_key(tuple(sorted([leg.get('id') for leg in self]))) 1935 else: 1936 return False
1937
1938 - def can_combine_to_0(self, ref_dict_to0, is_decay_chain=False):
1939 """If has at least two 'from_group' True and in ref_dict_to0, 1940 1941 return the vertex (with id from ref_dict_to0), otherwise return None 1942 1943 If is_decay_chain = True, we only allow clustering of the 1944 initial leg, since we want this to be the last wavefunction to 1945 be evaluated. 1946 """ 1947 if is_decay_chain: 1948 # Special treatment - here we only allow combination to 0 1949 # if the initial leg (marked by from_group = None) is 1950 # unclustered, since we want this to stay until the very 1951 # end. 1952 return any(leg.get('from_group') == None for leg in self) and \ 1953 ref_dict_to0.has_key(tuple(sorted([leg.get('id') \ 1954 for leg in self]))) 1955 1956 if self.minimum_two_from_group(): 1957 return ref_dict_to0.has_key(tuple(sorted([leg.get('id') for leg in self]))) 1958 else: 1959 return False
1960
1961 - def get_outgoing_id_list(self, model):
1962 """Returns the list of ids corresponding to the leglist with 1963 all particles outgoing""" 1964 1965 res = [] 1966 1967 assert isinstance(model, Model), "Error! model not model" 1968 1969 1970 for leg in self: 1971 if leg.get('state') == False: 1972 res.append(model.get('particle_dict')[leg.get('id')].get_anti_pdg_code()) 1973 else: 1974 res.append(leg.get('id')) 1975 1976 return res
1977
1978 - def sort(self,*args, **opts):
1979 """Match with FKSLegList""" 1980 Opts=copy.copy(opts) 1981 if 'pert' in Opts.keys(): 1982 del Opts['pert'] 1983 return super(LegList,self).sort(*args, **Opts)
1984
1985 1986 #=============================================================================== 1987 # MultiLeg 1988 #=============================================================================== 1989 -class MultiLeg(PhysicsObject):
1990 """MultiLeg object: ids (Particle or particles), I/F state 1991 """ 1992
1993 - def default_setup(self):
1994 """Default values for all properties""" 1995 1996 self['ids'] = [] 1997 self['state'] = True
1998
1999 - def filter(self, name, value):
2000 """Filter for valid multileg property values.""" 2001 2002 if name == 'ids': 2003 if not isinstance(value, list): 2004 raise self.PhysicsObjectError, \ 2005 "%s is not a valid list" % str(value) 2006 for i in value: 2007 if not isinstance(i, int): 2008 raise self.PhysicsObjectError, \ 2009 "%s is not a valid list of integers" % str(value) 2010 2011 if name == 'state': 2012 if not isinstance(value, bool): 2013 raise self.PhysicsObjectError, \ 2014 "%s is not a valid leg state (initial|final)" % \ 2015 str(value) 2016 2017 return True
2018
2019 - def get_sorted_keys(self):
2020 """Return particle property names as a nicely sorted list.""" 2021 2022 return ['ids', 'state']
2023
2024 #=============================================================================== 2025 # LegList 2026 #=============================================================================== 2027 -class MultiLegList(PhysicsObjectList):
2028 """List of MultiLeg objects 2029 """ 2030
2031 - def is_valid_element(self, obj):
2032 """Test if object obj is a valid MultiLeg for the list.""" 2033 2034 return isinstance(obj, MultiLeg)
2035
2036 #=============================================================================== 2037 # Vertex 2038 #=============================================================================== 2039 -class Vertex(PhysicsObject):
2040 """Vertex: list of legs (ordered), id (Interaction) 2041 """ 2042 2043 sorted_keys = ['id', 'legs'] 2044
2045 - def default_setup(self):
2046 """Default values for all properties""" 2047 2048 self['id'] = 0 2049 self['legs'] = LegList()
2050
2051 - def filter(self, name, value):
2052 """Filter for valid vertex property values.""" 2053 2054 if name == 'id': 2055 if not isinstance(value, int): 2056 raise self.PhysicsObjectError, \ 2057 "%s is not a valid integer for vertex id" % str(value) 2058 2059 if name == 'legs': 2060 if not isinstance(value, LegList): 2061 raise self.PhysicsObjectError, \ 2062 "%s is not a valid LegList object" % str(value) 2063 2064 return True
2065
2066 - def get_sorted_keys(self):
2067 """Return particle property names as a nicely sorted list.""" 2068 2069 return self.sorted_keys #['id', 'legs']
2070
2071 - def get_s_channel_id(self, model, ninitial):
2072 """Returns the id for the last leg as an outgoing 2073 s-channel. Returns 0 if leg is t-channel, or if identity 2074 vertex. Used to check for required and forbidden s-channel 2075 particles.""" 2076 2077 leg = self.get('legs')[-1] 2078 2079 if ninitial == 1: 2080 # For one initial particle, all legs are s-channel 2081 # Only need to flip particle id if state is False 2082 if leg.get('state') == True: 2083 return leg.get('id') 2084 else: 2085 return model.get('particle_dict')[leg.get('id')].\ 2086 get_anti_pdg_code() 2087 2088 # Number of initial particles is at least 2 2089 if self.get('id') == 0 or \ 2090 leg.get('state') == False: 2091 # identity vertex or t-channel particle 2092 return 0 2093 2094 if leg.get('loop_line'): 2095 # Loop lines never count as s-channel 2096 return 0 2097 2098 # Check if the particle number is <= ninitial 2099 # In that case it comes from initial and we should switch direction 2100 if leg.get('number') > ninitial: 2101 return leg.get('id') 2102 else: 2103 return model.get('particle_dict')[leg.get('id')].\ 2104 get_anti_pdg_code()
2105
2106 ## Check if the other legs are initial or final. 2107 ## If the latter, return leg id, if the former, return -leg id 2108 #if self.get('legs')[0].get('state') == True: 2109 # return leg.get('id') 2110 #else: 2111 # return model.get('particle_dict')[leg.get('id')].\ 2112 # get_anti_pdg_code() 2113 2114 #=============================================================================== 2115 # VertexList 2116 #=============================================================================== 2117 -class VertexList(PhysicsObjectList):
2118 """List of Vertex objects 2119 """ 2120 2121 orders = {} 2122
2123 - def is_valid_element(self, obj):
2124 """Test if object obj is a valid Vertex for the list.""" 2125 2126 return isinstance(obj, Vertex)
2127
2128 - def __init__(self, init_list=None, orders=None):
2129 """Creates a new list object, with an optional dictionary of 2130 coupling orders.""" 2131 2132 list.__init__(self) 2133 2134 if init_list is not None: 2135 for object in init_list: 2136 self.append(object) 2137 2138 if isinstance(orders, dict): 2139 self.orders = orders
2140
2141 2142 #=============================================================================== 2143 # Diagram 2144 #=============================================================================== 2145 -class Diagram(PhysicsObject):
2146 """Diagram: list of vertices (ordered) 2147 """ 2148
2149 - def default_setup(self):
2150 """Default values for all properties""" 2151 2152 self['vertices'] = VertexList() 2153 self['orders'] = {}
2154
2155 - def filter(self, name, value):
2156 """Filter for valid diagram property values.""" 2157 2158 if name == 'vertices': 2159 if not isinstance(value, VertexList): 2160 raise self.PhysicsObjectError, \ 2161 "%s is not a valid VertexList object" % str(value) 2162 2163 if name == 'orders': 2164 Interaction.filter(Interaction(), 'orders', value) 2165 2166 return True
2167
2168 - def get_sorted_keys(self):
2169 """Return particle property names as a nicely sorted list.""" 2170 2171 return ['vertices', 'orders']
2172
2173 - def nice_string(self):
2174 """Returns a nicely formatted string of the diagram content.""" 2175 2176 if self['vertices']: 2177 mystr = '(' 2178 for vert in self['vertices']: 2179 mystr = mystr + '(' 2180 for leg in vert['legs'][:-1]: 2181 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) + ',' 2182 2183 if self['vertices'].index(vert) < len(self['vertices']) - 1: 2184 # Do not want ">" in the last vertex 2185 mystr = mystr[:-1] + '>' 2186 mystr = mystr + str(vert['legs'][-1]['number']) + '(%s)' % str(vert['legs'][-1]['id']) + ',' 2187 mystr = mystr + 'id:' + str(vert['id']) + '),' 2188 mystr = mystr[:-1] + ')' 2189 mystr += " (%s)" % ",".join(["%s=%d" % (key, self['orders'][key]) \ 2190 for key in sorted(self['orders'].keys())]) 2191 return mystr 2192 else: 2193 return '()'
2194
2195 - def calculate_orders(self, model):
2196 """Calculate the actual coupling orders of this diagram. Note 2197 that the special order WEIGTHED corresponds to the sum of 2198 hierarchys for the couplings.""" 2199 2200 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')]) 2201 weight = 0 2202 for vertex in self['vertices']: 2203 if vertex.get('id') in [0,-1]: continue 2204 couplings = model.get('interaction_dict')[vertex.get('id')].\ 2205 get('orders') 2206 for coupling in couplings: 2207 coupling_orders[coupling] += couplings[coupling] 2208 weight += sum([model.get('order_hierarchy')[c]*n for \ 2209 (c,n) in couplings.items()]) 2210 coupling_orders['WEIGHTED'] = weight 2211 self.set('orders', coupling_orders)
2212
2213 - def pass_squared_order_constraints(self, diag_multiplier, squared_orders, 2214 sq_orders_types):
2215 """ Returns wether the contributiong consisting in the current diagram 2216 multiplied by diag_multiplier passes the *positive* squared_orders 2217 specified ( a dictionary ) of types sq_order_types (a dictionary whose 2218 values are the relational operator used to define the constraint of the 2219 order in key).""" 2220 2221 for order, value in squared_orders.items(): 2222 if value<0: 2223 continue 2224 combined_order = self.get_order(order) + \ 2225 diag_multiplier.get_order(order) 2226 if ( sq_orders_types[order]=='==' and combined_order != value ) or \ 2227 ( sq_orders_types[order] in ['=', '<='] and combined_order > value) or \ 2228 ( sq_orders_types[order]=='>' and combined_order <= value) : 2229 return False 2230 return True
2231
2232 - def get_order(self, order):
2233 """Return the order of this diagram. It returns 0 if it is not present.""" 2234 2235 try: 2236 return self['orders'][order] 2237 except Exception: 2238 return 0
2239
2240 - def renumber_legs(self, perm_map, leg_list):
2241 """Renumber legs in all vertices according to perm_map""" 2242 vertices = VertexList() 2243 min_dict = copy.copy(perm_map) 2244 # Dictionary from leg number to state 2245 state_dict = dict([(l.get('number'), l.get('state')) for l in leg_list]) 2246 # First renumber all legs in the n-1->1 vertices 2247 for vertex in self.get('vertices')[:-1]: 2248 vertex = copy.copy(vertex) 2249 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2250 for leg in leg_list[:-1]: 2251 leg.set('number', min_dict[leg.get('number')]) 2252 leg.set('state', state_dict[leg.get('number')]) 2253 min_number = min([leg.get('number') for leg in leg_list[:-1]]) 2254 leg = leg_list[-1] 2255 min_dict[leg.get('number')] = min_number 2256 # resulting leg is initial state if there is exactly one 2257 # initial state leg among the incoming legs 2258 state_dict[min_number] = len([l for l in leg_list[:-1] if \ 2259 not l.get('state')]) != 1 2260 leg.set('number', min_number) 2261 leg.set('state', state_dict[min_number]) 2262 vertex.set('legs', leg_list) 2263 vertices.append(vertex) 2264 # Now renumber the legs in final vertex 2265 vertex = copy.copy(self.get('vertices')[-1]) 2266 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2267 for leg in leg_list: 2268 leg.set('number', min_dict[leg.get('number')]) 2269 leg.set('state', state_dict[leg.get('number')]) 2270 vertex.set('legs', leg_list) 2271 vertices.append(vertex) 2272 # Finally create new diagram 2273 new_diag = copy.copy(self) 2274 new_diag.set('vertices', vertices) 2275 state_dict = {True:'T',False:'F'} 2276 return new_diag
2277
2278 - def get_vertex_leg_numbers(self):
2279 """Return a list of the number of legs in the vertices for 2280 this diagram""" 2281 2282 return [len(v.get('legs')) for v in self.get('vertices')]
2283
2284 - def get_num_configs(self, model, ninitial):
2285 """Return the maximum number of configs from this diagram, 2286 given by 2^(number of non-zero width s-channel propagators)""" 2287 2288 s_channels = [v.get_s_channel_id(model,ninitial) for v in \ 2289 self.get('vertices')[:-1]] 2290 num_props = len([i for i in s_channels if i != 0 and \ 2291 model.get_particle(i).get('width').lower() != 'zero']) 2292 2293 if num_props < 1: 2294 return 1 2295 else: 2296 return 2**num_props
2297
2298 - def get_flow_charge_diff(self, model):
2299 """return the difference of total diff of charge occuring on the 2300 lofw of the initial parton. return [None,None] if the two initial parton 2301 are connected and the (partial) value if None if the initial parton is 2302 not a fermiom""" 2303 2304 import madgraph.core.drawing as drawing 2305 drawdiag = drawing.FeynmanDiagram(self, model) 2306 drawdiag.load_diagram() 2307 out = [] 2308 2309 for v in drawdiag.initial_vertex: 2310 init_part = v.lines[0] 2311 if not init_part.is_fermion(): 2312 out.append(None) 2313 continue 2314 2315 init_charge = model.get_particle(init_part.id).get('charge') 2316 2317 l_last = init_part 2318 v_last = v 2319 vcurrent = l_last.end 2320 if vcurrent == v: 2321 vcurrent = l_last.begin 2322 security =0 2323 while not vcurrent.is_external(): 2324 if security > 1000: 2325 raise Exception, 'wrong diagram' 2326 next_l = [l for l in vcurrent.lines if l is not l_last and l.is_fermion()][0] 2327 next_v = next_l.end 2328 if next_v == vcurrent: 2329 next_v = next_l.begin 2330 l_last, vcurrent = next_l, next_v 2331 if vcurrent in drawdiag.initial_vertex: 2332 return [None, None] 2333 2334 out.append(model.get_particle(l_last.id).get('charge') - init_charge) 2335 return out
2336
2337 2338 #=============================================================================== 2339 # DiagramList 2340 #=============================================================================== 2341 -class DiagramList(PhysicsObjectList):
2342 """List of Diagram objects 2343 """ 2344
2345 - def is_valid_element(self, obj):
2346 """Test if object obj is a valid Diagram for the list.""" 2347 2348 return isinstance(obj, Diagram)
2349
2350 - def nice_string(self, indent=0):
2351 """Returns a nicely formatted string""" 2352 mystr = " " * indent + str(len(self)) + ' diagrams:\n' 2353 for i, diag in enumerate(self): 2354 mystr = mystr + " " * indent + str(i+1) + " " + \ 2355 diag.nice_string() + '\n' 2356 return mystr[:-1]
2357 2358 # Helper function 2359
2360 - def get_max_order(self,order):
2361 """ Return the order of the diagram in the list with the maximum coupling 2362 order for the coupling specified """ 2363 max_order=-1 2364 2365 for diag in self: 2366 if order in diag['orders'].keys(): 2367 if max_order==-1 or diag['orders'][order] > max_order: 2368 max_order = diag['orders'][order] 2369 2370 return max_order
2371
2372 - def apply_negative_sq_order(self, ref_diag_list, order, value, order_type):
2373 """ This function returns a fitlered version of the diagram list self 2374 which satisfy the negative squared_order constraint 'order' with negative 2375 value 'value' and of type 'order_type', assuming that the diagram_list 2376 it must be squared against is 'reg_diag_list'. It also returns the 2377 new postive target squared order which correspond to this negative order 2378 constraint. Example: u u~ > d d~ QED^2<=-2 means that one wants to 2379 pick terms only up to the the next-to-leading order contributiong in QED, 2380 which is QED=2 in this case, so that target_order=4 is returned.""" 2381 2382 # First we must compute all contributions to that order 2383 target_order = min(ref_diag_list.get_order_values(order))+\ 2384 min(self.get_order_values(order))+2*(-value-1) 2385 2386 new_list = self.apply_positive_sq_orders(ref_diag_list, 2387 {order:target_order}, {order:order_type}) 2388 2389 return new_list, target_order
2390
2391 - def apply_positive_sq_orders(self, ref_diag_list, sq_orders, sq_order_types):
2392 """ This function returns a filtered version of self which contain 2393 only the diagram which satisfy the positive squared order constraints 2394 sq_orders of type sq_order_types and assuming that the diagrams are 2395 multiplied with those of the reference diagram list ref_diag_list.""" 2396 2397 new_diag_list = DiagramList() 2398 for tested_diag in self: 2399 for ref_diag in ref_diag_list: 2400 if tested_diag.pass_squared_order_constraints(ref_diag, 2401 sq_orders,sq_order_types): 2402 new_diag_list.append(tested_diag) 2403 break 2404 return new_diag_list
2405
2406 - def get_min_order(self,order):
2407 """ Return the order of the diagram in the list with the mimimum coupling 2408 order for the coupling specified """ 2409 min_order=-1 2410 for diag in self: 2411 if order in diag['orders'].keys(): 2412 if min_order==-1 or diag['orders'][order] < min_order: 2413 min_order = diag['orders'][order] 2414 else: 2415 return 0 2416 2417 return min_order
2418
2419 - def get_order_values(self, order):
2420 """ Return the list of possible values appearing in the diagrams of this 2421 list for the order given in argument """ 2422 2423 values=set([]) 2424 for diag in self: 2425 if order in diag['orders'].keys(): 2426 values.add(diag['orders'][order]) 2427 else: 2428 values.add(0) 2429 2430 return list(values)
2431
2432 #=============================================================================== 2433 # Process 2434 #=============================================================================== 2435 -class Process(PhysicsObject):
2436 """Process: list of legs (ordered) 2437 dictionary of orders 2438 model 2439 process id 2440 """ 2441
2442 - def default_setup(self):
2443 """Default values for all properties""" 2444 2445 self['legs'] = LegList() 2446 # These define the orders restrict the born and loop amplitudes. 2447 self['orders'] = {} 2448 self['model'] = Model() 2449 # Optional number to identify the process 2450 self['id'] = 0 2451 self['uid'] = 0 # should be a uniq id number 2452 # Required s-channels are given as a list of id lists. Only 2453 # diagrams with all s-channels in any of the lists are 2454 # allowed. This enables generating e.g. Z/gamma as s-channel 2455 # propagators. 2456 self['required_s_channels'] = [] 2457 self['forbidden_onsh_s_channels'] = [] 2458 self['forbidden_s_channels'] = [] 2459 self['forbidden_particles'] = [] 2460 self['is_decay_chain'] = False 2461 self['overall_orders'] = {} 2462 # Decay chain processes associated with this process 2463 self['decay_chains'] = ProcessList() 2464 # Legs with decay chains substituted in 2465 self['legs_with_decays'] = LegList() 2466 # Loop particles if the process is to be computed at NLO 2467 self['perturbation_couplings']=[] 2468 # These orders restrict the order of the squared amplitude. 2469 # This dictionary possibly contains a key "WEIGHTED" which 2470 # gives the upper bound for the total weighted order of the 2471 # squared amplitude. 2472 self['squared_orders'] = {} 2473 # The squared order (sqorders) constraints above can either be upper 2474 # bound (<=) or exact match (==) depending on how they were specified 2475 # in the user input. This choice is stored in the dictionary below. 2476 # Notice that the upper bound is the default 2477 self['sqorders_types'] = {} 2478 self['has_born'] = True 2479 # The NLO_mode is always None for a tree-level process and can be 2480 # 'all', 'real', 'virt' for a loop process. 2481 self['NLO_mode'] = 'tree' 2482 # The user might want to have the individual matrix element evaluations 2483 # for specific values of the coupling orders. The list below specifies 2484 # what are the coupling names which need be individually treated. 2485 # For example, for the process p p > j j [] QED=2 (QED=2 is 2486 # then a squared order constraint), then QED will appear in the 2487 # 'split_orders' list so that the subroutine in matrix.f return the 2488 # evaluation of the matrix element individually for the pure QCD 2489 # contribution 'QCD=4 QED=0', the pure interference 'QCD=2 QED=2' and 2490 # the pure QED contribution of order 'QCD=0 QED=4'. 2491 self['split_orders'] = []
2492
2493 - def filter(self, name, value):
2494 """Filter for valid process property values.""" 2495 2496 if name in ['legs', 'legs_with_decays'] : 2497 if not isinstance(value, LegList): 2498 raise self.PhysicsObjectError, \ 2499 "%s is not a valid LegList object" % str(value) 2500 2501 if name in ['orders', 'overall_orders','squared_orders']: 2502 Interaction.filter(Interaction(), 'orders', value) 2503 2504 if name == 'sqorders_types': 2505 if not isinstance(value, dict): 2506 raise self.PhysicsObjectError, \ 2507 "%s is not a valid dictionary" % str(value) 2508 for order in value.keys()+value.values(): 2509 if not isinstance(order, str): 2510 raise self.PhysicsObjectError, \ 2511 "%s is not a valid string" % str(value) 2512 2513 if name == 'split_orders': 2514 if not isinstance(value, list): 2515 raise self.PhysicsObjectError, \ 2516 "%s is not a valid list" % str(value) 2517 for order in value: 2518 if not isinstance(order, str): 2519 raise self.PhysicsObjectError, \ 2520 "%s is not a valid string" % str(value) 2521 2522 if name == 'model': 2523 if not isinstance(value, Model): 2524 raise self.PhysicsObjectError, \ 2525 "%s is not a valid Model object" % str(value) 2526 if name in ['id', 'uid']: 2527 if not isinstance(value, int): 2528 raise self.PhysicsObjectError, \ 2529 "Process %s %s is not an integer" % (name, repr(value)) 2530 2531 if name == 'required_s_channels': 2532 if not isinstance(value, list): 2533 raise self.PhysicsObjectError, \ 2534 "%s is not a valid list" % str(value) 2535 for l in value: 2536 if not isinstance(l, list): 2537 raise self.PhysicsObjectError, \ 2538 "%s is not a valid list of lists" % str(value) 2539 for i in l: 2540 if not isinstance(i, int): 2541 raise self.PhysicsObjectError, \ 2542 "%s is not a valid list of integers" % str(l) 2543 if i == 0: 2544 raise self.PhysicsObjectError, \ 2545 "Not valid PDG code %d for s-channel particle" % i 2546 2547 if name in ['forbidden_onsh_s_channels', 'forbidden_s_channels']: 2548 if not isinstance(value, list): 2549 raise self.PhysicsObjectError, \ 2550 "%s is not a valid list" % str(value) 2551 for i in value: 2552 if not isinstance(i, int): 2553 raise self.PhysicsObjectError, \ 2554 "%s is not a valid list of integers" % str(value) 2555 if i == 0: 2556 raise self.PhysicsObjectError, \ 2557 "Not valid PDG code %d for s-channel particle" % str(value) 2558 2559 if name == 'forbidden_particles': 2560 if not isinstance(value, list): 2561 raise self.PhysicsObjectError, \ 2562 "%s is not a valid list" % str(value) 2563 for i in value: 2564 if not isinstance(i, int): 2565 raise self.PhysicsObjectError, \ 2566 "%s is not a valid list of integers" % str(value) 2567 if i <= 0: 2568 raise self.PhysicsObjectError, \ 2569 "Forbidden particles should have a positive PDG code" % str(value) 2570 2571 if name == 'perturbation_couplings': 2572 if not isinstance(value, list): 2573 raise self.PhysicsObjectError, \ 2574 "%s is not a valid list" % str(value) 2575 for order in value: 2576 if not isinstance(order, str): 2577 raise self.PhysicsObjectError, \ 2578 "%s is not a valid string" % str(value) 2579 2580 if name == 'is_decay_chain': 2581 if not isinstance(value, bool): 2582 raise self.PhysicsObjectError, \ 2583 "%s is not a valid bool" % str(value) 2584 2585 if name == 'has_born': 2586 if not isinstance(value, bool): 2587 raise self.PhysicsObjectError, \ 2588 "%s is not a valid bool" % str(value) 2589 2590 if name == 'decay_chains': 2591 if not isinstance(value, ProcessList): 2592 raise self.PhysicsObjectError, \ 2593 "%s is not a valid ProcessList" % str(value) 2594 2595 if name == 'NLO_mode': 2596 if value not in ['real','all','virt','tree']: 2597 raise self.PhysicsObjectError, \ 2598 "%s is not a valid NLO_mode" % str(value) 2599 return True
2600
2601 - def has_multiparticle_label(self):
2602 """ A process, not being a ProcessDefinition never carries multiple 2603 particles labels""" 2604 2605 return False
2606
2607 - def set(self, name, value):
2608 """Special set for forbidden particles - set to abs value.""" 2609 2610 if name == 'forbidden_particles': 2611 try: 2612 value = [abs(i) for i in value] 2613 except Exception: 2614 pass 2615 2616 if name == 'required_s_channels': 2617 # Required s-channels need to be a list of lists of ids 2618 if value and isinstance(value, list) and \ 2619 not isinstance(value[0], list): 2620 value = [value] 2621 2622 return super(Process, self).set(name, value) # call the mother routine
2623
2624 - def get_squared_order_type(self, order):
2625 """ Return what kind of squared order constraint was specified for the 2626 order 'order'.""" 2627 2628 if order in self['sqorders_types'].keys(): 2629 return self['sqorders_types'][order] 2630 else: 2631 # Default behavior '=' is interpreted as upper bound '<=' 2632 return '='
2633
2634 - def get(self, name):
2635 """Special get for legs_with_decays""" 2636 2637 if name == 'legs_with_decays': 2638 self.get_legs_with_decays() 2639 2640 if name == 'sqorders_types': 2641 # We must make sure that there is a type for each sqorder defined 2642 for order in self['squared_orders'].keys(): 2643 if order not in self['sqorders_types']: 2644 # Then assign its type to the default '=' 2645 self['sqorders_types'][order]='=' 2646 2647 return super(Process, self).get(name) # call the mother routine
2648
2649 - def get_sorted_keys(self):
2650 """Return process property names as a nicely sorted list.""" 2651 2652 return ['legs', 'orders', 'overall_orders', 'squared_orders', 2653 'model', 'id', 'required_s_channels', 2654 'forbidden_onsh_s_channels', 'forbidden_s_channels', 2655 'forbidden_particles', 'is_decay_chain', 'decay_chains', 2656 'legs_with_decays', 'perturbation_couplings', 'has_born', 2657 'NLO_mode','split_orders']
2658
2659 - def nice_string(self, indent=0, print_weighted = True):
2660 """Returns a nicely formated string about current process 2661 content. Since the WEIGHTED order is automatically set and added to 2662 the user-defined list of orders, it can be ommitted for some info 2663 displays.""" 2664 2665 mystr = " " * indent + "Process: " 2666 prevleg = None 2667 for leg in self['legs']: 2668 mypart = self['model'].get('particle_dict')[leg['id']] 2669 if prevleg and prevleg['state'] == False \ 2670 and leg['state'] == True: 2671 # Separate initial and final legs by > 2672 mystr = mystr + '> ' 2673 # Add required s-channels 2674 if self['required_s_channels'] and \ 2675 self['required_s_channels'][0]: 2676 mystr += "|".join([" ".join([self['model'].\ 2677 get('particle_dict')[req_id].get_name() \ 2678 for req_id in id_list]) \ 2679 for id_list in self['required_s_channels']]) 2680 mystr = mystr + ' > ' 2681 2682 mystr = mystr + mypart.get_name() + ' ' 2683 #mystr = mystr + '(%i) ' % leg['number'] 2684 prevleg = leg 2685 2686 # Add orders 2687 if self['orders']: 2688 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 2689 for key in self['orders'] if (print_weighted or key!='WEIGHTED') \ 2690 and not key in self['squared_orders'].keys()]) + ' ' 2691 2692 # Add perturbation_couplings 2693 if self['perturbation_couplings']: 2694 mystr = mystr + '[ ' 2695 if self['NLO_mode']!='tree': 2696 mystr = mystr + self['NLO_mode'] + ' = ' 2697 for order in self['perturbation_couplings']: 2698 mystr = mystr + order + ' ' 2699 mystr = mystr + '] ' 2700 2701 # Add squared orders 2702 if self['squared_orders']: 2703 mystr = mystr + " ".join([key + '^2%s%d'%\ 2704 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 2705 for key in self['squared_orders'].keys() \ 2706 if print_weighted or key!='WEIGHTED']) + ' ' 2707 2708 # Add forbidden s-channels 2709 if self['forbidden_onsh_s_channels']: 2710 mystr = mystr + '$ ' 2711 for forb_id in self['forbidden_onsh_s_channels']: 2712 forbpart = self['model'].get('particle_dict')[forb_id] 2713 mystr = mystr + forbpart.get_name() + ' ' 2714 2715 # Add double forbidden s-channels 2716 if self['forbidden_s_channels']: 2717 mystr = mystr + '$$ ' 2718 for forb_id in self['forbidden_s_channels']: 2719 forbpart = self['model'].get('particle_dict')[forb_id] 2720 mystr = mystr + forbpart.get_name() + ' ' 2721 2722 # Add forbidden particles 2723 if self['forbidden_particles']: 2724 mystr = mystr + '/ ' 2725 for forb_id in self['forbidden_particles']: 2726 forbpart = self['model'].get('particle_dict')[forb_id] 2727 mystr = mystr + forbpart.get_name() + ' ' 2728 2729 # Remove last space 2730 mystr = mystr[:-1] 2731 2732 if self.get('id') or self.get('overall_orders'): 2733 mystr += " @%d" % self.get('id') 2734 if self.get('overall_orders'): 2735 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 2736 for key in sorted(self['orders'])]) + ' ' 2737 2738 if not self.get('decay_chains'): 2739 return mystr 2740 2741 for decay in self['decay_chains']: 2742 mystr = mystr + '\n' + \ 2743 decay.nice_string(indent + 2).replace('Process', 'Decay') 2744 2745 return mystr
2746
2747 - def input_string(self):
2748 """Returns a process string corresponding to the input string 2749 in the command line interface.""" 2750 2751 mystr = "" 2752 prevleg = None 2753 2754 for leg in self['legs']: 2755 mypart = self['model'].get('particle_dict')[leg['id']] 2756 if prevleg and prevleg['state'] == False \ 2757 and leg['state'] == True: 2758 # Separate initial and final legs by ">" 2759 mystr = mystr + '> ' 2760 # Add required s-channels 2761 if self['required_s_channels'] and \ 2762 self['required_s_channels'][0]: 2763 mystr += "|".join([" ".join([self['model'].\ 2764 get('particle_dict')[req_id].get_name() \ 2765 for req_id in id_list]) \ 2766 for id_list in self['required_s_channels']]) 2767 mystr = mystr + '> ' 2768 2769 mystr = mystr + mypart.get_name() + ' ' 2770 #mystr = mystr + '(%i) ' % leg['number'] 2771 prevleg = leg 2772 2773 if self['orders']: 2774 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 2775 for key in self['orders']]) + ' ' 2776 2777 # Add perturbation orders 2778 if self['perturbation_couplings']: 2779 mystr = mystr + '[ ' 2780 if self['NLO_mode']: 2781 mystr = mystr + self['NLO_mode'] 2782 if not self['has_born']: 2783 mystr = mystr + '^2' 2784 mystr = mystr + '= ' 2785 2786 for order in self['perturbation_couplings']: 2787 mystr = mystr + order + ' ' 2788 mystr = mystr + '] ' 2789 2790 # Add squared orders 2791 if self['perturbation_couplings'] and self['squared_orders']: 2792 mystr = mystr + " ".join([key + '=' + repr(self['squared_orders'][key]) \ 2793 for key in self['squared_orders']]) + ' ' 2794 2795 # Add forbidden s-channels 2796 if self['forbidden_onsh_s_channels']: 2797 mystr = mystr + '$ ' 2798 for forb_id in self['forbidden_onsh_s_channels']: 2799 forbpart = self['model'].get('particle_dict')[forb_id] 2800 mystr = mystr + forbpart.get_name() + ' ' 2801 2802 # Add double forbidden s-channels 2803 if self['forbidden_s_channels']: 2804 mystr = mystr + '$$ ' 2805 for forb_id in self['forbidden_s_channels']: 2806 forbpart = self['model'].get('particle_dict')[forb_id] 2807 mystr = mystr + forbpart.get_name() + ' ' 2808 2809 # Add forbidden particles 2810 if self['forbidden_particles']: 2811 mystr = mystr + '/ ' 2812 for forb_id in self['forbidden_particles']: 2813 forbpart = self['model'].get('particle_dict')[forb_id] 2814 mystr = mystr + forbpart.get_name() + ' ' 2815 2816 # Remove last space 2817 mystr = mystr[:-1] 2818 2819 if self.get('overall_orders'): 2820 mystr += " @%d" % self.get('id') 2821 if self.get('overall_orders'): 2822 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 2823 for key in sorted(self['orders'])]) + ' ' 2824 2825 if not self.get('decay_chains'): 2826 return mystr 2827 2828 for decay in self['decay_chains']: 2829 paren1 = '' 2830 paren2 = '' 2831 if decay.get('decay_chains'): 2832 paren1 = '(' 2833 paren2 = ')' 2834 mystr += ', ' + paren1 + decay.input_string() + paren2 2835 2836 return mystr
2837
2838 - def base_string(self):
2839 """Returns a string containing only the basic process (w/o decays).""" 2840 2841 mystr = "" 2842 prevleg = None 2843 for leg in self.get_legs_with_decays(): 2844 mypart = self['model'].get('particle_dict')[leg['id']] 2845 if prevleg and prevleg['state'] == False \ 2846 and leg['state'] == True: 2847 # Separate initial and final legs by ">" 2848 mystr = mystr + '> ' 2849 mystr = mystr + mypart.get_name() + ' ' 2850 prevleg = leg 2851 2852 # Remove last space 2853 return mystr[:-1]
2854
2855 - def shell_string(self, schannel=True, forbid=True, main=True, pdg_order=False):
2856 """Returns process as string with '~' -> 'x', '>' -> '_', 2857 '+' -> 'p' and '-' -> 'm', including process number, 2858 intermediate s-channels and forbidden particles, 2859 pdg_order allow to order to leg order by pid.""" 2860 2861 mystr = "" 2862 if not self.get('is_decay_chain'): 2863 mystr += "%d_" % self['id'] 2864 2865 prevleg = None 2866 if pdg_order: 2867 legs = [l for l in self['legs'][1:]] 2868 def order_leg(l1,l2): 2869 id1 = l1.get('id') 2870 id2 = l2.get('id') 2871 return id2-id1
2872 legs.sort(cmp=order_leg) 2873 legs.insert(0, self['legs'][0]) 2874 else: 2875 legs = self['legs'] 2876 2877 2878 for leg in legs: 2879 mypart = self['model'].get('particle_dict')[leg['id']] 2880 if prevleg and prevleg['state'] == False \ 2881 and leg['state'] == True: 2882 # Separate initial and final legs by ">" 2883 mystr = mystr + '_' 2884 # Add required s-channels 2885 if self['required_s_channels'] and \ 2886 self['required_s_channels'][0] and schannel: 2887 mystr += "_or_".join(["".join([self['model'].\ 2888 get('particle_dict')[req_id].get_name() \ 2889 for req_id in id_list]) \ 2890 for id_list in self['required_s_channels']]) 2891 mystr = mystr + '_' 2892 if mypart['is_part']: 2893 mystr = mystr + mypart['name'] 2894 else: 2895 mystr = mystr + mypart['antiname'] 2896 prevleg = leg 2897 2898 # Check for forbidden particles 2899 if self['forbidden_particles'] and forbid: 2900 mystr = mystr + '_no_' 2901 for forb_id in self['forbidden_particles']: 2902 forbpart = self['model'].get('particle_dict')[forb_id] 2903 mystr = mystr + forbpart.get_name() 2904 2905 # Replace '~' with 'x' 2906 mystr = mystr.replace('~', 'x') 2907 # Replace '+' with 'p' 2908 mystr = mystr.replace('+', 'p') 2909 # Replace '-' with 'm' 2910 mystr = mystr.replace('-', 'm') 2911 # Just to be safe, remove all spaces 2912 mystr = mystr.replace(' ', '') 2913 2914 for decay in self.get('decay_chains'): 2915 mystr = mystr + "_" + decay.shell_string(schannel,forbid, main=False, 2916 pdg_order=pdg_order) 2917 2918 # Too long name are problematic so restrict them to a maximal of 70 char 2919 if len(mystr) > 64 and main: 2920 if schannel and forbid: 2921 out = self.shell_string(True, False, True, pdg_order) 2922 elif schannel: 2923 out = self.shell_string(False, False, True, pdg_order) 2924 else: 2925 out = mystr[:64] 2926 if not out.endswith('_%s' % self['uid']): 2927 out += '_%s' % self['uid'] 2928 return out 2929 2930 return mystr
2931
2932 - def shell_string_v4(self):
2933 """Returns process as v4-compliant string with '~' -> 'x' and 2934 '>' -> '_'""" 2935 2936 mystr = "%d_" % self['id'] 2937 prevleg = None 2938 for leg in self.get_legs_with_decays(): 2939 mypart = self['model'].get('particle_dict')[leg['id']] 2940 if prevleg and prevleg['state'] == False \ 2941 and leg['state'] == True: 2942 # Separate initial and final legs by ">" 2943 mystr = mystr + '_' 2944 if mypart['is_part']: 2945 mystr = mystr + mypart['name'] 2946 else: 2947 mystr = mystr + mypart['antiname'] 2948 prevleg = leg 2949 2950 # Replace '~' with 'x' 2951 mystr = mystr.replace('~', 'x') 2952 # Just to be safe, remove all spaces 2953 mystr = mystr.replace(' ', '') 2954 2955 return mystr
2956 2957 # Helper functions 2958
2959 - def are_negative_orders_present(self):
2960 """ Check iteratively that no coupling order constraint include negative 2961 values.""" 2962 2963 if any(val<0 for val in self.get('orders').values()+\ 2964 self.get('squared_orders').values()): 2965 return True 2966 2967 for procdef in self['decay_chains']: 2968 if procdef.are_negative_orders_present(): 2969 return True 2970 2971 return False
2972
2973 - def are_decays_perturbed(self):
2974 """ Check iteratively that the decayed processes are not perturbed """ 2975 2976 for procdef in self['decay_chains']: 2977 if procdef['perturbation_couplings'] or procdef.are_decays_perturbed(): 2978 return True 2979 return False
2980
2981 - def decays_have_squared_orders(self):
2982 """ Check iteratively that the decayed processes are not perturbed """ 2983 2984 for procdef in self['decay_chains']: 2985 if procdef['squared_orders']!={} or procdef.decays_have_squared_orders(): 2986 return True 2987 return False
2988
2989 - def get_ninitial(self):
2990 """Gives number of initial state particles""" 2991 2992 return len(filter(lambda leg: leg.get('state') == False, 2993 self.get('legs')))
2994
2995 - def get_initial_ids(self):
2996 """Gives the pdg codes for initial state particles""" 2997 2998 return [leg.get('id') for leg in \ 2999 filter(lambda leg: leg.get('state') == False, 3000 self.get('legs'))]
3001
3002 - def get_initial_pdg(self, number):
3003 """Return the pdg codes for initial state particles for beam number""" 3004 3005 return filter(lambda leg: leg.get('state') == False and\ 3006 leg.get('number') == number, 3007 self.get('legs'))[0].get('id')
3008
3009 - def get_initial_final_ids(self):
3010 """return a tuple of two tuple containing the id of the initial/final 3011 state particles. Each list is ordered""" 3012 3013 initial = [] 3014 final = [l.get('id') for l in self.get('legs')\ 3015 if l.get('state') or initial.append(l.get('id'))] 3016 initial.sort() 3017 final.sort() 3018 return (tuple(initial), tuple(final))
3019
3020 - def get_final_ids_after_decay(self):
3021 """Give the pdg code of the process including decay""" 3022 3023 finals = self.get_final_ids() 3024 for proc in self.get('decay_chains'): 3025 init = proc.get_initial_ids()[0] 3026 #while 1: 3027 try: 3028 pos = finals.index(init) 3029 except: 3030 break 3031 finals[pos] = proc.get_final_ids_after_decay() 3032 output = [] 3033 for d in finals: 3034 if isinstance(d, list): 3035 output += d 3036 else: 3037 output.append(d) 3038 3039 return output
3040 3041
3042 - def get_final_legs(self):
3043 """Gives the final state legs""" 3044 3045 return filter(lambda leg: leg.get('state') == True, 3046 self.get('legs'))
3047
3048 - def get_final_ids(self):
3049 """Gives the pdg codes for final state particles""" 3050 3051 return [l.get('id') for l in self.get_final_legs()]
3052 3053
3054 - def get_legs_with_decays(self):
3055 """Return process with all decay chains substituted in.""" 3056 3057 if self['legs_with_decays']: 3058 return self['legs_with_decays'] 3059 3060 legs = copy.deepcopy(self.get('legs')) 3061 org_decay_chains = copy.copy(self.get('decay_chains')) 3062 sorted_decay_chains = [] 3063 # Sort decay chains according to leg order 3064 for leg in legs: 3065 if not leg.get('state'): continue 3066 org_ids = [l.get('legs')[0].get('id') for l in \ 3067 org_decay_chains] 3068 if leg.get('id') in org_ids: 3069 sorted_decay_chains.append(org_decay_chains.pop(\ 3070 org_ids.index(leg.get('id')))) 3071 assert not org_decay_chains 3072 ileg = 0 3073 for decay in sorted_decay_chains: 3074 while legs[ileg].get('state') == False or \ 3075 legs[ileg].get('id') != decay.get('legs')[0].get('id'): 3076 ileg = ileg + 1 3077 decay_legs = decay.get_legs_with_decays() 3078 legs = legs[:ileg] + decay_legs[1:] + legs[ileg+1:] 3079 ileg = ileg + len(decay_legs) - 1 3080 3081 # Replace legs with copies 3082 legs = [copy.copy(l) for l in legs] 3083 3084 for ileg, leg in enumerate(legs): 3085 leg.set('number', ileg + 1) 3086 3087 self['legs_with_decays'] = LegList(legs) 3088 3089 return self['legs_with_decays']
3090
3091 - def list_for_sort(self):
3092 """Output a list that can be compared to other processes as: 3093 [id, sorted(initial leg ids), sorted(final leg ids), 3094 sorted(decay list_for_sorts)]""" 3095 3096 sorted_list = [self.get('id'), 3097 sorted(self.get_initial_ids()), 3098 sorted(self.get_final_ids())] 3099 3100 if self.get('decay_chains'): 3101 sorted_list.extend(sorted([d.list_for_sort() for d in \ 3102 self.get('decay_chains')])) 3103 3104 return sorted_list
3105
3106 - def compare_for_sort(self, other):
3107 """Sorting routine which allows to sort processes for 3108 comparison. Compare only process id and legs.""" 3109 3110 if self.list_for_sort() > other.list_for_sort(): 3111 return 1 3112 if self.list_for_sort() < other.list_for_sort(): 3113 return -1 3114 return 0
3115
3116 - def identical_particle_factor(self):
3117 """Calculate the denominator factor for identical final state particles 3118 """ 3119 3120 final_legs = filter(lambda leg: leg.get('state') == True, \ 3121 self.get_legs_with_decays()) 3122 3123 identical_indices = {} 3124 for leg in final_legs: 3125 if leg.get('id') in identical_indices: 3126 identical_indices[leg.get('id')] = \ 3127 identical_indices[leg.get('id')] + 1 3128 else: 3129 identical_indices[leg.get('id')] = 1 3130 return reduce(lambda x, y: x * y, [ math.factorial(val) for val in \ 3131 identical_indices.values() ], 1)
3132
3133 - def check_expansion_orders(self):
3134 """Ensure that maximum expansion orders from the model are 3135 properly taken into account in the process""" 3136 3137 # Ensure that expansion orders are taken into account 3138 expansion_orders = self.get('model').get('expansion_order') 3139 orders = self.get('orders') 3140 sq_orders = self.get('squared_orders') 3141 3142 tmp = [(k,v) for (k,v) in expansion_orders.items() if 0 < v < 99] 3143 for (k,v) in tmp: 3144 if k in orders: 3145 if v < orders[k]: 3146 if k in sq_orders.keys() and \ 3147 (sq_orders[k]>v or sq_orders[k]<0): 3148 logger.warning( 3149 '''The process with the squared coupling order (%s^2%s%s) specified can potentially 3150 recieve contributions with powers of the coupling %s larger than the maximal 3151 value allowed by the model builder (%s). Hence, MG5_aMC sets the amplitude order 3152 for that coupling to be this maximal one. '''%(k,self.get('sqorders_types')[k], 3153 self.get('squared_orders')[k],k,v)) 3154 else: 3155 logger.warning( 3156 '''The coupling order (%s=%s) specified is larger than the one allowed 3157 by the model builder. The maximal value allowed is %s. 3158 We set the %s order to this value''' % (k,orders[k],v,k)) 3159 orders[k] = v 3160 else: 3161 orders[k] = v
3162
3163 - def __eq__(self, other):
3164 """Overloading the equality operator, so that only comparison 3165 of process id and legs is being done, using compare_for_sort.""" 3166 3167 if not isinstance(other, Process): 3168 return False 3169 3170 return self.compare_for_sort(other) == 0
3171
3172 - def __ne__(self, other):
3173 return not self.__eq__(other)
3174
3175 #=============================================================================== 3176 # ProcessList 3177 #=============================================================================== 3178 -class ProcessList(PhysicsObjectList):
3179 """List of Process objects 3180 """ 3181
3182 - def is_valid_element(self, obj):
3183 """Test if object obj is a valid Process for the list.""" 3184 3185 return isinstance(obj, Process)
3186
3187 - def nice_string(self, indent = 0):
3188 """Returns a nicely formatted string of the matrix element processes.""" 3189 3190 mystr = "\n".join([p.nice_string(indent) for p in self]) 3191 3192 return mystr
3193
3194 #=============================================================================== 3195 # ProcessDefinition 3196 #=============================================================================== 3197 -class ProcessDefinition(Process):
3198 """ProcessDefinition: list of multilegs (ordered) 3199 dictionary of orders 3200 model 3201 process id 3202 """ 3203
3204 - def default_setup(self):
3205 """Default values for all properties""" 3206 3207 super(ProcessDefinition, self).default_setup() 3208 3209 self['legs'] = MultiLegList() 3210 # Decay chain processes associated with this process 3211 self['decay_chains'] = ProcessDefinitionList() 3212 if 'legs_with_decays' in self: del self['legs_with_decays']
3213
3214 - def filter(self, name, value):
3215 """Filter for valid process property values.""" 3216 3217 if name == 'legs': 3218 if not isinstance(value, MultiLegList): 3219 raise self.PhysicsObjectError, \ 3220 "%s is not a valid MultiLegList object" % str(value) 3221 elif name == 'decay_chains': 3222 if not isinstance(value, ProcessDefinitionList): 3223 raise self.PhysicsObjectError, \ 3224 "%s is not a valid ProcessDefinitionList" % str(value) 3225 3226 else: 3227 return super(ProcessDefinition, self).filter(name, value) 3228 3229 return True
3230
3231 - def has_multiparticle_label(self):
3232 """ Check that this process definition will yield a single process, as 3233 each multileg only has one leg""" 3234 3235 for mleg in self['legs']: 3236 if len(mleg['ids'])>1: 3237 return True 3238 3239 return False
3240
3241 - def get_sorted_keys(self):
3242 """Return process property names as a nicely sorted list.""" 3243 3244 keys = super(ProcessDefinition, self).get_sorted_keys() 3245 keys.remove('legs_with_decays') 3246 3247 return keys
3248
3249 - def get_minimum_WEIGHTED(self):
3250 """Retrieve the minimum starting guess for WEIGHTED order, to 3251 use in find_optimal_process_orders in MultiProcess diagram 3252 generation (as well as particles and hierarchy). The algorithm: 3253 3254 1) Pick out the legs in the multiprocess according to the 3255 highest hierarchy represented (so don't mix particles from 3256 different hierarchy classes in the same multiparticles!) 3257 3258 2) Find the starting maximum WEIGHTED order as the sum of the 3259 highest n-2 weighted orders 3260 3261 3) Pick out required s-channel particle hierarchies, and use 3262 the highest of the maximum WEIGHTED order from the legs and 3263 the minimum WEIGHTED order extracted from 2*s-channel 3264 hierarchys plus the n-2-2*(number of s-channels) lowest 3265 leg weighted orders. 3266 """ 3267 3268 model = self.get('model') 3269 3270 # Extract hierarchy and particles corresponding to the 3271 # different hierarchy levels from the model 3272 particles, hierarchy = model.get_particles_hierarchy() 3273 3274 # Find legs corresponding to the different orders 3275 # making sure we look at lowest hierarchy first for each leg 3276 max_order_now = [] 3277 new_legs = copy.copy(self.get('legs')) 3278 for parts, value in zip(particles, hierarchy): 3279 ileg = 0 3280 while ileg < len(new_legs): 3281 if any([id in parts for id in new_legs[ileg].get('ids')]): 3282 max_order_now.append(value) 3283 new_legs.pop(ileg) 3284 else: 3285 ileg += 1 3286 3287 # Now remove the two lowest orders to get maximum (since the 3288 # number of interactions is n-2) 3289 max_order_now = sorted(max_order_now)[2:] 3290 3291 # Find s-channel propagators corresponding to the different orders 3292 max_order_prop = [] 3293 for idlist in self.get('required_s_channels'): 3294 max_order_prop.append([0,0]) 3295 for id in idlist: 3296 for parts, value in zip(particles, hierarchy): 3297 if id in parts: 3298 max_order_prop[-1][0] += 2*value 3299 max_order_prop[-1][1] += 1 3300 break 3301 3302 if max_order_prop: 3303 if len(max_order_prop) >1: 3304 max_order_prop = min(*max_order_prop, key=lambda x:x[0]) 3305 else: 3306 max_order_prop = max_order_prop[0] 3307 3308 # Use either the max_order from the external legs or 3309 # the maximum order from the s-channel propagators, plus 3310 # the appropriate lowest orders from max_order_now 3311 max_order_now = max(sum(max_order_now), 3312 max_order_prop[0] + \ 3313 sum(max_order_now[:-2 * max_order_prop[1]])) 3314 else: 3315 max_order_now = sum(max_order_now) 3316 3317 return max_order_now, particles, hierarchy
3318
3319 - def nice_string(self, indent=0, print_weighted=False):
3320 """Returns a nicely formated string about current process 3321 content""" 3322 3323 mystr = " " * indent + "Process: " 3324 prevleg = None 3325 for leg in self['legs']: 3326 myparts = \ 3327 "/".join([self['model'].get('particle_dict')[id].get_name() \ 3328 for id in leg.get('ids')]) 3329 if prevleg and prevleg['state'] == False \ 3330 and leg['state'] == True: 3331 # Separate initial and final legs by ">" 3332 mystr = mystr + '> ' 3333 # Add required s-channels 3334 if self['required_s_channels'] and \ 3335 self['required_s_channels'][0]: 3336 mystr += "|".join([" ".join([self['model'].\ 3337 get('particle_dict')[req_id].get_name() \ 3338 for req_id in id_list]) \ 3339 for id_list in self['required_s_channels']]) 3340 mystr = mystr + '> ' 3341 3342 mystr = mystr + myparts + ' ' 3343 #mystr = mystr + '(%i) ' % leg['number'] 3344 prevleg = leg 3345 3346 # Add forbidden s-channels 3347 if self['forbidden_onsh_s_channels']: 3348 mystr = mystr + '$ ' 3349 for forb_id in self['forbidden_onsh_s_channels']: 3350 forbpart = self['model'].get('particle_dict')[forb_id] 3351 mystr = mystr + forbpart.get_name() + ' ' 3352 3353 # Add double forbidden s-channels 3354 if self['forbidden_s_channels']: 3355 mystr = mystr + '$$ ' 3356 for forb_id in self['forbidden_s_channels']: 3357 forbpart = self['model'].get('particle_dict')[forb_id] 3358 mystr = mystr + forbpart.get_name() + ' ' 3359 3360 # Add forbidden particles 3361 if self['forbidden_particles']: 3362 mystr = mystr + '/ ' 3363 for forb_id in self['forbidden_particles']: 3364 forbpart = self['model'].get('particle_dict')[forb_id] 3365 mystr = mystr + forbpart.get_name() + ' ' 3366 3367 if self['orders']: 3368 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3369 for key in sorted(self['orders'])]) + ' ' 3370 3371 # Add perturbation_couplings 3372 if self['perturbation_couplings']: 3373 mystr = mystr + '[ ' 3374 if self['NLO_mode']: 3375 mystr = mystr + self['NLO_mode'] + ' = ' 3376 for order in self['perturbation_couplings']: 3377 mystr = mystr + order + ' ' 3378 mystr = mystr + '] ' 3379 3380 if self['squared_orders']: 3381 mystr = mystr + " ".join([key + '^2%s%d'%\ 3382 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 3383 for key in self['squared_orders'].keys() \ 3384 if print_weighted or key!='WEIGHTED']) + ' ' 3385 3386 # Remove last space 3387 mystr = mystr[:-1] 3388 3389 if self.get('id') or self.get('overall_orders'): 3390 mystr += " @%d" % self.get('id') 3391 if self.get('overall_orders'): 3392 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3393 for key in sorted(self['orders'])]) + ' ' 3394 3395 if not self.get('decay_chains'): 3396 return mystr 3397 3398 for decay in self['decay_chains']: 3399 mystr = mystr + '\n' + \ 3400 decay.nice_string(indent + 2).replace('Process', 'Decay') 3401 3402 return mystr
3403
3404 - def get_process_with_legs(self, LegList):
3405 """ Return a Process object which has the same properties of this 3406 ProcessDefinition but with the specified LegList as legs attribute. 3407 """ 3408 3409 return Process({\ 3410 'legs': LegList, 3411 'model':self.get('model'), 3412 'id': self.get('id'), 3413 'orders': self.get('orders'), 3414 'sqorders_types': self.get('sqorders_types'), 3415 'squared_orders': self.get('squared_orders'), 3416 'has_born': self.get('has_born'), 3417 'required_s_channels': self.get('required_s_channels'), 3418 'forbidden_onsh_s_channels': self.get('forbidden_onsh_s_channels'), 3419 'forbidden_s_channels': self.get('forbidden_s_channels'), 3420 'forbidden_particles': self.get('forbidden_particles'), 3421 'perturbation_couplings': self.get('perturbation_couplings'), 3422 'is_decay_chain': self.get('is_decay_chain'), 3423 'overall_orders': self.get('overall_orders'), 3424 'split_orders': self.get('split_orders'), 3425 'NLO_mode': self.get('NLO_mode') 3426 })
3427
3428 - def get_process(self, initial_state_ids, final_state_ids):
3429 """ Return a Process object which has the same properties of this 3430 ProcessDefinition but with the specified given leg ids. """ 3431 3432 # First make sure that the desired particle ids belong to those defined 3433 # in this process definition. 3434 my_isids = [leg.get('ids') for leg in self.get('legs') \ 3435 if not leg.get('state')] 3436 my_fsids = [leg.get('ids') for leg in self.get('legs') \ 3437 if leg.get('state')] 3438 for i, is_id in enumerate(initial_state_ids): 3439 assert is_id in my_isids[i] 3440 for i, fs_id in enumerate(final_state_ids): 3441 assert fs_id in my_fsids[i] 3442 3443 return self.get_process_with_legs(LegList(\ 3444 [Leg({'id': id, 'state':False}) for id in initial_state_ids] + \ 3445 [Leg({'id': id, 'state':True}) for id in final_state_ids]))
3446
3447 - def __eq__(self, other):
3448 """Overloading the equality operator, so that only comparison 3449 of process id and legs is being done, using compare_for_sort.""" 3450 3451 return super(Process, self).__eq__(other)
3452
3453 #=============================================================================== 3454 # ProcessDefinitionList 3455 #=============================================================================== 3456 -class ProcessDefinitionList(PhysicsObjectList):
3457 """List of ProcessDefinition objects 3458 """ 3459
3460 - def is_valid_element(self, obj):
3461 """Test if object obj is a valid ProcessDefinition for the list.""" 3462 3463 return isinstance(obj, ProcessDefinition)
3464
3465 #=============================================================================== 3466 # Global helper functions 3467 #=============================================================================== 3468 3469 -def make_unique(doubletlist):
3470 """Make sure there are no doublets in the list doubletlist. 3471 Note that this is a slow implementation, so don't use if speed 3472 is needed""" 3473 3474 assert isinstance(doubletlist, list), \ 3475 "Argument to make_unique must be list" 3476 3477 3478 uniquelist = [] 3479 for elem in doubletlist: 3480 if elem not in uniquelist: 3481 uniquelist.append(elem) 3482 3483 doubletlist[:] = uniquelist[:]
3484