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