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