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