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'), l) 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, leg) 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 if leg.get('polarization'): 264 if leg.get('polarization') in [[-1,1],[1,-1]]: 265 name += 'T' 266 elif leg.get('polarization') == [-1]: 267 name += 'L' 268 elif leg.get('polarization') == [1]: 269 name += 'R' 270 else: 271 name += '%s' %''.join([str(p).replace('-','m') for p in leg.get('polarization')]) 272 273 274 for dc in process.get('decay_chains'): 275 name += "_" + self.generate_name(dc, criteria) 276 277 return name
278
279 - def get_nexternal_ninitial(self):
280 """Get number of external and initial particles for this group""" 281 282 assert self.get('matrix_elements'), \ 283 "Need matrix element to call get_nexternal_ninitial" 284 285 return self.get('matrix_elements')[0].\ 286 get_nexternal_ninitial()
287
288 - def get_num_configs(self):
289 """Get number of configs for this group""" 290 291 model = self.get('matrix_elements')[0].get('processes')[0].\ 292 get('model') 293 294 next, nini = self.get_nexternal_ninitial() 295 296 return sum([md.get_num_configs(model, nini) for md in 297 self.get('mapping_diagrams')])
298
299 - def find_mapping_diagrams(self):
300 """Find all unique diagrams for all processes in this 301 process class, and the mapping of their diagrams unto this 302 unique diagram.""" 303 304 assert self.get('matrix_elements'), \ 305 "Need matrix elements to run find_mapping_diagrams" 306 307 matrix_elements = self.get('matrix_elements') 308 model = matrix_elements[0].get('processes')[0].get('model') 309 # mapping_diagrams: The configurations for the non-reducable 310 # diagram topologies 311 mapping_diagrams = [] 312 # equiv_diags: Tags identifying diagrams that correspond to 313 # the same configuration 314 equiv_diagrams = [] 315 # diagram_maps: A dict from amplitude number to list of 316 # diagram maps, pointing to the mapping_diagrams (starting at 317 # 1). Diagrams with multi-particle vertices will have 0. 318 diagram_maps = {} 319 320 for ime, me in enumerate(matrix_elements): 321 # Define here a FDStructure repository which will be used for the 322 # tagging all the diagrams in get_contracted_loop_diagram. Remember 323 # the the tagging of each loop updates the FDStructre repository 324 # with the new structures identified. 325 326 if isinstance(me, loop_helas_objects.LoopHelasMatrixElement): 327 FDStructRepo = loop_base_objects.FDStructureList([]) 328 diagrams = [(d.get_contracted_loop_diagram(model,FDStructRepo) if 329 isinstance(d,loop_base_objects.LoopDiagram) else d) for d in 330 me.get('base_amplitude').get('loop_diagrams') if d.get('type')>0] 331 else: 332 diagrams = me.get('base_amplitude').get('diagrams') 333 334 # Check the minimal number of legs we need to include in order 335 # to make sure we'll have some valid configurations 336 vert_list = [max(diag.get_vertex_leg_numbers()) for diag in \ 337 diagrams if diag.get_vertex_leg_numbers()!=[]] 338 minvert = min(vert_list) if vert_list!=[] else 0 339 340 diagram_maps[ime] = [] 341 342 for diagram in diagrams: 343 # Only use diagrams with all vertices == min_legs, but do not 344 # consider artificial vertices, such as those coming from a 345 # contracted loop for example, which should be considered as new 346 # topologies (the contracted vertex has id == -2.) 347 if diagram.get_vertex_leg_numbers()!=[] and \ 348 max(diagram.get_vertex_leg_numbers()) > minvert: 349 diagram_maps[ime].append(0) 350 continue 351 # Create the equivalent diagram, in the format 352 # [[((ext_number1, mass_width_id1), ..., )], 353 # ...] (for each vertex) 354 equiv_diag = IdentifyConfigTag(diagram, model) 355 try: 356 diagram_maps[ime].append(equiv_diagrams.index(\ 357 equiv_diag) + 1) 358 except ValueError: 359 equiv_diagrams.append(equiv_diag) 360 mapping_diagrams.append(diagram) 361 diagram_maps[ime].append(equiv_diagrams.index(\ 362 equiv_diag) + 1) 363 return mapping_diagrams, diagram_maps
364
365 - def get_subproc_diagrams_for_config(self, iconfig):
366 """Find the diagrams (number + 1) for all subprocesses 367 corresponding to config number iconfig. Return 0 for subprocesses 368 without corresponding diagram. Note that the iconfig should 369 start at 0.""" 370 371 assert self.get('diagram_maps'), \ 372 "Need diagram_maps to run get_subproc_diagrams_for_config" 373 374 subproc_diagrams = [] 375 for iproc in \ 376 range(len(self.get('matrix_elements'))): 377 try: 378 subproc_diagrams.append(self.get('diagram_maps')[iproc].\ 379 index(iconfig + 1) + 1) 380 except ValueError: 381 subproc_diagrams.append(0) 382 383 return subproc_diagrams
384
385 - def set_diagrams_for_configs(self):
386 """Get a list of all diagrams_for_configs""" 387 388 subproc_diagrams_for_config = [] 389 for iconf in range(len(self.get('mapping_diagrams'))): 390 subproc_diagrams_for_config.append(\ 391 self.get_subproc_diagrams_for_config(iconf)) 392 393 self['diagrams_for_configs'] = subproc_diagrams_for_config
394 395 #=========================================================================== 396 # group_amplitudes 397 #=========================================================================== 398 @staticmethod
399 - def group_amplitudes(amplitudes, criteria='madevent', matrix_elements_opts={}):
400 """Return a SubProcessGroupList with the amplitudes divided 401 into subprocess groups""" 402 403 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 404 "Argument to group_amplitudes must be AmplitudeList" 405 406 if not criteria: 407 criteria = 'madevent' 408 assert criteria in ['madevent', 'madweight'] 409 410 logger.info("Organizing processes into subprocess groups") 411 412 process_classes = SubProcessGroup.find_process_classes(amplitudes,criteria) 413 ret_list = SubProcessGroupList() 414 process_class_numbers = sorted(list(set(process_classes.values()))) 415 for num in process_class_numbers: 416 amp_nums = [key for (key, val) in process_classes.items() if \ 417 val == num] 418 group = SubProcessGroup({'matrix_element_opts':matrix_elements_opts}) 419 group.set('amplitudes', 420 diagram_generation.AmplitudeList([amplitudes[i] for i in \ 421 amp_nums])) 422 group.set('number', group.get('amplitudes')[0].get('process').\ 423 get('id')) 424 group.set('name', group.generate_name(\ 425 group.get('amplitudes')[0].get('process'), 426 criteria=criteria)) 427 ret_list.append(group) 428 429 return ret_list
430 431 @staticmethod
432 - def find_process_classes(amplitudes, criteria):
433 """Find all different process classes, classified according to 434 initial state and final state. For initial state, we 435 differentiate fermions, antifermions, gluons, and masses. For 436 final state, only masses.""" 437 438 assert isinstance(amplitudes, diagram_generation.AmplitudeList), \ 439 "Argument to find_process_classes must be AmplitudeList" 440 assert amplitudes 441 assert criteria in ['madevent','madweight'] 442 443 model = amplitudes[0].get('process').get('model') 444 proc_classes = [] 445 amplitude_classes = {} 446 447 for iamp, amplitude in enumerate(amplitudes): 448 process = amplitude.get('process') 449 is_parts = [model.get_particle(l.get('id')) for l in \ 450 process.get('legs') if not \ 451 l.get('state')] 452 fs_parts = [model.get_particle(l.get('id')) for l in \ 453 process.get('legs') if l.get('state')] 454 455 # This is where the requirements for which particles to 456 # combine are defined. Include p.get('is_part') in 457 # is_parts selection to distinguish between q and qbar, 458 # remove p.get('spin') from fs_parts selection to combine 459 # q and g into "j" 460 if (criteria=="madevent"): 461 proc_class = [ [(p.is_fermion(), ) \ 462 for p in is_parts], # p.get('is_part') 463 [(p.get('mass'), p.get('spin'), 464 p.get('pdg_code') % 2 if p.get('color') == 1 else 0, 465 abs(p.get('color')),l.get('onshell')) for (p, l) \ 466 in zip(is_parts + fs_parts, process.get('legs'))], 467 amplitude.get('process').get('id'), 468 process.get('id')] 469 if (criteria=="madweight"): 470 proc_class = [ [(abs(p.get('pdg_code'))==5, abs(p.get('pdg_code'))==11, 471 abs(p.get('pdg_code'))==13, abs(p.get('pdg_code'))==15) for p in \ 472 fs_parts], 473 amplitude.get('process').get('id')] 474 475 try: 476 amplitude_classes[iamp] = proc_classes.index(proc_class) 477 except ValueError: 478 proc_classes.append(proc_class) 479 amplitude_classes[iamp] = len(proc_classes)-1 480 481 return amplitude_classes
482
483 #=============================================================================== 484 # SubProcessGroupList 485 #=============================================================================== 486 -class SubProcessGroupList(base_objects.PhysicsObjectList):
487 """List of SubProcessGroup objects""" 488
489 - def is_valid_element(self, obj):
490 """Test if object obj is a valid element.""" 491 492 return isinstance(obj, SubProcessGroup)
493
494 - def get_matrix_elements(self):
495 """Extract the list of matrix elements""" 496 return helas_objects.HelasMatrixElementList(\ 497 sum([group.get('matrix_elements') for group in self], []))
498
499 - def get_used_lorentz(self):
500 """Return the list of ALOHA routines used in these matrix elements""" 501 502 return helas_objects.HelasMultiProcess( 503 {'matrix_elements': self.get_matrix_elements()}).get_used_lorentz()
504
505 - def get_used_couplings(self):
506 """Return the list of ALOHA routines used in these matrix elements""" 507 508 return helas_objects.HelasMultiProcess( 509 {'matrix_elements': self.get_matrix_elements()}).get_used_couplings()
510
511 - def split_lepton_grouping(self):
512 """Return a list of grouping where they are no groupoing over the leptons.""" 513 514 output = SubProcessGroupList() 515 for group in self: 516 new_mes = {} 517 for me in group['matrix_elements']: 518 tags = {} 519 for proc in me['processes']: 520 ids = proc.get_final_ids_after_decay() 521 ids = tuple([t if abs(t) in [11, 13,15] else 0 for t in ids]) 522 if ids not in tags: 523 tags[ids] = base_objects.ProcessList() 524 tags[ids].append(proc) 525 for tag in tags: 526 new_me = copy.copy(me) 527 new_me['processes'] = tags[tag] 528 if tag not in new_mes: 529 new_mes[tag] = helas_objects.HelasMatrixElementList() 530 new_mes[tag].append(new_me) 531 for tag in tags: 532 new_group = copy.copy(group) 533 new_group['matrix_elements'] = new_mes[tag] 534 new_group.set('name', new_group.generate_name(\ 535 new_group['matrix_elements'][0]['processes'][0], 536 criteria='madweight')) 537 output.append(new_group) 538 return output
539
540 541 542 #=============================================================================== 543 # DecayChainSubProcessGroup 544 #=============================================================================== 545 546 -class DecayChainSubProcessGroup(SubProcessGroup):
547 """Class to keep track of subprocess groups from a list of decay chains""" 548
549 - def default_setup(self):
550 """Define object and give default values""" 551 552 self['core_groups'] = SubProcessGroupList() 553 self['decay_groups'] = DecayChainSubProcessGroupList() 554 # decay_chain_amplitudes is the original DecayChainAmplitudeList 555 self['decay_chain_amplitudes'] = diagram_generation.DecayChainAmplitudeList()
556
557 - def filter(self, name, value):
558 """Filter for valid property values.""" 559 560 if name == 'core_groups': 561 if not isinstance(value, SubProcessGroupList): 562 raise self.PhysicsObjectError, \ 563 "%s is not a valid core_groups" % str(value) 564 if name == 'decay_groups': 565 if not isinstance(value, DecayChainSubProcessGroupList): 566 raise self.PhysicsObjectError, \ 567 "%s is not a valid decay_groups" % str(value) 568 if name == 'decay_chain_amplitudes': 569 if not isinstance(value, diagram_generation.DecayChainAmplitudeList): 570 raise self.PhysicsObjectError, \ 571 "%s is not a valid DecayChainAmplitudeList" % str(value) 572 return True
573
574 - def get_sorted_keys(self):
575 """Return diagram property names as a nicely sorted list.""" 576 577 return ['core_groups', 'decay_groups', 'decay_chain_amplitudes']
578
579 - def nice_string(self, indent = 0):
580 """Returns a nicely formatted string of the content.""" 581 582 mystr = "" 583 for igroup, group in enumerate(self.get('core_groups')): 584 mystr += " " * indent + "Group %d:\n" % (igroup + 1) 585 for amplitude in group.get('amplitudes'): 586 mystr = mystr + amplitude.nice_string(indent + 2) + "\n" 587 588 if self.get('decay_groups'): 589 mystr += " " * indent + "Decay groups:\n" 590 for dec in self.get('decay_groups'): 591 mystr = mystr + dec.nice_string(indent + 2) + "\n" 592 593 return mystr[:-1]
594 595 #=========================================================================== 596 # generate_helas_decay_chain_subproc_groups 597 #===========================================================================
599 """Combine core_groups and decay_groups to give 600 HelasDecayChainProcesses and new diagram_maps. 601 """ 602 603 # Combine decays 604 matrix_elements = \ 605 helas_objects.HelasMultiProcess.generate_matrix_elements(\ 606 diagram_generation.AmplitudeList(\ 607 self.get('decay_chain_amplitudes'))) 608 609 610 # For each matrix element, check which group it should go into and 611 # calculate diagram_maps 612 me_assignments = {} 613 for me in matrix_elements: 614 group_assignment = self.assign_group_to_decay_process(\ 615 me.get('processes')[0]) 616 assert group_assignment 617 try: 618 me_assignments[group_assignment].append(me) 619 except KeyError: 620 me_assignments[group_assignment] = [me] 621 622 # Create subprocess groups corresponding to the different 623 # group_assignments 624 625 subproc_groups = SubProcessGroupList() 626 for key in sorted(me_assignments.keys()): 627 group = SubProcessGroup() 628 group.set('matrix_elements', helas_objects.HelasMatrixElementList(\ 629 me_assignments[key])) 630 group.set('number', group.get('matrix_elements')[0].\ 631 get('processes')[0].get('id')) 632 group.set('name', group.generate_name(\ 633 group.get('matrix_elements')[0].\ 634 get('processes')[0])) 635 subproc_groups.append(group) 636 637 return subproc_groups
638
639 - def assign_group_to_decay_process(self, process):
640 """Recursively identify which group process belongs to.""" 641 642 # Determine properties for the decay chains 643 # The entries of group_assignments are: 644 # [(decay_index, (decay_group_index, ...)), 645 # diagram_map (updated), len(mapping_diagrams)] 646 647 group_assignments = [] 648 649 for decay in process.get('decay_chains'): 650 # Find decay group that has this decay in it 651 ids = [l.get('id') for l in decay.get('legs')] 652 decay_groups = [(i, group) for (i, group) in \ 653 enumerate(self.get('decay_groups')) \ 654 if any([ids in [[l.get('id') for l in \ 655 a.get('process').get('legs')] \ 656 for a in g.get('amplitudes')] \ 657 for g in group.get('core_groups')])] 658 659 for decay_group in decay_groups: 660 661 group_assignment = \ 662 decay_group[1].assign_group_to_decay_process(decay) 663 664 if group_assignment: 665 group_assignments.append((decay_group[0], group_assignment)) 666 667 if process.get('decay_chains') and not group_assignments: 668 return None 669 670 # Now calculate the corresponding properties for process 671 672 # Find core process group 673 ids = [(l.get('id'),l.get('onshell')) for l in process.get('legs')] 674 core_groups = [(i, group) for (i, group) in \ 675 enumerate(self.get('core_groups')) \ 676 if ids in [[(l.get('id'),l.get('onshell')) for l in \ 677 a.get('process').get('legs')] \ 678 for a in group.get('amplitudes')] \ 679 and process.get('id') == group.get('number')] 680 681 if not core_groups: 682 return None 683 684 assert len(core_groups) == 1 685 686 core_group = core_groups[0] 687 # This is the first return argument - the chain of group indices 688 group_assignment = (core_group[0], 689 tuple([g for g in group_assignments])) 690 691 if not group_assignments: 692 # No decays - return the values for this process 693 return group_assignment 694 695 return group_assignment
696 697 #=========================================================================== 698 # group_amplitudes 699 #=========================================================================== 700 @staticmethod
701 - def group_amplitudes(decay_chain_amps, criteria='madevent', matrix_elements_opts={}):
702 """Recursive function. Starting from a DecayChainAmplitude, 703 return a DecayChainSubProcessGroup with the core amplitudes 704 and decay chains divided into subprocess groups""" 705 706 assert isinstance(decay_chain_amps, diagram_generation.DecayChainAmplitudeList), \ 707 "Argument to group_amplitudes must be DecayChainAmplitudeList" 708 if criteria in ['matrix', 'standalone','pythia8','standalone_cpp', False]: 709 criteria = 'madevent' 710 assert criteria in ['madevent', 'madweight'] 711 712 # Collect all amplitudes 713 amplitudes = diagram_generation.AmplitudeList() 714 for amp in decay_chain_amps: 715 amplitudes.extend(amp.get('amplitudes')) 716 717 # Determine core process groups 718 core_groups = SubProcessGroup.group_amplitudes(amplitudes, criteria) 719 720 dc_subproc_group = DecayChainSubProcessGroup(\ 721 {'core_groups': core_groups, 722 'decay_chain_amplitudes': decay_chain_amps}) 723 724 decays = diagram_generation.DecayChainAmplitudeList() 725 726 # Recursively determine decay chain groups 727 for decay_chain_amp in decay_chain_amps: 728 decays.extend(decay_chain_amp.get('decay_chains')) 729 730 if decays: 731 dc_subproc_group.get('decay_groups').append(\ 732 DecayChainSubProcessGroup.group_amplitudes(decays, criteria)) 733 734 return dc_subproc_group
735
736 737 738 739 #=============================================================================== 740 # DecayChainSubProcessGroupList 741 #=============================================================================== 742 -class DecayChainSubProcessGroupList(base_objects.PhysicsObjectList):
743 """List of DecayChainSubProcessGroup objects""" 744
745 - def is_valid_element(self, obj):
746 """Test if object obj is a valid element.""" 747 748 return isinstance(obj, DecayChainSubProcessGroup)
749