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