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