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