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