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