1
2
3
4
5
6
7
8
9
10
11
12
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')
56 """DiagramTag daughter class to identify diagrams giving the same
57 config. Need to compare leg number, mass, width, and color."""
58
59 @staticmethod
61 """Returns the end link for a leg needed to identify configs:
62 ((leg numer, spin, mass, width, color), number)."""
63
64 part = model.get_particle(leg.get('id'))
65
66 return [((leg.get('number'), part.get('spin'),
67 part.get('mass'), part.get('width'), part.get('color')),
68 leg.get('number'))]
69
70 @staticmethod
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
87 """Move the wavefunction part of propagator id appropriately"""
88
89 if len(new_vertex[0]) == 1 and len(old_vertex[0]) > 1:
90
91 return (old_vertex[0], new_vertex[0][0])
92 elif len(new_vertex[0]) > 1 and len(old_vertex[0]) == 1:
93
94 return (old_vertex[0],)
95
96 raise diagram_generation.DiagramTag.DiagramTagError, \
97 "Error in IdentifyConfigTag, wrong setup of vertices in link."
98
104 """Class to group a number of amplitudes with same initial states
105 into a subprocess group"""
106
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
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
167 - def get(self, name):
180
182 """Set mapping_diagrams and diagram_maps, to prepare for
183 generation of the super-config.inc files."""
184
185
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
194
220
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"
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
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
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
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
310
311 mapping_diagrams = []
312
313
314 equiv_diagrams = []
315
316
317
318 diagram_maps = {}
319
320 for ime, me in enumerate(matrix_elements):
321
322
323
324
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
335
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
344
345
346
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
352
353
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
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
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
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
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
456
457
458
459
460 if (criteria=="madevent"):
461 proc_class = [ [(p.is_fermion(), ) \
462 for p in is_parts],
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
487 """List of SubProcessGroup objects"""
488
490 """Test if object obj is a valid element."""
491
492 return isinstance(obj, SubProcessGroup)
493
498
504
510
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
547 """Class to keep track of subprocess groups from a list of decay chains"""
548
556
557 - def filter(self, name, value):
573
575 """Return diagram property names as a nicely sorted list."""
576
577 return ['core_groups', 'decay_groups', 'decay_chain_amplitudes']
578
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
597
638
640 """Recursively identify which group process belongs to."""
641
642
643
644
645
646
647 group_assignments = []
648
649 for decay in process.get('decay_chains'):
650
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
671
672
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
688 group_assignment = (core_group[0],
689 tuple([g for g in group_assignments]))
690
691 if not group_assignments:
692
693 return group_assignment
694
695 return group_assignment
696
697
698
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
713 amplitudes = diagram_generation.AmplitudeList()
714 for amp in decay_chain_amps:
715 amplitudes.extend(amp.get('amplitudes'))
716
717
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
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
743 """List of DecayChainSubProcessGroup objects"""
744
749