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