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