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 - def default_setup(self):
1025 1026 self['name'] = "" 1027 self['particles'] = ParticleList() 1028 self['interactions'] = InteractionList() 1029 self['parameters'] = None 1030 self['functions'] = None 1031 self['couplings'] = None 1032 self['lorentz'] = None 1033 self['particle_dict'] = {} 1034 self['interaction_dict'] = {} 1035 self['ref_dict_to0'] = {} 1036 self['ref_dict_to1'] = {} 1037 self['got_majoranas'] = None 1038 self['order_hierarchy'] = {} 1039 self['conserved_charge'] = set() 1040 self['coupling_orders'] = None 1041 self['expansion_order'] = None 1042 self['version_tag'] = None # position of the directory (for security) 1043 self['gauge'] = [0, 1] 1044 self['case_sensitive'] = True
1045 # attribute which might be define if needed 1046 #self['name2pdg'] = {'name': pdg} 1047 1048
1049 - def filter(self, name, value):
1050 """Filter for model property values""" 1051 1052 if name in ['name']: 1053 if not isinstance(value, str): 1054 raise self.PhysicsObjectError, \ 1055 "Object of type %s is not a string" %type(value) 1056 1057 elif name == 'particles': 1058 if not isinstance(value, ParticleList): 1059 raise self.PhysicsObjectError, \ 1060 "Object of type %s is not a ParticleList object" % \ 1061 type(value) 1062 elif name == 'interactions': 1063 if not isinstance(value, InteractionList): 1064 raise self.PhysicsObjectError, \ 1065 "Object of type %s is not a InteractionList object" % \ 1066 type(value) 1067 elif name == 'particle_dict': 1068 if not isinstance(value, dict): 1069 raise self.PhysicsObjectError, \ 1070 "Object of type %s is not a dictionary" % \ 1071 type(value) 1072 elif name == 'interaction_dict': 1073 if not isinstance(value, dict): 1074 raise self.PhysicsObjectError, \ 1075 "Object of type %s is not a dictionary" % type(value) 1076 1077 elif name == 'ref_dict_to0': 1078 if not isinstance(value, dict): 1079 raise self.PhysicsObjectError, \ 1080 "Object of type %s is not a dictionary" % type(value) 1081 1082 elif name == 'ref_dict_to1': 1083 if not isinstance(value, dict): 1084 raise self.PhysicsObjectError, \ 1085 "Object of type %s is not a dictionary" % type(value) 1086 1087 elif name == 'got_majoranas': 1088 if not (isinstance(value, bool) or value == None): 1089 raise self.PhysicsObjectError, \ 1090 "Object of type %s is not a boolean" % type(value) 1091 1092 elif name == 'conserved_charge': 1093 if not (isinstance(value, set)): 1094 raise self.PhysicsObjectError, \ 1095 "Object of type %s is not a set" % type(value) 1096 1097 elif name == 'version_tag': 1098 if not (isinstance(value, str)): 1099 raise self.PhysicsObjectError, \ 1100 "Object of type %s is not a string" % type(value) 1101 1102 elif name == 'order_hierarchy': 1103 if not isinstance(value, dict): 1104 raise self.PhysicsObjectError, \ 1105 "Object of type %s is not a dictionary" % \ 1106 type(value) 1107 for key in value.keys(): 1108 if not isinstance(value[key],int): 1109 raise self.PhysicsObjectError, \ 1110 "Object of type %s is not an integer" % \ 1111 type(value[key]) 1112 elif name == 'gauge': 1113 if not (isinstance(value, list)): 1114 raise self.PhysicsObjectError, \ 1115 "Object of type %s is not a list" % type(value) 1116 1117 elif name == 'case_sensitive': 1118 if not value in [True ,False]: 1119 raise self.PhysicsObjectError, \ 1120 "Object of type %s is not a boolean" % type(value) 1121 1122 1123 return True
1124
1125 - def get(self, name):
1126 """Get the value of the property name.""" 1127 1128 if (name == 'ref_dict_to0' or name == 'ref_dict_to1') and \ 1129 not self[name]: 1130 if self['interactions']: 1131 [self['ref_dict_to0'], self['ref_dict_to1']] = \ 1132 self['interactions'].generate_ref_dict() 1133 self['ref_dict_to0'].update( 1134 self['particles'].generate_ref_dict()) 1135 1136 if (name == 'particle_dict') and not self[name]: 1137 if self['particles']: 1138 self['particle_dict'] = self['particles'].generate_dict() 1139 if self['interactions']: 1140 self['interactions'].synchronize_interactions_with_particles(\ 1141 self['particle_dict']) 1142 if name == 'modelpath': 1143 modeldir = self.get('version_tag').rsplit('##',1)[0] 1144 if os.path.exists(modeldir): 1145 return modeldir 1146 else: 1147 raise Exception, "path %s not valid anymore." % modeldir 1148 #modeldir = os.path.join(os.path.dirname(modeldir), 1149 # os.path.basename(modeldir).rsplit("-",1)[0]) 1150 #if os.path.exists(modeldir): 1151 # return modeldir 1152 #raise Exception, 'Invalid Path information: %s' % self.get('version_tag') 1153 elif name == 'modelpath+restriction': 1154 modeldir = self.get('version_tag').rsplit('##',1)[0] 1155 modelname = self['name'] 1156 if not os.path.exists(modeldir): 1157 raise Exception, "path %s not valid anymore" % modeldir 1158 modeldir = os.path.dirname(modeldir) 1159 modeldir = pjoin(modeldir, modelname) 1160 return modeldir 1161 elif name == 'restrict_name': 1162 modeldir = self.get('version_tag').rsplit('##',1)[0] 1163 modelname = self['name'] 1164 basename = os.path.basename(modeldir) 1165 restriction = modelname[len(basename)+1:] 1166 return restriction 1167 1168 if (name == 'interaction_dict') and not self[name]: 1169 if self['interactions']: 1170 self['interaction_dict'] = self['interactions'].generate_dict() 1171 1172 if (name == 'got_majoranas') and self[name] == None: 1173 if self['particles']: 1174 self['got_majoranas'] = self.check_majoranas() 1175 1176 if (name == 'coupling_orders') and self[name] == None: 1177 if self['interactions']: 1178 self['coupling_orders'] = self.get_coupling_orders() 1179 1180 if (name == 'order_hierarchy') and not self[name]: 1181 if self['interactions']: 1182 self['order_hierarchy'] = self.get_order_hierarchy() 1183 1184 if (name == 'expansion_order') and self[name] == None: 1185 if self['interactions']: 1186 self['expansion_order'] = \ 1187 dict([(order, -1) for order in self.get('coupling_orders')]) 1188 1189 if (name == 'name2pdg') and 'name2pdg' not in self: 1190 self['name2pdg'] = {} 1191 for p in self.get('particles'): 1192 self['name2pdg'][p.get('antiname')] = -1*p.get('pdg_code') 1193 self['name2pdg'][p.get('name')] = p.get('pdg_code') 1194 1195 return Model.__bases__[0].get(self, name) # call the mother routine
1196
1197 - def set(self, name, value, force = False):
1198 """Special set for particles and interactions - need to 1199 regenerate dictionaries.""" 1200 1201 if name == 'particles': 1202 # Ensure no doublets in particle list 1203 make_unique(value) 1204 # Reset dictionaries 1205 self['particle_dict'] = {} 1206 self['ref_dict_to0'] = {} 1207 self['got_majoranas'] = None 1208 1209 if name == 'interactions': 1210 # Ensure no doublets in interaction list 1211 make_unique(value) 1212 # Reset dictionaries 1213 self['interaction_dict'] = {} 1214 self['ref_dict_to1'] = {} 1215 self['ref_dict_to0'] = {} 1216 self['got_majoranas'] = None 1217 self['coupling_orders'] = None 1218 self['order_hierarchy'] = {} 1219 self['expansion_order'] = None 1220 1221 result = Model.__bases__[0].set(self, name, value, force) # call the mother routine 1222 1223 if name == 'particles': 1224 # Recreate particle_dict 1225 self.get('particle_dict') 1226 1227 return result
1228
1229 - def actualize_dictionaries(self):
1230 """This function actualizes the dictionaries""" 1231 1232 [self['ref_dict_to0'], self['ref_dict_to1']] = \ 1233 self['interactions'].generate_ref_dict() 1234 self['ref_dict_to0'].update( 1235 self['particles'].generate_ref_dict())
1236
1237 - def get_sorted_keys(self):
1238 """Return process property names as a nicely sorted list.""" 1239 1240 return ['name', 'particles', 'parameters', 'interactions', 1241 'couplings','lorentz', 'gauge']
1242
1243 - def get_particle(self, id):
1244 """Return the particle corresponding to the id / name""" 1245 1246 try: 1247 return self["particle_dict"][id] 1248 except Exception: 1249 if isinstance(id, int): 1250 try: 1251 return self.get("particle_dict")[id] 1252 except Exception,error: 1253 return None 1254 else: 1255 if not hasattr(self, 'name2part'): 1256 self.create_name2part() 1257 try: 1258 return self.name2part[id] 1259 except: 1260 return None
1261
1262 - def create_name2part(self):
1263 """create a dictionary name 2 part""" 1264 1265 self.name2part = {} 1266 for part in self.get("particle_dict").values(): 1267 self.name2part[part.get('name')] = part
1268 1269 1270
1271 - def get_lorentz(self, name):
1272 """return the lorentz object from the associate name""" 1273 if hasattr(self, 'lorentz_name2obj'): 1274 return self.lorentz_name2obj[name] 1275 else: 1276 self.create_lorentz_dict() 1277 return self.lorentz_name2obj[name]
1278
1279 - def create_lorentz_dict(self):
1280 """create the dictionary linked to the lorentz structure""" 1281 self.lorentz_name2obj = {} 1282 self.lorentz_expr2name = {} 1283 if not self.get('lorentz'): 1284 return 1285 for lor in self.get('lorentz'): 1286 self.lorentz_name2obj[lor.name] = lor 1287 self.lorentz_expr2name[lor.structure] = lor.name
1288
1289 - def get_interaction(self, id):
1290 """Return the interaction corresponding to the id""" 1291 1292 try: 1293 return self.get("interaction_dict")[id] 1294 except Exception: 1295 return None
1296
1297 - def get_parameter(self, name):
1298 """Return the parameter associated to the name NAME""" 1299 1300 # If information is saved 1301 if hasattr(self, 'parameters_dict') and self.parameters_dict: 1302 try: 1303 return self.parameters_dict[name] 1304 except Exception: 1305 # try to reload it before crashing 1306 pass 1307 1308 # Else first build the dictionary 1309 self.parameters_dict = {} 1310 for data in self['parameters'].values(): 1311 [self.parameters_dict.__setitem__(p.name,p) for p in data] 1312 1313 return self.parameters_dict[name]
1314
1315 - def get_coupling_orders(self):
1316 """Determine the coupling orders of the model""" 1317 return set(sum([i.get('orders').keys() for i in \ 1318 self.get('interactions')], []))
1319
1320 - def get_order_hierarchy(self):
1321 """Set a default order hierarchy for the model if not set by the UFO.""" 1322 # Set coupling hierachy 1323 hierarchy = dict([(order, 1) for order in self.get('coupling_orders')]) 1324 # Special case for only QCD and QED couplings, unless already set 1325 if self.get('coupling_orders') == set(['QCD', 'QED']): 1326 hierarchy['QED'] = 2 1327 return hierarchy
1328 1329
1330 - def get_nflav(self):
1331 """returns the number of light quark flavours in the model.""" 1332 return len([p for p in self.get('particles') \ 1333 if p['spin'] == 2 and p['is_part'] and \ 1334 p ['color'] != 1 and p['mass'].lower() == 'zero'])
1335 1336
1337 - def get_particles_hierarchy(self):
1338 """Returns the order hierarchies of the model and the 1339 particles which have interactions in at least this hierarchy 1340 (used in find_optimal_process_orders in MultiProcess diagram 1341 generation): 1342 1343 Check the coupling hierarchy of the model. Assign all 1344 particles to the different coupling hierarchies so that a 1345 particle is considered to be in the highest hierarchy (i.e., 1346 with lowest value) where it has an interaction. 1347 """ 1348 1349 # Find coupling orders in model 1350 coupling_orders = self.get('coupling_orders') 1351 # Loop through the different coupling hierarchy values, so we 1352 # start with the most dominant and proceed to the least dominant 1353 hierarchy = sorted(list(set([self.get('order_hierarchy')[k] for \ 1354 k in coupling_orders]))) 1355 1356 # orders is a rising list of the lists of orders with a given hierarchy 1357 orders = [] 1358 for value in hierarchy: 1359 orders.append([ k for (k, v) in \ 1360 self.get('order_hierarchy').items() if \ 1361 v == value ]) 1362 1363 # Extract the interaction that correspond to the different 1364 # coupling hierarchies, and the corresponding particles 1365 interactions = [] 1366 particles = [] 1367 for iorder, order in enumerate(orders): 1368 sum_orders = sum(orders[:iorder+1], []) 1369 sum_interactions = sum(interactions[:iorder], []) 1370 sum_particles = sum([list(p) for p in particles[:iorder]], []) 1371 # Append all interactions that have only orders with at least 1372 # this hierarchy 1373 interactions.append([i for i in self.get('interactions') if \ 1374 not i in sum_interactions and \ 1375 not any([k not in sum_orders for k in \ 1376 i.get('orders').keys()])]) 1377 # Append the corresponding particles, excluding the 1378 # particles that have already been added 1379 particles.append(set(sum([[p.get_pdg_code() for p in \ 1380 inter.get('particles') if \ 1381 p.get_pdg_code() not in sum_particles] \ 1382 for inter in interactions[-1]], []))) 1383 1384 return particles, hierarchy
1385
1386 - def get_max_WEIGHTED(self):
1387 """Return the maximum WEIGHTED order for any interaction in the model, 1388 for equivalent 3-particle vertices. Note that it can be fractional.""" 1389 1390 return max([inter.get_WEIGHTED_order(self) for inter in \ 1391 self.get('interactions')])
1392 1393
1394 - def check_majoranas(self):
1395 """Return True if there is fermion flow violation, False otherwise""" 1396 1397 if any([part.is_fermion() and part.get('self_antipart') \ 1398 for part in self.get('particles')]): 1399 return True 1400 1401 # No Majorana particles, but may still be fermion flow 1402 # violating interactions 1403 for inter in self.get('interactions'): 1404 # Do not look at UV Wfct renormalization counterterms 1405 if len(inter.get('particles'))==1: 1406 continue 1407 fermions = [p for p in inter.get('particles') if p.is_fermion()] 1408 for i in range(0, len(fermions), 2): 1409 if fermions[i].get('is_part') == \ 1410 fermions[i+1].get('is_part'): 1411 # This is a fermion flow violating interaction 1412 return True 1413 # No fermion flow violations 1414 return False
1415
1416 - def reset_dictionaries(self):
1417 """Reset all dictionaries and got_majoranas. This is necessary 1418 whenever the particle or interaction content has changed. If 1419 particles or interactions are set using the set routine, this 1420 is done automatically.""" 1421 1422 self['particle_dict'] = {} 1423 self['ref_dict_to0'] = {} 1424 self['got_majoranas'] = None 1425 self['interaction_dict'] = {} 1426 self['ref_dict_to1'] = {} 1427 self['ref_dict_to0'] = {}
1428
1430 """Change the name of the particles such that all SM and MSSM particles 1431 follows the MG convention""" 1432 1433 # Check that default name/antiname is not already use 1434 def check_name_free(self, name): 1435 """ check if name is not use for a particle in the model if it is 1436 raise an MadGraph5error""" 1437 part = self['particles'].find_name(name) 1438 if part: 1439 error_text = \ 1440 '%s particles with pdg code %s is in conflict with MG ' + \ 1441 'convention name for particle %s.\n Use -modelname in order ' + \ 1442 'to use the particles name defined in the model and not the ' + \ 1443 'MadGraph5_aMC@NLO convention' 1444 1445 raise MadGraph5Error, error_text % \ 1446 (part.get_name(), part.get_pdg_code(), pdg)
1447 1448 default = self.load_default_name() 1449 1450 for pdg in default.keys(): 1451 part = self.get_particle(pdg) 1452 if not part: 1453 continue 1454 antipart = self.get_particle(-pdg) 1455 name = part.get_name() 1456 if name != default[pdg]: 1457 check_name_free(self, default[pdg]) 1458 if part.get('is_part'): 1459 part.set('name', default[pdg]) 1460 if antipart: 1461 antipart.set('name', default[pdg]) 1462 else: 1463 part.set('antiname', default[pdg]) 1464 else: 1465 part.set('antiname', default[pdg]) 1466 if antipart: 1467 antipart.set('antiname', default[pdg]) 1468 1469 #additional check for the Higgs in the mssm 1470 if self.get('name') == 'mssm' or self.get('name').startswith('mssm-'): 1471 part = self.get_particle(25) 1472 part.set('name', 'h1') 1473 part.set('antiname', 'h1')
1474 1475 1476
1477 - def change_parameter_name_with_prefix(self, prefix='mdl_'):
1478 """ Change all model parameter by a given prefix. 1479 Modify the parameter if some of them are identical up to the case""" 1480 1481 lower_dict={} 1482 duplicate = set() 1483 keys = self.get('parameters').keys() 1484 for key in keys: 1485 for param in self['parameters'][key]: 1486 lower_name = param.name.lower() 1487 if not lower_name: 1488 continue 1489 try: 1490 lower_dict[lower_name].append(param) 1491 except KeyError: 1492 lower_dict[lower_name] = [param] 1493 else: 1494 duplicate.add(lower_name) 1495 logger.debug('%s is defined both as lower case and upper case.' 1496 % lower_name) 1497 1498 if prefix == '' and not duplicate: 1499 return 1500 1501 re_expr = r'''\b(%s)\b''' 1502 to_change = [] 1503 change={} 1504 # recast all parameter in prefix_XX 1505 for key in keys: 1506 for param in self['parameters'][key]: 1507 value = param.name.lower() 1508 if value in ['as','mu_r', 'zero','aewm1','g']: 1509 continue 1510 elif value.startswith(prefix): 1511 continue 1512 elif value in duplicate: 1513 continue # handle later 1514 elif value: 1515 change[param.name] = '%s%s' % (prefix,param.name) 1516 to_change.append(param.name) 1517 param.name = change[param.name] 1518 1519 for value in duplicate: 1520 for i, var in enumerate(lower_dict[value]): 1521 to_change.append(var.name) 1522 new_name = '%s%s%s' % (prefix, var.name.lower(), 1523 ('__%d'%(i+1) if i>0 else '')) 1524 change[var.name] = new_name 1525 var.name = new_name 1526 to_change.append(var.name) 1527 assert 'zero' not in to_change 1528 replace = lambda match_pattern: change[match_pattern.groups()[0]] 1529 1530 if not to_change: 1531 return 1532 1533 if 'parameter_dict' in self: 1534 new_dict = dict( (change[name] if (name in change) else name, value) for 1535 name, value in self['parameter_dict'].items()) 1536 self['parameter_dict'] = new_dict 1537 1538 if hasattr(self,'map_CTcoup_CTparam'): 1539 # If the map for the dependence of couplings to CTParameters has 1540 # been defined, we must apply the renaming there as well. 1541 self.map_CTcoup_CTparam = dict( (coup_name, 1542 [change[name] if (name in change) else name for name in params]) 1543 for coup_name, params in self.map_CTcoup_CTparam.items() ) 1544 1545 i=0 1546 while i*1000 <= len(to_change): 1547 one_change = to_change[i*1000: min((i+1)*1000,len(to_change))] 1548 i+=1 1549 rep_pattern = re.compile('\\b%s\\b'% (re_expr % ('\\b|\\b'.join(one_change)))) 1550 1551 # change parameters 1552 for key in keys: 1553 if key == ('external',): 1554 continue 1555 for param in self['parameters'][key]: 1556 param.expr = rep_pattern.sub(replace, param.expr) 1557 # change couplings 1558 for key in self['couplings'].keys(): 1559 for coup in self['couplings'][key]: 1560 coup.expr = rep_pattern.sub(replace, coup.expr) 1561 1562 # change mass/width 1563 for part in self['particles']: 1564 if str(part.get('mass')) in one_change: 1565 part.set('mass', rep_pattern.sub(replace, str(part.get('mass')))) 1566 if str(part.get('width')) in one_change: 1567 part.set('width', rep_pattern.sub(replace, str(part.get('width')))) 1568 if hasattr(part, 'partial_widths'): 1569 for key, value in part.partial_widths.items(): 1570 part.partial_widths[key] = rep_pattern.sub(replace, value) 1571 1572 #ensure that the particle_dict is up-to-date 1573 self['particle_dict'] ='' 1574 self.get('particle_dict')
1575 1576 1577
1578 - def get_first_non_pdg(self):
1579 """Return the first positive number that is not a valid PDG code""" 1580 return [c for c in range(1, len(self.get('particles')) + 1) if \ 1581 c not in self.get('particle_dict').keys()][0]
1582
1583 - def write_param_card(self):
1584 """Write out the param_card, and return as string.""" 1585 1586 import models.write_param_card as writer 1587 out = StringIO.StringIO() # it's suppose to be written in a file 1588 param = writer.ParamCardWriter(self) 1589 param.define_output_file(out) 1590 param.write_card() 1591 return out.getvalue()
1592 1593 @ staticmethod
1594 - def load_default_name():
1595 """ load the default for name convention """ 1596 1597 logger.info('Change particles name to pass to MG5 convention') 1598 default = {} 1599 for line in open(os.path.join(MG5DIR, 'input', \ 1600 'particles_name_default.txt')): 1601 line = line.lstrip() 1602 if line.startswith('#'): 1603 continue 1604 1605 args = line.split() 1606 if len(args) != 2: 1607 logger.warning('Invalid syntax in interface/default_name:\n %s' % line) 1608 continue 1609 default[int(args[0])] = args[1].lower() 1610 1611 return default
1612
1613 - def change_electroweak_mode(self, mode):
1614 """Change the electroweak mode. The only valid mode now is external. 1615 Where in top of the default MW and sw2 are external parameters.""" 1616 1617 assert mode in ["external",set(['mz','mw','alpha'])] 1618 1619 try: 1620 W = self.get('particle_dict')[24] 1621 except KeyError: 1622 raise InvalidCmd('No W particle in the model impossible to '+ 1623 'change the EW scheme!') 1624 1625 if mode=='external': 1626 MW = self.get_parameter(W.get('mass')) 1627 if not isinstance(MW, ParamCardVariable): 1628 newMW = ParamCardVariable(MW.name, MW.value, 'MASS', [24]) 1629 if not newMW.value: 1630 newMW.value = 80.385 1631 #remove the old definition 1632 self.get('parameters')[MW.depend].remove(MW) 1633 # add the new one 1634 self.add_param(newMW, ['external']) 1635 1636 # Now check for sw2. if not define bypass this 1637 try: 1638 sw2 = self.get_parameter('sw2') 1639 except KeyError: 1640 try: 1641 sw2 = self.get_parameter('mdl_sw2') 1642 except KeyError: 1643 sw2=None 1644 1645 if sw2: 1646 newsw2 = ParamCardVariable(sw2.name,sw2.value, 'SMINPUTS', [4]) 1647 if not newsw2.value: 1648 newsw2.value = 0.222246485786 1649 #remove the old definition 1650 self.get('parameters')[sw2.depend].remove(sw2) 1651 # add the new one 1652 self.add_param(newsw2, ['external']) 1653 # Force a refresh of the parameter dictionary 1654 self.parameters_dict = None 1655 return true 1656 1657 elif mode==set(['mz','mw','alpha']): 1658 # For now, all we support is to go from mz, Gf, alpha to mz, mw, alpha 1659 W = self.get('particle_dict')[24] 1660 mass = self.get_parameter(W.get('mass')) 1661 mass_expr = 'cmath.sqrt(%(prefix)sMZ__exp__2/2. + cmath.sqrt('+\ 1662 '%(prefix)sMZ__exp__4/4. - (%(prefix)saEW*cmath.pi*%(prefix)s'+\ 1663 'MZ__exp__2)/(%(prefix)sGf*%(prefix)ssqrt__2)))' 1664 if 'external' in mass.depend: 1665 # Nothing to be done 1666 return True 1667 match = False 1668 if mass.expr == mass_expr%{'prefix':''}: 1669 prefix = '' 1670 match = True 1671 elif mass.expr == mass_expr%{'prefix':'mdl_'}: 1672 prefix = 'mdl_' 1673 match = True 1674 if match: 1675 MW = ParamCardVariable(mass.name, mass.value, 'MASS', [24]) 1676 if not MW.value: 1677 MW.value = 80.385 1678 self.get('parameters')[('external',)].append(MW) 1679 self.get('parameters')[mass.depend].remove(mass) 1680 # Make Gf an internal parameter 1681 new_param = ModelVariable('Gf', 1682 '-%(prefix)saEW*%(prefix)sMZ**2*cmath.pi/(cmath.sqrt(2)*%(MW)s**2*(%(MW)s**2 - %(prefix)sMZ**2))' %\ 1683 {'MW': mass.name,'prefix':prefix}, 'complex', mass.depend) 1684 Gf = self.get_parameter('%sGf'%prefix) 1685 self.get('parameters')[('external',)].remove(Gf) 1686 self.add_param(new_param, ['%saEW'%prefix]) 1687 # Force a refresh of the parameter dictionary 1688 self.parameters_dict = None 1689 return True 1690 else: 1691 return False
1692
1693 - def change_mass_to_complex_scheme(self, toCMS=True):
1694 """modify the expression changing the mass to complex mass scheme""" 1695 1696 # 1) Change the 'CMSParam' of loop_qcd_qed model to 1.0 so as to remove 1697 # the 'real' prefix fromall UVCT wf renormalization expressions. 1698 # If toCMS is False, then it makes sure CMSParam is 0.0 and returns 1699 # immediatly. 1700 # 2) Find All input parameter mass and width associated 1701 # Add a internal parameter and replace mass with that param 1702 # 3) Find All mass fixed by the model and width associated 1703 # -> Both need to be fixed with a real() /Imag() 1704 # 4) Find All width set by the model 1705 # -> Need to be set with a real() 1706 # 5) Fix the Yukawa mass to the value of the complex mass/ real mass 1707 # 6) Loop through all expression and modify those accordingly 1708 # Including all parameter expression as complex 1709 1710 try: 1711 CMSParam = self.get_parameter('CMSParam') 1712 except KeyError: 1713 try: 1714 CMSParam = self.get_parameter('mdl_CMSParam') 1715 except KeyError: 1716 CMSParam = None 1717 1718 # Handle the case where we want to make sure the CMS is turned off 1719 if not toCMS: 1720 if CMSParam: 1721 CMSParam.expr = '0.0' 1722 return 1723 1724 # Now handle the case where we want to turn to CMS. 1725 if CMSParam: 1726 CMSParam.expr = '1.0' 1727 1728 to_change = {} 1729 mass_widths = [] # parameter which should stay real 1730 for particle in self.get('particles'): 1731 m = particle.get('width') 1732 if m in mass_widths: 1733 continue 1734 mass_widths.append(particle.get('width')) 1735 mass_widths.append(particle.get('mass')) 1736 width = self.get_parameter(particle.get('width')) 1737 if (isinstance(width.value, (complex,float)) and abs(width.value)==0.0) or \ 1738 width.name.lower() =='zero': 1739 #everything is fine since the width is zero 1740 continue 1741 if not isinstance(width, ParamCardVariable): 1742 width.expr = 're(%s)' % width.expr 1743 mass = self.get_parameter(particle.get('mass')) 1744 if (isinstance(width.value, (complex,float)) and abs(width.value)!=0.0) or \ 1745 mass.name.lower() != 'zero': 1746 # special SM treatment to change the gauge scheme automatically. 1747 if particle.get('pdg_code') == 24 and isinstance(mass, 1748 ModelVariable): 1749 status = self.change_electroweak_mode( 1750 set(['mz','mw','alpha'])) 1751 # Use the newly defined parameter for the W mass 1752 mass = self.get_parameter(particle.get('mass')) 1753 if not status: 1754 logger.warning('The W mass is not an external '+ 1755 'parameter in this model and the automatic change of'+ 1756 ' electroweak scheme changed. This is not advised for '+ 1757 'applying the complex mass scheme.') 1758 1759 # Add A new parameter CMASS 1760 #first compute the dependencies (as,...) 1761 depend = list(set(mass.depend + width.depend)) 1762 if len(depend)>1 and 'external' in depend: 1763 depend.remove('external') 1764 depend = tuple(depend) 1765 if depend == ('external',): 1766 depend = () 1767 1768 # Create the new parameter 1769 if isinstance(mass, ParamCardVariable): 1770 New_param = ModelVariable('CMASS_'+mass.name, 1771 'cmath.sqrt(%(mass)s**2 - complex(0,1) * %(mass)s * %(width)s)' \ 1772 % {'mass': mass.name, 'width': width.name}, 1773 'complex', depend) 1774 else: 1775 New_param = ModelVariable('CMASS_'+mass.name, 1776 mass.expr, 'complex', depend) 1777 # Modify the treatment of the width in this case 1778 if not isinstance(width, ParamCardVariable): 1779 width.expr = '- im(%s**2) / cmath.sqrt(re(%s**2))' % (mass.expr, mass.expr) 1780 else: 1781 # Remove external parameter from the param_card 1782 New_width = ModelVariable(width.name, 1783 '-1 * im(CMASS_%s**2) / %s' % (mass.name, mass.name), 'real', mass.depend) 1784 self.get('parameters')[('external',)].remove(width) 1785 self.add_param(New_param, (mass,)) 1786 self.add_param(New_width, (New_param,)) 1787 mass.expr = 'cmath.sqrt(re(%s**2))' % mass.expr 1788 to_change[mass.name] = New_param.name 1789 continue 1790 1791 mass.expr = 're(%s)' % mass.expr 1792 self.add_param(New_param, (mass, width)) 1793 to_change[mass.name] = New_param.name 1794 1795 # Remove the Yukawa and fix those accordingly to the mass/complex mass 1796 yukawas = [p for p in self.get('parameters')[('external',)] 1797 if p.lhablock.lower() == 'yukawa'] 1798 for yukawa in yukawas: 1799 # clean the pevious parameter 1800 self.get('parameters')[('external',)].remove(yukawa) 1801 1802 particle = self.get_particle(yukawa.lhacode[0]) 1803 mass = self.get_parameter(particle.get('mass')) 1804 1805 # add the new parameter in the correct category 1806 if mass.depend == ('external',): 1807 depend = () 1808 else: 1809 depend = mass.depend 1810 1811 New_param = ModelVariable(yukawa.name, mass.name, 'real', depend) 1812 1813 # Add it in the model at the correct place (for the dependences) 1814 if mass.name in to_change: 1815 expr = 'CMASS_%s' % mass.name 1816 else: 1817 expr = mass.name 1818 param_depend = self.get_parameter(expr) 1819 self.add_param(New_param, [param_depend]) 1820 1821 if not to_change: 1822 return 1823 1824 1825 # So at this stage we still need to modify all parameters depending of 1826 # particle's mass. In addition all parameter (but mass/width/external 1827 # parameter) should be pass in complex mode. 1828 pat = '|'.join(to_change.keys()) 1829 pat = r'(%s)\b' % pat 1830 pat = re.compile(pat) 1831 def replace(match): 1832 return to_change[match.group()]
1833 1834 # Modify the parameters 1835 for dep, list_param in self['parameters'].items(): 1836 for param in list_param: 1837 if param.name.startswith('CMASS_') or param.name in mass_widths or\ 1838 isinstance(param, ParamCardVariable): 1839 continue 1840 param.type = 'complex' 1841 # print param.expr, to_change 1842 1843 param.expr = pat.sub(replace, param.expr) 1844 1845 # Modify the couplings 1846 for dep, list_coup in self['couplings'].items(): 1847 for coup in list_coup: 1848 coup.expr = pat.sub(replace, coup.expr) 1849
1850 - def add_param(self, new_param, depend_param):
1851 """add the parameter in the list of parameter in a correct position""" 1852 1853 pos = 0 1854 for i,param in enumerate(self.get('parameters')[new_param.depend]): 1855 if param.name in depend_param: 1856 pos = i + 1 1857 self.get('parameters')[new_param.depend].insert(pos, new_param)
1858
1859 1860 #def __repr__(self): 1861 # """ """ 1862 # raise Exception 1863 # return "Model(%s)" % self.get_name() 1864 #__str__ = __repr__ 1865 ################################################################################ 1866 # Class for Parameter / Coupling 1867 ################################################################################ 1868 -class ModelVariable(object):
1869 """A Class for storing the information about coupling/ parameter""" 1870
1871 - def __init__(self, name, expression, type, depend=()):
1872 """Initialize a new parameter/coupling""" 1873 1874 self.name = name 1875 self.expr = expression # python expression 1876 self.type = type # real/complex 1877 self.depend = depend # depend on some other parameter -tuple- 1878 self.value = None
1879
1880 - def __eq__(self, other):
1881 """Object with same name are identical, If the object is a string we check 1882 if the attribute name is equal to this string""" 1883 1884 try: 1885 return other.name == self.name 1886 except Exception: 1887 return other == self.name
1888
1889 -class ParamCardVariable(ModelVariable):
1890 """ A class for storing the information linked to all the parameter 1891 which should be define in the param_card.dat""" 1892 1893 depend = ('external',) 1894 type = 'real' 1895
1896 - def __init__(self, name, value, lhablock, lhacode):
1897 """Initialize a new ParamCardVariable 1898 name: name of the variable 1899 value: default numerical value 1900 lhablock: name of the block in the param_card.dat 1901 lhacode: code associate to the variable 1902 """ 1903 self.name = name 1904 self.value = value 1905 self.lhablock = lhablock 1906 self.lhacode = lhacode
1907
1908 1909 #=============================================================================== 1910 # Classes used in diagram generation and process definition: 1911 # Leg, Vertex, Diagram, Process 1912 #=============================================================================== 1913 1914 #=============================================================================== 1915 # Leg 1916 #=============================================================================== 1917 -class Leg(PhysicsObject):
1918 """Leg object: id (Particle), number, I/F state, flag from_group 1919 """ 1920
1921 - def default_setup(self):
1922 """Default values for all properties""" 1923 1924 self['id'] = 0 1925 self['number'] = 0 1926 # state: True = final, False = initial (boolean to save memory) 1927 self['state'] = True 1928 #self['loop_line'] = False 1929 self['loop_line'] = False 1930 # from_group: Used in diagram generation 1931 self['from_group'] = True 1932 # onshell: decaying leg (True), forbidden s-channel (False), none (None) 1933 self['onshell'] = None
1934
1935 - def filter(self, name, value):
1936 """Filter for valid leg property values.""" 1937 1938 if name in ['id', 'number']: 1939 if not isinstance(value, int): 1940 raise self.PhysicsObjectError, \ 1941 "%s is not a valid integer for leg id" % str(value) 1942 1943 if name == 'state': 1944 if not isinstance(value, bool): 1945 raise self.PhysicsObjectError, \ 1946 "%s is not a valid leg state (True|False)" % \ 1947 str(value) 1948 1949 if name == 'from_group': 1950 if not isinstance(value, bool) and value != None: 1951 raise self.PhysicsObjectError, \ 1952 "%s is not a valid boolean for leg flag from_group" % \ 1953 str(value) 1954 1955 if name == 'loop_line': 1956 if not isinstance(value, bool) and value != None: 1957 raise self.PhysicsObjectError, \ 1958 "%s is not a valid boolean for leg flag loop_line" % \ 1959 str(value) 1960 1961 if name == 'onshell': 1962 if not isinstance(value, bool) and value != None: 1963 raise self.PhysicsObjectError, \ 1964 "%s is not a valid boolean for leg flag onshell" % \ 1965 str(value) 1966 return True
1967
1968 - def get_sorted_keys(self):
1969 """Return particle property names as a nicely sorted list.""" 1970 1971 return ['id', 'number', 'state', 'from_group', 'loop_line', 'onshell']
1972
1973 - def is_fermion(self, model):
1974 """Returns True if the particle corresponding to the leg is a 1975 fermion""" 1976 1977 assert isinstance(model, Model), "%s is not a model" % str(model) 1978 1979 return model.get('particle_dict')[self['id']].is_fermion()
1980
1981 - def is_incoming_fermion(self, model):
1982 """Returns True if leg is an incoming fermion, i.e., initial 1983 particle or final antiparticle""" 1984 1985 assert isinstance(model, Model), "%s is not a model" % str(model) 1986 1987 part = model.get('particle_dict')[self['id']] 1988 return part.is_fermion() and \ 1989 (self.get('state') == False and part.get('is_part') or \ 1990 self.get('state') == True and not part.get('is_part'))
1991
1992 - def is_outgoing_fermion(self, model):
1993 """Returns True if leg is an outgoing fermion, i.e., initial 1994 antiparticle or final particle""" 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') == True and part.get('is_part') or \ 2001 self.get('state') == False and not part.get('is_part'))
2002 2003 # Helper function. We don't overload the == operator because it might be useful 2004 # to define it differently than that later. 2005
2006 - def same(self, leg):
2007 """ Returns true if the leg in argument has the same ID and the same numer """ 2008 2009 # In case we want to check this leg with an integer in the tagging procedure, 2010 # then it only has to match the leg number. 2011 if isinstance(leg,int): 2012 if self['number']==leg: 2013 return True 2014 else: 2015 return False 2016 2017 # If using a Leg object instead, we also want to compare the other relevant 2018 # properties. 2019 elif isinstance(leg, Leg): 2020 if self['id']==leg.get('id') and \ 2021 self['number']==leg.get('number') and \ 2022 self['loop_line']==leg.get('loop_line') : 2023 return True 2024 else: 2025 return False 2026 2027 else : 2028 return False
2029 2030 # Make sure sort() sorts lists of legs according to 'number'
2031 - def __lt__(self, other):
2032 return self['number'] < other['number']
2033
2034 #=============================================================================== 2035 # LegList 2036 #=============================================================================== 2037 -class LegList(PhysicsObjectList):
2038 """List of Leg objects 2039 """ 2040
2041 - def is_valid_element(self, obj):
2042 """Test if object obj is a valid Leg for the list.""" 2043 2044 return isinstance(obj, Leg)
2045 2046 # Helper methods for diagram generation 2047
2048 - def from_group_elements(self):
2049 """Return all elements which have 'from_group' True""" 2050 2051 return filter(lambda leg: leg.get('from_group'), self)
2052
2053 - def minimum_one_from_group(self):
2054 """Return True if at least one element has 'from_group' True""" 2055 2056 return len(self.from_group_elements()) > 0
2057
2058 - def minimum_two_from_group(self):
2059 """Return True if at least two elements have 'from_group' True""" 2060 2061 return len(self.from_group_elements()) > 1
2062
2063 - def can_combine_to_1(self, ref_dict_to1):
2064 """If has at least one 'from_group' True and in ref_dict_to1, 2065 return the return list from ref_dict_to1, otherwise return False""" 2066 if self.minimum_one_from_group(): 2067 return ref_dict_to1.has_key(tuple(sorted([leg.get('id') for leg in self]))) 2068 else: 2069 return False
2070
2071 - def can_combine_to_0(self, ref_dict_to0, is_decay_chain=False):
2072 """If has at least two 'from_group' True and in ref_dict_to0, 2073 2074 return the vertex (with id from ref_dict_to0), otherwise return None 2075 2076 If is_decay_chain = True, we only allow clustering of the 2077 initial leg, since we want this to be the last wavefunction to 2078 be evaluated. 2079 """ 2080 if is_decay_chain: 2081 # Special treatment - here we only allow combination to 0 2082 # if the initial leg (marked by from_group = None) is 2083 # unclustered, since we want this to stay until the very 2084 # end. 2085 return any(leg.get('from_group') == None for leg in self) and \ 2086 ref_dict_to0.has_key(tuple(sorted([leg.get('id') \ 2087 for leg in self]))) 2088 2089 if self.minimum_two_from_group(): 2090 return ref_dict_to0.has_key(tuple(sorted([leg.get('id') for leg in self]))) 2091 else: 2092 return False
2093
2094 - def get_outgoing_id_list(self, model):
2095 """Returns the list of ids corresponding to the leglist with 2096 all particles outgoing""" 2097 2098 res = [] 2099 2100 assert isinstance(model, Model), "Error! model not model" 2101 2102 2103 for leg in self: 2104 if leg.get('state') == False: 2105 res.append(model.get('particle_dict')[leg.get('id')].get_anti_pdg_code()) 2106 else: 2107 res.append(leg.get('id')) 2108 2109 return res
2110
2111 - def sort(self,*args, **opts):
2112 """Match with FKSLegList""" 2113 Opts=copy.copy(opts) 2114 if 'pert' in Opts.keys(): 2115 del Opts['pert'] 2116 return super(LegList,self).sort(*args, **Opts)
2117
2118 2119 #=============================================================================== 2120 # MultiLeg 2121 #=============================================================================== 2122 -class MultiLeg(PhysicsObject):
2123 """MultiLeg object: ids (Particle or particles), I/F state 2124 """ 2125
2126 - def default_setup(self):
2127 """Default values for all properties""" 2128 2129 self['ids'] = [] 2130 self['state'] = True
2131
2132 - def filter(self, name, value):
2133 """Filter for valid multileg property values.""" 2134 2135 if name == 'ids': 2136 if not isinstance(value, list): 2137 raise self.PhysicsObjectError, \ 2138 "%s is not a valid list" % str(value) 2139 for i in value: 2140 if not isinstance(i, int): 2141 raise self.PhysicsObjectError, \ 2142 "%s is not a valid list of integers" % str(value) 2143 2144 if name == 'state': 2145 if not isinstance(value, bool): 2146 raise self.PhysicsObjectError, \ 2147 "%s is not a valid leg state (initial|final)" % \ 2148 str(value) 2149 2150 return True
2151
2152 - def get_sorted_keys(self):
2153 """Return particle property names as a nicely sorted list.""" 2154 2155 return ['ids', 'state']
2156
2157 #=============================================================================== 2158 # LegList 2159 #=============================================================================== 2160 -class MultiLegList(PhysicsObjectList):
2161 """List of MultiLeg objects 2162 """ 2163
2164 - def is_valid_element(self, obj):
2165 """Test if object obj is a valid MultiLeg for the list.""" 2166 2167 return isinstance(obj, MultiLeg)
2168
2169 #=============================================================================== 2170 # Vertex 2171 #=============================================================================== 2172 -class Vertex(PhysicsObject):
2173 """Vertex: list of legs (ordered), id (Interaction) 2174 """ 2175 2176 sorted_keys = ['id', 'legs'] 2177 2178 # This sets what are the ID's of the vertices that must be ignored for the 2179 # purpose of the multi-channeling. 0 and -1 are ID's of various technical 2180 # vertices which have no relevance from the perspective of the diagram 2181 # topology, while -2 is the ID of a vertex that results from a shrunk loop 2182 # (for loop-induced integration with MadEvent) and one may or may not want 2183 # to consider these higher point loops for the purpose of the multi-channeling. 2184 # So, adding -2 to the list below makes sur that all loops are considered 2185 # for multichanneling. 2186 ID_to_veto_for_multichanneling = [0,-1,-2] 2187 2188 # For loop-induced integration, considering channels from up to box loops 2189 # typically leads to better efficiencies. Beyond that, it is detrimental 2190 # because the phase-space generation is not suited to map contact interactions 2191 # This parameter controls up to how many legs should loop-induced diagrams 2192 # be considered for multichanneling. 2193 # Notice that, in the grouped subprocess case mode, if -2 is not added to 2194 # the list ID_to_veto_for_multichanneling then all loop are considered by 2195 # default and the constraint below is not applied. 2196 max_n_loop_for_multichanneling = 4 2197
2198 - def default_setup(self):
2199 """Default values for all properties""" 2200 2201 # The 'id' of the vertex corresponds to the interaction ID it is made of. 2202 # Notice that this 'id' can take the special values : 2203 # -1 : A two-point vertex which either 'sews' the two L-cut particles 2204 # together or simply merges two wavefunctions to create an amplitude 2205 # (in the case of tree-level diagrams). 2206 # -2 : The id given to the ContractedVertices (i.e. a shrunk loop) so 2207 # that it can be easily identified when constructing the DiagramChainLinks. 2208 self['id'] = 0 2209 self['legs'] = LegList()
2210
2211 - def filter(self, name, value):
2212 """Filter for valid vertex property values.""" 2213 2214 if name == 'id': 2215 if not isinstance(value, int): 2216 raise self.PhysicsObjectError, \ 2217 "%s is not a valid integer for vertex id" % str(value) 2218 2219 if name == 'legs': 2220 if not isinstance(value, LegList): 2221 raise self.PhysicsObjectError, \ 2222 "%s is not a valid LegList object" % str(value) 2223 2224 return True
2225
2226 - def get_sorted_keys(self):
2227 """Return particle property names as a nicely sorted list.""" 2228 2229 return self.sorted_keys #['id', 'legs']
2230
2231 - def nice_string(self):
2232 """return a nice string""" 2233 2234 mystr = [] 2235 for leg in self['legs']: 2236 mystr.append( str(leg['number']) + '(%s)' % str(leg['id'])) 2237 mystr = '(%s,id=%s ,obj_id:%s)' % (', '.join(mystr), self['id'], id(self)) 2238 2239 return(mystr)
2240 2241
2242 - def get_s_channel_id(self, model, ninitial):
2243 """Returns the id for the last leg as an outgoing 2244 s-channel. Returns 0 if leg is t-channel, or if identity 2245 vertex. Used to check for required and forbidden s-channel 2246 particles.""" 2247 2248 leg = self.get('legs')[-1] 2249 2250 if ninitial == 1: 2251 # For one initial particle, all legs are s-channel 2252 # Only need to flip particle id if state is False 2253 if leg.get('state') == True: 2254 return leg.get('id') 2255 else: 2256 return model.get('particle_dict')[leg.get('id')].\ 2257 get_anti_pdg_code() 2258 2259 # Number of initial particles is at least 2 2260 if self.get('id') == 0 or \ 2261 leg.get('state') == False: 2262 # identity vertex or t-channel particle 2263 return 0 2264 2265 if leg.get('loop_line'): 2266 # Loop lines never count as s-channel 2267 return 0 2268 2269 # Check if the particle number is <= ninitial 2270 # In that case it comes from initial and we should switch direction 2271 if leg.get('number') > ninitial: 2272 return leg.get('id') 2273 else: 2274 return model.get('particle_dict')[leg.get('id')].\ 2275 get_anti_pdg_code()
2276
2277 ## Check if the other legs are initial or final. 2278 ## If the latter, return leg id, if the former, return -leg id 2279 #if self.get('legs')[0].get('state') == True: 2280 # return leg.get('id') 2281 #else: 2282 # return model.get('particle_dict')[leg.get('id')].\ 2283 # get_anti_pdg_code() 2284 2285 #=============================================================================== 2286 # VertexList 2287 #=============================================================================== 2288 -class VertexList(PhysicsObjectList):
2289 """List of Vertex objects 2290 """ 2291 2292 orders = {} 2293
2294 - def is_valid_element(self, obj):
2295 """Test if object obj is a valid Vertex for the list.""" 2296 2297 return isinstance(obj, Vertex)
2298
2299 - def __init__(self, init_list=None, orders=None):
2300 """Creates a new list object, with an optional dictionary of 2301 coupling orders.""" 2302 2303 list.__init__(self) 2304 2305 if init_list is not None: 2306 for object in init_list: 2307 self.append(object) 2308 2309 if isinstance(orders, dict): 2310 self.orders = orders
2311
2312 #=============================================================================== 2313 # ContractedVertex 2314 #=============================================================================== 2315 -class ContractedVertex(Vertex):
2316 """ContractedVertex: When contracting a loop to a given vertex, the created 2317 vertex object is then a ContractedVertex object which has additional 2318 information with respect to a regular vertex object. For example, it contains 2319 the PDG of the particles attached to it. (necessary because the contracted 2320 vertex doesn't have an interaction ID which would allow to retrieve such 2321 information). 2322 """ 2323
2324 - def default_setup(self):
2325 """Default values for all properties""" 2326 2327 self['PDGs'] = [] 2328 self['loop_tag'] = tuple() 2329 self['loop_orders'] = {} 2330 super(ContractedVertex, self).default_setup()
2331
2332 - def filter(self, name, value):
2333 """Filter for valid vertex property values.""" 2334 2335 if name == 'PDGs': 2336 if isinstance(value, list): 2337 for elem in value: 2338 if not isinstance(elem,int): 2339 raise self.PhysicsObjectError, \ 2340 "%s is not a valid integer for leg PDG" % str(elem) 2341 else: 2342 raise self.PhysicsObjectError, \ 2343 "%s is not a valid list for contracted vertex PDGs"%str(value) 2344 if name == 'loop_tag': 2345 if isinstance(value, tuple): 2346 for elem in value: 2347 if not (isinstance(elem,int) or isinstance(elem,tuple)): 2348 raise self.PhysicsObjectError, \ 2349 "%s is not a valid int or tuple for loop tag element"%str(elem) 2350 else: 2351 raise self.PhysicsObjectError, \ 2352 "%s is not a valid tuple for a contracted vertex loop_tag."%str(value) 2353 if name == 'loop_orders': 2354 Interaction.filter(Interaction(), 'orders', value) 2355 else: 2356 return super(ContractedVertex, self).filter(name, value) 2357 2358 return True
2359
2360 - def get_sorted_keys(self):
2361 """Return particle property names as a nicely sorted list.""" 2362 2363 return super(ContractedVertex, self).get_sorted_keys()+['PDGs']
2364
2365 #=============================================================================== 2366 # Diagram 2367 #=============================================================================== 2368 -class Diagram(PhysicsObject):
2369 """Diagram: list of vertices (ordered) 2370 """ 2371
2372 - def default_setup(self):
2373 """Default values for all properties""" 2374 2375 self['vertices'] = VertexList() 2376 self['orders'] = {}
2377
2378 - def filter(self, name, value):
2379 """Filter for valid diagram property values.""" 2380 2381 if name == 'vertices': 2382 if not isinstance(value, VertexList): 2383 raise self.PhysicsObjectError, \ 2384 "%s is not a valid VertexList object" % str(value) 2385 2386 if name == 'orders': 2387 Interaction.filter(Interaction(), 'orders', value) 2388 2389 return True
2390
2391 - def get_sorted_keys(self):
2392 """Return particle property names as a nicely sorted list.""" 2393 2394 return ['vertices', 'orders']
2395
2396 - def nice_string(self):
2397 """Returns a nicely formatted string of the diagram content.""" 2398 2399 pass_sanity = True 2400 if self['vertices']: 2401 mystr = '(' 2402 for vert in self['vertices']: 2403 used_leg = [] 2404 mystr = mystr + '(' 2405 for leg in vert['legs'][:-1]: 2406 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) + ',' 2407 used_leg.append(leg['number']) 2408 if __debug__ and len(used_leg) != len(set(used_leg)): 2409 pass_sanity = False 2410 responsible = id(vert) 2411 2412 if self['vertices'].index(vert) < len(self['vertices']) - 1: 2413 # Do not want ">" in the last vertex 2414 mystr = mystr[:-1] + '>' 2415 mystr = mystr + str(vert['legs'][-1]['number']) + '(%s)' % str(vert['legs'][-1]['id']) + ',' 2416 mystr = mystr + 'id:' + str(vert['id']) + '),' 2417 2418 mystr = mystr[:-1] + ')' 2419 mystr += " (%s)" % (",".join(["%s=%d" % (key, self['orders'][key]) \ 2420 for key in sorted(self['orders'].keys())])) 2421 2422 if not pass_sanity: 2423 raise Exception, "invalid diagram: %s. vert_id: %s" % (mystr, responsible) 2424 2425 return mystr 2426 else: 2427 return '()'
2428
2429 - def calculate_orders(self, model):
2430 """Calculate the actual coupling orders of this diagram. Note 2431 that the special order WEIGTHED corresponds to the sum of 2432 hierarchys for the couplings.""" 2433 2434 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')]) 2435 weight = 0 2436 for vertex in self['vertices']: 2437 if vertex.get('id') in [0,-1]: continue 2438 if vertex.get('id') == -2: 2439 couplings = vertex.get('loop_orders') 2440 else: 2441 couplings = model.get('interaction_dict')[vertex.get('id')].\ 2442 get('orders') 2443 for coupling in couplings: 2444 coupling_orders[coupling] += couplings[coupling] 2445 weight += sum([model.get('order_hierarchy')[c]*n for \ 2446 (c,n) in couplings.items()]) 2447 coupling_orders['WEIGHTED'] = weight 2448 self.set('orders', coupling_orders)
2449
2450 - def pass_squared_order_constraints(self, diag_multiplier, squared_orders, 2451 sq_orders_types):
2452 """ Returns wether the contributiong consisting in the current diagram 2453 multiplied by diag_multiplier passes the *positive* squared_orders 2454 specified ( a dictionary ) of types sq_order_types (a dictionary whose 2455 values are the relational operator used to define the constraint of the 2456 order in key).""" 2457 2458 for order, value in squared_orders.items(): 2459 if value<0: 2460 continue 2461 combined_order = self.get_order(order) + \ 2462 diag_multiplier.get_order(order) 2463 if ( sq_orders_types[order]=='==' and combined_order != value ) or \ 2464 ( sq_orders_types[order] in ['=', '<='] and combined_order > value) or \ 2465 ( sq_orders_types[order]=='>' and combined_order <= value) : 2466 return False 2467 return True
2468
2469 - def get_order(self, order):
2470 """Return the order of this diagram. It returns 0 if it is not present.""" 2471 2472 try: 2473 return self['orders'][order] 2474 except Exception: 2475 return 0
2476
2477 - def get_contracted_loop_diagram(self, struct_rep=None):
2478 """ Returns a Diagram which correspond to the loop diagram with the 2479 loop shrunk to a point. Of course for a instance of base_objects.Diagram 2480 one must simply return self.""" 2481 2482 return self
2483
2484 - def get_external_legs(self):
2485 """ Return the list of external legs of this diagram """ 2486 2487 external_legs = LegList([]) 2488 for leg in sum([vert.get('legs') for vert in self.get('vertices')],[]): 2489 if not leg.get('number') in [l.get('number') for l in external_legs]: 2490 external_legs.append(leg) 2491 2492 return external_legs
2493
2494 - def renumber_legs(self, perm_map, leg_list):
2495 """Renumber legs in all vertices according to perm_map""" 2496 2497 vertices = VertexList() 2498 min_dict = copy.copy(perm_map) 2499 # Dictionary from leg number to state 2500 state_dict = dict([(l.get('number'), l.get('state')) for l in leg_list]) 2501 # First renumber all legs in the n-1->1 vertices 2502 for vertex in self.get('vertices')[:-1]: 2503 vertex = copy.copy(vertex) 2504 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2505 for leg in leg_list[:-1]: 2506 leg.set('number', min_dict[leg.get('number')]) 2507 leg.set('state', state_dict[leg.get('number')]) 2508 min_number = min([leg.get('number') for leg in leg_list[:-1]]) 2509 leg = leg_list[-1] 2510 min_dict[leg.get('number')] = min_number 2511 # resulting leg is initial state if there is exactly one 2512 # initial state leg among the incoming legs 2513 state_dict[min_number] = len([l for l in leg_list[:-1] if \ 2514 not l.get('state')]) != 1 2515 leg.set('number', min_number) 2516 leg.set('state', state_dict[min_number]) 2517 vertex.set('legs', leg_list) 2518 vertices.append(vertex) 2519 # Now renumber the legs in final vertex 2520 vertex = copy.copy(self.get('vertices')[-1]) 2521 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2522 for leg in leg_list: 2523 leg.set('number', min_dict[leg.get('number')]) 2524 leg.set('state', state_dict[leg.get('number')]) 2525 vertex.set('legs', leg_list) 2526 vertices.append(vertex) 2527 # Finally create new diagram 2528 new_diag = copy.copy(self) 2529 new_diag.set('vertices', vertices) 2530 state_dict = {True:'T',False:'F'} 2531 return new_diag
2532
2533 - def get_vertex_leg_numbers(self, 2534 veto_inter_id=Vertex.ID_to_veto_for_multichanneling, 2535 max_n_loop=0):
2536 """Return a list of the number of legs in the vertices for 2537 this diagram. 2538 This function is only used for establishing the multi-channeling, so that 2539 we exclude from it all the fake vertices and the vertices resulting from 2540 shrunk loops (id=-2)""" 2541 2542 2543 if max_n_loop == 0: 2544 max_n_loop = Vertex.max_n_loop_for_multichanneling 2545 2546 res = [len(v.get('legs')) for v in self.get('vertices') if (v.get('id') \ 2547 not in veto_inter_id) or (v.get('id')==-2 and 2548 len(v.get('legs'))>max_n_loop)] 2549 2550 return res
2551
2552 - def get_num_configs(self, model, ninitial):
2553 """Return the maximum number of configs from this diagram, 2554 given by 2^(number of non-zero width s-channel propagators)""" 2555 2556 s_channels = [v.get_s_channel_id(model,ninitial) for v in \ 2557 self.get('vertices')[:-1]] 2558 num_props = len([i for i in s_channels if i != 0 and \ 2559 model.get_particle(i).get('width').lower() != 'zero']) 2560 2561 if num_props < 1: 2562 return 1 2563 else: 2564 return 2**num_props
2565
2566 - def get_flow_charge_diff(self, model):
2567 """return the difference of total diff of charge occuring on the 2568 lofw of the initial parton. return [None,None] if the two initial parton 2569 are connected and the (partial) value if None if the initial parton is 2570 not a fermiom""" 2571 2572 import madgraph.core.drawing as drawing 2573 drawdiag = drawing.FeynmanDiagram(self, model) 2574 drawdiag.load_diagram() 2575 out = [] 2576 2577 for v in drawdiag.initial_vertex: 2578 init_part = v.lines[0] 2579 if not init_part.is_fermion(): 2580 out.append(None) 2581 continue 2582 2583 init_charge = model.get_particle(init_part.id).get('charge') 2584 2585 l_last = init_part 2586 v_last = v 2587 vcurrent = l_last.end 2588 if vcurrent == v: 2589 vcurrent = l_last.begin 2590 security =0 2591 while not vcurrent.is_external(): 2592 if security > 1000: 2593 raise Exception, 'wrong diagram' 2594 next_l = [l for l in vcurrent.lines if l is not l_last and l.is_fermion()][0] 2595 next_v = next_l.end 2596 if next_v == vcurrent: 2597 next_v = next_l.begin 2598 l_last, vcurrent = next_l, next_v 2599 if vcurrent in drawdiag.initial_vertex: 2600 return [None, None] 2601 2602 out.append(model.get_particle(l_last.id).get('charge') - init_charge) 2603 return out
2604
2605 2606 #=============================================================================== 2607 # DiagramList 2608 #=============================================================================== 2609 -class DiagramList(PhysicsObjectList):
2610 """List of Diagram objects 2611 """ 2612
2613 - def is_valid_element(self, obj):
2614 """Test if object obj is a valid Diagram for the list.""" 2615 2616 return isinstance(obj, Diagram)
2617
2618 - def nice_string(self, indent=0):
2619 """Returns a nicely formatted string""" 2620 mystr = " " * indent + str(len(self)) + ' diagrams:\n' 2621 for i, diag in enumerate(self): 2622 mystr = mystr + " " * indent + str(i+1) + " " + \ 2623 diag.nice_string() + '\n' 2624 return mystr[:-1]
2625 2626 # Helper function 2627
2628 - def get_max_order(self,order):
2629 """ Return the order of the diagram in the list with the maximum coupling 2630 order for the coupling specified """ 2631 max_order=-1 2632 2633 for diag in self: 2634 if order in diag['orders'].keys(): 2635 if max_order==-1 or diag['orders'][order] > max_order: 2636 max_order = diag['orders'][order] 2637 2638 return max_order
2639
2640 - def apply_negative_sq_order(self, ref_diag_list, order, value, order_type):
2641 """ This function returns a fitlered version of the diagram list self 2642 which satisfy the negative squared_order constraint 'order' with negative 2643 value 'value' and of type 'order_type', assuming that the diagram_list 2644 it must be squared against is 'reg_diag_list'. It also returns the 2645 new postive target squared order which correspond to this negative order 2646 constraint. Example: u u~ > d d~ QED^2<=-2 means that one wants to 2647 pick terms only up to the the next-to-leading order contributiong in QED, 2648 which is QED=2 in this case, so that target_order=4 is returned.""" 2649 2650 # First we must compute all contributions to that order 2651 target_order = min(ref_diag_list.get_order_values(order))+\ 2652 min(self.get_order_values(order))+2*(-value-1) 2653 2654 new_list = self.apply_positive_sq_orders(ref_diag_list, 2655 {order:target_order}, {order:order_type}) 2656 2657 return new_list, target_order
2658
2659 - def apply_positive_sq_orders(self, ref_diag_list, sq_orders, sq_order_types):
2660 """ This function returns a filtered version of self which contain 2661 only the diagram which satisfy the positive squared order constraints 2662 sq_orders of type sq_order_types and assuming that the diagrams are 2663 multiplied with those of the reference diagram list ref_diag_list.""" 2664 2665 new_diag_list = DiagramList() 2666 for tested_diag in self: 2667 for ref_diag in ref_diag_list: 2668 if tested_diag.pass_squared_order_constraints(ref_diag, 2669 sq_orders,sq_order_types): 2670 new_diag_list.append(tested_diag) 2671 break 2672 return new_diag_list
2673
2674 - def filter_constrained_orders(self, order, value, operator):
2675 """ This function modifies the current object and remove the diagram 2676 which do not obey the condition """ 2677 2678 new = [] 2679 for tested_diag in self: 2680 if operator == '==': 2681 if tested_diag['orders'][order] == value: 2682 new.append(tested_diag) 2683 elif operator == '>': 2684 if tested_diag['orders'][order] > value: 2685 new.append(tested_diag) 2686 self[:] = new 2687 return self
2688 2689
2690 - def get_min_order(self,order):
2691 """ Return the order of the diagram in the list with the mimimum coupling 2692 order for the coupling specified """ 2693 min_order=-1 2694 for diag in self: 2695 if order in diag['orders'].keys(): 2696 if min_order==-1 or diag['orders'][order] < min_order: 2697 min_order = diag['orders'][order] 2698 else: 2699 return 0 2700 2701 return min_order
2702
2703 - def get_order_values(self, order):
2704 """ Return the list of possible values appearing in the diagrams of this 2705 list for the order given in argument """ 2706 2707 values=set([]) 2708 for diag in self: 2709 if order in diag['orders'].keys(): 2710 values.add(diag['orders'][order]) 2711 else: 2712 values.add(0) 2713 2714 return list(values)
2715
2716 #=============================================================================== 2717 # Process 2718 #=============================================================================== 2719 -class Process(PhysicsObject):
2720 """Process: list of legs (ordered) 2721 dictionary of orders 2722 model 2723 process id 2724 """ 2725
2726 - def default_setup(self):
2727 """Default values for all properties""" 2728 2729 self['legs'] = LegList() 2730 # These define the orders restrict the born and loop amplitudes. 2731 self['orders'] = {} 2732 self['model'] = Model() 2733 # Optional number to identify the process 2734 self['id'] = 0 2735 self['uid'] = 0 # should be a uniq id number 2736 # Required s-channels are given as a list of id lists. Only 2737 # diagrams with all s-channels in any of the lists are 2738 # allowed. This enables generating e.g. Z/gamma as s-channel 2739 # propagators. 2740 self['required_s_channels'] = [] 2741 self['forbidden_onsh_s_channels'] = [] 2742 self['forbidden_s_channels'] = [] 2743 self['forbidden_particles'] = [] 2744 self['is_decay_chain'] = False 2745 self['overall_orders'] = {} 2746 # Decay chain processes associated with this process 2747 self['decay_chains'] = ProcessList() 2748 # Legs with decay chains substituted in 2749 self['legs_with_decays'] = LegList() 2750 # Loop particles if the process is to be computed at NLO 2751 self['perturbation_couplings']=[] 2752 # These orders restrict the order of the squared amplitude. 2753 # This dictionary possibly contains a key "WEIGHTED" which 2754 # gives the upper bound for the total weighted order of the 2755 # squared amplitude. 2756 self['squared_orders'] = {} 2757 # The squared order (sqorders) constraints above can either be upper 2758 # bound (<=) or exact match (==) depending on how they were specified 2759 # in the user input. This choice is stored in the dictionary below. 2760 # Notice that the upper bound is the default 2761 self['sqorders_types'] = {} 2762 # other type of constraint at amplitude level 2763 self['constrained_orders'] = {} # {QED: (4,'>')} 2764 self['has_born'] = True 2765 # The NLO_mode is always None for a tree-level process and can be 2766 # 'all', 'real', 'virt' for a loop process. 2767 self['NLO_mode'] = 'tree' 2768 # The user might want to have the individual matrix element evaluations 2769 # for specific values of the coupling orders. The list below specifies 2770 # what are the coupling names which need be individually treated. 2771 # For example, for the process p p > j j [] QED=2 (QED=2 is 2772 # then a squared order constraint), then QED will appear in the 2773 # 'split_orders' list so that the subroutine in matrix.f return the 2774 # evaluation of the matrix element individually for the pure QCD 2775 # contribution 'QCD=4 QED=0', the pure interference 'QCD=2 QED=2' and 2776 # the pure QED contribution of order 'QCD=0 QED=4'. 2777 self['split_orders'] = []
2778
2779 - def filter(self, name, value):
2780 """Filter for valid process property values.""" 2781 2782 if name in ['legs', 'legs_with_decays'] : 2783 if not isinstance(value, LegList): 2784 raise self.PhysicsObjectError, \ 2785 "%s is not a valid LegList object" % str(value) 2786 2787 if name in ['orders', 'overall_orders','squared_orders']: 2788 Interaction.filter(Interaction(), 'orders', value) 2789 2790 if name == 'constrained_orders': 2791 if not isinstance(value, dict): 2792 raise self.PhysicsObjectError, \ 2793 "%s is not a valid dictionary" % str(value) 2794 2795 if name == 'sqorders_types': 2796 if not isinstance(value, dict): 2797 raise self.PhysicsObjectError, \ 2798 "%s is not a valid dictionary" % str(value) 2799 for order in value.keys()+value.values(): 2800 if not isinstance(order, str): 2801 raise self.PhysicsObjectError, \ 2802 "%s is not a valid string" % str(value) 2803 2804 if name == 'split_orders': 2805 if not isinstance(value, list): 2806 raise self.PhysicsObjectError, \ 2807 "%s is not a valid list" % str(value) 2808 for order in value: 2809 if not isinstance(order, str): 2810 raise self.PhysicsObjectError, \ 2811 "%s is not a valid string" % str(value) 2812 2813 if name == 'model': 2814 if not isinstance(value, Model): 2815 raise self.PhysicsObjectError, \ 2816 "%s is not a valid Model object" % str(value) 2817 if name in ['id', 'uid']: 2818 if not isinstance(value, int): 2819 raise self.PhysicsObjectError, \ 2820 "Process %s %s is not an integer" % (name, repr(value)) 2821 2822 if name == 'required_s_channels': 2823 if not isinstance(value, list): 2824 raise self.PhysicsObjectError, \ 2825 "%s is not a valid list" % str(value) 2826 for l in value: 2827 if not isinstance(l, list): 2828 raise self.PhysicsObjectError, \ 2829 "%s is not a valid list of lists" % str(value) 2830 for i in l: 2831 if not isinstance(i, int): 2832 raise self.PhysicsObjectError, \ 2833 "%s is not a valid list of integers" % str(l) 2834 if i == 0: 2835 raise self.PhysicsObjectError, \ 2836 "Not valid PDG code %d for s-channel particle" % i 2837 2838 if name in ['forbidden_onsh_s_channels', 'forbidden_s_channels']: 2839 if not isinstance(value, list): 2840 raise self.PhysicsObjectError, \ 2841 "%s is not a valid list" % str(value) 2842 for i in value: 2843 if not isinstance(i, int): 2844 raise self.PhysicsObjectError, \ 2845 "%s is not a valid list of integers" % str(value) 2846 if i == 0: 2847 raise self.PhysicsObjectError, \ 2848 "Not valid PDG code %d for s-channel particle" % str(value) 2849 2850 if name == 'forbidden_particles': 2851 if not isinstance(value, list): 2852 raise self.PhysicsObjectError, \ 2853 "%s is not a valid list" % str(value) 2854 for i in value: 2855 if not isinstance(i, int): 2856 raise self.PhysicsObjectError, \ 2857 "%s is not a valid list of integers" % str(value) 2858 if i <= 0: 2859 raise self.PhysicsObjectError, \ 2860 "Forbidden particles should have a positive PDG code" % str(value) 2861 2862 if name == 'perturbation_couplings': 2863 if not isinstance(value, list): 2864 raise self.PhysicsObjectError, \ 2865 "%s is not a valid list" % str(value) 2866 for order in value: 2867 if not isinstance(order, str): 2868 raise self.PhysicsObjectError, \ 2869 "%s is not a valid string" % str(value) 2870 2871 if name == 'is_decay_chain': 2872 if not isinstance(value, bool): 2873 raise self.PhysicsObjectError, \ 2874 "%s is not a valid bool" % str(value) 2875 2876 if name == 'has_born': 2877 if not isinstance(value, bool): 2878 raise self.PhysicsObjectError, \ 2879 "%s is not a valid bool" % str(value) 2880 2881 if name == 'decay_chains': 2882 if not isinstance(value, ProcessList): 2883 raise self.PhysicsObjectError, \ 2884 "%s is not a valid ProcessList" % str(value) 2885 2886 if name == 'NLO_mode': 2887 import madgraph.interface.madgraph_interface as mg 2888 if value not in mg.MadGraphCmd._valid_nlo_modes: 2889 raise self.PhysicsObjectError, \ 2890 "%s is not a valid NLO_mode" % str(value) 2891 return True
2892
2893 - def has_multiparticle_label(self):
2894 """ A process, not being a ProcessDefinition never carries multiple 2895 particles labels""" 2896 2897 return False
2898
2899 - def set(self, name, value):
2900 """Special set for forbidden particles - set to abs value.""" 2901 2902 if name == 'forbidden_particles': 2903 try: 2904 value = [abs(i) for i in value] 2905 except Exception: 2906 pass 2907 2908 if name == 'required_s_channels': 2909 # Required s-channels need to be a list of lists of ids 2910 if value and isinstance(value, list) and \ 2911 not isinstance(value[0], list): 2912 value = [value] 2913 2914 return super(Process, self).set(name, value) # call the mother routine
2915
2916 - def get_squared_order_type(self, order):
2917 """ Return what kind of squared order constraint was specified for the 2918 order 'order'.""" 2919 2920 if order in self['sqorders_types'].keys(): 2921 return self['sqorders_types'][order] 2922 else: 2923 # Default behavior '=' is interpreted as upper bound '<=' 2924 return '='
2925
2926 - def get(self, name):
2927 """Special get for legs_with_decays""" 2928 2929 if name == 'legs_with_decays': 2930 self.get_legs_with_decays() 2931 2932 if name == 'sqorders_types': 2933 # We must make sure that there is a type for each sqorder defined 2934 for order in self['squared_orders'].keys(): 2935 if order not in self['sqorders_types']: 2936 # Then assign its type to the default '=' 2937 self['sqorders_types'][order]='=' 2938 2939 return super(Process, self).get(name) # call the mother routine
2940
2941 - def get_sorted_keys(self):
2942 """Return process property names as a nicely sorted list.""" 2943 2944 return ['legs', 'orders', 'overall_orders', 'squared_orders', 2945 'constrained_orders', 2946 'model', 'id', 'required_s_channels', 2947 'forbidden_onsh_s_channels', 'forbidden_s_channels', 2948 'forbidden_particles', 'is_decay_chain', 'decay_chains', 2949 'legs_with_decays', 'perturbation_couplings', 'has_born', 2950 'NLO_mode','split_orders']
2951
2952 - def nice_string(self, indent=0, print_weighted = True, prefix=True):
2953 """Returns a nicely formated string about current process 2954 content. Since the WEIGHTED order is automatically set and added to 2955 the user-defined list of orders, it can be ommitted for some info 2956 displays.""" 2957 2958 if prefix: 2959 mystr = " " * indent + "Process: " 2960 else: 2961 mystr = "" 2962 prevleg = None 2963 for leg in self['legs']: 2964 mypart = self['model'].get('particle_dict')[leg['id']] 2965 if prevleg and prevleg['state'] == False \ 2966 and leg['state'] == True: 2967 # Separate initial and final legs by > 2968 mystr = mystr + '> ' 2969 # Add required s-channels 2970 if self['required_s_channels'] and \ 2971 self['required_s_channels'][0]: 2972 mystr += "|".join([" ".join([self['model'].\ 2973 get('particle_dict')[req_id].get_name() \ 2974 for req_id in id_list]) \ 2975 for id_list in self['required_s_channels']]) 2976 mystr = mystr + ' > ' 2977 2978 mystr = mystr + mypart.get_name() + ' ' 2979 #mystr = mystr + '(%i) ' % leg['number'] 2980 prevleg = leg 2981 2982 # Add orders 2983 if self['orders']: 2984 to_add = [] 2985 for key in self['orders']: 2986 if not print_weighted and key == 'WEIGHTED': 2987 continue 2988 value = int(self['orders'][key]) 2989 if key in self['squared_orders']: 2990 if self.get_squared_order_type(key) in ['<=', '==', '='] and \ 2991 self['squared_orders'][key] == value: 2992 continue 2993 if self.get_squared_order_type(key) in ['>'] and value == 99: 2994 continue 2995 if key in self['constrained_orders']: 2996 if value == self['constrained_orders'][key][0] and\ 2997 self['constrained_orders'][key][1] in ['=', '<=', '==']: 2998 continue 2999 if value == 0: 3000 to_add.append('%s=0' % key) 3001 else: 3002 to_add.append('%s<=%s' % (key,value)) 3003 3004 if to_add: 3005 mystr = mystr + " ".join(to_add) + ' ' 3006 3007 if self['constrained_orders']: 3008 mystr = mystr + " ".join('%s%s%d' % (key, type, value) for 3009 (key,(value,type)) 3010 in self['constrained_orders'].items()) + ' ' 3011 3012 # Add perturbation_couplings 3013 if self['perturbation_couplings']: 3014 mystr = mystr + '[ ' 3015 if self['NLO_mode']!='tree': 3016 if self['NLO_mode']=='virt' and not self['has_born']: 3017 mystr = mystr + 'sqrvirt = ' 3018 else: 3019 mystr = mystr + self['NLO_mode'] + ' = ' 3020 for order in self['perturbation_couplings']: 3021 mystr = mystr + order + ' ' 3022 mystr = mystr + '] ' 3023 3024 # Add squared orders 3025 if self['squared_orders']: 3026 to_add = [] 3027 for key in self['squared_orders'].keys(): 3028 if not print_weighted and key == 'WEIGHTED': 3029 continue 3030 if key in self['constrained_orders']: 3031 if self['constrained_orders'][key][0] == self['squared_orders'][key]/2 and \ 3032 self['constrained_orders'][key][1] == self.get_squared_order_type(key): 3033 continue 3034 to_add.append(key + '^2%s%d'%\ 3035 (self.get_squared_order_type(key),self['squared_orders'][key])) 3036 3037 if to_add: 3038 mystr = mystr + " ".join(to_add) + ' ' 3039 3040 3041 # Add forbidden s-channels 3042 if self['forbidden_onsh_s_channels']: 3043 mystr = mystr + '$ ' 3044 for forb_id in self['forbidden_onsh_s_channels']: 3045 forbpart = self['model'].get('particle_dict')[forb_id] 3046 mystr = mystr + forbpart.get_name() + ' ' 3047 3048 # Add double forbidden s-channels 3049 if self['forbidden_s_channels']: 3050 mystr = mystr + '$$ ' 3051 for forb_id in self['forbidden_s_channels']: 3052 forbpart = self['model'].get('particle_dict')[forb_id] 3053 mystr = mystr + forbpart.get_name() + ' ' 3054 3055 # Add forbidden particles 3056 if self['forbidden_particles']: 3057 mystr = mystr + '/ ' 3058 for forb_id in self['forbidden_particles']: 3059 forbpart = self['model'].get('particle_dict')[forb_id] 3060 mystr = mystr + forbpart.get_name() + ' ' 3061 3062 # Remove last space 3063 mystr = mystr[:-1] 3064 3065 if self.get('id') or self.get('overall_orders'): 3066 mystr += " @%d" % self.get('id') 3067 if self.get('overall_orders'): 3068 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3069 for key in sorted(self['orders'])]) + ' ' 3070 3071 if not self.get('decay_chains'): 3072 return mystr 3073 3074 for decay in self['decay_chains']: 3075 mystr = mystr + '\n' + \ 3076 decay.nice_string(indent + 2).replace('Process', 'Decay') 3077 3078 return mystr
3079
3080 - def input_string(self):
3081 """Returns a process string corresponding to the input string 3082 in the command line interface.""" 3083 3084 mystr = "" 3085 prevleg = None 3086 3087 for leg in self['legs']: 3088 mypart = self['model'].get('particle_dict')[leg['id']] 3089 if prevleg and prevleg['state'] == False \ 3090 and leg['state'] == True: 3091 # Separate initial and final legs by ">" 3092 mystr = mystr + '> ' 3093 # Add required s-channels 3094 if self['required_s_channels'] and \ 3095 self['required_s_channels'][0]: 3096 mystr += "|".join([" ".join([self['model'].\ 3097 get('particle_dict')[req_id].get_name() \ 3098 for req_id in id_list]) \ 3099 for id_list in self['required_s_channels']]) 3100 mystr = mystr + '> ' 3101 3102 mystr = mystr + mypart.get_name() + ' ' 3103 #mystr = mystr + '(%i) ' % leg['number'] 3104 prevleg = leg 3105 3106 if self['orders']: 3107 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3108 for key in self['orders']]) + ' ' 3109 3110 # Add perturbation orders 3111 if self['perturbation_couplings']: 3112 mystr = mystr + '[ ' 3113 if self['NLO_mode']: 3114 mystr = mystr + self['NLO_mode'] 3115 if not self['has_born']: 3116 mystr = mystr + '^2' 3117 mystr = mystr + '= ' 3118 3119 for order in self['perturbation_couplings']: 3120 mystr = mystr + order + ' ' 3121 mystr = mystr + '] ' 3122 3123 # Add squared orders 3124 if self['perturbation_couplings'] and self['squared_orders']: 3125 mystr = mystr + " ".join([key + '=' + repr(self['squared_orders'][key]) \ 3126 for key in self['squared_orders']]) + ' ' 3127 3128 # Add forbidden s-channels 3129 if self['forbidden_onsh_s_channels']: 3130 mystr = mystr + '$ ' 3131 for forb_id in self['forbidden_onsh_s_channels']: 3132 forbpart = self['model'].get('particle_dict')[forb_id] 3133 mystr = mystr + forbpart.get_name() + ' ' 3134 3135 # Add double forbidden s-channels 3136 if self['forbidden_s_channels']: 3137 mystr = mystr + '$$ ' 3138 for forb_id in self['forbidden_s_channels']: 3139 forbpart = self['model'].get('particle_dict')[forb_id] 3140 mystr = mystr + forbpart.get_name() + ' ' 3141 3142 # Add forbidden particles 3143 if self['forbidden_particles']: 3144 mystr = mystr + '/ ' 3145 for forb_id in self['forbidden_particles']: 3146 forbpart = self['model'].get('particle_dict')[forb_id] 3147 mystr = mystr + forbpart.get_name() + ' ' 3148 3149 # Remove last space 3150 mystr = mystr[:-1] 3151 3152 if self.get('overall_orders'): 3153 mystr += " @%d" % self.get('id') 3154 if self.get('overall_orders'): 3155 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3156 for key in sorted(self['orders'])]) + ' ' 3157 3158 if not self.get('decay_chains'): 3159 return mystr 3160 3161 for decay in self['decay_chains']: 3162 paren1 = '' 3163 paren2 = '' 3164 if decay.get('decay_chains'): 3165 paren1 = '(' 3166 paren2 = ')' 3167 mystr += ', ' + paren1 + decay.input_string() + paren2 3168 3169 return mystr
3170
3171 - def base_string(self):
3172 """Returns a string containing only the basic process (w/o decays).""" 3173 3174 mystr = "" 3175 prevleg = None 3176 for leg in self.get_legs_with_decays(): 3177 mypart = self['model'].get('particle_dict')[leg['id']] 3178 if prevleg and prevleg['state'] == False \ 3179 and leg['state'] == True: 3180 # Separate initial and final legs by ">" 3181 mystr = mystr + '> ' 3182 mystr = mystr + mypart.get_name() + ' ' 3183 prevleg = leg 3184 3185 # Remove last space 3186 return mystr[:-1]
3187
3188 - def shell_string(self, schannel=True, forbid=True, main=True, pdg_order=False, 3189 print_id = True):
3190 """Returns process as string with '~' -> 'x', '>' -> '_', 3191 '+' -> 'p' and '-' -> 'm', including process number, 3192 intermediate s-channels and forbidden particles, 3193 pdg_order allow to order to leg order by pid.""" 3194 3195 mystr = "" 3196 if not self.get('is_decay_chain') and print_id: 3197 mystr += "%d_" % self['id'] 3198 3199 prevleg = None 3200 if pdg_order: 3201 legs = [l for l in self['legs'][1:]] 3202 def order_leg(l1,l2): 3203 id1 = l1.get('id') 3204 id2 = l2.get('id') 3205 return id2-id1
3206 legs.sort(cmp=order_leg) 3207 legs.insert(0, self['legs'][0]) 3208 else: 3209 legs = self['legs'] 3210 3211 3212 for leg in legs: 3213 mypart = self['model'].get('particle_dict')[leg['id']] 3214 if prevleg and prevleg['state'] == False \ 3215 and leg['state'] == True: 3216 # Separate initial and final legs by ">" 3217 mystr = mystr + '_' 3218 # Add required s-channels 3219 if self['required_s_channels'] and \ 3220 self['required_s_channels'][0] and schannel: 3221 mystr += "_or_".join(["".join([self['model'].\ 3222 get('particle_dict')[req_id].get_name() \ 3223 for req_id in id_list]) \ 3224 for id_list in self['required_s_channels']]) 3225 mystr = mystr + '_' 3226 if mypart['is_part']: 3227 mystr = mystr + mypart['name'] 3228 else: 3229 mystr = mystr + mypart['antiname'] 3230 prevleg = leg 3231 3232 # Check for forbidden particles 3233 if self['forbidden_particles'] and forbid: 3234 mystr = mystr + '_no_' 3235 for forb_id in self['forbidden_particles']: 3236 forbpart = self['model'].get('particle_dict')[forb_id] 3237 mystr = mystr + forbpart.get_name() 3238 3239 # Replace '~' with 'x' 3240 mystr = mystr.replace('~', 'x') 3241 # Replace '+' with 'p' 3242 mystr = mystr.replace('+', 'p') 3243 # Replace '-' with 'm' 3244 mystr = mystr.replace('-', 'm') 3245 # Just to be safe, remove all spaces 3246 mystr = mystr.replace(' ', '') 3247 3248 for decay in self.get('decay_chains'): 3249 mystr = mystr + "_" + decay.shell_string(schannel,forbid, main=False, 3250 pdg_order=pdg_order) 3251 3252 # Too long name are problematic so restrict them to a maximal of 70 char 3253 if len(mystr) > 64 and main: 3254 if schannel and forbid: 3255 out = self.shell_string(True, False, True, pdg_order) 3256 elif schannel: 3257 out = self.shell_string(False, False, True, pdg_order) 3258 else: 3259 out = mystr[:64] 3260 if not out.endswith('_%s' % self['uid']): 3261 out += '_%s' % self['uid'] 3262 return out 3263 3264 return mystr
3265
3266 - def shell_string_v4(self):
3267 """Returns process as v4-compliant string with '~' -> 'x' and 3268 '>' -> '_'""" 3269 3270 mystr = "%d_" % self['id'] 3271 prevleg = None 3272 for leg in self.get_legs_with_decays(): 3273 mypart = self['model'].get('particle_dict')[leg['id']] 3274 if prevleg and prevleg['state'] == False \ 3275 and leg['state'] == True: 3276 # Separate initial and final legs by ">" 3277 mystr = mystr + '_' 3278 if mypart['is_part']: 3279 mystr = mystr + mypart['name'] 3280 else: 3281 mystr = mystr + mypart['antiname'] 3282 prevleg = leg 3283 3284 # Replace '~' with 'x' 3285 mystr = mystr.replace('~', 'x') 3286 # Just to be safe, remove all spaces 3287 mystr = mystr.replace(' ', '') 3288 3289 return mystr
3290 3291 # Helper functions 3292
3293 - def are_negative_orders_present(self):
3294 """ Check iteratively that no coupling order constraint include negative 3295 values.""" 3296 3297 if any(val<0 for val in self.get('orders').values()+\ 3298 self.get('squared_orders').values()): 3299 return True 3300 3301 for procdef in self['decay_chains']: 3302 if procdef.are_negative_orders_present(): 3303 return True 3304 3305 return False
3306
3307 - def are_decays_perturbed(self):
3308 """ Check iteratively that the decayed processes are not perturbed """ 3309 3310 for procdef in self['decay_chains']: 3311 if procdef['perturbation_couplings'] or procdef.are_decays_perturbed(): 3312 return True 3313 return False
3314
3315 - def decays_have_squared_orders(self):
3316 """ Check iteratively that the decayed processes are not perturbed """ 3317 3318 for procdef in self['decay_chains']: 3319 if procdef['squared_orders']!={} or procdef.decays_have_squared_orders(): 3320 return True 3321 return False
3322
3323 - def get_ninitial(self):
3324 """Gives number of initial state particles""" 3325 3326 return len(filter(lambda leg: leg.get('state') == False, 3327 self.get('legs')))
3328
3329 - def get_initial_ids(self):
3330 """Gives the pdg codes for initial state particles""" 3331 3332 return [leg.get('id') for leg in \ 3333 filter(lambda leg: leg.get('state') == False, 3334 self.get('legs'))]
3335
3336 - def get_initial_pdg(self, number):
3337 """Return the pdg codes for initial state particles for beam number""" 3338 3339 return filter(lambda leg: leg.get('state') == False and\ 3340 leg.get('number') == number, 3341 self.get('legs'))[0].get('id')
3342
3343 - def get_initial_final_ids(self):
3344 """return a tuple of two tuple containing the id of the initial/final 3345 state particles. Each list is ordered""" 3346 3347 initial = [] 3348 final = [l.get('id') for l in self.get('legs')\ 3349 if l.get('state') or initial.append(l.get('id'))] 3350 initial.sort() 3351 final.sort() 3352 return (tuple(initial), tuple(final))
3353
3354 - def get_final_ids_after_decay(self):
3355 """Give the pdg code of the process including decay""" 3356 3357 finals = self.get_final_ids() 3358 for proc in self.get('decay_chains'): 3359 init = proc.get_initial_ids()[0] 3360 #while 1: 3361 try: 3362 pos = finals.index(init) 3363 except: 3364 break 3365 finals[pos] = proc.get_final_ids_after_decay() 3366 output = [] 3367 for d in finals: 3368 if isinstance(d, list): 3369 output += d 3370 else: 3371 output.append(d) 3372 3373 return output
3374 3375
3376 - def get_final_legs(self):
3377 """Gives the final state legs""" 3378 3379 return filter(lambda leg: leg.get('state') == True, 3380 self.get('legs'))
3381
3382 - def get_final_ids(self):
3383 """Gives the pdg codes for final state particles""" 3384 3385 return [l.get('id') for l in self.get_final_legs()]
3386 3387
3388 - def get_legs_with_decays(self):
3389 """Return process with all decay chains substituted in.""" 3390 3391 if self['legs_with_decays']: 3392 return self['legs_with_decays'] 3393 3394 legs = copy.deepcopy(self.get('legs')) 3395 org_decay_chains = copy.copy(self.get('decay_chains')) 3396 sorted_decay_chains = [] 3397 # Sort decay chains according to leg order 3398 for leg in legs: 3399 if not leg.get('state'): continue 3400 org_ids = [l.get('legs')[0].get('id') for l in \ 3401 org_decay_chains] 3402 if leg.get('id') in org_ids: 3403 sorted_decay_chains.append(org_decay_chains.pop(\ 3404 org_ids.index(leg.get('id')))) 3405 assert not org_decay_chains 3406 ileg = 0 3407 for decay in sorted_decay_chains: 3408 while legs[ileg].get('state') == False or \ 3409 legs[ileg].get('id') != decay.get('legs')[0].get('id'): 3410 ileg = ileg + 1 3411 decay_legs = decay.get_legs_with_decays() 3412 legs = legs[:ileg] + decay_legs[1:] + legs[ileg+1:] 3413 ileg = ileg + len(decay_legs) - 1 3414 3415 # Replace legs with copies 3416 legs = [copy.copy(l) for l in legs] 3417 3418 for ileg, leg in enumerate(legs): 3419 leg.set('number', ileg + 1) 3420 3421 self['legs_with_decays'] = LegList(legs) 3422 3423 return self['legs_with_decays']
3424
3425 - def list_for_sort(self):
3426 """Output a list that can be compared to other processes as: 3427 [id, sorted(initial leg ids), sorted(final leg ids), 3428 sorted(decay list_for_sorts)]""" 3429 3430 sorted_list = [self.get('id'), 3431 sorted(self.get_initial_ids()), 3432 sorted(self.get_final_ids())] 3433 3434 if self.get('decay_chains'): 3435 sorted_list.extend(sorted([d.list_for_sort() for d in \ 3436 self.get('decay_chains')])) 3437 3438 return sorted_list
3439
3440 - def compare_for_sort(self, other):
3441 """Sorting routine which allows to sort processes for 3442 comparison. Compare only process id and legs.""" 3443 3444 if self.list_for_sort() > other.list_for_sort(): 3445 return 1 3446 if self.list_for_sort() < other.list_for_sort(): 3447 return -1 3448 return 0
3449
3450 - def identical_particle_factor(self):
3451 """Calculate the denominator factor for identical final state particles 3452 """ 3453 3454 final_legs = filter(lambda leg: leg.get('state') == True, \ 3455 self.get_legs_with_decays()) 3456 3457 identical_indices = {} 3458 for leg in final_legs: 3459 if leg.get('id') in identical_indices: 3460 identical_indices[leg.get('id')] = \ 3461 identical_indices[leg.get('id')] + 1 3462 else: 3463 identical_indices[leg.get('id')] = 1 3464 return reduce(lambda x, y: x * y, [ math.factorial(val) for val in \ 3465 identical_indices.values() ], 1)
3466
3467 - def check_expansion_orders(self):
3468 """Ensure that maximum expansion orders from the model are 3469 properly taken into account in the process""" 3470 3471 # Ensure that expansion orders are taken into account 3472 expansion_orders = self.get('model').get('expansion_order') 3473 orders = self.get('orders') 3474 sq_orders = self.get('squared_orders') 3475 3476 tmp = [(k,v) for (k,v) in expansion_orders.items() if 0 < v < 99] 3477 for (k,v) in tmp: 3478 if k in orders: 3479 if v < orders[k]: 3480 if k in sq_orders.keys() and \ 3481 (sq_orders[k]>v or sq_orders[k]<0): 3482 logger.warning( 3483 '''The process with the squared coupling order (%s^2%s%s) specified can potentially 3484 recieve contributions with powers of the coupling %s larger than the maximal 3485 value allowed by the model builder (%s). Hence, MG5_aMC sets the amplitude order 3486 for that coupling to be this maximal one. '''%(k,self.get('sqorders_types')[k], 3487 self.get('squared_orders')[k],k,v)) 3488 else: 3489 logger.warning( 3490 '''The coupling order (%s=%s) specified is larger than the one allowed 3491 by the model builder. The maximal value allowed is %s. 3492 We set the %s order to this value''' % (k,orders[k],v,k)) 3493 orders[k] = v 3494 else: 3495 orders[k] = v
3496
3497 - def __eq__(self, other):
3498 """Overloading the equality operator, so that only comparison 3499 of process id and legs is being done, using compare_for_sort.""" 3500 3501 if not isinstance(other, Process): 3502 return False 3503 3504 return self.compare_for_sort(other) == 0
3505
3506 - def __ne__(self, other):
3507 return not self.__eq__(other)
3508
3509 #=============================================================================== 3510 # ProcessList 3511 #=============================================================================== 3512 -class ProcessList(PhysicsObjectList):
3513 """List of Process objects 3514 """ 3515
3516 - def is_valid_element(self, obj):
3517 """Test if object obj is a valid Process for the list.""" 3518 3519 return isinstance(obj, Process)
3520
3521 - def nice_string(self, indent = 0):
3522 """Returns a nicely formatted string of the matrix element processes.""" 3523 3524 mystr = "\n".join([p.nice_string(indent) for p in self]) 3525 3526 return mystr
3527
3528 #=============================================================================== 3529 # ProcessDefinition 3530 #=============================================================================== 3531 -class ProcessDefinition(Process):
3532 """ProcessDefinition: list of multilegs (ordered) 3533 dictionary of orders 3534 model 3535 process id 3536 """ 3537
3538 - def default_setup(self):
3539 """Default values for all properties""" 3540 3541 super(ProcessDefinition, self).default_setup() 3542 3543 self['legs'] = MultiLegList() 3544 # Decay chain processes associated with this process 3545 self['decay_chains'] = ProcessDefinitionList() 3546 if 'legs_with_decays' in self: del self['legs_with_decays']
3547
3548 - def filter(self, name, value):
3549 """Filter for valid process property values.""" 3550 3551 if name == 'legs': 3552 if not isinstance(value, MultiLegList): 3553 raise self.PhysicsObjectError, \ 3554 "%s is not a valid MultiLegList object" % str(value) 3555 elif name == 'decay_chains': 3556 if not isinstance(value, ProcessDefinitionList): 3557 raise self.PhysicsObjectError, \ 3558 "%s is not a valid ProcessDefinitionList" % str(value) 3559 3560 else: 3561 return super(ProcessDefinition, self).filter(name, value) 3562 3563 return True
3564
3565 - def has_multiparticle_label(self):
3566 """ Check that this process definition will yield a single process, as 3567 each multileg only has one leg""" 3568 3569 for process in self['decay_chains']: 3570 if process.has_multiparticle_label(): 3571 return True 3572 3573 for mleg in self['legs']: 3574 if len(mleg['ids'])>1: 3575 return True 3576 3577 return False
3578
3579 - def get_sorted_keys(self):
3580 """Return process property names as a nicely sorted list.""" 3581 3582 keys = super(ProcessDefinition, self).get_sorted_keys() 3583 keys.remove('legs_with_decays') 3584 3585 return keys
3586
3587 - def get_minimum_WEIGHTED(self):
3588 """Retrieve the minimum starting guess for WEIGHTED order, to 3589 use in find_optimal_process_orders in MultiProcess diagram 3590 generation (as well as particles and hierarchy). The algorithm: 3591 3592 1) Pick out the legs in the multiprocess according to the 3593 highest hierarchy represented (so don't mix particles from 3594 different hierarchy classes in the same multiparticles!) 3595 3596 2) Find the starting maximum WEIGHTED order as the sum of the 3597 highest n-2 weighted orders 3598 3599 3) Pick out required s-channel particle hierarchies, and use 3600 the highest of the maximum WEIGHTED order from the legs and 3601 the minimum WEIGHTED order extracted from 2*s-channel 3602 hierarchys plus the n-2-2*(number of s-channels) lowest 3603 leg weighted orders. 3604 """ 3605 3606 model = self.get('model') 3607 3608 # Extract hierarchy and particles corresponding to the 3609 # different hierarchy levels from the model 3610 particles, hierarchy = model.get_particles_hierarchy() 3611 3612 # Find legs corresponding to the different orders 3613 # making sure we look at lowest hierarchy first for each leg 3614 max_order_now = [] 3615 new_legs = copy.copy(self.get('legs')) 3616 for parts, value in zip(particles, hierarchy): 3617 ileg = 0 3618 while ileg < len(new_legs): 3619 if any([id in parts for id in new_legs[ileg].get('ids')]): 3620 max_order_now.append(value) 3621 new_legs.pop(ileg) 3622 else: 3623 ileg += 1 3624 3625 # Now remove the two lowest orders to get maximum (since the 3626 # number of interactions is n-2) 3627 max_order_now = sorted(max_order_now)[2:] 3628 3629 # Find s-channel propagators corresponding to the different orders 3630 max_order_prop = [] 3631 for idlist in self.get('required_s_channels'): 3632 max_order_prop.append([0,0]) 3633 for id in idlist: 3634 for parts, value in zip(particles, hierarchy): 3635 if id in parts: 3636 max_order_prop[-1][0] += 2*value 3637 max_order_prop[-1][1] += 1 3638 break 3639 3640 if max_order_prop: 3641 if len(max_order_prop) >1: 3642 max_order_prop = min(*max_order_prop, key=lambda x:x[0]) 3643 else: 3644 max_order_prop = max_order_prop[0] 3645 3646 # Use either the max_order from the external legs or 3647 # the maximum order from the s-channel propagators, plus 3648 # the appropriate lowest orders from max_order_now 3649 max_order_now = max(sum(max_order_now), 3650 max_order_prop[0] + \ 3651 sum(max_order_now[:-2 * max_order_prop[1]])) 3652 else: 3653 max_order_now = sum(max_order_now) 3654 3655 return max_order_now, particles, hierarchy
3656
3657 - def __iter__(self):
3658 """basic way to loop over all the process definition. 3659 not used by MG which used some smarter version (use by ML)""" 3660 3661 isids = [leg['ids'] for leg in self['legs'] \ 3662 if leg['state'] == False] 3663 fsids = [leg['ids'] for leg in self['legs'] \ 3664 if leg['state'] == True] 3665 3666 red_isidlist = [] 3667 # Generate all combinations for the initial state 3668 for prod in itertools.product(*isids): 3669 islegs = [Leg({'id':id, 'state': False}) for id in prod] 3670 if tuple(sorted(prod)) in red_isidlist: 3671 continue 3672 red_isidlist.append(tuple(sorted(prod))) 3673 red_fsidlist = [] 3674 for prod in itertools.product(*fsids): 3675 # Remove double counting between final states 3676 if tuple(sorted(prod)) in red_fsidlist: 3677 continue 3678 red_fsidlist.append(tuple(sorted(prod))) 3679 leg_list = [copy.copy(leg) for leg in islegs] 3680 leg_list.extend([Leg({'id':id, 'state': True}) for id in prod]) 3681 legs = LegList(leg_list) 3682 process = self.get_process_with_legs(legs) 3683 yield process
3684
3685 - def nice_string(self, indent=0, print_weighted=False, prefix=True):
3686 """Returns a nicely formated string about current process 3687 content""" 3688 3689 if prefix: 3690 mystr = " " * indent + "Process: " 3691 else: 3692 mystr="" 3693 prevleg = None 3694 for leg in self['legs']: 3695 myparts = \ 3696 "/".join([self['model'].get('particle_dict')[id].get_name() \ 3697 for id in leg.get('ids')]) 3698 if prevleg and prevleg['state'] == False \ 3699 and leg['state'] == True: 3700 # Separate initial and final legs by ">" 3701 mystr = mystr + '> ' 3702 # Add required s-channels 3703 if self['required_s_channels'] and \ 3704 self['required_s_channels'][0]: 3705 mystr += "|".join([" ".join([self['model'].\ 3706 get('particle_dict')[req_id].get_name() \ 3707 for req_id in id_list]) \ 3708 for id_list in self['required_s_channels']]) 3709 mystr = mystr + '> ' 3710 3711 mystr = mystr + myparts + ' ' 3712 #mystr = mystr + '(%i) ' % leg['number'] 3713 prevleg = leg 3714 3715 # Add forbidden s-channels 3716 if self['forbidden_onsh_s_channels']: 3717 mystr = mystr + '$ ' 3718 for forb_id in self['forbidden_onsh_s_channels']: 3719 forbpart = self['model'].get('particle_dict')[forb_id] 3720 mystr = mystr + forbpart.get_name() + ' ' 3721 3722 # Add double forbidden s-channels 3723 if self['forbidden_s_channels']: 3724 mystr = mystr + '$$ ' 3725 for forb_id in self['forbidden_s_channels']: 3726 forbpart = self['model'].get('particle_dict')[forb_id] 3727 mystr = mystr + forbpart.get_name() + ' ' 3728 3729 # Add forbidden particles 3730 if self['forbidden_particles']: 3731 mystr = mystr + '/ ' 3732 for forb_id in self['forbidden_particles']: 3733 forbpart = self['model'].get('particle_dict')[forb_id] 3734 mystr = mystr + forbpart.get_name() + ' ' 3735 3736 if self['orders']: 3737 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3738 for key in sorted(self['orders'])]) + ' ' 3739 3740 if self['constrained_orders']: 3741 mystr = mystr + " ".join('%s%s%d' % (key, operator, value) for 3742 (key,(value, operator)) 3743 in self['constrained_orders'].items()) + ' ' 3744 3745 # Add perturbation_couplings 3746 if self['perturbation_couplings']: 3747 mystr = mystr + '[ ' 3748 if self['NLO_mode']!='tree': 3749 if self['NLO_mode']=='virt' and not self['has_born']: 3750 mystr = mystr + 'sqrvirt = ' 3751 else: 3752 mystr = mystr + self['NLO_mode'] + ' = ' 3753 for order in self['perturbation_couplings']: 3754 mystr = mystr + order + ' ' 3755 mystr = mystr + '] ' 3756 3757 if self['squared_orders']: 3758 mystr = mystr + " ".join([key + '^2%s%d'%\ 3759 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 3760 for key in self['squared_orders'].keys() \ 3761 if print_weighted or key!='WEIGHTED']) + ' ' 3762 3763 # Remove last space 3764 mystr = mystr[:-1] 3765 3766 if self.get('id') or self.get('overall_orders'): 3767 mystr += " @%d" % self.get('id') 3768 if self.get('overall_orders'): 3769 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3770 for key in sorted(self['orders'])]) + ' ' 3771 3772 if not self.get('decay_chains'): 3773 return mystr 3774 3775 for decay in self['decay_chains']: 3776 mystr = mystr + '\n' + \ 3777 decay.nice_string(indent + 2).replace('Process', 'Decay') 3778 3779 return mystr
3780
3781 - def get_process_with_legs(self, LegList):
3782 """ Return a Process object which has the same properties of this 3783 ProcessDefinition but with the specified LegList as legs attribute. 3784 """ 3785 3786 return Process({\ 3787 'legs': LegList, 3788 'model':self.get('model'), 3789 'id': self.get('id'), 3790 'orders': self.get('orders'), 3791 'sqorders_types': self.get('sqorders_types'), 3792 'squared_orders': self.get('squared_orders'), 3793 'constrained_orders': self.get('constrained_orders'), 3794 'has_born': self.get('has_born'), 3795 'required_s_channels': self.get('required_s_channels'), 3796 'forbidden_onsh_s_channels': self.get('forbidden_onsh_s_channels'), 3797 'forbidden_s_channels': self.get('forbidden_s_channels'), 3798 'forbidden_particles': self.get('forbidden_particles'), 3799 'perturbation_couplings': self.get('perturbation_couplings'), 3800 'is_decay_chain': self.get('is_decay_chain'), 3801 'overall_orders': self.get('overall_orders'), 3802 'split_orders': self.get('split_orders'), 3803 'NLO_mode': self.get('NLO_mode') 3804 })
3805
3806 - def get_process(self, initial_state_ids, final_state_ids):
3807 """ Return a Process object which has the same properties of this 3808 ProcessDefinition but with the specified given leg ids. """ 3809 3810 # First make sure that the desired particle ids belong to those defined 3811 # in this process definition. 3812 my_isids = [leg.get('ids') for leg in self.get('legs') \ 3813 if not leg.get('state')] 3814 my_fsids = [leg.get('ids') for leg in self.get('legs') \ 3815 if leg.get('state')] 3816 for i, is_id in enumerate(initial_state_ids): 3817 assert is_id in my_isids[i] 3818 for i, fs_id in enumerate(final_state_ids): 3819 assert fs_id in my_fsids[i] 3820 3821 return self.get_process_with_legs(LegList(\ 3822 [Leg({'id': id, 'state':False}) for id in initial_state_ids] + \ 3823 [Leg({'id': id, 'state':True}) for id in final_state_ids]))
3824
3825 - def __eq__(self, other):
3826 """Overloading the equality operator, so that only comparison 3827 of process id and legs is being done, using compare_for_sort.""" 3828 3829 return super(Process, self).__eq__(other)
3830
3831 #=============================================================================== 3832 # ProcessDefinitionList 3833 #=============================================================================== 3834 -class ProcessDefinitionList(PhysicsObjectList):
3835 """List of ProcessDefinition objects 3836 """ 3837
3838 - def is_valid_element(self, obj):
3839 """Test if object obj is a valid ProcessDefinition for the list.""" 3840 3841 return isinstance(obj, ProcessDefinition)
3842
3843 #=============================================================================== 3844 # Global helper functions 3845 #=============================================================================== 3846 3847 -def make_unique(doubletlist):
3848 """Make sure there are no doublets in the list doubletlist. 3849 Note that this is a slow implementation, so don't use if speed 3850 is needed""" 3851 3852 assert isinstance(doubletlist, list), \ 3853 "Argument to make_unique must be list" 3854 3855 3856 uniquelist = [] 3857 for elem in doubletlist: 3858 if elem not in uniquelist: 3859 uniquelist.append(elem) 3860 3861 doubletlist[:] = uniquelist[:]
3862