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