11import os,time,subprocess,glob,re,sys
12from AthenaCommon
import Logging
13from AthenaCommon.SystemOfUnits
import GeV
14from MadGraphControl.MadGraphUtilsHelpers
import error_check,modify_param_card
15from MadGraphControl.MadGraphParamHelpers
import do_PMG_updates
16from MadGraphControl.MadGraphSystematicsUtils
import convertSysCalcArguments,get_pdf_and_systematic_settings,parse_systematics_arguments,SYSTEMATICS_WEIGHT_INFO_ALTDYNSCALES,SYSTEMATICS_WEIGHT_INFO,write_systematics_arguments
18mglog = Logging.logging.getLogger(
'MadGraphUtils')
23MADGRAPH_GRIDPACK_LOCATION =
'madevent'
25MADGRAPH_RUN_NAME =
'run_01'
27MADGRAPH_CATCH_ERRORS =
True
29MADGRAPH_PDFSETTING =
None
35MADGRAPH_DEVICES =
None
38 def __init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False, pdf_setting=None, devices=None, catch_errors=MADGRAPH_CATCH_ERRORS):
39 """ Generate a new process in madgraph.
40 Pass a process string.
41 Optionally request JPEGs to be kept and request for PMG settings to be used in the param card
42 Return the name of the process directory.
44 self.
mglog = Logging.logging.getLogger(
'MadGraphUtils')
49 self.
pdf_setting = MADGRAPH_PDFSETTING
if pdf_setting
is None else pdf_setting
50 self.
devices = MADGRAPH_DEVICES
if devices
is None else devices
51 self.
catch_errors = MADGRAPH_CATCH_ERRORS
if catch_errors
is None else catch_errors
61 card_loc =
'proc_card_mg5.dat'
62 mglog.info(
'Writing process card to '+card_loc)
63 a_card = open( card_loc ,
'w' )
64 for l
in process.split(
'\n'):
70 if '-nojpeg' not in l
and not keepJpegs:
73 outline = outline.split(
'#')[0]+
' -nojpeg #'+outline.split(
'#')[1]
75 outline = outline +
' -nojpeg'
78 if self.
devices.lower()
in [
'madevent_simd',
'madevent_gpu']:
79 outline =
'output '+self.
devices.lower()+
' '+outline.split(
'output')[1]
80 elif self.
devices.lower() ==
'max':
81 self.
mglog.warning(
'Not fully implemented yet; setting avx')
82 outline =
'output madevent_simd '+outline.split(
'output')[1]
83 a_card.write(outline+
'\n')
86 madpath = os.environ[
'MADPATH']
93 for l
in process.split(
'\n'):
95 if 'output' not in l.split(
'#')[0].
split():
98 tmplist = l.split(
'#')[0].
split(
' -')[0]
100 if len(tmplist.split())==2:
101 process_dir = tmplist.split()[1]
103 elif len(tmplist.split())==3:
104 process_dir = tmplist.split()[2]
107 mglog.info(
'Saw that you asked for a special output directory: '+str(process_dir))
110 mglog.info(
'Started process generation at '+str(time.asctime()))
112 plugin_cmd =
'--mode='+plugin
if plugin
is not None else ''
117 self.
MADGRAPH_COMMAND_STACK += [
' '.join([python,madpath+
'/bin/mg5_aMC '+plugin_cmd+
' << EOF\n'+process+
'\nEOF\n'])]
118 generate = subprocess.Popen([python,madpath+
'/bin/mg5_aMC',plugin_cmd,card_loc],stdin=subprocess.PIPE,stderr=subprocess.PIPE
if self.
catch_errors else None)
119 (out,err) = generate.communicate()
120 error_check(err,generate.returncode)
122 mglog.info(
'Finished process generation at '+str(time.asctime()))
125 if process_dir ==
'':
126 for adir
in sorted(glob.glob( os.getcwd()+
'/*PROC*' ),reverse=
True):
127 if os.access(
'%s/SubProcesses/subproc.mg'%adir,os.R_OK):
131 mglog.warning(
'Additional possible process directory, '+adir+
' found. Had '+process_dir)
132 mglog.warning(
'Likely this is because you did not run from a clean directory, and this may cause errors later.')
134 if not os.access(
'%s/SubProcesses/subproc.mg'%process_dir,os.R_OK):
135 raise RuntimeError(
'No diagrams for this process in user-define dir='+str(process_dir))
137 raise RuntimeError(
'No diagrams for this process from list: '+str(sorted(glob.glob(os.getcwd()+
'/*PROC*'),reverse=
True)))
151 mglog.info(
'Setting default sde_strategy to old default (1)')
159 if self.
devices.lower()==
'madevent_simd':
161 elif self.
devices.lower()==
'madevent_gpu':
165 os.environ[
'ALLOW_UNSUPPORTED_COMPILER_IN_CUDA'] =
'Y'
166 elif self.
devices.lower() ==
'max':
167 self.
mglog.warning(
'Not fully implemented yet; setting avx')
178 if 'PYTHONPATH' in os.environ:
179 if not any( [(
'Generators/madgraph/models' in x)
for x
in os.environ[
'PYTHONPATH'].
split(
':') ]):
180 os.environ[
'PYTHONPATH'] +=
':/cvmfs/atlas.cern.ch/repo/sw/Generators/madgraph/models/latest'
181 self.
MADGRAPH_COMMAND_STACK += [
'export PYTHONPATH=${PYTHONPATH}:/cvmfs/atlas.cern.ch/repo/sw/Generators/madgraph/models/latest']
183 if 'GFORTRAN_TMPDIR' in os.environ:
185 if 'TMPDIR' in os.environ:
186 os.environ[
'GFORTRAN_TMPDIR']=os.environ[
'TMPDIR']
189 if 'TMP' in os.environ:
190 os.environ[
'GFORTRAN_TMPDIR']=os.environ[
'TMP']
194 """Builds a dictionary from the run card.
195 This function takes in the card location and saves the contents as a dictionary object in the MGControl class.
197 run_card = self.
process_dir +
'/Cards/run_card.dat'
199 if os.access(run_card,os.R_OK):
200 mglog.info(
'Copying default run_card.dat from '+str(run_card))
202 run_card = self.
process_dir+
'/Cards/run_card_default.dat'
203 if os.access(run_card,os.R_OK):
204 mglog.info(
'Copying default run_card.dat from '+str(run_card))
206 raise RuntimeError(
'Cannot find default run_card.dat or run_card_default.dat! I was looking here: %s'%run_card)
208 card = open(run_card)
210 for line
in iter(card):
211 if not line.strip().startswith(
'#'):
212 command = line.split(
'!', 1)[0]
214 setting = command.split(
'=')[-1].
strip()
215 value =
'='.join(command.split(
'=')[:-1]).
strip()
217 value = value.lower()
218 setting = setting.lower()
224 """Gets the config card location and determines if the process is LO or NLO
225 This function takes in the process diectory as an input and uses it to find the configuration.
226 Using the path to the config path, we can determine if the process will require a LO or NLO configuration.
230 lo_config_card = self.
process_dir+
'/Cards/me5_configuration.txt'
231 nlo_config_card = self.
process_dir+
'/Cards/amcatnlo_configuration.txt'
233 if os.access(lo_config_card,os.R_OK)
and not os.access(nlo_config_card,os.R_OK):
236 elif os.access(nlo_config_card,os.R_OK)
and not os.access(lo_config_card,os.R_OK):
239 elif os.access(nlo_config_card,os.R_OK)
and os.access(lo_config_card,os.R_OK):
240 mglog.error(
'Found both types of config card in '+str(self.
process_dir))
241 raise RuntimeError(
'Unable to locate configuration card')
243 mglog.error(
'No config card in '+str(self.
process_dir))
244 raise RuntimeError(
'Unable to locate configuration card')
248 """Builds a dictionary from the config card.
249 This function creates a dictionary object configCardDict from the config card.
250 Using the config card location, we copy over th settings to the dictionary.
251 Note: This function is works in the same way as self.getRunCardDict() however with small changes based on how the card is written.
253 card = open(card_loc)
256 for line
in iter(card):
257 if not line.strip().startswith(
'#'):
258 command = line.split(
'!', 1)[0]
261 value = command.split(
'=')[-1].
strip()
262 setting =
'='.join(command.split(
'=')[:-1]).
strip()
265 value = value.lower()
266 setting = setting.lower()
272 """This function gets the beam energy and random seed from the runArguments
276 raise RuntimeError(
'runArgs must be provided!')
278 if hasattr(runArgs,
'ecmEnergy'):
281 raise RuntimeError(
"No center of mass energy found in runArgs.")
283 if hasattr(runArgs,
'randomSeed'):
286 raise RuntimeError(
"No random seed found in runArgs.")
290 """This function gets the beam energy and random seed from the configuration flags."""
292 raise RuntimeError(
'flags must be provided!')
296 self.
beamEnergy = float(flags.Beam.Energy) / GeV
297 except AttributeError
as e:
298 raise RuntimeError(
"No beam energy found in flags (expected flags.Beam.Energy).")
from e
302 except AttributeError
as e:
303 raise RuntimeError(
"No random seed found in flags (expected flags.Random.SeedOffset).")
from e
307 """Add seed and beam settings to runCardDict."""
314 raise RuntimeError(
'Do not set beamenergy in the run card. Use flags (or runArgs during migration) instead.')
323 """This function adds run arguments to the self.runCardDict.
324 If the runArgs argument is left blank, the function will get the runArgs information before adding to the dictionary
326 if runArgs
is not None:
333 """This function adds flag-derived seed and beam settings to self.runCardDict."""
334 if flags
is not None:
341 """Build a new run_card.dat from a run card dictionary.
342 This function can get a fresh run card from the runCardDict object.
343 Before writing the dictionary to the run card, we require to check a few things first
347 if flags
is not None:
348 if runArgs
is not None:
349 mglog.warning(
'Both runArgs and flags were provided to write_runCard. Using flags.')
363 if flags
is not None and hasattr(flags,
'Generator')
and hasattr(flags.Generator,
'jobConfig')
and flags.Generator.jobConfig:
364 cfgdir = flags.Generator.jobConfig[0]
if isinstance(flags.Generator.jobConfig, (list, tuple))
else flags.Generator.jobConfig
365 elif runArgs
is not None and hasattr(runArgs,
'jobConfig'):
366 cfgdir = runArgs.jobConfig[0]
if isinstance(runArgs.jobConfig, (list, tuple))
else runArgs.jobConfig
367 elif flags
is not None and 'JOBOPTSEARCHPATH' in os.environ:
368 cfgdir = os.environ[
'JOBOPTSEARCHPATH'].
split(
':')[0]
372 full_path = os.path.join(cfgdir, raw_name)
373 self.
runCardDict[
'custom_fcts'] = os.path.abspath(full_path)
374 mglog.info(f
"Using custom function(s), specified in custom_fcts with path: {self.runCardDict['custom_fcts']}")
377 self.
runCardDict[
'custom_fcts'] = os.path.abspath(raw_name)
380 runCard_old = self.
process_dir+
'/Cards/run_card.dat.old_to_be_deleted'
381 os.rename(self.
process_dir+
'/Cards/run_card.dat', runCard_old)
387 with open(runCard_old)
as oldCard, open(self.
process_dir+
'/Cards/run_card.dat',
'w')
as newCard:
388 for line
in iter(oldCard):
390 if line.strip().startswith(
'#'):
393 command= line.split(
'!',1)[0]
394 if len(line.split(
'!',1)) > 1:
395 comment= line.split(
'!',1)[1]
399 setting = command.split(
'=')[-1].
strip()
402 newCard.write(
' '+str(self.
runCardDict[setting])+
' = '+str(setting)+
' ! '+ comment)
403 listSettings.append(str(setting))
405 raise RuntimeError(
'Could not find '+str(setting)+
' in the Run Card Dictionary!')
409 newCard.write(
"""#***********************************************************************
410# Any Additional settings can be added here *
411#***********************************************************************
416 if setting
not in listSettings:
417 newCard.write(
' '+str(self.
runCardDict[setting])+
' = '+str(setting)+
'\n')
426 mglog.info(
'Finished writing to run card.')
427 os.unlink(runCard_old)
430 """Build a new configuration from a config card dictionary.
431 This function can get a fresh runcard from the configCardDict object.
432 This function behaves similaraly to self.write_runCard()
434 mglog.info(
'Writing config card in '+self.
process_dir)
437 config_pathOLD = self.
config_path+
'.old_to_be_deleted'
445 mglog.info(
'Writing option '+setting+
' to the config card. Adding a setting to '+str(self.
configCardDict[setting]))
446 newCard.write(
' '+str(setting)+
' = '+str(self.
configCardDict[setting])+
'\n')
451 mglog.info(
'Finished writing to config card.')
453 os.unlink(config_pathOLD)
458 """This function checks that the casing in the run card dictionary is the same as the default run card.
459 It checks if the default setting appears, with the correct casing, in the updated card
460 If it isn't in the run card, if then checks if the default setting (in lower case) appears in the lowered (updated) card
461 Assuming that any inconsistencies have just lowered the casing of the setting, the function then attempts to resolve the inconsistency
466 lower_card = [key.lower()
for key
in self.
runCardDict]
474 if default_setting
in temp_run_card:
476 elif default_setting.lower()
in lower_card:
477 mglog.warning(f
"The casing in the run card seems to be wrong for {default_setting}. We will try fix this now.")
480 temp_run_card[default_setting] = temp_run_card[default_setting.lower()]
481 temp_run_card.pop(default_setting.lower())
484 raise RuntimeError(
"Run Card Dictionary casing is inconsistent")
489 mglog.info(
'Run card casing looks good!')
492 """Checks the consistency of runCardDict.
493 This function should be called before writing runCardDict to disk to ensure that the run card is consistent and has appropriate settings.
501 mglog.warning(
"setting event_norm to average, there is basically no use case where event_norm=sum is a good idea")
506 log=
'Bad combination of settings for CKKW-L merging! ktdurham=%s and ickkw=%s.'%(self.
runCardDict[
'ktdurham'],self.
runCardDict[
'ickkw'])
508 raise RuntimeError(log)
511 if 'systematics_program' not in self.
runCardDict or self.
runCardDict[
'systematics_program']==
'systematics':
512 syscalc_settings = [
'sys_pdf',
'sys_scalefact',
'sys_alpsfact',
'sys_matchscale']
513 found_syscalc_setting =
False
514 for s
in syscalc_settings:
516 mglog.warning(
'Using syscalc setting '+s+
' with new systematics script. Systematics script is default from 2.6.2 and steered differently (https://cp3.irmp.ucl.ac.be/projects/madgraph/wiki/Systematics#Systematicspythonmodule)')
517 found_syscalc_setting =
True
518 if found_syscalc_setting:
519 syst_arguments = convertSysCalcArguments(self.
runCardDict)
520 mglog.info(
'Converted syscalc arguments to systematics arguments: '+syst_arguments)
521 syst_settings_update = {
'systematics_arguments':syst_arguments}
522 for s
in syscalc_settings:
523 syst_settings_update[s] =
None
527 mglog.info(
'Checking PDF and systematics settings')
530 syst_settings = get_pdf_and_systematic_settings(self.
pdf_setting,self.
isNLO)
534 systematics_arguments = parse_systematics_arguments(self.
runCardDict[
'systematics_arguments'])
535 if 'weight_info' not in systematics_arguments:
536 mglog.info(
'Enforcing systematic weight name convention')
538 if '--dyn' in systematics_arguments
or ' dyn' in systematics_arguments:
539 if '--dyn' in systematics_arguments:
540 dyn = systematics_arguments.split(
'--dyn')[1]
541 if ' dyn' in systematics_arguments:
542 dyn = systematics_arguments.split(
' dyn')[1]
544 if dyn
is not None and len(dyn.split(
','))>1:
545 systematics_arguments[
'weight_info'] = SYSTEMATICS_WEIGHT_INFO_ALTDYNSCALES
547 systematics_arguments[
'weight_info'] = SYSTEMATICS_WEIGHT_INFO
548 self.
runCardDict[
'systematics_arguments'] = write_systematics_arguments(systematics_arguments)
552 mglog.warning(
'No python seed set in run_card -- adding one with same value as iseed')
560 with open(self.
process_dir+
'/Cards/proc_card_mg5.dat',
'r')
as file:
561 content = file.readlines()
563 for rawline
in content:
564 line = rawline.split(
'#')[0]
565 if line.startswith(
"define p"):
566 if (
'b' in line.split()
and 'b~' in line.split())
or (
'5' in line.split()
and '-5' in line.split()):
569 if 'j' in line.split()
and jet_5flav:
571 if line.startswith(
"define j"):
572 if (
'b' in line.split()
and 'b~' in line.split())
or (
'5' in line.split()
and '-5' in line.split()):
575 if 'p' in line.split()
and proton_5flav:
577 if proton_5flav
or jet_5flav:
578 FS_updates[
'asrwgtflavor'] = 5
581 mglog.warning(
'Found 5-flavour jets but 4-flavour proton. This is inconsistent - please pick one.')
582 mglog.warning(
'Will proceed assuming 5-flavour scheme.')
584 mglog.warning(
'Found 5-flavour protons but 4-flavour jets. This is inconsistent - please pick one.')
585 mglog.warning(
'Will proceed assuming 5-flavour scheme.')
587 FS_updates[
'asrwgtflavor'] = 4
589 if len(FS_updates)==0:
590 mglog.warning(f
'Could not identify 4- or 5-flavor scheme from process card {self.process_dir}/Cards/proc_card_mg5.dat')
594 if FS_updates[
'asrwgtflavor'] == 5:
598 mglog.warning(
'b and b~ included in p and j for 5-flavor scheme but run card settings are inconsistent; adjusting run card')
599 run_card_updates = {
'asrwgtflavor': 5,
'maxjetflavor': 5,
'pdgs_for_merging_cut':
'1, 2, 3, 4, 5, 21'}
602 modify_param_card(process_dir=self.
process_dir, params={
'MASS': {
'5':
'0.000000e+00'}})
604 mglog.debug(
'Consistent 5-flavor scheme setup detected.')
606 if FS_updates[
'asrwgtflavor'] == 4:
610 mglog.warning(
'b and b~ not included in p and j (4-flavor scheme) but run card settings are inconsistent; adjusting run card')
611 run_card_updates = {
'asrwgtflavor': 4,
'maxjetflavor': 4,
'pdgs_for_merging_cut':
'1, 2, 3, 4, 21'}
614 modify_param_card(process_dir=self.
process_dir, params={
'MASS': {
'5':
'4.700000e+00'}})
616 mglog.debug(
'Consistent 4-flavor scheme setup detected.')
619 if FS_updates[
'asrwgtflavor'] == 4:
621 mglog.warning(
'Flavor scheme setup is missing, adding by hand according to process card - b and b~ not included in p and j, 4-flavor scheme setup will be used; adjusting run card.')
622 run_card_updates = {
'asrwgtflavor': 4,
'maxjetflavor': 4,
'pdgs_for_merging_cut':
'1, 2, 3, 4, 21'}
624 modify_param_card(process_dir=self.
process_dir, params={
'MASS': {
'5':
'4.700000e+00'}})
625 elif FS_updates[
'asrwgtflavor'] == 5:
626 mglog.warning(
'Flavor scheme setup is missing, adding by hand according to process card - b and b~ included in p and j, 5-flavor scheme setup will be used; adjusting run card.')
627 run_card_updates = {
'asrwgtflavor': 5,
'maxjetflavor': 5,
'pdgs_for_merging_cut':
'1, 2, 3, 4, 5, 21'}
629 modify_param_card(process_dir=self.
process_dir, params={
'MASS': {
'5':
'0.000000e+00'}})
633 mglog.error(
'Seems you set "scale" in the run card without setting "fixed_ren_scale" to True. Not sure what to do here, throwing an error.')
634 raise ValueError(
"Renormalization scale setting incorrect")
636 and self.
runCardDict.
get(
'fixed_fac_scale',
'f').lower()
in [
'f',
'false']:
637 mglog.error(
'Seems you set "dsqrt_q2fact1" or "dsqrt_q2fact2" in the run card without setting "fixed_fac_scale" to True. Not sure what to do here, throwing an error.')
638 raise ValueError(
"Factorization scale setting incorrect")
640 mglog.info(
'Finished checking run card - All OK!')
651 if the_base_fragment
is None:
652 mglog.warning(
'!!! No pdf base fragment was included in your job options. PDFs should be set with an include file. You might be unable to follow the PDF4LHC uncertainty prescription. Let\'s hope you know what you doing !!!')
653 if not extras.get(
'pdlabel',
None) ==
'lhapdf' or 'lhaid' not in extras:
654 mglog.warning(
'!!! No pdf base fragment was included in your job options and you did not specify a LHAPDF yourself -- in the future, this will cause an error !!!')
660 correct_settings=get_pdf_and_systematic_settings(the_base_fragment,isNLO)
663 for s
in correct_settings:
664 if s
is None and s
in extras:
667 if s
not in extras
or extras[s]!=correct_settings[s]:
677 """Boilerplate code that returns a bare CA fragment.
678 To be used in MadGraphConfig.py"""
679 from AthenaConfiguration.ComponentAccumulator
import ComponentAccumulator
680 from GeneratorConfig.Sequences
import EvgenSequence, EvgenSequenceFactory
682 ca = ComponentAccumulator(EvgenSequenceFactory(EvgenSequence.Generator))
compare_runCardCasing(self)
add_flags(self, flags=None)
get_runArgs_info(self, runArgs)
_add_seed_and_beam_settings(self)
write_runCard(self, runArgs=None, flags=None)
setup_path_protection(self)
get_flags_info(self, flags)
base_fragment_setup_check(self, the_base_fragment, extras, isNLO)
__init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False, pdf_setting=None, devices=None, catch_errors=MADGRAPH_CATCH_ERRORS)
list MADGRAPH_COMMAND_STACK
add_runArgs(self, runArgs=None)
run_card_consistency_check(self)
getConfigFromPath(self, card_loc, lowercase=False)
getRunCardDict(self, lowercase=False)
std::string replace(std::string s, const std::string &s2, const std::string &s3)
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
std::vector< std::string > split(const std::string &s, const std::string &t=":")