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