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