Package models :: Module import_ufo
[hide private]
[frames] | no frames]

Source Code for Module models.import_ufo

   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  """ How to import a UFO model to the MG5 format """ 
  16   
  17   
  18  import fractions 
  19  import logging 
  20  import os 
  21  import re 
  22  import sys 
  23  import time 
  24   
  25   
  26  from madgraph import MadGraph5Error, MG5DIR, ReadWrite 
  27  import madgraph.core.base_objects as base_objects 
  28  import madgraph.loop.loop_base_objects as loop_base_objects 
  29  import madgraph.core.color_algebra as color 
  30  import madgraph.iolibs.files as files 
  31  import madgraph.iolibs.save_load_object as save_load_object 
  32  from madgraph.core.color_algebra import * 
  33  import madgraph.various.misc as misc 
  34   
  35   
  36  import aloha 
  37  import aloha.create_aloha as create_aloha 
  38  import aloha.aloha_fct as aloha_fct 
  39   
  40  import models as ufomodels 
  41  import models.model_reader as model_reader 
  42  logger = logging.getLogger('madgraph.model') 
  43  logger_mod = logging.getLogger('madgraph.model') 
  44   
  45  root_path = os.path.dirname(os.path.realpath( __file__ )) 
  46  sys.path.append(root_path) 
  47   
  48  sys.path.append(os.path.join(root_path, os.path.pardir, 'Template', 'bin', 'internal')) 
  49  import check_param_card  
  50   
  51  pjoin = os.path.join 
  52  logger = logging.getLogger("madgraph.model") 
  53   
  54   
55 -class UFOImportError(MadGraph5Error):
56 """ a error class for wrong import of UFO model"""
57
58 -class InvalidModel(MadGraph5Error):
59 """ a class for invalid Model """
60
61 -def find_ufo_path(model_name):
62 """ find the path to a model """ 63 64 # Check for a valid directory 65 if model_name.startswith('./') and os.path.isdir(model_name): 66 model_path = model_name 67 elif os.path.isdir(os.path.join(MG5DIR, 'models', model_name)): 68 model_path = os.path.join(MG5DIR, 'models', model_name) 69 elif os.path.isdir(model_name): 70 model_path = model_name 71 else: 72 raise UFOImportError("Path %s is not a valid pathname" % model_name) 73 74 return model_path
75
76 -def import_model(model_name, decay=False, restrict=True, prefix='mdl_'):
77 """ a practical and efficient way to import a model""" 78 79 # check if this is a valid path or if this include restriction file 80 try: 81 model_path = find_ufo_path(model_name) 82 except UFOImportError: 83 if '-' not in model_name: 84 raise 85 split = model_name.split('-') 86 model_name = '-'.join([text for text in split[:-1]]) 87 model_path = find_ufo_path(model_name) 88 restrict_name = split[-1] 89 90 restrict_file = os.path.join(model_path, 'restrict_%s.dat'% restrict_name) 91 92 #if restriction is full, then we by pass restriction (avoid default) 93 if split[-1] == 'full': 94 restrict_file = None 95 else: 96 # Check if by default we need some restrictions 97 restrict_name = "" 98 if restrict and os.path.exists(os.path.join(model_path,'restrict_default.dat')): 99 restrict_file = os.path.join(model_path,'restrict_default.dat') 100 else: 101 restrict_file = None 102 103 if isinstance(restrict, str): 104 if os.path.exists(os.path.join(model_path, restrict)): 105 restrict_file = os.path.join(model_path, restrict) 106 elif os.path.exists(restrict): 107 restrict_file = restrict 108 else: 109 raise Exception, "%s is not a valid path for restrict file" % restrict 110 111 #import the FULL model 112 model = import_full_model(model_path, decay, prefix) 113 114 if os.path.exists(pjoin(model_path, "README")): 115 logger.info("Please read carefully the README of the model file for instructions/restrictions of the model.",'$MG:color:BLACK') 116 # restore the model name 117 if restrict_name: 118 model["name"] += '-' + restrict_name 119 120 #restrict it if needed 121 if restrict_file: 122 try: 123 logger.info('Restrict model %s with file %s .' % (model_name, os.path.relpath(restrict_file))) 124 except OSError: 125 # sometimes has trouble with relative path 126 logger.info('Restrict model %s with file %s .' % (model_name, restrict_file)) 127 128 if logger_mod.getEffectiveLevel() > 10: 129 logger.info('Run \"set stdout_level DEBUG\" before import for more information.') 130 # Modify the mother class of the object in order to allow restriction 131 model = RestrictModel(model) 132 133 if model_name == 'mssm' or os.path.basename(model_name) == 'mssm': 134 keep_external=True 135 else: 136 keep_external=False 137 model.restrict_model(restrict_file, rm_parameter=not decay, 138 keep_external=keep_external) 139 model.path = model_path 140 141 return model
142 143 144 _import_once = []
145 -def import_full_model(model_path, decay=False, prefix=''):
146 """ a practical and efficient way to import one of those models 147 (no restriction file use)""" 148 149 assert model_path == find_ufo_path(model_path) 150 151 if prefix is True: 152 prefix='mdl_' 153 154 # Check the validity of the model 155 files_list_prov = ['couplings.py','lorentz.py','parameters.py', 156 'particles.py', 'vertices.py', 'function_library.py', 157 'propagators.py' ] 158 159 if decay: 160 files_list_prov.append('decays.py') 161 162 files_list = [] 163 for filename in files_list_prov: 164 filepath = os.path.join(model_path, filename) 165 if not os.path.isfile(filepath): 166 if filename not in ['propagators.py', 'decays.py']: 167 raise UFOImportError, "%s directory is not a valid UFO model: \n %s is missing" % \ 168 (model_path, filename) 169 files_list.append(filepath) 170 171 # use pickle files if defined and up-to-date 172 if aloha.unitary_gauge: 173 pickle_name = 'model.pkl' 174 else: 175 pickle_name = 'model_Feynman.pkl' 176 if decay: 177 pickle_name = 'dec_%s' % pickle_name 178 179 allow_reload = False 180 if files.is_uptodate(os.path.join(model_path, pickle_name), files_list): 181 allow_reload = True 182 try: 183 model = save_load_object.load_from_file( \ 184 os.path.join(model_path, pickle_name)) 185 except Exception, error: 186 logger.info('failed to load model from pickle file. Try importing UFO from File') 187 else: 188 # We don't care about the restrict_card for this comparison 189 if model.has_key('version_tag') and not model.get('version_tag') is None and \ 190 model.get('version_tag').startswith(os.path.realpath(model_path)) and \ 191 model.get('version_tag').endswith('##' + str(misc.get_pkg_info())): 192 #check if the prefix is correct one. 193 for key in model.get('parameters'): 194 for param in model['parameters'][key]: 195 value = param.name.lower() 196 if value in ['as','mu_r', 'zero','aewm1']: 197 continue 198 if prefix: 199 if value.startswith(prefix): 200 _import_once.append((model_path, aloha.unitary_gauge, prefix, decay)) 201 return model 202 else: 203 logger.info('reload from .py file') 204 break 205 else: 206 if value.startswith('mdl_'): 207 logger.info('reload from .py file') 208 break 209 else: 210 _import_once.append((model_path, aloha.unitary_gauge, prefix, decay)) 211 return model 212 else: 213 continue 214 break 215 else: 216 logger.info('reload from .py file') 217 218 if (model_path, aloha.unitary_gauge, prefix, decay) in _import_once and not allow_reload: 219 raise MadGraph5Error, 'This model %s is modified on disk. To reload it you need to quit/relaunch MG5_aMC ' % model_path 220 221 # Load basic information 222 ufo_model = ufomodels.load_model(model_path, decay) 223 ufo2mg5_converter = UFOMG5Converter(ufo_model) 224 model = ufo2mg5_converter.load_model() 225 if model_path[-1] == '/': model_path = model_path[:-1] #avoid empty name 226 model.set('name', os.path.split(model_path)[-1]) 227 228 # Load the Parameter/Coupling in a convenient format. 229 parameters, couplings = OrganizeModelExpression(ufo_model).main(\ 230 additional_couplings =(ufo2mg5_converter.wavefunction_CT_couplings if ufo2mg5_converter.perturbation_couplings else [])) 231 232 model.set('parameters', parameters) 233 model.set('couplings', couplings) 234 model.set('functions', ufo_model.all_functions) 235 236 # Optional UFO part: decay_width information 237 238 239 if decay and hasattr(ufo_model, 'all_decays') and ufo_model.all_decays: 240 start = time.time() 241 for ufo_part in ufo_model.all_particles: 242 name = ufo_part.name 243 if not model['case_sensitive']: 244 name = name.lower() 245 p = model['particles'].find_name(name) 246 if hasattr(ufo_part, 'partial_widths'): 247 p.partial_widths = ufo_part.partial_widths 248 elif p and not hasattr(p, 'partial_widths'): 249 p.partial_widths = {} 250 # might be None for ghost 251 logger.debug("load width takes %s", time.time()-start) 252 253 if prefix: 254 start = time.time() 255 model.change_parameter_name_with_prefix() 256 logger.debug("model prefixing takes %s", time.time()-start) 257 258 path = os.path.dirname(os.path.realpath(model_path)) 259 path = os.path.join(path, model.get('name')) 260 model.set('version_tag', os.path.realpath(path) +'##'+ str(misc.get_pkg_info())) 261 262 # save in a pickle files to fasten future usage 263 if ReadWrite: 264 save_load_object.save_to_file(os.path.join(model_path, pickle_name), 265 model, log=False) 266 267 #if default and os.path.exists(os.path.join(model_path, 'restrict_default.dat')): 268 # restrict_file = os.path.join(model_path, 'restrict_default.dat') 269 # model = import_ufo.RestrictModel(model) 270 # model.restrict_model(restrict_file) 271 272 return model
273
274 -class UFOMG5Converter(object):
275 """Convert a UFO model to the MG5 format""" 276
277 - def __init__(self, model, auto=False):
278 """ initialize empty list for particles/interactions """ 279 280 self.particles = base_objects.ParticleList() 281 self.interactions = base_objects.InteractionList() 282 self.wavefunction_CT_couplings = [] 283 284 # Check here if we can extract the couplings perturbed in this model 285 # which indicate a loop model or if this model is only meant for 286 # tree-level computations 287 self.perturbation_couplings = {} 288 try: 289 for order in model.all_orders: 290 if(order.perturbative_expansion>0): 291 self.perturbation_couplings[order.name]=order.perturbative_expansion 292 except AttributeError,error: 293 pass 294 295 if self.perturbation_couplings!={}: 296 self.model = loop_base_objects.LoopModel({'perturbation_couplings':\ 297 self.perturbation_couplings.keys()}) 298 else: 299 self.model = base_objects.Model() 300 self.model.set('particles', self.particles) 301 self.model.set('interactions', self.interactions) 302 self.conservecharge = set(['charge']) 303 304 self.ufomodel = model 305 self.checked_lor = set() 306 307 if auto: 308 self.load_model()
309
310 - def load_model(self):
311 """load the different of the model first particles then interactions""" 312 313 # Check the validity of the model 314 # 1) check that all lhablock are single word. 315 def_name = [] 316 for param in self.ufomodel.all_parameters: 317 if param.nature == "external": 318 if len(param.lhablock.split())>1: 319 raise InvalidModel, '''LHABlock should be single word which is not the case for 320 \'%s\' parameter with lhablock \'%s\' ''' % (param.name, param.lhablock) 321 if param.name in def_name: 322 raise InvalidModel, "name %s define multiple time. Please correct the UFO model!" \ 323 % (param.name) 324 else: 325 def_name.append(param.name) 326 327 328 if hasattr(self.ufomodel, 'gauge'): 329 self.model.set('gauge', self.ufomodel.gauge) 330 logger.info('load particles') 331 # Check if multiple particles have the same name but different case. 332 # Otherwise, we can use lowercase particle names. 333 if len(set([p.name for p in self.ufomodel.all_particles] + \ 334 [p.antiname for p in self.ufomodel.all_particles])) == \ 335 len(set([p.name.lower() for p in self.ufomodel.all_particles] + \ 336 [p.antiname.lower() for p in self.ufomodel.all_particles])): 337 self.model['case_sensitive'] = False 338 339 340 # check which of the fermion/anti-fermion should be set as incoming 341 self.detect_incoming_fermion() 342 343 for particle_info in self.ufomodel.all_particles: 344 self.add_particle(particle_info) 345 346 # Find which particles is in the 3/3bar color states (retrun {id: 3/-3}) 347 color_info = self.find_color_anti_color_rep() 348 349 # load the lorentz structure. 350 self.model.set('lorentz', self.ufomodel.all_lorentz) 351 352 logger.info('load vertices') 353 for interaction_info in self.ufomodel.all_vertices: 354 self.add_interaction(interaction_info, color_info) 355 356 if self.perturbation_couplings: 357 try: 358 self.ufomodel.add_NLO() 359 except Exception, error: 360 pass 361 for interaction_info in self.ufomodel.all_CTvertices: 362 self.add_CTinteraction(interaction_info, color_info) 363 364 self.model.set('conserved_charge', self.conservecharge) 365 366 # If we deal with a Loop model here, the order hierarchy MUST be 367 # defined in the file coupling_orders.py and we import it from 368 # there. 369 all_orders = [] 370 try: 371 all_orders = self.ufomodel.all_orders 372 except AttributeError: 373 if self.perturbation_couplings: 374 raise MadGraph5Error, "The loop model MG5 attemps to import does not specify the attribute 'all_order'." 375 else: 376 pass 377 378 hierarchy={} 379 try: 380 for order in all_orders: 381 hierarchy[order.name]=order.hierarchy 382 except AttributeError: 383 if self.perturbation_couplings: 384 raise MadGraph5Error, 'The loop model MG5 attemps to import does not specify an order hierarchy.' 385 else: 386 pass 387 else: 388 self.model.set('order_hierarchy', hierarchy) 389 390 # Also set expansion_order, i.e., maximum coupling order per process 391 expansion_order={} 392 # And finally the UVCT coupling order counterterms 393 coupling_order_counterterms={} 394 try: 395 for order in all_orders: 396 expansion_order[order.name]=order.expansion_order 397 coupling_order_counterterms[order.name]=order.expansion_order 398 except AttributeError: 399 if self.perturbation_couplings: 400 raise MadGraph5Error, 'The loop model MG5 attemps to import does not specify an expansion_order for all coupling orders.' 401 else: 402 pass 403 else: 404 self.model.set('expansion_order', expansion_order) 405 self.model.set('expansion_order', expansion_order) 406 407 #clean memory 408 del self.checked_lor 409 410 return self.model
411 412
413 - def add_particle(self, particle_info):
414 """ convert and add a particle in the particle list """ 415 416 loop_particles = [[[]]] 417 counterterms = {} 418 419 # MG5 have only one entry for particle and anti particles. 420 #UFO has two. use the color to avoid duplictions 421 pdg = particle_info.pdg_code 422 if pdg in self.incoming or (pdg not in self.outcoming and pdg <0): 423 return 424 425 # MG5 doesn't use ghost for tree models: physical sum on the polarization 426 if not self.perturbation_couplings and particle_info.spin < 0: 427 return 428 429 if (aloha.unitary_gauge and 0 in self.model['gauge']) \ 430 or (1 not in self.model['gauge']): 431 432 # MG5 doesn't use goldstone boson 433 if hasattr(particle_info, 'GoldstoneBoson') and particle_info.GoldstoneBoson: 434 return 435 elif hasattr(particle_info, 'goldstone') and particle_info.goldstone: 436 return 437 # Initialize a particles 438 particle = base_objects.Particle() 439 440 nb_property = 0 #basic check that the UFO information is complete 441 # Loop over the element defining the UFO particles 442 for key,value in particle_info.__dict__.items(): 443 # Check if we use it in the MG5 definition of a particles 444 if key in base_objects.Particle.sorted_keys and not key=='counterterm': 445 nb_property +=1 446 if key in ['name', 'antiname']: 447 if not self.model['case_sensitive']: 448 particle.set(key, value.lower()) 449 else: 450 particle.set(key, value) 451 elif key == 'charge': 452 particle.set(key, float(value)) 453 elif key in ['mass','width']: 454 particle.set(key, str(value)) 455 elif key == 'spin': 456 # MG5 internally treats ghost with positive spin for loop models and 457 # ignore them otherwise 458 particle.set(key,abs(value)) 459 if value<0: 460 particle.set('ghost',True) 461 elif key == 'propagator': 462 if value: 463 if aloha.unitary_gauge: 464 particle.set(key, str(value[0])) 465 else: 466 particle.set(key, str(value[1])) 467 else: 468 particle.set(key, '') 469 else: 470 particle.set(key, value) 471 elif key == 'loop_particles': 472 loop_particles = value 473 elif key == 'counterterm': 474 counterterms = value 475 elif key.lower() not in ('ghostnumber','selfconjugate','goldstone', 476 'goldstoneboson','partial_widths'): 477 # add charge -we will check later if those are conserve 478 self.conservecharge.add(key) 479 particle.set(key,value, force=True) 480 481 if not hasattr(particle_info, 'propagator'): 482 nb_property += 1 483 if particle.get('spin') >= 3: 484 if particle.get('mass').lower() == 'zero': 485 particle.set('propagator', 0) 486 elif particle.get('spin') == 3 and not aloha.unitary_gauge: 487 particle.set('propagator', 0) 488 489 assert(13 == nb_property) #basic check that all the information is there 490 491 # Identify self conjugate particles 492 if particle_info.name == particle_info.antiname: 493 particle.set('self_antipart', True) 494 495 # Proceed only if we deal with a loop model and that this particle 496 # has wavefunction renormalization 497 if not self.perturbation_couplings or counterterms=={}: 498 self.particles.append(particle) 499 return 500 501 # Set here the 'counterterm' attribute to the particle. 502 # First we must change the couplings dictionary keys from the entry format 503 # (order1,order2,...,orderN,loop_particle#):LaurentSerie 504 # two a dictionary with format 505 # ('ORDER_OF_COUNTERTERM',((Particle_list_PDG))):{laurent_order:CTCouplingName} 506 particle_counterterms = {} 507 for key, counterterm in counterterms.items(): 508 # Makes sure this counterterm contributes at one-loop. 509 if len([1 for k in key[:-1] if k==1])==1 and \ 510 not any(k>1 for k in key[:-1]): 511 newParticleCountertermKey=[None,\ 512 # The line below is for loop UFO Model with the 'attribute' 513 # 'loop_particles' of the Particle objects to be defined with 514 # instances of the particle class. The new convention is to use 515 # pdg numbers instead. 516 # tuple([tuple([abs(part.pdg_code) for part in loop_parts]) for\ 517 tuple([tuple(loop_parts) for\ 518 loop_parts in loop_particles[key[-1]]])] 519 for i, order in enumerate(self.ufomodel.all_orders[:-1]): 520 if key[i]==1: 521 newParticleCountertermKey[0]=order.name 522 newCouplingName='UVWfct_'+particle_info.name+'_'+str(key[-1]) 523 particle_counterterms[tuple(newParticleCountertermKey)]=\ 524 dict([(key,newCouplingName+('' if key==0 else '_'+str(-key)+'eps'))\ 525 for key in counterterm]) 526 # We want to create the new coupling for this wavefunction 527 # renormalization. 528 self.ufomodel.object_library.Coupling(\ 529 name = newCouplingName, 530 value = counterterm, 531 order = {newParticleCountertermKey[0]:2}) 532 self.wavefunction_CT_couplings.append(self.ufomodel.all_couplings.pop()) 533 534 particle.set('counterterm',particle_counterterms) 535 self.particles.append(particle) 536 return
537
538 - def add_CTinteraction(self, interaction, color_info):
539 """ Split this interaction in order to call add_interaction for 540 interactions for each element of the loop_particles list. Also it 541 is necessary to unfold here the contributions to the different laurent 542 expansion orders of the couplings.""" 543 544 # Work on a local copy of the interaction provided 545 interaction_info=copy.copy(interaction) 546 547 intType='' 548 if interaction_info.type not in ['UV','UVloop','UVtree','UVmass','R2']: 549 raise MadGraph5Error, 'MG5 only supports the following types of'+\ 550 ' vertices, R2, UV and UVmass. %s is not in this list.'%interaction_info.type 551 else: 552 intType=interaction_info.type 553 # If not specified and simply set to UV, guess the appropriate type 554 if interaction_info.type=='UV': 555 if len(interaction_info.particles)==2 and interaction_info.\ 556 particles[0].name==interaction_info.particles[1].name: 557 intType='UVmass' 558 else: 559 intType='UVloop' 560 561 # Make sure that if it is a UV mass renromalization counterterm it is 562 # defined as such. 563 # if len(intType)>2 and intType[:2]=='UV' and len(interaction_info.particles)==2 \ 564 # and interaction_info.particles[0].name==interaction_info.particles[1].name: 565 # intType='UVmass' 566 567 # Now we create a couplings dictionary for each element of the loop_particles list 568 # and for each expansion order of the laurent serie in the coupling. 569 # Format is new_couplings[loop_particles][laurent_order] and each element 570 # is a couplings dictionary. 571 new_couplings=[[{} for j in range(0,3)] for i in \ 572 range(0,max(1,len(interaction_info.loop_particles)))] 573 # So sort all entries in the couplings dictionary to put them a the 574 # correct place in new_couplings. 575 for key, coupling in interaction_info.couplings.items(): 576 for poleOrder in range(0,3): 577 if coupling.pole(poleOrder)!='ZERO': 578 newCoupling=copy.copy(coupling) 579 if poleOrder!=0: 580 newCoupling.name=newCoupling.name+"_"+str(poleOrder)+"eps" 581 newCoupling.value=coupling.pole(poleOrder) 582 new_couplings[key[2]][poleOrder][(key[0],key[1])] = newCoupling 583 584 # Now we can add an interaction for each. 585 for i, all_couplings in enumerate(new_couplings): 586 loop_particles=[[]] 587 if len(interaction_info.loop_particles)>0: 588 loop_particles=[[part.pdg_code for part in loop_parts] \ 589 for loop_parts in interaction_info.loop_particles[i]] 590 for poleOrder in range(0,3): 591 if all_couplings[poleOrder]!={}: 592 interaction_info.couplings=all_couplings[poleOrder] 593 self.add_interaction(interaction_info, color_info,\ 594 (intType if poleOrder==0 else (intType+str(poleOrder)+\ 595 'eps')),loop_particles)
596 597
598 - def find_color_anti_color_rep(self, output=None):
599 """find which color are in the 3/3bar states""" 600 # method look at the 3 3bar 8 configuration. 601 # If the color is T(3,2,1) and the interaction F1 F2 V 602 # Then set F1 to anticolor (and F2 to color) 603 # if this is T(3,1,2) set the opposite 604 if not output: 605 output = {} 606 607 for interaction_info in self.ufomodel.all_vertices: 608 if len(interaction_info.particles) != 3: 609 continue 610 colors = [abs(p.color) for p in interaction_info.particles] 611 if colors[:2] == [3,3]: 612 if 'T(3,2,1)' in interaction_info.color: 613 color, anticolor, other = interaction_info.particles 614 elif 'T(3,1,2)' in interaction_info.color: 615 anticolor, color, _ = interaction_info.particles 616 elif 'Identity(1,2)' in interaction_info.color or \ 617 'Identity(2,1)' in interaction_info.color: 618 first, second, _ = interaction_info.particles 619 if first.pdg_code in output: 620 if output[first.pdg_code] == 3: 621 color, anticolor = first, second 622 else: 623 color, anticolor = second, first 624 elif second.pdg_code in output: 625 if output[second.pdg_code] == 3: 626 color, anticolor = second, first 627 else: 628 color, anticolor = first, second 629 else: 630 continue 631 else: 632 continue 633 elif colors[1:] == [3,3]: 634 if 'T(1,2,3)' in interaction_info.color: 635 other, anticolor, color = interaction_info.particles 636 elif 'T(1,3,2)' in interaction_info.color: 637 other, color, anticolor = interaction_info.particles 638 elif 'Identity(2,3)' in interaction_info.color or \ 639 'Identity(3,2)' in interaction_info.color: 640 _, first, second = interaction_info.particles 641 if first.pdg_code in output: 642 if output[first.pdg_code] == 3: 643 color, anticolor = first, second 644 else: 645 color, anticolor = second, first 646 elif second.pdg_code in output: 647 if output[second.pdg_code] == 3: 648 color, anticolor = second, first 649 else: 650 color, anticolor = first, second 651 else: 652 continue 653 else: 654 continue 655 656 elif colors.count(3) == 2: 657 if 'T(2,3,1)' in interaction_info.color: 658 color, other, anticolor = interaction_info.particles 659 elif 'T(2,1,3)' in interaction_info.color: 660 anticolor, other, color = interaction_info.particles 661 elif 'Identity(1,3)' in interaction_info.color or \ 662 'Identity(3,1)' in interaction_info.color: 663 first, _, second = interaction_info.particles 664 if first.pdg_code in output: 665 if output[first.pdg_code] == 3: 666 color, anticolor = first, second 667 else: 668 color, anticolor = second, first 669 elif second.pdg_code in output: 670 if output[second.pdg_code] == 3: 671 color, anticolor = second, first 672 else: 673 color, anticolor = first, second 674 else: 675 continue 676 else: 677 continue 678 else: 679 continue 680 681 # Check/assign for the color particle 682 if color.pdg_code in output: 683 if output[color.pdg_code] == -3: 684 raise InvalidModel, 'Particles %s is sometimes in the 3 and sometimes in the 3bar representations' \ 685 % color.name 686 else: 687 output[color.pdg_code] = 3 688 689 # Check/assign for the anticolor particle 690 if anticolor.pdg_code in output: 691 if output[anticolor.pdg_code] == 3: 692 raise InvalidModel, 'Particles %s is sometimes set as in the 3 and sometimes in the 3bar representations' \ 693 % anticolor.name 694 else: 695 output[anticolor.pdg_code] = -3 696 697 return output
698
699 - def detect_incoming_fermion(self):
700 """define which fermion should be incoming 701 for that we look at F F~ X interactions 702 """ 703 self.incoming = [] 704 self.outcoming = [] 705 for interaction_info in self.ufomodel.all_vertices: 706 # check if the interaction meet requirements: 707 pdg = [p.pdg_code for p in interaction_info.particles if p.spin in [2,4]] 708 if len(pdg) % 2: 709 raise InvalidModel, 'Odd number of fermion in vertex: %s' % [p.pdg_code for p in interaction_info.particles] 710 for i in range(0, len(pdg),2): 711 if pdg[i] == - pdg[i+1]: 712 if pdg[i] in self.outcoming: 713 raise InvalidModel, '%s has not coherent incoming/outcoming status between interactions' %\ 714 [p for p in interaction_info.particles if p.spin in [2,4]][i].name 715 716 elif not pdg[i] in self.incoming: 717 self.incoming.append(pdg[i]) 718 self.outcoming.append(pdg[i+1])
719
720 - def add_interaction(self, interaction_info, color_info, type='base', loop_particles=None):
721 """add an interaction in the MG5 model. interaction_info is the 722 UFO vertices information.""" 723 # Import particles content: 724 particles = [self.model.get_particle(particle.pdg_code) \ 725 for particle in interaction_info.particles] 726 if None in particles: 727 # Interaction with a ghost/goldstone 728 return 729 particles = base_objects.ParticleList(particles) 730 731 # Import Lorentz content: 732 lorentz = [helas for helas in interaction_info.lorentz] 733 734 # Check the coherence of the Fermion Flow 735 nb_fermion = sum([ 1 if p.is_fermion() else 0 for p in particles]) 736 try: 737 if nb_fermion == 2: 738 # Fermion Flow is suppose to be dealt by UFO 739 [aloha_fct.check_flow_validity(helas.structure, nb_fermion) \ 740 for helas in interaction_info.lorentz 741 if helas.name not in self.checked_lor] 742 self.checked_lor.update(set([helas.name for helas in interaction_info.lorentz])) 743 elif nb_fermion: 744 if any(p.selfconjugate for p in interaction_info.particles if p.spin % 2 == 0): 745 text = "Majorana can not be dealt in 4/6/... fermion interactions" 746 raise InvalidModel, text 747 except aloha_fct.WrongFermionFlow, error: 748 text = 'Fermion Flow error for interactions %s: %s: %s\n %s' % \ 749 (', '.join([p.name for p in interaction_info.particles]), 750 helas.name, helas.structure, error) 751 raise InvalidModel, text 752 753 754 755 # Now consider the name only 756 lorentz = [helas.name for helas in lorentz] 757 # Import color information: 758 colors = [self.treat_color(color_obj, interaction_info, color_info) 759 for color_obj in interaction_info.color] 760 761 762 order_to_int={} 763 764 for key, couplings in interaction_info.couplings.items(): 765 if not isinstance(couplings, list): 766 couplings = [couplings] 767 if interaction_info.lorentz[key[1]].name not in lorentz: 768 continue 769 # get the sign for the coupling (if we need to adapt the flow) 770 if nb_fermion > 2: 771 flow = aloha_fct.get_fermion_flow(interaction_info.lorentz[key[1]].structure, 772 nb_fermion) 773 coupling_sign = self.get_sign_flow(flow, nb_fermion) 774 else: 775 coupling_sign = '' 776 for coupling in couplings: 777 order = tuple(coupling.order.items()) 778 if '1' in order: 779 raise InvalidModel, '''Some couplings have \'1\' order. 780 This is not allowed in MG. 781 Please defines an additional coupling to your model''' 782 if order in order_to_int: 783 order_to_int[order].get('couplings')[key] = '%s%s' % \ 784 (coupling_sign,coupling.name) 785 else: 786 # Initialize a new interaction with a new id tag 787 interaction = base_objects.Interaction({'id':len(self.interactions)+1}) 788 interaction.set('particles', particles) 789 interaction.set('lorentz', lorentz) 790 interaction.set('couplings', {key: 791 '%s%s' %(coupling_sign,coupling.name)}) 792 interaction.set('orders', coupling.order) 793 interaction.set('color', colors) 794 interaction.set('type', type) 795 interaction.set('loop_particles', loop_particles) 796 order_to_int[order] = interaction 797 # add to the interactions 798 self.interactions.append(interaction) 799 800 # check if this interaction conserve the charge defined 801 # if type=='base': 802 for charge in list(self.conservecharge): #duplicate to allow modification 803 total = 0 804 for part in interaction_info.particles: 805 try: 806 total += getattr(part, charge) 807 except AttributeError: 808 pass 809 if abs(total) > 1e-12: 810 logger.info('The model has interaction violating the charge: %s' % charge) 811 self.conservecharge.discard(charge)
812 813
814 - def get_sign_flow(self, flow, nb_fermion):
815 """ensure that the flow of particles/lorentz are coherent with flow 816 and return a correct version if needed""" 817 818 if not flow or nb_fermion < 4: 819 return '' 820 821 expected = {} 822 for i in range(nb_fermion//2): 823 expected[i+1] = i+2 824 825 if flow == expected: 826 return '' 827 828 switch = {} 829 for i in range(1, nb_fermion+1): 830 if not i in flow: 831 continue 832 switch[i] = len(switch) 833 switch[flow[i]] = len(switch) 834 835 # compute the sign of the permutation 836 sign = 1 837 done = [] 838 839 # make a list of consecutive number which correspond to the new 840 # order of the particles in the new list. 841 new_order = [] 842 for id in range(nb_fermion): # id is the position in the particles order (starts 0) 843 nid = switch[id+1]-1 # nid is the position in the new_particles 844 #order (starts 0) 845 new_order.append(nid) 846 847 # compute the sign: 848 sign =1 849 for k in range(len(new_order)-1): 850 for l in range(k+1,len(new_order)): 851 if new_order[l] < new_order[k]: 852 sign *= -1 853 854 return '' if sign ==1 else '-'
855 856 857 858
859 - def add_lorentz(self, name, spins , expr):
860 """ Add a Lorentz expression which is not present in the UFO """ 861 862 new = self.model['lorentz'][0].__class__(name = name, 863 spins = spins, 864 structure = expr) 865 866 self.model['lorentz'].append(new) 867 self.model.create_lorentz_dict() 868 return name
869 870 _pat_T = re.compile(r'T\((?P<first>\d*),(?P<second>\d*)\)') 871 _pat_id = re.compile(r'Identity\((?P<first>\d*),(?P<second>\d*)\)') 872
873 - def treat_color(self, data_string, interaction_info, color_info):
874 """ convert the string to ColorString""" 875 876 #original = copy.copy(data_string) 877 #data_string = p.sub('color.T(\g<first>,\g<second>)', data_string) 878 879 880 output = [] 881 factor = 1 882 for term in data_string.split('*'): 883 pattern = self._pat_id.search(term) 884 if pattern: 885 particle = interaction_info.particles[int(pattern.group('first'))-1] 886 particle2 = interaction_info.particles[int(pattern.group('second'))-1] 887 if particle.color == particle2.color and particle.color in [-6, 6]: 888 error_msg = 'UFO model have inconsistency in the format:\n' 889 error_msg += 'interactions for particles %s has color information %s\n' 890 error_msg += ' but both fermion are in the same representation %s' 891 raise InvalidModel, error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color) 892 if particle.color == particle2.color and particle.color in [-3, 3]: 893 if particle.pdg_code in color_info and particle2.pdg_code in color_info: 894 if color_info[particle.pdg_code] == color_info[particle2.pdg_code]: 895 error_msg = 'UFO model have inconsistency in the format:\n' 896 error_msg += 'interactions for particles %s has color information %s\n' 897 error_msg += ' but both fermion are in the same representation %s' 898 raise InvalidModel, error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color) 899 elif particle.pdg_code in color_info: 900 color_info[particle2.pdg_code] = -particle.pdg_code 901 elif particle2.pdg_code in color_info: 902 color_info[particle.pdg_code] = -particle2.pdg_code 903 else: 904 error_msg = 'UFO model have inconsistency in the format:\n' 905 error_msg += 'interactions for particles %s has color information %s\n' 906 error_msg += ' but both fermion are in the same representation %s' 907 raise InvalidModel, error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color) 908 909 910 if particle.color == 6: 911 output.append(self._pat_id.sub('color.T6(\g<first>,\g<second>)', term)) 912 elif particle.color == -6 : 913 output.append(self._pat_id.sub('color.T6(\g<second>,\g<first>)', term)) 914 elif particle.color == 8: 915 output.append(self._pat_id.sub('color.Tr(\g<first>,\g<second>)', term)) 916 factor *= 2 917 elif particle.color in [-3,3]: 918 if particle.pdg_code not in color_info: 919 #try to find it one more time 3 -3 1 might help 920 logger.debug('fail to find 3/3bar representation: Retry to find it') 921 color_info = self.find_color_anti_color_rep(color_info) 922 if particle.pdg_code not in color_info: 923 logger.debug('Not able to find the 3/3bar rep from the interactions for particle %s' % particle.name) 924 color_info[particle.pdg_code] = particle.color 925 else: 926 logger.debug('succeed') 927 if particle2.pdg_code not in color_info: 928 #try to find it one more time 3 -3 1 might help 929 logger.debug('fail to find 3/3bar representation: Retry to find it') 930 color_info = self.find_color_anti_color_rep(color_info) 931 if particle2.pdg_code not in color_info: 932 logger.debug('Not able to find the 3/3bar rep from the interactions for particle %s' % particle2.name) 933 color_info[particle2.pdg_code] = particle2.color 934 else: 935 logger.debug('succeed') 936 937 if color_info[particle.pdg_code] == 3 : 938 output.append(self._pat_id.sub('color.T(\g<second>,\g<first>)', term)) 939 elif color_info[particle.pdg_code] == -3: 940 output.append(self._pat_id.sub('color.T(\g<first>,\g<second>)', term)) 941 else: 942 raise MadGraph5Error, \ 943 "Unknown use of Identity for particle with color %d" \ 944 % particle.color 945 else: 946 output.append(term) 947 data_string = '*'.join(output) 948 949 # Change convention for summed indices 950 p = re.compile(r'\'\w(?P<number>\d+)\'') 951 data_string = p.sub('-\g<number>', data_string) 952 953 # Shift indices by -1 954 new_indices = {} 955 new_indices = dict([(j,i) for (i,j) in \ 956 enumerate(range(1, 957 len(interaction_info.particles)+1))]) 958 959 960 output = data_string.split('*') 961 output = color.ColorString([eval(data) \ 962 for data in output if data !='1']) 963 output.coeff = fractions.Fraction(factor) 964 for col_obj in output: 965 col_obj.replace_indices(new_indices) 966 967 return output
968
969 -class OrganizeModelExpression:
970 """Organize the cou plings/parameters of a model""" 971 972 track_dependant = ['aS','aEWM1','MU_R'] # list of variable from which we track 973 #dependencies those variables should be define 974 #as external parameters 975 976 # regular expression to shorten the expressions 977 complex_number = re.compile(r'''complex\((?P<real>[^,\(\)]+),(?P<imag>[^,\(\)]+)\)''') 978 expo_expr = re.compile(r'''(?P<expr>[\w.]+)\s*\*\*\s*(?P<expo>[\d.+-]+)''') 979 cmath_expr = re.compile(r'''cmath.(?P<operation>\w+)\((?P<expr>\w+)\)''') 980 #operation is usualy sqrt / sin / cos / tan 981 conj_expr = re.compile(r'''complexconjugate\((?P<expr>\w+)\)''') 982 983 #RE expression for is_event_dependent 984 separator = re.compile(r'''[+,\-*/()\s]*''') 985
986 - def __init__(self, model):
987 988 self.model = model # UFOMODEL 989 self.perturbation_couplings = {} 990 try: 991 for order in model.all_orders: # Check if it is a loop model or not 992 if(order.perturbative_expansion>0): 993 self.perturbation_couplings[order.name]=order.perturbative_expansion 994 except AttributeError: 995 pass 996 self.params = {} # depend on -> ModelVariable 997 self.couplings = {} # depend on -> ModelVariable 998 self.all_expr = {} # variable_name -> ModelVariable
999
1000 - def main(self, additional_couplings = []):
1001 """Launch the actual computation and return the associate 1002 params/couplings. Possibly consider additional_couplings in addition 1003 to those defined in the UFO model attribute all_couplings """ 1004 1005 self.analyze_parameters() 1006 self.analyze_couplings(additional_couplings = additional_couplings) 1007 return self.params, self.couplings
1008 1009
1010 - def analyze_parameters(self):
1011 """ separate the parameters needed to be recomputed events by events and 1012 the others""" 1013 # in order to match in Gmu scheme 1014 # test whether aEWM1 is the external or not 1015 # if not, take Gf as the track_dependant variable 1016 present_aEWM1 = any(param.name == 'aEWM1' for param in 1017 self.model.all_parameters if param.nature == 'external') 1018 1019 if not present_aEWM1: 1020 self.track_dependant = ['aS','Gf','MU_R'] 1021 1022 for param in self.model.all_parameters: 1023 if param.nature == 'external': 1024 parameter = base_objects.ParamCardVariable(param.name, param.value, \ 1025 param.lhablock, param.lhacode) 1026 1027 else: 1028 expr = self.shorten_expr(param.value) 1029 depend_on = self.find_dependencies(expr) 1030 parameter = base_objects.ModelVariable(param.name, expr, param.type, depend_on) 1031 1032 self.add_parameter(parameter)
1033 1034
1035 - def add_parameter(self, parameter):
1036 """ add consistently the parameter in params and all_expr. 1037 avoid duplication """ 1038 1039 assert isinstance(parameter, base_objects.ModelVariable) 1040 1041 if parameter.name in self.all_expr: 1042 return 1043 1044 self.all_expr[parameter.name] = parameter 1045 try: 1046 self.params[parameter.depend].append(parameter) 1047 except: 1048 self.params[parameter.depend] = [parameter]
1049
1050 - def add_coupling(self, coupling):
1051 """ add consistently the coupling in couplings and all_expr. 1052 avoid duplication """ 1053 1054 assert isinstance(coupling, base_objects.ModelVariable) 1055 1056 if coupling.name in self.all_expr: 1057 return 1058 self.all_expr[coupling.value] = coupling 1059 try: 1060 self.coupling[coupling.depend].append(coupling) 1061 except: 1062 self.coupling[coupling.depend] = [coupling]
1063 1064 1065
1066 - def analyze_couplings(self,additional_couplings=[]):
1067 """creates the shortcut for all special function/parameter 1068 separate the couplings dependent of track variables of the others""" 1069 1070 # First expand the couplings on all their non-zero contribution to the 1071 # three laurent orders 0, -1 and -2. 1072 if self.perturbation_couplings: 1073 couplings_list=[] 1074 for coupling in self.model.all_couplings + additional_couplings: 1075 for poleOrder in range(0,3): 1076 newCoupling=copy.deepcopy(coupling) 1077 if poleOrder!=0: 1078 newCoupling.name=newCoupling.name+"_"+str(poleOrder)+"eps" 1079 if newCoupling.pole(poleOrder)!='ZERO': 1080 newCoupling.value=newCoupling.pole(poleOrder) 1081 couplings_list.append(newCoupling) 1082 else: 1083 couplings_list = self.model.all_couplings + additional_couplings 1084 couplings_list = [c for c in couplings_list if not isinstance(c.value, dict)] 1085 1086 for coupling in couplings_list: 1087 # shorten expression, find dependencies, create short object 1088 expr = self.shorten_expr(coupling.value) 1089 depend_on = self.find_dependencies(expr) 1090 parameter = base_objects.ModelVariable(coupling.name, expr, 'complex', depend_on) 1091 # Add consistently in the couplings/all_expr 1092 try: 1093 self.couplings[depend_on].append(parameter) 1094 except KeyError: 1095 self.couplings[depend_on] = [parameter] 1096 self.all_expr[coupling.value] = parameter
1097
1098 - def find_dependencies(self, expr):
1099 """check if an expression should be evaluated points by points or not 1100 """ 1101 depend_on = set() 1102 1103 # Treat predefined result 1104 #if name in self.track_dependant: 1105 # return tuple() 1106 1107 # Split the different part of the expression in order to say if a 1108 #subexpression is dependent of one of tracked variable 1109 expr = self.separator.split(expr) 1110 1111 # look for each subexpression 1112 for subexpr in expr: 1113 if subexpr in self.track_dependant: 1114 depend_on.add(subexpr) 1115 1116 elif subexpr in self.all_expr and self.all_expr[subexpr].depend: 1117 [depend_on.add(value) for value in self.all_expr[subexpr].depend 1118 if self.all_expr[subexpr].depend != ('external',)] 1119 if depend_on: 1120 return tuple(depend_on) 1121 else: 1122 return tuple()
1123 1124
1125 - def shorten_expr(self, expr):
1126 """ apply the rules of contraction and fullfill 1127 self.params with dependent part""" 1128 try: 1129 expr = self.complex_number.sub(self.shorten_complex, expr) 1130 expr = self.expo_expr.sub(self.shorten_expo, expr) 1131 expr = self.cmath_expr.sub(self.shorten_cmath, expr) 1132 expr = self.conj_expr.sub(self.shorten_conjugate, expr) 1133 except Exception: 1134 logger.critical("fail to handle expression: %s, type()=%s", expr,type(expr)) 1135 raise 1136 return expr
1137 1138
1139 - def shorten_complex(self, matchobj):
1140 """add the short expression, and return the nice string associate""" 1141 1142 float_real = float(eval(matchobj.group('real'))) 1143 float_imag = float(eval(matchobj.group('imag'))) 1144 if float_real == 0 and float_imag ==1: 1145 new_param = base_objects.ModelVariable('complexi', 'complex(0,1)', 'complex') 1146 self.add_parameter(new_param) 1147 return 'complexi' 1148 else: 1149 return 'complex(%s, %s)' % (matchobj.group('real'), matchobj.group('imag'))
1150 1151
1152 - def shorten_expo(self, matchobj):
1153 """add the short expression, and return the nice string associate""" 1154 1155 expr = matchobj.group('expr') 1156 exponent = matchobj.group('expo') 1157 new_exponent = exponent.replace('.','_').replace('+','').replace('-','_m_') 1158 output = '%s__exp__%s' % (expr, new_exponent) 1159 old_expr = '%s**%s' % (expr,exponent) 1160 1161 if expr.startswith('cmath'): 1162 return old_expr 1163 1164 if expr.isdigit(): 1165 output = 'nb__' + output #prevent to start with a number 1166 new_param = base_objects.ModelVariable(output, old_expr,'real') 1167 else: 1168 depend_on = self.find_dependencies(expr) 1169 type = self.search_type(expr) 1170 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1171 self.add_parameter(new_param) 1172 return output
1173
1174 - def shorten_cmath(self, matchobj):
1175 """add the short expression, and return the nice string associate""" 1176 1177 expr = matchobj.group('expr') 1178 operation = matchobj.group('operation') 1179 output = '%s__%s' % (operation, expr) 1180 old_expr = ' cmath.%s(%s) ' % (operation, expr) 1181 if expr.isdigit(): 1182 new_param = base_objects.ModelVariable(output, old_expr , 'real') 1183 else: 1184 depend_on = self.find_dependencies(expr) 1185 type = self.search_type(expr) 1186 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1187 self.add_parameter(new_param) 1188 1189 return output
1190
1191 - def shorten_conjugate(self, matchobj):
1192 """add the short expression, and retrun the nice string associate""" 1193 1194 expr = matchobj.group('expr') 1195 output = 'conjg__%s' % (expr) 1196 old_expr = ' complexconjugate(%s) ' % expr 1197 depend_on = self.find_dependencies(expr) 1198 type = 'complex' 1199 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1200 self.add_parameter(new_param) 1201 1202 return output
1203 1204 1205
1206 - def search_type(self, expr, dep=''):
1207 """return the type associate to the expression if define""" 1208 1209 try: 1210 return self.all_expr[expr].type 1211 except: 1212 return 'complex'
1213
1214 -class RestrictModel(model_reader.ModelReader):
1215 """ A class for restricting a model for a given param_card. 1216 rules applied: 1217 - Vertex with zero couplings are throw away 1218 - external parameter with zero/one input are changed into internal parameter. 1219 - identical coupling/mass/width are replace in the model by a unique one 1220 """ 1221
1222 - def default_setup(self):
1223 """define default value""" 1224 self.del_coup = [] 1225 super(RestrictModel, self).default_setup() 1226 self.rule_card = check_param_card.ParamCardRule() 1227 self.restrict_card = None
1228
1229 - def restrict_model(self, param_card, rm_parameter=True, keep_external=False):
1230 """apply the model restriction following param_card. 1231 rm_parameter defines if the Zero/one parameter are removed or not from 1232 the model. 1233 keep_external if the param_card need to be kept intact 1234 """ 1235 if self.get('name') == "mssm" and not keep_external: 1236 raise Exception 1237 self.restrict_card = param_card 1238 # Reset particle dict to ensure synchronized particles and interactions 1239 self.set('particles', self.get('particles')) 1240 1241 # compute the value of all parameters 1242 self.set_parameters_and_couplings(param_card) 1243 # associate to each couplings the associated vertex: def self.coupling_pos 1244 self.locate_coupling() 1245 # deal with couplings 1246 zero_couplings, iden_couplings = self.detect_identical_couplings() 1247 1248 # remove the out-dated interactions 1249 self.remove_interactions(zero_couplings) 1250 1251 # replace in interactions identical couplings 1252 for iden_coups in iden_couplings: 1253 self.merge_iden_couplings(iden_coups) 1254 1255 # remove zero couplings and other pointless couplings 1256 self.del_coup += zero_couplings 1257 self.remove_couplings(self.del_coup) 1258 1259 # deal with parameters 1260 parameters = self.detect_special_parameters() 1261 self.fix_parameter_values(*parameters, simplify=rm_parameter, 1262 keep_external=keep_external) 1263 1264 # deal with identical parameters 1265 if not keep_external: 1266 iden_parameters = self.detect_identical_parameters() 1267 for iden_param in iden_parameters: 1268 self.merge_iden_parameters(iden_param) 1269 1270 iden_parameters = self.detect_identical_parameters() 1271 for iden_param in iden_parameters: 1272 self.merge_iden_parameters(iden_param, keep_external) 1273 1274 # change value of default parameter if they have special value: 1275 # 9.999999e-1 -> 1.0 1276 # 0.000001e-99 -> 0 Those value are used to avoid restriction 1277 for name, value in self['parameter_dict'].items(): 1278 if value == 9.999999e-1: 1279 self['parameter_dict'][name] = 1 1280 elif value == 0.000001e-99: 1281 self['parameter_dict'][name] = 0
1282 1283
1284 - def locate_coupling(self):
1285 """ create a dict couplings_name -> vertex or (particle, counterterm_key) """ 1286 1287 self.coupling_pos = {} 1288 for vertex in self['interactions']: 1289 for key, coupling in vertex['couplings'].items(): 1290 if coupling in self.coupling_pos: 1291 if vertex not in self.coupling_pos[coupling]: 1292 self.coupling_pos[coupling].append(vertex) 1293 else: 1294 self.coupling_pos[coupling] = [vertex] 1295 1296 for particle in self['particles']: 1297 for key, coupling_dict in particle['counterterm'].items(): 1298 for LaurentOrder, coupling in coupling_dict.items(): 1299 if coupling in self.coupling_pos: 1300 if (particle,key) not in self.coupling_pos[coupling]: 1301 self.coupling_pos[coupling].append((particle,key)) 1302 else: 1303 self.coupling_pos[coupling] = [(particle,key)] 1304 1305 return self.coupling_pos
1306
1307 - def detect_identical_couplings(self, strict_zero=False):
1308 """return a list with the name of all vanishing couplings""" 1309 1310 dict_value_coupling = {} 1311 iden_key = set() 1312 zero_coupling = [] 1313 iden_coupling = [] 1314 1315 for name, value in self['coupling_dict'].items(): 1316 if value == 0: 1317 zero_coupling.append(name) 1318 continue 1319 elif not strict_zero and abs(value) < 1e-13: 1320 logger.debug('coupling with small value %s: %s treated as zero' % 1321 (name, value)) 1322 zero_coupling.append(name) 1323 elif not strict_zero and abs(value) < 1e-10: 1324 return self.detect_identical_couplings(strict_zero=True) 1325 1326 1327 if value in dict_value_coupling: 1328 iden_key.add(value) 1329 dict_value_coupling[value].append(name) 1330 else: 1331 dict_value_coupling[value] = [name] 1332 1333 for key in iden_key: 1334 iden_coupling.append(dict_value_coupling[key]) 1335 1336 return zero_coupling, iden_coupling
1337 1338
1339 - def detect_special_parameters(self):
1340 """ return the list of (name of) parameter which are zero """ 1341 1342 null_parameters = [] 1343 one_parameters = [] 1344 for name, value in self['parameter_dict'].items(): 1345 if value == 0 and name != 'ZERO': 1346 null_parameters.append(name) 1347 elif value == 1: 1348 one_parameters.append(name) 1349 1350 return null_parameters, one_parameters
1351
1353 """ return the list of tuple of name of parameter with the same 1354 input value """ 1355 1356 # Extract external parameters 1357 external_parameters = self['parameters'][('external',)] 1358 1359 # define usefull variable to detect identical input 1360 block_value_to_var={} #(lhablok, value): list_of_var 1361 mult_param = set([]) # key of the previous dict with more than one 1362 #parameter. 1363 1364 #detect identical parameter and remove the duplicate parameter 1365 for param in external_parameters[:]: 1366 value = self['parameter_dict'][param.name] 1367 if value in [0,1,0.000001e-99,9.999999e-1]: 1368 continue 1369 if param.lhablock.lower() == 'decay': 1370 continue 1371 1372 key = (param.lhablock, value) 1373 mkey = (param.lhablock, -value) 1374 if key in block_value_to_var: 1375 block_value_to_var[key].append((param,1)) 1376 mult_param.add(key) 1377 elif mkey in block_value_to_var: 1378 block_value_to_var[mkey].append((param,-1)) 1379 mult_param.add(mkey) 1380 else: 1381 block_value_to_var[key] = [(param,1)] 1382 1383 output=[] 1384 for key in mult_param: 1385 output.append(block_value_to_var[key]) 1386 1387 return output
1388 1389
1390 - def merge_iden_couplings(self, couplings):
1391 """merge the identical couplings in the interactions and particle 1392 counterterms""" 1393 1394 1395 logger_mod.debug(' Fuse the Following coupling (they have the same value): %s '% \ 1396 ', '.join([obj for obj in couplings])) 1397 1398 main = couplings[0] 1399 self.del_coup += couplings[1:] # add the other coupl to the suppress list 1400 1401 for coupling in couplings[1:]: 1402 # check if param is linked to an interaction 1403 if coupling not in self.coupling_pos: 1404 continue 1405 # replace the coupling, by checking all coupling of the interaction 1406 vertices = [ vert for vert in self.coupling_pos[coupling] if 1407 isinstance(vert, base_objects.Interaction)] 1408 for vertex in vertices: 1409 for key, value in vertex['couplings'].items(): 1410 if value == coupling: 1411 vertex['couplings'][key] = main 1412 1413 # replace the coupling appearing in the particle counterterm 1414 particles_ct = [ pct for pct in self.coupling_pos[coupling] if 1415 isinstance(pct, tuple)] 1416 for pct in particles_ct: 1417 for key, value in pct[0]['counterterm'][pct[1]].items(): 1418 if value == coupling: 1419 pct[0]['counterterm'][pct[1]][key] = main
1420 1421
1422 - def merge_iden_parameters(self, parameters, keep_external=False):
1423 """ merge the identical parameters given in argument. 1424 keep external force to keep the param_card untouched (up to comment)""" 1425 1426 logger_mod.debug('Parameters set to identical values: %s '% \ 1427 ', '.join(['%s*%s' % (f, obj.name.replace('mdl_','')) for (obj,f) in parameters])) 1428 1429 # Extract external parameters 1430 external_parameters = self['parameters'][('external',)] 1431 for i, (obj, factor) in enumerate(parameters): 1432 # Keeped intact the first one and store information 1433 if i == 0: 1434 obj.info = 'set of param :' + \ 1435 ', '.join([str(f)+'*'+param.name.replace('mdl_','') 1436 for (param, f) in parameters]) 1437 expr = obj.name 1438 continue 1439 # Add a Rule linked to the param_card 1440 if factor ==1: 1441 self.rule_card.add_identical(obj.lhablock.lower(), obj.lhacode, 1442 parameters[0][0].lhacode ) 1443 else: 1444 self.rule_card.add_opposite(obj.lhablock.lower(), obj.lhacode, 1445 parameters[0][0].lhacode ) 1446 obj_name = obj.name 1447 # delete the old parameters 1448 if not keep_external: 1449 external_parameters.remove(obj) 1450 elif obj.lhablock.upper() in ['MASS','DECAY']: 1451 external_parameters.remove(obj) 1452 else: 1453 obj.name = '' 1454 obj.info = 'MG5 will not use this value use instead %s*%s' %(factor,expr) 1455 # replace by the new one pointing of the first obj of the class 1456 new_param = base_objects.ModelVariable(obj_name, '%s*%s' %(factor, expr), 'real') 1457 self['parameters'][()].insert(0, new_param) 1458 1459 # For Mass-Width, we need also to replace the mass-width in the particles 1460 #This allows some optimization for multi-process. 1461 if parameters[0][0].lhablock in ['MASS','DECAY']: 1462 new_name = parameters[0][0].name 1463 if parameters[0][0].lhablock == 'MASS': 1464 arg = 'mass' 1465 else: 1466 arg = 'width' 1467 change_name = [p.name for (p,f) in parameters[1:]] 1468 [p.set(arg, new_name) for p in self['particle_dict'].values() 1469 if p[arg] in change_name]
1470
1471 - def remove_interactions(self, zero_couplings):
1472 """ remove the interactions and particle counterterms 1473 associated to couplings""" 1474 1475 1476 mod_vertex = [] 1477 mod_particle_ct = [] 1478 for coup in zero_couplings: 1479 # some coupling might be not related to any interactions 1480 if coup not in self.coupling_pos: 1481 continue 1482 1483 # Remove the corresponding interactions. 1484 1485 vertices = [ vert for vert in self.coupling_pos[coup] if 1486 isinstance(vert, base_objects.Interaction) ] 1487 for vertex in vertices: 1488 modify = False 1489 for key, coupling in vertex['couplings'].items(): 1490 if coupling in zero_couplings: 1491 modify=True 1492 del vertex['couplings'][key] 1493 if modify: 1494 mod_vertex.append(vertex) 1495 1496 # Remove the corresponding particle counterterm 1497 particles_ct = [ pct for pct in self.coupling_pos[coup] if 1498 isinstance(pct, tuple)] 1499 for pct in particles_ct: 1500 modify = False 1501 for key, coupling in pct[0]['counterterm'][pct[1]].items(): 1502 if coupling in zero_couplings: 1503 modify=True 1504 del pct[0]['counterterm'][pct[1]][key] 1505 if modify: 1506 mod_particle_ct.append(pct) 1507 1508 # print useful log and clean the empty interaction 1509 for vertex in mod_vertex: 1510 part_name = [part['name'] for part in vertex['particles']] 1511 orders = ['%s=%s' % (order,value) for order,value in vertex['orders'].items()] 1512 1513 if not vertex['couplings']: 1514 logger_mod.debug('remove interactions: %s at order: %s' % \ 1515 (' '.join(part_name),', '.join(orders))) 1516 self['interactions'].remove(vertex) 1517 else: 1518 logger_mod.debug('modify interactions: %s at order: %s' % \ 1519 (' '.join(part_name),', '.join(orders))) 1520 1521 # print useful log and clean the empty counterterm values 1522 for pct in mod_particle_ct: 1523 part_name = pct[0]['name'] 1524 order = pct[1][0] 1525 loop_parts = ','.join(['('+','.join([\ 1526 self.get_particle(p)['name'] for p in part])+')' \ 1527 for part in pct[1][1]]) 1528 1529 if not pct[0]['counterterm'][pct[1]]: 1530 logger_mod.debug('remove counterterm of particle %s'%part_name+\ 1531 ' with loop particles (%s)'%loop_parts+\ 1532 ' perturbing order %s'%order) 1533 del pct[0]['counterterm'][pct[1]] 1534 else: 1535 logger_mod.debug('Modify counterterm of particle %s'%part_name+\ 1536 ' with loop particles (%s)'%loop_parts+\ 1537 ' perturbing order %s'%order) 1538 1539 return
1540
1541 - def remove_couplings(self, couplings):
1542 #clean the coupling list: 1543 for name, data in self['couplings'].items(): 1544 for coupling in data[:]: 1545 if coupling.name in couplings: 1546 data.remove(coupling)
1547 1548
1549 - def fix_parameter_values(self, zero_parameters, one_parameters, 1550 simplify=True, keep_external=False):
1551 """ Remove all instance of the parameters in the model and replace it by 1552 zero when needed.""" 1553 1554 1555 # treat specific cases for masses and width 1556 for particle in self['particles']: 1557 if particle['mass'] in zero_parameters: 1558 particle['mass'] = 'ZERO' 1559 if particle['width'] in zero_parameters: 1560 particle['width'] = 'ZERO' 1561 if particle['width'] in one_parameters: 1562 one_parameters.remove(particle['width']) 1563 1564 for pdg, particle in self['particle_dict'].items(): 1565 if particle['mass'] in zero_parameters: 1566 particle['mass'] = 'ZERO' 1567 if particle['width'] in zero_parameters: 1568 particle['width'] = 'ZERO' 1569 1570 1571 # Add a rule for zero/one parameter 1572 external_parameters = self['parameters'][('external',)] 1573 for param in external_parameters[:]: 1574 value = self['parameter_dict'][param.name] 1575 block = param.lhablock.lower() 1576 if value == 0: 1577 self.rule_card.add_zero(block, param.lhacode) 1578 elif value == 1: 1579 self.rule_card.add_one(block, param.lhacode) 1580 1581 special_parameters = zero_parameters + one_parameters 1582 1583 1584 1585 if simplify: 1586 # check if the parameters is still usefull: 1587 re_str = '|'.join(special_parameters) 1588 if len(re_str) > 25000: # size limit on mac 1589 split = len(special_parameters) // 2 1590 re_str = ['|'.join(special_parameters[:split]), 1591 '|'.join(special_parameters[split:])] 1592 else: 1593 re_str = [ re_str ] 1594 used = set() 1595 for expr in re_str: 1596 re_pat = re.compile(r'''\b(%s)\b''' % expr) 1597 # check in coupling 1598 for name, coupling_list in self['couplings'].items(): 1599 for coupling in coupling_list: 1600 for use in re_pat.findall(coupling.expr): 1601 used.add(use) 1602 else: 1603 used = set([i for i in special_parameters if i]) 1604 1605 # simplify the regular expression 1606 re_str = '|'.join([param for param in special_parameters if param not in used]) 1607 if len(re_str) > 25000: # size limit on mac 1608 split = len(special_parameters) // 2 1609 re_str = ['|'.join(special_parameters[:split]), 1610 '|'.join(special_parameters[split:])] 1611 else: 1612 re_str = [ re_str ] 1613 for expr in re_str: 1614 re_pat = re.compile(r'''\b(%s)\b''' % expr) 1615 1616 param_info = {} 1617 # check in parameters 1618 for dep, param_list in self['parameters'].items(): 1619 for tag, parameter in enumerate(param_list): 1620 # update information concerning zero/one parameters 1621 if parameter.name in special_parameters: 1622 param_info[parameter.name]= {'dep': dep, 'tag': tag, 1623 'obj': parameter} 1624 continue 1625 1626 # Bypass all external parameter 1627 if isinstance(parameter, base_objects.ParamCardVariable): 1628 continue 1629 1630 if simplify: 1631 for use in re_pat.findall(parameter.expr): 1632 used.add(use) 1633 1634 # modify the object for those which are still used 1635 for param in used: 1636 if not param: 1637 continue 1638 data = self['parameters'][param_info[param]['dep']] 1639 data.remove(param_info[param]['obj']) 1640 tag = param_info[param]['tag'] 1641 data = self['parameters'][()] 1642 if param in zero_parameters: 1643 data.insert(0, base_objects.ModelVariable(param, '0.0', 'real')) 1644 else: 1645 data.insert(0, base_objects.ModelVariable(param, '1.0', 'real')) 1646 1647 # remove completely useless parameters 1648 for param in special_parameters: 1649 #by pass parameter still in use 1650 if param in used or \ 1651 (keep_external and param_info[param]['dep'] == ('external',)): 1652 logger_mod.debug('fix parameter value: %s' % param) 1653 continue 1654 logger_mod.debug('remove parameters: %s' % (param)) 1655 data = self['parameters'][param_info[param]['dep']] 1656 data.remove(param_info[param]['obj'])
1657