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

Source Code for Module madgraph.core.base_objects

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