ATLAS Offline Software
Loading...
Searching...
No Matches
Herwig7Control.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2# \file Herwig7Control.py
3# \brief Main python interface for %Herwig7 for preparing the event generation
4# \author Daniel Rauch (daniel.rauch@desy.de)
5# \author Lukas Kretschmann (lukas.kretschmann@cern.ch)
6#
7# This part of the interface provides functionality for running all the tasks
8# necessary to initialize and prepare the event generation.
9# More concretely, it handles the read or alternatively the build/integrate/
10# mergegrids steps in order to produce the Herwig runfile and all other
11# ingredients for a run, possibly also creating a gridpack.
12# The event generation itself starting from reading the runfile is handled
13# in Herwig7_i/Herwig7.h and src/Herwig7.cxx.
14
15import os, shutil, subprocess, sys
16
17from . import Herwig7Utils as hw7Utils
18from . import Herwig7JOChecker as JOChecker
19from . import Herwig7ConfigDecoder as ConfigDecoder
20
21from AthenaCommon import Logging
22athMsgLog = Logging.logging.getLogger('Herwig7Control')
23
24
25# \brief Get path to the `share/Herwig` folder
26#
27# Try to get it from the `InstallArea` first.
28# If this fails fall back to `$HERWIG7_PATH/share/Herwig`
29#
31
32 cmt_paths = os.environ.get("CMAKE_PREFIX_PATH")
33 cmt_config = os.environ.get("BINARY_TAG")
34
35 # trying to get it from the `InstallArea`
36 for path in cmt_paths.split(':'):
37 path = os.path.join(path, "InstallArea", cmt_config, "share")
38 try:
39 filelist = os.listdir(path)
40 except Exception:
41 filelist = []
42 if "HerwigDefaults.rpo" in filelist: return(path)
43
44 # falling back to `$HERWIG7_PATH`
45 path = os.path.join(os.environ['HERWIG7_PATH'], 'share/Herwig')
46 if os.path.isfile(os.path.join(path, 'HerwigDefaults.rpo')):
47 return(path)
48
49 # raise exception if none of the two methods work out
50 raise RuntimeError(hw7Utils.ansi_format_error('Could not find a valid share/Herwig folder'))
51
52
53# proper handling with path set in External/Herwig7/cmt/requirements
54herwig7_path = os.environ['HERWIG7_PATH']
55herwig7_bin_path = os.path.join(herwig7_path, 'bin')
56herwig7_share_path = get_share_path()
57
58herwig7_binary = os.path.join(herwig7_bin_path, 'Herwig')
59
60
61# Do the read/run sequence.
62#
63# This function should provide the read and run step in one go
64def run(gen_config):
65
66 # perform the read step
67 do_read(gen_config)
68
69 # start the event generation
70 do_run(gen_config, cleanup_herwig_scratch=False)
71
72
73# Do the build, integrate, mergegrids and run step in one go
74# without creating a gridpack
75#
76# \param[in] cleanup_herwig_scratch Remove `Herwig-scratch` or 'Herwig-cache' folder after event generation to save disk space
77def matchbox_run(gen_config, integration_jobs, cleanup_herwig_scratch):
78
79 # perform build/integrate/mergegrids sequence
80 do_build_integrate_mergegrids(gen_config, integration_jobs)
81
82 # start the event generation
83 do_run(gen_config, cleanup_herwig_scratch)
84
85
86# Either do the build, integrate and mergegrids steps and create a gridpack
87# or extract it and generate events from it
88#
89# \param[in] cleanup_herwig_scratch Remove `Herwig-scratch` or 'Herwig-cache' folder after event generation to save disk space
90def matchbox_run_gridpack(gen_config, integration_jobs, gridpack_name, cleanup_herwig_scratch, integrate):
91
92 # print start banner including version numbers
93 log(message=start_banner())
94
95 if not gridpack_name or integrate:
96
97 # create infile from jobOption commands
98 write_infile(gen_config)
99
100 # do build/integrate/mergegrids sequence
101 xsec, err = do_build_integrate_mergegrids(gen_config, integration_jobs)
102
103 # compress infile, runfile and process folder to gridpack tarball
104 do_compress_gridpack(gen_config.run_name, gridpack_name)
105
106 # display banner and exit
107 log(message=exit_banner(gridpack_name, xsec, err))
108 sys.exit(0)
109
110 else:
111
112 # unpack the gridpack
113 DSIS_dir = gen_config.runArgs.jobConfig[0]+"/"
114 do_uncompress_gridpack(DSIS_dir+gridpack_name)
115 athMsgLog.info("Finished unpacking the gridpack")
116
117 # start the event generation
118 do_run(gen_config, cleanup_herwig_scratch)
119
120
121
122def do_step(step, command, logfile_name=None):
123
124 athMsgLog.info(hw7Utils.ansi_format_info("Starting Herwig7 '{}' step with command '{}'".format(step, ' '.join(command))))
125
126 logfile = open(logfile_name, 'w') if logfile_name else None
127 do = subprocess.Popen(command, stdout=logfile, stderr=logfile)
128 do.wait()
129 if not do.returncode == 0:
130 raise RuntimeError(hw7Utils.ansi_format_error("Some error occured during the '{}' step.".format(step)))
131
132 if logfile:
133 athMsgLog.info("Content of %s log file '%s':", step, logfile_name)
134 athMsgLog.info("")
135 with open(logfile_name, 'r') as logfile:
136 for line in logfile:
137 athMsgLog.info(' %s', line.rstrip('\n'))
138 athMsgLog.info("")
139
140
142 athMsgLog.info(hw7Utils.ansi_format_info("Doing abort"))
143 sys.exit(0)
144
145
146def render_infile(gen_config):
147 """
148 Render the full Herwig infile as a single string.
149 This is needed to avoid writing a file to disk (old workflow)
150 so that we avoid side effects in the CA fragments.
151 """
152
153 gen_config.default_commands.lock()
154 gen_config.commands.lock()
155
156 commands = \
157 gen_config.global_pre_commands().splitlines() \
158 + gen_config.local_pre_commands().splitlines() \
159 + ["",
160 "## ================",
161 "## Default Commands",
162 "## ================"] \
163 + str(gen_config.default_commands.commands).splitlines() \
164 + ["",
165 "## ========================",
166 "## Commands from jobOptions",
167 "## ========================"] \
168 + str(gen_config.commands.commands).splitlines() \
169 + gen_config.local_post_commands().splitlines()
170
171 return('\n'.join(commands) + '\n')
172
173
175 runfile_name,
176 random_seed=None,
177 me_pdf_name=None,
178 mpi_pdf_name=None,
179 cleanup_herwig_scratch=None,
180 run_settings=None,
181 decode_runfile=False):
182 """Apply the Herwig7 algorithm settings"""
183
184 alg.RunFile = runfile_name
185
186 # Pass CA-supplied run settings to the C++ algorithm. This is materialised
187 # as a Herwig infile only during run time.
188 if run_settings is not None:
189 alg.RunSettings = run_settings
190 alg.Repository = os.path.join(herwig7_share_path, 'HerwigDefaults.rpo')
191
192 if decode_runfile:
193 ConfigDecoder.DecodeRunCard(input_file=alg.RunFile)
194
195 # Overwrite athena's seed for the random number generator.
196 if random_seed is None:
197 alg.UseRandomSeedFromGeneratetf = False
198 else:
199 alg.UseRandomSeedFromGeneratetf = True
200 alg.RandomSeedFromGeneratetf = random_seed
201
202 # Set matrix element PDF name in the Herwig7 C++ class.
203 if me_pdf_name is not None:
204 alg.PDFNameME = me_pdf_name
205
206 # Set underlying event PDF name in the Herwig7 C++ class.
207 if mpi_pdf_name is not None:
208 alg.PDFNameMPI = mpi_pdf_name
209
210 # Delete Herwig-scratch folder after finishing the event generation.
211 if cleanup_herwig_scratch is not None:
212 alg.CleanupHerwigScratch = cleanup_herwig_scratch
213
214
215# Configure the legacy Herwig7 algorithm from a gen_config object
217 runfile_name,
218 cleanup_herwig_scratch=None,
219 decode_runfile=False):
220
222 gen_config.genSeq.Herwig7,
223 runfile_name,
224 gen_config.runArgs.randomSeed,
225 gen_config.me_pdf_name,
226 gen_config.mpi_pdf_name,
227 cleanup_herwig_scratch=cleanup_herwig_scratch,
228 decode_runfile=decode_runfile,
229 )
230
231
232# Do the read step
233def do_read(gen_config):
234
235 # print start banner including version numbers
236 log(message=start_banner())
237
238 # create infile from JobOption object
239 write_infile(gen_config)
240
241 # copy HerwigDefaults.rpo to the current working directory
243
244 # call Herwig7 binary to do the read step
245 share_path = get_share_path()
246 do_step('read', [herwig7_binary, 'read', get_infile_name(gen_config.run_name), '-I', share_path])
247
248# Do the read step and re-use an already existing infile
250
251 # print start banner including version numbers
252 log(message=start_banner())
253
254 # copy HerwigDefaults.rpo to the current working directory
256
257 # call Herwig7 binary to do the read step
258 share_path = get_share_path()
259 do_step('read', [herwig7_binary, 'read', gen_config.infile_name, '-I', share_path])
260
261
262# Do the build step
263def do_build(gen_config, integration_jobs):
264
265 # print start banner including version numbers
266 log(message=start_banner())
267
268 # create infile from JobOption object
269 write_infile(gen_config)
270
271 # copy HerwigDefaults.rpo to the current working directory
273
274 # call the Herwig7 binary to do the build step
275 share_path = get_share_path()
276 do_step('build', [herwig7_binary, 'build', get_infile_name(gen_config.run_name), '-I', share_path, '-y '+str(integration_jobs)])
277
278
279# Do the integrate step for one specific integration job
280# \todo provide info about the range
281def do_integrate(run_name, integration_job):
282
283 runfile_name = get_runfile_name(run_name)
284
285 integrate_log = run_name+'.integrate'+str(integration_job)+'.log'
286 integrate_command = [herwig7_binary,'integrate',runfile_name,'--jobid='+str(integration_job)]
287
288 do_step('integrate', integrate_command, integrate_log)
289
290
291# This function provides the mergegrids step
292def do_mergegrids(run_name, integration_jobs):
293
294 runfile_name = get_runfile_name(run_name)
295 mergegrids_command = [herwig7_binary, 'mergegrids', runfile_name]
296
297 do_step('mergegrids', mergegrids_command)
298
299 # calculate the cross section from the integration logfiles and possibly warn about low accuracy
300 xsec, err = hw7Utils.get_cross_section(run_name, integration_jobs)
301
302 return(xsec, err)
303
304
305# Subsequent build, integrate and mergegrid steps
306def do_build_integrate_mergegrids(gen_config, integration_jobs):
307
308 # run build step
309 do_build(gen_config, integration_jobs)
310
311 # run integration jobs in parallel subprocesses
312 runfile_name = get_runfile_name(gen_config.run_name)
313 athMsgLog.info(hw7Utils.ansi_format_info('Starting integration with {} jobs'.format(integration_jobs)))
314
315 integration_procs = []
316 for integration_job in range(integration_jobs):
317 integrate_log = gen_config.run_name+'.integrate'+str(integration_job)+'.log'
318 integrate_command = [herwig7_binary,'integrate',runfile_name,'--jobid='+str(integration_job)]
319 integration_procs.append(hw7Utils.Process(integration_job, integrate_command, integrate_log))
320
321 integration_handler = hw7Utils.ProcessHandler(integration_procs, athMsgLog)
322 if not integration_handler.success():
323 raise RuntimeError(hw7Utils.ansi_format_error('Not all of the integration jobs finished successfully'))
324
325 athMsgLog.info(hw7Utils.ansi_format_ok('All integration jobs finished successfully'))
326
327 # combine the different integration grids
328 xsec, err = do_mergegrids(gen_config.run_name, integration_jobs)
329
330 return(xsec, err)
331
332
333def do_compress_gridpack(run_name, gridpack_name):
334
335 if not (gridpack_name.endswith('.tar.gz') or gridpack_name.endswith('.tgz')): gridpack_name += '.tar.gz'
336 infile_name = get_infile_name(run_name)
337 runfile_name = get_runfile_name(run_name)
338 version = herwig_version()
339 athMsgLog.debug("Scratch area, this is Herwig version '{}'".format(version))
340 if "7.1" in version or "7.0" in version:
341 do_step('compress', ['tar', 'czf', gridpack_name, infile_name, runfile_name, 'Herwig-scratch'])
342 else:
343 do_step('compress', ['tar', 'czf', gridpack_name, infile_name, runfile_name, 'Herwig-cache'])
344
345
346def do_uncompress_gridpack(gridpack_name):
347
348 athMsgLog.info("unpacking gridpack '%s'", gridpack_name)
349 do_step('uncompress', ['tar', 'xzf', gridpack_name])
350
351
352# \param[in] cleanup_herwig_scratch Remove `Herwig-scratch` folder after event generation to save disk space
353def do_run(gen_config, cleanup_herwig_scratch=True):
354
355 # this is necessary to make Herwig aware of the name of the run file
356 runfile_name = get_runfile_name(gen_config.run_name)
357 gen_config.genSeq.Herwig7.RunFile = runfile_name
358
359 # check the options in the .in file
360 JOChecker.check_file()
361
363 gen_config,
364 runfile_name,
365 cleanup_herwig_scratch=cleanup_herwig_scratch,
366 decode_runfile=True,
367 )
368
369 # don't break out here so that the job options can be finished and the C++
370 # part of the interface can take over and generate the events
371 athMsgLog.info(hw7Utils.ansi_format_info("Returning to the job options and starting the event generation afterwards"))
372
373
374# Do the run step and re-use an already existing runfile
376
377 # this is necessary to make Herwig aware of the name of the run file
379 gen_config,
380 gen_config.runfile_name,
381 )
382
383 # don't break out here so that the job options can be finished and the C++
384 # part of the interface can take over and generate the events
385 athMsgLog.info(hw7Utils.ansi_format_info("Returning to the job options and starting the event generation afterwards"))
386
387
388# utility functions -----------------------------------------------------------
389
390
392
393 versions = get_software_versions()
394 return(' '.join(versions[0].split()[1:]))
395
396
398
399 return("H"+herwig_version()+"-Default")
400
401
403
404 versions = get_software_versions()
405 return(' '.join(versions[1].split()[1:]))
406
408
409 herwig_version_number = herwig_version()
410 thepeg_version_number = thepeg_version()
411 herwig_version_space = ' '.join(['' for i in range(14-len(herwig_version_number))])
412 thepeg_version_space = ' '.join(['' for i in range(14-len(thepeg_version_number))])
413
414 banner = ''
415 banner += "#####################################\n"
416 banner += "## {} ##\n".format(hw7Utils.ansi_format_ok("---------------------------"))
417 banner += "## {} ##\n".format(hw7Utils.ansi_format_ok("Starting HERWIG 7 in ATHENA"))
418 banner += "## {} ##\n".format(hw7Utils.ansi_format_ok("---------------------------"))
419 banner += "## ##\n"
420 banner += "## with software versions: ##\n"
421 banner += "## - Herwig7: {}{} ##\n".format(herwig_version_number, herwig_version_space)
422 banner += "## - ThePEG: {}{} ##\n".format(thepeg_version_number, thepeg_version_space)
423 banner += "## ##\n"
424 banner += "#####################################\n"
425 return(banner)
426
427
429
430 return(subprocess.check_output([herwig7_binary,'--version'], text=True).splitlines())
431
432
433def get_infile_name(run_name="Herwig-Matchbox"):
434
435 return('{}.in'.format(run_name))
436
437
438def get_setupfile_name(run_name="Herwig-Matchbox"):
439
440 return('{}.setupfile.in'.format(run_name))
441
442
443def get_runfile_name(run_name="Herwig-Matchbox"):
444
445 return('{}.run'.format(run_name) if not run_name.endswith('.run') else run_name)
446
447
448def write_infile(gen_config, print_infile=True):
449
450 infile_name = get_infile_name(gen_config.run_name)
451 if print_infile: athMsgLog.info("")
452 athMsgLog.info(hw7Utils.ansi_format_info("Writing infile '{}'".format(infile_name)))
453 infile_text = render_infile(gen_config)
454 commands = infile_text.splitlines()
455 try:
456 with open(infile_name, 'w') as infile:
457 for command in commands:
458 infile.write(command+'\n')
459 except Exception:
460 raise RuntimeError('Could not write Herwig/Matchbox infile')
461
462 if print_infile:
463 athMsgLog.info("")
464 for command in commands:
465 athMsgLog.info(' %s', command)
466 athMsgLog.info("")
467
468
469def write_setupfile(run_name, commands, print_setupfile=True):
470
471 setupfile_name = get_setupfile_name(run_name)
472
473 if len(commands) > 0:
474 if print_setupfile: athMsgLog.info("")
475 athMsgLog.info("Writing setupfile '%s'", setupfile_name)
476 try:
477 with open(setupfile_name, 'w') as setupfile:
478 for command in commands: setupfile.write(command+'\n')
479 except Exception:
480 raise RuntimeError('Could not write Herwig/Matchbox setupfile')
481
482 if print_setupfile:
483 athMsgLog.info("")
484 for command in commands: athMsgLog.info(' %s', command)
485 athMsgLog.info("")
486
487 else:
488 athMsgLog.info("No setupfile commands given.")
489
490
491# \brief Copy default repository `HerwigDefaults.rpo` to current working directory
492#
494
495 shutil.copy(os.path.join(get_share_path(), 'HerwigDefaults.rpo'), 'HerwigDefaults.rpo')
496
497
498def log(level='info', message=''):
499
500 if level in ['info', 'warn', 'error']:
501 logger = getattr(athMsgLog, level)
502 for line in message.splitlines(): logger(line)
503 else:
504 raise ValueError("Unknown logging level'{}' specified. Possible values are 'info', 'warn' or 'error'".format(level))
505
506
507def exit_banner(gridpack, cross_section, cross_section_error):
508
509 size = hw7Utils.humanize_bytes(hw7Utils.get_size(gridpack))
510 space_size = hw7Utils.get_repeated_pattern(' ', 31-len(size))
511
512 xsec = '{:f}'.format(cross_section)
513 err = '{:f}'.format(cross_section_error)
514 rel_err = '{:.2f}'.format(cross_section_error / cross_section * 100.0)
515 space_xsec = hw7Utils.get_repeated_pattern(' ', 37-len(xsec)-len(err)-len(rel_err))
516
517 banner = ''
518 space = ' '.join(['' for i in range(70+4+1-len(gridpack))])
519 banner += "##########################################################################################\n"
520 banner += "## ------------------------------------------------------------------------------- ##\n"
521 banner += "## {} (size: {}){} ##\n".format(hw7Utils.ansi_format_ok("HERWIG 7 successfully created the gridpack"), size, space_size)
522 banner += "## ##\n"
523 banner += "## {}{} ##\n".format(hw7Utils.ansi_format_info(gridpack), space)
524 banner += "## ##\n"
525 banner += "## cross section from integration: {} +/- {} ({}%) nb {} ##\n".format(xsec, err, rel_err, space_xsec)
526
527 if cross_section_error / cross_section > hw7Utils.integration_grids_precision_threshold:
528 threshold = '{}%'.format(hw7Utils.integration_grids_precision_threshold*100.0)
529 space_threshold = hw7Utils.get_repeated_pattern(' ', 6-len(threshold))
530 banner += "## ##\n"
531 banner += "## {} ##\n".format(hw7Utils.ansi_format_warning("! WARNING: The integration grids only have a low precision (worse than {}){}!".format(threshold, space_threshold)))
532
533 banner += "## ##\n"
534 banner += "## ------------------------------------------------------------------------------- ##\n"
535 banner += "## ##\n"
536 banner += "## Please ignore the error ##\n"
537 banner += "## ##\n"
538 banner += "## No such file or directory: 'evgen.root' raised while stating file evgen.root ##\n"
539 banner += "## ##\n"
540 banner += "##########################################################################################\n"
541 return(banner)
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179
static Root::TMsgLogger logger("iLumiCalc")
configure_algorithm(alg, runfile_name, random_seed=None, me_pdf_name=None, mpi_pdf_name=None, cleanup_herwig_scratch=None, run_settings=None, decode_runfile=False)
do_mergegrids(run_name, integration_jobs)
get_infile_name(run_name="Herwig-Matchbox")
do_read_existing_infile(gen_config)
exit_banner(gridpack, cross_section, cross_section_error)
do_uncompress_gridpack(gridpack_name)
do_build(gen_config, integration_jobs)
run(gen_config)
get_setupfile_name(run_name="Herwig-Matchbox")
do_compress_gridpack(run_name, gridpack_name)
log(level='info', message='')
do_step(step, command, logfile_name=None)
do_integrate(run_name, integration_job)
do_build_integrate_mergegrids(gen_config, integration_jobs)
matchbox_run_gridpack(gen_config, integration_jobs, gridpack_name, cleanup_herwig_scratch, integrate)
matchbox_run(gen_config, integration_jobs, cleanup_herwig_scratch)
render_infile(gen_config)
write_infile(gen_config, print_infile=True)
_configure_run_algorithm(gen_config, runfile_name, cleanup_herwig_scratch=None, decode_runfile=False)
do_read(gen_config)
get_runfile_name(run_name="Herwig-Matchbox")
do_run_existing_runfile(gen_config)
do_run(gen_config, cleanup_herwig_scratch=True)
write_setupfile(run_name, commands, print_setupfile=True)