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