Package madgraph :: Package iolibs :: Module group_subprocs
[hide private]
[frames] | no frames]

Source Code for Module madgraph.iolibs.group_subprocs

  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  """Methods and classes to group subprocesses according to initial 
 16  states, and produce the corresponding grouped subprocess directories.""" 
 17   
 18  from __future__ import absolute_import 
 19  import array 
 20  import copy 
 21  import fractions 
 22  import glob 
 23  import itertools 
 24  import logging 
 25  import os 
 26  import re 
 27  import shutil 
 28  import subprocess 
 29   
 30  import madgraph.core.base_objects as base_objects 
 31  import madgraph.loop.loop_base_objects as loop_base_objects 
 32  import madgraph.core.diagram_generation as diagram_generation 
 33  import madgraph.core.helas_objects as helas_objects 
 34  import madgraph.iolibs.drawing_eps as draw 
 35  import madgraph.iolibs.files as files 
 36  import madgraph.iolibs.file_writers as writers 
 37  import madgraph.iolibs.template_files as template_files 
 38  import madgraph.iolibs.ufo_expression_parsers as parsers 
 39  import madgraph.loop.loop_diagram_generation as loop_diagram_generation 
 40  import madgraph.loop.loop_helas_objects as loop_helas_objects 
 41   
 42  import madgraph.various.misc as misc 
 43   
 44  import aloha.create_aloha as create_aloha 
 45   
 46  import models.write_param_card as write_param_card 
 47  from madgraph import MG5DIR 
 48  from madgraph.iolibs.files import cp, ln, mv 
 49  from six.moves import range 
 50  from six.moves import zip 
 51  _file_path = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + '/' 
 52  logger = logging.getLogger('madgraph.group_subprocs') 
53 54 #=============================================================================== 55 # DiagramTag class to identify diagrams giving the same config 56 #=============================================================================== 57 58 -class IdentifyConfigTag(diagram_generation.DiagramTag):
59 """DiagramTag daughter class to identify diagrams giving the same 60 config. Need to compare leg number, mass, width, and color.""" 61 62 @staticmethod 81 82 83 @staticmethod
84 - def vertex_id_from_vertex(vertex, last_vertex, model, ninitial):
85 """Returns the info needed to identify configs: 86 interaction color, mass, width.""" 87 88 inter = model.get_interaction(vertex.get('id')) 89 90 if last_vertex: 91 return ((0,),) 92 else: 93 leg = vertex.get('legs')[-1] 94 part = model.get_particle(leg.get('id')) 95 if abs(part.get('pdg_code')) in [23,25] and leg.get('state') == False: 96 part2 = model.get_particle(22) 97 mass = part2.get('mass') 98 width = part2.get('width') 99 else: 100 mass = part.get('mass') 101 width = part.get('width') 102 103 return ((part.get('color'), 104 mass, width), 105 0)
106 107 @staticmethod
108 - def flip_vertex(new_vertex, old_vertex, links):
109 """Move the wavefunction part of propagator id appropriately""" 110 111 if len(new_vertex[0]) == 1 and len(old_vertex[0]) > 1: 112 # We go from a last link to next-to-last link - 113 return (old_vertex[0], new_vertex[0][0]) 114 elif len(new_vertex[0]) > 1 and len(old_vertex[0]) == 1: 115 # We go from next-to-last link to last link - remove propagator info 116 return (old_vertex[0],) 117 # We should not get here 118 raise diagram_generation.DiagramTag.DiagramTagError("Error in IdentifyConfigTag, wrong setup of vertices in link.")
119
120 #=============================================================================== 121 # SubProcessGroup 122 #=============================================================================== 123 124 -class SubProcessGroup(base_objects.PhysicsObject):
125 """Class to group a number of amplitudes with same initial states 126 into a subprocess group""" 127
128 - def default_setup(self):
129 """Define object and give default values""" 130 131 self['number'] = 0 132 self['name'] = "" 133 self['amplitudes'] = diagram_generation.AmplitudeList() 134 self['matrix_elements'] = helas_objects.HelasMatrixElementList() 135 self['mapping_diagrams'] = [] 136 self['diagram_maps'] = {} 137 self['diagrams_for_configs'] = [] 138 self['amplitude_map'] = {} 139 self['matrix_element_opts'] = {}
140
141 - def filter(self, name, value):
142 """Filter for valid property values.""" 143 144 if name == 'number': 145 if not isinstance(value, int): 146 raise self.PhysicsObjectError("%s is not a valid int object" % str(value)) 147 if name == 'name': 148 if not isinstance(value, str): 149 raise self.PhysicsObjectError("%s is not a valid str object" % str(value)) 150 if name == 'amplitudes': 151 if not isinstance(value, diagram_generation.AmplitudeList): 152 raise self.PhysicsObjectError("%s is not a valid amplitudelist" % str(value)) 153 if name in ['mapping_diagrams', 'diagrams_for_configs']: 154 if not isinstance(value, list): 155 raise self.PhysicsObjectError("%s is not a valid list" % str(value)) 156 if name == 'diagram_maps': 157 if not isinstance(value, dict): 158 raise self.PhysicsObjectError("%s is not a valid dict" % str(value)) 159 if name == 'matrix_elements': 160 if not isinstance(value, helas_objects.HelasMatrixElementList): 161 raise self.PhysicsObjectError("%s is not a valid HelasMatrixElementList" % str(value)) 162 163 if name == 'amplitude_map': 164 if not isinstance(value, dict): 165 raise self.PhysicsObjectError("%s is not a valid dict object" % str(value)) 166 167 if name == 'matrix_element_opts': 168 if not isinstance(value, dict): 169 raise self.PhysicsObjectError("%s is not a valid dictionary object" % str(value)) 170 171 return True
172
173 - def get_sorted_keys(self):
174 """Return diagram property names as a nicely sorted list.""" 175 176 return ['number', 'name', 'amplitudes', 'mapping_diagrams', 177 'diagram_maps', 'matrix_elements', 'amplitude_map']
178 179 # Enhanced get function
180 - def get(self, name):
181 """Get the value of the property name.""" 182 183 if name == 'matrix_elements' and not self[name]: 184 self.generate_matrix_elements() 185 186 if name in ['mapping_diagrams', 'diagram_maps'] and not self[name]: 187 self.set_mapping_diagrams() 188 189 if name in ['diagrams_for_configs'] and not self[name]: 190 self.set_diagrams_for_configs() 191 192 return super(SubProcessGroup, self).get(name)
193
194 - def set_mapping_diagrams(self):
195 """Set mapping_diagrams and diagram_maps, to prepare for 196 generation of the super-config.inc files.""" 197 198 # Find the mapping diagrams 199 mapping_diagrams, diagram_maps = \ 200 self.find_mapping_diagrams() 201 202 self.set('mapping_diagrams', mapping_diagrams) 203 self.set('diagram_maps', diagram_maps)
204 205 #=========================================================================== 206 # generate_matrix_elements 207 #===========================================================================
208 - def generate_matrix_elements(self):
209 """Create a HelasMultiProcess corresponding to the amplitudes 210 in self""" 211 212 if not self.get('amplitudes'): 213 raise self.PhysicsObjectError("Need amplitudes to generate matrix_elements") 214 215 amplitudes = copy.copy(self.get('amplitudes')) 216 217 # The conditional statement tests whether we are dealing with a 218 # loop induced process. We must set compute_loop_nc to True here 219 # since the knowledge of the power of Nc coming from potential 220 # loop color trace is necessary for the loop induced output with MadEvent 221 if isinstance(amplitudes[0], loop_diagram_generation.LoopAmplitude): 222 self.set('matrix_elements', 223 loop_helas_objects.LoopHelasProcess.generate_matrix_elements( 224 amplitudes, compute_loop_nc=True, 225 matrix_element_opts = self['matrix_element_opts'])) 226 else: 227 self.set('matrix_elements', 228 helas_objects.HelasMultiProcess.\ 229 generate_matrix_elements(amplitudes)) 230 231 self.set('amplitudes', diagram_generation.AmplitudeList())
232
233 - def generate_name(self, process, criteria='madevent'):
234 """Generate a convenient name for the group, based on and 235 masses""" 236 237 beam = [l.get('id') for l in process.get('legs') if not l.get('state')] 238 fs = [(l.get('id'), l) for l in process.get('legs') if l.get('state')] 239 name = "" 240 for beam in beam: 241 part = process.get('model').get_particle(beam) 242 if part.get('mass').lower() == 'zero' and part.is_fermion() and \ 243 part.get('color') != 1: 244 name += "q" 245 elif criteria == 'madweight': 246 name += part.get_name().replace('~', 'x').\ 247 replace('+', 'p').replace('-', 'm') 248 elif part.get('mass').lower() == 'zero' and part.is_fermion() and \ 249 part.get('color') == 1 and part.get('pdg_code') % 2 == 1: 250 name += "l" 251 elif part.get('mass').lower() == 'zero' and part.is_fermion() and \ 252 part.get('color') == 1 and part.get('pdg_code') % 2 == 0: 253 name += "vl" 254 else: 255 name += part.get_name().replace('~', 'x').\ 256 replace('+', 'p').replace('-', 'm') 257 name += "_" 258 for (fs_part, leg) in fs: 259 part = process.get('model').get_particle(fs_part) 260 if part.get('mass').lower() == 'zero' and part.get('color') != 1 \ 261 and part.get('spin') == 2: 262 name += "q" # "j" 263 elif criteria == 'madweight': 264 name += part.get_name().replace('~', 'x').\ 265 replace('+', 'p').replace('-', 'm') 266 elif part.get('mass').lower() == 'zero' and part.get('color') == 1 \ 267 and part.get('spin') == 2: 268 if part.get('charge') == 0: 269 name += "vl" 270 else: 271 name += "l" 272 else: 273 name += part.get_name().replace('~', 'x').\ 274 replace('+', 'p').replace('-', 'm') 275 if leg.get('polarization'): 276 if leg.get('polarization') in [[-1,1],[1,-1]]: 277 name += 'T' 278 elif leg.get('polarization') == [-1]: 279 name += 'L' 280 elif leg.get('polarization') == [1]: 281 name += 'R' 282 else: 283 name += '%s' %''.join([str(p).replace('-','m') for p in leg.get('polarization')]) 284 285 286 for dc in process.get('decay_chains'): 287 name += "_" + self.generate_name(dc, criteria) 288 289 return name
290
291 - def get_nexternal_ninitial(self):
292 """Get number of external and initial particles for this group""" 293 294 assert self.get('matrix_elements'), \ 295 "Need matrix element to call get_nexternal_ninitial" 296 297 return self.get('matrix_elements')[0].\ 298 get_nexternal_ninitial()
299
300 - def get_num_configs(self):
301 """Get number of configs for this group""" 302 303 model = self.get('matrix_elements')[0].get('processes')[0].\ 304 get('model') 305 306 next, nini = self.get_nexternal_ninitial() 307 308 return sum([md.get_num_configs(model, nini) for md in 309 self.get('mapping_diagrams')])
310
311 - def find_mapping_diagrams(self, max_tpropa=0):
312 """Find all unique diagrams for all processes in this 313 process class, and the mapping of their diagrams unto this 314 unique diagram.""" 315 316 assert self.get('matrix_elements'), \ 317 "Need matrix elements to run find_mapping_diagrams" 318 319 if max_tpropa == 0: 320 max_tpropa = base_objects.Vertex.max_tpropa 321 322 matrix_elements = self.get('matrix_elements') 323 model = matrix_elements[0].get('processes')[0].get('model') 324 # mapping_diagrams: The configurations for the non-reducable 325 # diagram topologies 326 mapping_diagrams = [] 327 # equiv_diags: Tags identifying diagrams that correspond to 328 # the same configuration 329 equiv_diagrams = [] 330 # diagram_maps: A dict from amplitude number to list of 331 # diagram maps, pointing to the mapping_diagrams (starting at 332 # 1). Diagrams with multi-particle vertices will have 0. 333 diagram_maps = {} 334 335 for ime, me in enumerate(matrix_elements): 336 # Define here a FDStructure repository which will be used for the 337 # tagging all the diagrams in get_contracted_loop_diagram. Remember 338 # the the tagging of each loop updates the FDStructre repository 339 # with the new structures identified. 340 341 if isinstance(me, loop_helas_objects.LoopHelasMatrixElement): 342 FDStructRepo = loop_base_objects.FDStructureList([]) 343 diagrams = [(d.get_contracted_loop_diagram(model,FDStructRepo) if 344 isinstance(d,loop_base_objects.LoopDiagram) else d) for d in 345 me.get('base_amplitude').get('loop_diagrams') if d.get('type')>0] 346 else: 347 diagrams = me.get('base_amplitude').get('diagrams') 348 349 # Check the minimal number of legs we need to include in order 350 # to make sure we'll have some valid configurations 351 vert_list = [max(diag.get_vertex_leg_numbers()) for diag in \ 352 diagrams if diag.get_vertex_leg_numbers()!=[]] 353 minvert = min(vert_list) if vert_list!=[] else 0 354 355 diagram_maps[ime] = [] 356 357 for diagram in diagrams: 358 # Only use diagrams with all vertices == min_legs, but do not 359 # consider artificial vertices, such as those coming from a 360 # contracted loop for example, which should be considered as new 361 # topologies (the contracted vertex has id == -2.) 362 if diagram.get_vertex_leg_numbers()!=[] and \ 363 max(diagram.get_vertex_leg_numbers()) > minvert: 364 diagram_maps[ime].append(0) 365 continue 366 if diagram.get_nb_t_channel() > max_tpropa: 367 diagram_maps[ime].append(0) 368 continue 369 # Create the equivalent diagram, in the format 370 # [[((ext_number1, mass_width_id1), ..., )], 371 # ...] (for each vertex) 372 equiv_diag = IdentifyConfigTag(diagram, model) 373 try: 374 diagram_maps[ime].append(equiv_diagrams.index(\ 375 equiv_diag) + 1) 376 except ValueError: 377 equiv_diagrams.append(equiv_diag) 378 mapping_diagrams.append(diagram) 379 diagram_maps[ime].append(equiv_diagrams.index(\ 380 equiv_diag) + 1) 381 return mapping_diagrams, diagram_maps
382
383 - def get_subproc_diagrams_for_config(self, iconfig):
384 """Find the diagrams (number + 1) for all subprocesses 385 corresponding to config number iconfig. Return 0 for subprocesses 386 without corresponding diagram. Note that the iconfig should 387 start at 0.""" 388 389 assert self.get('diagram_maps'), \ 390 "Need diagram_maps to run get_subproc_diagrams_for_config" 391 392 subproc_diagrams = [] 393 for iproc in \ 394 range(len(self.get('matrix_elements'))): 395 try: 396 subproc_diagrams.append(self.get('diagram_maps')[iproc].\ 397 index(iconfig + 1) + 1) 398 except ValueError: 399 subproc_diagrams.append(0) 400 401 return subproc_diagrams
402
403 - def set_diagrams_for_configs(self):
404 """Get a list of all diagrams_for_configs""" 405 406 subproc_diagrams_for_config = [] 407 for iconf in range(len(self.get('mapping_diagrams'))): 408 subproc_diagrams_for_config.append(\ 409 self.get_subproc_diagrams_for_config(iconf)) 410 411 self['diagrams_for_configs'] = subproc_diagrams_for_config
412 413 #=========================================================================== 414 # group_amplitudes 415 #=========================================================================== 416 @staticmethod
417 - def group_amplitudes(amplitudes, criteria='madevent', matrix_elements_opts={}):
418 """Return a SubProcessGroupList with the amplitudes divided 419 into subprocess groups""" 420 421 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 422 "Argument to group_amplitudes must be AmplitudeList" 423 424 if not criteria: 425 criteria = 'madevent' 426 assert criteria in ['madevent', 'madweight'] 427 428 logger.info("Organizing processes into subprocess groups") 429 430 process_classes = SubProcessGroup.find_process_classes(amplitudes,criteria) 431 ret_list = SubProcessGroupList() 432 process_class_numbers = sorted(list(set(process_classes.values()))) 433 for num in process_class_numbers: 434 amp_nums = [key for (key, val) in process_classes.items() if \ 435 val == num] 436 group = SubProcessGroup({'matrix_element_opts':matrix_elements_opts}) 437 group.set('amplitudes', 438 diagram_generation.AmplitudeList([amplitudes[i] for i in \ 439 amp_nums])) 440 group.set('number', group.get('amplitudes')[0].get('process').\ 441 get('id')) 442 group.set('name', group.generate_name(\ 443 group.get('amplitudes')[0].get('process'), 444 criteria=criteria)) 445 ret_list.append(group) 446 447 return ret_list
448 449 @staticmethod
450 - def find_process_classes(amplitudes, criteria):
451 """Find all different process classes, classified according to 452 initial state and final state. For initial state, we 453 differentiate fermions, antifermions, gluons, and masses. For 454 final state, only masses.""" 455 456 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 457 "Argument to find_process_classes must be AmplitudeList" 458 assert amplitudes 459 assert criteria in ['madevent','madweight'] 460 461 model = amplitudes[0].get('process').get('model') 462 proc_classes = [] 463 amplitude_classes = {} 464 465 for iamp, amplitude in enumerate(amplitudes): 466 process = amplitude.get('process') 467 is_parts = [model.get_particle(l.get('id')) for l in \ 468 process.get('legs') if not \ 469 l.get('state')] 470 fs_parts = [model.get_particle(l.get('id')) for l in \ 471 process.get('legs') if l.get('state')] 472 473 # This is where the requirements for which particles to 474 # combine are defined. Include p.get('is_part') in 475 # is_parts selection to distinguish between q and qbar, 476 # remove p.get('spin') from fs_parts selection to combine 477 # q and g into "j" 478 if (criteria=="madevent"): 479 proc_class = [ [(p.is_fermion(), ) \ 480 for p in is_parts], # p.get('is_part') 481 [(p.get('mass'), p.get('spin'), 482 p.get('pdg_code') % 2 if p.get('color') == 1 else 0, 483 abs(p.get('color')),l.get('onshell')) for (p, l) \ 484 in zip(is_parts + fs_parts, process.get('legs'))], 485 amplitude.get('process').get('id'), 486 process.get('id')] 487 if (criteria=="madweight"): 488 proc_class = [ [(abs(p.get('pdg_code'))==5, abs(p.get('pdg_code'))==11, 489 abs(p.get('pdg_code'))==13, abs(p.get('pdg_code'))==15) for p in \ 490 fs_parts], 491 amplitude.get('process').get('id')] 492 493 try: 494 amplitude_classes[iamp] = proc_classes.index(proc_class) 495 except ValueError: 496 proc_classes.append(proc_class) 497 amplitude_classes[iamp] = len(proc_classes)-1 498 499 return amplitude_classes
500
501 #=============================================================================== 502 # SubProcessGroupList 503 #=============================================================================== 504 -class SubProcessGroupList(base_objects.PhysicsObjectList):
505 """List of SubProcessGroup objects""" 506
507 - def is_valid_element(self, obj):
508 """Test if object obj is a valid element.""" 509 510 return isinstance(obj, SubProcessGroup)
511
512 - def get_matrix_elements(self):
513 """Extract the list of matrix elements""" 514 return helas_objects.HelasMatrixElementList(\ 515 sum([group.get('matrix_elements') for group in self], []))
516
517 - def get_used_lorentz(self):
518 """Return the list of ALOHA routines used in these matrix elements""" 519 520 return helas_objects.HelasMultiProcess( 521 {'matrix_elements': self.get_matrix_elements()}).get_used_lorentz()
522
523 - def get_used_couplings(self):
524 """Return the list of ALOHA routines used in these matrix elements""" 525 526 return helas_objects.HelasMultiProcess( 527 {'matrix_elements': self.get_matrix_elements()}).get_used_couplings()
528
529 - def split_lepton_grouping(self):
530 """Return a list of grouping where they are no groupoing over the leptons.""" 531 532 output = SubProcessGroupList() 533 for group in self: 534 new_mes = {} 535 for me in group['matrix_elements']: 536 tags = {} 537 for proc in me['processes']: 538 ids = proc.get_final_ids_after_decay() 539 ids = tuple([t if abs(t) in [11, 13,15] else 0 for t in ids]) 540 if ids not in tags: 541 tags[ids] = base_objects.ProcessList() 542 tags[ids].append(proc) 543 for tag in tags: 544 new_me = copy.copy(me) 545 new_me['processes'] = tags[tag] 546 if tag not in new_mes: 547 new_mes[tag] = helas_objects.HelasMatrixElementList() 548 new_mes[tag].append(new_me) 549 for tag in tags: 550 new_group = copy.copy(group) 551 new_group['matrix_elements'] = new_mes[tag] 552 new_group.set('name', new_group.generate_name(\ 553 new_group['matrix_elements'][0]['processes'][0], 554 criteria='madweight')) 555 output.append(new_group) 556 return output
557
558 559 560 #=============================================================================== 561 # DecayChainSubProcessGroup 562 #=============================================================================== 563 564 -class DecayChainSubProcessGroup(SubProcessGroup):
565 """Class to keep track of subprocess groups from a list of decay chains""" 566
567 - def default_setup(self):
568 """Define object and give default values""" 569 570 self['core_groups'] = SubProcessGroupList() 571 self['decay_groups'] = DecayChainSubProcessGroupList() 572 # decay_chain_amplitudes is the original DecayChainAmplitudeList 573 self['decay_chain_amplitudes'] = diagram_generation.DecayChainAmplitudeList()
574
575 - def filter(self, name, value):
576 """Filter for valid property values.""" 577 578 if name == 'core_groups': 579 if not isinstance(value, SubProcessGroupList): 580 raise self.PhysicsObjectError("%s is not a valid core_groups" % str(value)) 581 if name == 'decay_groups': 582 if not isinstance(value, DecayChainSubProcessGroupList): 583 raise self.PhysicsObjectError("%s is not a valid decay_groups" % str(value)) 584 if name == 'decay_chain_amplitudes': 585 if not isinstance(value, diagram_generation.DecayChainAmplitudeList): 586 raise self.PhysicsObjectError("%s is not a valid DecayChainAmplitudeList" % str(value)) 587 return True
588
589 - def get_sorted_keys(self):
590 """Return diagram property names as a nicely sorted list.""" 591 592 return ['core_groups', 'decay_groups', 'decay_chain_amplitudes']
593
594 - def nice_string(self, indent = 0):
595 """Returns a nicely formatted string of the content.""" 596 597 mystr = "" 598 for igroup, group in enumerate(self.get('core_groups')): 599 mystr += " " * indent + "Group %d:\n" % (igroup + 1) 600 for amplitude in group.get('amplitudes'): 601 mystr = mystr + amplitude.nice_string(indent + 2) + "\n" 602 603 if self.get('decay_groups'): 604 mystr += " " * indent + "Decay groups:\n" 605 for dec in self.get('decay_groups'): 606 mystr = mystr + dec.nice_string(indent + 2) + "\n" 607 608 return mystr[:-1]
609 610 #=========================================================================== 611 # generate_helas_decay_chain_subproc_groups 612 #===========================================================================
614 """Combine core_groups and decay_groups to give 615 HelasDecayChainProcesses and new diagram_maps. 616 """ 617 618 # Combine decays 619 matrix_elements = \ 620 helas_objects.HelasMultiProcess.generate_matrix_elements(\ 621 diagram_generation.AmplitudeList(\ 622 self.get('decay_chain_amplitudes'))) 623 624 625 # For each matrix element, check which group it should go into and 626 # calculate diagram_maps 627 me_assignments = {} 628 for me in matrix_elements: 629 group_assignment = self.assign_group_to_decay_process(\ 630 me.get('processes')[0]) 631 assert group_assignment 632 try: 633 me_assignments[group_assignment].append(me) 634 except KeyError: 635 me_assignments[group_assignment] = [me] 636 637 # Create subprocess groups corresponding to the different 638 # group_assignments 639 640 subproc_groups = SubProcessGroupList() 641 for key in sorted(me_assignments.keys()): 642 group = SubProcessGroup() 643 group.set('matrix_elements', helas_objects.HelasMatrixElementList(\ 644 me_assignments[key])) 645 group.set('number', group.get('matrix_elements')[0].\ 646 get('processes')[0].get('id')) 647 group.set('name', group.generate_name(\ 648 group.get('matrix_elements')[0].\ 649 get('processes')[0])) 650 subproc_groups.append(group) 651 652 return subproc_groups
653
654 - def assign_group_to_decay_process(self, process):
655 """Recursively identify which group process belongs to.""" 656 657 # Determine properties for the decay chains 658 # The entries of group_assignments are: 659 # [(decay_index, (decay_group_index, ...)), 660 # diagram_map (updated), len(mapping_diagrams)] 661 662 group_assignments = [] 663 664 for decay in process.get('decay_chains'): 665 # Find decay group that has this decay in it 666 ids = [l.get('id') for l in decay.get('legs')] 667 decay_groups = [(i, group) for (i, group) in \ 668 enumerate(self.get('decay_groups')) \ 669 if any([ids in [[l.get('id') for l in \ 670 a.get('process').get('legs')] \ 671 for a in g.get('amplitudes')] \ 672 for g in group.get('core_groups')])] 673 674 for decay_group in decay_groups: 675 676 group_assignment = \ 677 decay_group[1].assign_group_to_decay_process(decay) 678 679 if group_assignment: 680 group_assignments.append((decay_group[0], group_assignment)) 681 682 if process.get('decay_chains') and not group_assignments: 683 return None 684 685 # Now calculate the corresponding properties for process 686 687 # Find core process group 688 ids = [(l.get('id'),l.get('onshell')) for l in process.get('legs')] 689 core_groups = [(i, group) for (i, group) in \ 690 enumerate(self.get('core_groups')) \ 691 if ids in [[(l.get('id'),l.get('onshell')) for l in \ 692 a.get('process').get('legs')] \ 693 for a in group.get('amplitudes')] \ 694 and process.get('id') == group.get('number')] 695 696 if not core_groups: 697 return None 698 699 assert len(core_groups) == 1 700 701 core_group = core_groups[0] 702 # This is the first return argument - the chain of group indices 703 group_assignment = (core_group[0], 704 tuple([g for g in group_assignments])) 705 706 if not group_assignments: 707 # No decays - return the values for this process 708 return group_assignment 709 710 return group_assignment
711 712 #=========================================================================== 713 # group_amplitudes 714 #=========================================================================== 715 @staticmethod
716 - def group_amplitudes(decay_chain_amps, criteria='madevent', matrix_elements_opts={}):
717 """Recursive function. Starting from a DecayChainAmplitude, 718 return a DecayChainSubProcessGroup with the core amplitudes 719 and decay chains divided into subprocess groups""" 720 721 assert isinstance(decay_chain_amps, diagram_generation.DecayChainAmplitudeList), \ 722 "Argument to group_amplitudes must be DecayChainAmplitudeList" 723 if criteria in ['matrix', 'standalone','pythia8','standalone_cpp', False]: 724 criteria = 'madevent' 725 assert criteria in ['madevent', 'madweight'] 726 727 # Collect all amplitudes 728 amplitudes = diagram_generation.AmplitudeList() 729 for amp in decay_chain_amps: 730 amplitudes.extend(amp.get('amplitudes')) 731 732 # Determine core process groups 733 core_groups = SubProcessGroup.group_amplitudes(amplitudes, criteria) 734 735 dc_subproc_group = DecayChainSubProcessGroup(\ 736 {'core_groups': core_groups, 737 'decay_chain_amplitudes': decay_chain_amps}) 738 739 decays = diagram_generation.DecayChainAmplitudeList() 740 741 # Recursively determine decay chain groups 742 for decay_chain_amp in decay_chain_amps: 743 decays.extend(decay_chain_amp.get('decay_chains')) 744 745 if decays: 746 dc_subproc_group.get('decay_groups').append(\ 747 DecayChainSubProcessGroup.group_amplitudes(decays, criteria)) 748 749 return dc_subproc_group
750
751 752 753 754 #=============================================================================== 755 # DecayChainSubProcessGroupList 756 #=============================================================================== 757 -class DecayChainSubProcessGroupList(base_objects.PhysicsObjectList):
758 """List of DecayChainSubProcessGroup objects""" 759
760 - def is_valid_element(self, obj):
761 """Test if object obj is a valid element.""" 762 763 return isinstance(obj, DecayChainSubProcessGroup)
764