1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """A user friendly command line interface to access all MadGraph5_aMC@NLO features.
16 Uses the cmd package for command interpretation and tab completion.
17 """
18
19 import os
20 import shutil
21 import time
22 import logging
23 import re
24
25 import madgraph
26 from madgraph import MG4DIR, MG5DIR, MadGraph5Error
27 import madgraph.interface.madgraph_interface as mg_interface
28 import madgraph.interface.launch_ext_program as launch_ext
29 import madgraph.core.base_objects as base_objects
30 import madgraph.core.diagram_generation as diagram_generation
31 import madgraph.loop.loop_diagram_generation as loop_diagram_generation
32 import madgraph.loop.loop_base_objects as loop_base_objects
33 import madgraph.loop.loop_helas_objects as loop_helas_objects
34 import madgraph.core.helas_objects as helas_objects
35 import madgraph.iolibs.export_v4 as export_v4
36 import madgraph.iolibs.helas_call_writers as helas_call_writers
37 import madgraph.iolibs.file_writers as writers
38 import madgraph.interface.launch_ext_program as launch_ext
39 import madgraph.various.misc as misc
40 import madgraph.fks.fks_base as fks_base
41 import aloha
42
43
44 logger = logging.getLogger('cmdprint')
45
46
47 pjoin = os.path.join
48
49 -class CheckLoop(mg_interface.CheckValidForCmd):
50
52 """ Check the arguments of the display diagrams command in the context
53 of the Loop interface."""
54
55 mg_interface.MadGraphCmd.check_display(self,args)
56
57 if all([not amp['process']['has_born'] for amp in self._curr_amps]):
58 if args[0]=='diagrams' and len(args)>=2 and args[1]=='born':
59 raise self.InvalidCmd("Processes generated do not have born diagrams.")
60
61 if args[0]=='diagrams' and len(args)>=3 and args[1] not in ['born','loop']:
62 raise self.InvalidCmd("Can only display born or loop diagrams, not %s."%args[1])
63
71
73 """ If no model is defined yet, make sure to load the right loop one """
74
75 if not self._curr_model:
76 pert_coupl_finder = re.compile(r"^(?P<proc>.+)\s*\[\s*((?P<option>\w+)"+
77 r"\s*\=)?\s*(?P<pertOrders>(\w+\s*)*)\s*\]\s*(?P<rest>.*)$")
78 pert_coupl = pert_coupl_finder.match(' '.join(args))
79 model_name = 'loop_sm'
80 if pert_coupl:
81 pert_coupls = pert_coupl.group("pertOrders")
82 if "QED" in pert_coupls:
83 model_name = 'loop_qcd_qed_sm'
84 self.do_import('model %s'%model_name)
85
86 mg_interface.MadGraphCmd.check_add(self,args)
87
96
97
99 """ Further check that only valid options are given to the MadLoop
100 default launcher."""
101
102 mg_interface.MadGraphCmd.check_launch(self,args,options)
103 if int(options.cluster) != 0 :
104 return self.InvalidCmd, 'MadLoop standalone runs cannot be '+\
105 'performed on a cluster.'
106
107 if int(options.multicore) != 0 :
108 logger.warning('MadLoop standalone can only run on a single core,'+\
109 ' so the -m option is ignored.')
110 options.multicore = '0'
111
112 if options.laststep != '' :
113 logger.warning('The -laststep option is only used for Madevent.'+\
114 'Ignoring this option')
115 options.multicore = ''
116
117 if options.interactive :
118 logger.warning('No interactive mode for MadLoop standalone runs.')
119 options.interactive = False
120
121 -class CheckLoopWeb(mg_interface.CheckValidForCmdWeb, CheckLoop):
123
125
127 "Complete the display command in the context of the Loop interface"
128
129 args = self.split_arg(line[0:begidx])
130
131 if len(args) == 2 and args[1] == 'diagrams':
132 return self.list_completion(text, ['born', 'loop'])
133 else:
134 return mg_interface.MadGraphCmd.complete_display(self, text, line,
135 begidx, endidx)
136
138
140 mg_interface.MadGraphCmd.help_display(self)
141 logger.info(" In ML5, after display diagrams, the user can add the option")
142 logger.info(" \"born\" or \"loop\" to display only the corresponding diagrams.")
143
144
146 """ An additional layer between MadGraphInterface and LoopInterface as well
147 as aMCatNLO interface, to put the common feature of these two here."""
148
150 """ Gives an integer more or less representing the difficulty of the process.
151 For now it is very basic and such that "difficult" processes start at
152 a value of about 35."""
153
154 def pdg_difficulty(pdg):
155 """ Gives a score from the pdg of a leg to state how it increases the
156 difficulty of the process """
157
158
159 part=self._curr_model.get_particle(pdg)
160 if abs(part.get_color())==1:
161 return 2
162 elif abs(part.get_color())==3:
163 return 3
164 elif abs(part.get_color())==6:
165 return 4
166 elif abs(part.get_color())==8:
167 return 6
168
169 score = 0
170 for leg in proc.get('legs'):
171 if isinstance(leg,base_objects.MultiLeg):
172 score += max([pdg_difficulty(id) for id in leg['ids']])
173
174 if len(leg['ids'])>1:
175 score += 1
176 else:
177 score += pdg_difficulty(leg.get('id'))
178
179
180 if proc['NLO_mode']=='virt':
181 score = score - 6
182
183 if proc['NLO_mode']=='real':
184 score = score - 6
185
186 if proc['NLO_mode']=='tree':
187 return 0
188 return score
189
190 - def do_set(self, line, log=True):
191 """Set the loop optimized output while correctly switching to the
192 Feynman gauge if necessary.
193 """
194
195 mg_interface.MadGraphCmd.do_set(self,line,log)
196
197 args = self.split_arg(line)
198 self.check_set(args)
199
200 if args[0] == 'gauge' and args[1] == 'unitary' and \
201 not self.options['gauge']=='unitary' and \
202 isinstance(self._curr_model,loop_base_objects.LoopModel) and \
203 not self._curr_model['perturbation_couplings'] in [[],['QCD']]:
204 if log: logger.warning('You will only be able to do tree level and QCD'+\
205 ' corrections in the unitary gauge.')
206
208 """ Check that the process or processDefinition describes a process that
209 ML5 can handle. Mode specifies who called the function,
210 typically ML5, ML5_check or aMCatNLO. This allows to relieve some limitation
211 depending on the functionality."""
212
213 tool = 'MadLoop' if mode.startswith('ML5') else 'aMC@NLO'
214
215
216 difficulty_threshold = 100
217
218 if not proc:
219 raise self.InvalidCmd("Empty or wrong format process, please try again.")
220
221
222
223 if self._curr_amps and self._curr_amps[0].get_ninitial() != \
224 proc.get_ninitial():
225 raise self.InvalidCmd("Can not mix processes with different number of initial states.")
226
227
228
229
230
231
232
233
234
235 if isinstance(proc, base_objects.ProcessDefinition) and mode=='ML5':
236 if proc.has_multiparticle_label():
237 raise self.InvalidCmd(
238 "When running ML5 standalone, multiparticle labels cannot be"+\
239 " employed.")
240
241 if proc['decay_chains']:
242 raise self.InvalidCmd(
243 "ML5 cannot yet decay a core process including loop corrections.")
244
245 if proc.are_decays_perturbed():
246 raise self.InvalidCmd(
247 "The processes defining the decay of the core process cannot"+\
248 " include loop corrections.")
249
250 if not proc['perturbation_couplings'] and mode.startswith('ML5'):
251 raise self.InvalidCmd(
252 "Please perform tree-level generations within default MG5 interface.")
253 if not 'real':
254 if not isinstance(self._curr_model,loop_base_objects.LoopModel) or \
255 not proc['perturbation_couplings']:
256 raise self.InvalidCmd(
257 "The current model does not allow for loop computations.")
258
259 miss_order = [ p_order for p_order in proc['perturbation_couplings'] \
260 if p_order not in self._curr_model.get('perturbation_couplings')]
261 if len(miss_order)>0 and not 'real' in mode:
262 raise self.InvalidCmd(
263 "Perturbation orders %s not among"%str(miss_order) + \
264 " the perturbation orders allowed for by the loop model.")
265
266 if proc['perturbation_couplings'] not in [[],['QCD']]:
267 raise self.InvalidCmd(
268 "The process perturbation coupling orders %s are beyond "+\
269 "tree level or only QCD corrections. MadLoop can only work"+\
270 " in the Feynman gauge for these. Please set the gauge to "+\
271 " Feynman and try again.")
272
273 proc_diff = self.rate_proc_difficulty(proc, mode)
274 logger.debug('Process difficulty estimation: %d'%proc_diff)
275 if proc_diff >= difficulty_threshold:
276 msg = """
277 The %s you attempt to generate appears to be of challenging difficulty, but it will be tried anyway. If you have successfully studied it with MadGraph5_aMC@NLO, please report it.
278 """
279 logger.warning(msg%proc.nice_string().replace('Process:','process'))
280
281 - def validate_model(self, loop_type='virtual',coupling_type=['QCD'], stop=True):
282 """ Upgrade the model sm to loop_sm if needed """
283
284
285
286 if isinstance(coupling_type,str):
287 coupling_type = [coupling_type,]
288
289 if not isinstance(self._curr_model,loop_base_objects.LoopModel) or \
290 self._curr_model['perturbation_couplings']==[] or \
291 any((coupl not in self._curr_model['perturbation_couplings']) \
292 for coupl in coupling_type):
293 if loop_type.startswith('real') or loop_type == 'LOonly':
294 if loop_type == 'real':
295 logger.info(\
296 "Beware that real corrections are generated from a tree-level model.")
297 if loop_type == 'real_init' and \
298 self._curr_model.get('name').split('-')[0]!='sm':
299 logger.info(\
300 "You are entering aMC@NLO with a model which does not "+\
301 " support loop corrections.")
302 else:
303 logger.info(\
304 "The current model %s does not allow to generate"%self._curr_model.get('name')+
305 " loop corrections of type %s."%str(coupling_type))
306 model_path = self._curr_model.get('modelpath')
307 model_name = self._curr_model.get('name')
308 if model_name.split('-')[0]=='loop_sm':
309 model_name = model_name[5:]
310 if model_name.split('-')[0]=='sm':
311
312 if not self.options['gauge']=='Feynman' and 'QED' in coupling_type:
313 logger.info('Switch to Feynman gauge because '+\
314 'model loop_qcd_qed_sm is restricted only to Feynman gauge.')
315 self._curr_model = None
316 mg_interface.MadGraphCmd.do_set(self,'gauge Feynman')
317 if coupling_type == ['QCD',]:
318 add_on = ''
319 elif coupling_type in [['QED'],['QCD','QED']]:
320 add_on = 'qcd_qed_'
321 else:
322 raise MadGraph5Error(
323 "The pertubation coupling cannot be '%s'"\
324 %str(coupling_type)+" in SM loop processes")
325
326 logger.info("MG5_aMC now loads 'loop_%s%s'."%(add_on,model_name))
327
328
329 self.history.move_to_last('generate')
330 last_command = self.history[-1]
331 self.exec_cmd(" import model loop_%s%s" % (add_on,model_name), precmd=True)
332 self.history.append(last_command)
333 elif stop:
334 raise self.InvalidCmd(
335 "The model %s cannot handle loop processes"%model_name)
336
337 if loop_type and not loop_type.startswith('real') and \
338 not self.options['gauge']=='Feynman' and \
339 not self._curr_model['perturbation_couplings'] in [[],['QCD']]:
340 if 1 in self._curr_model.get('gauge'):
341 logger.info("Setting gauge to Feynman in order to process all"+\
342 " possible loop computations available in the model.")
343 mg_interface.MadGraphCmd.do_set(self,'gauge Feynman')
344 else:
345 logger.warning("You will only be able to do tree level and QCD"+\
346 " corrections with this model because it does not support Feynman gauge.")
347
348 -class LoopInterface(CheckLoop, CompleteLoop, HelpLoop, CommonLoopInterface):
349
350 supported_ML_format = ['standalone', 'standalone_rw', 'matchbox']
351
352 - def __init__(self, mgme_dir = '', *completekey, **stdin):
353 """ Special init tasks for the Loop Interface """
354
355 mg_interface.MadGraphCmd.__init__(self, mgme_dir = '', *completekey, **stdin)
356 self.setup()
357
359 """ Special tasks when switching to this interface """
360
361
362
363
364
365
366 self.history.clean(remove_bef_last='import',
367 to_keep=['set','load','import', 'define'])
368
369 self._done_export=False
370 self._curr_amps = diagram_generation.AmplitudeList()
371 self._curr_matrix_elements = helas_objects.HelasMultiProcess()
372 self._v4_export_formats = []
373 self._export_formats = [ 'matrix', 'standalone' ]
374 self._nlo_modes_for_completion = ['virt']
375 self.validate_model()
376
377
378
379 self._cuttools_dir=str(os.path.join(self._mgme_dir,'vendor','CutTools'))
380 if not os.path.isdir(os.path.join(self._cuttools_dir, 'src','cts')):
381 logger.warning(('Warning: Directory %s is not a valid CutTools directory.'+\
382 'Using default CutTools instead.') % \
383 self._cuttools_dir)
384 self._cuttools_dir=str(os.path.join(self._mgme_dir,'vendor','CutTools'))
385
386 self._iregi_dir=str(os.path.join(self._mgme_dir,'vendor','IREGI','src'))
387 if not os.path.isdir(self._iregi_dir):
388 logger.warning(('Warning: Directory %s is not a valid IREGI directory.'+\
389 'Using default IREGI instead.')%\
390 self._iregi_dir)
391 self._iregi_dir=str(os.path.join(self._mgme_dir,'vendor','IREGI','src'))
392
408
410 """Main commands:Initialize a new Template or reinitialize one"""
411
412 args = self.split_arg(line)
413
414 self.check_output(args)
415
416 noclean = '-noclean' in args
417 force = '-f' in args
418 nojpeg = '-nojpeg' in args
419 main_file_name = ""
420 try:
421 main_file_name = args[args.index('-name') + 1]
422 except Exception:
423 pass
424
425
426
427 aloha_original_quad_mode = aloha.mp_precision
428 aloha.mp_precision = True
429
430 if self._export_format not in self.supported_ML_format:
431 raise self.InvalidCmd('ML5 only support "%s" as export format.' % \
432 ''.join(self.supported_ML_format))
433
434 if not os.path.isdir(self._export_dir) and self._export_format in ['matrix']:
435 raise self.InvalidCmd('Specified export directory %s does not exist.'\
436 %str(self._export_dir))
437
438 if not force and not noclean and os.path.isdir(self._export_dir)\
439 and self._export_format.startswith('standalone'):
440
441 logger.info('INFO: directory %s already exists.' % self._export_dir)
442 logger.info('If you continue this directory will be cleaned')
443 answer = self.ask('Do you want to continue?', 'y', ['y','n'])
444 if answer != 'y':
445 raise self.InvalidCmd('Stopped by user request')
446 else:
447 try:
448 shutil.rmtree(self._export_dir)
449 except OSError:
450 raise self.InvalidCmd('Could not remove directory %s.'\
451 %str(self._export_dir))
452
453 if self._export_format.startswith('standalone'):
454 output_type = 'madloop'
455 elif self._export_format == 'matchbox':
456 output_type = 'madloop_matchbox'
457
458 self._curr_exporter = export_v4.ExportV4Factory(self, \
459 noclean, output_type=output_type, group_subprocesses=False)
460
461 if self._export_format in ['standalone', 'matchbox']:
462 self._curr_exporter.copy_v4template(modelname=self._curr_model.get('name'))
463
464 if self._export_format == "standalone_rw":
465 self._export_format = "standalone"
466 self._curr_exporter.copy_v4template(modelname=self._curr_model.get('name'))
467 self._export_format = "standalone_rw"
468
469
470 self._done_export = False
471
472
473 self.ML5export(nojpeg, main_file_name)
474
475
476 self.ML5finalize(nojpeg)
477
478
479 self._done_export = (self._export_dir, self._export_format)
480
481
482 self._export_dir = None
483
484
485 aloha.mp_precision = aloha_original_quad_mode
486
487
488
489 - def ML5export(self, nojpeg = False, main_file_name = ""):
517
518
519 ndiags, cpu_time = generate_matrix_elements(self)
520
521 calls = 0
522
523 path = self._export_dir
524 if self._export_format in self.supported_ML_format:
525 path = pjoin(path, 'SubProcesses')
526
527 cpu_time1 = time.time()
528
529
530 matrix_elements = \
531 self._curr_matrix_elements.get_matrix_elements()
532
533
534 if self._export_format in self.supported_ML_format:
535 for me in matrix_elements:
536 calls = calls + \
537 self._curr_exporter.generate_subprocess_directory_v4(\
538 me, self._curr_fortran_model)
539
540
541
542
543 if self.options['loop_optimized_output'] and len(matrix_elements)>1:
544 max_lwfspins = [m.get_max_loop_particle_spin() for m in \
545 matrix_elements]
546 try:
547 max_loop_vert_ranks = [me.get_max_loop_vertex_rank() for me in \
548 matrix_elements]
549 except MadGraph5Error:
550 pass
551 else:
552 if len(set(max_lwfspins))>1 or len(set(max_loop_vert_ranks))>1:
553 self._curr_exporter.fix_coef_specs(max(max_lwfspins),\
554 max(max_loop_vert_ranks))
555 logger.warning('ML5 has just output processes which do not'+\
556 ' share the same maximum loop wavefunction size or the '+\
557 ' same maximum loop vertex rank. This is potentially '+\
558 ' dangerous. Please prefer to output them separately.')
559
560
561 if self._export_format == 'matrix':
562 for me in matrix_elements:
563 filename = pjoin(path, 'matrix_' + \
564 me.get('processes')[0].shell_string() + ".f")
565 if os.path.isfile(filename):
566 logger.warning("Overwriting existing file %s" % filename)
567 else:
568 logger.info("Creating new file %s" % filename)
569 calls = calls + self._curr_exporter.write_matrix_element_v4(\
570 writers.FortranWriter(filename),\
571 me, self._curr_fortran_model)
572
573 cpu_time2 = time.time() - cpu_time1
574
575 logger.info(("Generated helas calls for %d subprocesses " + \
576 "(%d diagrams) in %0.3f s") % \
577 (len(matrix_elements),
578 ndiags, cpu_time))
579
580 if calls:
581 if "cpu_time2" in locals():
582 logger.info("Wrote files for %d OPP calls in %0.3f s" % \
583 (calls, cpu_time2))
584 else:
585 logger.info("Wrote files for %d OPP calls" % \
586 (calls))
587
588
589
590
591 self._curr_amps = diagram_generation.AmplitudeList(\
592 [me.get('base_amplitude') for me in \
593 matrix_elements])
594
631
633 """Main commands: Check that the type of launch is fine before proceeding with the
634 mother function. """
635
636 args = self.split_arg(line)
637
638 (options, args) = mg_interface._launch_parser.parse_args(args)
639
640 self.check_launch(args, options)
641
642 if not args[0].startswith('standalone'):
643 raise self.InvalidCmd('ML5 can only launch standalone runs.')
644
645 start_cwd = os.getcwd()
646 options = options.__dict__
647
648
649 ext_program = launch_ext.MadLoopLauncher(self, args[1], \
650 options=self.options, **options)
651 ext_program.run()
652 os.chdir(start_cwd)
653
655 """Check a given process or set of processes"""
656
657 argss = self.split_arg(line, *args,**opt)
658
659 perturbation_couplings_pattern = \
660 re.compile("^(?P<proc>.+)\s*\[\s*((?P<option>\w+)\s*\=)?\s*(?P<pertOrders>(\w+\s*)*)\s*\]\s*(?P<rest>.*)$")
661 perturbation_couplings_re = perturbation_couplings_pattern.match(line)
662 perturbation_couplings=""
663 if perturbation_couplings_re:
664 perturbation_couplings = perturbation_couplings_re.group("pertOrders")
665 QED_found=re.search("QED",perturbation_couplings)
666 if QED_found:
667 self.validate_model(coupling_type='QED')
668 else:
669 self.validate_model()
670
671 param_card = self.check_check(argss)
672 reuse = argss[1]=="-reuse"
673 argss = argss[:1]+argss[2:]
674
675
676 if argss[0] in ['stability', 'profile']:
677 stab_statistics = int(argss[1])
678 argss = argss[:1]+argss[2:]
679
680 i=-1
681 while argss[i].startswith('--'):
682 i=i-1
683
684 proc = " ".join(argss[1:i+1])
685 myprocdef = self.extract_process(proc)
686 self.proc_validity(myprocdef,'ML5_check_cms' if argss[0]=='cms' else \
687 'ML5_check')
688
689 return mg_interface.MadGraphCmd.do_check(self, line, *args,**opt)
690
691 - def do_add(self, line, *args,**opt):
692 """Generate an amplitude for a given process and add to
693 existing amplitudes
694 """
695 args = self.split_arg(line)
696
697 self.check_add(args)
698 perturbation_couplings_pattern = \
699 re.compile("^(?P<proc>.+)\s*\[\s*((?P<option>\w+)\s*\=)?\s*(?P<pertOrders>(\w+\s*)*)\s*\]\s*(?P<rest>.*)$")
700 perturbation_couplings_re = perturbation_couplings_pattern.match(line)
701 perturbation_couplings=""
702 if perturbation_couplings_re:
703 perturbation_couplings = perturbation_couplings_re.group("pertOrders")
704 QED_found=re.search('QED',perturbation_couplings)
705 if QED_found:
706 self.validate_model(coupling_type='QED')
707 else:
708 self.validate_model()
709
710 if args[0] == 'process':
711
712 line = ' '.join(args[1:])
713
714
715 if not self._generate_info:
716 self._generate_info = line
717
718
719 self._curr_matrix_elements = helas_objects.HelasMultiProcess()
720
721
722
723 myprocdef = self.extract_process(line)
724
725
726
727
728 all_ids = [amp.get('process').get('id') for amp in self._curr_amps]
729 if myprocdef.get('id') in all_ids:
730 myprocdef.set('id',max(all_ids)+1)
731
732 self.proc_validity(myprocdef,'ML5')
733
734 cpu_time1 = time.time()
735
736
737 multiprocessclass=None
738 if myprocdef['perturbation_couplings']!=[]:
739 multiprocessclass=loop_diagram_generation.LoopMultiProcess
740 else:
741 multiprocessclass=diagram_generation.MultiProcess
742
743 myproc = multiprocessclass(myprocdef, collect_mirror_procs = False,
744 ignore_six_quark_processes = False)
745
746 for amp in myproc.get('amplitudes'):
747 if amp not in self._curr_amps:
748 self._curr_amps.append(amp)
749 else:
750 warning = "Warning: Already in processes:\n%s" % \
751 amp.nice_string_processes()
752 logger.warning(warning)
753
754
755 self._done_export = False
756
757 cpu_time2 = time.time()
758
759 ndiags = sum([len(amp.get('loop_diagrams')) for \
760 amp in myproc.get('amplitudes')])
761 logger.info("Process generated in %0.3f s" % \
762 (cpu_time2 - cpu_time1))
763
766