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 if leg.get('polarization'): 2469 mystr = mystr + str(leg['number']) + '(%s{%s})' % (str(leg['id']),leg['polarization']) + ',' 2470 else: 2471 mystr = mystr + str(leg['number']) + '(%s)' % str(leg['id']) + ',' 2472 2473 used_leg.append(leg['number']) 2474 if __debug__ and len(used_leg) != len(set(used_leg)): 2475 pass_sanity = False 2476 responsible = id(vert) 2477 2478 if self['vertices'].index(vert) < len(self['vertices']) - 1: 2479 # Do not want ">" in the last vertex 2480 mystr = mystr[:-1] + '>' 2481 lastleg = vert['legs'][-1] 2482 if lastleg['polarization']: 2483 mystr = mystr + str(lastleg['number']) + '(%s{%s})' % (str(lastleg['id']), lastleg['polarization']) + ',' 2484 else: 2485 mystr = mystr + str(lastleg['number']) + '(%s)' % str(lastleg['id']) + ',' 2486 mystr = mystr + 'id:' + str(vert['id']) + '),' 2487 2488 mystr = mystr[:-1] + ')' 2489 mystr += " (%s)" % (",".join(["%s=%d" % (key, self['orders'][key]) \ 2490 for key in sorted(self['orders'].keys())])) 2491 2492 if not pass_sanity: 2493 raise Exception, "invalid diagram: %s. vert_id: %s" % (mystr, responsible) 2494 2495 return mystr 2496 else: 2497 return '()'
2498
2499 - def calculate_orders(self, model):
2500 """Calculate the actual coupling orders of this diagram. Note 2501 that the special order WEIGTHED corresponds to the sum of 2502 hierarchys for the couplings.""" 2503 2504 coupling_orders = dict([(c, 0) for c in model.get('coupling_orders')]) 2505 weight = 0 2506 for vertex in self['vertices']: 2507 if vertex.get('id') in [0,-1]: continue 2508 if vertex.get('id') == -2: 2509 couplings = vertex.get('loop_orders') 2510 else: 2511 couplings = model.get('interaction_dict')[vertex.get('id')].\ 2512 get('orders') 2513 for coupling in couplings: 2514 coupling_orders[coupling] += couplings[coupling] 2515 weight += sum([model.get('order_hierarchy')[c]*n for \ 2516 (c,n) in couplings.items()]) 2517 coupling_orders['WEIGHTED'] = weight 2518 self.set('orders', coupling_orders)
2519
2520 - def pass_squared_order_constraints(self, diag_multiplier, squared_orders, 2521 sq_orders_types):
2522 """ Returns wether the contributiong consisting in the current diagram 2523 multiplied by diag_multiplier passes the *positive* squared_orders 2524 specified ( a dictionary ) of types sq_order_types (a dictionary whose 2525 values are the relational operator used to define the constraint of the 2526 order in key).""" 2527 2528 for order, value in squared_orders.items(): 2529 if value<0: 2530 continue 2531 combined_order = self.get_order(order) + \ 2532 diag_multiplier.get_order(order) 2533 if ( sq_orders_types[order]=='==' and combined_order != value ) or \ 2534 ( sq_orders_types[order] in ['=', '<='] and combined_order > value) or \ 2535 ( sq_orders_types[order]=='>' and combined_order <= value) : 2536 return False 2537 return True
2538
2539 - def get_order(self, order):
2540 """Return the order of this diagram. It returns 0 if it is not present.""" 2541 2542 try: 2543 return self['orders'][order] 2544 except Exception: 2545 return 0
2546
2547 - def get_contracted_loop_diagram(self, struct_rep=None):
2548 """ Returns a Diagram which correspond to the loop diagram with the 2549 loop shrunk to a point. Of course for a instance of base_objects.Diagram 2550 one must simply return self.""" 2551 2552 return self
2553
2554 - def get_external_legs(self):
2555 """ Return the list of external legs of this diagram """ 2556 2557 external_legs = LegList([]) 2558 for leg in sum([vert.get('legs') for vert in self.get('vertices')],[]): 2559 if not leg.get('number') in [l.get('number') for l in external_legs]: 2560 external_legs.append(leg) 2561 2562 return external_legs
2563
2564 - def renumber_legs(self, perm_map, leg_list):
2565 """Renumber legs in all vertices according to perm_map""" 2566 2567 vertices = VertexList() 2568 min_dict = copy.copy(perm_map) 2569 # Dictionary from leg number to state 2570 state_dict = dict([(l.get('number'), l.get('state')) for l in leg_list]) 2571 # First renumber all legs in the n-1->1 vertices 2572 for vertex in self.get('vertices')[:-1]: 2573 vertex = copy.copy(vertex) 2574 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2575 for leg in leg_list[:-1]: 2576 leg.set('number', min_dict[leg.get('number')]) 2577 leg.set('state', state_dict[leg.get('number')]) 2578 min_number = min([leg.get('number') for leg in leg_list[:-1]]) 2579 leg = leg_list[-1] 2580 min_dict[leg.get('number')] = min_number 2581 # resulting leg is initial state if there is exactly one 2582 # initial state leg among the incoming legs 2583 state_dict[min_number] = len([l for l in leg_list[:-1] if \ 2584 not l.get('state')]) != 1 2585 leg.set('number', min_number) 2586 leg.set('state', state_dict[min_number]) 2587 vertex.set('legs', leg_list) 2588 vertices.append(vertex) 2589 # Now renumber the legs in final vertex 2590 vertex = copy.copy(self.get('vertices')[-1]) 2591 leg_list = LegList([copy.copy(l) for l in vertex.get('legs')]) 2592 for leg in leg_list: 2593 leg.set('number', min_dict[leg.get('number')]) 2594 leg.set('state', state_dict[leg.get('number')]) 2595 vertex.set('legs', leg_list) 2596 vertices.append(vertex) 2597 # Finally create new diagram 2598 new_diag = copy.copy(self) 2599 new_diag.set('vertices', vertices) 2600 state_dict = {True:'T',False:'F'} 2601 return new_diag
2602
2603 - def get_vertex_leg_numbers(self, 2604 veto_inter_id=Vertex.ID_to_veto_for_multichanneling, 2605 max_n_loop=0):
2606 """Return a list of the number of legs in the vertices for 2607 this diagram. 2608 This function is only used for establishing the multi-channeling, so that 2609 we exclude from it all the fake vertices and the vertices resulting from 2610 shrunk loops (id=-2)""" 2611 2612 2613 if max_n_loop == 0: 2614 max_n_loop = Vertex.max_n_loop_for_multichanneling 2615 2616 res = [len(v.get('legs')) for v in self.get('vertices') if (v.get('id') \ 2617 not in veto_inter_id) or (v.get('id')==-2 and 2618 len(v.get('legs'))>max_n_loop)] 2619 2620 return res
2621
2622 - def get_num_configs(self, model, ninitial):
2623 """Return the maximum number of configs from this diagram, 2624 given by 2^(number of non-zero width s-channel propagators)""" 2625 2626 s_channels = [v.get_s_channel_id(model,ninitial) for v in \ 2627 self.get('vertices')[:-1]] 2628 num_props = len([i for i in s_channels if i != 0 and \ 2629 model.get_particle(i).get('width').lower() != 'zero']) 2630 2631 if num_props < 1: 2632 return 1 2633 else: 2634 return 2**num_props
2635
2636 - def get_flow_charge_diff(self, model):
2637 """return the difference of total diff of charge occuring on the 2638 lofw of the initial parton. return [None,None] if the two initial parton 2639 are connected and the (partial) value if None if the initial parton is 2640 not a fermiom""" 2641 2642 import madgraph.core.drawing as drawing 2643 drawdiag = drawing.FeynmanDiagram(self, model) 2644 drawdiag.load_diagram() 2645 out = [] 2646 2647 for v in drawdiag.initial_vertex: 2648 init_part = v.lines[0] 2649 if not init_part.is_fermion(): 2650 out.append(None) 2651 continue 2652 2653 init_charge = model.get_particle(init_part.id).get('charge') 2654 2655 l_last = init_part 2656 v_last = v 2657 vcurrent = l_last.end 2658 if vcurrent == v: 2659 vcurrent = l_last.begin 2660 security =0 2661 while not vcurrent.is_external(): 2662 if security > 1000: 2663 raise Exception, 'wrong diagram' 2664 next_l = [l for l in vcurrent.lines if l is not l_last and l.is_fermion()][0] 2665 next_v = next_l.end 2666 if next_v == vcurrent: 2667 next_v = next_l.begin 2668 l_last, vcurrent = next_l, next_v 2669 if vcurrent in drawdiag.initial_vertex: 2670 return [None, None] 2671 2672 out.append(model.get_particle(l_last.id).get('charge') - init_charge) 2673 return out
2674
2675 2676 #=============================================================================== 2677 # DiagramList 2678 #=============================================================================== 2679 -class DiagramList(PhysicsObjectList):
2680 """List of Diagram objects 2681 """ 2682
2683 - def is_valid_element(self, obj):
2684 """Test if object obj is a valid Diagram for the list.""" 2685 2686 return isinstance(obj, Diagram)
2687
2688 - def nice_string(self, indent=0):
2689 """Returns a nicely formatted string""" 2690 mystr = " " * indent + str(len(self)) + ' diagrams:\n' 2691 for i, diag in enumerate(self): 2692 mystr = mystr + " " * indent + str(i+1) + " " + \ 2693 diag.nice_string() + '\n' 2694 return mystr[:-1]
2695 2696 # Helper function 2697
2698 - def get_max_order(self,order):
2699 """ Return the order of the diagram in the list with the maximum coupling 2700 order for the coupling specified """ 2701 max_order=-1 2702 2703 for diag in self: 2704 if order in diag['orders'].keys(): 2705 if max_order==-1 or diag['orders'][order] > max_order: 2706 max_order = diag['orders'][order] 2707 2708 return max_order
2709
2710 - def apply_negative_sq_order(self, ref_diag_list, order, value, order_type):
2711 """ This function returns a fitlered version of the diagram list self 2712 which satisfy the negative squared_order constraint 'order' with negative 2713 value 'value' and of type 'order_type', assuming that the diagram_list 2714 it must be squared against is 'reg_diag_list'. It also returns the 2715 new postive target squared order which correspond to this negative order 2716 constraint. Example: u u~ > d d~ QED^2<=-2 means that one wants to 2717 pick terms only up to the the next-to-leading order contributiong in QED, 2718 which is QED=2 in this case, so that target_order=4 is returned.""" 2719 2720 # First we must compute all contributions to that order 2721 target_order = min(ref_diag_list.get_order_values(order))+\ 2722 min(self.get_order_values(order))+2*(-value-1) 2723 2724 new_list = self.apply_positive_sq_orders(ref_diag_list, 2725 {order:target_order}, {order:order_type}) 2726 2727 return new_list, target_order
2728
2729 - def apply_positive_sq_orders(self, ref_diag_list, sq_orders, sq_order_types):
2730 """ This function returns a filtered version of self which contain 2731 only the diagram which satisfy the positive squared order constraints 2732 sq_orders of type sq_order_types and assuming that the diagrams are 2733 multiplied with those of the reference diagram list ref_diag_list.""" 2734 2735 new_diag_list = DiagramList() 2736 for tested_diag in self: 2737 for ref_diag in ref_diag_list: 2738 if tested_diag.pass_squared_order_constraints(ref_diag, 2739 sq_orders,sq_order_types): 2740 new_diag_list.append(tested_diag) 2741 break 2742 return new_diag_list
2743
2744 - def filter_constrained_orders(self, order, value, operator):
2745 """ This function modifies the current object and remove the diagram 2746 which do not obey the condition """ 2747 2748 new = [] 2749 for tested_diag in self: 2750 if operator == '==': 2751 if tested_diag['orders'][order] == value: 2752 new.append(tested_diag) 2753 elif operator == '>': 2754 if tested_diag['orders'][order] > value: 2755 new.append(tested_diag) 2756 self[:] = new 2757 return self
2758 2759
2760 - def get_min_order(self,order):
2761 """ Return the order of the diagram in the list with the mimimum coupling 2762 order for the coupling specified """ 2763 min_order=-1 2764 for diag in self: 2765 if order in diag['orders'].keys(): 2766 if min_order==-1 or diag['orders'][order] < min_order: 2767 min_order = diag['orders'][order] 2768 else: 2769 return 0 2770 2771 return min_order
2772
2773 - def get_order_values(self, order):
2774 """ Return the list of possible values appearing in the diagrams of this 2775 list for the order given in argument """ 2776 2777 values=set([]) 2778 for diag in self: 2779 if order in diag['orders'].keys(): 2780 values.add(diag['orders'][order]) 2781 else: 2782 values.add(0) 2783 2784 return list(values)
2785
2786 #=============================================================================== 2787 # Process 2788 #=============================================================================== 2789 -class Process(PhysicsObject):
2790 """Process: list of legs (ordered) 2791 dictionary of orders 2792 model 2793 process id 2794 """ 2795
2796 - def default_setup(self):
2797 """Default values for all properties""" 2798 2799 self['legs'] = LegList() 2800 # These define the orders restrict the born and loop amplitudes. 2801 self['orders'] = {} 2802 self['model'] = Model() 2803 # Optional number to identify the process 2804 self['id'] = 0 2805 self['uid'] = 0 # should be a uniq id number 2806 # Required s-channels are given as a list of id lists. Only 2807 # diagrams with all s-channels in any of the lists are 2808 # allowed. This enables generating e.g. Z/gamma as s-channel 2809 # propagators. 2810 self['required_s_channels'] = [] 2811 self['forbidden_onsh_s_channels'] = [] 2812 self['forbidden_s_channels'] = [] 2813 self['forbidden_particles'] = [] 2814 self['is_decay_chain'] = False 2815 self['overall_orders'] = {} 2816 # Decay chain processes associated with this process 2817 self['decay_chains'] = ProcessList() 2818 # Legs with decay chains substituted in 2819 self['legs_with_decays'] = LegList() 2820 # Loop particles if the process is to be computed at NLO 2821 self['perturbation_couplings']=[] 2822 # These orders restrict the order of the squared amplitude. 2823 # This dictionary possibly contains a key "WEIGHTED" which 2824 # gives the upper bound for the total weighted order of the 2825 # squared amplitude. 2826 self['squared_orders'] = {} 2827 # The squared order (sqorders) constraints above can either be upper 2828 # bound (<=) or exact match (==) depending on how they were specified 2829 # in the user input. This choice is stored in the dictionary below. 2830 # Notice that the upper bound is the default 2831 self['sqorders_types'] = {} 2832 # other type of constraint at amplitude level 2833 self['constrained_orders'] = {} # {QED: (4,'>')} 2834 self['has_born'] = True 2835 # The NLO_mode is always None for a tree-level process and can be 2836 # 'all', 'real', 'virt' for a loop process. 2837 self['NLO_mode'] = 'tree' 2838 # The user might want to have the individual matrix element evaluations 2839 # for specific values of the coupling orders. The list below specifies 2840 # what are the coupling names which need be individually treated. 2841 # For example, for the process p p > j j [] QED=2 (QED=2 is 2842 # then a squared order constraint), then QED will appear in the 2843 # 'split_orders' list so that the subroutine in matrix.f return the 2844 # evaluation of the matrix element individually for the pure QCD 2845 # contribution 'QCD=4 QED=0', the pure interference 'QCD=2 QED=2' and 2846 # the pure QED contribution of order 'QCD=0 QED=4'. 2847 self['split_orders'] = []
2848
2849 - def filter(self, name, value):
2850 """Filter for valid process property values.""" 2851 2852 if name in ['legs', 'legs_with_decays'] : 2853 if not isinstance(value, LegList): 2854 raise self.PhysicsObjectError, \ 2855 "%s is not a valid LegList object" % str(value) 2856 2857 if name in ['orders', 'overall_orders','squared_orders']: 2858 Interaction.filter(Interaction(), 'orders', value) 2859 2860 if name == 'constrained_orders': 2861 if not isinstance(value, dict): 2862 raise self.PhysicsObjectError, \ 2863 "%s is not a valid dictionary" % str(value) 2864 2865 if name == 'sqorders_types': 2866 if not isinstance(value, dict): 2867 raise self.PhysicsObjectError, \ 2868 "%s is not a valid dictionary" % str(value) 2869 for order in value.keys()+value.values(): 2870 if not isinstance(order, str): 2871 raise self.PhysicsObjectError, \ 2872 "%s is not a valid string" % str(value) 2873 2874 if name == 'split_orders': 2875 if not isinstance(value, list): 2876 raise self.PhysicsObjectError, \ 2877 "%s is not a valid list" % str(value) 2878 for order in value: 2879 if not isinstance(order, str): 2880 raise self.PhysicsObjectError, \ 2881 "%s is not a valid string" % str(value) 2882 2883 if name == 'model': 2884 if not isinstance(value, Model): 2885 raise self.PhysicsObjectError, \ 2886 "%s is not a valid Model object" % str(value) 2887 if name in ['id', 'uid']: 2888 if not isinstance(value, int): 2889 raise self.PhysicsObjectError, \ 2890 "Process %s %s is not an integer" % (name, repr(value)) 2891 2892 if name == 'required_s_channels': 2893 if not isinstance(value, list): 2894 raise self.PhysicsObjectError, \ 2895 "%s is not a valid list" % str(value) 2896 for l in value: 2897 if not isinstance(l, list): 2898 raise self.PhysicsObjectError, \ 2899 "%s is not a valid list of lists" % str(value) 2900 for i in l: 2901 if not isinstance(i, int): 2902 raise self.PhysicsObjectError, \ 2903 "%s is not a valid list of integers" % str(l) 2904 if i == 0: 2905 raise self.PhysicsObjectError, \ 2906 "Not valid PDG code %d for s-channel particle" % i 2907 2908 if name in ['forbidden_onsh_s_channels', 'forbidden_s_channels']: 2909 if not isinstance(value, list): 2910 raise self.PhysicsObjectError, \ 2911 "%s is not a valid list" % str(value) 2912 for i in value: 2913 if not isinstance(i, int): 2914 raise self.PhysicsObjectError, \ 2915 "%s is not a valid list of integers" % str(value) 2916 if i == 0: 2917 raise self.PhysicsObjectError, \ 2918 "Not valid PDG code %d for s-channel particle" % str(value) 2919 2920 if name == 'forbidden_particles': 2921 if not isinstance(value, list): 2922 raise self.PhysicsObjectError, \ 2923 "%s is not a valid list" % str(value) 2924 for i in value: 2925 if not isinstance(i, int): 2926 raise self.PhysicsObjectError, \ 2927 "%s is not a valid list of integers" % str(value) 2928 if i <= 0: 2929 raise self.PhysicsObjectError, \ 2930 "Forbidden particles should have a positive PDG code" % str(value) 2931 2932 if name == 'perturbation_couplings': 2933 if not isinstance(value, list): 2934 raise self.PhysicsObjectError, \ 2935 "%s is not a valid list" % str(value) 2936 for order in value: 2937 if not isinstance(order, str): 2938 raise self.PhysicsObjectError, \ 2939 "%s is not a valid string" % str(value) 2940 2941 if name == 'is_decay_chain': 2942 if not isinstance(value, bool): 2943 raise self.PhysicsObjectError, \ 2944 "%s is not a valid bool" % str(value) 2945 2946 if name == 'has_born': 2947 if not isinstance(value, bool): 2948 raise self.PhysicsObjectError, \ 2949 "%s is not a valid bool" % str(value) 2950 2951 if name == 'decay_chains': 2952 if not isinstance(value, ProcessList): 2953 raise self.PhysicsObjectError, \ 2954 "%s is not a valid ProcessList" % str(value) 2955 2956 if name == 'NLO_mode': 2957 import madgraph.interface.madgraph_interface as mg 2958 if value not in mg.MadGraphCmd._valid_nlo_modes: 2959 raise self.PhysicsObjectError, \ 2960 "%s is not a valid NLO_mode" % str(value) 2961 return True
2962
2963 - def has_multiparticle_label(self):
2964 """ A process, not being a ProcessDefinition never carries multiple 2965 particles labels""" 2966 2967 return False
2968
2969 - def set(self, name, value):
2970 """Special set for forbidden particles - set to abs value.""" 2971 2972 if name == 'forbidden_particles': 2973 try: 2974 value = [abs(i) for i in value] 2975 except Exception: 2976 pass 2977 2978 if name == 'required_s_channels': 2979 # Required s-channels need to be a list of lists of ids 2980 if value and isinstance(value, list) and \ 2981 not isinstance(value[0], list): 2982 value = [value] 2983 2984 return super(Process, self).set(name, value) # call the mother routine
2985
2986 - def get_squared_order_type(self, order):
2987 """ Return what kind of squared order constraint was specified for the 2988 order 'order'.""" 2989 2990 if order in self['sqorders_types'].keys(): 2991 return self['sqorders_types'][order] 2992 else: 2993 # Default behavior '=' is interpreted as upper bound '<=' 2994 return '='
2995
2996 - def get(self, name):
2997 """Special get for legs_with_decays""" 2998 2999 if name == 'legs_with_decays': 3000 self.get_legs_with_decays() 3001 3002 if name == 'sqorders_types': 3003 # We must make sure that there is a type for each sqorder defined 3004 for order in self['squared_orders'].keys(): 3005 if order not in self['sqorders_types']: 3006 # Then assign its type to the default '=' 3007 self['sqorders_types'][order]='=' 3008 3009 return super(Process, self).get(name) # call the mother routine
3010 3011 3012
3013 - def get_sorted_keys(self):
3014 """Return process property names as a nicely sorted list.""" 3015 3016 return ['legs', 'orders', 'overall_orders', 'squared_orders', 3017 'constrained_orders', 3018 'model', 'id', 'required_s_channels', 3019 'forbidden_onsh_s_channels', 'forbidden_s_channels', 3020 'forbidden_particles', 'is_decay_chain', 'decay_chains', 3021 'legs_with_decays', 'perturbation_couplings', 'has_born', 3022 'NLO_mode','split_orders']
3023
3024 - def nice_string(self, indent=0, print_weighted = True, prefix=True):
3025 """Returns a nicely formated string about current process 3026 content. Since the WEIGHTED order is automatically set and added to 3027 the user-defined list of orders, it can be ommitted for some info 3028 displays.""" 3029 3030 if isinstance(prefix, bool) and prefix: 3031 mystr = " " * indent + "Process: " 3032 elif isinstance(prefix, str): 3033 mystr = prefix 3034 else: 3035 mystr = "" 3036 prevleg = None 3037 for leg in self['legs']: 3038 mypart = self['model'].get('particle_dict')[leg['id']] 3039 if prevleg and prevleg['state'] == False \ 3040 and leg['state'] == True: 3041 # Separate initial and final legs by > 3042 mystr = mystr + '> ' 3043 # Add required s-channels 3044 if self['required_s_channels'] and \ 3045 self['required_s_channels'][0]: 3046 mystr += "|".join([" ".join([self['model'].\ 3047 get('particle_dict')[req_id].get_name() \ 3048 for req_id in id_list]) \ 3049 for id_list in self['required_s_channels']]) 3050 mystr = mystr + ' > ' 3051 3052 mystr = mystr + mypart.get_name() 3053 if leg.get('polarization'): 3054 if leg.get('polarization') in [[-1,1],[1,-1]]: 3055 mystr = mystr + '{T} ' 3056 elif leg.get('polarization') == [-1]: 3057 mystr = mystr + '{L} ' 3058 elif leg.get('polarization') == [1]: 3059 mystr = mystr + '{R} ' 3060 else: 3061 mystr = mystr + '{%s} ' %','.join([str(p) for p in leg.get('polarization')]) 3062 else: 3063 mystr = mystr + ' ' 3064 #mystr = mystr + '(%i) ' % leg['number'] 3065 prevleg = leg 3066 3067 # Add orders 3068 if self['orders']: 3069 to_add = [] 3070 for key in sorted(self['orders'].keys()): 3071 if not print_weighted and key == 'WEIGHTED': 3072 continue 3073 value = int(self['orders'][key]) 3074 if key in self['squared_orders']: 3075 if self.get_squared_order_type(key) in ['<=', '==', '='] and \ 3076 self['squared_orders'][key] == value: 3077 continue 3078 if self.get_squared_order_type(key) in ['>'] and value == 99: 3079 continue 3080 if key in self['constrained_orders']: 3081 if value == self['constrained_orders'][key][0] and\ 3082 self['constrained_orders'][key][1] in ['=', '<=', '==']: 3083 continue 3084 if value == 0: 3085 to_add.append('%s=0' % key) 3086 else: 3087 to_add.append('%s<=%s' % (key,value)) 3088 3089 if to_add: 3090 mystr = mystr + " ".join(to_add) + ' ' 3091 3092 if self['constrained_orders']: 3093 mystr = mystr + " ".join('%s%s%d' % (key, 3094 self['constrained_orders'][key][1], self['constrained_orders'][key][0]) 3095 for key in sorted(self['constrained_orders'].keys())) + ' ' 3096 3097 # Add perturbation_couplings 3098 if self['perturbation_couplings']: 3099 mystr = mystr + '[ ' 3100 if self['NLO_mode']!='tree': 3101 if self['NLO_mode']=='virt' and not self['has_born']: 3102 mystr = mystr + 'sqrvirt = ' 3103 else: 3104 mystr = mystr + self['NLO_mode'] + ' = ' 3105 for order in self['perturbation_couplings']: 3106 mystr = mystr + order + ' ' 3107 mystr = mystr + '] ' 3108 3109 # Add squared orders 3110 if self['squared_orders']: 3111 to_add = [] 3112 for key in sorted(self['squared_orders'].keys()): 3113 if not print_weighted and key == 'WEIGHTED': 3114 continue 3115 if key in self['constrained_orders']: 3116 if self['constrained_orders'][key][0] == self['squared_orders'][key]/2 and \ 3117 self['constrained_orders'][key][1] == self.get_squared_order_type(key): 3118 continue 3119 to_add.append(key + '^2%s%d'%\ 3120 (self.get_squared_order_type(key),self['squared_orders'][key])) 3121 3122 if to_add: 3123 mystr = mystr + " ".join(to_add) + ' ' 3124 3125 3126 # Add forbidden s-channels 3127 if self['forbidden_onsh_s_channels']: 3128 mystr = mystr + '$ ' 3129 for forb_id in self['forbidden_onsh_s_channels']: 3130 forbpart = self['model'].get('particle_dict')[forb_id] 3131 mystr = mystr + forbpart.get_name() + ' ' 3132 3133 # Add double forbidden s-channels 3134 if self['forbidden_s_channels']: 3135 mystr = mystr + '$$ ' 3136 for forb_id in self['forbidden_s_channels']: 3137 forbpart = self['model'].get('particle_dict')[forb_id] 3138 mystr = mystr + forbpart.get_name() + ' ' 3139 3140 # Add forbidden particles 3141 if self['forbidden_particles']: 3142 mystr = mystr + '/ ' 3143 for forb_id in self['forbidden_particles']: 3144 forbpart = self['model'].get('particle_dict')[forb_id] 3145 mystr = mystr + forbpart.get_name() + ' ' 3146 3147 # Remove last space 3148 mystr = mystr[:-1] 3149 3150 if self.get('id') or self.get('overall_orders'): 3151 mystr += " @%d" % self.get('id') 3152 if self.get('overall_orders'): 3153 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3154 for key in sorted(self['orders'])]) + ' ' 3155 3156 if not self.get('decay_chains'): 3157 return mystr 3158 3159 for decay in self['decay_chains']: 3160 mystr = mystr + '\n' + \ 3161 decay.nice_string(indent + 2).replace('Process', 'Decay') 3162 3163 return mystr
3164
3165 - def input_string(self):
3166 """Returns a process string corresponding to the input string 3167 in the command line interface.""" 3168 3169 mystr = "" 3170 prevleg = None 3171 3172 for leg in self['legs']: 3173 mypart = self['model'].get('particle_dict')[leg['id']] 3174 if prevleg and prevleg['state'] == False \ 3175 and leg['state'] == True: 3176 # Separate initial and final legs by ">" 3177 mystr = mystr + '> ' 3178 # Add required s-channels 3179 if self['required_s_channels'] and \ 3180 self['required_s_channels'][0]: 3181 mystr += "|".join([" ".join([self['model'].\ 3182 get('particle_dict')[req_id].get_name() \ 3183 for req_id in id_list]) \ 3184 for id_list in self['required_s_channels']]) 3185 mystr = mystr + '> ' 3186 3187 mystr = mystr + mypart.get_name() 3188 if leg.get('polarization'): 3189 if leg.get('polarization') in [[-1,1],[1,-1]]: 3190 mystr = mystr + '{T} ' 3191 elif leg.get('polarization') == [-1]: 3192 mystr = mystr + '{L} ' 3193 elif leg.get('polarization') == [1]: 3194 mystr = mystr + '{R} ' 3195 else: 3196 mystr = mystr + '{%s} ' %','.join([str(p) for p in leg.get('polarization')]) 3197 else: 3198 mystr = mystr + ' ' 3199 3200 #mystr = mystr + '(%i) ' % leg['number'] 3201 prevleg = leg 3202 3203 if self['orders']: 3204 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3205 for key in self['orders']]) + ' ' 3206 3207 # Add squared orders 3208 if self['squared_orders']: 3209 mystr = mystr + " ".join([key + '^2=' + repr(self['squared_orders'][key]) \ 3210 for key in self['squared_orders']]) + ' ' 3211 3212 # Add perturbation orders 3213 if self['perturbation_couplings']: 3214 mystr = mystr + '[ ' 3215 if self['NLO_mode']: 3216 mystr = mystr + self['NLO_mode'] 3217 if not self['has_born']: 3218 mystr = mystr + '^2' 3219 mystr = mystr + '= ' 3220 3221 for order in self['perturbation_couplings']: 3222 mystr = mystr + order + ' ' 3223 mystr = mystr + '] ' 3224 3225 3226 # Add forbidden s-channels 3227 if self['forbidden_onsh_s_channels']: 3228 mystr = mystr + '$ ' 3229 for forb_id in self['forbidden_onsh_s_channels']: 3230 forbpart = self['model'].get('particle_dict')[forb_id] 3231 mystr = mystr + forbpart.get_name() + ' ' 3232 3233 # Add double forbidden s-channels 3234 if self['forbidden_s_channels']: 3235 mystr = mystr + '$$ ' 3236 for forb_id in self['forbidden_s_channels']: 3237 forbpart = self['model'].get('particle_dict')[forb_id] 3238 mystr = mystr + forbpart.get_name() + ' ' 3239 3240 # Add forbidden particles 3241 if self['forbidden_particles']: 3242 mystr = mystr + '/ ' 3243 for forb_id in self['forbidden_particles']: 3244 forbpart = self['model'].get('particle_dict')[forb_id] 3245 mystr = mystr + forbpart.get_name() + ' ' 3246 3247 # Remove last space 3248 mystr = mystr[:-1] 3249 3250 if self.get('overall_orders'): 3251 mystr += " @%d" % self.get('id') 3252 if self.get('overall_orders'): 3253 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3254 for key in sorted(self['orders'])]) + ' ' 3255 3256 if not self.get('decay_chains'): 3257 return mystr 3258 3259 for decay in self['decay_chains']: 3260 paren1 = '' 3261 paren2 = '' 3262 if decay.get('decay_chains'): 3263 paren1 = '(' 3264 paren2 = ')' 3265 mystr += ', ' + paren1 + decay.input_string() + paren2 3266 3267 return mystr
3268
3269 - def base_string(self):
3270 """Returns a string containing only the basic process (w/o decays).""" 3271 3272 mystr = "" 3273 prevleg = None 3274 for leg in self.get_legs_with_decays(): 3275 mypart = self['model'].get('particle_dict')[leg['id']] 3276 if prevleg and prevleg['state'] == False \ 3277 and leg['state'] == True: 3278 # Separate initial and final legs by ">" 3279 mystr = mystr + '> ' 3280 mystr = mystr + mypart.get_name() 3281 if leg.get('polarization'): 3282 if leg.get('polarization') in [[-1,1],[1,-1]]: 3283 mystr = mystr + '{T} ' 3284 elif leg.get('polarization') == [-1]: 3285 mystr = mystr + '{L} ' 3286 elif leg.get('polarization') == [1]: 3287 mystr = mystr + '{R} ' 3288 else: 3289 mystr = mystr + '{%s} ' %','.join([str(p) for p in leg.get('polarization')]) 3290 else: 3291 mystr = mystr + ' ' 3292 prevleg = leg 3293 3294 # Remove last space 3295 return mystr[:-1]
3296
3297 - def shell_string(self, schannel=True, forbid=True, main=True, pdg_order=False, 3298 print_id = True):
3299 """Returns process as string with '~' -> 'x', '>' -> '_', 3300 '+' -> 'p' and '-' -> 'm', including process number, 3301 intermediate s-channels and forbidden particles, 3302 pdg_order allow to order to leg order by pid.""" 3303 3304 mystr = "" 3305 if not self.get('is_decay_chain') and print_id: 3306 mystr += "%d_" % self['id'] 3307 3308 prevleg = None 3309 if pdg_order: 3310 legs = [l for l in self['legs'][1:]] 3311 def order_leg(l1,l2): 3312 id1 = l1.get('id') 3313 id2 = l2.get('id') 3314 return id2-id1
3315 legs.sort(cmp=order_leg) 3316 legs.insert(0, self['legs'][0]) 3317 else: 3318 legs = self['legs'] 3319 3320 3321 for leg in legs: 3322 mypart = self['model'].get('particle_dict')[leg['id']] 3323 if prevleg and prevleg['state'] == False \ 3324 and leg['state'] == True: 3325 # Separate initial and final legs by ">" 3326 mystr = mystr + '_' 3327 # Add required s-channels 3328 if self['required_s_channels'] and \ 3329 self['required_s_channels'][0] and schannel: 3330 mystr += "_or_".join(["".join([self['model'].\ 3331 get('particle_dict')[req_id].get_name() \ 3332 for req_id in id_list]) \ 3333 for id_list in self['required_s_channels']]) 3334 mystr = mystr + '_' 3335 if mypart['is_part']: 3336 mystr = mystr + mypart['name'] 3337 else: 3338 mystr = mystr + mypart['antiname'] 3339 if leg.get('polarization'): 3340 if leg.get('polarization') in [[-1,1],[1,-1]]: 3341 mystr = mystr + 'T' 3342 elif leg.get('polarization') == [-1]: 3343 mystr = mystr + 'L' 3344 elif leg.get('polarization') == [1]: 3345 mystr = mystr + 'R' 3346 else: 3347 mystr = mystr + '%s ' %''.join([str(p).replace('-','m') for p in leg.get('polarization')]) 3348 3349 prevleg = leg 3350 3351 # Check for forbidden particles 3352 if self['forbidden_particles'] and forbid: 3353 mystr = mystr + '_no_' 3354 for forb_id in self['forbidden_particles']: 3355 forbpart = self['model'].get('particle_dict')[forb_id] 3356 mystr = mystr + forbpart.get_name() 3357 3358 # Replace '~' with 'x' 3359 mystr = mystr.replace('~', 'x') 3360 # Replace '+' with 'p' 3361 mystr = mystr.replace('+', 'p') 3362 # Replace '-' with 'm' 3363 mystr = mystr.replace('-', 'm') 3364 # Just to be safe, remove all spaces 3365 mystr = mystr.replace(' ', '') 3366 3367 for decay in self.get('decay_chains'): 3368 mystr = mystr + "_" + decay.shell_string(schannel,forbid, main=False, 3369 pdg_order=pdg_order) 3370 3371 # Too long name are problematic so restrict them to a maximal of 70 char 3372 if len(mystr) > 64 and main: 3373 if schannel and forbid: 3374 out = self.shell_string(True, False, True, pdg_order) 3375 elif schannel: 3376 out = self.shell_string(False, False, True, pdg_order) 3377 else: 3378 out = mystr[:64] 3379 if not out.endswith('_%s' % self['uid']): 3380 out += '_%s' % self['uid'] 3381 return out 3382 3383 return mystr
3384
3385 - def shell_string_v4(self):
3386 """Returns process as v4-compliant string with '~' -> 'x' and 3387 '>' -> '_'""" 3388 3389 mystr = "%d_" % self['id'] 3390 prevleg = None 3391 for leg in self.get_legs_with_decays(): 3392 mypart = self['model'].get('particle_dict')[leg['id']] 3393 if prevleg and prevleg['state'] == False \ 3394 and leg['state'] == True: 3395 # Separate initial and final legs by ">" 3396 mystr = mystr + '_' 3397 if mypart['is_part']: 3398 mystr = mystr + mypart['name'] 3399 else: 3400 mystr = mystr + mypart['antiname'] 3401 if leg.get('polarization'): 3402 if leg.get('polarization') in [[-1,1],[1,-1]]: 3403 mystr = mystr + 'T' 3404 elif leg.get('polarization') == [-1]: 3405 mystr = mystr + 'L' 3406 elif leg.get('polarization') == [1]: 3407 mystr = mystr + 'R' 3408 else: 3409 mystr = mystr + '%s ' %''.join([str(p).replace('-','m') for p in leg.get('polarization')]) 3410 3411 prevleg = leg 3412 3413 # Replace '~' with 'x' 3414 mystr = mystr.replace('~', 'x') 3415 # Just to be safe, remove all spaces 3416 mystr = mystr.replace(' ', '') 3417 3418 return mystr
3419 3420 # Helper functions 3421
3422 - def are_negative_orders_present(self):
3423 """ Check iteratively that no coupling order constraint include negative 3424 values.""" 3425 3426 if any(val<0 for val in self.get('orders').values()+\ 3427 self.get('squared_orders').values()): 3428 return True 3429 3430 for procdef in self['decay_chains']: 3431 if procdef.are_negative_orders_present(): 3432 return True 3433 3434 return False
3435
3436 - def are_decays_perturbed(self):
3437 """ Check iteratively that the decayed processes are not perturbed """ 3438 3439 for procdef in self['decay_chains']: 3440 if procdef['perturbation_couplings'] or procdef.are_decays_perturbed(): 3441 return True 3442 return False
3443
3444 - def decays_have_squared_orders(self):
3445 """ Check iteratively that the decayed processes are not perturbed """ 3446 3447 for procdef in self['decay_chains']: 3448 if procdef['squared_orders']!={} or procdef.decays_have_squared_orders(): 3449 return True 3450 return False
3451
3452 - def get_ninitial(self):
3453 """Gives number of initial state particles""" 3454 3455 return len(filter(lambda leg: leg.get('state') == False, 3456 self.get('legs')))
3457
3458 - def get_initial_ids(self):
3459 """Gives the pdg codes for initial state particles""" 3460 3461 return [leg.get('id') for leg in \ 3462 filter(lambda leg: leg.get('state') == False, 3463 self.get('legs'))]
3464
3465 - def get_initial_pdg(self, number):
3466 """Return the pdg codes for initial state particles for beam number""" 3467 3468 legs = filter(lambda leg: leg.get('state') == False and\ 3469 leg.get('number') == number, 3470 self.get('legs')) 3471 if not legs: 3472 return None 3473 else: 3474 return legs[0].get('id')
3475
3476 - def get_initial_final_ids(self):
3477 """return a tuple of two tuple containing the id of the initial/final 3478 state particles. Each list is ordered""" 3479 3480 initial = [] 3481 final = [l.get('id') for l in self.get('legs')\ 3482 if l.get('state') or initial.append(l.get('id'))] 3483 initial.sort() 3484 final.sort() 3485 return (tuple(initial), tuple(final))
3486
3487 - def get_initial_final_ids_after_decay(self, max_depth=-1):
3488 """return a tuple of two tuple containing the id of the initial/final 3489 state particles. Each list is ordered""" 3490 3491 initial = [l.get('id') for l in self.get('legs')\ 3492 if not l.get('state')] 3493 final = self.get_final_ids_after_decay(max_depth=max_depth) 3494 initial.sort() 3495 final.sort() 3496 return (tuple(initial), tuple(final))
3497 3498
3499 - def get_final_ids_after_decay(self, max_depth=-1):
3500 """Give the pdg code of the process including decay""" 3501 3502 finals = self.get_final_ids() 3503 if max_depth !=0 : 3504 for proc in self.get('decay_chains'): 3505 init = proc.get_initial_ids()[0] 3506 #while 1: 3507 try: 3508 pos = finals.index(init) 3509 except: 3510 break 3511 finals[pos] = proc.get_final_ids_after_decay(max_depth-1) 3512 output = [] 3513 for d in finals: 3514 if isinstance(d, list): 3515 output += d 3516 else: 3517 output.append(d) 3518 3519 return output
3520 3521
3522 - def get_final_legs(self):
3523 """Gives the final state legs""" 3524 3525 return filter(lambda leg: leg.get('state') == True, 3526 self.get('legs'))
3527
3528 - def get_final_ids(self):
3529 """Gives the pdg codes for final state particles""" 3530 3531 return [l.get('id') for l in self.get_final_legs()]
3532 3533
3534 - def get_legs_with_decays(self):
3535 """Return process with all decay chains substituted in.""" 3536 3537 if self['legs_with_decays']: 3538 return self['legs_with_decays'] 3539 3540 legs = copy.deepcopy(self.get('legs')) 3541 org_decay_chains = copy.copy(self.get('decay_chains')) 3542 sorted_decay_chains = [] 3543 # Sort decay chains according to leg order 3544 for leg in legs: 3545 if not leg.get('state'): continue 3546 org_ids = [l.get('legs')[0].get('id') for l in \ 3547 org_decay_chains] 3548 if leg.get('id') in org_ids: 3549 sorted_decay_chains.append(org_decay_chains.pop(\ 3550 org_ids.index(leg.get('id')))) 3551 assert not org_decay_chains 3552 ileg = 0 3553 for decay in sorted_decay_chains: 3554 while legs[ileg].get('state') == False or \ 3555 legs[ileg].get('id') != decay.get('legs')[0].get('id'): 3556 ileg = ileg + 1 3557 decay_legs = decay.get_legs_with_decays() 3558 legs = legs[:ileg] + decay_legs[1:] + legs[ileg+1:] 3559 ileg = ileg + len(decay_legs) - 1 3560 3561 # Replace legs with copies 3562 legs = [copy.copy(l) for l in legs] 3563 3564 for ileg, leg in enumerate(legs): 3565 leg.set('number', ileg + 1) 3566 3567 self['legs_with_decays'] = LegList(legs) 3568 3569 return self['legs_with_decays']
3570
3571 - def get_tag(self):
3572 """return the tag for standalone call""" 3573 3574 initial = [] #filled in the next line 3575 final = [l.get('id') for l in self.get('legs')\ 3576 if l.get('state') or initial.append(l.get('id'))] 3577 decay_finals = self.get_final_ids_after_decay() 3578 decay_finals.sort() 3579 tag = (tuple(initial), tuple(decay_finals)) 3580 return tag
3581 3582
3583 - def list_for_sort(self):
3584 """Output a list that can be compared to other processes as: 3585 [id, sorted(initial leg ids), sorted(final leg ids), 3586 sorted(decay list_for_sorts)]""" 3587 3588 sorted_list = [self.get('id'), 3589 sorted(self.get_initial_ids()), 3590 sorted(self.get_final_ids())] 3591 3592 if self.get('decay_chains'): 3593 sorted_list.extend(sorted([d.list_for_sort() for d in \ 3594 self.get('decay_chains')])) 3595 3596 return sorted_list
3597
3598 - def compare_for_sort(self, other):
3599 """Sorting routine which allows to sort processes for 3600 comparison. Compare only process id and legs.""" 3601 3602 if self.list_for_sort() > other.list_for_sort(): 3603 return 1 3604 if self.list_for_sort() < other.list_for_sort(): 3605 return -1 3606 return 0
3607
3608 - def identical_particle_factor(self):
3609 """Calculate the denominator factor for identical final state particles 3610 """ 3611 3612 3613 final_legs = filter(lambda leg: leg.get('state') == True, \ 3614 self.get_legs_with_decays()) 3615 3616 identical_indices = collections.defaultdict(int) 3617 for leg in final_legs: 3618 key = (leg.get('id'), tuple(leg.get('polarization'))) 3619 identical_indices[key] += 1 3620 3621 3622 return reduce(lambda x, y: x * y, [ math.factorial(val) for val in \ 3623 identical_indices.values() ], 1)
3624
3625 - def check_expansion_orders(self):
3626 """Ensure that maximum expansion orders from the model are 3627 properly taken into account in the process""" 3628 3629 # Ensure that expansion orders are taken into account 3630 expansion_orders = self.get('model').get('expansion_order') 3631 orders = self.get('orders') 3632 sq_orders = self.get('squared_orders') 3633 3634 tmp = [(k,v) for (k,v) in expansion_orders.items() if 0 < v < 99] 3635 for (k,v) in tmp: 3636 if k in orders: 3637 if v < orders[k]: 3638 if k in sq_orders.keys() and \ 3639 (sq_orders[k]>v or sq_orders[k]<0): 3640 logger.warning( 3641 '''The process with the squared coupling order (%s^2%s%s) specified can potentially 3642 recieve contributions with powers of the coupling %s larger than the maximal 3643 value allowed by the model builder (%s). Hence, MG5_aMC sets the amplitude order 3644 for that coupling to be this maximal one. '''%(k,self.get('sqorders_types')[k], 3645 self.get('squared_orders')[k],k,v)) 3646 else: 3647 logger.warning( 3648 '''The coupling order (%s=%s) specified is larger than the one allowed 3649 by the model builder. The maximal value allowed is %s. 3650 We set the %s order to this value''' % (k,orders[k],v,k)) 3651 orders[k] = v 3652 else: 3653 orders[k] = v
3654
3655 - def __eq__(self, other):
3656 """Overloading the equality operator, so that only comparison 3657 of process id and legs is being done, using compare_for_sort.""" 3658 3659 if not isinstance(other, Process): 3660 return False 3661 3662 return self.compare_for_sort(other) == 0
3663
3664 - def __ne__(self, other):
3665 return not self.__eq__(other)
3666
3667 #=============================================================================== 3668 # ProcessList 3669 #=============================================================================== 3670 -class ProcessList(PhysicsObjectList):
3671 """List of Process objects 3672 """ 3673
3674 - def is_valid_element(self, obj):
3675 """Test if object obj is a valid Process for the list.""" 3676 3677 return isinstance(obj, Process)
3678
3679 - def nice_string(self, indent = 0):
3680 """Returns a nicely formatted string of the matrix element processes.""" 3681 3682 mystr = "\n".join([p.nice_string(indent) for p in self]) 3683 3684 return mystr
3685
3686 #=============================================================================== 3687 # ProcessDefinition 3688 #=============================================================================== 3689 -class ProcessDefinition(Process):
3690 """ProcessDefinition: list of multilegs (ordered) 3691 dictionary of orders 3692 model 3693 process id 3694 """ 3695
3696 - def default_setup(self):
3697 """Default values for all properties""" 3698 3699 super(ProcessDefinition, self).default_setup() 3700 3701 self['legs'] = MultiLegList() 3702 # Decay chain processes associated with this process 3703 self['decay_chains'] = ProcessDefinitionList() 3704 if 'legs_with_decays' in self: del self['legs_with_decays']
3705
3706 - def filter(self, name, value):
3707 """Filter for valid process property values.""" 3708 3709 if name == 'legs': 3710 if not isinstance(value, MultiLegList): 3711 raise self.PhysicsObjectError, \ 3712 "%s is not a valid MultiLegList object" % str(value) 3713 elif name == 'decay_chains': 3714 if not isinstance(value, ProcessDefinitionList): 3715 raise self.PhysicsObjectError, \ 3716 "%s is not a valid ProcessDefinitionList" % str(value) 3717 3718 else: 3719 return super(ProcessDefinition, self).filter(name, value) 3720 3721 return True
3722
3723 - def has_multiparticle_label(self):
3724 """ Check that this process definition will yield a single process, as 3725 each multileg only has one leg""" 3726 3727 for process in self['decay_chains']: 3728 if process.has_multiparticle_label(): 3729 return True 3730 3731 for mleg in self['legs']: 3732 if len(mleg['ids'])>1: 3733 return True 3734 3735 return False
3736
3737 - def check_polarization(self):
3738 """ raise a critical information if someone tries something like 3739 p p > Z{T} Z 3740 return True if no issue and False if some issue is found 3741 """ 3742 3743 pol = {} 3744 for leg in self.get('legs'): 3745 if not leg.get('state'): 3746 continue 3747 if leg.get('polarization'): 3748 for pid in leg.get('ids'): 3749 if pid not in pol: 3750 pol[pid] = [leg.get('polarization')] 3751 elif leg.get('polarization') in pol[pid]: 3752 # already present polarization -> no issue 3753 continue 3754 else: 3755 for p in leg.get('polarization'): 3756 if any(p in o for o in pol[pid]): 3757 return False 3758 pol[pid].append(leg.get('polarization')) 3759 else: 3760 for pid in leg.get('ids'): 3761 if pid not in pol: 3762 pol[pid] = [list(range(-3,4))] 3763 elif pol[pid] == [list(range(-3,4))]: 3764 continue 3765 else: 3766 return False 3767 3768 return True
3769
3770 - def get_sorted_keys(self):
3771 """Return process property names as a nicely sorted list.""" 3772 3773 keys = super(ProcessDefinition, self).get_sorted_keys() 3774 keys.remove('legs_with_decays') 3775 3776 return keys
3777
3778 - def get_minimum_WEIGHTED(self):
3779 """Retrieve the minimum starting guess for WEIGHTED order, to 3780 use in find_optimal_process_orders in MultiProcess diagram 3781 generation (as well as particles and hierarchy). The algorithm: 3782 3783 1) Pick out the legs in the multiprocess according to the 3784 highest hierarchy represented (so don't mix particles from 3785 different hierarchy classes in the same multiparticles!) 3786 3787 2) Find the starting maximum WEIGHTED order as the sum of the 3788 highest n-2 weighted orders 3789 3790 3) Pick out required s-channel particle hierarchies, and use 3791 the highest of the maximum WEIGHTED order from the legs and 3792 the minimum WEIGHTED order extracted from 2*s-channel 3793 hierarchys plus the n-2-2*(number of s-channels) lowest 3794 leg weighted orders. 3795 """ 3796 3797 model = self.get('model') 3798 3799 # Extract hierarchy and particles corresponding to the 3800 # different hierarchy levels from the model 3801 particles, hierarchy = model.get_particles_hierarchy() 3802 # Find legs corresponding to the different orders 3803 # making sure we look at lowest hierarchy first for each leg 3804 max_order_now = [] 3805 new_legs = copy.copy(self.get('legs')) 3806 import madgraph.core.base_objects as base_objects 3807 for parts, value in zip(particles, hierarchy): 3808 ileg = 0 3809 while ileg < len(new_legs): 3810 if any([id in parts for id in new_legs[ileg].get('ids')]): 3811 max_order_now.append(value) 3812 new_legs.pop(ileg) 3813 else: 3814 ileg += 1 3815 3816 # Now remove the two lowest orders to get maximum (since the 3817 # number of interactions is n-2) 3818 max_order_now = sorted(max_order_now)[2:] 3819 3820 # Find s-channel propagators corresponding to the different orders 3821 max_order_prop = [] 3822 for idlist in self.get('required_s_channels'): 3823 max_order_prop.append([0,0]) 3824 for id in idlist: 3825 for parts, value in zip(particles, hierarchy): 3826 if id in parts: 3827 max_order_prop[-1][0] += 2*value 3828 max_order_prop[-1][1] += 1 3829 break 3830 3831 if max_order_prop: 3832 if len(max_order_prop) >1: 3833 max_order_prop = min(*max_order_prop, key=lambda x:x[0]) 3834 else: 3835 max_order_prop = max_order_prop[0] 3836 3837 # Use either the max_order from the external legs or 3838 # the maximum order from the s-channel propagators, plus 3839 # the appropriate lowest orders from max_order_now 3840 max_order_now = max(sum(max_order_now), 3841 max_order_prop[0] + \ 3842 sum(max_order_now[:-2 * max_order_prop[1]])) 3843 else: 3844 max_order_now = sum(max_order_now) 3845 3846 return max_order_now, particles, hierarchy
3847
3848 - def __iter__(self):
3849 """basic way to loop over all the process definition. 3850 not used by MG which used some smarter version (use by ML)""" 3851 3852 isids = [leg['ids'] for leg in self['legs'] \ 3853 if leg['state'] == False] 3854 fsids = [leg['ids'] for leg in self['legs'] \ 3855 if leg['state'] == True] 3856 3857 red_isidlist = [] 3858 # Generate all combinations for the initial state 3859 for prod in itertools.product(*isids): 3860 islegs = [Leg({'id':id, 'state': False}) for id in prod] 3861 if tuple(sorted(prod)) in red_isidlist: 3862 continue 3863 red_isidlist.append(tuple(sorted(prod))) 3864 red_fsidlist = [] 3865 for prod in itertools.product(*fsids): 3866 # Remove double counting between final states 3867 if tuple(sorted(prod)) in red_fsidlist: 3868 continue 3869 red_fsidlist.append(tuple(sorted(prod))) 3870 leg_list = [copy.copy(leg) for leg in islegs] 3871 leg_list.extend([Leg({'id':id, 'state': True}) for id in prod]) 3872 legs = LegList(leg_list) 3873 process = self.get_process_with_legs(legs) 3874 yield process
3875
3876 - def nice_string(self, indent=0, print_weighted=False, prefix=True):
3877 """Returns a nicely formated string about current process 3878 content""" 3879 3880 if prefix: 3881 mystr = " " * indent + "Process: " 3882 else: 3883 mystr="" 3884 prevleg = None 3885 for leg in self['legs']: 3886 myparts = \ 3887 "/".join([self['model'].get('particle_dict')[id].get_name() \ 3888 for id in leg.get('ids')]) 3889 if prevleg and prevleg['state'] == False \ 3890 and leg['state'] == True: 3891 # Separate initial and final legs by ">" 3892 mystr = mystr + '> ' 3893 # Add required s-channels 3894 if self['required_s_channels'] and \ 3895 self['required_s_channels'][0]: 3896 mystr += "|".join([" ".join([self['model'].\ 3897 get('particle_dict')[req_id].get_name() \ 3898 for req_id in id_list]) \ 3899 for id_list in self['required_s_channels']]) 3900 mystr = mystr + '> ' 3901 3902 mystr = mystr + myparts 3903 if leg.get('polarization'): 3904 if leg.get('polarization') in [[-1,1],[1,-1]]: 3905 mystr = mystr + '{T}' 3906 elif leg.get('polarization') == [-1]: 3907 mystr = mystr + '{L}' 3908 elif leg.get('polarization') == [1]: 3909 mystr = mystr + '{R}' 3910 else: 3911 mystr = mystr + '{%s} ' %''.join([str(p) for p in leg.get('polarization')]) 3912 else: 3913 mystr = mystr + ' ' 3914 #mystr = mystr + '(%i) ' % leg['number'] 3915 prevleg = leg 3916 3917 # Add forbidden s-channels 3918 if self['forbidden_onsh_s_channels']: 3919 mystr = mystr + '$ ' 3920 for forb_id in self['forbidden_onsh_s_channels']: 3921 forbpart = self['model'].get('particle_dict')[forb_id] 3922 mystr = mystr + forbpart.get_name() + ' ' 3923 3924 # Add double forbidden s-channels 3925 if self['forbidden_s_channels']: 3926 mystr = mystr + '$$ ' 3927 for forb_id in self['forbidden_s_channels']: 3928 forbpart = self['model'].get('particle_dict')[forb_id] 3929 mystr = mystr + forbpart.get_name() + ' ' 3930 3931 # Add forbidden particles 3932 if self['forbidden_particles']: 3933 mystr = mystr + '/ ' 3934 for forb_id in self['forbidden_particles']: 3935 forbpart = self['model'].get('particle_dict')[forb_id] 3936 mystr = mystr + forbpart.get_name() + ' ' 3937 3938 if self['orders']: 3939 mystr = mystr + " ".join([key + '=' + repr(self['orders'][key]) \ 3940 for key in sorted(self['orders'])]) + ' ' 3941 3942 if self['constrained_orders']: 3943 mystr = mystr + " ".join('%s%s%d' % (key, operator, value) for 3944 (key,(value, operator)) 3945 in self['constrained_orders'].items()) + ' ' 3946 3947 # Add perturbation_couplings 3948 if self['perturbation_couplings']: 3949 mystr = mystr + '[ ' 3950 if self['NLO_mode']!='tree': 3951 if self['NLO_mode']=='virt' and not self['has_born']: 3952 mystr = mystr + 'sqrvirt = ' 3953 else: 3954 mystr = mystr + self['NLO_mode'] + ' = ' 3955 for order in self['perturbation_couplings']: 3956 mystr = mystr + order + ' ' 3957 mystr = mystr + '] ' 3958 3959 if self['squared_orders']: 3960 mystr = mystr + " ".join([key + '^2%s%d'%\ 3961 (self.get_squared_order_type(key),self['squared_orders'][key]) \ 3962 for key in self['squared_orders'].keys() \ 3963 if print_weighted or key!='WEIGHTED']) + ' ' 3964 3965 # Remove last space 3966 mystr = mystr[:-1] 3967 3968 if self.get('id') or self.get('overall_orders'): 3969 mystr += " @%d" % self.get('id') 3970 if self.get('overall_orders'): 3971 mystr += " " + " ".join([key + '=' + repr(self['orders'][key]) \ 3972 for key in sorted(self['orders'])]) + ' ' 3973 3974 if not self.get('decay_chains'): 3975 return mystr 3976 3977 for decay in self['decay_chains']: 3978 mystr = mystr + '\n' + \ 3979 decay.nice_string(indent + 2).replace('Process', 'Decay') 3980 3981 return mystr
3982
3983 - def get_process_with_legs(self, LegList):
3984 """ Return a Process object which has the same properties of this 3985 ProcessDefinition but with the specified LegList as legs attribute. 3986 """ 3987 3988 return Process({\ 3989 'legs': LegList, 3990 'model':self.get('model'), 3991 'id': self.get('id'), 3992 'orders': self.get('orders'), 3993 'sqorders_types': self.get('sqorders_types'), 3994 'squared_orders': self.get('squared_orders'), 3995 'constrained_orders': self.get('constrained_orders'), 3996 'has_born': self.get('has_born'), 3997 'required_s_channels': self.get('required_s_channels'), 3998 'forbidden_onsh_s_channels': self.get('forbidden_onsh_s_channels'), 3999 'forbidden_s_channels': self.get('forbidden_s_channels'), 4000 'forbidden_particles': self.get('forbidden_particles'), 4001 'perturbation_couplings': self.get('perturbation_couplings'), 4002 'is_decay_chain': self.get('is_decay_chain'), 4003 'overall_orders': self.get('overall_orders'), 4004 'split_orders': self.get('split_orders'), 4005 'NLO_mode': self.get('NLO_mode') 4006 })
4007
4008 - def get_process(self, initial_state_ids, final_state_ids):
4009 """ Return a Process object which has the same properties of this 4010 ProcessDefinition but with the specified given leg ids. """ 4011 4012 # First make sure that the desired particle ids belong to those defined 4013 # in this process definition. 4014 if __debug__: 4015 my_isids = [leg.get('ids') for leg in self.get('legs') \ 4016 if not leg.get('state')] 4017 my_fsids = [leg.get('ids') for leg in self.get('legs') \ 4018 if leg.get('state')] 4019 for i, is_id in enumerate(initial_state_ids): 4020 assert is_id in my_isids[i] 4021 for i, fs_id in enumerate(final_state_ids): 4022 assert fs_id in my_fsids[i] 4023 4024 return self.get_process_with_legs(LegList(\ 4025 [Leg({'id': id, 'state':False, 'polarization':[]}) for id in initial_state_ids] + \ 4026 [Leg({'id': id, 'state':True, 'polarization':[]}) for id in final_state_ids]))
4027
4028 - def __eq__(self, other):
4029 """Overloading the equality operator, so that only comparison 4030 of process id and legs is being done, using compare_for_sort.""" 4031 4032 return super(Process, self).__eq__(other)
4033
4034 #=============================================================================== 4035 # ProcessDefinitionList 4036 #=============================================================================== 4037 -class ProcessDefinitionList(PhysicsObjectList):
4038 """List of ProcessDefinition objects 4039 """ 4040
4041 - def is_valid_element(self, obj):
4042 """Test if object obj is a valid ProcessDefinition for the list.""" 4043 4044 return isinstance(obj, ProcessDefinition)
4045
4046 #=============================================================================== 4047 # Global helper functions 4048 #=============================================================================== 4049 4050 -def make_unique(doubletlist):
4051 """Make sure there are no doublets in the list doubletlist. 4052 Note that this is a slow implementation, so don't use if speed 4053 is needed""" 4054 4055 assert isinstance(doubletlist, list), \ 4056 "Argument to make_unique must be list" 4057 4058 4059 uniquelist = [] 4060 for elem in doubletlist: 4061 if elem not in uniquelist: 4062 uniquelist.append(elem) 4063 4064 doubletlist[:] = uniquelist[:]
4065