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 abs(p.get('color')),l.get('onshell')) for (p, l) \ 455 in zip(is_parts + fs_parts, process.get('legs'))], 456 amplitude.get('process').get('id'), 457 process.get('id')] 458 if (criteria=="madweight"): 459 proc_class = [ [(abs(p.get('pdg_code'))==5, abs(p.get('pdg_code'))==11, 460 abs(p.get('pdg_code'))==13, abs(p.get('pdg_code'))==15) for p in \ 461 fs_parts], 462 amplitude.get('process').get('id')] 463 464 try: 465 amplitude_classes[iamp] = proc_classes.index(proc_class) 466 except ValueError: 467 proc_classes.append(proc_class) 468 amplitude_classes[iamp] = len(proc_classes)-1 469 470 return amplitude_classes
471
472 #=============================================================================== 473 # SubProcessGroupList 474 #=============================================================================== 475 -class SubProcessGroupList(base_objects.PhysicsObjectList):
476 """List of SubProcessGroup objects""" 477
478 - def is_valid_element(self, obj):
479 """Test if object obj is a valid element.""" 480 481 return isinstance(obj, SubProcessGroup)
482
483 - def get_matrix_elements(self):
484 """Extract the list of matrix elements""" 485 return helas_objects.HelasMatrixElementList(\ 486 sum([group.get('matrix_elements') for group in self], []))
487
488 - def get_used_lorentz(self):
489 """Return the list of ALOHA routines used in these matrix elements""" 490 491 return helas_objects.HelasMultiProcess( 492 {'matrix_elements': self.get_matrix_elements()}).get_used_lorentz()
493
494 - def get_used_couplings(self):
495 """Return the list of ALOHA routines used in these matrix elements""" 496 497 return helas_objects.HelasMultiProcess( 498 {'matrix_elements': self.get_matrix_elements()}).get_used_couplings()
499
500 - def split_lepton_grouping(self):
501 """Return a list of grouping where they are no groupoing over the leptons.""" 502 503 output = SubProcessGroupList() 504 for group in self: 505 new_mes = {} 506 for me in group['matrix_elements']: 507 tags = {} 508 for proc in me['processes']: 509 ids = proc.get_final_ids_after_decay() 510 ids = tuple([t if abs(t) in [11, 13,15] else 0 for t in ids]) 511 if ids not in tags: 512 tags[ids] = base_objects.ProcessList() 513 tags[ids].append(proc) 514 for tag in tags: 515 new_me = copy.copy(me) 516 new_me['processes'] = tags[tag] 517 if tag not in new_mes: 518 new_mes[tag] = helas_objects.HelasMatrixElementList() 519 new_mes[tag].append(new_me) 520 for tag in tags: 521 new_group = copy.copy(group) 522 new_group['matrix_elements'] = new_mes[tag] 523 new_group.set('name', new_group.generate_name(\ 524 new_group['matrix_elements'][0]['processes'][0], 525 criteria='madweight')) 526 output.append(new_group) 527 return output
528
529 530 531 #=============================================================================== 532 # DecayChainSubProcessGroup 533 #=============================================================================== 534 535 -class DecayChainSubProcessGroup(SubProcessGroup):
536 """Class to keep track of subprocess groups from a list of decay chains""" 537
538 - def default_setup(self):
539 """Define object and give default values""" 540 541 self['core_groups'] = SubProcessGroupList() 542 self['decay_groups'] = DecayChainSubProcessGroupList() 543 # decay_chain_amplitudes is the original DecayChainAmplitudeList 544 self['decay_chain_amplitudes'] = diagram_generation.DecayChainAmplitudeList()
545
546 - def filter(self, name, value):
547 """Filter for valid property values.""" 548 549 if name == 'core_groups': 550 if not isinstance(value, SubProcessGroupList): 551 raise self.PhysicsObjectError, \ 552 "%s is not a valid core_groups" % str(value) 553 if name == 'decay_groups': 554 if not isinstance(value, DecayChainSubProcessGroupList): 555 raise self.PhysicsObjectError, \ 556 "%s is not a valid decay_groups" % str(value) 557 if name == 'decay_chain_amplitudes': 558 if not isinstance(value, diagram_generation.DecayChainAmplitudeList): 559 raise self.PhysicsObjectError, \ 560 "%s is not a valid DecayChainAmplitudeList" % str(value) 561 return True
562
563 - def get_sorted_keys(self):
564 """Return diagram property names as a nicely sorted list.""" 565 566 return ['core_groups', 'decay_groups', 'decay_chain_amplitudes']
567
568 - def nice_string(self, indent = 0):
569 """Returns a nicely formatted string of the content.""" 570 571 mystr = "" 572 for igroup, group in enumerate(self.get('core_groups')): 573 mystr += " " * indent + "Group %d:\n" % (igroup + 1) 574 for amplitude in group.get('amplitudes'): 575 mystr = mystr + amplitude.nice_string(indent + 2) + "\n" 576 577 if self.get('decay_groups'): 578 mystr += " " * indent + "Decay groups:\n" 579 for dec in self.get('decay_groups'): 580 mystr = mystr + dec.nice_string(indent + 2) + "\n" 581 582 return mystr[:-1]
583 584 #=========================================================================== 585 # generate_helas_decay_chain_subproc_groups 586 #===========================================================================
588 """Combine core_groups and decay_groups to give 589 HelasDecayChainProcesses and new diagram_maps. 590 """ 591 592 # Combine decays 593 matrix_elements = \ 594 helas_objects.HelasMultiProcess.generate_matrix_elements(\ 595 diagram_generation.AmplitudeList(\ 596 self.get('decay_chain_amplitudes'))) 597 598 599 # For each matrix element, check which group it should go into and 600 # calculate diagram_maps 601 me_assignments = {} 602 for me in matrix_elements: 603 group_assignment = self.assign_group_to_decay_process(\ 604 me.get('processes')[0]) 605 assert group_assignment 606 try: 607 me_assignments[group_assignment].append(me) 608 except KeyError: 609 me_assignments[group_assignment] = [me] 610 611 # Create subprocess groups corresponding to the different 612 # group_assignments 613 614 subproc_groups = SubProcessGroupList() 615 for key in sorted(me_assignments.keys()): 616 group = SubProcessGroup() 617 group.set('matrix_elements', helas_objects.HelasMatrixElementList(\ 618 me_assignments[key])) 619 group.set('number', group.get('matrix_elements')[0].\ 620 get('processes')[0].get('id')) 621 group.set('name', group.generate_name(\ 622 group.get('matrix_elements')[0].\ 623 get('processes')[0])) 624 subproc_groups.append(group) 625 626 return subproc_groups
627
628 - def assign_group_to_decay_process(self, process):
629 """Recursively identify which group process belongs to.""" 630 631 # Determine properties for the decay chains 632 # The entries of group_assignments are: 633 # [(decay_index, (decay_group_index, ...)), 634 # diagram_map (updated), len(mapping_diagrams)] 635 636 group_assignments = [] 637 638 for decay in process.get('decay_chains'): 639 # Find decay group that has this decay in it 640 ids = [l.get('id') for l in decay.get('legs')] 641 decay_groups = [(i, group) for (i, group) in \ 642 enumerate(self.get('decay_groups')) \ 643 if any([ids in [[l.get('id') for l in \ 644 a.get('process').get('legs')] \ 645 for a in g.get('amplitudes')] \ 646 for g in group.get('core_groups')])] 647 648 for decay_group in decay_groups: 649 650 group_assignment = \ 651 decay_group[1].assign_group_to_decay_process(decay) 652 653 if group_assignment: 654 group_assignments.append((decay_group[0], group_assignment)) 655 656 if process.get('decay_chains') and not group_assignments: 657 return None 658 659 # Now calculate the corresponding properties for process 660 661 # Find core process group 662 ids = [(l.get('id'),l.get('onshell')) for l in process.get('legs')] 663 core_groups = [(i, group) for (i, group) in \ 664 enumerate(self.get('core_groups')) \ 665 if ids in [[(l.get('id'),l.get('onshell')) for l in \ 666 a.get('process').get('legs')] \ 667 for a in group.get('amplitudes')] \ 668 and process.get('id') == group.get('number')] 669 670 if not core_groups: 671 return None 672 673 assert len(core_groups) == 1 674 675 core_group = core_groups[0] 676 # This is the first return argument - the chain of group indices 677 group_assignment = (core_group[0], 678 tuple([g for g in group_assignments])) 679 680 if not group_assignments: 681 # No decays - return the values for this process 682 return group_assignment 683 684 return group_assignment
685 686 #=========================================================================== 687 # group_amplitudes 688 #=========================================================================== 689 @staticmethod
690 - def group_amplitudes(decay_chain_amps, criteria='madevent', matrix_elements_opts={}):
691 """Recursive function. Starting from a DecayChainAmplitude, 692 return a DecayChainSubProcessGroup with the core amplitudes 693 and decay chains divided into subprocess groups""" 694 695 assert isinstance(decay_chain_amps, diagram_generation.DecayChainAmplitudeList), \ 696 "Argument to group_amplitudes must be DecayChainAmplitudeList" 697 if criteria in ['matrix', 'standalone','pythia8','standalone_cpp', False]: 698 criteria = 'madevent' 699 assert criteria in ['madevent', 'madweight'] 700 701 # Collect all amplitudes 702 amplitudes = diagram_generation.AmplitudeList() 703 for amp in decay_chain_amps: 704 amplitudes.extend(amp.get('amplitudes')) 705 706 # Determine core process groups 707 core_groups = SubProcessGroup.group_amplitudes(amplitudes, criteria) 708 709 dc_subproc_group = DecayChainSubProcessGroup(\ 710 {'core_groups': core_groups, 711 'decay_chain_amplitudes': decay_chain_amps}) 712 713 decays = diagram_generation.DecayChainAmplitudeList() 714 715 # Recursively determine decay chain groups 716 for decay_chain_amp in decay_chain_amps: 717 decays.extend(decay_chain_amp.get('decay_chains')) 718 719 if decays: 720 dc_subproc_group.get('decay_groups').append(\ 721 DecayChainSubProcessGroup.group_amplitudes(decays, criteria)) 722 723 return dc_subproc_group
724
725 726 727 728 #=============================================================================== 729 # DecayChainSubProcessGroupList 730 #=============================================================================== 731 -class DecayChainSubProcessGroupList(base_objects.PhysicsObjectList):
732 """List of DecayChainSubProcessGroup objects""" 733
734 - def is_valid_element(self, obj):
735 """Test if object obj is a valid element.""" 736 737 return isinstance(obj, DecayChainSubProcessGroup)
738