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