Package madgraph :: Package fks :: Module fks_base
[hide private]
[frames] | no frames]

Source Code for Module madgraph.fks.fks_base

  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   
 16  """Definitions of the objects needed for the implementation of MadFKS""" 
 17   
 18  from __future__ import absolute_import 
 19  from __future__ import print_function 
 20  import madgraph.core.base_objects as MG 
 21  import madgraph.core.helas_objects as helas_objects 
 22  import madgraph.core.diagram_generation as diagram_generation 
 23  import madgraph.core.color_amp as color_amp 
 24  import madgraph.core.color_algebra as color_algebra 
 25  import madgraph.loop.loop_diagram_generation as loop_diagram_generation 
 26  import madgraph.fks.fks_common as fks_common 
 27  import copy 
 28  import logging 
 29  import array 
 30  import madgraph.various.misc as misc 
 31  from madgraph import InvalidCmd 
 32  from six.moves import range 
 33   
 34  logger = logging.getLogger('madgraph.fks_base') 
 35   
 36   
37 -class NoBornException(Exception): pass
38 39 #=============================================================================== 40 # FKS Process 41 #=============================================================================== 42 43
44 -class FKSMultiProcess(diagram_generation.MultiProcess): #test written
45 """A multi process class that contains informations on the born processes 46 and the reals. 47 """ 48
49 - def default_setup(self):
50 """Default values for all properties""" 51 super(FKSMultiProcess, self).default_setup() 52 self['real_amplitudes'] = diagram_generation.AmplitudeList() 53 self['pdgs'] = [] 54 self['born_processes'] = FKSProcessList() 55 56 if not 'OLP' in list(self.keys()): 57 self['OLP'] = 'MadLoop' 58 self['ncores_for_proc_gen'] = 0
59
60 - def get_sorted_keys(self):
61 """Return particle property names as a nicely sorted list.""" 62 keys = super(FKSMultiProcess, self).get_sorted_keys() 63 keys += ['born_processes', 'real_amplitudes', 'real_pdgs', 'has_isr', 64 'has_fsr', 'spltting_types', 'OLP', 'ncores_for_proc_gen'] 65 return keys
66
67 - def filter(self, name, value):
68 """Filter for valid leg property values.""" 69 70 if name == 'born_processes': 71 if not isinstance(value, FKSProcessList): 72 raise self.PhysicsObjectError("%s is not a valid list for born_processes " % str(value)) 73 74 if name == 'real_amplitudes': 75 if not isinstance(value, diagram_generation.AmplitudeList): 76 raise self.PhysicsObjectError("%s is not a valid list for real_amplitudes " % str(value)) 77 78 if name == 'real_pdgs': 79 if not isinstance(value, list): 80 raise self.PhysicsObjectError("%s is not a valid list for real_amplitudes " % str(value)) 81 82 if name == 'OLP': 83 if not isinstance(value,str): 84 raise self.PhysicsObjectError("%s is not a valid string for OLP " % str(value)) 85 86 if name == 'ncores_for_proc_gen': 87 if not isinstance(value,int): 88 raise self.PhysicsObjectError("%s is not a valid value for ncores_for_proc_gen " % str(value)) 89 90 return super(FKSMultiProcess,self).filter(name, value)
91 92
93 - def check_ij_confs(self):
94 """check that there is no duplicate FKS ij configuration""" 95 ijconfs_dict = {} 96 for born in self['born_processes']: 97 # the copy.copy is needed as duplicate configurations will be removed on the fly 98 for real in copy.copy(born.real_amps): 99 pdgs = ' '.join([ '%d' % pdg for pdg in real.pdgs]) 100 for info in copy.copy(real.fks_infos): 101 ij = [info['i'], info['j']] 102 try: 103 if ij in ijconfs_dict[pdgs]: 104 logger.debug('Duplicate FKS configuration found for %s : ij = %s' % 105 (real.process.nice_string(), str(ij))) 106 #remove the configuration 107 born.real_amps[born.real_amps.index(real)].fks_infos.remove(info) 108 else: 109 ijconfs_dict[pdgs].append(ij) 110 except KeyError: 111 ijconfs_dict[pdgs] = [ij] 112 # check if any FKS configuration remains for the real emission, otherwise 113 # remove it 114 if not born.real_amps[born.real_amps.index(real)].fks_infos: 115 logger.debug('Removing real %s from born %s' % \ 116 (real.process.nice_string(), born.born_amp['process'].nice_string())) 117 born.real_amps.remove(real)
118 119
120 - def __init__(self, procdef=None, options={}):
121 """Initializes the original multiprocess, then generates the amps for the 122 borns, then generate the born processes and the reals. 123 Real amplitudes are stored in real_amplitudes according on the pdgs of their 124 legs (stored in pdgs, so that they need to be generated only once and then reicycled 125 """ 126 127 128 if 'nlo_mixed_expansion' in options: 129 self['nlo_mixed_expansion'] = options['nlo_mixed_expansion'] 130 del options['nlo_mixed_expansion'] 131 132 133 #swhich the other loggers off 134 loggers_off = [logging.getLogger('madgraph.diagram_generation'), 135 logging.getLogger('madgraph.loop_diagram_generation')] 136 old_levels = [logg.level for logg in loggers_off] 137 for logg in loggers_off: 138 logg.setLevel(logging.WARNING) 139 140 self['real_amplitudes'] = diagram_generation.AmplitudeList() 141 self['pdgs'] = [] 142 143 # OLP option 144 olp='MadLoop' 145 if 'OLP' in list(options.keys()): 146 olp = options['OLP'] 147 del options['OLP'] 148 149 self['init_lep_split']=False 150 if 'init_lep_split' in list(options.keys()): 151 self['init_lep_split']=options['init_lep_split'] 152 del options['init_lep_split'] 153 154 ncores_for_proc_gen = 0 155 # ncores_for_proc_gen has the following meaning 156 # 0 : do things the old way 157 # > 0 use ncores_for_proc_gen 158 # -1 : use all cores 159 if 'ncores_for_proc_gen' in list(options.keys()): 160 ncores_for_proc_gen = options['ncores_for_proc_gen'] 161 del options['ncores_for_proc_gen'] 162 163 try: 164 # Now generating the borns for the first time. 165 super(FKSMultiProcess, self).__init__(procdef, **options) 166 167 except diagram_generation.NoDiagramException as error: 168 # If no born, then this process most likely does not have any. 169 raise NoBornException("Born diagrams could not be generated for the "+\ 170 self['process_definitions'][0].nice_string().replace('Process',\ 171 'process')+". Notice that aMC@NLO does not handle loop-induced"+\ 172 " processes yet, but you can still use MadLoop if you want to "+\ 173 "only generate them."+\ 174 " For this, use the 'virt=' mode, without multiparticle labels.") 175 176 self['OLP'] = olp 177 self['ncores_for_proc_gen'] = ncores_for_proc_gen 178 179 #check process definition(s): 180 # a process such as g g > g g will lead to real emissions 181 # (e.g: u g > u g g ) which will miss some corresponding born, 182 # leading to non finite results 183 perturbation = [] 184 for procdef in self['process_definitions']: 185 soft_particles = [] 186 # do not warn for decay processes 187 if [ i['state'] for i in procdef['legs']].count(False) == 1: 188 continue 189 for pert in procdef['perturbation_couplings']: 190 if pert not in perturbation: 191 perturbation.append(pert) 192 soft_particles.extend(\ 193 fks_common.find_pert_particles_interactions(\ 194 procdef['model'], pert)['soft_particles']) 195 soft_particles_string = ', '.join( \ 196 [procdef['model'].get('particle_dict')[id][\ 197 {True:'name', False:'antiname'}[id >0] ] \ 198 for id in sorted(soft_particles, reverse=True)]) 199 for leg in procdef['legs']: 200 if any([id in soft_particles for id in leg['ids']]) \ 201 and sorted(leg['ids']) != soft_particles: 202 logger.warning('Use of multiparticles is non-trivial for NLO '+ \ 203 'process generation and depends on the orders included, '+ \ 204 'the process considered, as well as the PDF set chosen. '+ \ 205 'See appendix D of arXiv:1804.10017 [hep-ph] for some '+ \ 206 'guidance.') 207 break 208 209 amps = self.get('amplitudes') 210 211 # get the list of leptons from the model, in order to discard 212 # lepton-initiated processes unless the init_lep_split flag is specified 213 if self['process_definitions']: 214 leptons = self['process_definitions'][0]['model'].get_lepton_pdgs() 215 else: 216 leptons = [] 217 218 #generate reals, but combine them after having combined the borns 219 for i, amp in enumerate(amps): 220 # skip amplitudes with two initial leptons unless the init_lep_split option is True 221 if not self['init_lep_split'] and \ 222 all([l['id'] in leptons \ 223 for l in [ll for ll in amp.get('process').get('legs') if not ll['state']]]): 224 logger.info(('Discarding process%s.\n If you want to include it, set the \n' + \ 225 ' \'include_lepton_initiated_processes\' option to True') % \ 226 amp.get('process').nice_string().replace('Process', '')) 227 continue 228 229 logger.info("Generating FKS-subtracted matrix elements for born process%s (%d / %d)" \ 230 % (amp['process'].nice_string(print_weighted=False, print_perturbated=False).replace('Process', ''), 231 i + 1, len(amps))) 232 233 born = FKSProcess(amp, ncores_for_proc_gen = self['ncores_for_proc_gen'], \ 234 init_lep_split=self['init_lep_split']) 235 self['born_processes'].append(born) 236 237 born.generate_reals(self['pdgs'], self['real_amplitudes'], combine = False) 238 239 # finally combine the real amplitudes 240 born.combine_real_amplitudes() 241 242 if not self['ncores_for_proc_gen']: 243 # old generation mode 244 245 born_pdg_list = [[l['id'] for l in born.get_leglist()] \ 246 for born in self['born_processes'] ] 247 248 for born in self['born_processes']: 249 for real in born.real_amps: 250 real.find_fks_j_from_i(born_pdg_list) 251 if amps: 252 if self['process_definitions'][0].get('NLO_mode') in ['all']: 253 self.generate_virtuals() 254 255 elif not self['process_definitions'][0].get('NLO_mode') in ['all', 'real','LOonly']: 256 raise fks_common.FKSProcessError(\ 257 "Not a valid NLO_mode for a FKSMultiProcess: %s" % \ 258 self['process_definitions'][0].get('NLO_mode')) 259 260 # now get the total number of diagrams 261 n_diag_born = sum([len(amp.get('diagrams')) 262 for amp in self.get_born_amplitudes()]) 263 n_diag_real = sum([len(amp.get('diagrams')) 264 for amp in self.get_real_amplitudes()]) 265 n_diag_virt = sum([len(amp.get('loop_diagrams')) 266 for amp in self.get_virt_amplitudes()]) 267 268 if n_diag_virt == 0 and n_diag_real ==0 and \ 269 not self['process_definitions'][0].get('NLO_mode') == 'LOonly': 270 raise fks_common.FKSProcessError( 271 'This process does not have any correction up to NLO in %s'\ 272 %','.join(perturbation)) 273 274 logger.info(('Generated %d subprocesses with %d real emission diagrams, ' + \ 275 '%d born diagrams and %d virtual diagrams') % \ 276 (len(self['born_processes']), n_diag_real, n_diag_born, n_diag_virt)) 277 278 for i, logg in enumerate(loggers_off): 279 logg.setLevel(old_levels[i]) 280 281 self['has_isr'] = any([proc.isr for proc in self['born_processes']]) 282 self['has_fsr'] = any([proc.fsr for proc in self['born_processes']])
283
284 - def add(self, other):
285 """combines self and other, extending the lists of born/real amplitudes""" 286 self['process_definitions'].extend(other['process_definitions']) 287 self['amplitudes'].extend(other['amplitudes']) 288 self['born_processes'].extend(other['born_processes']) 289 self['real_amplitudes'].extend(other['real_amplitudes']) 290 self['pdgs'].extend(other['pdgs']) 291 self['has_isr'] = self['has_isr'] or other['has_isr'] 292 self['has_fsr'] = self['has_fsr'] or other['has_fsr'] 293 self['OLP'] = other['OLP'] 294 self['ncores_for_proc_gen'] = other['ncores_for_proc_gen']
295 296
297 - def get_born_amplitudes(self):
298 """return an amplitudelist with the born amplitudes""" 299 return diagram_generation.AmplitudeList([ 300 born.born_amp for \ 301 born in self['born_processes']])
302 303
304 - def get_virt_amplitudes(self):
305 """return an amplitudelist with the virt amplitudes""" 306 return diagram_generation.AmplitudeList([born.virt_amp \ 307 for born in self['born_processes'] if born.virt_amp])
308 309
310 - def get_real_amplitudes(self):
311 """return an amplitudelist with the real amplitudes""" 312 return self.get('real_amplitudes')
313 314
315 - def generate_virtuals(self):
316 """For each process among the born_processes, creates the corresponding 317 virtual amplitude""" 318 319 # If not using MadLoop, then the LH order file generation and processing 320 # will be entirely done during the output, so nothing must be done at 321 # this stage yet. 322 if self['OLP']!='MadLoop': 323 logger.info("The loop matrix elements will be generated by "+\ 324 '%s at the output stage only.'%self['OLP']) 325 return 326 327 if not self['nlo_mixed_expansion']: 328 # determine the orders to be used to generate the loop 329 loop_orders = {} 330 for born in self['born_processes']: 331 for coup, val in fks_common.find_orders(born.born_amp).items(): 332 try: 333 loop_orders[coup] = max([loop_orders[coup], val]) 334 except KeyError: 335 loop_orders[coup] = val 336 337 338 for i, born in enumerate(self['born_processes']): 339 myproc = copy.copy(born.born_amp['process']) 340 #misc.sprint(born.born_proc) 341 #misc.sprint(myproc.input_string()) 342 #misc.sprint(myproc['orders']) 343 # if [orders] are not specified, then 344 # include all particles in the loops 345 # i.e. allow all orders to be perturbed 346 # (this is the case for EW corrections, where only squared oders 347 # are imposed) 348 if not self['nlo_mixed_expansion']: 349 myproc['orders'] = loop_orders 350 elif not myproc['orders']: 351 myproc['perturbation_couplings'] = myproc['model']['coupling_orders'] 352 # take the orders that are actually used bu the matrix element 353 myproc['legs'] = fks_common.to_legs(copy.copy(myproc['legs'])) 354 logger.info('Generating virtual matrix element with MadLoop for process%s (%d / %d)' \ 355 % (myproc.nice_string(print_weighted= False, print_perturbated= False).replace(\ 356 'Process', ''), 357 i + 1, len(self['born_processes']))) 358 try: 359 myamp = loop_diagram_generation.LoopAmplitude(myproc) 360 born.virt_amp = myamp 361 except InvalidCmd: 362 logger.debug('invalid command for loop') 363 pass
364 365
366 -class FKSRealProcess(object):
367 """Contains information about a real process: 368 -- fks_infos (list containing the possible fks configs for a given process 369 -- amplitude 370 -- is_to_integrate 371 """ 372
373 - def __init__(self, born_proc, leglist, ij, ij_id, born_pdgs, splitting_type, 374 perturbed_orders = ['QCD']): #test written
375 """Initializes the real process based on born_proc and leglist. 376 Stores the fks informations into the list of dictionaries fks_infos 377 """ 378 #safety check 379 assert type(splitting_type) == list and not type(splitting_type) == str 380 self.fks_infos = [] 381 for leg in leglist: 382 if leg.get('fks') == 'i': 383 i_fks = leg.get('number') 384 # i is a gluon or a photon 385 need_color_links = leg.get('massless') \ 386 and leg.get('spin') == 3 \ 387 and leg.get('self_antipart') \ 388 and leg.get('color') == 8 389 need_charge_links = leg.get('massless') \ 390 and leg.get('spin') == 3 \ 391 and leg.get('self_antipart') \ 392 and leg.get('color') == 1 393 if leg.get('fks') == 'j': 394 j_fks = leg.get('number') 395 self.fks_infos.append({'i': i_fks, 396 'j': j_fks, 397 'ij': ij, 398 'ij_id': ij_id, 399 'underlying_born': born_pdgs, 400 'splitting_type': splitting_type, 401 'need_color_links': need_color_links, 402 'need_charge_links': need_charge_links, 403 'extra_cnt_index': -1}) 404 405 self.process = copy.copy(born_proc) 406 self.process['perturbation_couplings'] = \ 407 copy.copy(born_proc['perturbation_couplings']) 408 for o in splitting_type: 409 if o not in self.process['perturbation_couplings']: 410 self.process['perturbation_couplings'].append(o) 411 # set the orders to empty, to force the use of the squared_orders 412 self.process['orders'] = copy.copy(born_proc['orders']) 413 414 legs = [(leg.get('id'), leg) for leg in leglist] 415 self.pdgs = array.array('i',[s[0] for s in legs]) 416 self.colors = [leg['color'] for leg in leglist] 417 if not self.process['perturbation_couplings'] == ['QCD']: 418 self.charges = [leg['charge'] for leg in leglist] 419 else: 420 self.charges = [0.] * len(leglist) 421 self.perturbation = 'QCD' 422 self.process.set('legs', MG.LegList(leglist)) 423 self.process.set('legs_with_decays', MG.LegList()) 424 self.amplitude = diagram_generation.Amplitude() 425 self.is_to_integrate = True 426 self.is_nbody_only = False 427 self.fks_j_from_i = {} 428 self.missing_borns = [] 429 430
431 - def generate_real_amplitude(self):
432 """generates the real emission amplitude starting from self.process""" 433 self.amplitude = diagram_generation.Amplitude(self.process) 434 return self.amplitude
435 436
437 - def find_fks_j_from_i(self, born_pdg_list): #test written
438 """Returns a dictionary with the entries i : [j_from_i], if the born pdgs are in 439 born_pdg_list""" 440 fks_j_from_i = {} 441 for i in self.process.get('legs'): 442 fks_j_from_i[i.get('number')] = [] 443 if i.get('state'): 444 for j in [l for l in self.process.get('legs') if \ 445 l.get('number') != i.get('number')]: 446 for pert_order in self.process.get('perturbation_couplings'): 447 ijlist = fks_common.combine_ij(i, j, self.process.get('model'), {},\ 448 pert=pert_order) 449 for ij in ijlist: 450 born_leglist = fks_common.to_fks_legs( 451 copy.deepcopy(self.process.get('legs')), 452 self.process.get('model')) 453 born_leglist.remove(i) 454 born_leglist.remove(j) 455 born_leglist.insert(ij.get('number') - 1, ij) 456 born_leglist.sort(pert = self.perturbation) 457 if [leg['id'] for leg in born_leglist] in born_pdg_list \ 458 and not j.get('number') in fks_j_from_i[i.get('number')]: 459 fks_j_from_i[i.get('number')].append(\ 460 j.get('number')) 461 462 self.fks_j_from_i = fks_j_from_i 463 return fks_j_from_i 464 465
466 - def get_leg_i(self): #test written
467 """Returns leg corresponding to i fks. 468 An error is raised if the fks_infos list has more than one entry""" 469 if len(self.fks_infos) > 1: 470 raise fks_common.FKSProcessError(\ 471 'get_leg_i should only be called before combining processes') 472 return self.process.get('legs')[self.fks_infos[0]['i'] - 1] 473
474 - def get_leg_j(self): #test written
475 """Returns leg corresponding to j fks. 476 An error is raised if the fks_infos list has more than one entry""" 477 if len(self.fks_infos) > 1: 478 raise fks_common.FKSProcessError(\ 479 'get_leg_j should only be called before combining processes') 480 return self.process.get('legs')[self.fks_infos[0]['j'] - 1] 481 482
483 -class FKSProcessList(MG.PhysicsObjectList):
484 """Class to handle lists of FKSProcesses.""" 485
486 - def is_valid_element(self, obj):
487 """Test if object obj is a valid FKSProcess for the list.""" 488 return isinstance(obj, FKSProcess)
489 490
491 -class FKSProcess(object):
492 """The class for a FKS process. Starts from the born process and finds 493 all the possible splittings.""" 494 495 496 #helper functions 497
498 - def get_colors(self):
499 """return the list of color representations 500 for each leg in born_amp""" 501 return [leg.get('color') for \ 502 leg in self.born_amp['process']['legs']]
503 504
505 - def get_charges(self):
506 """return the list of charges 507 for each leg in born_amp""" 508 return [leg.get('charge') for \ 509 leg in self.born_amp['process']['legs']]
510 511
512 - def get_nlegs(self):
513 """return the number of born legs""" 514 return len(self.born_amp['process']['legs'])
515 516
517 - def get_born_nice_string(self):
518 """Return the nice string for the born process. 519 """ 520 return self.born_amp['process'].nice_string()
521 522
523 - def get_pdg_codes(self):
524 """return the list of the pdg codes 525 of each leg in born_amp""" 526 return [leg.get('id') for \ 527 leg in self.born_amp['process']['legs']]
528 529
530 - def get_leglist(self):
531 """return the leg list 532 for the born amp""" 533 return fks_common.to_fks_legs( \ 534 self.born_amp['process']['legs'], \ 535 self.born_amp['process']['model'])
536 537 538 ############################################################################### 539
540 - def __init__(self, start_proc = None, remove_reals = True, ncores_for_proc_gen=0, init_lep_split = False):
541 """initialization: starts either from an amplitude or a process, 542 then init the needed variables. 543 remove_borns tells if the borns not needed for integration will be removed 544 from the born list (mainly used for testing) 545 ncores_for_proc_gen has the following meaning 546 0 : do things the old way 547 > 0 use ncores_for_proc_gen 548 -1 : use all cores 549 """ 550 551 self.reals = [] 552 self.myorders = {} 553 self.real_amps = [] 554 self.remove_reals = remove_reals 555 self.init_lep_split = init_lep_split 556 self.nincoming = 0 557 self.virt_amp = None 558 self.perturbation = 'QCD' 559 self.born_amp = diagram_generation.Amplitude() 560 self.extra_cnt_amp_list = diagram_generation.AmplitudeList() 561 self.ncores_for_proc_gen = ncores_for_proc_gen 562 563 if not remove_reals in [True, False]: 564 raise fks_common.FKSProcessError(\ 565 'Not valid type for remove_reals in FKSProcess') 566 567 if start_proc: 568 #initilaize with process definition (for test purporses) 569 if isinstance(start_proc, MG.Process): 570 pertur = start_proc['perturbation_couplings'] 571 if pertur: 572 self.perturbation = sorted(pertur)[0] 573 self.born_amp = diagram_generation.Amplitude(\ 574 copy.copy(fks_common.sort_proc(\ 575 start_proc, pert = self.perturbation))) 576 #initialize with an amplitude 577 elif isinstance(start_proc, diagram_generation.Amplitude): 578 pertur = start_proc.get('process')['perturbation_couplings'] 579 self.born_amp = diagram_generation.Amplitude(\ 580 copy.copy(fks_common.sort_proc(\ 581 start_proc['process'], 582 pert = self.perturbation))) 583 else: 584 raise fks_common.FKSProcessError(\ 585 'Not valid start_proc in FKSProcess') 586 self.born_amp['process'].set('legs_with_decays', MG.LegList()) 587 588 # special treatment of photon is needed ! 589 #MZ to be fixed 590 ###self.isr = set([leg.get(color) for leg in self.leglist if not leg.get('state')]) != set([zero]) 591 ###self.fsr = set([leg.get(color) for leg in self.leglist if leg.get('state')]) != set([zero]) 592 self.isr = False 593 self.fsr = False 594 ####### 595 self.nincoming = len([l for l in self.born_amp['process']['legs'] \ 596 if not l['state']]) 597 598 self.ndirs = 0 599 # generate reals, when the mode is not LOonly 600 # when is LOonly it is supposed to be a 'fake' NLO process 601 # e.g. to be used in merged sampels at high multiplicities 602 if self.born_amp['process']['NLO_mode'] != 'LOonly': 603 self.find_reals()
604 605
606 - def generate_real_amplitudes(self, pdg_list, real_amp_list):
607 """generates the real amplitudes for all the real emission processes, using pdgs and real_amps 608 to avoid multiple generation of the same amplitude. 609 Amplitude without diagrams are discarded at this stage""" 610 611 no_diags_amps = [] 612 for amp in self.real_amps: 613 try: 614 amp.amplitude = real_amp_list[pdg_list.index(amp.pdgs)] 615 except ValueError: 616 amplitude = amp.generate_real_amplitude() 617 if amplitude['diagrams']: 618 pdg_list.append(amp.pdgs) 619 real_amp_list.append(amplitude) 620 else: 621 no_diags_amps.append(amp) 622 623 for amp in no_diags_amps: 624 self.real_amps.remove(amp)
625 626 627
628 - def combine_real_amplitudes(self):
629 """combines real emission processes if the pdgs are the same, combining the lists 630 of fks_infos""" 631 pdgs = [] 632 real_amps = [] 633 old_real_amps = copy.copy(self.real_amps) 634 for amp in old_real_amps: 635 try: 636 real_amps[pdgs.index(amp.pdgs)].fks_infos.extend(amp.fks_infos) 637 except ValueError: 638 real_amps.append(amp) 639 pdgs.append(amp.pdgs) 640 641 self.real_amps = real_amps
642 643
644 - def generate_reals(self, pdg_list, real_amp_list, combine=True): #test written
645 """For all the possible splittings, creates an FKSRealProcess. 646 It removes double counted configorations from the ones to integrates and 647 sets the one which includes the bosn (is_nbody_only). 648 if combine is true, FKS_real_processes having the same pdgs (i.e. real amplitude) 649 are combined together 650 """ 651 #copy the born process 652 born_proc = copy.copy(self.born_amp['process']) 653 born_pdgs = self.get_pdg_codes() 654 leglist = self.get_leglist() 655 extra_cnt_pdgs = [] 656 for i, real_list in enumerate(self.reals): 657 # i is the born leg which splits 658 # keep track of the id of the mother (will be used to constrct the 659 # spin-correlated borns) 660 ij_id = leglist[i].get('id') 661 ij = leglist[i].get('number') 662 for real_dict in real_list: 663 nmom = 0 664 665 # check first if other counterterms need to be generated 666 # (e.g. g/a > q qbar) 667 # this is quite a tricky business, as double counting 668 # the singular configuration must be avoided. 669 # Let real, born, cnt be the real emission, the born process 670 # (that will give the name to the P0_** dir) and the 671 # extra counterterm (obtained by the born process replacing 672 # ij with the extra mother). 673 # If there are extra mothers, first check that 674 # 1) born, at order born[squared_orders] - 675 # 2 * (the perturbation type of the real emission) has diagrams 676 # 2) cnt at order born[squared_orders] - 677 # 2 * (the perturbation type of the extra mom) has diagrams 678 679 cnt_amp = diagram_generation.Amplitude() 680 born_cnt_amp = diagram_generation.Amplitude() 681 mom_cnt = 0 682 cnt_ord = None 683 684 # check condition 1) above (has_coll_sing_born) 685 born_proc_coll_sing = copy.copy(born_proc) 686 born_proc_coll_sing['squared_orders'] = copy.copy(born_proc['squared_orders']) 687 if born_proc_coll_sing['squared_orders'][real_dict['perturbation'][0]] < 2: 688 has_coll_sing_born = False 689 else: 690 born_proc_coll_sing['squared_orders'][real_dict['perturbation'][0]] += -2 691 has_coll_sing_born = bool(diagram_generation.Amplitude(born_proc_coll_sing)['diagrams']) 692 693 # check that there is at most one extra mother 694 allmothers = [] 695 for order, mothers in real_dict['extra_mothers'].items(): 696 allmothers += mothers 697 if mothers: 698 cnt_ord = order 699 700 if len(allmothers) > 1: 701 raise fks_common.FKSProcessError(\ 702 'Error, more than one extra mother has been found: %d', len(allmothers)) 703 # here we are sure to have just one extra mother 704 705 has_coll_sing_cnt = False 706 if allmothers: 707 mom_cnt = allmothers[0] 708 709 # generate a new process with the mother particle 710 # replaced by the new mother and with the 711 # squared orders changed accordingly 712 713 cnt_process = copy.copy(born_proc) 714 cnt_process['legs'] = copy.deepcopy(born_proc['legs']) 715 cnt_process['legs'][i]['id'] = mom_cnt 716 cnt_process['legs'] = fks_common.to_fks_legs( 717 cnt_process['legs'], cnt_process['model']) 718 cnt_process['squared_orders'] = \ 719 copy.copy(born_proc['squared_orders']) 720 721 # check if the cnt amplitude exists with the current 722 # squared orders (i.e. if it will appear as a P0 dir) 723 # if it does not exist, then no need to worry about anything, as all further 724 # checks will have stricter orders than here 725 726 cnt_process_for_amp = copy.copy(cnt_process) 727 cnt_process_for_amp['squared_orders'] = copy.copy(cnt_process['squared_orders']) 728 cnt_amp = diagram_generation.Amplitude(cnt_process_for_amp) 729 730 if bool(cnt_amp['diagrams']) and \ 731 cnt_process['squared_orders'][cnt_ord] >= 2: 732 733 # check condition 2) above (has_coll_sing_cnt) 734 # MZMZ17062014 beware that the Amplitude reorders the legs 735 cnt_process['squared_orders'][cnt_ord] += -2 736 born_cnt_amp = diagram_generation.Amplitude(cnt_process) 737 has_coll_sing_cnt = bool(born_cnt_amp['diagrams']) 738 739 # remember there is at most one mom 740 # now, one of these cases can occur 741 # a) no real collinear singularity exists (e.g. just the interference 742 # has to be integrated for the real emission). Add the real process to 743 # this born process if ij_id < mom_cnt. No extra_cnt is needed 744 # b) a collinear singularity exists, with the underlying born being the born 745 # process of this dir, while no singularity is there for the extra_cnt. 746 # In this case keep the real emission in this directory, without any 747 # extra_cnt 748 # c) a collinear singularity exists, with the underlying born being the 749 # extra_cnt, while no singularity is there for the born of the dir. 750 # In this case skip the real emission, it will be included in the 751 # directory of the extra cnt 752 # d) a collinear singularity exists, and both the process-dir born and 753 # the extra cnt are needed to subtract it. Add the real process to 754 # this born process if ij_id < mom_cnt and keeping the extra_cnt 755 # 756 # in all cases, remember that mom_cnt is set to 0 if no extra mother is there 757 758 # the real emission has to be skipped if mom_cnt(!=0) < ij_id and either a) or d) 759 if mom_cnt and mom_cnt < ij_id: 760 if ((not has_coll_sing_born and not has_coll_sing_cnt) or \ 761 (has_coll_sing_born and has_coll_sing_cnt)): 762 continue 763 764 # the real emission has also to be skipped in case of c) 765 if has_coll_sing_cnt and not has_coll_sing_born: 766 continue 767 768 # if we arrive here, we need to keep this real mession 769 ij = leglist[i].get('number') 770 self.real_amps.append(FKSRealProcess( \ 771 born_proc, real_dict['leglist'], ij, ij_id, \ 772 [born_pdgs], 773 real_dict['perturbation'], \ 774 perturbed_orders = born_proc['perturbation_couplings'])) 775 776 # keep the extra_cnt if needed 777 if has_coll_sing_cnt: 778 # for the moment we just check the pdgs, regardless of any 779 # permutation in the final state 780 try: 781 indx = extra_cnt_pdgs.index([l['id'] for l in cnt_process['legs']]) 782 except ValueError: 783 extra_cnt_pdgs.append([l['id'] for l in cnt_process['legs']]) 784 assert cnt_amp != None 785 self.extra_cnt_amp_list.append(cnt_amp) 786 indx = len(self.extra_cnt_amp_list) - 1 787 788 # update the fks infos 789 self.real_amps[-1].fks_infos[-1]['extra_cnt_index'] = indx 790 self.real_amps[-1].fks_infos[-1]['underlying_born'].append(\ 791 [l['id'] for l in cnt_process['legs']]) 792 self.real_amps[-1].fks_infos[-1]['splitting_type'].append(cnt_ord) 793 794 self.find_reals_to_integrate() 795 if combine: 796 self.combine_real_amplitudes() 797 if not self.ncores_for_proc_gen: 798 self.generate_real_amplitudes(pdg_list, real_amp_list) 799 self.link_born_reals()
800 801 820 821 822
823 - def find_reals(self, pert_orders = []):
824 """finds the FKS real configurations for a given process. 825 self.reals[i] is a list of dictionaries corresponding to the real 826 emissions obtained splitting leg i. 827 The dictionaries contain the leglist, the type (order) of the 828 splitting and extra born particles which can give the same 829 splitting (e.g. gluon/photon -> qqbar). 830 If pert orders is empty, all the orders of the model will be used 831 """ 832 833 model = self.born_amp['process']['model'] 834 # if [orders] are not specified, then 835 # include all kind of splittings 836 # i.e. allow all orders to be perturbed 837 # (this is the case for EW corrections, where only squared oders 838 # are imposed) 839 if not pert_orders: 840 if not self.born_amp['process']['orders']: 841 pert_orders = model['coupling_orders'] 842 else: 843 pert_orders = self.born_amp['process']['perturbation_couplings'] 844 845 leglist = self.get_leglist() 846 if list(range(len(leglist))) != [l['number']-1 for l in leglist]: 847 raise fks_common.FKSProcessError('Disordered numbers of leglist') 848 849 if [ i['state'] for i in leglist].count(False) == 1: 850 decay_process=True 851 else: 852 decay_process=False 853 854 # count the number of initial-state leptons 855 ninit_lep = [l['id'] in model.get_lepton_pdgs() and not l['state'] for l in leglist].count(True) 856 857 for i in leglist: 858 i_i = i['number'] - 1 859 self.reals.append([]) 860 861 # for 2->1 processes, map only the initial-state singularities 862 # this is because final-state mapping preserves shat, which 863 # is not possible in 2->1 864 if len(leglist) == 3 and not decay_process and i['state']: 865 continue 866 for pert_order in pert_orders: 867 # no splittings for initial states in decay processes 868 if decay_process and not i['state']: 869 splittings=[] 870 # if there are leptons in the initial state and init_lep_split is False, 871 # only split initial state leptons; do nothing for any other particle 872 elif not self.init_lep_split and ninit_lep >= 1 and \ 873 (i['state'] or i['id'] not in model.get_lepton_pdgs()): 874 splittings=[] 875 else: 876 splittings = fks_common.find_splittings( \ 877 i, model, {}, pert_order, \ 878 include_init_leptons=self.init_lep_split) 879 for split in splittings: 880 # find other 'mother' particles which can end up in the same splitting 881 extra_mothers = {} 882 for pert in pert_orders: 883 extra_mothers[pert] = fks_common.find_mothers(split[0], split[1], model, pert=pert, 884 mom_mass=model.get('particle_dict')[i['id']]['mass'].lower()) 885 886 #remove the current mother from the extra mothers 887 if i['state']: 888 extra_mothers[pert_order].remove(i['id']) 889 else: 890 extra_mothers[pert_order].remove(model.get('particle_dict')[i['id']].get_anti_pdg_code()) 891 892 self.reals[i_i].append({ 893 'leglist': fks_common.insert_legs(leglist, i, split ,pert=pert_order), 894 'perturbation': [pert_order], 895 'extra_mothers': extra_mothers})
896 897
898 - def find_reals_to_integrate(self): #test written
899 """Finds double countings in the real emission configurations, sets the 900 is_to_integrate variable and if "self.remove_reals" is True removes the 901 not needed ones from the born list. 902 """ 903 #find the initial number of real configurations 904 ninit = len(self.real_amps) 905 remove = self.remove_reals 906 907 for m in range(ninit): 908 for n in range(m + 1, ninit): 909 real_m = self.real_amps[m] 910 real_n = self.real_amps[n] 911 if len(real_m.fks_infos) > 1 or len(real_m.fks_infos) > 1: 912 raise fks_common.FKSProcessError(\ 913 'find_reals_to_integrate should only be called before combining processes') 914 915 i_m = real_m.fks_infos[0]['i'] 916 j_m = real_m.fks_infos[0]['j'] 917 i_n = real_n.fks_infos[-1]['i'] 918 j_n = real_n.fks_infos[0]['j'] 919 ij_id_m = real_m.fks_infos[0]['ij_id'] 920 ij_id_n = real_n.fks_infos[0]['ij_id'] 921 if j_m > self.nincoming and j_n > self.nincoming: 922 # make sure i and j in the two real emissions have the same mother 923 if (ij_id_m != ij_id_n): 924 continue 925 if (real_m.get_leg_i()['id'] == real_n.get_leg_i()['id'] \ 926 and \ 927 real_m.get_leg_j()['id'] == real_n.get_leg_j()['id']) \ 928 or \ 929 (real_m.get_leg_i()['id'] == real_n.get_leg_j()['id'] \ 930 and \ 931 real_m.get_leg_j()['id'] == real_n.get_leg_i()['id']): 932 if i_m > i_n: 933 if real_m.get_leg_i()['id'] == -real_m.get_leg_j()['id']: 934 self.real_amps[m].is_to_integrate = False 935 else: 936 self.real_amps[n].is_to_integrate = False 937 elif i_m == i_n and j_m > j_n: 938 if real_m.get_leg_i()['id'] == -real_m.get_leg_j()['id']: 939 self.real_amps[m].is_to_integrate = False 940 else: 941 self.real_amps[n].is_to_integrate = False 942 # in case of g(a) > ffx splitting, keep the lowest ij 943 elif i_m == i_n and j_m == j_n and \ 944 not real_m.get_leg_j()['self_antipart'] and \ 945 not real_m.get_leg_i()['self_antipart']: 946 if real_m.fks_infos[0]['ij'] > real_n.fks_infos[0]['ij']: 947 real_m.is_to_integrate = False 948 else: 949 real_n.is_to_integrate = False 950 else: 951 if real_m.get_leg_i()['id'] == -real_m.get_leg_j()['id']: 952 self.real_amps[n].is_to_integrate = False 953 else: 954 self.real_amps[m].is_to_integrate = False 955 # self.real_amps[m].is_to_integrate = False 956 elif j_m <= self.nincoming and j_n == j_m: 957 if real_m.get_leg_i()['id'] == real_n.get_leg_i()['id'] and \ 958 real_m.get_leg_j()['id'] == real_n.get_leg_j()['id']: 959 if i_m > i_n: 960 self.real_amps[n].is_to_integrate = False 961 else: 962 self.real_amps[m].is_to_integrate = False 963 if remove: 964 newreal_amps = [] 965 for real in self.real_amps: 966 if real.is_to_integrate: 967 newreal_amps.append(real) 968 self.real_amps = newreal_amps 969