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