| Trees | Indices | Help |
|---|
|
|
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 """Classes for diagram generation with loop features.
16 """
17
18 import array
19 import copy
20 import itertools
21 import logging
22
23 import madgraph.loop.loop_base_objects as loop_base_objects
24 import madgraph.core.base_objects as base_objects
25 import madgraph.core.diagram_generation as diagram_generation
26 import madgraph.various.misc as misc
27
28 from madgraph import MadGraph5Error
29 from madgraph import InvalidCmd
30 logger = logging.getLogger('madgraph.loop_diagram_generation')
33 # This subroutine has typically quite large DEBUG info.
34 # So even in debug mode, they are turned off by default.
35 # Remove the line below for loop diagram generation diagnostic
36 if not force: return
37
38 flag = "LoopGenInfo: "
39 if len(msg)>40:
40 logger.debug(flag+msg[:35]+" [...] = %s"%str(val))
41 else:
42 logger.debug(flag+msg+''.join([' ']*(40-len(msg)))+' = %s'%str(val))
43
44 #===============================================================================
45 # LoopAmplitude
46 #===============================================================================
47 -class LoopAmplitude(diagram_generation.Amplitude):
48 """NLOAmplitude: process + list of diagrams (ordered)
49 Initialize with a process, then call generate_diagrams() to
50 generate the diagrams for the amplitude
51 """
52
54 """Default values for all properties"""
55
56 # The 'diagrams' entry from the mother class is inherited but will not
57 # be used in NLOAmplitude, because it is split into the four following
58 # different categories of diagrams.
59 super(LoopAmplitude, self).default_setup()
60 self['born_diagrams'] = None
61 self['loop_diagrams'] = None
62 self['loop_UVCT_diagrams'] = base_objects.DiagramList()
63 # This is in principle equal to self['born_diagram']==[] but it can be
64 # that for some reason the born diagram can be generated but do not
65 # contribute.
66 # This will decide wether the virtual is squared against the born or
67 # itself.
68 self['has_born'] = True
69 # This where the structures obtained for this amplitudes are stored
70 self['structure_repository'] = loop_base_objects.FDStructureList()
71
72 # A list that registers what Lcut particle have already been
73 # employed in order to forbid them as loop particles in the
74 # subsequent diagram generation runs.
75 self.lcutpartemployed=[]
76
78 """Allow initialization with Process.
79 If loop_filter is not None, then it will be applied to all subsequent
80 diagram generation from this LoopAmplitude."""
81
82 self.loop_filter = loop_filter
83
84 if isinstance(argument, base_objects.Process):
85 super(LoopAmplitude, self).__init__()
86 self.set('process', argument)
87 self.generate_diagrams()
88 elif argument != None:
89 # call the mother routine
90 super(LoopAmplitude, self).__init__(argument)
91 else:
92 # call the mother routine
93 super(LoopAmplitude, self).__init__()
94
96 """Return diagram property names as a nicely sorted list."""
97
98 return ['process', 'diagrams', 'has_mirror_process', 'born_diagrams',
99 'loop_diagrams','has_born',
100 'structure_repository']
101
103 """Filter for valid amplitude property values."""
104
105 if name == 'diagrams':
106 if not isinstance(value, base_objects.DiagramList):
107 raise self.PhysicsObjectError, \
108 "%s is not a valid DiagramList" % str(value)
109 for diag in value:
110 if not isinstance(diag,loop_base_objects.LoopDiagram) and \
111 not isinstance(diag,loop_base_objects.LoopUVCTDiagram):
112 raise self.PhysicsObjectError, \
113 "%s contains a diagram which is not an NLODiagrams." % str(value)
114 if name == 'born_diagrams':
115 if not isinstance(value, base_objects.DiagramList):
116 raise self.PhysicsObjectError, \
117 "%s is not a valid DiagramList" % str(value)
118 for diag in value:
119 if not isinstance(diag,loop_base_objects.LoopDiagram):
120 raise self.PhysicsObjectError, \
121 "%s contains a diagram which is not an NLODiagrams." % str(value)
122 if name == 'loop_diagrams':
123 if not isinstance(value, base_objects.DiagramList):
124 raise self.PhysicsObjectError, \
125 "%s is not a valid DiagramList" % str(value)
126 for diag in value:
127 if not isinstance(diag,loop_base_objects.LoopDiagram):
128 raise self.PhysicsObjectError, \
129 "%s contains a diagram which is not an NLODiagrams." % str(value)
130 if name == 'has_born':
131 if not isinstance(value, bool):
132 raise self.PhysicsObjectError, \
133 "%s is not a valid bool" % str(value)
134 if name == 'structure_repository':
135 if not isinstance(value, loop_base_objects.FDStructureList):
136 raise self.PhysicsObjectError, \
137 "%s is not a valid bool" % str(value)
138
139 else:
140 super(LoopAmplitude, self).filter(name, value)
141
142 return True
143
145 """Redefine set for the particular case of diagrams"""
146
147 if name == 'diagrams':
148 if self.filter(name, value):
149 self['born_diagrams']=base_objects.DiagramList([diag for diag in value if \
150 not isinstance(diag,loop_base_objects.LoopUVCTDiagram) and diag['type']==0])
151 self['loop_diagrams']=base_objects.DiagramList([diag for diag in value if \
152 not isinstance(diag,loop_base_objects.LoopUVCTDiagram) and diag['type']!=0])
153 self['loop_UVCT_diagrams']=base_objects.DiagramList([diag for diag in value if \
154 isinstance(diag,loop_base_objects.LoopUVCTDiagram)])
155
156 else:
157 return super(LoopAmplitude, self).set(name, value)
158
159 return True
160
162 """Redefine get for the particular case of '*_diagrams' property"""
163
164 if name == 'diagrams':
165 if self['process'] and self['loop_diagrams'] == None:
166 self.generate_diagrams()
167 return base_objects.DiagramList(self['born_diagrams']+\
168 self['loop_diagrams']+\
169 self['loop_UVCT_diagrams'])
170
171 if name == 'born_diagrams':
172 if self['born_diagrams'] == None:
173 # Have not yet generated born diagrams for this process
174 if self['process']['has_born']:
175 if self['process']:
176 self.generate_born_diagrams()
177 else:
178 self['born_diagrams']=base_objects.DiagramList()
179
180 return LoopAmplitude.__bases__[0].get(self, name) #return the mother routine
181
182 # Functions of the different tasks performed in generate_diagram
184 """ Choose the configuration of non-perturbed coupling orders to be
185 retained for all diagrams. This is used when the user did not specify
186 any order. """
187 chosen_order_config = {}
188 min_wgt = self['born_diagrams'].get_min_order('WEIGHTED')
189 # Scan the born diagrams of minimum weight to chose a configuration
190 # of non-perturbed orders.
191 min_non_pert_order_wgt = -1
192 for diag in [d for d in self['born_diagrams'] if \
193 d.get_order('WEIGHTED')==min_wgt]:
194 non_pert_order_wgt = min_wgt - sum([diag.get_order(order)*\
195 self['process']['model']['order_hierarchy'][order] for order in \
196 self['process']['perturbation_couplings']])
197 if min_non_pert_order_wgt == -1 or \
198 non_pert_order_wgt<min_non_pert_order_wgt:
199 chosen_order_config = self.get_non_pert_order_config(diag)
200 logger.info("Chosen coupling orders configuration: (%s)"\
201 %self.print_config(chosen_order_config))
202 return chosen_order_config
203
205 """If squared orders (other than WEIGHTED) are defined, then they can be
206 used for determining what is the expected upper bound for the order
207 restricting loop diagram generation."""
208 for order, value in self['process']['squared_orders'].items():
209 if order.upper()!='WEIGHTED' and order not in self['process']['orders']:
210 # If the bound is of type '>' we cannot say anything
211 if self['process'].get('sqorders_types')[order]=='>':
212 continue
213 # If there is no born, the min order will simply be 0 as it should.
214 bornminorder=self['born_diagrams'].get_min_order(order)
215 if value>=0:
216 self['process']['orders'][order]=value-bornminorder
217 elif self['process']['has_born']:
218 # This means the user want the leading if order=-1 or N^n
219 # Leading term if order=-n. If there is a born diag, we can
220 # infer the necessary maximum order in the loop:
221 # bornminorder+2*(n-1).
222 # If there is no born diag, then we cannot say anything.
223 self['process']['orders'][order]=bornminorder+2*(-value-1)
224
226 """Guess the upper bound for the orders for loop diagram generation
227 based on either no squared orders or simply 'Weighted'"""
228
229 hierarchy = self['process']['model']['order_hierarchy']
230
231 # Maximum of the hierarchy weigtht among all perturbed order
232 max_pert_wgt = max([hierarchy[order] for order in \
233 self['process']['perturbation_couplings']])
234
235 # In order to be sure to catch the corrections to all born diagrams that
236 # the user explicitly asked for with the amplitude orders, we take here
237 # the minimum weighted order as being the maximum between the min weighted
238 # order detected in the Born diagrams and the weight computed from the
239 # user input amplitude orders.
240 user_min_wgt = 0
241
242 # One can chose between the two behaviors below. It is debatable which
243 # one is best. The first one tries to only consider the loop which are
244 # dominant, even when the user selects the amplitude orders and the
245 # second chosen here makes sure that the user gets a correction of the
246 # desired type for all the born diagrams generated with its amplitude
247 # order specification.
248 # min_born_wgt=self['born_diagrams'].get_min_order('WEIGHTED')
249 min_born_wgt=max(self['born_diagrams'].get_min_order('WEIGHTED'),
250 sum([hierarchy[order]*val for order, val in user_orders.items() \
251 if order!='WEIGHTED']))
252
253 if 'WEIGHTED' not in [key.upper() for key in \
254 self['process']['squared_orders'].keys()]:
255 # Then we guess it from the born
256 self['process']['squared_orders']['WEIGHTED']= 2*(min_born_wgt+\
257 max_pert_wgt)
258
259 # Now we know that the remaining weighted orders which can fit in
260 # the loop diagram is (self['target_weighted_order']-
261 # min_born_weighted_order) so for each perturbed order we just have to
262 # take that number divided by its hierarchy weight to have the maximum
263 # allowed order for the loop diagram generation. Of course,
264 # we don't overwrite any order already defined by the user.
265 if self['process']['squared_orders']['WEIGHTED']>=0:
266 trgt_wgt=self['process']['squared_orders']['WEIGHTED']-min_born_wgt
267 else:
268 trgt_wgt=min_born_wgt+(-self['process']['squared_orders']['WEIGHTED']+1)*2
269 # We also need the minimum number of vertices in the born.
270 min_nvert=min([len([1 for vert in diag['vertices'] if vert['id']!=0]) \
271 for diag in self['born_diagrams']])
272 # And the minimum weight for the ordered declared as perturbed
273 min_pert=min([hierarchy[order] for order in \
274 self['process']['perturbation_couplings']])
275
276 for order, value in hierarchy.items():
277 if order not in self['process']['orders']:
278 # The four cases below come from a study of the maximal order
279 # needed in the loop for the weighted order needed and the
280 # number of vertices available.
281 if order in self['process']['perturbation_couplings']:
282 if value!=1:
283 self['process']['orders'][order]=\
284 int((trgt_wgt-min_nvert-2)/(value-1))
285 else:
286 self['process']['orders'][order]=int(trgt_wgt)
287 else:
288 if value!=1:
289 self['process']['orders'][order]=\
290 int((trgt_wgt-min_nvert-2*min_pert)/(value-1))
291 else:
292 self['process']['orders'][order]=\
293 int(trgt_wgt-2*min_pert)
294 # Now for the remaining orders for which the user has not set squared
295 # orders neither amplitude orders, we use the max order encountered in
296 # the born (and add 2 if this is a perturbed order).
297 # It might be that this upper bound is better than the one guessed
298 # from the hierarchy.
299 for order in self['process']['model']['coupling_orders']:
300 neworder=self['born_diagrams'].get_max_order(order)
301 if order in self['process']['perturbation_couplings']:
302 neworder+=2
303 if order not in self['process']['orders'].keys() or \
304 neworder<self['process']['orders'][order]:
305 self['process']['orders'][order]=neworder
306
308 """ Filter diags to select only the diagram with the non perturbed orders
309 configuration config and update discarded_configurations.Diags is the
310 name of the key attribute of this class containing the diagrams to
311 filter."""
312 newdiagselection = base_objects.DiagramList()
313 for diag in self[diags]:
314 diag_config = self.get_non_pert_order_config(diag)
315 if diag_config == config:
316 newdiagselection.append(diag)
317 elif diag_config not in discarded_configurations:
318 discarded_configurations.append(diag_config)
319 self[diags] = newdiagselection
320
322 """ Remove the loops which are zero because of Furry theorem. So as to
323 limit any possible mistake in case of BSM model, I limit myself here to
324 removing SM-quark loops with external legs with an odd number of photons,
325 possibly including exactly two gluons."""
326
327 new_diag_selection = base_objects.DiagramList()
328
329 n_discarded = 0
330 for diag in self['loop_diagrams']:
331 if diag.get('tag')==[]:
332 raise MadGraph5Error, "The loop diagrams should have been tagged"+\
333 " before going through the Furry filter."
334
335 loop_line_pdgs = diag.get_loop_lines_pdgs()
336 attached_pdgs = diag.get_pdgs_attached_to_loop(structs)
337 if (attached_pdgs.count(22)%2==1) and \
338 (attached_pdgs.count(21) in [0,2]) and \
339 (all(pdg in [22,21] for pdg in attached_pdgs)) and \
340 (abs(loop_line_pdgs[0]) in list(range(1,7))) and \
341 (all(abs(pdg)==abs(loop_line_pdgs[0]) for pdg in loop_line_pdgs)):
342 n_discarded += 1
343 else:
344 new_diag_selection.append(diag)
345
346 self['loop_diagrams'] = new_diag_selection
347
348 if n_discarded > 0:
349 logger.debug(("MadLoop discarded %i diagram%s because they appeared"+\
350 " to be zero because of Furry theorem.")%(n_discarded,'' if \
351 n_discarded<=1 else 's'))
352
353 @staticmethod
355 """ Returns a function which applies the filter corresponding to the
356 conditional expression encoded in filterdef."""
357
358 def filter(diag, structs, model, id):
359 """ The filter function generated '%s'."""%filterdef
360
361 loop_pdgs = diag.get_loop_lines_pdgs()
362 struct_pdgs = diag.get_pdgs_attached_to_loop(structs)
363 loop_masses = [model.get_particle(pdg).get('mass') for pdg in loop_pdgs]
364 struct_masses = [model.get_particle(pdg).get('mass') for pdg in struct_pdgs]
365 if not eval(filterdef.lower(),{'n':len(loop_pdgs),
366 'loop_pdgs':loop_pdgs,
367 'struct_pdgs':struct_pdgs,
368 'loop_masses':loop_masses,
369 'struct_masses':struct_masses,
370 'id':id}):
371 return False
372 else:
373 return True
374
375 return filter
376
378 """ User-defined user-filter. By default it is not called, but the expert
379 user can turn it on and code here is own filter. Some default examples
380 are provided here.
381 The tagging of the loop diagrams must be performed before using this
382 user loop filter"""
383
384 # By default the user filter does nothing if filter is not set,
385 # if you want to turn it on and edit it by hand, then set the
386 # variable edit_filter_manually to True
387 edit_filter_manually = False
388 if not edit_filter_manually and filter in [None,'None']:
389 return
390
391 if filter not in [None,'None']:
392 filter_func = LoopAmplitude.get_loop_filter(filter)
393 else:
394 filter_func = None
395
396 new_diag_selection = base_objects.DiagramList()
397 discarded_diags = base_objects.DiagramList()
398 i=0
399 for diag in self['loop_diagrams']:
400 if diag.get('tag')==[]:
401 raise MadGraph5Error, "Before using the user_filter, please "+\
402 "make sure that the loop diagrams have been tagged first."
403 valid_diag = True
404 i=i+1
405
406 # Apply the custom filter specified if any
407 if filter_func:
408 try:
409 valid_diag = filter_func(diag, structs, model, i)
410 except Exception as e:
411 raise InvalidCmd("The user-defined filter '%s' did not"%filter+
412 " returned the following error:\n > %s"%str(e))
413 # if any([abs(i)!=1000021 for i in diag.get_loop_lines_pdgs()]):
414 # valid_diag=False
415 # if len(diag.get_loop_lines_pdgs())<4:
416 # valid_diag = False
417
418 # Ex. 0: Chose a specific diagram number, here the 8th one for ex.
419 # if i not in [31]:
420 # valid_diag = False
421
422 # Ex. 0: Keeps only the top quark loops.
423 # if any([pdg not in [6,-6] for pdg in diag.get_loop_lines_pdgs()]):
424 # valid_diag = False
425
426 # Ex. 1: Chose the topology, i.e. number of loop line.
427 # Notice that here particles and antiparticles are not
428 # differentiated and always the particle PDG is returned.
429 # In this example, only boxes are selected.
430 # if len(diag.get_loop_lines_pdgs())>2 and \
431 # any([i in diag.get_loop_lines_pdgs() for i in[24,-24,23]]):
432 # valid_diag=False
433
434 # Ex. 2: Use the pdgs of the particles directly attached to the loop.
435 # In this example, we forbid the Z to branch off the loop.
436 # if any([pdg not in [6,-6] for pdg in diag.get_loop_lines_pdgs()]) or \
437 # 25 not in diag.get_pdgs_attached_to_loop(structs):
438 # valid_diag=False
439
440 # Ex. 3: Filter based on the mass of the particles running in the
441 # loop. It shows how to access the particles properties from
442 # the PDG.
443 # In this example, only massive parts. are allowed in the loop.
444 # if 'ZERO' in [model.get_particle(pdg).get('mass') for pdg in \
445 # diag.get_loop_lines_pdgs()]:
446 # valid_diag=False
447
448 # Ex. 4: Complicated filter which gets rid of all bubble diagrams made
449 # of two vertices being the four gluon vertex and the effective
450 # glu-glu-Higgs vertex.
451 # if len(diag.get_loop_lines_pdgs())==2:
452 # bubble_lines_pdgs=[abs(diag.get('canonical_tag')[0][0]),
453 # abs(diag.get('canonical_tag')[0][0])]
454 # first_vertex_pdgs=bubble_lines_pdgs+\
455 # [abs(structs.get_struct(struct_ID).get('binding_leg').get('id')) \
456 # for struct_ID in diag.get('canonical_tag')[0][1]]
457 # second_vertex_pdgs=bubble_lines_pdgs+\
458 # [abs(structs.get_struct(struct_ID).get('binding_leg').get('id')) \
459 # for struct_ID in diag.get('canonical_tag')[1][1]]
460 # first_vertex_pdgs.sort()
461 # second_vertex_pdgs.sort()
462 # bubble_vertices=[first_vertex_pdgs,second_vertex_pdgs]
463 # bubble_vertices.sort()
464 # if bubble_vertices==[[21,21,21,21],[21,21,25]]:
465 # valid_diag=False
466
467 # If you need any more advanced function for your filter and cannot
468 # figure out how to implement them. Just contact the authors.
469
470 if valid_diag:
471 new_diag_selection.append(diag)
472 else:
473 discarded_diags.append(diag)
474
475 self['loop_diagrams'] = new_diag_selection
476 if filter in [None,'None']:
477 warn_msg = """
478 The user-defined loop diagrams filter is turned on and discarded %d loops."""\
479 %len(discarded_diags)
480 else:
481 warn_msg = """
482 The loop diagrams filter '%s' is turned on and discarded %d loops."""\
483 %(filter,len(discarded_diags))
484 logger.warning(warn_msg)
485
487 """ Filter the loop diagrams to make sure they belong to the class
488 of coupling orders perturbed. """
489
490 # First define what are the set of particles allowed to run in the loop.
491 allowedpart=[]
492 for part in self['process']['model']['particles']:
493 for order in self['process']['perturbation_couplings']:
494 if part.is_perturbating(order,self['process']['model']):
495 allowedpart.append(part.get_pdg_code())
496 break
497
498 newloopselection=base_objects.DiagramList()
499 warned=False
500 warning_msg = ("Some loop diagrams contributing to this process"+\
501 " are discarded because they are not pure (%s)-perturbation.\nMake sure"+\
502 " you did not want to include them.")%\
503 ('+'.join(self['process']['perturbation_couplings']))
504 for i,diag in enumerate(self['loop_diagrams']):
505 # Now collect what are the coupling orders building the loop which
506 # are also perturbed order.
507 loop_orders=diag.get_loop_orders(self['process']['model'])
508 pert_loop_order=set(loop_orders.keys()).intersection(\
509 set(self['process']['perturbation_couplings']))
510 # Then make sure that the particle running in the loop for all
511 # diagrams belong to the set above. Also make sure that there is at
512 # least one coupling order building the loop which is in the list
513 # of the perturbed order.
514 valid_diag=True
515 if (diag.get_loop_line_types()-set(allowedpart))!=set() or \
516 pert_loop_order==set([]):
517 valid_diag=False
518 if not warned:
519 logger.warning(warning_msg)
520 warned=True
521 if len([col for col in [
522 self['process'].get('model').get_particle(pdg).get('color') \
523 for pdg in diag.get_pdgs_attached_to_loop(\
524 self['structure_repository'])] if col!=1])==1:
525 valid_diag=False
526
527 if valid_diag:
528 newloopselection.append(diag)
529 self['loop_diagrams']=newloopselection
530 # To monitor what are the diagrams filtered, simply comment the line
531 # directly above and uncomment the two directly below.
532 # self['loop_diagrams'] = base_objects.DiagramList(
533 # [diag for diag in self['loop_diagrams'] if diag not in newloopselection])
534
536 """ Makes sure that all non perturbed orders factorize the born diagrams
537 """
538 warning_msg = "All Born diagrams do not factorize the same sum of power(s) "+\
539 "of the the perturbed order(s) %s.\nThis is potentially dangerous"+\
540 " as the real-emission diagrams from aMC@NLO will not be consistent"+\
541 " with these virtual contributions."
542 if self['process']['has_born']:
543 trgt_summed_order = sum([self['born_diagrams'][0].get_order(order)
544 for order in self['process']['perturbation_couplings']])
545 for diag in self['born_diagrams'][1:]:
546 if sum([diag.get_order(order) for order in self['process']
547 ['perturbation_couplings']])!=trgt_summed_order:
548 logger.warning(warning_msg%' '.join(self['process']
549 ['perturbation_couplings']))
550 break
551
552 warning_msg = "All born diagrams do not factorize the same power of "+\
553 "the order %s which is not perturbed and for which you have not"+\
554 "specified any amplitude order. \nThis is potentially dangerous"+\
555 " as the real-emission diagrams from aMC@NLO will not be consistent"+\
556 " with these virtual contributions."
557 if self['process']['has_born']:
558 for order in self['process']['model']['coupling_orders']:
559 if order not in self['process']['perturbation_couplings'] and \
560 order not in user_orders.keys():
561 order_power=self['born_diagrams'][0].get_order(order)
562 for diag in self['born_diagrams'][1:]:
563 if diag.get_order(order)!=order_power:
564 logger.warning(warning_msg%order)
565 break
566
567 # Helper function
569 """ Return a dictionary of all the coupling orders of this diagram which
570 are not the perturbed ones."""
571 return dict([(order, diagram.get_order(order)) for \
572 order in self['process']['model']['coupling_orders'] if \
573 not order in self['process']['perturbation_couplings'] ])
574
576 """Return a string describing the coupling order configuration"""
577 res = []
578 for order in self['process']['model']['coupling_orders']:
579 try:
580 res.append('%s=%d'%(order,config[order]))
581 except KeyError:
582 res.append('%s=*'%order)
583 return ','.join(res)
584
586 """ Generates all diagrams relevant to this Loop Process """
587
588 # Description of the algorithm to guess the leading contribution.
589 # The summed weighted order of each diagram will be compared to
590 # 'target_weighted_order' which acts as a threshold to decide which
591 # diagram to keep. Here is an example on how MG5 sets the
592 # 'target_weighted_order'.
593 #
594 # In the sm process uu~ > dd~ [QCD, QED] with hierarchy QCD=1, QED=2 we
595 # would have at leading order contribution like
596 # (QED=4) , (QED=2, QCD=2) , (QCD=4)
597 # leading to a summed weighted order of respectively
598 # (4*2=8) , (2*2+2*1=6) , (4*1=4)
599 # at NLO in QCD and QED we would have the following possible contributions
600 # (QED=6), (QED=4,QCD=2), (QED=2,QCD=4) and (QCD=6)
601 # which translate into the following weighted orders, respectively
602 # 12, 10, 8 and 6
603 # So, now we take the largest weighted order at born level, 4, and add two
604 # times the largest weight in the hierarchy among the order for which we
605 # consider loop perturbation, in this case 2*2 wich gives us a
606 # target_weighted_order of 8. based on this we will now keep all born
607 # contributions and exclude the NLO contributions (QED=6) and (QED=4,QCD=2)
608
609 # Use the globally defined loop_filter if the locally defined one is empty
610 if (not self.loop_filter is None) and (loop_filter is None):
611 loop_filter = self.loop_filter
612
613 logger.debug("Generating %s "\
614 %self['process'].nice_string().replace('Process', 'process'))
615
616 # Hierarchy and model shorthands
617 model = self['process']['model']
618 hierarchy = model['order_hierarchy']
619
620 # Later, we will specify the orders for the loop amplitude.
621 # It is a temporary change that will be reverted after loop diagram
622 # generation. We then back up here its value prior modification.
623 user_orders=copy.copy(self['process']['orders'])
624 # First generate the born diagram if the user asked for it
625 if self['process']['has_born']:
626 bornsuccessful = self.generate_born_diagrams()
627 ldg_debug_info("# born diagrams after first generation",\
628 len(self['born_diagrams']))
629 else:
630 self['born_diagrams'] = base_objects.DiagramList()
631 bornsuccessful = True
632 logger.debug("Born diagrams generation skipped by user request.")
633
634 # Make sure that all orders specified belong to the model:
635 for order in self['process']['orders'].keys()+\
636 self['process']['squared_orders'].keys():
637 if not order in model.get('coupling_orders') and \
638 order != 'WEIGHTED':
639 raise InvalidCmd("Coupling order %s not found"%order +\
640 " in any interaction of the current model %s."%model['name'])
641
642 # The decision of whether the virtual must be squared against the born or the
643 # virtual is made based on whether there are Born or not unless the user
644 # already asked for the loop squared.
645 if self['process']['has_born']:
646 self['process']['has_born'] = self['born_diagrams']!=[]
647 self['has_born'] = self['process']['has_born']
648
649 ldg_debug_info("User input born orders",self['process']['orders'])
650 ldg_debug_info("User input squared orders",
651 self['process']['squared_orders'])
652 ldg_debug_info("User input perturbation",\
653 self['process']['perturbation_couplings'])
654
655 # Now, we can further specify the orders for the loop amplitude.
656 # Those specified by the user of course remain the same, increased by
657 # two if they are perturbed. It is a temporary change that will be
658 # reverted after loop diagram generation.
659 user_orders=copy.copy(self['process']['orders'])
660 user_squared_orders=copy.copy(self['process']['squared_orders'])
661
662 # If the user did not specify any order, we can expect him not to be an
663 # expert. So we must make sure the born all factorize the same powers of
664 # coupling orders which are not perturbed. If not we chose a configuration
665 # of non-perturbed order which has the smallest total weight and inform
666 # the user about this. It is then stored below for later filtering of
667 # the loop diagrams.
668 chosen_order_config={}
669 if self['process']['squared_orders']=={} and \
670 self['process']['orders']=={} and self['process']['has_born']:
671 chosen_order_config = self.choose_order_config()
672
673 discarded_configurations = []
674 # The born diagrams are now filtered according to the chose configuration
675 if chosen_order_config != {}:
676 self.filter_from_order_config('born_diagrams', \
677 chosen_order_config,discarded_configurations)
678
679 # Before proceeding with the loop contributions, we must make sure that
680 # the born diagram generated factorize the same sum of power of the
681 # perturbed couplings. If this is not true, then it is very
682 # cumbersome to get the real radiation contribution correct and consistent
683 # with the computations of the virtuals (for now).
684 # Also, when MadLoop5 guesses the a loop amplitude order on its own, it
685 # might decide not to include some subleading loop which might be not
686 # be consistently neglected for now in the MadFKS5 so that its best to
687 # warn the user that he should enforce that target born amplitude order
688 # to any value of his choice.
689 self.check_factorization(user_orders)
690
691 # Now find an upper bound for the loop diagram generation.
692 self.guess_loop_orders_from_squared()
693
694 # If the user had not specified any fixed squared order other than
695 # WEIGHTED, we will use the guessed weighted order to assign a bound to
696 # the loop diagram order. Later we will check if the order deduced from
697 # the max order appearing in the born diagrams is a better upper bound.
698 # It will set 'WEIGHTED' to the desired value if it was not already set
699 # by the user. This is why you see the process defined with 'WEIGHTED'
700 # in the squared orders no matter the user input. Leave it like this.
701 if [k.upper() for k in self['process']['squared_orders'].keys()] in \
702 [[],['WEIGHTED']] and self['process']['has_born']:
703 self.guess_loop_orders(user_orders)
704
705 # Finally we enforce the use of the orders specified for the born
706 # (augmented by two if perturbed) by the user, no matter what was
707 # the best guess performed above.
708 for order in user_orders.keys():
709 if order in self['process']['perturbation_couplings']:
710 self['process']['orders'][order]=user_orders[order]+2
711 else:
712 self['process']['orders'][order]=user_orders[order]
713 if 'WEIGHTED' in user_orders.keys():
714 self['process']['orders']['WEIGHTED']=user_orders['WEIGHTED']+\
715 2*min([hierarchy[order] for order in \
716 self['process']['perturbation_couplings']])
717
718 ldg_debug_info("Orders used for loop generation",\
719 self['process']['orders'])
720
721 # Make sure to warn the user if we already possibly excluded mixed order
722 # loops by smartly setting up the orders
723 warning_msg = ("Some loop diagrams contributing to this process might "+\
724 "be discarded because they are not pure (%s)-perturbation.\nMake sure"+\
725 " there are none or that you did not want to include them.")%(\
726 ','.join(self['process']['perturbation_couplings']))
727
728 if self['process']['has_born']:
729 for order in model['coupling_orders']:
730 if order not in self['process']['perturbation_couplings']:
731 try:
732 if self['process']['orders'][order]< \
733 self['born_diagrams'].get_max_order(order):
734 logger.warning(warning_msg)
735 break
736 except KeyError:
737 pass
738
739 # Now we can generate the loop diagrams.
740 totloopsuccessful=self.generate_loop_diagrams()
741
742 # If there is no born neither loop diagrams, return now.
743 if not self['process']['has_born'] and not self['loop_diagrams']:
744 self['process']['orders'].clear()
745 self['process']['orders'].update(user_orders)
746 return False
747
748 # We add here the UV renormalization contribution built in
749 # LoopUVCTDiagram. It is done before the squared order selection because
750 # it is possible that some UV-renorm. diagrams are removed as well.
751 if self['process']['has_born']:
752 self.set_Born_CT()
753
754 ldg_debug_info("#UVCTDiags generated",len(self['loop_UVCT_diagrams']))
755
756 # Reset the orders to their original specification by the user
757 self['process']['orders'].clear()
758 self['process']['orders'].update(user_orders)
759
760 # If there was no born, we will guess the WEIGHT squared order only now,
761 # based on the minimum weighted order of the loop contributions, if it
762 # was not specified by the user.
763 if not self['process']['has_born'] and not \
764 self['process']['squared_orders'] and not\
765 self['process']['orders'] and hierarchy:
766 pert_order_weights=[hierarchy[order] for order in \
767 self['process']['perturbation_couplings']]
768 self['process']['squared_orders']['WEIGHTED']=2*(\
769 self['loop_diagrams'].get_min_order('WEIGHTED')+\
770 max(pert_order_weights)-min(pert_order_weights))
771
772 ldg_debug_info("Squared orders after treatment",\
773 self['process']['squared_orders'])
774 ldg_debug_info("#Diags after diagram generation",\
775 len(self['loop_diagrams']))
776
777
778 # If a special non perturbed order configuration was chosen at the
779 # beginning because of the absence of order settings by the user,
780 # the corresponding filter is applied now to loop diagrams.
781 # List of discarded configurations
782 if chosen_order_config != {}:
783 self.filter_from_order_config('loop_diagrams', \
784 chosen_order_config,discarded_configurations)
785 # # Warn about discarded configurations.
786 if discarded_configurations!=[]:
787 msg = ("The contribution%s of th%s coupling orders "+\
788 "configuration%s %s discarded :%s")%(('s','ese','s','are','\n')\
789 if len(discarded_configurations)>1 else ('','is','','is',' '))
790 msg = msg + '\n'.join(['(%s)'%self.print_config(conf) for conf \
791 in discarded_configurations])
792 msg = msg + "\nManually set the coupling orders to "+\
793 "generate %sthe contribution%s above."%(('any of ','s') if \
794 len(discarded_configurations)>1 else ('',''))
795 logger.info(msg)
796
797 # The minimum of the different orders used for the selections can
798 # possibly increase, after some loop diagrams are selected out.
799 # So this check must be iterated until the number of diagrams
800 # remaining is stable.
801 # We first apply the selection rules without the negative constraint.
802 # (i.e. QCD=1 for LO contributions only)
803 regular_constraints = dict([(key,val) for (key,val) in
804 self['process']['squared_orders'].items() if val>=0])
805 negative_constraints = dict([(key,val) for (key,val) in
806 self['process']['squared_orders'].items() if val<0])
807 while True:
808 ndiag_remaining=len(self['loop_diagrams']+self['born_diagrams'])
809 self.check_squared_orders(regular_constraints)
810 if len(self['loop_diagrams']+self['born_diagrams'])==ndiag_remaining:
811 break
812 # And then only the negative ones
813 if negative_constraints!={}:
814 # It would be meaningless here to iterate because <order>=-X would
815 # have a different meaning every time.
816 # notice that this function will change the negative values of
817 # self['process']['squared_orders'] to their corresponding positive
818 # constraint for the present process.
819 # For example, u u~ > d d~ QCD^2=-2 becomes u u~ > d d~ QCD=2
820 # because the LO QCD contribution has QED=4, QCD=0 and the NLO one
821 # selected with -2 is QED=2, QCD=2.
822 self.check_squared_orders(negative_constraints,user_squared_orders)
823
824 ldg_debug_info("#Diags after constraints",len(self['loop_diagrams']))
825 ldg_debug_info("#Born diagrams after constraints",len(self['born_diagrams']))
826 ldg_debug_info("#UVCTDiags after constraints",len(self['loop_UVCT_diagrams']))
827
828 # Now the loop diagrams are tagged and filtered for redundancy.
829 tag_selected=[]
830 loop_basis=base_objects.DiagramList()
831 for diag in self['loop_diagrams']:
832 diag.tag(self['structure_repository'],model)
833 # Make sure not to consider wave-function renormalization, vanishing tadpoles,
834 # or redundant diagrams
835 if not diag.is_wf_correction(self['structure_repository'], \
836 model) and not diag.is_vanishing_tadpole(model) and \
837 diag['canonical_tag'] not in tag_selected:
838 loop_basis.append(diag)
839 tag_selected.append(diag['canonical_tag'])
840
841 self['loop_diagrams']=loop_basis
842
843 # Now select only the loops corresponding to the perturbative orders
844 # asked for.
845 self.filter_loop_for_perturbative_orders()
846
847 if len(self['loop_diagrams'])==0 and len(self['born_diagrams'])!=0:
848 raise InvalidCmd('All loop diagrams discarded by user selection.\n'+\
849 'Consider using a tree-level generation or relaxing the coupling'+\
850 ' order constraints.')
851 # If there is no born neither loop diagrams after filtering, return now.
852 if not self['process']['has_born'] and not self['loop_diagrams']:
853 self['process']['squared_orders'].clear()
854 self['process']['squared_orders'].update(user_squared_orders)
855 return False
856
857
858 # Discard diagrams which are zero because of Furry theorem
859 self.remove_Furry_loops(model,self['structure_repository'])
860
861 # Apply here some user-defined filter.
862 # For expert only, you can edit your own filter by modifying the
863 # user_filter() function which by default does nothing but in which you
864 # will find examples of common filters.
865 self.user_filter(model,self['structure_repository'], filter=loop_filter)
866
867 # Set the necessary UV/R2 CounterTerms for each loop diagram generated
868 self.set_LoopCT_vertices()
869
870 # Now revert the squared order. This function typically adds to the
871 # squared order list the target WEIGHTED order which has been detected.
872 # This is typically not desired because if the user types in directly
873 # what it sees on the screen, it does not get back the same process.
874 # for example, u u~ > d d~ [virt=QCD] becomes
875 # u u~ > d d~ [virt=QCD] WEIGHTED=6
876 # but of course the photon-gluon s-channel Born interference is not
877 # counted in.
878 # However, if you type it in generate again with WEIGHTED=6, you will
879 # get it.
880 self['process']['squared_orders'].clear()
881 self['process']['squared_orders'].update(user_squared_orders)
882
883 # The computation below is just to report what split order are computed
884 # and which one are considered (i.e. kept using the order specifications)
885 self.print_split_order_infos()
886
887 # Give some info about the run
888 nLoopDiag = 0
889 nCT={'UV':0,'R2':0}
890 for ldiag in self['loop_UVCT_diagrams']:
891 nCT[ldiag['type'][:2]]+=len(ldiag['UVCT_couplings'])
892 for ldiag in self['loop_diagrams']:
893 nLoopDiag+=1
894 nCT['UV']+=len(ldiag.get_CT(model,'UV'))
895 nCT['R2']+=len(ldiag.get_CT(model,'R2'))
896
897 # The identification of numerically equivalent diagrams is done here.
898 # Simply comment the line above to remove it for testing purposes
899 # (i.e. to make sure it does not alter the result).
900 nLoopsIdentified = self.identify_loop_diagrams()
901 if nLoopsIdentified > 0:
902 logger.debug("A total of %d loop diagrams "%nLoopsIdentified+\
903 "were identified with equivalent ones.")
904 logger.info("Contributing diagrams generated: "+\
905 "%d Born, %d%s loops, %d R2, %d UV"%(len(self['born_diagrams']),
906 len(self['loop_diagrams']),'(+%d)'%nLoopsIdentified \
907 if nLoopsIdentified>0 else '' ,nCT['R2'],nCT['UV']))
908
909 ldg_debug_info("#Diags after filtering",len(self['loop_diagrams']))
910 ldg_debug_info("# of different structures identified",\
911 len(self['structure_repository']))
912
913 return (bornsuccessful or totloopsuccessful)
914
916 """ Uses a loop_tag characterizing the loop with only physical
917 information about it (mass, coupling, width, color, etc...) so as to
918 recognize numerically equivalent diagrams and group them together,
919 such as massless quark loops in pure QCD gluon loop amplitudes."""
920
921 # This dictionary contains key-value pairs of the form
922 # (loop_tag, DiagramList) where the loop_tag key unambiguously
923 # characterizes a class of equivalent diagrams and the DiagramList value
924 # lists all the diagrams belonging to this class.
925 # In the end, the first diagram of this DiagramList will be used as
926 # the reference included in the numerical code for the loop matrix
927 # element computations and all the others will be omitted, being
928 # included via a simple multiplicative factor applied to the first one.
929 diagram_identification = {}
930
931 for i, loop_diag in enumerate(self['loop_diagrams']):
932 loop_tag = loop_diag.build_loop_tag_for_diagram_identification(
933 self['process']['model'], self.get('structure_repository'),
934 use_FDStructure_ID_for_tag = True)
935 # We store the loop diagrams in a 2-tuple that keeps track of 'i'
936 # so that we don't lose their original order. It is just for
937 # convenience, and not strictly necessary.
938 try:
939 diagram_identification[loop_tag].append((i+1,loop_diag))
940 except KeyError:
941 diagram_identification[loop_tag] = [(i+1,loop_diag)]
942
943 # Now sort the loop_tag keys according to their order of appearance
944 sorted_loop_tag_keys = sorted(diagram_identification.keys(),
945 key=lambda k:diagram_identification[k][0][0])
946
947 new_loop_diagram_base = base_objects.DiagramList([])
948 n_loops_identified = 0
949 for loop_tag in sorted_loop_tag_keys:
950 n_diag_in_class = len(diagram_identification[loop_tag])
951 n_loops_identified += n_diag_in_class-1
952 new_loop_diagram_base.append(diagram_identification[loop_tag][0][1])
953 # We must add the counterterms of all the identified loop diagrams
954 # to the reference one.
955 new_loop_diagram_base[-1]['multiplier'] = n_diag_in_class
956 for ldiag in diagram_identification[loop_tag][1:]:
957 new_loop_diagram_base[-1].get('CT_vertices').extend(
958 copy.copy(ldiag[1].get('CT_vertices')))
959 if n_diag_in_class > 1:
960 ldg_debug_info("# Diagram equivalence class detected","#(%s) -> #%d"\
961 %(','.join('%d'%diag[0] for diag in diagram_identification[loop_tag][1:])+
962 (',' if n_diag_in_class==2 else ''),diagram_identification[loop_tag][0][0]))
963
964
965 self.set('loop_diagrams',new_loop_diagram_base)
966 return n_loops_identified
967
969 """This function is solely for monitoring purposes. It reports what are
970 the coupling order combination which are obtained with the diagram
971 genarated and among those which ones correspond to those selected by
972 the process definition and which ones are the extra combinations which
973 comes as a byproduct of the computation of the desired one. The typical
974 example is that if you ask for d d~ > u u~ QCD^2==2 [virt=QCD, QED],
975 you will not only get (QCD,QED)=(2,2);(2,4) which are the desired ones
976 but the code output will in principle also be able to return
977 (QCD,QED)=(4,0);(4,2);(0,4);(0,6) because they involve the same amplitudes
978 """
979
980 hierarchy = self['process']['model']['order_hierarchy']
981
982 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
983 # The WEIGHTED order might have been automatically assigned to the
984 # squared order constraints, so we must assign it a type if not specified
985 if 'WEIGHTED' not in sqorders_types:
986 sqorders_types['WEIGHTED']='<='
987
988 sorted_hierarchy = [order[0] for order in \
989 sorted(hierarchy.items(), key=lambda el: el[1])]
990
991 loop_SOs = set(tuple([d.get_order(order) for order in sorted_hierarchy])
992 for d in self['loop_diagrams']+self['loop_UVCT_diagrams'])
993
994 if self['process']['has_born']:
995 born_SOs = set(tuple([d.get_order(order) for order in \
996 sorted_hierarchy]) for d in self['born_diagrams'])
997 else:
998 born_SOs = set([])
999
1000 born_sqSOs = set(tuple([x + y for x, y in zip(b1_SO, b2_SO)]) for b1_SO
1001 in born_SOs for b2_SO in born_SOs)
1002 if self['process']['has_born']:
1003 ref_amps = born_SOs
1004 else:
1005 ref_amps = loop_SOs
1006 loop_sqSOs = set(tuple([x + y for x, y in zip(b_SO, l_SO)]) for b_SO in
1007 ref_amps for l_SO in loop_SOs)
1008
1009 # Append the corresponding WEIGHT of each contribution
1010 sorted_hierarchy.append('WEIGHTED')
1011 born_sqSOs = sorted([b_sqso+(sum([b*hierarchy[sorted_hierarchy[i]] for
1012 i, b in enumerate(b_sqso)]),) for b_sqso in born_sqSOs],
1013 key=lambda el: el[1])
1014 loop_sqSOs = sorted([l_sqso+(sum([l*hierarchy[sorted_hierarchy[i]] for
1015 i, l in enumerate(l_sqso)]),) for l_sqso in loop_sqSOs],
1016 key=lambda el: el[1])
1017
1018
1019 logger.debug("Coupling order combinations considered:"+\
1020 " (%s)"%','.join(sorted_hierarchy))
1021
1022 # Now check what is left
1023 born_considered = []
1024 loop_considered = []
1025 for i, sqSOList in enumerate([born_sqSOs,loop_sqSOs]):
1026 considered = []
1027 extra = []
1028 for sqSO in sqSOList:
1029 for sqo, constraint in self['process']['squared_orders'].items():
1030 sqo_index = sorted_hierarchy.index(sqo)
1031 # Notice that I assume here that the negative coupling order
1032 # constraint should have been replaced here (by its
1033 # corresponding positive value).
1034 if (sqorders_types[sqo]=='==' and
1035 sqSO[sqo_index]!=constraint ) or \
1036 (sqorders_types[sqo] in ['=','<='] and
1037 sqSO[sqo_index]>constraint) or \
1038 (sqorders_types[sqo] in ['>'] and
1039 sqSO[sqo_index]<=constraint):
1040 extra.append(sqSO)
1041 break;
1042
1043 # Set the ones considered to be the complement of the omitted ones
1044 considered = [sqSO for sqSO in sqSOList if sqSO not in extra]
1045
1046 if i==0:
1047 born_considered = considered
1048 name = "Born"
1049 if not self['process']['has_born']:
1050 logger.debug(" > No Born contributions for this process.")
1051 continue
1052 elif i==1:
1053 loop_considered = considered
1054 name = "loop"
1055
1056 if len(considered)==0:
1057 logger.debug(" > %s : None"%name)
1058 else:
1059 logger.debug(" > %s : %s"%(name,' '.join(['(%s,W%d)'%(
1060 ','.join(list('%d'%s for s in c[:-1])),c[-1])
1061 for c in considered])))
1062
1063 if len(extra)!=0:
1064 logger.debug(" > %s (not selected but available): %s"%(name,' '.
1065 join(['(%s,W%d)'%(','.join(list('%d'%s for s in e[:-1])),
1066 e[-1]) for e in extra])))
1067
1068 # In case it is needed, the considered orders are returned
1069 # (it is used by some of the unit tests)
1070 return (born_considered,
1071 [sqSO for sqSO in born_sqSOs if sqSO not in born_considered],
1072 loop_considered,
1073 [sqSO for sqSO in loop_sqSOs if sqSO not in loop_considered])
1074
1075
1077 """ Generates all born diagrams relevant to this NLO Process """
1078
1079 bornsuccessful, self['born_diagrams'] = \
1080 diagram_generation.Amplitude.generate_diagrams(self,True)
1081
1082 return bornsuccessful
1083
1085 """ Generates all loop diagrams relevant to this NLO Process """
1086
1087 # Reinitialize the loop diagram container
1088 self['loop_diagrams']=base_objects.DiagramList()
1089 totloopsuccessful=False
1090
1091 # Make sure to start with an empty l-cut particle list.
1092 self.lcutpartemployed=[]
1093
1094 for order in self['process']['perturbation_couplings']:
1095 ldg_debug_info("Perturbation coupling generated now ",order)
1096 lcutPart=[particle for particle in \
1097 self['process']['model']['particles'] if \
1098 (particle.is_perturbating(order, self['process']['model']) and \
1099 particle.get_pdg_code() not in \
1100 self['process']['forbidden_particles'])]
1101 # lcutPart = [lp for lp in lcutPart if abs(lp.get('pdg_code'))==6]
1102 # misc.sprint("lcutPart=",[part.get('name') for part in lcutPart])
1103 for part in lcutPart:
1104 if part.get_pdg_code() not in self.lcutpartemployed:
1105 # First create the two L-cut particles to add to the process.
1106 # Remember that in the model only the particles should be
1107 # tagged as contributing to the a perturbation. Never the
1108 # anti-particle. We chose here a specific orientation for
1109 # the loop momentum flow, say going IN lcutone and OUT
1110 # lcuttwo. We also define here the 'positive' loop fermion
1111 # flow by always setting lcutone to be a particle and
1112 # lcuttwo the corresponding anti-particle.
1113 ldg_debug_info("Generating loop diagram with L-cut type",\
1114 part.get_name())
1115 lcutone=base_objects.Leg({'id': part.get_pdg_code(),
1116 'state': True,
1117 'loop_line': True})
1118 lcuttwo=base_objects.Leg({'id': part.get_anti_pdg_code(),
1119 'state': True,
1120 'loop_line': True})
1121 self['process'].get('legs').extend([lcutone,lcuttwo])
1122 # WARNING, it is important for the tagging to notice here
1123 # that lcuttwo is the last leg in the process list of legs
1124 # and will therefore carry the highest 'number' attribute as
1125 # required to insure that it will never be 'propagated' to
1126 # any output leg.
1127
1128 # We generate the diagrams now
1129 loopsuccessful, lcutdiaglist = \
1130 super(LoopAmplitude, self).generate_diagrams(True)
1131
1132 # Now get rid of all the previously defined l-cut particles.
1133 leg_to_remove=[leg for leg in self['process']['legs'] \
1134 if leg['loop_line']]
1135 for leg in leg_to_remove:
1136 self['process']['legs'].remove(leg)
1137
1138 # The correct L-cut type is specified
1139 for diag in lcutdiaglist:
1140 diag.set('type',part.get_pdg_code())
1141 self['loop_diagrams']+=lcutdiaglist
1142
1143 # Update the list of already employed L-cut particles such
1144 # that we never use them again in loop particles
1145 self.lcutpartemployed.append(part.get_pdg_code())
1146 self.lcutpartemployed.append(part.get_anti_pdg_code())
1147
1148 ldg_debug_info("#Diags generated w/ this L-cut particle",\
1149 len(lcutdiaglist))
1150 # Accordingly update the totloopsuccessful tag
1151 if loopsuccessful:
1152 totloopsuccessful=True
1153
1154 # Reset the l-cut particle list
1155 self.lcutpartemployed=[]
1156
1157 return totloopsuccessful
1158
1159
1161 """ Scan all born diagrams and add for each all the corresponding UV
1162 counterterms. It creates one LoopUVCTDiagram per born diagram and set
1163 of possible coupling_order (so that QCD and QED wavefunction corrections
1164 are not in the same LoopUVCTDiagram for example). Notice that this takes
1165 care only of the UV counterterm which factorize with the born and the
1166 other contributions like the UV mass renormalization are added in the
1167 function setLoopCTVertices"""
1168
1169 # return True
1170 # ============================================
1171 # Including the UVtree contributions
1172 # ============================================
1173
1174 # The following lists the UV interactions potentially giving UV counterterms
1175 # (The UVmass interactions is accounted for like the R2s)
1176 UVCTvertex_interactions = base_objects.InteractionList()
1177 for inter in self['process']['model']['interactions'].get_UV():
1178 if inter.is_UVtree() and len(inter['particles'])>1 and \
1179 inter.is_perturbating(self['process']['perturbation_couplings']) \
1180 and (set(inter['orders'].keys()).intersection(\
1181 set(self['process']['perturbation_couplings'])))!=set([]) and \
1182 (any([set(loop_parts).intersection(set(self['process']\
1183 ['forbidden_particles']))==set([]) for loop_parts in \
1184 inter.get('loop_particles')]) or \
1185 inter.get('loop_particles')==[[]]):
1186 UVCTvertex_interactions.append(inter)
1187
1188 # Temporarly give the tagging order 'UVCT_SPECIAL' to those interactions
1189 self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']=0
1190 self['process']['model'].get('coupling_orders').add('UVCT_SPECIAL')
1191 for inter in UVCTvertex_interactions:
1192 neworders=copy.copy(inter.get('orders'))
1193 neworders['UVCT_SPECIAL']=1
1194 inter.set('orders',neworders)
1195 # Refresh the model interaction dictionary while including those special
1196 # interactions
1197 self['process']['model'].actualize_dictionaries(useUVCT=True)
1198
1199 # Generate the UVCTdiagrams (born diagrams with 'UVCT_SPECIAL'=0 order
1200 # will be generated along)
1201 self['process']['orders']['UVCT_SPECIAL']=1
1202
1203 UVCTsuccessful, UVCTdiagrams = \
1204 super(LoopAmplitude, self).generate_diagrams(True)
1205
1206 for UVCTdiag in UVCTdiagrams:
1207 if UVCTdiag.get_order('UVCT_SPECIAL')==1:
1208 newUVCTDiag = loop_base_objects.LoopUVCTDiagram({\
1209 'vertices':copy.deepcopy(UVCTdiag['vertices'])})
1210 UVCTinter = newUVCTDiag.get_UVCTinteraction(self['process']['model'])
1211 newUVCTDiag.set('type',UVCTinter.get('type'))
1212 # This interaction counter-term must be accounted for as many times
1213 # as they are list of loop_particles defined and allowed for by
1214 # the process.
1215 newUVCTDiag.get('UVCT_couplings').append((len([1 for loop_parts \
1216 in UVCTinter.get('loop_particles') if set(loop_parts).intersection(\
1217 set(self['process']['forbidden_particles']))==set([])])) if
1218 loop_parts!=[[]] else 1)
1219 self['loop_UVCT_diagrams'].append(newUVCTDiag)
1220
1221 # Remove the additional order requirement in the born orders for this
1222 # process
1223 del self['process']['orders']['UVCT_SPECIAL']
1224 # Remove the fake order added to the selected UVCT interactions
1225 del self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']
1226 self['process']['model'].get('coupling_orders').remove('UVCT_SPECIAL')
1227 for inter in UVCTvertex_interactions:
1228 del inter.get('orders')['UVCT_SPECIAL']
1229 # Revert the model interaction dictionaries to default
1230 self['process']['model'].actualize_dictionaries(useUVCT=False)
1231
1232 # Set the correct orders to the loop_UVCT_diagrams
1233 for UVCTdiag in self['loop_UVCT_diagrams']:
1234 UVCTdiag.calculate_orders(self['process']['model'])
1235
1236 # ============================================
1237 # Wavefunction renormalization
1238 # ============================================
1239
1240 if not self['process']['has_born']:
1241 return UVCTsuccessful
1242
1243 # We now scan each born diagram, adding the necessary wavefunction
1244 # renormalizations
1245 for bornDiag in self['born_diagrams']:
1246 # This dictionary takes for keys the tuple
1247 # (('OrderName1',power1),...,('OrderNameN',powerN) representing
1248 # the power brought by the counterterm and the value is the
1249 # corresponding LoopUVCTDiagram.
1250 # The last entry is of the form ('EpsilonOrder', value) to put the
1251 # contribution of each different EpsilonOrder to different
1252 # LoopUVCTDiagrams.
1253 LoopUVCTDiagramsAdded={}
1254 for leg in self['process']['legs']:
1255 counterterm=self['process']['model'].get_particle(abs(leg['id'])).\
1256 get('counterterm')
1257 for key, value in counterterm.items():
1258 if key[0] in self['process']['perturbation_couplings']:
1259 for laurentOrder, CTCoupling in value.items():
1260 # Create the order key of the UV counterterm
1261 orderKey=[(key[0],2),]
1262 orderKey.sort()
1263 orderKey.append(('EpsilonOrder',-laurentOrder))
1264 CTCouplings=[CTCoupling for loop_parts in key[1] if
1265 set(loop_parts).intersection(set(self['process']\
1266 ['forbidden_particles']))==set([])]
1267 if CTCouplings!=[]:
1268 try:
1269 LoopUVCTDiagramsAdded[tuple(orderKey)].get(\
1270 'UVCT_couplings').extend(CTCouplings)
1271 except KeyError:
1272 LoopUVCTDiagramsAdded[tuple(orderKey)]=\
1273 loop_base_objects.LoopUVCTDiagram({\
1274 'vertices':copy.deepcopy(bornDiag['vertices']),
1275 'type':'UV'+('' if laurentOrder==0 else
1276 str(-laurentOrder)+'eps'),
1277 'UVCT_orders':{key[0]:2},
1278 'UVCT_couplings':CTCouplings})
1279
1280 for LoopUVCTDiagram in LoopUVCTDiagramsAdded.values():
1281 LoopUVCTDiagram.calculate_orders(self['process']['model'])
1282 self['loop_UVCT_diagrams'].append(LoopUVCTDiagram)
1283
1284 return UVCTsuccessful
1285
1287 """ Scan each loop diagram and recognizes what are the R2/UVmass
1288 CounterTerms associated to them """
1289 #return # debug
1290 # We first create a base dictionary with as a key (tupleA,tupleB). For
1291 # each R2/UV interaction, tuple B is the ordered tuple of the loop
1292 # particles (not anti-particles, so that the PDG is always positive!)
1293 # listed in its loop_particles attribute. Tuple A is the ordered tuple
1294 # of external particles PDGs. making up this interaction. The values of
1295 # the dictionary are a list of the interaction ID having the same key
1296 # above.
1297 CT_interactions = {}
1298 for inter in self['process']['model']['interactions']:
1299 if inter.is_UVmass() or inter.is_UVloop() or inter.is_R2() and \
1300 len(inter['particles'])>1 and inter.is_perturbating(\
1301 self['process']['perturbation_couplings']):
1302 # This interaction might have several possible loop particles
1303 # yielding the same CT. So we add this interaction ID
1304 # for each entry in the list loop_particles.
1305 for i, lparts in enumerate(inter['loop_particles']):
1306 keya=copy.copy(lparts)
1307 keya.sort()
1308 if inter.is_UVloop():
1309 # If it is a CT of type UVloop, then do not specify the
1310 # keya (leave it empty) but make sure the particles
1311 # specified as loop particles are not forbidden before
1312 # adding this CT to CT_interactions
1313 if (set(self['process']['forbidden_particles']) & \
1314 set(lparts)) != set([]):
1315 continue
1316 else:
1317 keya=[]
1318 keyb=[part.get_pdg_code() for part in inter['particles']]
1319 keyb.sort()
1320 key=(tuple(keyb),tuple(keya))
1321 # We keep track of 'i' (i.e. the position of the
1322 # loop_particle list in the inter['loop_particles']) so
1323 # that each coupling in a vertex of type 'UVloop' is
1324 # correctly accounted for since the keya is always replaced
1325 # by an empty list since the constraint on the loop particles
1326 # is simply that there is not corresponding forbidden
1327 # particles in the process definition and not that the
1328 # actual particle content of the loop generate matches.
1329 #
1330 # This can also happen with the type 'UVmass' or 'R2'
1331 # CTvertex ex1(
1332 # type='UVmass'
1333 # [...]
1334 # loop_particles=[[[d,g],[d,g]]])
1335 # Which is a bit silly but can happen and would mean that
1336 # we must account twice for the coupling associated to each
1337 # of these loop_particles.
1338 # One might imagine someone doing it with
1339 # loop_particles=[[[],[]]], for example, because he wanted
1340 # to get rid of the loop particle constraint for some reason.
1341 try:
1342 CT_interactions[key].append((inter['id'],i))
1343 except KeyError:
1344 CT_interactions[key]=[(inter['id'],i),]
1345
1346 # The dictionary CTmass_added keeps track of what are the CounterTerms of
1347 # type UVmass or R2 already added and prevents us from adding them again.
1348 # For instance, the fermion boxes with four external gluons exists in 6 copies
1349 # (with different crossings of the external legs each time) and the
1350 # corresponding R2 must be added only once. The key of this dictionary
1351 # characterizing the loop is (tupleA,tupleB). Tuple A is made from the
1352 # list of the ID of the external structures attached to this loop and
1353 # tuple B from list of the pdg of the particles building this loop.
1354
1355 # Notice that when a CT of type UVmass is specified with an empty
1356 # loop_particles attribute, then it means it must be added once for each
1357 # particle with a matching topology, irrespectively of the loop content.
1358 # Whenever added, such a CT is put in the dictionary CT_added with a key
1359 # having an empty tupleB.
1360 # Finally, because CT interactions of type UVloop do specify a
1361 # loop_particles attribute, but which serves only to be filtered against
1362 # particles forbidden in the process definition, they will also be added
1363 # with an empty tupleB.
1364 CT_added = {}
1365
1366 for diag in self['loop_diagrams']:
1367 # First build the key from this loop for the CT_interaction dictionary
1368 # (Searching Key) and the key for the CT_added dictionary (tracking Key)
1369 searchingKeyA=[]
1370 # Notice that searchingKeyB below also serves as trackingKeyB
1371 searchingKeyB=[]
1372 trackingKeyA=[]
1373 for tagElement in diag['canonical_tag']:
1374 for structID in tagElement[1]:
1375 trackingKeyA.append(structID)
1376 searchingKeyA.append(self['process']['model'].get_particle(\
1377 self['structure_repository'][structID]['binding_leg']['id']).\
1378 get_pdg_code())
1379 searchingKeyB.append(self['process']['model'].get_particle(\
1380 tagElement[0]).get('pdg_code'))
1381 searchingKeyA.sort()
1382 # We do not repeat particles present many times in the loop
1383 searchingKeyB=list(set(searchingKeyB))
1384 searchingKeyB.sort()
1385 trackingKeyA.sort()
1386 # I repeat, they are two kinds of keys:
1387 # searchingKey:
1388 # This serves to scan the CT interactions defined and then find
1389 # which ones match a given loop topology and particle.
1390 # trackingKey:
1391 # Once some CT vertices are identified to be a match for a loop,
1392 # the trackingKey is used in conjunction with the dictionary
1393 # CT_added to make sure that this CT has not already been included.
1394
1395 # Each of these two keys above, has the format
1396 # (tupleA, tupleB)
1397 # with tupleB being the loop_content and either contains the set of
1398 # loop particles PDGs of the interaction (for the searchingKey)
1399 # or of the loops already scanned (trackingKey). It can also be
1400 # empty when considering interactions of type UVmass or R2 which
1401 # have an empty loop_particle attribute or those of type UVloop.
1402 # TupleA is the set of external particle PDG (for the searchingKey)
1403 # and the unordered list of structID attached to the loop (for the
1404 # trackingKey)
1405 searchingKeySimple=(tuple(searchingKeyA),())
1406 searchingKeyLoopPart=(tuple(searchingKeyA),tuple(searchingKeyB))
1407 trackingKeySimple=(tuple(trackingKeyA),())
1408 trackingKeyLoopPart=(tuple(trackingKeyA),tuple(searchingKeyB))
1409 # Now we look for a CT which might correspond to this loop by looking
1410 # for its searchingKey in CT_interactions
1411
1412 # misc.sprint("I have the following CT_interactions=",CT_interactions)
1413 try:
1414 CTIDs=copy.copy(CT_interactions[searchingKeySimple])
1415 except KeyError:
1416 CTIDs=[]
1417 try:
1418 CTIDs.extend(copy.copy(CT_interactions[searchingKeyLoopPart]))
1419 except KeyError:
1420 pass
1421 if not CTIDs:
1422 continue
1423 # We have found some CT interactions corresponding to this loop
1424 # so we must make sure we have not included them already
1425 try:
1426 usedIDs=copy.copy(CT_added[trackingKeySimple])
1427 except KeyError:
1428 usedIDs=[]
1429 try:
1430 usedIDs.extend(copy.copy(CT_added[trackingKeyLoopPart]))
1431 except KeyError:
1432 pass
1433
1434 for CTID in CTIDs:
1435 # Make sure it has not been considered yet and that the loop
1436 # orders match
1437 if CTID not in usedIDs and diag.get_loop_orders(\
1438 self['process']['model'])==\
1439 self['process']['model']['interaction_dict'][CTID[0]]['orders']:
1440 # Create the amplitude vertex corresponding to this CT
1441 # and add it to the LoopDiagram treated.
1442 CTleglist = base_objects.LegList()
1443 for tagElement in diag['canonical_tag']:
1444 for structID in tagElement[1]:
1445 CTleglist.append(\
1446 self['structure_repository'][structID]['binding_leg'])
1447 CTVertex = base_objects.Vertex({'id':CTID[0], \
1448 'legs':CTleglist})
1449 diag['CT_vertices'].append(CTVertex)
1450 # Now add this CT vertex to the CT_added dictionary so that
1451 # we are sure it will not be double counted
1452 if self['process']['model']['interaction_dict'][CTID[0]]\
1453 ['loop_particles'][CTID[1]]==[] or \
1454 self['process']['model']['interaction_dict'][CTID[0]].\
1455 is_UVloop():
1456 try:
1457 CT_added[trackingKeySimple].append(CTID)
1458 except KeyError:
1459 CT_added[trackingKeySimple] = [CTID, ]
1460 else:
1461 try:
1462 CT_added[trackingKeyLoopPart].append(CTID)
1463 except KeyError:
1464 CT_added[trackingKeyLoopPart] = [CTID, ]
1465
1467 """ Return a LoopDiagram created."""
1468 return loop_base_objects.LoopDiagram({'vertices':vertexlist})
1469
1471 """ Returns a DGLoopLeg list instead of the default copy_leglist
1472 defined in base_objects.Amplitude """
1473
1474 dgloopleglist=base_objects.LegList()
1475 for leg in leglist:
1476 dgloopleglist.append(loop_base_objects.DGLoopLeg(leg))
1477
1478 return dgloopleglist
1479
1481 """ Overloaded here to convert back all DGLoopLegs into Legs. """
1482 for vertexlist in vertexdoublelist:
1483 for vertex in vertexlist:
1484 if not isinstance(vertex['legs'][0],loop_base_objects.DGLoopLeg):
1485 continue
1486 vertex['legs'][:]=[leg.convert_to_leg() for leg in \
1487 vertex['legs']]
1488 return True
1489
1491 """Create a set of new legs from the info given."""
1492
1493 looplegs=[leg for leg in legs if leg['loop_line']]
1494
1495 # Get rid of all vanishing tadpoles
1496 #Ease the access to the model
1497 model=self['process']['model']
1498 exlegs=[leg for leg in looplegs if leg['depth']==0]
1499 if(len(exlegs)==2):
1500 if(any([part['mass'].lower()=='zero' for pdg,part in model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1501 return []
1502
1503 # Correctly propagate the loopflow
1504 loopline=(len(looplegs)==1)
1505 mylegs = []
1506 for i, (leg_id, vert_id) in enumerate(leg_vert_ids):
1507 # We can now create the set of possible merged legs.
1508 # However, we make sure that its PDG is not in the list of
1509 # L-cut particles we already explored. If it is, we simply reject
1510 # the diagram.
1511 if not loopline or not (leg_id in self.lcutpartemployed):
1512 # Reminder: The only purpose of the "depth" flag is to get rid
1513 # of (some, not all) of the wave-function renormalization
1514 # already during diagram generation. We reckognize a wf
1515 # renormalization diagram as follows:
1516 if len(legs)==2 and len(looplegs)==2:
1517 # We have candidate
1518 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1519 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1520 # Check that the PDG of the outter particle in the
1521 # wavefunction renormalization bubble is equal to the
1522 # one of the inner particle.
1523 continue
1524
1525 # If depth is not 0 because of being an external leg and not
1526 # the propagated PDG, then we set it to -1 so that from that
1527 # point we are sure the diagram will not be reckognized as a
1528 # wave-function renormalization.
1529 depth=-1
1530 # When creating a loop leg from exactly two external legs, we
1531 # set the depth to the PDG of the external non-loop line.
1532 if len(legs)==2 and loopline and (legs[0]['depth'],\
1533 legs[1]['depth'])==(0,0):
1534 if not legs[0]['loop_line']:
1535 depth=legs[0]['id']
1536 else:
1537 depth=legs[1]['id']
1538 # In case of two point interactions among two same particle
1539 # we propagate the existing depth
1540 if len(legs)==1 and legs[0]['id']==leg_id:
1541 depth=legs[0]['depth']
1542 # In all other cases we set the depth to -1 since no
1543 # wave-function renormalization diagram can arise from this
1544 # side of the diagram construction.
1545
1546 mylegs.append((loop_base_objects.DGLoopLeg({'id':leg_id,
1547 'number':number,
1548 'state':state,
1549 'from_group':True,
1550 'depth': depth,
1551 'loop_line': loopline}),
1552 vert_id))
1553 return mylegs
1554
1556 """Allow for selection of vertex ids."""
1557
1558 looplegs=[leg for leg in legs if leg['loop_line']]
1559 nonlooplegs=[leg for leg in legs if not leg['loop_line']]
1560
1561 # Get rid of all vanishing tadpoles
1562 model=self['process']['model']
1563 exlegs=[leg for leg in looplegs if leg['depth']==0]
1564 if(len(exlegs)==2):
1565 if(any([part['mass'].lower()=='zero' for pdg,part in \
1566 model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1567 return []
1568
1569
1570 # Get rid of some wave-function renormalization diagrams already during
1571 # diagram generation already.In a similar manner as in get_combined_legs.
1572 if(len(legs)==3 and len(looplegs)==2):
1573 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1574 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1575 return []
1576
1577 return vert_ids
1578
1579 # Helper function
1580
1582 """ Filters the diagrams according to the constraints on the squared
1583 orders in argument and wether the process has a born or not. """
1584
1585 diagRef=base_objects.DiagramList()
1586 AllLoopDiagrams=base_objects.DiagramList(self['loop_diagrams']+\
1587 self['loop_UVCT_diagrams'])
1588
1589 AllBornDiagrams=base_objects.DiagramList(self['born_diagrams'])
1590 if self['process']['has_born']:
1591 diagRef=AllBornDiagrams
1592 else:
1593 diagRef=AllLoopDiagrams
1594
1595 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
1596
1597 # The WEIGHTED order might have been automatically assigned to the
1598 # squared order constraints, so we must assign it a type if not specified
1599 if 'WEIGHTED' not in sqorders_types:
1600 sqorders_types['WEIGHTED']='<='
1601
1602 if len(diagRef)==0:
1603 # If no born contributes but they were supposed to ( in the
1604 # case of self['process']['has_born']=True) then it means that
1605 # the loop cannot be squared against anything and none should
1606 # contribute either. The squared order constraints are just too
1607 # tight for anything to contribute.
1608 AllLoopDiagrams = base_objects.DiagramList()
1609
1610
1611 # Start by filtering the loop diagrams
1612 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(diagRef,
1613 sq_order_constrains, sqorders_types)
1614 # And now the Born ones if there are any
1615 if self['process']['has_born']:
1616 # We consider both the Born*Born and Born*Loop squared terms here
1617 AllBornDiagrams = AllBornDiagrams.apply_positive_sq_orders(
1618 AllLoopDiagrams+AllBornDiagrams, sq_order_constrains, sqorders_types)
1619
1620 # Now treat the negative squared order constraint (at most one)
1621 neg_orders = [(order, value) for order, value in \
1622 sq_order_constrains.items() if value<0]
1623 if len(neg_orders)==1:
1624 neg_order, neg_value = neg_orders[0]
1625 # If there is a Born contribution, then the target order will
1626 # be computed over all Born*Born and Born*loop contributions
1627 if self['process']['has_born']:
1628 AllBornDiagrams, target_order =\
1629 AllBornDiagrams.apply_negative_sq_order(
1630 base_objects.DiagramList(AllLoopDiagrams+AllBornDiagrams),
1631 neg_order,neg_value,sqorders_types[neg_order])
1632 # Now we must filter the loop diagrams using to the target_order
1633 # computed above from the LO and NLO contributions
1634 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(
1635 diagRef,{neg_order:target_order},
1636 {neg_order:sqorders_types[neg_order]})
1637
1638 # If there is no Born, then the situation is completely analoguous
1639 # to the tree level case since it is simply Loop*Loop
1640 else:
1641 AllLoopDiagrams, target_order = \
1642 AllLoopDiagrams.apply_negative_sq_order(
1643 diagRef,neg_order,neg_value,sqorders_types[neg_order])
1644
1645 # Substitute the negative value to this positive one
1646 # (also in the backed up values in user_squared_orders so that
1647 # this change is permanent and we will still have access to
1648 # it at the output stage)
1649 self['process']['squared_orders'][neg_order]=target_order
1650 user_squared_orders[neg_order]=target_order
1651
1652 elif len(neg_orders)>1:
1653 raise MadGraph5Error('At most one negative squared order constraint'+\
1654 ' can be specified, not %s.'%str(neg_orders))
1655
1656 if self['process']['has_born']:
1657 self['born_diagrams'] = AllBornDiagrams
1658 self['loop_diagrams']=[diag for diag in AllLoopDiagrams if not \
1659 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1660 self['loop_UVCT_diagrams']=[diag for diag in AllLoopDiagrams if \
1661 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1662
1664 """ This is a helper function for order_diagrams_according_to_split_orders
1665 and intended to be used from LoopHelasAmplitude only"""
1666
1667 # The dictionary below has keys being the tuple (split_order<i>_values)
1668 # and values being diagram lists sharing the same split orders.
1669 diag_by_so = {}
1670
1671 for diag in diag_set:
1672 so_key = tuple([diag.get_order(order) for order in split_orders])
1673 try:
1674 diag_by_so[so_key].append(diag)
1675 except KeyError:
1676 diag_by_so[so_key]=base_objects.DiagramList([diag,])
1677
1678 so_keys = diag_by_so.keys()
1679 # Complete the order hierarchy by possibly missing defined order for
1680 # which we set the weight to zero by default (so that they are ignored).
1681 order_hierarchy = self.get('process').get('model').get('order_hierarchy')
1682 order_weights = copy.copy(order_hierarchy)
1683 for so in split_orders:
1684 if so not in order_hierarchy.keys():
1685 order_weights[so]=0
1686
1687 # Now order the keys of diag_by_so by the WEIGHT of the split_orders
1688 # (and only those, the orders not included in the split_orders do not
1689 # count for this ordering as they could be mixed in any given group).
1690 so_keys = sorted(so_keys, key = lambda elem: (sum([power*order_weights[\
1691 split_orders[i]] for i,power in enumerate(elem)])))
1692
1693 # Now put the diagram back, ordered this time, in diag_set
1694 diag_set[:] = []
1695 for so_key in so_keys:
1696 diag_set.extend(diag_by_so[so_key])
1697
1698
1700 """ Reorder the loop and Born diagrams (if any) in group of diagrams
1701 sharing the same coupling orders are put together and these groups are
1702 order in decreasing WEIGHTED orders.
1703 Notice that this function is only called for now by the
1704 LoopHelasMatrixElement instances at the output stage.
1705 """
1706
1707 # If no split order is present (unlikely since the 'corrected order'
1708 # normally is a split_order by default, then do nothing
1709 if len(split_orders)==0:
1710 return
1711
1712 self.order_diagram_set(self['born_diagrams'], split_orders)
1713 self.order_diagram_set(self['loop_diagrams'], split_orders)
1714 self.order_diagram_set(self['loop_UVCT_diagrams'], split_orders)
1715
1716 #===============================================================================
1717 # LoopMultiProcess
1718 #===============================================================================
1719 -class LoopMultiProcess(diagram_generation.MultiProcess):
1720 """LoopMultiProcess: MultiProcess with loop features.
1721 """
1722
1723 @classmethod
1725 """ Return the correct amplitude type according to the characteristics
1726 of the process proc """
1727 return LoopAmplitude({"process": proc},**opts)
1728
1729 #===============================================================================
1730 # LoopInducedMultiProcess
1731 #===============================================================================
1732 -class LoopInducedMultiProcess(diagram_generation.MultiProcess):
1733 """Special mode for the LoopInduced."""
1734
1735 @classmethod
1737 """ Return the correct amplitude type according to the characteristics of
1738 the process proc """
1739 return LoopAmplitude({"process": proc, 'has_born':False},**opts)
1740
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Mon Aug 1 11:10:04 2016 | http://epydoc.sourceforge.net |