Package madgraph :: Package core :: Module base_objects
[hide private]
[frames] | no frames]

Source Code for Module madgraph.core.base_objects

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