1
2
3
4
5
6
7
8
9
10
11
12
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
34
35
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
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
76
77 - def __init__(self, argument=None, loop_filter=None):
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
98 - def filter(self, name, value):
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
140 - def set(self, name, value):
156
157 - def get(self, name):
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
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)
177
178
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
186
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
207 if self['process'].get('sqorders_types')[order]=='>':
208 continue
209
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
215
216
217
218
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
228 max_pert_wgt = max([hierarchy[order] for order in \
229 self['process']['perturbation_couplings']])
230
231
232
233
234
235
236 user_min_wgt = 0
237
238
239
240
241
242
243
244
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
252 self['process']['squared_orders']['WEIGHTED']= 2*(min_born_wgt+\
253 max_pert_wgt)
254
255
256
257
258
259
260
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
266 min_nvert=min([len([1 for vert in diag['vertices'] if vert['id']!=0]) \
267 for diag in self['born_diagrams']])
268
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
275
276
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
291
292
293
294
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
349 @staticmethod
351 """ Returns a function which applies the filter corresponding to the
352 conditional expression encoded in filterdef."""
353
354 def filter(diag, structs, model, id):
355 """ The filter function generated '%s'."""%filterdef
356
357 loop_pdgs = diag.get_loop_lines_pdgs()
358 struct_pdgs = diag.get_pdgs_attached_to_loop(structs)
359 loop_masses = [model.get_particle(pdg).get('mass') for pdg in loop_pdgs]
360 struct_masses = [model.get_particle(pdg).get('mass') for pdg in struct_pdgs]
361 if not eval(filterdef.lower(),{'n':len(loop_pdgs),
362 'loop_pdgs':loop_pdgs,
363 'struct_pdgs':struct_pdgs,
364 'loop_masses':loop_masses,
365 'struct_masses':struct_masses,
366 'id':id}):
367 return False
368 else:
369 return True
370
371 return filter
372
374 """ User-defined user-filter. By default it is not called, but the expert
375 user can turn it on and code here is own filter. Some default examples
376 are provided here.
377 The tagging of the loop diagrams must be performed before using this
378 user loop filter"""
379
380
381
382
383 edit_filter_manually = False
384 if not edit_filter_manually and filter in [None,'None']:
385 return
386
387 if filter not in [None,'None']:
388 filter_func = LoopAmplitude.get_loop_filter(filter)
389 else:
390 filter_func = None
391
392 new_diag_selection = base_objects.DiagramList()
393 discarded_diags = base_objects.DiagramList()
394 i=0
395 for diag in self['loop_diagrams']:
396 if diag.get('tag')==[]:
397 raise MadGraph5Error, "Before using the user_filter, please "+\
398 "make sure that the loop diagrams have been tagged first."
399 valid_diag = True
400 i=i+1
401
402
403 if filter_func:
404 try:
405 valid_diag = filter_func(diag, structs, model, i)
406 except Exception as e:
407 raise InvalidCmd("The user-defined filter '%s' did not"%filter+
408 " returned the following error:\n > %s"%str(e))
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464 if valid_diag:
465 new_diag_selection.append(diag)
466 else:
467 discarded_diags.append(diag)
468
469 self['loop_diagrams'] = new_diag_selection
470 if filter in [None,'None']:
471 warn_msg = """
472 The user-defined loop diagrams filter is turned on and discarded %d loops."""\
473 %len(discarded_diags)
474 else:
475 warn_msg = """
476 The loop diagrams filter '%s' is turned on and discarded %d loops."""\
477 %(filter,len(discarded_diags))
478 logger.warning(warn_msg)
479
481 """ Filter the loop diagrams to make sure they belong to the class
482 of coupling orders perturbed. """
483
484
485 allowedpart=[]
486 for part in self['process']['model']['particles']:
487 for order in self['process']['perturbation_couplings']:
488 if part.is_perturbating(order,self['process']['model']):
489 allowedpart.append(part.get_pdg_code())
490 break
491
492 newloopselection=base_objects.DiagramList()
493 warned=False
494 warning_msg = ("Some loop diagrams contributing to this process"+\
495 " are discarded because they are not pure (%s)-perturbation.\nMake sure"+\
496 " you did not want to include them.")%\
497 ('+'.join(self['process']['perturbation_couplings']))
498 for i,diag in enumerate(self['loop_diagrams']):
499
500
501 loop_orders=diag.get_loop_orders(self['process']['model'])
502 pert_loop_order=set(loop_orders.keys()).intersection(\
503 set(self['process']['perturbation_couplings']))
504
505
506
507
508 valid_diag=True
509 if (diag.get_loop_line_types()-set(allowedpart))!=set() or \
510 pert_loop_order==set([]):
511 valid_diag=False
512 if not warned:
513 logger.warning(warning_msg)
514 warned=True
515 if len([col for col in [
516 self['process'].get('model').get_particle(pdg).get('color') \
517 for pdg in diag.get_pdgs_attached_to_loop(\
518 self['structure_repository'])] if col!=1])==1:
519 valid_diag=False
520
521 if valid_diag:
522 newloopselection.append(diag)
523 self['loop_diagrams']=newloopselection
524
525
526
527
528
530 """ Makes sure that all non perturbed orders factorize the born diagrams
531 """
532 warning_msg = "All Born diagrams do not factorize the same sum of power(s) "+\
533 "of the the perturbed order(s) %s.\nThis is potentially dangerous"+\
534 " as the real-emission diagrams from aMC@NLO will not be consistent"+\
535 " with these virtual contributions."
536 if self['process']['has_born']:
537 trgt_summed_order = sum([self['born_diagrams'][0].get_order(order)
538 for order in self['process']['perturbation_couplings']])
539 for diag in self['born_diagrams'][1:]:
540 if sum([diag.get_order(order) for order in self['process']
541 ['perturbation_couplings']])!=trgt_summed_order:
542 logger.warning(warning_msg%' '.join(self['process']
543 ['perturbation_couplings']))
544 break
545
546 warning_msg = "All born diagrams do not factorize the same power of "+\
547 "the order %s which is not perturbed and for which you have not"+\
548 "specified any amplitude order. \nThis is potentially dangerous"+\
549 " as the real-emission diagrams from aMC@NLO will not be consistent"+\
550 " with these virtual contributions."
551 if self['process']['has_born']:
552 for order in self['process']['model']['coupling_orders']:
553 if order not in self['process']['perturbation_couplings'] and \
554 order not in user_orders.keys():
555 order_power=self['born_diagrams'][0].get_order(order)
556 for diag in self['born_diagrams'][1:]:
557 if diag.get_order(order)!=order_power:
558 logger.warning(warning_msg%order)
559 break
560
561
563 """ Return a dictionary of all the coupling orders of this diagram which
564 are not the perturbed ones."""
565 return dict([(order, diagram.get_order(order)) for \
566 order in self['process']['model']['coupling_orders'] if \
567 not order in self['process']['perturbation_couplings'] ])
568
570 """Return a string describing the coupling order configuration"""
571 res = []
572 for order in self['process']['model']['coupling_orders']:
573 try:
574 res.append('%s=%d'%(order,config[order]))
575 except KeyError:
576 res.append('%s=*'%order)
577 return ','.join(res)
578
580 """ Generates all diagrams relevant to this Loop Process """
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603 logger.debug("Generating %s "\
604 %self['process'].nice_string().replace('Process', 'process'))
605
606
607 model = self['process']['model']
608 hierarchy = model['order_hierarchy']
609
610
611
612
613 user_orders=copy.copy(self['process']['orders'])
614
615 if self['process']['has_born']:
616 bornsuccessful = self.generate_born_diagrams()
617 ldg_debug_info("# born diagrams after first generation",\
618 len(self['born_diagrams']))
619 else:
620 self['born_diagrams'] = base_objects.DiagramList()
621 bornsuccessful = True
622 logger.debug("Born diagrams generation skipped by user request.")
623
624
625 for order in self['process']['orders'].keys()+\
626 self['process']['squared_orders'].keys():
627 if not order in model.get('coupling_orders') and \
628 order != 'WEIGHTED':
629 raise InvalidCmd("Coupling order %s not found"%order +\
630 " in any interaction of the current model %s."%model['name'])
631
632
633
634
635 if self['process']['has_born']:
636 self['process']['has_born'] = self['born_diagrams']!=[]
637 self['has_born'] = self['process']['has_born']
638
639 ldg_debug_info("User input born orders",self['process']['orders'])
640 ldg_debug_info("User input squared orders",
641 self['process']['squared_orders'])
642 ldg_debug_info("User input perturbation",\
643 self['process']['perturbation_couplings'])
644
645
646
647
648
649 user_orders=copy.copy(self['process']['orders'])
650 user_squared_orders=copy.copy(self['process']['squared_orders'])
651
652
653
654
655
656
657
658 chosen_order_config={}
659 if self['process']['squared_orders']=={} and \
660 self['process']['orders']=={} and self['process']['has_born']:
661 chosen_order_config = self.choose_order_config()
662
663 discarded_configurations = []
664
665 if chosen_order_config != {}:
666 self.filter_from_order_config('born_diagrams', \
667 chosen_order_config,discarded_configurations)
668
669
670
671
672
673
674
675
676
677
678
679 self.check_factorization(user_orders)
680
681
682 self.guess_loop_orders_from_squared()
683
684
685
686
687
688
689
690
691 if [k.upper() for k in self['process']['squared_orders'].keys()] in \
692 [[],['WEIGHTED']] and self['process']['has_born']:
693 self.guess_loop_orders(user_orders)
694
695
696
697
698 for order in user_orders.keys():
699 if order in self['process']['perturbation_couplings']:
700 self['process']['orders'][order]=user_orders[order]+2
701 else:
702 self['process']['orders'][order]=user_orders[order]
703 if 'WEIGHTED' in user_orders.keys():
704 self['process']['orders']['WEIGHTED']=user_orders['WEIGHTED']+\
705 2*min([hierarchy[order] for order in \
706 self['process']['perturbation_couplings']])
707
708 ldg_debug_info("Orders used for loop generation",\
709 self['process']['orders'])
710
711
712
713 warning_msg = ("Some loop diagrams contributing to this process might "+\
714 "be discarded because they are not pure (%s)-perturbation.\nMake sure"+\
715 " there are none or that you did not want to include them.")%(\
716 ','.join(self['process']['perturbation_couplings']))
717
718 if self['process']['has_born']:
719 for order in model['coupling_orders']:
720 if order not in self['process']['perturbation_couplings']:
721 try:
722 if self['process']['orders'][order]< \
723 self['born_diagrams'].get_max_order(order):
724 logger.warning(warning_msg)
725 break
726 except KeyError:
727 pass
728
729
730 totloopsuccessful=self.generate_loop_diagrams()
731
732
733 if not self['process']['has_born'] and not self['loop_diagrams']:
734 self['process']['orders'].clear()
735 self['process']['orders'].update(user_orders)
736 return False
737
738
739
740
741 if self['process']['has_born']:
742 self.set_Born_CT()
743
744 ldg_debug_info("#UVCTDiags generated",len(self['loop_UVCT_diagrams']))
745
746
747 self['process']['orders'].clear()
748 self['process']['orders'].update(user_orders)
749
750
751
752
753 if not self['process']['has_born'] and not \
754 self['process']['squared_orders'] and hierarchy:
755 pert_order_weights=[hierarchy[order] for order in \
756 self['process']['perturbation_couplings']]
757 self['process']['squared_orders']['WEIGHTED']=2*(\
758 self['loop_diagrams'].get_min_order('WEIGHTED')+\
759 max(pert_order_weights)-min(pert_order_weights))
760
761 ldg_debug_info("Squared orders after treatment",\
762 self['process']['squared_orders'])
763 ldg_debug_info("#Diags after diagram generation",\
764 len(self['loop_diagrams']))
765
766
767
768
769
770
771 if chosen_order_config != {}:
772 self.filter_from_order_config('loop_diagrams', \
773 chosen_order_config,discarded_configurations)
774
775 if discarded_configurations!=[]:
776 msg = ("The contribution%s of th%s coupling orders "+\
777 "configuration%s %s discarded :%s")%(('s','ese','s','are','\n')\
778 if len(discarded_configurations)>1 else ('','is','','is',' '))
779 msg = msg + '\n'.join(['(%s)'%self.print_config(conf) for conf \
780 in discarded_configurations])
781 msg = msg + "\nManually set the coupling orders to "+\
782 "generate %sthe contribution%s above."%(('any of ','s') if \
783 len(discarded_configurations)>1 else ('',''))
784 logger.info(msg)
785
786
787
788
789
790
791
792 regular_constraints = dict([(key,val) for (key,val) in
793 self['process']['squared_orders'].items() if val>=0])
794 negative_constraints = dict([(key,val) for (key,val) in
795 self['process']['squared_orders'].items() if val<0])
796 while True:
797 ndiag_remaining=len(self['loop_diagrams']+self['born_diagrams'])
798 self.check_squared_orders(regular_constraints)
799 if len(self['loop_diagrams']+self['born_diagrams'])==ndiag_remaining:
800 break
801
802 if negative_constraints!={}:
803
804
805
806
807
808
809
810
811 self.check_squared_orders(negative_constraints,user_squared_orders)
812
813 ldg_debug_info("#Diags after constraints",len(self['loop_diagrams']))
814 ldg_debug_info("#Born diagrams after constraints",len(self['born_diagrams']))
815 ldg_debug_info("#UVCTDiags after constraints",len(self['loop_UVCT_diagrams']))
816
817
818 tag_selected=[]
819 loop_basis=base_objects.DiagramList()
820 for diag in self['loop_diagrams']:
821 diag.tag(self['structure_repository'],model)
822
823
824 if not diag.is_wf_correction(self['structure_repository'], \
825 model) and not diag.is_vanishing_tadpole(model) and \
826 diag['canonical_tag'] not in tag_selected:
827 loop_basis.append(diag)
828 tag_selected.append(diag['canonical_tag'])
829
830 self['loop_diagrams']=loop_basis
831
832
833
834 self.filter_loop_for_perturbative_orders()
835
836 if len(self['loop_diagrams'])==0 and len(self['born_diagrams'])!=0:
837 raise InvalidCmd('All loop diagrams discarded by user selection.\n'+\
838 'Consider using a tree-level generation or relaxing the coupling'+\
839 ' order constraints.')
840
841 if not self['process']['has_born'] and not self['loop_diagrams']:
842 self['process']['squared_orders'].clear()
843 self['process']['squared_orders'].update(user_squared_orders)
844 return False
845
846
847 self.set_LoopCT_vertices()
848
849
850 self.remove_Furry_loops(model,self['structure_repository'])
851
852
853
854
855
856 self.user_filter(model,self['structure_repository'], filter=loop_filter)
857
858
859
860
861
862
863
864
865
866
867
868 self['process']['squared_orders'].clear()
869 self['process']['squared_orders'].update(user_squared_orders)
870
871
872
873 self.print_split_order_infos()
874
875
876 nLoopDiag = 0
877 nCT={'UV':0,'R2':0}
878 for ldiag in self['loop_UVCT_diagrams']:
879 nCT[ldiag['type'][:2]]+=len(ldiag['UVCT_couplings'])
880 for ldiag in self['loop_diagrams']:
881 nLoopDiag+=1
882 nCT['UV']+=len(ldiag.get_CT(model,'UV'))
883 nCT['R2']+=len(ldiag.get_CT(model,'R2'))
884
885
886
887
888 nLoopsIdentified = self.identify_loop_diagrams()
889 if nLoopsIdentified > 0:
890 logger.debug("A total of %d loop diagrams "%nLoopsIdentified+\
891 "were identified with equivalent ones.")
892 logger.info("Contributing diagrams generated: "+\
893 "%d Born, %d%s loops, %d R2, %d UV"%(len(self['born_diagrams']),
894 len(self['loop_diagrams']),'(+%d)'%nLoopsIdentified \
895 if nLoopsIdentified>0 else '' ,nCT['R2'],nCT['UV']))
896
897 ldg_debug_info("#Diags after filtering",len(self['loop_diagrams']))
898 ldg_debug_info("# of different structures identified",\
899 len(self['structure_repository']))
900
901 return (bornsuccessful or totloopsuccessful)
902
904 """ Uses a loop_tag characterizing the loop with only physical
905 information about it (mass, coupling, width, color, etc...) so as to
906 recognize numerically equivalent diagrams and group them together,
907 such as massless quark loops in pure QCD gluon loop amplitudes."""
908
909
910
911
912
913
914
915
916
917 diagram_identification = {}
918
919 for i, loop_diag in enumerate(self['loop_diagrams']):
920 loop_tag = loop_diag.build_loop_tag_for_diagram_identification(
921 self['process']['model'], self.get('structure_repository'),
922 use_FDStructure_ID_for_tag = True)
923
924
925
926 try:
927 diagram_identification[loop_tag].append((i+1,loop_diag))
928 except KeyError:
929 diagram_identification[loop_tag] = [(i+1,loop_diag)]
930
931
932 sorted_loop_tag_keys = sorted(diagram_identification.keys(),
933 key=lambda k:diagram_identification[k][0][0])
934
935 new_loop_diagram_base = base_objects.DiagramList([])
936 n_loops_identified = 0
937 for loop_tag in sorted_loop_tag_keys:
938 n_diag_in_class = len(diagram_identification[loop_tag])
939 n_loops_identified += n_diag_in_class-1
940 new_loop_diagram_base.append(diagram_identification[loop_tag][0][1])
941
942
943 new_loop_diagram_base[-1]['multiplier'] = n_diag_in_class
944 for ldiag in diagram_identification[loop_tag][1:]:
945 new_loop_diagram_base[-1].get('CT_vertices').extend(
946 copy.copy(ldiag[1].get('CT_vertices')))
947 if n_diag_in_class > 1:
948 ldg_debug_info("# Diagram equivalence class detected","#(%s) -> #%d"\
949 %(','.join('%d'%diag[0] for diag in diagram_identification[loop_tag][1:])+
950 (',' if n_diag_in_class==2 else ''),diagram_identification[loop_tag][0][0]))
951
952
953 self.set('loop_diagrams',new_loop_diagram_base)
954 return n_loops_identified
955
957 """This function is solely for monitoring purposes. It reports what are
958 the coupling order combination which are obtained with the diagram
959 genarated and among those which ones correspond to those selected by
960 the process definition and which ones are the extra combinations which
961 comes as a byproduct of the computation of the desired one. The typical
962 example is that if you ask for d d~ > u u~ QCD^2==2 [virt=QCD, QED],
963 you will not only get (QCD,QED)=(2,2);(2,4) which are the desired ones
964 but the code output will in principle also be able to return
965 (QCD,QED)=(4,0);(4,2);(0,4);(0,6) because they involve the same amplitudes
966 """
967
968 hierarchy = self['process']['model']['order_hierarchy']
969
970 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
971
972
973 if 'WEIGHTED' not in sqorders_types:
974 sqorders_types['WEIGHTED']='<='
975
976 sorted_hierarchy = [order[0] for order in \
977 sorted(hierarchy.items(), key=lambda el: el[1])]
978
979 loop_SOs = set(tuple([d.get_order(order) for order in sorted_hierarchy])
980 for d in self['loop_diagrams']+self['loop_UVCT_diagrams'])
981
982 if self['process']['has_born']:
983 born_SOs = set(tuple([d.get_order(order) for order in \
984 sorted_hierarchy]) for d in self['born_diagrams'])
985 else:
986 born_SOs = set([])
987
988 born_sqSOs = set(tuple([x + y for x, y in zip(b1_SO, b2_SO)]) for b1_SO
989 in born_SOs for b2_SO in born_SOs)
990 if self['process']['has_born']:
991 ref_amps = born_SOs
992 else:
993 ref_amps = loop_SOs
994 loop_sqSOs = set(tuple([x + y for x, y in zip(b_SO, l_SO)]) for b_SO in
995 ref_amps for l_SO in loop_SOs)
996
997
998 sorted_hierarchy.append('WEIGHTED')
999 born_sqSOs = sorted([b_sqso+(sum([b*hierarchy[sorted_hierarchy[i]] for
1000 i, b in enumerate(b_sqso)]),) for b_sqso in born_sqSOs],
1001 key=lambda el: el[1])
1002 loop_sqSOs = sorted([l_sqso+(sum([l*hierarchy[sorted_hierarchy[i]] for
1003 i, l in enumerate(l_sqso)]),) for l_sqso in loop_sqSOs],
1004 key=lambda el: el[1])
1005
1006
1007 logger.debug("Coupling order combinations considered:"+\
1008 " (%s)"%','.join(sorted_hierarchy))
1009
1010
1011 born_considered = []
1012 loop_considered = []
1013 for i, sqSOList in enumerate([born_sqSOs,loop_sqSOs]):
1014 considered = []
1015 extra = []
1016 for sqSO in sqSOList:
1017 for sqo, constraint in self['process']['squared_orders'].items():
1018 sqo_index = sorted_hierarchy.index(sqo)
1019
1020
1021
1022 if (sqorders_types[sqo]=='==' and
1023 sqSO[sqo_index]!=constraint ) or \
1024 (sqorders_types[sqo] in ['=','<='] and
1025 sqSO[sqo_index]>constraint) or \
1026 (sqorders_types[sqo] in ['>'] and
1027 sqSO[sqo_index]<=constraint):
1028 extra.append(sqSO)
1029 break;
1030
1031
1032 considered = [sqSO for sqSO in sqSOList if sqSO not in extra]
1033
1034 if i==0:
1035 born_considered = considered
1036 name = "Born"
1037 if not self['process']['has_born']:
1038 logger.debug(" > No Born contributions for this process.")
1039 continue
1040 elif i==1:
1041 loop_considered = considered
1042 name = "loop"
1043
1044 if len(considered)==0:
1045 logger.debug(" > %s : None"%name)
1046 else:
1047 logger.debug(" > %s : %s"%(name,' '.join(['(%s,W%d)'%(
1048 ','.join(list('%d'%s for s in c[:-1])),c[-1])
1049 for c in considered])))
1050
1051 if len(extra)!=0:
1052 logger.debug(" > %s (not selected but available): %s"%(name,' '.
1053 join(['(%s,W%d)'%(','.join(list('%d'%s for s in e[:-1])),
1054 e[-1]) for e in extra])))
1055
1056
1057
1058 return (born_considered,
1059 [sqSO for sqSO in born_sqSOs if sqSO not in born_considered],
1060 loop_considered,
1061 [sqSO for sqSO in loop_sqSOs if sqSO not in loop_considered])
1062
1063
1071
1073 """ Generates all loop diagrams relevant to this NLO Process """
1074
1075
1076 self['loop_diagrams']=base_objects.DiagramList()
1077 totloopsuccessful=False
1078
1079
1080 self.lcutpartemployed=[]
1081
1082 for order in self['process']['perturbation_couplings']:
1083 ldg_debug_info("Perturbation coupling generated now ",order)
1084 lcutPart=[particle for particle in \
1085 self['process']['model']['particles'] if \
1086 (particle.is_perturbating(order, self['process']['model']) and \
1087 particle.get_pdg_code() not in \
1088 self['process']['forbidden_particles'])]
1089
1090
1091 for part in lcutPart:
1092 if part.get_pdg_code() not in self.lcutpartemployed:
1093
1094
1095
1096
1097
1098
1099
1100
1101 ldg_debug_info("Generating loop diagram with L-cut type",\
1102 part.get_name())
1103 lcutone=base_objects.Leg({'id': part.get_pdg_code(),
1104 'state': True,
1105 'loop_line': True})
1106 lcuttwo=base_objects.Leg({'id': part.get_anti_pdg_code(),
1107 'state': True,
1108 'loop_line': True})
1109 self['process'].get('legs').extend([lcutone,lcuttwo])
1110
1111
1112
1113
1114
1115
1116
1117 loopsuccessful, lcutdiaglist = \
1118 super(LoopAmplitude, self).generate_diagrams(True)
1119
1120
1121 leg_to_remove=[leg for leg in self['process']['legs'] \
1122 if leg['loop_line']]
1123 for leg in leg_to_remove:
1124 self['process']['legs'].remove(leg)
1125
1126
1127 for diag in lcutdiaglist:
1128 diag.set('type',part.get_pdg_code())
1129 self['loop_diagrams']+=lcutdiaglist
1130
1131
1132
1133 self.lcutpartemployed.append(part.get_pdg_code())
1134 self.lcutpartemployed.append(part.get_anti_pdg_code())
1135
1136 ldg_debug_info("#Diags generated w/ this L-cut particle",\
1137 len(lcutdiaglist))
1138
1139 if loopsuccessful:
1140 totloopsuccessful=True
1141
1142
1143 self.lcutpartemployed=[]
1144
1145 return totloopsuccessful
1146
1147
1149 """ Scan all born diagrams and add for each all the corresponding UV
1150 counterterms. It creates one LoopUVCTDiagram per born diagram and set
1151 of possible coupling_order (so that QCD and QED wavefunction corrections
1152 are not in the same LoopUVCTDiagram for example). Notice that this takes
1153 care only of the UV counterterm which factorize with the born and the
1154 other contributions like the UV mass renormalization are added in the
1155 function setLoopCTVertices"""
1156
1157
1158
1159
1160
1161
1162
1163
1164 UVCTvertex_interactions = base_objects.InteractionList()
1165 for inter in self['process']['model']['interactions'].get_UV():
1166 if inter.is_UVtree() and len(inter['particles'])>1 and \
1167 inter.is_perturbating(self['process']['perturbation_couplings']) \
1168 and (set(inter['orders'].keys()).intersection(\
1169 set(self['process']['perturbation_couplings'])))!=set([]) and \
1170 (any([set(loop_parts).intersection(set(self['process']\
1171 ['forbidden_particles']))==set([]) for loop_parts in \
1172 inter.get('loop_particles')]) or \
1173 inter.get('loop_particles')==[[]]):
1174 UVCTvertex_interactions.append(inter)
1175
1176
1177 self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']=0
1178 self['process']['model'].get('coupling_orders').add('UVCT_SPECIAL')
1179 for inter in UVCTvertex_interactions:
1180 neworders=copy.copy(inter.get('orders'))
1181 neworders['UVCT_SPECIAL']=1
1182 inter.set('orders',neworders)
1183
1184
1185 self['process']['model'].actualize_dictionaries(useUVCT=True)
1186
1187
1188
1189 self['process']['orders']['UVCT_SPECIAL']=1
1190
1191 UVCTsuccessful, UVCTdiagrams = \
1192 super(LoopAmplitude, self).generate_diagrams(True)
1193
1194 for UVCTdiag in UVCTdiagrams:
1195 if UVCTdiag.get_order('UVCT_SPECIAL')==1:
1196 newUVCTDiag = loop_base_objects.LoopUVCTDiagram({\
1197 'vertices':copy.deepcopy(UVCTdiag['vertices'])})
1198 UVCTinter = newUVCTDiag.get_UVCTinteraction(self['process']['model'])
1199 newUVCTDiag.set('type',UVCTinter.get('type'))
1200
1201
1202
1203 newUVCTDiag.get('UVCT_couplings').append((len([1 for loop_parts \
1204 in UVCTinter.get('loop_particles') if set(loop_parts).intersection(\
1205 set(self['process']['forbidden_particles']))==set([])])) if
1206 loop_parts!=[[]] else 1)
1207 self['loop_UVCT_diagrams'].append(newUVCTDiag)
1208
1209
1210
1211 del self['process']['orders']['UVCT_SPECIAL']
1212
1213 del self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']
1214 self['process']['model'].get('coupling_orders').remove('UVCT_SPECIAL')
1215 for inter in UVCTvertex_interactions:
1216 del inter.get('orders')['UVCT_SPECIAL']
1217
1218 self['process']['model'].actualize_dictionaries(useUVCT=False)
1219
1220
1221 for UVCTdiag in self['loop_UVCT_diagrams']:
1222 UVCTdiag.calculate_orders(self['process']['model'])
1223
1224
1225
1226
1227
1228 if not self['process']['has_born']:
1229 return UVCTsuccessful
1230
1231
1232
1233 for bornDiag in self['born_diagrams']:
1234
1235
1236
1237
1238
1239
1240
1241 LoopUVCTDiagramsAdded={}
1242 for leg in self['process']['legs']:
1243 counterterm=self['process']['model'].get_particle(abs(leg['id'])).\
1244 get('counterterm')
1245 for key, value in counterterm.items():
1246 if key[0] in self['process']['perturbation_couplings']:
1247 for laurentOrder, CTCoupling in value.items():
1248
1249 orderKey=[(key[0],2),]
1250 orderKey.sort()
1251 orderKey.append(('EpsilonOrder',-laurentOrder))
1252 CTCouplings=[CTCoupling for loop_parts in key[1] if
1253 set(loop_parts).intersection(set(self['process']\
1254 ['forbidden_particles']))==set([])]
1255 if CTCouplings!=[]:
1256 try:
1257 LoopUVCTDiagramsAdded[tuple(orderKey)].get(\
1258 'UVCT_couplings').extend(CTCouplings)
1259 except KeyError:
1260 LoopUVCTDiagramsAdded[tuple(orderKey)]=\
1261 loop_base_objects.LoopUVCTDiagram({\
1262 'vertices':copy.deepcopy(bornDiag['vertices']),
1263 'type':'UV'+('' if laurentOrder==0 else
1264 str(-laurentOrder)+'eps'),
1265 'UVCT_orders':{key[0]:2},
1266 'UVCT_couplings':CTCouplings})
1267
1268 for LoopUVCTDiagram in LoopUVCTDiagramsAdded.values():
1269 LoopUVCTDiagram.calculate_orders(self['process']['model'])
1270 self['loop_UVCT_diagrams'].append(LoopUVCTDiagram)
1271
1272 return UVCTsuccessful
1273
1275 """ Scan each loop diagram and recognizes what are the R2/UVmass
1276 CounterTerms associated to them """
1277
1278
1279
1280
1281
1282
1283
1284
1285 CT_interactions = {}
1286 for inter in self['process']['model']['interactions']:
1287 if inter.is_UVmass() or inter.is_UVloop() or inter.is_R2() and \
1288 len(inter['particles'])>1 and inter.is_perturbating(\
1289 self['process']['perturbation_couplings']):
1290
1291
1292
1293 for i, lparts in enumerate(inter['loop_particles']):
1294 keya=copy.copy(lparts)
1295 keya.sort()
1296 if inter.is_UVloop():
1297
1298
1299
1300
1301 if (set(self['process']['forbidden_particles']) & \
1302 set(lparts)) != set([]):
1303 continue
1304 else:
1305 keya=[]
1306 keyb=[part.get_pdg_code() for part in inter['particles']]
1307 keyb.sort()
1308 key=(tuple(keyb),tuple(keya))
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329 try:
1330 CT_interactions[key].append((inter['id'],i))
1331 except KeyError:
1332 CT_interactions[key]=[(inter['id'],i),]
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352 CT_added = {}
1353
1354 for diag in self['loop_diagrams']:
1355
1356
1357 searchingKeyA=[]
1358
1359 searchingKeyB=[]
1360 trackingKeyA=[]
1361 for tagElement in diag['canonical_tag']:
1362 for structID in tagElement[1]:
1363 trackingKeyA.append(structID)
1364 searchingKeyA.append(self['process']['model'].get_particle(\
1365 self['structure_repository'][structID]['binding_leg']['id']).\
1366 get_pdg_code())
1367 searchingKeyB.append(self['process']['model'].get_particle(\
1368 tagElement[0]).get('pdg_code'))
1369 searchingKeyA.sort()
1370
1371 searchingKeyB=list(set(searchingKeyB))
1372 searchingKeyB.sort()
1373 trackingKeyA.sort()
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393 searchingKeySimple=(tuple(searchingKeyA),())
1394 searchingKeyLoopPart=(tuple(searchingKeyA),tuple(searchingKeyB))
1395 trackingKeySimple=(tuple(trackingKeyA),())
1396 trackingKeyLoopPart=(tuple(trackingKeyA),tuple(searchingKeyB))
1397
1398
1399
1400
1401 try:
1402 CTIDs=copy.copy(CT_interactions[searchingKeySimple])
1403 except KeyError:
1404 CTIDs=[]
1405 try:
1406 CTIDs.extend(copy.copy(CT_interactions[searchingKeyLoopPart]))
1407 except KeyError:
1408 pass
1409 if not CTIDs:
1410 continue
1411
1412
1413 try:
1414 usedIDs=copy.copy(CT_added[trackingKeySimple])
1415 except KeyError:
1416 usedIDs=[]
1417 try:
1418 usedIDs.extend(copy.copy(CT_added[trackingKeyLoopPart]))
1419 except KeyError:
1420 pass
1421
1422 for CTID in CTIDs:
1423
1424
1425 if CTID not in usedIDs and diag.get_loop_orders(\
1426 self['process']['model'])==\
1427 self['process']['model']['interaction_dict'][CTID[0]]['orders']:
1428
1429
1430 CTleglist = base_objects.LegList()
1431 for tagElement in diag['canonical_tag']:
1432 for structID in tagElement[1]:
1433 CTleglist.append(\
1434 self['structure_repository'][structID]['binding_leg'])
1435 CTVertex = base_objects.Vertex({'id':CTID[0], \
1436 'legs':CTleglist})
1437 diag['CT_vertices'].append(CTVertex)
1438
1439
1440 if self['process']['model']['interaction_dict'][CTID[0]]\
1441 ['loop_particles'][CTID[1]]==[] or \
1442 self['process']['model']['interaction_dict'][CTID[0]].\
1443 is_UVloop():
1444 try:
1445 CT_added[trackingKeySimple].append(CTID)
1446 except KeyError:
1447 CT_added[trackingKeySimple] = [CTID, ]
1448 else:
1449 try:
1450 CT_added[trackingKeyLoopPart].append(CTID)
1451 except KeyError:
1452 CT_added[trackingKeyLoopPart] = [CTID, ]
1453
1457
1459 """ Returns a DGLoopLeg list instead of the default copy_leglist
1460 defined in base_objects.Amplitude """
1461
1462 dgloopleglist=base_objects.LegList()
1463 for leg in leglist:
1464 dgloopleglist.append(loop_base_objects.DGLoopLeg(leg))
1465
1466 return dgloopleglist
1467
1469 """ Overloaded here to convert back all DGLoopLegs into Legs. """
1470 for vertexlist in vertexdoublelist:
1471 for vertex in vertexlist:
1472 if not isinstance(vertex['legs'][0],loop_base_objects.DGLoopLeg):
1473 continue
1474 vertex['legs'][:]=[leg.convert_to_leg() for leg in \
1475 vertex['legs']]
1476 return True
1477
1479 """Create a set of new legs from the info given."""
1480
1481 looplegs=[leg for leg in legs if leg['loop_line']]
1482
1483
1484
1485 model=self['process']['model']
1486 exlegs=[leg for leg in looplegs if leg['depth']==0]
1487 if(len(exlegs)==2):
1488 if(any([part['mass'].lower()=='zero' for pdg,part in model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1489 return []
1490
1491
1492 loopline=(len(looplegs)==1)
1493 mylegs = []
1494 for i, (leg_id, vert_id) in enumerate(leg_vert_ids):
1495
1496
1497
1498
1499 if not loopline or not (leg_id in self.lcutpartemployed):
1500
1501
1502
1503
1504 if len(legs)==2 and len(looplegs)==2:
1505
1506 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1507 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1508
1509
1510
1511 continue
1512
1513
1514
1515
1516
1517 depth=-1
1518
1519
1520 if len(legs)==2 and loopline and (legs[0]['depth'],\
1521 legs[1]['depth'])==(0,0):
1522 if not legs[0]['loop_line']:
1523 depth=legs[0]['id']
1524 else:
1525 depth=legs[1]['id']
1526
1527
1528 if len(legs)==1 and legs[0]['id']==leg_id:
1529 depth=legs[0]['depth']
1530
1531
1532
1533
1534 mylegs.append((loop_base_objects.DGLoopLeg({'id':leg_id,
1535 'number':number,
1536 'state':state,
1537 'from_group':True,
1538 'depth': depth,
1539 'loop_line': loopline}),
1540 vert_id))
1541 return mylegs
1542
1544 """Allow for selection of vertex ids."""
1545
1546 looplegs=[leg for leg in legs if leg['loop_line']]
1547 nonlooplegs=[leg for leg in legs if not leg['loop_line']]
1548
1549
1550 model=self['process']['model']
1551 exlegs=[leg for leg in looplegs if leg['depth']==0]
1552 if(len(exlegs)==2):
1553 if(any([part['mass'].lower()=='zero' for pdg,part in \
1554 model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1555 return []
1556
1557
1558
1559
1560 if(len(legs)==3 and len(looplegs)==2):
1561 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1562 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1563 return []
1564
1565 return vert_ids
1566
1567
1568
1570 """ Filters the diagrams according to the constraints on the squared
1571 orders in argument and wether the process has a born or not. """
1572
1573 diagRef=base_objects.DiagramList()
1574 AllLoopDiagrams=base_objects.DiagramList(self['loop_diagrams']+\
1575 self['loop_UVCT_diagrams'])
1576
1577 AllBornDiagrams=base_objects.DiagramList(self['born_diagrams'])
1578 if self['process']['has_born']:
1579 diagRef=AllBornDiagrams
1580 else:
1581 diagRef=AllLoopDiagrams
1582
1583 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
1584
1585
1586
1587 if 'WEIGHTED' not in sqorders_types:
1588 sqorders_types['WEIGHTED']='<='
1589
1590 if len(diagRef)==0:
1591
1592
1593
1594
1595
1596 AllLoopDiagrams = base_objects.DiagramList()
1597
1598
1599
1600 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(diagRef,
1601 sq_order_constrains, sqorders_types)
1602
1603 if self['process']['has_born']:
1604
1605 AllBornDiagrams = AllBornDiagrams.apply_positive_sq_orders(
1606 AllLoopDiagrams+AllBornDiagrams, sq_order_constrains, sqorders_types)
1607
1608
1609 neg_orders = [(order, value) for order, value in \
1610 sq_order_constrains.items() if value<0]
1611 if len(neg_orders)==1:
1612 neg_order, neg_value = neg_orders[0]
1613
1614
1615 if self['process']['has_born']:
1616 AllBornDiagrams, target_order =\
1617 AllBornDiagrams.apply_negative_sq_order(
1618 base_objects.DiagramList(AllLoopDiagrams+AllBornDiagrams),
1619 neg_order,neg_value,sqorders_types[neg_order])
1620
1621
1622 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(
1623 diagRef,{neg_order:target_order},
1624 {neg_order:sqorders_types[neg_order]})
1625
1626
1627
1628 else:
1629 AllLoopDiagrams, target_order = \
1630 AllLoopDiagrams.apply_negative_sq_order(
1631 diagRef,neg_order,neg_value,sqorders_types[neg_order])
1632
1633
1634
1635
1636
1637 self['process']['squared_orders'][neg_order]=target_order
1638 user_squared_orders[neg_order]=target_order
1639
1640 elif len(neg_orders)>1:
1641 raise MadGraph5Error('At most one negative squared order constraint'+\
1642 ' can be specified, not %s.'%str(neg_orders))
1643
1644 if self['process']['has_born']:
1645 self['born_diagrams'] = AllBornDiagrams
1646 self['loop_diagrams']=[diag for diag in AllLoopDiagrams if not \
1647 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1648 self['loop_UVCT_diagrams']=[diag for diag in AllLoopDiagrams if \
1649 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1650
1652 """ This is a helper function for order_diagrams_according_to_split_orders
1653 and intended to be used from LoopHelasAmplitude only"""
1654
1655
1656
1657 diag_by_so = {}
1658
1659 for diag in diag_set:
1660 so_key = tuple([diag.get_order(order) for order in split_orders])
1661 try:
1662 diag_by_so[so_key].append(diag)
1663 except KeyError:
1664 diag_by_so[so_key]=base_objects.DiagramList([diag,])
1665
1666 so_keys = diag_by_so.keys()
1667
1668
1669 order_hierarchy = self.get('process').get('model').get('order_hierarchy')
1670 order_weights = copy.copy(order_hierarchy)
1671 for so in split_orders:
1672 if so not in order_hierarchy.keys():
1673 order_weights[so]=0
1674
1675
1676
1677
1678 so_keys = sorted(so_keys, key = lambda elem: (sum([power*order_weights[\
1679 split_orders[i]] for i,power in enumerate(elem)])))
1680
1681
1682 diag_set[:] = []
1683 for so_key in so_keys:
1684 diag_set.extend(diag_by_so[so_key])
1685
1686
1688 """ Reorder the loop and Born diagrams (if any) in group of diagrams
1689 sharing the same coupling orders are put together and these groups are
1690 order in decreasing WEIGHTED orders.
1691 Notice that this function is only called for now by the
1692 LoopHelasMatrixElement instances at the output stage.
1693 """
1694
1695
1696
1697 if len(split_orders)==0:
1698 return
1699
1700 self.order_diagram_set(self['born_diagrams'], split_orders)
1701 self.order_diagram_set(self['loop_diagrams'], split_orders)
1702 self.order_diagram_set(self['loop_UVCT_diagrams'], split_orders)
1703
1708 """LoopMultiProcess: MultiProcess with loop features.
1709 """
1710
1711 @classmethod
1713 """ Return the correct amplitude type according to the characteristics
1714 of the process proc """
1715 return LoopAmplitude({"process": proc})
1716
1724 """Special mode for the LoopInduced."""
1725
1726 @classmethod
1728 """ Return the correct amplitude type according to the characteristics of
1729 the process proc """
1730 return LoopAmplitude({"process": proc, 'has_born':False})
1731