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  from __future__ import absolute_import 
  18  import collections 
  19  import fractions 
  20  import logging 
  21  import math 
  22  import os 
  23  import re 
  24  import sys 
  25  import time 
  26  import collections 
  27   
  28  import madgraph 
  29  from madgraph import MadGraph5Error, MG5DIR, ReadWrite 
  30  import madgraph.core.base_objects as base_objects 
  31  import madgraph.loop.loop_base_objects as loop_base_objects 
  32  import madgraph.core.color_algebra as color 
  33  import madgraph.iolibs.files as files 
  34  import madgraph.iolibs.save_load_object as save_load_object 
  35  from madgraph.core.color_algebra import * 
  36  import madgraph.various.misc as misc 
  37  import madgraph.iolibs.ufo_expression_parsers as parsers 
  38   
  39  import aloha 
  40  import aloha.create_aloha as create_aloha 
  41  import aloha.aloha_fct as aloha_fct 
  42   
  43  import models as ufomodels 
  44  import models.model_reader as model_reader 
  45  import six 
  46  from six.moves import range 
  47  from six.moves import zip 
  48  logger = logging.getLogger('madgraph.model') 
  49  logger_mod = logging.getLogger('madgraph.model') 
  50   
  51  root_path = os.path.dirname(os.path.realpath( __file__ )) 
  52  sys.path.append(root_path) 
  53   
  54  sys.path.append(os.path.join(root_path, os.path.pardir, 'Template', 'bin', 'internal')) 
  55  from . import check_param_card  
  56   
  57  pjoin = os.path.join 
  58   
  59  # Suffixes to employ for the various poles of CTparameters 
  60  pole_dict = {-2:'2EPS',-1:'1EPS',0:'FIN'} 
61 62 -class UFOImportError(MadGraph5Error):
63 """ a error class for wrong import of UFO model"""
64
65 -class InvalidModel(MadGraph5Error):
66 """ a class for invalid Model """
67 68 last_model_path =''
69 -def find_ufo_path(model_name, web_search=True):
70 """ find the path to a model """ 71 72 global last_model_path 73 74 # Check for a valid directory 75 if model_name.startswith(('./','../')) and os.path.isdir(model_name): 76 return model_name 77 elif os.path.isdir(os.path.join(MG5DIR, 'models', model_name)): 78 return os.path.join(MG5DIR, 'models', model_name) 79 elif 'PYTHONPATH' in os.environ: 80 for p in os.environ['PYTHONPATH'].split(':'): 81 if os.path.isdir(os.path.join(MG5DIR, p, model_name)): 82 if last_model_path != os.path.join(MG5DIR, p, model_name): 83 logger.info("model loaded from PYTHONPATH: %s", os.path.join(MG5DIR, p, model_name)) 84 last_model_path = os.path.join(MG5DIR, p, model_name) 85 return os.path.join(MG5DIR, p, model_name) 86 if os.path.isdir(model_name): 87 logger.warning('No model %s found in default path. Did you mean \'import model ./%s\'', 88 model_name, model_name) 89 if os.path.sep in model_name: 90 raise UFOImportError("Path %s is not a valid pathname" % model_name) 91 elif web_search and '-' not in model_name: 92 found = import_model_from_db(model_name) 93 if found: 94 return find_ufo_path(model_name, web_search=False) 95 else: 96 raise UFOImportError("Path %s is not a valid pathname" % model_name) 97 else: 98 raise UFOImportError("Path %s is not a valid pathname" % model_name) 99 100 raise UFOImportError("Path %s is not a valid pathname" % model_name) 101 return
102
103 104 -def get_model_db():
105 """return the file with the online model database""" 106 107 data_path = ['http://madgraph.phys.ucl.ac.be/models_db.dat', 108 'http://madgraph.physics.illinois.edu/models_db.dat'] 109 import random 110 import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error 111 r = random.randint(0,1) 112 r = [r, (1-r)] 113 114 if 'MG5aMC_WWW' in os.environ and os.environ['MG5aMC_WWW']: 115 data_path.append(os.environ['MG5aMC_WWW']+'/models_db.dat') 116 r.insert(0, 2) 117 118 119 for index in r: 120 cluster_path = data_path[index] 121 try: 122 data = six.moves.urllib.request.urlopen(cluster_path) 123 except Exception: 124 continue 125 if data.getcode() != 200: 126 continue 127 break 128 else: 129 raise MadGraph5Error('''Model not found locally and Impossible to connect any of us servers. 130 Please check your internet connection or retry later''') 131 132 return data
133
134 -def import_model_from_db(model_name, local_dir=False):
135 """ import the model with a given name """ 136 137 if os.path.sep in model_name and os.path.exists(os.path.dirname(model_name)): 138 target = os.path.dirname(model_name) 139 model_name = os.path.basename(model_name) 140 else: 141 target = None 142 data =get_model_db() 143 link = None 144 for line in data: 145 split = line.decode().split() 146 if model_name == split[0]: 147 link = split[1] 148 break 149 else: 150 logger.debug('no model with that name (%s) found online', model_name) 151 return False 152 153 #get target directory 154 # 1. PYTHONPATH containing UFO --only for omattelaer user 155 # 2. models directory 156 157 username = '' 158 if not target: 159 try: 160 import pwd 161 username =pwd.getpwuid( os.getuid() )[ 0 ] 162 except Exception as error: 163 misc.sprint(str(error)) 164 username = '' 165 if username in ['omatt', 'mattelaer', 'olivier', 'omattelaer'] and target is None and \ 166 'PYTHONPATH' in os.environ and not local_dir: 167 for directory in os.environ['PYTHONPATH'].split(':'): 168 #condition only for my setup --ATLAS did not like it 169 if 'UFOMODEL' == os.path.basename(directory) and os.path.exists(directory) and\ 170 misc.glob('*/couplings.py', path=directory) and 'matt' in directory: 171 target= directory 172 173 if target is None: 174 target = pjoin(MG5DIR, 'models') 175 try: 176 os.remove(pjoin(target, 'tmp.tgz')) 177 except Exception: 178 pass 179 logger.info("download model from %s to the following directory: %s", link, target, '$MG:color:BLACK') 180 misc.wget(link, 'tmp.tgz', cwd=target) 181 182 #untar the file. 183 # .tgz 184 if link.endswith(('.tgz','.tar.gz','.tar')): 185 try: 186 proc = misc.call('tar -xzpvf tmp.tgz', shell=True, cwd=target)#, stdout=devnull, stderr=devnull) 187 if proc: raise Exception 188 except: 189 proc = misc.call('tar -xpvf tmp.tgz', shell=True, cwd=target)#, stdout=devnull, stderr=devnull) 190 # .zip 191 if link.endswith(('.zip')): 192 try: 193 proc = misc.call('unzip tmp.tgz', shell=True, cwd=target)#, stdout=devnull, stderr=devnull) 194 if proc: raise Exception 195 except: 196 proc = misc.call('tar -xzpvf tmp.tgz', shell=True, cwd=target)#, stdout=devnull, stderr=devnull) 197 if proc: 198 raise Exception("Impossible to unpack the model. Please install it manually") 199 return True
200
201 -def get_path_restrict(model_name, restrict=True):
202 # check if this is a valid path or if this include restriction file 203 try: 204 model_path = find_ufo_path(model_name) 205 except UFOImportError: 206 if '-' not in model_name: 207 if model_name == "mssm": 208 logger.error("mssm model has been replaced by MSSM_SLHA2 model.\n The new model require SLHA2 format. You can use the \"update to_slha2\" command to convert your slha1 param_card.\n That option is available at the time of the edition of the cards.") 209 raise 210 split = model_name.split('-') 211 model_name = '-'.join([text for text in split[:-1]]) 212 try: 213 model_path = find_ufo_path(model_name) 214 except UFOImportError: 215 if model_name == "mssm": 216 logger.error("mssm model has been replaced by MSSM_SLHA2 model.\n The new model require SLHA2 format. You can use the \"update to_slha2\" command to convert your slha1 param_card.\n That option is available at the time of the edition of the cards.") 217 raise 218 restrict_name = split[-1] 219 220 restrict_file = os.path.join(model_path, 'restrict_%s.dat'% restrict_name) 221 222 #if restriction is full, then we by pass restriction (avoid default) 223 if split[-1] == 'full': 224 restrict_file = None 225 else: 226 # Check if by default we need some restrictions 227 restrict_name = "" 228 if restrict and os.path.exists(os.path.join(model_path,'restrict_default.dat')): 229 restrict_file = os.path.join(model_path,'restrict_default.dat') 230 else: 231 restrict_file = None 232 233 if isinstance(restrict, str): 234 if os.path.exists(os.path.join(model_path, restrict)): 235 restrict_file = os.path.join(model_path, restrict) 236 elif os.path.exists(restrict): 237 restrict_file = restrict 238 else: 239 raise Exception("%s is not a valid path for restrict file" % restrict) 240 241 return model_path, restrict_file, restrict_name
242
243 -def import_model(model_name, decay=False, restrict=True, prefix='mdl_', 244 complex_mass_scheme = None):
245 """ a practical and efficient way to import a model""" 246 247 model_path, restrict_file, restrict_name = get_path_restrict(model_name, restrict) 248 249 #import the FULL model 250 model = import_full_model(model_path, decay, prefix) 251 252 if os.path.exists(pjoin(model_path, "README")): 253 logger.info("Please read carefully the README of the model file for instructions/restrictions of the model.",'$MG:color:BLACK') 254 # restore the model name 255 if restrict_name: 256 model["name"] += '-' + restrict_name 257 258 # Decide whether complex mass scheme is on or not 259 useCMS = (complex_mass_scheme is None and aloha.complex_mass) or \ 260 complex_mass_scheme==True 261 #restrict it if needed 262 if restrict_file: 263 try: 264 logger.info('Restrict model %s with file %s .' % (model_name, os.path.relpath(restrict_file))) 265 except OSError: 266 # sometimes has trouble with relative path 267 logger.info('Restrict model %s with file %s .' % (model_name, restrict_file)) 268 269 if logger_mod.getEffectiveLevel() > 10: 270 logger.info('Run \"set stdout_level DEBUG\" before import for more information.') 271 # Modify the mother class of the object in order to allow restriction 272 model = RestrictModel(model) 273 274 # Change to complex mass scheme if necessary. This must be done BEFORE 275 # the restriction. 276 if useCMS: 277 # We read the param_card a first time so that the function 278 # change_mass_to_complex_scheme can know if a particle is to 279 # be considered massive or not and with zero width or not. 280 # So we read the restrict card a first time, with the CMS set to 281 # False because we haven't changed the model yet. 282 model.set_parameters_and_couplings(param_card = restrict_file, 283 complex_mass_scheme=False) 284 model.change_mass_to_complex_scheme(toCMS=True) 285 else: 286 # Make sure that the parameter 'CMSParam' of the model is set to 0.0 287 # as it should in order to have the correct NWA renormalization condition. 288 # It might be that the default of the model is CMS. 289 model.change_mass_to_complex_scheme(toCMS=False) 290 291 blocks = model.get_param_block() 292 if model_name == 'mssm' or os.path.basename(model_name) == 'mssm': 293 keep_external=True 294 elif all( b in blocks for b in ['USQMIX', 'SL2', 'MSOFT', 'YE', 'NMIX', 'TU','MSE2','UPMNS']): 295 keep_external=True 296 elif model_name == 'MSSM_SLHA2' or os.path.basename(model_name) == 'MSSM_SLHA2': 297 keep_external=True 298 else: 299 keep_external=False 300 if keep_external: 301 logger.info('Detect SLHA2 format. keeping restricted parameter in the param_card') 302 303 model.restrict_model(restrict_file, rm_parameter=not decay, 304 keep_external=keep_external, complex_mass_scheme=complex_mass_scheme) 305 model.path = model_path 306 else: 307 # Change to complex mass scheme if necessary 308 if useCMS: 309 model.change_mass_to_complex_scheme(toCMS=True) 310 else: 311 # It might be that the default of the model (i.e. 'CMSParam') is CMS. 312 model.change_mass_to_complex_scheme(toCMS=False) 313 314 return model
315 316 317 _import_once = []
318 -def import_full_model(model_path, decay=False, prefix=''):
319 """ a practical and efficient way to import one of those models 320 (no restriction file use)""" 321 322 assert model_path == find_ufo_path(model_path) 323 324 if prefix is True: 325 prefix='mdl_' 326 327 # Check the validity of the model 328 files_list_prov = ['couplings.py','lorentz.py','parameters.py', 329 'particles.py', 'vertices.py', 'function_library.py', 330 'propagators.py', 'coupling_orders.py'] 331 332 if decay: 333 files_list_prov.append('decays.py') 334 335 files_list = [] 336 for filename in files_list_prov: 337 filepath = os.path.join(model_path, filename) 338 if not os.path.isfile(filepath): 339 if filename not in ['propagators.py', 'decays.py', 'coupling_orders.py']: 340 raise UFOImportError("%s directory is not a valid UFO model: \n %s is missing" % \ 341 (model_path, filename)) 342 files_list.append(filepath) 343 # use pickle files if defined and up-to-date 344 if aloha.unitary_gauge: 345 pickle_name = 'model.pkl' 346 else: 347 pickle_name = 'model_Feynman.pkl' 348 if decay: 349 pickle_name = 'dec_%s' % pickle_name 350 if six.PY3: 351 pickle_name = 'py3_%s' % pickle_name 352 353 allow_reload = False 354 if files.is_uptodate(os.path.join(model_path, pickle_name), files_list): 355 allow_reload = True 356 try: 357 model = save_load_object.load_from_file( \ 358 os.path.join(model_path, pickle_name)) 359 except Exception as error: 360 logger.info('failed to load model from pickle file. Try importing UFO from File') 361 else: 362 # We don't care about the restrict_card for this comparison 363 if 'version_tag' in model and not model.get('version_tag') is None and \ 364 model.get('version_tag').startswith(os.path.realpath(model_path)) and \ 365 model.get('version_tag').endswith('##' + str(misc.get_pkg_info())): 366 #check if the prefix is correct one. 367 for key in model.get('parameters'): 368 for param in model['parameters'][key]: 369 value = param.name.lower() 370 if value in ['as','mu_r', 'zero','aewm1']: 371 continue 372 if prefix: 373 if value.startswith(prefix): 374 _import_once.append((model_path, aloha.unitary_gauge, prefix, decay)) 375 return model 376 else: 377 logger.info('reload from .py file') 378 break 379 else: 380 if value.startswith('mdl_'): 381 logger.info('reload from .py file') 382 break 383 else: 384 _import_once.append((model_path, aloha.unitary_gauge, prefix, decay)) 385 return model 386 else: 387 continue 388 break 389 else: 390 logger.info('reload from .py file') 391 392 if (model_path, aloha.unitary_gauge, prefix, decay) in _import_once and not allow_reload: 393 raise MadGraph5Error('This model %s is modified on disk. To reload it you need to quit/relaunch MG5_aMC ' % model_path) 394 395 # Load basic information 396 ufo_model = ufomodels.load_model(model_path, decay) 397 ufo2mg5_converter = UFOMG5Converter(ufo_model) 398 model = ufo2mg5_converter.load_model() 399 if model_path[-1] == '/': model_path = model_path[:-1] #avoid empty name 400 model.set('name', os.path.split(model_path)[-1]) 401 402 # Load the Parameter/Coupling in a convenient format. 403 parameters, couplings = OrganizeModelExpression(ufo_model).main(\ 404 additional_couplings =(ufo2mg5_converter.wavefunction_CT_couplings 405 if ufo2mg5_converter.perturbation_couplings else [])) 406 407 model.set('parameters', parameters) 408 model.set('couplings', couplings) 409 model.set('functions', ufo_model.all_functions) 410 411 # Optional UFO part: decay_width information 412 413 414 if decay and hasattr(ufo_model, 'all_decays') and ufo_model.all_decays: 415 start = time.time() 416 for ufo_part in ufo_model.all_particles: 417 name = ufo_part.name 418 if not model['case_sensitive']: 419 name = name.lower() 420 p = model['particles'].find_name(name) 421 if hasattr(ufo_part, 'partial_widths'): 422 p.partial_widths = ufo_part.partial_widths 423 elif p and not hasattr(p, 'partial_widths'): 424 p.partial_widths = {} 425 # might be None for ghost 426 logger.debug("load width takes %s", time.time()-start) 427 428 if prefix: 429 start = time.time() 430 model.change_parameter_name_with_prefix() 431 logger.debug("model prefixing takes %s", time.time()-start) 432 433 path = os.path.dirname(os.path.realpath(model_path)) 434 path = os.path.join(path, model.get('name')) 435 model.set('version_tag', os.path.realpath(path) +'##'+ str(misc.get_pkg_info())) 436 437 # save in a pickle files to fasten future usage 438 if ReadWrite and model['allow_pickle']: 439 save_load_object.save_to_file(os.path.join(model_path, pickle_name), 440 model, log=False, allow_fail=True) 441 442 #if default and os.path.exists(os.path.join(model_path, 'restrict_default.dat')): 443 # restrict_file = os.path.join(model_path, 'restrict_default.dat') 444 # model = import_ufo.RestrictModel(model) 445 # model.restrict_model(restrict_file) 446 447 return model
448
449 -class UFOMG5Converter(object):
450 """Convert a UFO model to the MG5 format""" 451
452 - def __init__(self, model, auto=False):
453 """ initialize empty list for particles/interactions """ 454 455 if hasattr(model, '__header__'): 456 header = model.__header__ 457 if len(header) > 500 or header.count('\n') > 5: 458 logger.debug("Too long header") 459 else: 460 logger.info("\n"+header) 461 else: 462 f =collections.defaultdict(lambda : 'n/a') 463 for key in ['author', 'version', 'email', 'arxiv']: 464 if hasattr(model, '__%s__' % key): 465 val = getattr(model, '__%s__' % key) 466 if 'Duhr' in val: 467 continue 468 f[key] = getattr(model, '__%s__' % key) 469 470 if len(f)>2: 471 logger.info("This model [version %(version)s] is provided by %(author)s (email: %(email)s). Please cite %(arxiv)s" % f, '$MG:color:BLACK') 472 elif hasattr(model, '__arxiv__'): 473 logger.info('Please cite %s when using this model', model.__arxiv__, '$MG:color:BLACK') 474 475 self.particles = base_objects.ParticleList() 476 self.interactions = base_objects.InteractionList() 477 self.non_qcd_gluon_emission = 0 # vertex where a gluon is emitted withou QCD interaction 478 # only trigger if all particles are of QCD type (not h>gg) 479 self.wavefunction_CT_couplings = [] 480 481 # Check here if we can extract the couplings perturbed in this model 482 # which indicate a loop model or if this model is only meant for 483 # tree-level computations 484 self.perturbation_couplings = {} 485 try: 486 for order in model.all_orders: 487 if(order.perturbative_expansion>0): 488 self.perturbation_couplings[order.name]=order.perturbative_expansion 489 except AttributeError as error: 490 pass 491 492 if self.perturbation_couplings!={}: 493 self.model = loop_base_objects.LoopModel({'perturbation_couplings':\ 494 list(self.perturbation_couplings.keys())}) 495 else: 496 self.model = base_objects.Model() 497 self.model.set('particles', self.particles) 498 self.model.set('interactions', self.interactions) 499 self.conservecharge = set(['charge']) 500 501 self.ufomodel = model 502 self.checked_lor = set() 503 504 if auto: 505 self.load_model()
506
507 - def load_model(self):
508 """load the different of the model first particles then interactions""" 509 510 # Check the validity of the model 511 # 1) check that all lhablock are single word. 512 def_name = [] 513 for param in self.ufomodel.all_parameters: 514 if param.nature == "external": 515 if len(param.lhablock.split())>1: 516 raise InvalidModel('''LHABlock should be single word which is not the case for 517 \'%s\' parameter with lhablock \'%s\' ''' % (param.name, param.lhablock)) 518 if param.name in def_name: 519 raise InvalidModel("name %s define multiple time. Please correct the UFO model!" \ 520 % (param.name)) 521 else: 522 def_name.append(param.name) 523 524 # For each CTParameter, check that there is no name conflict with the 525 # set of re-defined CTParameters with EPS and FIN suffixes. 526 if hasattr(self.ufomodel,'all_CTparameters'): 527 for CTparam in self.ufomodel.all_CTparameters: 528 for pole in pole_dict: 529 if CTparam.pole(pole)!='ZERO': 530 new_param_name = '%s_%s_'%(CTparam.name,pole_dict[pole]) 531 if new_param_name in def_name: 532 raise InvalidModel("CT name %s"% (new_param_name)+\ 533 " the model. Please change its name.") 534 535 if hasattr(self.ufomodel, 'gauge'): 536 self.model.set('gauge', self.ufomodel.gauge) 537 logger.info('load particles') 538 # Check if multiple particles have the same name but different case. 539 # Otherwise, we can use lowercase particle names. 540 if len(set([p.name for p in self.ufomodel.all_particles] + \ 541 [p.antiname for p in self.ufomodel.all_particles])) == \ 542 len(set([p.name.lower() for p in self.ufomodel.all_particles] + \ 543 [p.antiname.lower() for p in self.ufomodel.all_particles])): 544 self.model['case_sensitive'] = False 545 546 547 # check which of the fermion/anti-fermion should be set as incoming 548 self.detect_incoming_fermion() 549 550 for particle_info in self.ufomodel.all_particles: 551 self.add_particle(particle_info) 552 553 # Find which particles is in the 3/3bar color states (retrun {id: 3/-3}) 554 color_info = self.find_color_anti_color_rep() 555 556 # load the lorentz structure. 557 self.model.set('lorentz', list(self.ufomodel.all_lorentz)) 558 559 # Substitute the expression of CT couplings which include CTparameters 560 # in their definition with the corresponding dictionaries, e.g. 561 # CTCoupling.value = 2*CTParam -> 562 # CTCoupling.value = {-1: 2*CTParam_1EPS_, 0: 2*CTParam_FIN_} 563 # for example if CTParam had a non-zero finite and single pole. 564 # This change affects directly the UFO model and it will be reverted in 565 # OrganizeModelExpression only, so that the main() function of this class 566 # *must* be run on the UFO to have this change reverted. 567 if hasattr(self.ufomodel,'all_CTparameters'): 568 logger.debug('Handling couplings defined with CTparameters...') 569 start_treat_coupling = time.time() 570 self.treat_couplings(self.ufomodel.all_couplings, 571 self.ufomodel.all_CTparameters) 572 tot_time = time.time()-start_treat_coupling 573 if tot_time>5.0: 574 logger.debug('... done in %s'%misc.format_time(tot_time)) 575 576 logger.info('load vertices') 577 for interaction_info in self.ufomodel.all_vertices: 578 self.add_interaction(interaction_info, color_info) 579 580 if self.non_qcd_gluon_emission: 581 logger.critical("Model with non QCD emission of gluon (found %i of those).\n This type of model is not fully supported within MG5aMC.\n"+\ 582 " Restriction on LO dynamical scale and MLM matching/merging can occur for some processes.\n"+\ 583 " Use such features with care.", self.non_qcd_gluon_emission) 584 585 self.model['allow_pickle'] = False 586 self.model['limitations'].append('MLM') 587 588 if self.perturbation_couplings: 589 try: 590 self.ufomodel.add_NLO() 591 except Exception as error: 592 pass 593 594 for interaction_info in self.ufomodel.all_CTvertices: 595 self.add_CTinteraction(interaction_info, color_info) 596 597 598 for interaction in list(self.interactions): 599 self.optimise_interaction(interaction) 600 if not interaction['couplings']: 601 self.interactions.remove(interaction) 602 603 604 self.model.set('conserved_charge', self.conservecharge) 605 606 # If we deal with a Loop model here, the order hierarchy MUST be 607 # defined in the file coupling_orders.py and we import it from 608 # there. 609 all_orders = [] 610 try: 611 all_orders = self.ufomodel.all_orders 612 except AttributeError: 613 if self.perturbation_couplings: 614 raise MadGraph5Error("The loop model MG5 attemps to import does not specify the attribute 'all_order'.") 615 else: 616 pass 617 618 hierarchy={} 619 try: 620 for order in all_orders: 621 hierarchy[order.name]=order.hierarchy 622 except AttributeError: 623 if self.perturbation_couplings: 624 raise MadGraph5Error('The loop model MG5 attemps to import does not specify an order hierarchy.') 625 else: 626 pass 627 else: 628 self.model.set('order_hierarchy', hierarchy) 629 630 # Also set expansion_order, i.e., maximum coupling order per process 631 expansion_order={} 632 # And finally the UVCT coupling order counterterms 633 coupling_order_counterterms={} 634 try: 635 for order in all_orders: 636 expansion_order[order.name]=order.expansion_order 637 coupling_order_counterterms[order.name]=order.expansion_order 638 except AttributeError: 639 if self.perturbation_couplings: 640 raise MadGraph5Error('The loop model MG5 attemps to import does not specify an expansion_order for all coupling orders.') 641 else: 642 pass 643 else: 644 self.model.set('expansion_order', expansion_order) 645 self.model.set('expansion_order', expansion_order) 646 647 #clean memory 648 del self.checked_lor 649 650 return self.model
651
652 - def optimise_interaction(self, interaction):
653 654 655 # Check if two couplings have exactly the same definition. 656 # If so replace one by the other 657 if not hasattr(self, 'iden_couplings'): 658 coups = collections.defaultdict(list) 659 coups['0'].append('ZERO') 660 for coupling in self.ufomodel.all_couplings: 661 #if isinstance(coupling.value, str): 662 coups[str(coupling.value)].append( coupling.name) 663 664 self.iden_couplings = {} 665 for idens in [c for c in coups.values() if len(c)>1]: 666 for i in range(1, len(idens)): 667 self.iden_couplings[idens[i]] = idens[0] 668 669 # apply the replacement by identical expression 670 for key, coup in list(interaction['couplings'].items()): 671 if coup in self.iden_couplings: 672 interaction['couplings'][key] = self.iden_couplings[coup] 673 if interaction['couplings'][key] == 'ZERO': 674 del interaction['couplings'][key] 675 676 677 678 679 680 # we want to check if the same coupling is used for two lorentz strucutre 681 # for the same color structure. 682 to_lor = {} 683 for (color, lor), coup in interaction['couplings'].items(): 684 key = (color, coup) 685 if key in to_lor: 686 to_lor[key].append(lor) 687 else: 688 to_lor[key] = [lor] 689 690 nb_reduce = [] 691 optimize = False 692 for key in to_lor: 693 if len(to_lor[key]) >1: 694 nb_reduce.append(len(to_lor[key])-1) 695 optimize = True 696 697 if not optimize: 698 return 699 700 if not hasattr(self, 'defined_lorentz_expr'): 701 self.defined_lorentz_expr = {} 702 self.lorentz_info = {} 703 self.lorentz_combine = {} 704 for lor in self.model['lorentz']: 705 self.defined_lorentz_expr[lor.get('structure')] = lor.get('name') 706 self.lorentz_info[lor.get('name')] = lor #(lor.get('structure'), lor.get('spins')) 707 708 for key in to_lor: 709 if len(to_lor[key]) == 1: 710 continue 711 names = [interaction['lorentz'][i] for i in to_lor[key]] 712 names.sort() 713 if self.lorentz_info[names[0]].get('structure') == 'external': 714 continue 715 # get name of the new lorentz 716 if tuple(names) in self.lorentz_combine: 717 # already created new loretnz 718 new_name = self.lorentz_combine[tuple(names)] 719 else: 720 new_name = self.add_merge_lorentz(names) 721 722 # remove the old couplings 723 color, coup = key 724 to_remove = [(color, lor) for lor in to_lor[key]] 725 for rm in to_remove: 726 del interaction['couplings'][rm] 727 728 #add the lorentz structure to the interaction 729 if new_name not in [l for l in interaction.get('lorentz')]: 730 interaction.get('lorentz').append(new_name) 731 732 #find the associate index 733 new_l = interaction.get('lorentz').index(new_name) 734 # adding the new combination (color,lor) associate to this sum of structure 735 interaction['couplings'][(color, new_l)] = coup
736 737
738 - def add_merge_lorentz(self, names):
739 """add a lorentz structure which is the sume of the list given above""" 740 741 742 #create new_name 743 ii = len(names[0]) 744 while ii>0: 745 if not all(n.startswith(names[0][:ii]) for n in names[1:]): 746 ii -=1 747 else: 748 base_name = names[0][:ii] 749 break 750 else: 751 base_name = 'LMER' 752 753 i = 1 754 while '%s%s' %(base_name, i) in self.lorentz_info: 755 i +=1 756 new_name = '%s%s' %(base_name, i) 757 self.lorentz_combine[tuple(names)] = new_name 758 assert new_name not in self.lorentz_info 759 assert new_name not in [l.name for l in self.model['lorentz']] 760 761 # load the associate lorentz expression 762 new_struct = ' + '.join([self.lorentz_info[n].get('structure') for n in names]) 763 spins = self.lorentz_info[names[0]].get('spins') 764 formfactors = sum([ self.lorentz_info[n].get('formfactors') for n in names \ 765 if hasattr(self.lorentz_info[n], 'formfactors') \ 766 and self.lorentz_info[n].get('formfactors') \ 767 ],[]) 768 769 new_lor = self.add_lorentz(new_name, spins, new_struct, formfactors) 770 self.lorentz_info[new_name] = new_lor 771 772 return new_name
773 774 # We also have to create the new lorentz 775 776 777 778 779
780 - def add_particle(self, particle_info):
781 """ convert and add a particle in the particle list """ 782 783 loop_particles = [[[]]] 784 counterterms = {} 785 786 # MG5 have only one entry for particle and anti particles. 787 #UFO has two. use the color to avoid duplictions 788 pdg = particle_info.pdg_code 789 if pdg in self.incoming or (pdg not in self.outcoming and pdg <0): 790 return 791 792 # MG5 doesn't use ghost for tree models: physical sum on the polarization 793 if not self.perturbation_couplings and particle_info.spin < 0: 794 return 795 796 if (aloha.unitary_gauge and 0 in self.model['gauge']) \ 797 or (1 not in self.model['gauge']): 798 799 # MG5 doesn't use goldstone boson 800 if hasattr(particle_info, 'GoldstoneBoson') and particle_info.GoldstoneBoson: 801 return 802 elif hasattr(particle_info, 'goldstone') and particle_info.goldstone: 803 return 804 # Initialize a particles 805 particle = base_objects.Particle() 806 807 # MG5 doesn't use goldstone boson 808 if (hasattr(particle_info, 'GoldstoneBoson') and particle_info.GoldstoneBoson) \ 809 or (hasattr(particle_info, 'goldstoneboson') and particle_info.goldstoneboson): 810 particle.set('type', 'goldstone') 811 elif hasattr(particle_info, 'goldstone') and particle_info.goldstone: 812 particle.set('type', 'goldstone') 813 814 nb_property = 0 #basic check that the UFO information is complete 815 # Loop over the element defining the UFO particles 816 for key,value in particle_info.__dict__.items(): 817 # Check if we use it in the MG5 definition of a particles 818 if key in base_objects.Particle.sorted_keys and not key=='counterterm': 819 nb_property +=1 820 if key in ['name', 'antiname']: 821 if not self.model['case_sensitive']: 822 particle.set(key, value.lower()) 823 else: 824 particle.set(key, value) 825 elif key == 'charge': 826 particle.set(key, float(value)) 827 elif key in ['mass','width']: 828 particle.set(key, str(value)) 829 elif key == 'spin': 830 # MG5 internally treats ghost with positive spin for loop models and 831 # ignore them otherwise 832 particle.set(key,abs(value)) 833 if value<0: 834 particle.set('type','ghost') 835 elif key == 'propagating': 836 if not value: 837 particle.set('line', None) 838 elif key == 'line': 839 if particle.get('line') is None: 840 pass # This means that propagating is on False 841 else: 842 particle.set('line', value) 843 elif key == 'propagator': 844 if value: 845 if isinstance(value, (list,dict)): 846 if aloha.unitary_gauge: 847 particle.set(key, str(value[0])) 848 else: 849 particle.set(key, str(value[1])) 850 else: 851 particle.set(key, str(value)) 852 else: 853 particle.set(key, '') 854 else: 855 particle.set(key, value) 856 elif key == 'loop_particles': 857 loop_particles = value 858 elif key == 'counterterm': 859 counterterms = value 860 elif key.lower() not in ('ghostnumber','selfconjugate','goldstone', 861 'goldstoneboson','partial_widths', 862 'texname', 'antitexname', 'propagating', 'ghost' 863 ): 864 # add charge -we will check later if those are conserve 865 self.conservecharge.add(key) 866 particle.set(key,value, force=True) 867 868 if not hasattr(particle_info, 'propagator'): 869 nb_property += 1 870 if particle.get('spin') >= 3: 871 if particle.get('mass').lower() == 'zero': 872 particle.set('propagator', 0) 873 elif particle.get('spin') == 3 and not aloha.unitary_gauge: 874 particle.set('propagator', 0) 875 876 assert(10 == nb_property) #basic check that all the information is there 877 878 # Identify self conjugate particles 879 if particle_info.name == particle_info.antiname: 880 particle.set('self_antipart', True) 881 882 # Proceed only if we deal with a loop model and that this particle 883 # has wavefunction renormalization 884 if not self.perturbation_couplings or counterterms=={}: 885 self.particles.append(particle) 886 return 887 888 # Set here the 'counterterm' attribute to the particle. 889 # First we must change the couplings dictionary keys from the entry format 890 # (order1,order2,...,orderN,loop_particle#):LaurentSerie 891 # two a dictionary with format 892 # ('ORDER_OF_COUNTERTERM',((Particle_list_PDG))):{laurent_order:CTCouplingName} 893 particle_counterterms = {} 894 for key, counterterm in counterterms.items(): 895 # Makes sure this counterterm contributes at one-loop. 896 if len([1 for k in key[:-1] if k==1])==1 and \ 897 not any(k>1 for k in key[:-1]): 898 newParticleCountertermKey=[None,\ 899 # The line below is for loop UFO Model with the 'attribute' 900 # 'loop_particles' of the Particle objects to be defined with 901 # instances of the particle class. The new convention is to use 902 # pdg numbers instead. 903 # tuple([tuple([abs(part.pdg_code) for part in loop_parts]) for\ 904 tuple([tuple(loop_parts) for\ 905 loop_parts in loop_particles[key[-1]]])] 906 for i, order in enumerate(self.ufomodel.all_orders[:-1]): 907 if key[i]==1: 908 newParticleCountertermKey[0]=order.name 909 newCouplingName='UVWfct_'+particle_info.name+'_'+str(key[-1]) 910 particle_counterterms[tuple(newParticleCountertermKey)]=\ 911 dict([(key,newCouplingName+('' if key==0 else '_'+str(-key)+'eps'))\ 912 for key in counterterm]) 913 # We want to create the new coupling for this wavefunction 914 # renormalization. 915 self.ufomodel.object_library.Coupling(\ 916 name = newCouplingName, 917 value = counterterm, 918 order = {newParticleCountertermKey[0]:2}) 919 self.wavefunction_CT_couplings.append(self.ufomodel.all_couplings.pop()) 920 921 particle.set('counterterm',particle_counterterms) 922 self.particles.append(particle) 923 return
924
925 - def treat_couplings(self, couplings, all_CTparameters):
926 """ This function scan each coupling to see if it contains a CT parameter. 927 when it does, it changes its value to a dictionary with the CT parameter 928 changed to a new parameter for each pole and finite part. For instance, 929 the following coupling: 930 coupling.value = '2*(myCTParam1 + myParam*(myCTParam2 + myCTParam3)' 931 with CTparameters 932 myCTParam1 = {0: Something, -1: SomethingElse} 933 myCTParam2 = {0: OtherSomething } 934 myCTParam3 = {-1: YetOtherSomething } 935 would be turned into 936 coupling.value = {0: '2*(myCTParam1_FIN_ + myParam*(myCTParam2_FIN_ + ZERO)' 937 -1: '2*(myCTParam1_EPS_ + myParam*(ZERO + myCTParam2_EPS_)'} 938 939 all_CTParameter is the list of all CTParameters in the model""" 940 941 # First define a list of regular expressions for each CT parameter 942 # and put them in a dictionary whose keys are the CT parameter names 943 # and the values are a tuple with the substituting patter in the first 944 # entry and the list of substituting functions (one for each pole) 945 # as the second entry of this tuple. 946 CTparameter_patterns = {} 947 zero_substitution = lambda matchedObj: matchedObj.group('first')+\ 948 'ZERO'+matchedObj.group('second') 949 def function_factory(arg): 950 return lambda matchedObj: \ 951 matchedObj.group('first')+arg+matchedObj.group('second')
952 for CTparam in all_CTparameters: 953 pattern_finder = re.compile(r"(?P<first>\A|\*|\+|\-|\(|\s)(?P<name>"+ 954 CTparam.name+r")(?P<second>\Z|\*|\+|\-|\)|/|\\|\s)") 955 956 sub_functions = [None if CTparam.pole(pole)=='ZERO' else 957 function_factory('%s_%s_'%(CTparam.name,pole_dict[-pole])) 958 for pole in range(3)] 959 CTparameter_patterns[CTparam.name] = (pattern_finder,sub_functions) 960 961 times_zero = re.compile('\*\s*-?ZERO') 962 zero_times = re.compile('ZERO\s*(\*|\/)') 963 def is_expr_zero(expresson): 964 """ Checks whether a single term (involving only the operations 965 * or / is zero. """ 966 for term in expresson.split('-'): 967 for t in term.split('+'): 968 t = t.strip() 969 if t in ['ZERO','']: 970 continue 971 if not (times_zero.search(t) or zero_times.search(t)): 972 return False 973 return True
974 975 def find_parenthesis(expr): 976 end = expr.find(')') 977 if end == -1: 978 return None 979 start = expr.rfind('(',0,end+1) 980 if start ==-1: 981 raise InvalidModel('Parenthesis of expression %s are malformed'%expr) 982 return [expr[:start],expr[start+1:end],expr[end+1:]] 983 984 start_parenthesis = re.compile(r".*\s*[\+\-\*\/\)\(]\s*$") 985 986 def is_value_zero(value): 987 """Check whether an expression like ((A+B)*ZERO+C)*ZERO is zero. 988 Only +,-,/,* operations are allowed and 'ZERO' is a tag for an 989 analytically zero quantity.""" 990 991 curr_value = value 992 parenthesis = find_parenthesis(curr_value) 993 while parenthesis: 994 # Allow the complexconjugate function 995 if parenthesis[0].endswith('complexconjugate'): 996 # Then simply remove it 997 parenthesis[0] = parenthesis[0][:-16] 998 if parenthesis[0]=='' or re.match(start_parenthesis, 999 parenthesis[0]): 1000 if is_value_zero(parenthesis[1]): 1001 new_parenthesis = 'ZERO' 1002 else: 1003 new_parenthesis = 'PARENTHESIS' 1004 else: 1005 new_parenthesis = '_FUNCTIONARGS' 1006 curr_value = parenthesis[0]+new_parenthesis+parenthesis[2] 1007 parenthesis = find_parenthesis(curr_value) 1008 return is_expr_zero(curr_value) 1009 1010 def CTCoupling_pole(CTCoupling, pole): 1011 """Compute the pole of the CTCoupling in two cases: 1012 a) Its value is a dictionary, then just return the corresponding 1013 entry in the dictionary. 1014 b) It is expressed in terms of CTParameters which are themselves 1015 dictionary so we want to substitute their expression to get 1016 the value of the pole. In the current implementation, this is 1017 just to see if the pole is zero or not. 1018 """ 1019 1020 if isinstance(CTCoupling.value,dict): 1021 if -pole in list(CTCoupling.value.keys()): 1022 return CTCoupling.value[-pole], [], 0 1023 else: 1024 return 'ZERO', [], 0 1025 1026 new_expression = CTCoupling.value 1027 CTparamNames = [] 1028 n_CTparams = 0 1029 for paramname, value in CTparameter_patterns.items(): 1030 pattern = value[0] 1031 # Keep track of which CT parameters enter in the definition of 1032 # which coupling. 1033 if not re.search(pattern,new_expression): 1034 continue 1035 n_CTparams += 1 1036 # If the contribution of this CTparam to this pole is non 1037 # zero then the substituting function is not None: 1038 if not value[1][pole] is None: 1039 CTparamNames.append('%s_%s_'%(paramname,pole_dict[-pole])) 1040 1041 substitute_function = zero_substitution if \ 1042 value[1][pole] is None else value[1][pole] 1043 new_expression = pattern.sub(substitute_function,new_expression) 1044 1045 # If no CTParam was found and we ask for a pole, then it can only 1046 # be zero. 1047 if pole!=0 and n_CTparams==0: 1048 return 'ZERO', [], n_CTparams 1049 1050 # Check if resulting expression is analytically zero or not. 1051 # Remember that when the value of a CT_coupling is not a dictionary 1052 # then the only operators allowed in the definition are +,-,*,/ 1053 # and each term added or subtracted must contain *exactly one* 1054 # CTParameter and never at the denominator. 1055 if n_CTparams > 0 and is_value_zero(new_expression): 1056 return 'ZERO', [], n_CTparams 1057 else: 1058 return new_expression, CTparamNames, n_CTparams 1059 1060 # For each coupling we substitute its value if necessary 1061 for coupl in couplings: 1062 new_value = {} 1063 for pole in range(0,3): 1064 expression, CTparamNames, n_CTparams = CTCoupling_pole(coupl, pole) 1065 # Make sure it uses CT parameters, otherwise do nothing 1066 if n_CTparams == 0: 1067 break 1068 elif expression!='ZERO': 1069 new_value[-pole] = expression 1070 couplname = coupl.name 1071 if pole!=0: 1072 couplname += "_%deps"%pole 1073 # Add the parameter dependency found to the dependency map 1074 # of the model being built. In principle, since we should 1075 # be building a loop model now, it should always have this 1076 # attribute defined, but it is better to make sure. 1077 if hasattr(self.model, 'map_CTcoup_CTparam'): 1078 self.model.map_CTcoup_CTparam[couplname] = CTparamNames 1079 1080 # Finally modify the value of this CTCoupling so that it is no 1081 # longer a string expression in terms of CTParameters but rather 1082 # a dictionary with the CTparameters replaced by their _FIN_ and 1083 # _EPS_ counterparts. 1084 # This is useful for the addCT_interaction() step. I will be reverted 1085 # right after the addCT_interaction() function so as to leave 1086 # the UFO intact, as it should. 1087 if new_value: 1088 coupl.old_value = coupl.value 1089 coupl.value = new_value 1090
1091 - def add_CTinteraction(self, interaction, color_info):
1092 """ Split this interaction in order to call add_interaction for 1093 interactions for each element of the loop_particles list. Also it 1094 is necessary to unfold here the contributions to the different laurent 1095 expansion orders of the couplings.""" 1096 1097 # Work on a local copy of the interaction provided 1098 interaction_info=copy.copy(interaction) 1099 1100 intType='' 1101 if interaction_info.type not in ['UV','UVloop','UVtree','UVmass','R2']: 1102 raise MadGraph5Error('MG5 only supports the following types of'+\ 1103 ' vertices, R2, UV and UVmass. %s is not in this list.'%interaction_info.type) 1104 else: 1105 intType=interaction_info.type 1106 # If not specified and simply set to UV, guess the appropriate type 1107 if interaction_info.type=='UV': 1108 if len(interaction_info.particles)==2 and interaction_info.\ 1109 particles[0].name==interaction_info.particles[1].name: 1110 intType='UVmass' 1111 else: 1112 intType='UVloop' 1113 1114 # Make sure that if it is a UV mass renromalization counterterm it is 1115 # defined as such. 1116 # if len(intType)>2 and intType[:2]=='UV' and len(interaction_info.particles)==2 \ 1117 # and interaction_info.particles[0].name==interaction_info.particles[1].name: 1118 # intType='UVmass' 1119 1120 # Now we create a couplings dictionary for each element of the loop_particles list 1121 # and for each expansion order of the laurent serie in the coupling. 1122 # and for each coupling order 1123 # Format is new_couplings[loop_particles][laurent_order] and each element 1124 # is a couplings dictionary. 1125 order_to_interactions= {} 1126 # will contains the new coupling of form 1127 #new_couplings=[[{} for j in range(0,3)] for i in \ 1128 # range(0,max(1,len(interaction_info.loop_particles)))] 1129 # So sort all entries in the couplings dictionary to put them a the 1130 # correct place in new_couplings. 1131 for key, couplings in interaction_info.couplings.items(): 1132 if not isinstance(couplings, list): 1133 couplings = [couplings] 1134 for coupling in couplings: 1135 order = tuple(coupling.order.items()) 1136 if order not in order_to_interactions: 1137 order_to_interactions[order] = [ 1138 [{} for j in range(0,3)] for i in \ 1139 range(0,max(1,len(interaction_info.loop_particles)))] 1140 new_couplings = order_to_interactions[order] 1141 else: 1142 new_couplings = order_to_interactions[order] 1143 1144 for poleOrder in range(0,3): 1145 expression = coupling.pole(poleOrder) 1146 if expression!='ZERO': 1147 if poleOrder==2: 1148 raise InvalidModel(""" 1149 The CT coupling %s was found with a contribution to the double pole. 1150 This is either an error in the model or a parsing error in the function 'is_value_zero'. 1151 The expression of the non-zero double pole coupling is: 1152 %s 1153 """%(coupling.name,str(coupling.value))) 1154 # It is actually safer that the new coupling associated to 1155 # the interaction added is not a reference to an original 1156 # coupling in the ufo model. So copy.copy is right here. 1157 newCoupling = copy.copy(coupling) 1158 if poleOrder!=0: 1159 newCoupling.name=newCoupling.name+"_"+str(poleOrder)+"eps" 1160 newCoupling.value = expression 1161 # assign the CT parameter dependences 1162 #if hasattr(coupling,'CTparam_dependence') and \ 1163 # (-poleOrder in coupling.CTparam_dependence) and \ 1164 # coupling.CTparam_dependence[-poleOrder]: 1165 # newCoupling.CTparam_dependence = coupling.CTparam_dependence[-poleOrder] 1166 #elif hasattr(newCoupling,'CTparam_dependence'): 1167 # delattr(newCoupling,"CTparam_dependence") 1168 new_couplings[key[2]][poleOrder][(key[0],key[1])] = newCoupling 1169 1170 for new_couplings in order_to_interactions.values(): 1171 # Now we can add an interaction for each. 1172 for i, all_couplings in enumerate(new_couplings): 1173 loop_particles=[[]] 1174 if len(interaction_info.loop_particles)>0: 1175 loop_particles=[[part.pdg_code for part in loop_parts] \ 1176 for loop_parts in interaction_info.loop_particles[i]] 1177 for poleOrder in range(0,3): 1178 if all_couplings[poleOrder]!={}: 1179 interaction_info.couplings=all_couplings[poleOrder] 1180 self.add_interaction(interaction_info, color_info,\ 1181 (intType if poleOrder==0 else (intType+str(poleOrder)+\ 1182 'eps')),loop_particles)
1183
1184 - def find_color_anti_color_rep(self, output=None):
1185 """find which color are in the 3/3bar states""" 1186 # method look at the 3 3bar 8 configuration. 1187 # If the color is T(3,2,1) and the interaction F1 F2 V 1188 # Then set F1 to anticolor (and F2 to color) 1189 # if this is T(3,1,2) set the opposite 1190 if not output: 1191 output = {} 1192 1193 for interaction_info in self.ufomodel.all_vertices: 1194 if len(interaction_info.particles) != 3: 1195 continue 1196 colors = [abs(p.color) for p in interaction_info.particles] 1197 if colors[:2] == [3,3]: 1198 if 'T(3,2,1)' in interaction_info.color: 1199 color, anticolor, other = interaction_info.particles 1200 elif 'T(3,1,2)' in interaction_info.color: 1201 anticolor, color, _ = interaction_info.particles 1202 elif 'Identity(1,2)' in interaction_info.color or \ 1203 'Identity(2,1)' in interaction_info.color: 1204 first, second, _ = interaction_info.particles 1205 if first.pdg_code in output: 1206 if output[first.pdg_code] == 3: 1207 color, anticolor = first, second 1208 else: 1209 color, anticolor = second, first 1210 elif second.pdg_code in output: 1211 if output[second.pdg_code] == 3: 1212 color, anticolor = second, first 1213 else: 1214 color, anticolor = first, second 1215 else: 1216 continue 1217 else: 1218 continue 1219 elif colors[1:] == [3,3]: 1220 if 'T(1,2,3)' in interaction_info.color: 1221 other, anticolor, color = interaction_info.particles 1222 elif 'T(1,3,2)' in interaction_info.color: 1223 other, color, anticolor = interaction_info.particles 1224 elif 'Identity(2,3)' in interaction_info.color or \ 1225 'Identity(3,2)' in interaction_info.color: 1226 _, first, second = interaction_info.particles 1227 if first.pdg_code in output: 1228 if output[first.pdg_code] == 3: 1229 color, anticolor = first, second 1230 else: 1231 color, anticolor = second, first 1232 elif second.pdg_code in output: 1233 if output[second.pdg_code] == 3: 1234 color, anticolor = second, first 1235 else: 1236 color, anticolor = first, second 1237 else: 1238 continue 1239 else: 1240 continue 1241 1242 elif colors.count(3) == 2: 1243 if 'T(2,3,1)' in interaction_info.color: 1244 color, other, anticolor = interaction_info.particles 1245 elif 'T(2,1,3)' in interaction_info.color: 1246 anticolor, other, color = interaction_info.particles 1247 elif 'Identity(1,3)' in interaction_info.color or \ 1248 'Identity(3,1)' in interaction_info.color: 1249 first, _, second = interaction_info.particles 1250 if first.pdg_code in output: 1251 if output[first.pdg_code] == 3: 1252 color, anticolor = first, second 1253 else: 1254 color, anticolor = second, first 1255 elif second.pdg_code in output: 1256 if output[second.pdg_code] == 3: 1257 color, anticolor = second, first 1258 else: 1259 color, anticolor = first, second 1260 else: 1261 continue 1262 else: 1263 continue 1264 else: 1265 continue 1266 1267 # Check/assign for the color particle 1268 if color.pdg_code in output: 1269 if output[color.pdg_code] == -3: 1270 raise InvalidModel('Particles %s is sometimes in the 3 and sometimes in the 3bar representations' \ 1271 % color.name) 1272 else: 1273 output[color.pdg_code] = 3 1274 1275 # Check/assign for the anticolor particle 1276 if anticolor.pdg_code in output: 1277 if output[anticolor.pdg_code] == 3: 1278 raise InvalidModel('Particles %s is sometimes set as in the 3 and sometimes in the 3bar representations' \ 1279 % anticolor.name) 1280 else: 1281 output[anticolor.pdg_code] = -3 1282 1283 return output
1284
1285 - def detect_incoming_fermion(self):
1286 """define which fermion should be incoming 1287 for that we look at F F~ X interactions 1288 """ 1289 self.incoming = [] 1290 self.outcoming = [] 1291 for interaction_info in self.ufomodel.all_vertices: 1292 # check if the interaction meet requirements: 1293 pdg = [p.pdg_code for p in interaction_info.particles if p.spin in [2,4]] 1294 if len(pdg) % 2: 1295 raise InvalidModel('Odd number of fermion in vertex: %s' % [p.pdg_code for p in interaction_info.particles]) 1296 for i in range(0, len(pdg),2): 1297 if pdg[i] == - pdg[i+1]: 1298 if pdg[i] in self.outcoming: 1299 raise InvalidModel('%s has not coherent incoming/outcoming status between interactions' %\ 1300 [p for p in interaction_info.particles if p.spin in [2,4]][i].name) 1301 1302 elif not pdg[i] in self.incoming: 1303 self.incoming.append(pdg[i]) 1304 self.outcoming.append(pdg[i+1])
1305
1306 - def add_interaction(self, interaction_info, color_info, type='base', loop_particles=None):
1307 """add an interaction in the MG5 model. interaction_info is the 1308 UFO vertices information.""" 1309 # Import particles content: 1310 particles = [self.model.get_particle(particle.pdg_code) \ 1311 for particle in interaction_info.particles] 1312 if None in particles: 1313 # Interaction with a ghost/goldstone 1314 return 1315 particles = base_objects.ParticleList(particles) 1316 1317 # Import Lorentz content: 1318 lorentz = [helas for helas in interaction_info.lorentz] 1319 1320 # Check the coherence of the Fermion Flow 1321 nb_fermion = sum([ 1 if p.is_fermion() else 0 for p in particles]) 1322 try: 1323 if nb_fermion == 2: 1324 # Fermion Flow is suppose to be dealt by UFO 1325 [aloha_fct.check_flow_validity(helas.structure, nb_fermion) \ 1326 for helas in interaction_info.lorentz 1327 if helas.name not in self.checked_lor] 1328 self.checked_lor.update(set([helas.name for helas in interaction_info.lorentz])) 1329 elif nb_fermion: 1330 if any(p.selfconjugate for p in interaction_info.particles if p.spin % 2 == 0): 1331 text = "Majorana can not be dealt in 4/6/... fermion interactions" 1332 raise InvalidModel(text) 1333 except aloha_fct.WrongFermionFlow as error: 1334 text = 'Fermion Flow error for interactions %s: %s: %s\n %s' % \ 1335 (', '.join([p.name for p in interaction_info.particles]), 1336 helas.name, helas.structure, error) 1337 raise InvalidModel(text) 1338 1339 1340 1341 1342 # Now consider the name only 1343 lorentz = [helas.name for helas in lorentz] 1344 # Import color information: 1345 colors = [self.treat_color(color_obj, interaction_info, color_info) 1346 for color_obj in interaction_info.color] 1347 1348 1349 order_to_int={} 1350 1351 for key, couplings in interaction_info.couplings.items(): 1352 if not isinstance(couplings, list): 1353 couplings = [couplings] 1354 if interaction_info.lorentz[key[1]].name not in lorentz: 1355 continue 1356 # get the sign for the coupling (if we need to adapt the flow) 1357 if nb_fermion > 2: 1358 flow = aloha_fct.get_fermion_flow(interaction_info.lorentz[key[1]].structure, 1359 nb_fermion) 1360 coupling_sign = self.get_sign_flow(flow, nb_fermion) 1361 else: 1362 coupling_sign = '' 1363 for coupling in couplings: 1364 order = tuple(coupling.order.items()) 1365 if '1' in coupling.order: 1366 raise InvalidModel('''Some couplings have \'1\' order. 1367 This is not allowed in MG. 1368 Please defines an additional coupling to your model''') 1369 1370 # check that gluon emission from quark are QCD tagged 1371 if 21 in [particle.pdg_code for particle in interaction_info.particles] and\ 1372 'QCD' not in coupling.order: 1373 col = [par.get('color') for par in particles] 1374 if 1 not in col: 1375 self.non_qcd_gluon_emission +=1 1376 1377 if order in order_to_int: 1378 order_to_int[order].get('couplings')[key] = '%s%s' % \ 1379 (coupling_sign,coupling.name) 1380 else: 1381 # Initialize a new interaction with a new id tag 1382 interaction = base_objects.Interaction({'id':len(self.interactions)+1}) 1383 interaction.set('particles', particles) 1384 interaction.set('lorentz', lorentz) 1385 interaction.set('couplings', {key: 1386 '%s%s' %(coupling_sign,coupling.name)}) 1387 interaction.set('orders', coupling.order) 1388 interaction.set('color', colors) 1389 interaction.set('type', type) 1390 interaction.set('loop_particles', loop_particles) 1391 order_to_int[order] = interaction 1392 # add to the interactions 1393 self.interactions.append(interaction) 1394 1395 1396 # check if this interaction conserve the charge defined 1397 # if type=='base': 1398 for charge in list(self.conservecharge): #duplicate to allow modification 1399 total = 0 1400 for part in interaction_info.particles: 1401 try: 1402 total += getattr(part, charge) 1403 except AttributeError: 1404 pass 1405 if abs(total) > 1e-12: 1406 logger.info('The model has interaction violating the charge: %s' % charge) 1407 self.conservecharge.discard(charge) 1408 1409 1410
1411 - def get_sign_flow(self, flow, nb_fermion):
1412 """ensure that the flow of particles/lorentz are coherent with flow 1413 and return a correct version if needed""" 1414 1415 if not flow or nb_fermion < 4: 1416 return '' 1417 1418 expected = {} 1419 for i in range(nb_fermion//2): 1420 expected[i+1] = i+2 1421 1422 if flow == expected: 1423 return '' 1424 1425 switch = {} 1426 for i in range(1, nb_fermion+1): 1427 if not i in flow: 1428 continue 1429 switch[i] = len(switch) 1430 switch[flow[i]] = len(switch) 1431 1432 # compute the sign of the permutation 1433 sign = 1 1434 done = [] 1435 1436 # make a list of consecutive number which correspond to the new 1437 # order of the particles in the new list. 1438 new_order = [] 1439 for id in range(nb_fermion): # id is the position in the particles order (starts 0) 1440 nid = switch[id+1]-1 # nid is the position in the new_particles 1441 #order (starts 0) 1442 new_order.append(nid) 1443 1444 # compute the sign: 1445 sign =1 1446 for k in range(len(new_order)-1): 1447 for l in range(k+1,len(new_order)): 1448 if new_order[l] < new_order[k]: 1449 sign *= -1 1450 1451 return '' if sign ==1 else '-'
1452
1453 - def add_lorentz(self, name, spins , expr, formfact=None):
1454 """ Add a Lorentz expression which is not present in the UFO """ 1455 1456 logger.debug('MG5 converter defines %s to %s', name, expr) 1457 assert name not in [l.name for l in self.model['lorentz']] 1458 with misc.TMP_variable(self.ufomodel.object_library, 'all_lorentz', 1459 self.model['lorentz']): 1460 new = self.model['lorentz'][0].__class__(name = name, 1461 spins = spins, 1462 structure = expr) 1463 if formfact: 1464 new.formfactors = formfact 1465 if self.model['lorentz'][-1].name != name: 1466 self.model['lorentz'].append(new) 1467 if name in [l.name for l in self.ufomodel.all_lorentz]: 1468 self.ufomodel.all_lorentz.remove(new) 1469 1470 assert name in [l.name for l in self.model['lorentz']] 1471 assert name not in [l.name for l in self.ufomodel.all_lorentz] 1472 #self.model['lorentz'].append(new) # already done by above command 1473 self.model.create_lorentz_dict() 1474 return new
1475 1476 _pat_T = re.compile(r'T\((?P<first>\d*),(?P<second>\d*)\)') 1477 _pat_id = re.compile(r'Identity\((?P<first>\d*),(?P<second>\d*)\)') 1478
1479 - def treat_color(self, data_string, interaction_info, color_info):
1480 """ convert the string to ColorString""" 1481 1482 #original = copy.copy(data_string) 1483 #data_string = p.sub('color.T(\g<first>,\g<second>)', data_string) 1484 1485 1486 output = [] 1487 factor = 1 1488 for term in data_string.split('*'): 1489 pattern = self._pat_id.search(term) 1490 if pattern: 1491 particle = interaction_info.particles[int(pattern.group('first'))-1] 1492 particle2 = interaction_info.particles[int(pattern.group('second'))-1] 1493 if particle.color == particle2.color and particle.color in [-6, 6]: 1494 error_msg = 'UFO model have inconsistency in the format:\n' 1495 error_msg += 'interactions for particles %s has color information %s\n' 1496 error_msg += ' but both fermion are in the same representation %s' 1497 raise InvalidModel(error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color)) 1498 if particle.color == particle2.color and particle.color in [-3, 3]: 1499 if particle.pdg_code in color_info and particle2.pdg_code in color_info: 1500 if color_info[particle.pdg_code] == color_info[particle2.pdg_code]: 1501 error_msg = 'UFO model have inconsistency in the format:\n' 1502 error_msg += 'interactions for particles %s has color information %s\n' 1503 error_msg += ' but both fermion are in the same representation %s' 1504 raise InvalidModel(error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color)) 1505 elif particle.pdg_code in color_info: 1506 color_info[particle2.pdg_code] = -particle.pdg_code 1507 elif particle2.pdg_code in color_info: 1508 color_info[particle.pdg_code] = -particle2.pdg_code 1509 else: 1510 error_msg = 'UFO model have inconsistency in the format:\n' 1511 error_msg += 'interactions for particles %s has color information %s\n' 1512 error_msg += ' but both fermion are in the same representation %s' 1513 raise InvalidModel(error_msg % (', '.join([p.name for p in interaction_info.particles]),data_string, particle.color)) 1514 1515 1516 if particle.color == 6: 1517 output.append(self._pat_id.sub('color.T6(\g<first>,\g<second>)', term)) 1518 elif particle.color == -6 : 1519 output.append(self._pat_id.sub('color.T6(\g<second>,\g<first>)', term)) 1520 elif particle.color == 8: 1521 output.append(self._pat_id.sub('color.Tr(\g<first>,\g<second>)', term)) 1522 factor *= 2 1523 elif particle.color in [-3,3]: 1524 if particle.pdg_code not in color_info: 1525 #try to find it one more time 3 -3 1 might help 1526 logger.debug('fail to find 3/3bar representation: Retry to find it') 1527 color_info = self.find_color_anti_color_rep(color_info) 1528 if particle.pdg_code not in color_info: 1529 logger.debug('Not able to find the 3/3bar rep from the interactions for particle %s' % particle.name) 1530 color_info[particle.pdg_code] = particle.color 1531 else: 1532 logger.debug('succeed') 1533 if particle2.pdg_code not in color_info: 1534 #try to find it one more time 3 -3 1 might help 1535 logger.debug('fail to find 3/3bar representation: Retry to find it') 1536 color_info = self.find_color_anti_color_rep(color_info) 1537 if particle2.pdg_code not in color_info: 1538 logger.debug('Not able to find the 3/3bar rep from the interactions for particle %s' % particle2.name) 1539 color_info[particle2.pdg_code] = particle2.color 1540 else: 1541 logger.debug('succeed') 1542 1543 if color_info[particle.pdg_code] == 3 : 1544 output.append(self._pat_id.sub('color.T(\g<second>,\g<first>)', term)) 1545 elif color_info[particle.pdg_code] == -3: 1546 output.append(self._pat_id.sub('color.T(\g<first>,\g<second>)', term)) 1547 else: 1548 raise MadGraph5Error("Unknown use of Identity for particle with color %d" \ 1549 % particle.color) 1550 else: 1551 output.append(term) 1552 data_string = '*'.join(output) 1553 1554 # Change convention for summed indices 1555 p = re.compile(r'\'\w(?P<number>\d+)\'') 1556 data_string = p.sub('-\g<number>', data_string) 1557 1558 # Shift indices by -1 1559 new_indices = {} 1560 new_indices = dict([(j,i) for (i,j) in \ 1561 enumerate(range(1, 1562 len(interaction_info.particles)+1))]) 1563 1564 1565 output = data_string.split('*') 1566 output = color.ColorString([eval(data) \ 1567 for data in output if data !='1']) 1568 output.coeff = fractions.Fraction(factor) 1569 for col_obj in output: 1570 col_obj.replace_indices(new_indices) 1571 1572 return output
1573
1574 -class OrganizeModelExpression:
1575 """Organize the couplings/parameters of a model""" 1576 1577 track_dependant = ['aS','aEWM1','MU_R'] # list of variable from which we track 1578 #dependencies those variables should be define 1579 #as external parameters 1580 1581 # regular expression to shorten the expressions 1582 complex_number = re.compile(r'''complex\((?P<real>[^,\(\)]+),(?P<imag>[^,\(\)]+)\)''') 1583 expo_expr = re.compile(r'''(?P<expr>[\w.]+)\s*\*\*\s*(?P<expo>[+-]?[\d.]+)''') 1584 cmath_expr = re.compile(r'''cmath.(?P<operation>\w+)\((?P<expr>\w+)\)''') 1585 #operation is usualy sqrt / sin / cos / tan 1586 conj_expr = re.compile(r'''complexconjugate\((?P<expr>\w+)\)''') 1587 1588 #RE expression for is_event_dependent 1589 separator = re.compile(r'''[+,\-*/()\s]+''') 1590 1591
1592 - def __init__(self, model):
1593 1594 self.model = model # UFOMODEL 1595 self.perturbation_couplings = {} 1596 try: 1597 for order in model.all_orders: # Check if it is a loop model or not 1598 if(order.perturbative_expansion>0): 1599 self.perturbation_couplings[order.name]=order.perturbative_expansion 1600 except AttributeError: 1601 pass 1602 self.params = {} # depend on -> ModelVariable 1603 self.couplings = {} # depend on -> ModelVariable 1604 self.all_expr = {} # variable_name -> ModelVariable
1605
1606 - def main(self, additional_couplings = []):
1607 """Launch the actual computation and return the associate 1608 params/couplings. Possibly consider additional_couplings in addition 1609 to those defined in the UFO model attribute all_couplings """ 1610 1611 additional_params = [] 1612 if hasattr(self.model,'all_CTparameters'): 1613 additional_params = self.get_additional_CTparameters() 1614 1615 self.analyze_parameters(additional_params = additional_params) 1616 self.analyze_couplings(additional_couplings = additional_couplings) 1617 1618 # Finally revert the possible modifications done by treat_couplings() 1619 if hasattr(self.model,'all_CTparameters'): 1620 self.revert_CTCoupling_modifications() 1621 1622 return self.params, self.couplings
1623
1625 """ Finally revert the possible modifications done by treat_couplings() 1626 in UFOMG5Converter which were useful for the add_CTinteraction() in 1627 particular. This modification consisted in expanding the value of a 1628 CTCoupling which consisted in an expression in terms of a CTParam to 1629 its corresponding dictionary (e.g 1630 CTCoupling.value = 2*CTParam -> 1631 CTCoupling.value = {-1: 2*CTParam_1EPS_, 0: 2*CTParam_FIN_} 1632 for example if CTParam had a non-zero finite and single pole.""" 1633 1634 for coupl in self.model.all_couplings: 1635 if hasattr(coupl,'old_value'): 1636 coupl.value = coupl.old_value 1637 del(coupl.old_value)
1638
1640 """ For each CTparameter split it into spimple parameter for each pole 1641 and the finite part if not zero.""" 1642 1643 additional_params = [] 1644 for CTparam in self.model.all_CTparameters: 1645 for pole in range(3): 1646 if CTparam.pole(pole) != 'ZERO': 1647 CTparam_piece = copy.copy(CTparam) 1648 CTparam_piece.name = '%s_%s_'%(CTparam.name,pole_dict[-pole]) 1649 CTparam_piece.nature = 'internal' 1650 CTparam_piece.type = CTparam.type 1651 CTparam_piece.value = CTparam.pole(pole) 1652 CTparam_piece.texname = '%s_{%s}'%\ 1653 (CTparam.texname,pole_dict[-pole]) 1654 additional_params.append(CTparam_piece) 1655 return additional_params
1656
1657 - def analyze_parameters(self, additional_params=[]):
1658 """ separate the parameters needed to be recomputed events by events and 1659 the others""" 1660 # in order to match in Gmu scheme 1661 # test whether aEWM1 is the external or not 1662 # if not, take Gf as the track_dependant variable 1663 present_aEWM1 = any(param.name == 'aEWM1' for param in 1664 self.model.all_parameters if param.nature == 'external') 1665 1666 if not present_aEWM1: 1667 self.track_dependant = ['aS','Gf','MU_R'] 1668 1669 for param in self.model.all_parameters+additional_params: 1670 if param.nature == 'external': 1671 parameter = base_objects.ParamCardVariable(param.name, param.value, \ 1672 param.lhablock, param.lhacode) 1673 1674 else: 1675 expr = self.shorten_expr(param.value) 1676 depend_on = self.find_dependencies(expr) 1677 parameter = base_objects.ModelVariable(param.name, expr, param.type, depend_on) 1678 1679 self.add_parameter(parameter)
1680
1681 - def add_parameter(self, parameter):
1682 """ add consistently the parameter in params and all_expr. 1683 avoid duplication """ 1684 1685 assert isinstance(parameter, base_objects.ModelVariable) 1686 1687 if parameter.name in self.all_expr: 1688 return 1689 1690 self.all_expr[parameter.name] = parameter 1691 try: 1692 self.params[parameter.depend].append(parameter) 1693 except: 1694 self.params[parameter.depend] = [parameter]
1695
1696 - def add_coupling(self, coupling):
1697 """ add consistently the coupling in couplings and all_expr. 1698 avoid duplication """ 1699 1700 assert isinstance(coupling, base_objects.ModelVariable) 1701 1702 if coupling.name in self.all_expr: 1703 return 1704 self.all_expr[coupling.value] = coupling 1705 try: 1706 self.coupling[coupling.depend].append(coupling) 1707 except: 1708 self.coupling[coupling.depend] = [coupling]
1709
1710 - def analyze_couplings(self,additional_couplings=[]):
1711 """creates the shortcut for all special function/parameter 1712 separate the couplings dependent of track variables of the others""" 1713 1714 # For loop models, make sure that all couplings with dictionary values 1715 # are turned into set of couplings, one for each pole and finite part. 1716 if self.perturbation_couplings: 1717 couplings_list=[] 1718 for coupling in self.model.all_couplings + additional_couplings: 1719 if not isinstance(coupling.value,dict): 1720 couplings_list.append(coupling) 1721 else: 1722 for poleOrder in range(0,3): 1723 if coupling.pole(poleOrder)!='ZERO': 1724 newCoupling=copy.copy(coupling) 1725 if poleOrder!=0: 1726 newCoupling.name += "_%deps"%poleOrder 1727 newCoupling.value=coupling.pole(poleOrder) 1728 # assign the CT parameter dependences 1729 # if hasattr(coupling,'CTparam_dependence') and \ 1730 # (-poleOrder in coupling.CTparam_dependence) and \ 1731 # coupling.CTparam_dependence[-poleOrder]: 1732 # newCoupling.CTparam_dependence = coupling.CTparam_dependence[-poleOrder] 1733 # elif hasattr(newCoupling,'CTparam_dependence'): 1734 # delattr(newCoupling,"CTparam_dependence") 1735 couplings_list.append(newCoupling) 1736 else: 1737 couplings_list = self.model.all_couplings + additional_couplings 1738 couplings_list = [c for c in couplings_list if not isinstance(c.value, dict)] 1739 1740 for coupling in couplings_list: 1741 # shorten expression, find dependencies, create short object 1742 expr = self.shorten_expr(coupling.value) 1743 depend_on = self.find_dependencies(expr) 1744 parameter = base_objects.ModelVariable(coupling.name, expr, 'complex', depend_on) 1745 # Add consistently in the couplings/all_expr 1746 try: 1747 self.couplings[depend_on].append(parameter) 1748 except KeyError: 1749 self.couplings[depend_on] = [parameter] 1750 self.all_expr[coupling.value] = parameter
1751
1752 - def find_dependencies(self, expr):
1753 """check if an expression should be evaluated points by points or not 1754 """ 1755 depend_on = set() 1756 1757 # Treat predefined result 1758 #if name in self.track_dependant: 1759 # return tuple() 1760 1761 # Split the different part of the expression in order to say if a 1762 #subexpression is dependent of one of tracked variable 1763 expr = self.separator.split(expr) 1764 # look for each subexpression 1765 for subexpr in expr: 1766 if subexpr in self.track_dependant: 1767 depend_on.add(subexpr) 1768 1769 elif subexpr in self.all_expr and self.all_expr[subexpr].depend: 1770 [depend_on.add(value) for value in self.all_expr[subexpr].depend 1771 if self.all_expr[subexpr].depend != ('external',)] 1772 if depend_on: 1773 return tuple(depend_on) 1774 else: 1775 return tuple()
1776 1777
1778 - def shorten_expr(self, expr):
1779 """ apply the rules of contraction and fullfill 1780 self.params with dependent part""" 1781 try: 1782 expr = self.complex_number.sub(self.shorten_complex, expr) 1783 expr = self.expo_expr.sub(self.shorten_expo, expr) 1784 expr = self.cmath_expr.sub(self.shorten_cmath, expr) 1785 expr = self.conj_expr.sub(self.shorten_conjugate, expr) 1786 except Exception: 1787 logger.critical("fail to handle expression: %s, type()=%s", expr,type(expr)) 1788 raise 1789 return expr
1790 1791
1792 - def shorten_complex(self, matchobj):
1793 """add the short expression, and return the nice string associate""" 1794 1795 float_real = float(eval(matchobj.group('real'))) 1796 float_imag = float(eval(matchobj.group('imag'))) 1797 if float_real == 0 and float_imag ==1: 1798 new_param = base_objects.ModelVariable('complexi', 'complex(0,1)', 'complex') 1799 self.add_parameter(new_param) 1800 return 'complexi' 1801 else: 1802 return 'complex(%s, %s)' % (matchobj.group('real'), matchobj.group('imag'))
1803 1804
1805 - def shorten_expo(self, matchobj):
1806 """add the short expression, and return the nice string associate""" 1807 1808 expr = matchobj.group('expr') 1809 exponent = matchobj.group('expo') 1810 new_exponent = exponent.replace('.','_').replace('+','').replace('-','_m_') 1811 output = '%s__exp__%s' % (expr, new_exponent) 1812 old_expr = '%s**%s' % (expr,exponent) 1813 1814 if expr.startswith('cmath'): 1815 return old_expr 1816 1817 if expr.isdigit(): 1818 output = 'nb__' + output #prevent to start with a number 1819 new_param = base_objects.ModelVariable(output, old_expr,'real') 1820 else: 1821 depend_on = self.find_dependencies(expr) 1822 type = self.search_type(expr) 1823 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1824 self.add_parameter(new_param) 1825 return output
1826
1827 - def shorten_cmath(self, matchobj):
1828 """add the short expression, and return the nice string associate""" 1829 1830 expr = matchobj.group('expr') 1831 operation = matchobj.group('operation') 1832 output = '%s__%s' % (operation, expr) 1833 old_expr = ' cmath.%s(%s) ' % (operation, expr) 1834 if expr.isdigit(): 1835 new_param = base_objects.ModelVariable(output, old_expr , 'real') 1836 else: 1837 depend_on = self.find_dependencies(expr) 1838 type = self.search_type(expr) 1839 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1840 self.add_parameter(new_param) 1841 1842 return output
1843
1844 - def shorten_conjugate(self, matchobj):
1845 """add the short expression, and retrun the nice string associate""" 1846 1847 expr = matchobj.group('expr') 1848 output = 'conjg__%s' % (expr) 1849 old_expr = ' complexconjugate(%s) ' % expr 1850 depend_on = self.find_dependencies(expr) 1851 type = 'complex' 1852 new_param = base_objects.ModelVariable(output, old_expr, type, depend_on) 1853 self.add_parameter(new_param) 1854 1855 return output
1856 1857 1858
1859 - def search_type(self, expr, dep=''):
1860 """return the type associate to the expression if define""" 1861 1862 try: 1863 return self.all_expr[expr].type 1864 except: 1865 return 'complex'
1866
1867 -class RestrictModel(model_reader.ModelReader):
1868 """ A class for restricting a model for a given param_card. 1869 rules applied: 1870 - Vertex with zero couplings are throw away 1871 - external parameter with zero/one input are changed into internal parameter. 1872 - identical coupling/mass/width are replace in the model by a unique one 1873 """ 1874 1875 log_level = 10 1876 if madgraph.ADMIN_DEBUG: 1877 log_level = 5 1878
1879 - def default_setup(self):
1880 """define default value""" 1881 self.del_coup = [] 1882 super(RestrictModel, self).default_setup() 1883 self.rule_card = check_param_card.ParamCardRule() 1884 self.restrict_card = None 1885 self.coupling_order_dict ={} 1886 self.autowidth = []
1887
1888 - def modify_autowidth(self, cards, id):
1889 self.autowidth.append([int(id[0])]) 1890 return math.log10(2*len(self.autowidth))
1891
1892 - def restrict_model(self, param_card, rm_parameter=True, keep_external=False, 1893 complex_mass_scheme=None):
1894 """apply the model restriction following param_card. 1895 rm_parameter defines if the Zero/one parameter are removed or not from 1896 the model. 1897 keep_external if the param_card need to be kept intact 1898 """ 1899 1900 if self.get('name') == "mssm" and not keep_external: 1901 raise Exception 1902 1903 self.restrict_card = param_card 1904 # Reset particle dict to ensure synchronized particles and interactions 1905 self.set('particles', self.get('particles')) 1906 1907 # compute the value of all parameters 1908 # Get the list of definition of model functions, parameter values. 1909 model_definitions = self.set_parameters_and_couplings(param_card, 1910 complex_mass_scheme=complex_mass_scheme, 1911 auto_width=self.modify_autowidth) 1912 1913 # Simplify conditional statements 1914 logger.log(self.log_level, 'Simplifying conditional expressions') 1915 modified_params, modified_couplings = \ 1916 self.detect_conditional_statements_simplifications(model_definitions) 1917 1918 # Apply simplifications 1919 self.apply_conditional_simplifications(modified_params, modified_couplings) 1920 1921 # associate to each couplings the associated vertex: def self.coupling_pos 1922 self.locate_coupling() 1923 # deal with couplings 1924 zero_couplings, iden_couplings = self.detect_identical_couplings() 1925 1926 # remove the out-dated interactions 1927 self.remove_interactions(zero_couplings) 1928 1929 # replace in interactions identical couplings 1930 for iden_coups in iden_couplings: 1931 self.merge_iden_couplings(iden_coups) 1932 1933 # remove zero couplings and other pointless couplings 1934 self.del_coup += zero_couplings 1935 self.remove_couplings(self.del_coup) 1936 1937 # modify interaction to avoid to have identical coupling with different lorentz 1938 for interaction in list(self.get('interactions')): 1939 self.optimise_interaction(interaction) 1940 1941 # deal with parameters 1942 parameters = self.detect_special_parameters() 1943 self.fix_parameter_values(*parameters, simplify=rm_parameter, 1944 keep_external=keep_external) 1945 1946 # deal with identical parameters 1947 if not keep_external: 1948 iden_parameters = self.detect_identical_parameters() 1949 for iden_param in iden_parameters: 1950 self.merge_iden_parameters(iden_param) 1951 1952 iden_parameters = self.detect_identical_parameters() 1953 for iden_param in iden_parameters: 1954 self.merge_iden_parameters(iden_param, keep_external) 1955 1956 # change value of default parameter if they have special value: 1957 # 9.999999e-1 -> 1.0 1958 # 0.000001e-99 -> 0 Those value are used to avoid restriction 1959 for name, value in self['parameter_dict'].items(): 1960 if value == 9.999999e-1: 1961 self['parameter_dict'][name] = 1 1962 elif value == 0.000001e-99: 1963 self['parameter_dict'][name] = 0 1964 1965 # 1966 # restore auto-width value 1967 # 1968 #for lhacode in self.autowidth: 1969 for parameter in self['parameters'][('external',)]: 1970 if parameter.lhablock.lower() == 'decay' and parameter.lhacode in self.autowidth: 1971 parameter.value = 'auto' 1972 if parameter.name in self['parameter_dict']: 1973 self['parameter_dict'][parameter.name] = 'auto' 1974 elif parameter.name.startswith('mdl_'): 1975 self['parameter_dict'][parameter.name[4:]] = 'auto' 1976 else: 1977 raise Exception 1978 1979 # delete cache for coupling_order if some coupling are not present in the model anymore 1980 old_order = self['coupling_orders'] 1981 self['coupling_orders'] = None 1982 if old_order and old_order != self.get('coupling_orders'): 1983 removed = set(old_order).difference(set(self.get('coupling_orders'))) 1984 logger.warning("Some coupling order do not have any coupling associated to them: %s", list(removed)) 1985 logger.warning("Those coupling order will not be valid anymore for this model") 1986 1987 self['order_hierarchy'] = {} 1988 self['expansion_order'] = None 1989 #and re-initialize it to avoid any potential side effect 1990 self.get('order_hierarchy') 1991 self.get('expansion_order')
1992 1993 1994 1995
1996 - def locate_coupling(self):
1997 """ create a dict couplings_name -> vertex or (particle, counterterm_key) """ 1998 1999 self.coupling_pos = {} 2000 for vertex in self['interactions']: 2001 for key, coupling in vertex['couplings'].items(): 2002 if coupling.startswith('-'): 2003 coupling = coupling[1:] 2004 if coupling in self.coupling_pos: 2005 if vertex not in self.coupling_pos[coupling]: 2006 self.coupling_pos[coupling].append(vertex) 2007 else: 2008 self.coupling_pos[coupling] = [vertex] 2009 2010 for particle in self['particles']: 2011 for key, coupling_dict in particle['counterterm'].items(): 2012 for LaurentOrder, coupling in coupling_dict.items(): 2013 if coupling in self.coupling_pos: 2014 if (particle,key) not in self.coupling_pos[coupling]: 2015 self.coupling_pos[coupling].append((particle,key)) 2016 else: 2017 self.coupling_pos[coupling] = [(particle,key)] 2018 2019 return self.coupling_pos
2020
2021 - def detect_identical_couplings(self, strict_zero=False):
2022 """return a list with the name of all vanishing couplings""" 2023 2024 dict_value_coupling = {} 2025 iden_key = set() 2026 zero_coupling = [] 2027 iden_coupling = [] 2028 2029 2030 keys = list(self['coupling_dict'].keys()) 2031 keys.sort() 2032 for name in keys: 2033 value = self['coupling_dict'][name] 2034 if value == 0: 2035 zero_coupling.append(name) 2036 continue 2037 elif not strict_zero and abs(value) < 1e-13: 2038 logger.log(self.log_level, 'coupling with small value %s: %s treated as zero' % 2039 (name, value)) 2040 zero_coupling.append(name) 2041 continue 2042 elif not strict_zero and abs(value) < 1e-10: 2043 return self.detect_identical_couplings(strict_zero=True) 2044 2045 2046 if value in dict_value_coupling or -1*value in dict_value_coupling: 2047 if value in dict_value_coupling: 2048 iden_key.add(value) 2049 dict_value_coupling[value].append((name,1)) 2050 else: 2051 iden_key.add(-1*value) 2052 dict_value_coupling[-1*value].append((name,-1)) 2053 else: 2054 dict_value_coupling[value] = [(name,1)] 2055 for key in iden_key: 2056 tmp = [] 2057 if key in dict_value_coupling: 2058 tmp += dict_value_coupling[key] 2059 elif -1*key in dict_value_coupling: 2060 tmp += dict_value_coupling[-1*key] 2061 assert tmp 2062 2063 #ensure that all coupling have the same coupling order. 2064 ords = [self.get_coupling_order(k) for k,c in tmp] 2065 coup_by_ord = collections.defaultdict(list) 2066 for o,t in zip(ords, tmp): 2067 coup_by_ord[str(o)].append(t) 2068 # add the remaining identical 2069 for tmp3 in coup_by_ord.values(): 2070 if len(tmp3) > 1: 2071 if tmp3[0][1] == -1: #ensure that the first coupling has positif value 2072 tmp3 = [(t0,-t1) for t0, t1 in tmp3] 2073 iden_coupling.append(tmp3) 2074 2075 2076 2077 2078 return zero_coupling, iden_coupling
2079
2080 - def get_coupling_order(self, cname):
2081 """return the coupling order associated to a coupling """ 2082 2083 if cname in self.coupling_order_dict: 2084 return self.coupling_order_dict[cname] 2085 2086 for v in self['interactions']: 2087 for c in v['couplings'].values(): 2088 self.coupling_order_dict[c] = v['orders'] 2089 2090 if cname not in self.coupling_order_dict: 2091 self.coupling_order_dict[cname] = None 2092 #can happen when some vertex are discarded due to ghost/... 2093 2094 2095 return self.coupling_order_dict[cname]
2096 2097 2098
2099 - def detect_special_parameters(self):
2100 """ return the list of (name of) parameter which are zero """ 2101 2102 null_parameters = [] 2103 one_parameters = [] 2104 for name, value in self['parameter_dict'].items(): 2105 if value == 0 and name != 'ZERO': 2106 null_parameters.append(name) 2107 elif value == 1: 2108 one_parameters.append(name) 2109 2110 return null_parameters, one_parameters
2111
2112 - def apply_conditional_simplifications(self, modified_params, 2113 modified_couplings):
2114 """ Apply the conditional statement simplifications for parameters and 2115 couplings detected by 'simplify_conditional_statements'. 2116 modified_params (modified_couplings) are list of tuples (a,b) with a 2117 parameter (resp. coupling) instance and b is the simplified expression.""" 2118 2119 if modified_params: 2120 logger.log(self.log_level, "Conditional expressions are simplified for parameters:") 2121 logger.log(self.log_level, ",".join("%s"%param[0].name for param in modified_params)) 2122 for param, new_expr in modified_params: 2123 param.expr = new_expr 2124 2125 if modified_couplings: 2126 logger.log(self.log_level, "Conditional expressions are simplified for couplings:") 2127 logger.log(self.log_level, ",".join("%s"%coupl[0].name for coupl in modified_couplings)) 2128 for coupl, new_expr in modified_couplings: 2129 coupl.expr = new_expr
2130
2131 - def detect_conditional_statements_simplifications(self, model_definitions, 2132 objects=['couplings','parameters']):
2133 """ Simplifies the 'if' statements in the pythonic UFO expressions 2134 of parameters using the default variables specified in the restrict card. 2135 It returns a list of objects (parameters or couplings) and the new 2136 expression that they should take. Model definitions include all definitons 2137 of the model functions and parameters.""" 2138 2139 param_modifications = [] 2140 coupl_modifications = [] 2141 ifparser = parsers.UFOExpressionParserPythonIF(model_definitions) 2142 2143 start_param = time.time() 2144 if 'parameters' in objects: 2145 for dependences, param_list in self['parameters'].items(): 2146 if 'external' in dependences: 2147 continue 2148 for param in param_list: 2149 new_expr, n_changes = ifparser.parse(param.expr) 2150 if n_changes > 0: 2151 param_modifications.append((param, new_expr)) 2152 2153 end_param = time.time() 2154 2155 if 'couplings' in objects: 2156 for dependences, coupl_list in self['couplings'].items(): 2157 for coupl in coupl_list: 2158 new_expr, n_changes = ifparser.parse(coupl.expr) 2159 if n_changes > 0: 2160 coupl_modifications.append((coupl, new_expr)) 2161 2162 end_coupl = time.time() 2163 2164 tot_param_time = end_param-start_param 2165 tot_coupl_time = end_coupl-end_param 2166 if tot_param_time>5.0: 2167 logger.log(self.log_level, "Simplification of conditional statements"+\ 2168 " in parameter expressions done in %s."%misc.format_time(tot_param_time)) 2169 if tot_coupl_time>5.0: 2170 logger.log(self.log_level, "Simplification of conditional statements"+\ 2171 " in couplings expressions done in %s."%misc.format_time(tot_coupl_time)) 2172 2173 return param_modifications, coupl_modifications
2174
2176 """ return the list of tuple of name of parameter with the same 2177 input value """ 2178 2179 # Extract external parameters 2180 external_parameters = self['parameters'][('external',)] 2181 2182 # define usefull variable to detect identical input 2183 block_value_to_var={} #(lhablok, value): list_of_var 2184 mult_param = set([]) # key of the previous dict with more than one 2185 #parameter. 2186 2187 #detect identical parameter and remove the duplicate parameter 2188 for param in external_parameters[:]: 2189 value = self['parameter_dict'][param.name] 2190 if value in [0,1,0.000001e-99,9.999999e-1]: 2191 continue 2192 if param.lhablock.lower() == 'decay': 2193 continue 2194 key = (param.lhablock, value) 2195 mkey = (param.lhablock, -value) 2196 2197 if key in block_value_to_var: 2198 block_value_to_var[key].append((param,1)) 2199 mult_param.add(key) 2200 elif mkey in block_value_to_var: 2201 block_value_to_var[mkey].append((param,-1)) 2202 mult_param.add(mkey) 2203 else: 2204 block_value_to_var[key] = [(param,1)] 2205 2206 output=[] 2207 for key in mult_param: 2208 output.append(block_value_to_var[key]) 2209 2210 return output
2211 2212 2213 @staticmethod
2214 - def get_new_coupling_name(main, coupling, value, coeff):
2215 """ We have main == coeff * coupling 2216 coeff is only +1 or -1 2217 main can be either GC_X or -GC_X 2218 coupling can be either GC_Y or -GC_Y 2219 value is either GC_Y or -GC_Y 2220 the return is either GC_X or -GC_X 2221 such that we have value == OUTPUT 2222 """ 2223 assert coeff in [-1,1] 2224 assert value == coupling or value == '-%s' % coupling or coupling == '-%s' % value 2225 assert isinstance(main, str) 2226 assert isinstance(coupling, str) 2227 assert isinstance(value, str) 2228 if coeff ==1: 2229 if value == coupling: 2230 return main # 4/4 2231 else: 2232 if main.startswith('-'): 2233 return main[1:] # 2/2 2234 else: 2235 return '-%s' % main # 2/2 2236 else: 2237 if value == coupling: 2238 if main.startswith('-'): 2239 return main[1:] # 2/2 2240 else: 2241 return '-%s' % main # 2/2 2242 else: 2243 return main # 4/4
2244 2245
2246 - def merge_iden_couplings(self, couplings):
2247 """merge the identical couplings in the interactions and particle 2248 counterterms""" 2249 2250 2251 logger_mod.log(self.log_level, ' Fuse the Following coupling (they have the same value): %s '% \ 2252 ', '.join([str(obj) for obj in couplings])) 2253 2254 main = couplings[0][0] 2255 assert couplings[0][1] == 1 2256 self.del_coup += [c[0] for c in couplings[1:]] # add the other coupl to the suppress list 2257 2258 for coupling, coeff in couplings[1:]: 2259 # check if param is linked to an interaction 2260 if coupling not in self.coupling_pos: 2261 continue 2262 # replace the coupling, by checking all coupling of the interaction 2263 vertices = [ vert for vert in self.coupling_pos[coupling] if 2264 isinstance(vert, base_objects.Interaction)] 2265 for vertex in vertices: 2266 for key, value in vertex['couplings'].items(): 2267 if value == coupling or value == '-%s' % coupling or coupling == '-%s' % value: 2268 vertex['couplings'][key] = self.get_new_coupling_name(\ 2269 main, coupling, value, coeff) 2270 2271 2272 2273 2274 # replace the coupling appearing in the particle counterterm 2275 particles_ct = [ pct for pct in self.coupling_pos[coupling] if 2276 isinstance(pct, tuple)] 2277 for pct in particles_ct: 2278 for key, value in pct[0]['counterterm'][pct[1]].items(): 2279 if value == coupling: 2280 pct[0]['counterterm'][pct[1]][key] = main
2281 2282 2283
2284 - def get_param_block(self):
2285 """return the list of block defined in the param_card""" 2286 2287 blocks = set([p.lhablock for p in self['parameters'][('external',)]]) 2288 return blocks
2289
2290 - def merge_iden_parameters(self, parameters, keep_external=False):
2291 """ merge the identical parameters given in argument. 2292 keep external force to keep the param_card untouched (up to comment)""" 2293 2294 logger_mod.log(self.log_level, 'Parameters set to identical values: %s '% \ 2295 ', '.join(['%s*%s' % (f, obj.name.replace('mdl_','')) for (obj,f) in parameters])) 2296 2297 # Extract external parameters 2298 external_parameters = self['parameters'][('external',)] 2299 for i, (obj, factor) in enumerate(parameters): 2300 # Keeped intact the first one and store information 2301 if i == 0: 2302 obj.info = 'set of param :' + \ 2303 ', '.join([str(f)+'*'+param.name.replace('mdl_','') 2304 for (param, f) in parameters]) 2305 expr = obj.name 2306 continue 2307 # Add a Rule linked to the param_card 2308 if factor ==1: 2309 self.rule_card.add_identical(obj.lhablock.lower(), obj.lhacode, 2310 parameters[0][0].lhacode ) 2311 else: 2312 self.rule_card.add_opposite(obj.lhablock.lower(), obj.lhacode, 2313 parameters[0][0].lhacode ) 2314 obj_name = obj.name 2315 # delete the old parameters 2316 if not keep_external: 2317 external_parameters.remove(obj) 2318 elif obj.lhablock.upper() in ['MASS','DECAY']: 2319 external_parameters.remove(obj) 2320 else: 2321 obj.name = '' 2322 obj.info = 'MG5 will not use this value use instead %s*%s' %(factor,expr) 2323 # replace by the new one pointing of the first obj of the class 2324 new_param = base_objects.ModelVariable(obj_name, '%s*%s' %(factor, expr), 'real') 2325 self['parameters'][()].insert(0, new_param) 2326 2327 # For Mass-Width, we need also to replace the mass-width in the particles 2328 #This allows some optimization for multi-process. 2329 if parameters[0][0].lhablock in ['MASS','DECAY']: 2330 new_name = parameters[0][0].name 2331 if parameters[0][0].lhablock == 'MASS': 2332 arg = 'mass' 2333 else: 2334 arg = 'width' 2335 change_name = [p.name for (p,f) in parameters[1:]] 2336 factor_for_name = [f for (p,f) in parameters[1:]] 2337 [p.set(arg, new_name) for p in self['particle_dict'].values() 2338 if p[arg] in change_name and 2339 factor_for_name[change_name.index(p[arg])]==1]
2340
2341 - def remove_interactions(self, zero_couplings):
2342 """ remove the interactions and particle counterterms 2343 associated to couplings""" 2344 2345 2346 mod_vertex = [] 2347 mod_particle_ct = [] 2348 for coup in zero_couplings: 2349 # some coupling might be not related to any interactions 2350 if coup not in self.coupling_pos: 2351 continue 2352 2353 # Remove the corresponding interactions. 2354 2355 vertices = [ vert for vert in self.coupling_pos[coup] if 2356 isinstance(vert, base_objects.Interaction) ] 2357 for vertex in vertices: 2358 modify = False 2359 for key, coupling in list(vertex['couplings'].items()): 2360 if coupling in zero_couplings: 2361 modify=True 2362 del vertex['couplings'][key] 2363 elif coupling.startswith('-'): 2364 coupling = coupling[1:] 2365 if coupling in zero_couplings: 2366 modify=True 2367 del vertex['couplings'][key] 2368 2369 if modify: 2370 mod_vertex.append(vertex) 2371 2372 # Remove the corresponding particle counterterm 2373 particles_ct = [ pct for pct in self.coupling_pos[coup] if 2374 isinstance(pct, tuple)] 2375 for pct in particles_ct: 2376 modify = False 2377 for key, coupling in list(pct[0]['counterterm'][pct[1]].items()): 2378 if coupling in zero_couplings: 2379 modify=True 2380 del pct[0]['counterterm'][pct[1]][key] 2381 if modify: 2382 mod_particle_ct.append(pct) 2383 2384 # print useful log and clean the empty interaction 2385 for vertex in mod_vertex: 2386 part_name = [part['name'] for part in vertex['particles']] 2387 orders = ['%s=%s' % (order,value) for order,value in vertex['orders'].items()] 2388 2389 if not vertex['couplings']: 2390 logger_mod.log(self.log_level, 'remove interactions: %s at order: %s' % \ 2391 (' '.join(part_name),', '.join(orders))) 2392 self['interactions'].remove(vertex) 2393 else: 2394 logger_mod.log(self.log_level, 'modify interactions: %s at order: %s' % \ 2395 (' '.join(part_name),', '.join(orders))) 2396 2397 # print useful log and clean the empty counterterm values 2398 for pct in mod_particle_ct: 2399 part_name = pct[0]['name'] 2400 order = pct[1][0] 2401 loop_parts = ','.join(['('+','.join([\ 2402 self.get_particle(p)['name'] for p in part])+')' \ 2403 for part in pct[1][1]]) 2404 2405 if not pct[0]['counterterm'][pct[1]]: 2406 logger_mod.log(self.log_level, 'remove counterterm of particle %s'%part_name+\ 2407 ' with loop particles (%s)'%loop_parts+\ 2408 ' perturbing order %s'%order) 2409 del pct[0]['counterterm'][pct[1]] 2410 else: 2411 logger_mod.log(self.log_level, 'Modify counterterm of particle %s'%part_name+\ 2412 ' with loop particles (%s)'%loop_parts+\ 2413 ' perturbing order %s'%order) 2414 2415 return
2416
2417 - def remove_couplings(self, couplings):
2418 #clean the coupling list: 2419 for name, data in self['couplings'].items(): 2420 for coupling in data[:]: 2421 if coupling.name in couplings: 2422 data.remove(coupling)
2423 2424
2425 - def fix_parameter_values(self, zero_parameters, one_parameters, 2426 simplify=True, keep_external=False):
2427 """ Remove all instance of the parameters in the model and replace it by 2428 zero when needed.""" 2429 2430 2431 # treat specific cases for masses and width 2432 for particle in self['particles']: 2433 if particle['mass'] in zero_parameters: 2434 particle['mass'] = 'ZERO' 2435 if particle['width'] in zero_parameters: 2436 particle['width'] = 'ZERO' 2437 if particle['width'] in one_parameters: 2438 one_parameters.remove(particle['width']) 2439 if particle['mass'] in one_parameters: 2440 one_parameters.remove(particle['mass']) 2441 2442 for pdg, particle in self['particle_dict'].items(): 2443 if particle['mass'] in zero_parameters: 2444 particle['mass'] = 'ZERO' 2445 if particle['width'] in zero_parameters: 2446 particle['width'] = 'ZERO' 2447 2448 2449 # Add a rule for zero/one parameter 2450 external_parameters = self['parameters'][('external',)] 2451 for param in external_parameters[:]: 2452 value = self['parameter_dict'][param.name] 2453 block = param.lhablock.lower() 2454 if value == 0: 2455 self.rule_card.add_zero(block, param.lhacode) 2456 elif value == 1: 2457 self.rule_card.add_one(block, param.lhacode) 2458 2459 special_parameters = zero_parameters + one_parameters 2460 2461 2462 2463 if simplify: 2464 # check if the parameters is still useful: 2465 re_str = '|'.join(special_parameters) 2466 if len(re_str) > 25000: # size limit on mac 2467 split = len(special_parameters) // 2 2468 re_str = ['|'.join(special_parameters[:split]), 2469 '|'.join(special_parameters[split:])] 2470 else: 2471 re_str = [ re_str ] 2472 used = set() 2473 for expr in re_str: 2474 re_pat = re.compile(r'''\b(%s)\b''' % expr) 2475 # check in coupling 2476 for name, coupling_list in self['couplings'].items(): 2477 for coupling in coupling_list: 2478 for use in re_pat.findall(coupling.expr): 2479 used.add(use) 2480 2481 # check in form-factor 2482 for lor in self['lorentz']: 2483 if hasattr(lor, 'formfactors') and lor.formfactors: 2484 for ff in lor.formfactors: 2485 for use in re_pat.findall(ff.value): 2486 used.add(use) 2487 else: 2488 used = set([i for i in special_parameters if i]) 2489 2490 # simplify the regular expression 2491 re_str = '|'.join([param for param in special_parameters if param not in used]) 2492 if len(re_str) > 25000: # size limit on mac 2493 split = len(special_parameters) // 2 2494 re_str = ['|'.join(special_parameters[:split]), 2495 '|'.join(special_parameters[split:])] 2496 else: 2497 re_str = [ re_str ] 2498 for expr in re_str: 2499 re_pat = re.compile(r'''\b(%s)\b''' % expr) 2500 2501 param_info = {} 2502 # check in parameters 2503 for dep, param_list in self['parameters'].items(): 2504 for tag, parameter in enumerate(param_list): 2505 # update information concerning zero/one parameters 2506 if parameter.name in special_parameters: 2507 param_info[parameter.name]= {'dep': dep, 'tag': tag, 2508 'obj': parameter} 2509 continue 2510 2511 # Bypass all external parameter 2512 if isinstance(parameter, base_objects.ParamCardVariable): 2513 continue 2514 2515 if simplify: 2516 for use in re_pat.findall(parameter.expr): 2517 used.add(use) 2518 2519 # modify the object for those which are still used 2520 for param in used: 2521 if not param: 2522 continue 2523 data = self['parameters'][param_info[param]['dep']] 2524 data.remove(param_info[param]['obj']) 2525 tag = param_info[param]['tag'] 2526 data = self['parameters'][()] 2527 if param in zero_parameters: 2528 data.insert(0, base_objects.ModelVariable(param, '0.0', 'real')) 2529 else: 2530 data.insert(0, base_objects.ModelVariable(param, '1.0', 'real')) 2531 2532 # remove completely useless parameters 2533 for param in special_parameters: 2534 #by pass parameter still in use 2535 if param in used or \ 2536 (keep_external and param_info[param]['dep'] == ('external',)): 2537 logger_mod.log(self.log_level, 'fix parameter value: %s' % param) 2538 continue 2539 logger_mod.log(self.log_level,'remove parameters: %s' % (param)) 2540 data = self['parameters'][param_info[param]['dep']] 2541 data.remove(param_info[param]['obj'])
2542 2543
2544 - def optimise_interaction(self, interaction):
2545 2546 # we want to check if the same coupling (up to the sign) is used for two lorentz structure 2547 # for the same color structure. 2548 to_lor = {} 2549 for (color, lor), coup in interaction['couplings'].items(): 2550 abscoup, coeff = (coup[1:],-1) if coup.startswith('-') else (coup, 1) 2551 key = (color, abscoup) 2552 if key in to_lor: 2553 to_lor[key].append((lor,coeff)) 2554 else: 2555 to_lor[key] = [(lor,coeff)] 2556 2557 nb_reduce = [] 2558 optimize = False 2559 for key in to_lor: 2560 if len(to_lor[key]) >1: 2561 nb_reduce.append(len(to_lor[key])-1) 2562 optimize = True 2563 2564 if not optimize: 2565 return 2566 2567 if not hasattr(self, 'defined_lorentz_expr'): 2568 self.defined_lorentz_expr = {} 2569 self.lorentz_info = {} 2570 self.lorentz_combine = {} 2571 for lor in self.get('lorentz'): 2572 self.defined_lorentz_expr[lor.get('structure')] = lor.get('name') 2573 self.lorentz_info[lor.get('name')] = lor #(lor.get('structure'), lor.get('spins')) 2574 2575 for key in to_lor: 2576 if len(to_lor[key]) == 1: 2577 continue 2578 names = ['u%s' % interaction['lorentz'][i[0]] if i[1] ==1 else \ 2579 'd%s' % interaction['lorentz'][i[0]] for i in to_lor[key]] 2580 2581 names.sort() 2582 2583 # get name of the new lorentz 2584 if tuple(names) in self.lorentz_combine: 2585 # already created new loretnz 2586 new_name = self.lorentz_combine[tuple(names)] 2587 else: 2588 new_name = self.add_merge_lorentz(names) 2589 2590 # remove the old couplings 2591 color, coup = key 2592 to_remove = [(color, lor[0]) for lor in to_lor[key]] 2593 for rm in to_remove: 2594 del interaction['couplings'][rm] 2595 2596 #add the lorentz structure to the interaction 2597 if new_name not in [l for l in interaction.get('lorentz')]: 2598 interaction.get('lorentz').append(new_name) 2599 2600 #find the associate index 2601 new_l = interaction.get('lorentz').index(new_name) 2602 # adding the new combination (color,lor) associate to this sum of structure 2603 interaction['couplings'][(color, new_l)] = coup
2604 2605 2606
2607 - def add_merge_lorentz(self, names):
2608 """add a lorentz structure which is the sume of the list given above""" 2609 2610 #create new_name 2611 ii = len(names[0]) 2612 while ii>1: 2613 #do not count the initial "u/d letter whcih indicates the sign" 2614 if not all(n[1:].startswith(names[0][1:ii]) for n in names[1:]): 2615 ii -=1 2616 else: 2617 base_name = names[0][1:ii] 2618 break 2619 else: 2620 base_name = 'LMER' 2621 i = 1 2622 while '%s%s' %(base_name, i) in self.lorentz_info: 2623 i +=1 2624 new_name = '%s%s' %(base_name, i) 2625 self.lorentz_combine[tuple(names)] = new_name 2626 2627 # load the associate lorentz expression 2628 new_struct = ' + '.join([self.lorentz_info[n[1:]].get('structure') for n in names if n.startswith('u')]) 2629 if any( n.startswith('d') for n in names ): 2630 new_struct += '-' + ' - '.join(['1.*(%s)' %self.lorentz_info[n[1:]].get('structure') for n in names if n.startswith('d')]) 2631 spins = self.lorentz_info[names[0][1:]].get('spins') 2632 formfact = sum([ self.lorentz_info[n[1:]].get('formfactors') for n in names \ 2633 if hasattr(self.lorentz_info[n[1:]], 'formfactors') \ 2634 and self.lorentz_info[n[1:]].get('formfactors') \ 2635 ],[]) 2636 2637 2638 2639 2640 new_lor = self.add_lorentz(new_name, spins, new_struct, formfact) 2641 self.lorentz_info[new_name] = new_lor 2642 2643 return new_name
2644
2645 - def add_lorentz(self, name, spin, struct, formfact=None):
2646 """adding lorentz structure to the current model""" 2647 new = self['lorentz'][0].__class__(name = name, 2648 spins = spin, 2649 structure = struct) 2650 if formfact: 2651 new.formfactors = formfact 2652 self['lorentz'].append(new) 2653 self.create_lorentz_dict() 2654 2655 return None
2656