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