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