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