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