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 self['process']['forbidden_particles']=[]
654
655
656 if not self['process']['has_born'] and not self['loop_diagrams']:
657 return False
658
659
660
661
662 if self['process']['has_born']:
663 self.set_Born_CT()
664
665 ldg_debug_info("#UVCTDiags generated",len(self['loop_UVCT_diagrams']))
666
667
668 self['process']['orders'].clear()
669 self['process']['orders'].update(user_orders)
670
671
672
673
674 if not self['process']['has_born'] and not \
675 self['process']['squared_orders'] and hierarchy:
676 pert_order_weights=[hierarchy[order] for order in \
677 self['process']['perturbation_couplings']]
678 self['process']['squared_orders']['WEIGHTED']=2*(\
679 self['loop_diagrams'].get_min_order('WEIGHTED')+\
680 max(pert_order_weights)-min(pert_order_weights))
681
682 ldg_debug_info("Squared orders after treatment",\
683 self['process']['squared_orders'])
684 ldg_debug_info("#Diags after diagram generation",\
685 len(self['loop_diagrams']))
686
687
688
689
690
691
692 if chosen_order_config != {}:
693 self.filter_from_order_config('loop_diagrams', \
694 chosen_order_config,discarded_configurations)
695
696 if discarded_configurations!=[]:
697 msg = ("The contribution%s of th%s coupling orders "+\
698 "configuration%s %s discarded :%s")%(('s','ese','s','are','\n')\
699 if len(discarded_configurations)>1 else ('','is','','is',' '))
700 msg = msg + '\n'.join(['(%s)'%self.print_config(conf) for conf \
701 in discarded_configurations])
702 msg = msg + "\nManually set the coupling orders to "+\
703 "generate %sthe contribution%s above."%(('any of ','s') if \
704 len(discarded_configurations)>1 else ('',''))
705 logger.info(msg)
706
707
708
709
710
711
712
713 regular_constraints = dict([(key,val) for (key,val) in
714 self['process']['squared_orders'].items() if val>=0])
715 negative_constraints = dict([(key,val) for (key,val) in
716 self['process']['squared_orders'].items() if val<0])
717 while True:
718 ndiag_remaining=len(self['loop_diagrams']+self['born_diagrams'])
719 self.check_squared_orders(regular_constraints)
720 if len(self['loop_diagrams']+self['born_diagrams'])==ndiag_remaining:
721 break
722
723 if negative_constraints!={}:
724
725
726
727
728
729
730
731
732 self.check_squared_orders(negative_constraints,user_squared_orders)
733
734 ldg_debug_info("#Diags after constraints",len(self['loop_diagrams']))
735 ldg_debug_info("#Born diagrams after constraints",len(self['born_diagrams']))
736 ldg_debug_info("#UVCTDiags after constraints",len(self['loop_UVCT_diagrams']))
737
738
739 tag_selected=[]
740 loop_basis=base_objects.DiagramList()
741 for diag in self['loop_diagrams']:
742 diag.tag(self['structure_repository'],len(self['process']['legs'])+1\
743 ,len(self['process']['legs'])+2,self['process'])
744
745
746 if not diag.is_wf_correction(self['structure_repository'], \
747 model) and not diag.is_vanishing_tadpole(model) and \
748 diag['canonical_tag'] not in tag_selected:
749 loop_basis.append(diag)
750 tag_selected.append(diag['canonical_tag'])
751
752 self['loop_diagrams']=loop_basis
753
754
755
756 self.filter_loop_for_perturbative_orders()
757
758 if len(self['loop_diagrams'])==0 and len(self['born_diagrams'])!=0:
759 raise InvalidCmd('All loop diagrams discarded by user selection.\n'+\
760 'Consider using a tree-level generation or relaxing the coupling'+\
761 ' order constraints.')
762
763 if not self['process']['has_born'] and not self['loop_diagrams']:
764 return False
765
766
767 self.set_LoopCT_vertices()
768
769
770
771
772
773 self.user_filter(model,self['structure_repository'])
774
775
776
777
778
779
780
781
782
783
784
785 self['process']['squared_orders'].clear()
786 self['process']['squared_orders'].update(user_squared_orders)
787
788
789
790 self.print_split_order_infos()
791
792
793 nLoopDiag = 0
794 nCT={'UV':0,'R2':0}
795 for ldiag in self['loop_UVCT_diagrams']:
796 nCT[ldiag['type'][:2]]+=len(ldiag['UVCT_couplings'])
797 for ldiag in self['loop_diagrams']:
798 nLoopDiag+=1
799 nCT['UV']+=len(ldiag.get_CT(model,'UV'))
800 nCT['R2']+=len(ldiag.get_CT(model,'R2'))
801
802 logger.info("Contributing diagrams generated: "+\
803 "%d born, %d loop, %d R2, %d UV"%\
804 (len(self['born_diagrams']),nLoopDiag,nCT['R2'],nCT['UV']))
805
806 ldg_debug_info("#Diags after filtering",len(self['loop_diagrams']))
807 ldg_debug_info("# of different structures identified",\
808 len(self['structure_repository']))
809
810 return (bornsuccessful or totloopsuccessful)
811
813 """This function is solely for monitoring purposes. It reports what are
814 the coupling order combination which are obtained with the diagram
815 genarated and among those which ones correspond to those selected by
816 the process definition and which ones are the extra combinations which
817 comes as a byproduct of the computation of the desired one. The typical
818 example is that if you ask for d d~ > u u~ QCD^2==2 [virt=QCD, QED],
819 you will not only get (QCD,QED)=(2,2);(2,4) which are the desired ones
820 but the code output will in principle also be able to return
821 (QCD,QED)=(4,0);(4,2);(0,4);(0,6) because they involve the same amplitudes
822 """
823
824 hierarchy = self['process']['model']['order_hierarchy']
825
826 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
827
828
829 if 'WEIGHTED' not in sqorders_types:
830 sqorders_types['WEIGHTED']='<='
831
832 sorted_hierarchy = [order[0] for order in \
833 sorted(hierarchy.items(), key=lambda el: el[1])]
834
835 loop_SOs = set(tuple([d.get_order(order) for order in sorted_hierarchy])
836 for d in self['loop_diagrams']+self['loop_UVCT_diagrams'])
837
838 if self['process']['has_born']:
839 born_SOs = set(tuple([d.get_order(order) for order in \
840 sorted_hierarchy]) for d in self['born_diagrams'])
841 else:
842 born_SOs = set([])
843
844 born_sqSOs = set(tuple([x + y for x, y in zip(b1_SO, b2_SO)]) for b1_SO
845 in born_SOs for b2_SO in born_SOs)
846 if self['process']['has_born']:
847 ref_amps = born_SOs
848 else:
849 ref_amps = loop_SOs
850 loop_sqSOs = set(tuple([x + y for x, y in zip(b_SO, l_SO)]) for b_SO in
851 ref_amps for l_SO in loop_SOs)
852
853
854 sorted_hierarchy.append('WEIGHTED')
855 born_sqSOs = sorted([b_sqso+(sum([b*hierarchy[sorted_hierarchy[i]] for
856 i, b in enumerate(b_sqso)]),) for b_sqso in born_sqSOs],
857 key=lambda el: el[1])
858 loop_sqSOs = sorted([l_sqso+(sum([l*hierarchy[sorted_hierarchy[i]] for
859 i, l in enumerate(l_sqso)]),) for l_sqso in loop_sqSOs],
860 key=lambda el: el[1])
861
862
863 logger.debug("Coupling order combinations considered:"+\
864 " (%s)"%','.join(sorted_hierarchy))
865
866
867 born_considered = []
868 loop_considered = []
869 for i, sqSOList in enumerate([born_sqSOs,loop_sqSOs]):
870 considered = []
871 extra = []
872 for sqSO in sqSOList:
873 for sqo, constraint in self['process']['squared_orders'].items():
874 sqo_index = sorted_hierarchy.index(sqo)
875
876
877
878 if (sqorders_types[sqo]=='==' and
879 sqSO[sqo_index]!=constraint ) or \
880 (sqorders_types[sqo] in ['=','<='] and
881 sqSO[sqo_index]>constraint) or \
882 (sqorders_types[sqo] in ['>'] and
883 sqSO[sqo_index]<=constraint):
884 extra.append(sqSO)
885 break;
886
887
888 considered = [sqSO for sqSO in sqSOList if sqSO not in extra]
889
890 if i==0:
891 born_considered = considered
892 name = "Born"
893 if not self['process']['has_born']:
894 logger.debug(" > No Born contributions for this process.")
895 continue
896 elif i==1:
897 loop_considered = considered
898 name = "loop"
899
900 if len(considered)==0:
901 logger.debug(" > %s : None"%name)
902 else:
903 logger.debug(" > %s : %s"%(name,' '.join(['(%s,W%d)'%(
904 ','.join(list('%d'%s for s in c[:-1])),c[-1])
905 for c in considered])))
906
907 if len(extra)!=0:
908 logger.debug(" > %s (not selected but available): %s"%(name,' '.
909 join(['(%s,W%d)'%(','.join(list('%d'%s for s in e[:-1])),
910 e[-1]) for e in extra])))
911
912
913
914 return (born_considered,
915 [sqSO for sqSO in born_sqSOs if sqSO not in born_considered],
916 loop_considered,
917 [sqSO for sqSO in loop_sqSOs if sqSO not in loop_considered])
918
919
921 """ Generates all born diagrams relevant to this NLO Process """
922
923 bornsuccessful, self['born_diagrams'] = \
924 super(LoopAmplitude, self).generate_diagrams(True)
925
926 return bornsuccessful
927
929 """ Generates all loop diagrams relevant to this NLO Process """
930
931
932 self['loop_diagrams']=base_objects.DiagramList()
933 totloopsuccessful=False
934
935
936 self.lcutpartemployed=[]
937
938 for order in self['process']['perturbation_couplings']:
939 ldg_debug_info("Perturbation coupling generated now ",order)
940 lcutPart=[particle for particle in \
941 self['process']['model']['particles'] if \
942 (particle.is_perturbating(order, self['process']['model']) and \
943 particle.get_pdg_code() not in \
944 self['process']['forbidden_particles'])]
945
946
947 for part in lcutPart:
948 if part.get_pdg_code() not in self.lcutpartemployed:
949
950
951
952
953
954
955
956
957 ldg_debug_info("Generating loop diagram with L-cut type",\
958 part.get_name())
959 lcutone=base_objects.Leg({'id': part.get_pdg_code(),
960 'state': True,
961 'loop_line': True})
962 lcuttwo=base_objects.Leg({'id': part.get_anti_pdg_code(),
963 'state': True,
964 'loop_line': True})
965 self['process'].get('legs').extend([lcutone,lcuttwo])
966
967
968
969
970
971
972
973 loopsuccessful, lcutdiaglist = \
974 super(LoopAmplitude, self).generate_diagrams(True)
975
976
977 leg_to_remove=[leg for leg in self['process']['legs'] \
978 if leg['loop_line']]
979 for leg in leg_to_remove:
980 self['process']['legs'].remove(leg)
981
982
983 for diag in lcutdiaglist:
984 diag.set('type',part.get_pdg_code())
985 self['loop_diagrams']+=lcutdiaglist
986
987
988
989 self.lcutpartemployed.append(part.get_pdg_code())
990 self.lcutpartemployed.append(part.get_anti_pdg_code())
991
992 ldg_debug_info("#Diags generated w/ this L-cut particle",\
993 len(lcutdiaglist))
994
995 if loopsuccessful:
996 totloopsuccessful=True
997
998
999 self.lcutpartemployed=[]
1000
1001 return totloopsuccessful
1002
1003
1005 """ Scan all born diagrams and add for each all the corresponding UV
1006 counterterms. It creates one LoopUVCTDiagram per born diagram and set
1007 of possible coupling_order (so that QCD and QED wavefunction corrections
1008 are not in the same LoopUVCTDiagram for example). Notice that this takes
1009 care only of the UV counterterm which factorize with the born and the
1010 other contributions like the UV mass renormalization are added in the
1011 function setLoopCTVertices"""
1012
1013
1014
1015
1016
1017
1018
1019
1020 UVCTvertex_interactions = base_objects.InteractionList()
1021 for inter in self['process']['model']['interactions'].get_UV():
1022 if inter.is_UVtree() and len(inter['particles'])>1 and \
1023 inter.is_perturbating(self['process']['perturbation_couplings']) \
1024 and (set(inter['orders'].keys()).intersection(\
1025 set(self['process']['perturbation_couplings'])))!=set([]) and \
1026 (any([set(loop_parts).intersection(set(self['process']\
1027 ['forbidden_particles']))==set([]) for loop_parts in \
1028 inter.get('loop_particles')]) or \
1029 inter.get('loop_particles')==[[]]):
1030 UVCTvertex_interactions.append(inter)
1031
1032
1033 self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']=0
1034 self['process']['model'].get('coupling_orders').add('UVCT_SPECIAL')
1035 for inter in UVCTvertex_interactions:
1036 neworders=copy.copy(inter.get('orders'))
1037 neworders['UVCT_SPECIAL']=1
1038 inter.set('orders',neworders)
1039
1040
1041 self['process']['model'].actualize_dictionaries(useUVCT=True)
1042
1043
1044
1045 self['process']['orders']['UVCT_SPECIAL']=1
1046
1047 UVCTsuccessful, UVCTdiagrams = \
1048 super(LoopAmplitude, self).generate_diagrams(True)
1049
1050 for UVCTdiag in UVCTdiagrams:
1051 if UVCTdiag.get_order('UVCT_SPECIAL')==1:
1052 newUVCTDiag = loop_base_objects.LoopUVCTDiagram({\
1053 'vertices':copy.deepcopy(UVCTdiag['vertices'])})
1054 UVCTinter = newUVCTDiag.get_UVCTinteraction(self['process']['model'])
1055 newUVCTDiag.set('type',UVCTinter.get('type'))
1056
1057
1058
1059 newUVCTDiag.get('UVCT_couplings').append((len([1 for loop_parts \
1060 in UVCTinter.get('loop_particles') if set(loop_parts).intersection(\
1061 set(self['process']['forbidden_particles']))==set([])])) if
1062 loop_parts!=[[]] else 1)
1063 self['loop_UVCT_diagrams'].append(newUVCTDiag)
1064
1065
1066
1067 del self['process']['orders']['UVCT_SPECIAL']
1068
1069 del self['process']['model'].get('order_hierarchy')['UVCT_SPECIAL']
1070 self['process']['model'].get('coupling_orders').remove('UVCT_SPECIAL')
1071 for inter in UVCTvertex_interactions:
1072 del inter.get('orders')['UVCT_SPECIAL']
1073
1074 self['process']['model'].actualize_dictionaries(useUVCT=False)
1075
1076
1077 for UVCTdiag in self['loop_UVCT_diagrams']:
1078 UVCTdiag.calculate_orders(self['process']['model'])
1079
1080
1081
1082
1083
1084 if not self['process']['has_born']:
1085 return UVCTsuccessful
1086
1087
1088
1089 for bornDiag in self['born_diagrams']:
1090
1091
1092
1093
1094
1095
1096
1097 LoopUVCTDiagramsAdded={}
1098 for leg in self['process']['legs']:
1099 counterterm=self['process']['model'].get_particle(abs(leg['id'])).\
1100 get('counterterm')
1101 for key, value in counterterm.items():
1102 if key[0] in self['process']['perturbation_couplings']:
1103 for laurentOrder, CTCoupling in value.items():
1104
1105 orderKey=[(key[0],2),]
1106 orderKey.sort()
1107 orderKey.append(('EpsilonOrder',-laurentOrder))
1108 CTCouplings=[CTCoupling for loop_parts in key[1] if
1109 set(loop_parts).intersection(set(self['process']\
1110 ['forbidden_particles']))==set([])]
1111 if CTCouplings!=[]:
1112 try:
1113 LoopUVCTDiagramsAdded[tuple(orderKey)].get(\
1114 'UVCT_couplings').extend(CTCouplings)
1115 except KeyError:
1116 LoopUVCTDiagramsAdded[tuple(orderKey)]=\
1117 loop_base_objects.LoopUVCTDiagram({\
1118 'vertices':copy.deepcopy(bornDiag['vertices']),
1119 'type':'UV'+('' if laurentOrder==0 else
1120 str(-laurentOrder)+'eps'),
1121 'UVCT_orders':{key[0]:2},
1122 'UVCT_couplings':CTCouplings})
1123
1124 for LoopUVCTDiagram in LoopUVCTDiagramsAdded.values():
1125 LoopUVCTDiagram.calculate_orders(self['process']['model'])
1126 self['loop_UVCT_diagrams'].append(LoopUVCTDiagram)
1127
1128 return UVCTsuccessful
1129
1131 """ Scan each loop diagram and recognizes what are the R2/UVmass
1132 CounterTerms associated to them """
1133
1134
1135
1136
1137
1138
1139
1140
1141 CT_interactions = {}
1142 for inter in self['process']['model']['interactions']:
1143 if inter.is_UVmass() or inter.is_UVloop() or inter.is_R2() and \
1144 len(inter['particles'])>1 and inter.is_perturbating(\
1145 self['process']['perturbation_couplings']):
1146
1147
1148
1149 for i, lparts in enumerate(inter['loop_particles']):
1150 keya=copy.copy(lparts)
1151 keya.sort()
1152 if inter.is_UVloop():
1153
1154
1155
1156
1157 if (set(self['process']['forbidden_particles']) & \
1158 set(lparts)) != set([]):
1159 continue
1160 else:
1161 keya=[]
1162 keyb=[part.get_pdg_code() for part in inter['particles']]
1163 keyb.sort()
1164 key=(tuple(keyb),tuple(keya))
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185 try:
1186 CT_interactions[key].append((inter['id'],i))
1187 except KeyError:
1188 CT_interactions[key]=[(inter['id'],i),]
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208 CT_added = {}
1209
1210 for diag in self['loop_diagrams']:
1211
1212
1213 searchingKeyA=[]
1214
1215 searchingKeyB=[]
1216 trackingKeyA=[]
1217 for tagElement in diag['canonical_tag']:
1218 for structID in tagElement[1]:
1219 trackingKeyA.append(structID)
1220 searchingKeyA.append(self['process']['model'].get_particle(\
1221 self['structure_repository'][structID]['binding_leg']['id']).\
1222 get_pdg_code())
1223 searchingKeyB.append(self['process']['model'].get_particle(\
1224 tagElement[0]).get('pdg_code'))
1225 searchingKeyA.sort()
1226
1227 searchingKeyB=list(set(searchingKeyB))
1228 searchingKeyB.sort()
1229 trackingKeyA.sort()
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249 searchingKeySimple=(tuple(searchingKeyA),())
1250 searchingKeyLoopPart=(tuple(searchingKeyA),tuple(searchingKeyB))
1251 trackingKeySimple=(tuple(trackingKeyA),())
1252 trackingKeyLoopPart=(tuple(trackingKeyA),tuple(searchingKeyB))
1253
1254
1255
1256
1257 try:
1258 CTIDs=copy.copy(CT_interactions[searchingKeySimple])
1259 except KeyError:
1260 CTIDs=[]
1261 try:
1262 CTIDs.extend(copy.copy(CT_interactions[searchingKeyLoopPart]))
1263 except KeyError:
1264 pass
1265 if not CTIDs:
1266 continue
1267
1268
1269 try:
1270 usedIDs=copy.copy(CT_added[trackingKeySimple])
1271 except KeyError:
1272 usedIDs=[]
1273 try:
1274 usedIDs.extend(copy.copy(CT_added[trackingKeyLoopPart]))
1275 except KeyError:
1276 pass
1277
1278 for CTID in CTIDs:
1279
1280
1281 if CTID not in usedIDs and diag.get_loop_orders(\
1282 self['process']['model'])==\
1283 self['process']['model']['interaction_dict'][CTID[0]]['orders']:
1284
1285
1286 CTleglist = base_objects.LegList()
1287 for tagElement in diag['canonical_tag']:
1288 for structID in tagElement[1]:
1289 CTleglist.append(\
1290 self['structure_repository'][structID]['binding_leg'])
1291 CTVertex = base_objects.Vertex({'id':CTID[0], \
1292 'legs':CTleglist})
1293 diag['CT_vertices'].append(CTVertex)
1294
1295
1296 if self['process']['model']['interaction_dict'][CTID[0]]\
1297 ['loop_particles'][CTID[1]]==[] or \
1298 self['process']['model']['interaction_dict'][CTID[0]].\
1299 is_UVloop():
1300 try:
1301 CT_added[trackingKeySimple].append(CTID)
1302 except KeyError:
1303 CT_added[trackingKeySimple] = [CTID, ]
1304 else:
1305 try:
1306 CT_added[trackingKeyLoopPart].append(CTID)
1307 except KeyError:
1308 CT_added[trackingKeyLoopPart] = [CTID, ]
1309
1313
1315 """ Returns a DGLoopLeg list instead of the default copy_leglist
1316 defined in base_objects.Amplitude """
1317
1318 dgloopleglist=base_objects.LegList()
1319 for leg in leglist:
1320 dgloopleglist.append(loop_base_objects.DGLoopLeg(leg))
1321
1322 return dgloopleglist
1323
1325 """ Overloaded here to convert back all DGLoopLegs into Legs. """
1326 for vertexlist in vertexdoublelist:
1327 for vertex in vertexlist:
1328 if not isinstance(vertex['legs'][0],loop_base_objects.DGLoopLeg):
1329 continue
1330 vertex['legs'][:]=[leg.convert_to_leg() for leg in \
1331 vertex['legs']]
1332 return True
1333
1335 """Create a set of new legs from the info given."""
1336
1337 looplegs=[leg for leg in legs if leg['loop_line']]
1338
1339
1340
1341 model=self['process']['model']
1342 exlegs=[leg for leg in looplegs if leg['depth']==0]
1343 if(len(exlegs)==2):
1344 if(any([part['mass'].lower()=='zero' for pdg,part in model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1345 return []
1346
1347
1348 loopline=(len(looplegs)==1)
1349 mylegs = []
1350 for i, (leg_id, vert_id) in enumerate(leg_vert_ids):
1351
1352
1353
1354
1355 if not loopline or not (leg_id in self.lcutpartemployed):
1356
1357
1358
1359
1360 if len(legs)==2 and len(looplegs)==2:
1361
1362 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1363 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1364
1365
1366
1367 continue
1368
1369
1370
1371
1372
1373 depth=-1
1374
1375
1376 if len(legs)==2 and loopline and (legs[0]['depth'],\
1377 legs[1]['depth'])==(0,0):
1378 if not legs[0]['loop_line']:
1379 depth=legs[0]['id']
1380 else:
1381 depth=legs[1]['id']
1382
1383
1384 if len(legs)==1 and legs[0]['id']==leg_id:
1385 depth=legs[0]['depth']
1386
1387
1388
1389
1390 mylegs.append((loop_base_objects.DGLoopLeg({'id':leg_id,
1391 'number':number,
1392 'state':state,
1393 'from_group':True,
1394 'depth': depth,
1395 'loop_line': loopline}),
1396 vert_id))
1397 return mylegs
1398
1400 """Allow for selection of vertex ids."""
1401
1402 looplegs=[leg for leg in legs if leg['loop_line']]
1403 nonlooplegs=[leg for leg in legs if not leg['loop_line']]
1404
1405
1406 model=self['process']['model']
1407 exlegs=[leg for leg in looplegs if leg['depth']==0]
1408 if(len(exlegs)==2):
1409 if(any([part['mass'].lower()=='zero' for pdg,part in \
1410 model.get('particle_dict').items() if pdg==abs(exlegs[0]['id'])])):
1411 return []
1412
1413
1414
1415
1416 if(len(legs)==3 and len(looplegs)==2):
1417 depths=(looplegs[0]['depth'],looplegs[1]['depth'])
1418 if (0 in depths) and (-1 not in depths) and depths!=(0,0):
1419 return []
1420
1421 return vert_ids
1422
1423
1424
1426 """ Filters the diagrams according to the constraints on the squared
1427 orders in argument and wether the process has a born or not. """
1428
1429 diagRef=base_objects.DiagramList()
1430 AllLoopDiagrams=base_objects.DiagramList(self['loop_diagrams']+\
1431 self['loop_UVCT_diagrams'])
1432
1433 AllBornDiagrams=base_objects.DiagramList(self['born_diagrams'])
1434 if self['process']['has_born']:
1435 diagRef=AllBornDiagrams
1436 else:
1437 diagRef=AllLoopDiagrams
1438
1439 sqorders_types=copy.copy(self['process'].get('sqorders_types'))
1440
1441
1442
1443 if 'WEIGHTED' not in sqorders_types:
1444 sqorders_types['WEIGHTED']='<='
1445
1446 if len(diagRef)==0:
1447
1448
1449
1450
1451
1452 AllLoopDiagrams = base_objects.DiagramList()
1453
1454
1455
1456 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(diagRef,
1457 sq_order_constrains, sqorders_types)
1458
1459 if self['process']['has_born']:
1460
1461 AllBornDiagrams = AllBornDiagrams.apply_positive_sq_orders(
1462 AllLoopDiagrams+AllBornDiagrams, sq_order_constrains, sqorders_types)
1463
1464
1465 neg_orders = [(order, value) for order, value in \
1466 sq_order_constrains.items() if value<0]
1467 if len(neg_orders)==1:
1468 neg_order, neg_value = neg_orders[0]
1469
1470
1471 if self['process']['has_born']:
1472 AllBornDiagrams, target_order =\
1473 AllBornDiagrams.apply_negative_sq_order(
1474 base_objects.DiagramList(AllLoopDiagrams+AllBornDiagrams),
1475 neg_order,neg_value,sqorders_types[neg_order])
1476
1477
1478 AllLoopDiagrams = AllLoopDiagrams.apply_positive_sq_orders(
1479 diagRef,{neg_order:target_order},
1480 {neg_order:sqorders_types[neg_order]})
1481
1482
1483
1484 else:
1485 AllLoopDiagrams, target_order = \
1486 AllLoopDiagrams.apply_negative_sq_order(
1487 diagRef,neg_order,neg_value,sqorders_types[neg_order])
1488
1489
1490
1491
1492
1493 self['process']['squared_orders'][neg_order]=target_order
1494 user_squared_orders[neg_order]=target_order
1495
1496 elif len(neg_orders)>1:
1497 raise MadGraph5Error('At most one negative squared order constraint'+\
1498 ' can be specified, not %s.'%str(neg_orders))
1499
1500 if self['process']['has_born']:
1501 self['born_diagrams'] = AllBornDiagrams
1502 self['loop_diagrams']=[diag for diag in AllLoopDiagrams if not \
1503 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1504 self['loop_UVCT_diagrams']=[diag for diag in AllLoopDiagrams if \
1505 isinstance(diag,loop_base_objects.LoopUVCTDiagram)]
1506
1508 """ This is a helper function for order_diagrams_according_to_split_orders
1509 and intended to be used from LoopHelasAmplitude only"""
1510
1511
1512
1513 diag_by_so = {}
1514
1515 for diag in diag_set:
1516 so_key = tuple([diag.get_order(order) for order in split_orders])
1517 try:
1518 diag_by_so[so_key].append(diag)
1519 except KeyError:
1520 diag_by_so[so_key]=base_objects.DiagramList([diag,])
1521
1522 so_keys = diag_by_so.keys()
1523
1524
1525 order_hierarchy = self.get('process').get('model').get('order_hierarchy')
1526 order_weights = copy.copy(order_hierarchy)
1527 for so in split_orders:
1528 if so not in order_hierarchy.keys():
1529 order_weights[so]=0
1530
1531
1532
1533
1534 so_keys = sorted(so_keys, key = lambda elem: (sum([power*order_weights[\
1535 split_orders[i]] for i,power in enumerate(elem)])))
1536
1537
1538 diag_set[:] = []
1539 for so_key in so_keys:
1540 diag_set.extend(diag_by_so[so_key])
1541
1542
1544 """ Reorder the loop and Born diagrams (if any) in group of diagrams
1545 sharing the same coupling orders are put together and these groups are
1546 order in decreasing WEIGHTED orders.
1547 Notice that this function is only called for now by the
1548 LoopHelasMatrixElement instances at the output stage.
1549 """
1550
1551
1552
1553 if len(split_orders)==0:
1554 return
1555
1556 self.order_diagram_set(self['born_diagrams'], split_orders)
1557 self.order_diagram_set(self['loop_diagrams'], split_orders)
1558 self.order_diagram_set(self['loop_UVCT_diagrams'], split_orders)
1559
1564 """LoopMultiProcess: MultiProcess with loop features.
1565 """
1566
1567 @classmethod
1569 """ Return the correct amplitude type according to the characteristics
1570 of the process proc """
1571 return LoopAmplitude({"process": proc})
1572