ATLAS Offline Software
Loading...
Searching...
No Matches
MGC.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3# Pythonized version of MadGraph steering executables
4# written by Zach Marshall <zach.marshall@cern.ch>
5# updates for aMC@NLO by Josh McFayden <mcfayden@cern.ch>
6# updates to LHE handling and SUSY functionality by Emma Kuwertz <ekuwertz@cern.ch>
7# Attempts to remove path-dependence of MadGraph
8# Class-based version of MadGraph Control
9# written by Kael Kemp <kael.kemp@cern.ch>
10
11import os,time,subprocess,glob,re,sys # noqa: F401
12from AthenaCommon import Logging
13from MadGraphControl.MadGraphUtilsHelpers import error_check,modify_param_card # noqa: F401
14from MadGraphControl.MadGraphParamHelpers import do_PMG_updates # noqa: F401
15from MadGraphControl.MadGraphSystematicsUtils import convertSysCalcArguments,get_pdf_and_systematic_settings,parse_systematics_arguments,SYSTEMATICS_WEIGHT_INFO_ALTDYNSCALES,SYSTEMATICS_WEIGHT_INFO,write_systematics_arguments # noqa: F401
16
17mglog = Logging.logging.getLogger('MadGraphUtils')
18
19# Name of python executable
20python = 'python'
21# Magic name of gridpack directory
22MADGRAPH_GRIDPACK_LOCATION = 'madevent'
23# Name for the run (since we only have 1, just needs consistency)
24MADGRAPH_RUN_NAME = 'run_01'
25# For error handling
26MADGRAPH_CATCH_ERRORS = True
27# PDF setting (global setting)
28MADGRAPH_PDFSETTING = None
29
30
34MADGRAPH_DEVICES = None
35
37 def __init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False):
38 """ Generate a new process in madgraph.
39 Pass a process string.
40 Optionally request JPEGs to be kept and request for PMG settings to be used in the param card
41 Return the name of the process directory.
42 """
43 self.mglog = Logging.logging.getLogger('MadGraphUtils')
44 self.process = process
45 self.plugin = plugin
46 self.keepJpegs = keepJpegs
47 self.usePMGSettings = usePMGSettings
49 self.beamEnergy = 0
50 #is_gen_from gridpack
51 self.is_gen_from_gridpack = os.access(MADGRAPH_GRIDPACK_LOCATION,os.R_OK)
52 # Don't run if generating events from gridpack
54 self.process_dir = MADGRAPH_GRIDPACK_LOCATION
55 return
56 # Actually just sent the process card contents - let's make a card
57 card_loc = 'proc_card_mg5.dat'
58 mglog.info('Writing process card to '+card_loc)
59 a_card = open( card_loc , 'w' )
60 for l in process.split('\n'):
61 if 'output' not in l:
62 a_card.write(l+'\n')
63 else:
64 # Special handling for output line
65 outline = l.strip()
66 if '-nojpeg' not in l and not keepJpegs:
67 # We need to add -nojpeg somehow
68 if '#' in l:
69 outline = outline.split('#')[0]+' -nojpeg #'+outline.split('#')[1]
70 else:
71 outline = outline + ' -nojpeg'
72 # Special handling for devises
73 if MADGRAPH_DEVICES is not None:
74 if MADGRAPH_DEVICES.lower() in ['madevent_simd','madevent_gpu']:
75 outline = 'output '+MADGRAPH_DEVICES.lower()+' '+outline.split('output')[1]
76 elif MADGRAPH_DEVICES.lower() == 'max':
77 self.mglog.warning('Not fully implemented yet; setting avx')
78 outline = 'output madevent_simd '+outline.split('output')[1]
79 a_card.write(outline+'\n')
80 a_card.close()
81
82 madpath = os.environ['MADPATH']
84 # Just in case
86
87 # Check if we have a special output directory
88 process_dir = ''
89 for l in process.split('\n'):
90 # Look for an output line
91 if 'output' not in l.split('#')[0].split():
92 continue
93 # Check how many things before the options start
94 tmplist = l.split('#')[0].split(' -')[0]
95 # if two things, second is the directory
96 if len(tmplist.split())==2:
97 process_dir = tmplist.split()[1]
98 # if three things, third is the directory (second is the format)
99 elif len(tmplist.split())==3:
100 process_dir = tmplist.split()[2]
101 # See if we got a directory
102 if ''!=process_dir:
103 mglog.info('Saw that you asked for a special output directory: '+str(process_dir))
104 break
105
106 mglog.info('Started process generation at '+str(time.asctime()))
107
108 plugin_cmd = '--mode='+plugin if plugin is not None else ''
109
110 # Note special handling here to explicitly print the process
111 self.MADGRAPH_COMMAND_STACK += ['# All jobs should start in a clean directory']
112 self.MADGRAPH_COMMAND_STACK += ['mkdir standalone_test; cd standalone_test']
113 self.MADGRAPH_COMMAND_STACK += [' '.join([python,madpath+'/bin/mg5_aMC '+plugin_cmd+' << EOF\n'+process+'\nEOF\n'])]
114 generate = subprocess.Popen([python,madpath+'/bin/mg5_aMC',plugin_cmd,card_loc],stdin=subprocess.PIPE,stderr=subprocess.PIPE if MADGRAPH_CATCH_ERRORS else None)
115 (out,err) = generate.communicate()
116 error_check(err,generate.returncode)
117
118 mglog.info('Finished process generation at '+str(time.asctime()))
119
120 # at this point process_dir is for sure defined - it's equal to '' in the worst case
121 if process_dir == '': # no user-defined value, need to find the directory created by MadGraph5
122 for adir in sorted(glob.glob( os.getcwd()+'/*PROC*' ),reverse=True):
123 if os.access('%s/SubProcesses/subproc.mg'%adir,os.R_OK):
124 if process_dir=='':
125 process_dir = adir
126 else:
127 mglog.warning('Additional possible process directory, '+adir+' found. Had '+process_dir)
128 mglog.warning('Likely this is because you did not run from a clean directory, and this may cause errors later.')
129 else: # user-defined directory
130 if not os.access('%s/SubProcesses/subproc.mg'%process_dir,os.R_OK):
131 raise RuntimeError('No diagrams for this process in user-define dir='+str(process_dir))
132 if process_dir=='':
133 raise RuntimeError('No diagrams for this process from list: '+str(sorted(glob.glob(os.getcwd()+'/*PROC*'),reverse=True)))
134
135 self.process_dir = process_dir
136 self.get_config_cardloc()
138
139 #load up the run card dictionary
140 self.getRunCardDict()
141
142 # If requested, apply PMG default settings
143 if usePMGSettings:
144 do_PMG_updates(self.process_dir)
145
146 if not self.isNLO:
147 mglog.info('Setting default sde_strategy to old default (1)')
148 self.runCardDict['sde_strategy'] = 1
149
150 #tell MadGraph not to bother trying to create popup windows since this is running in a CLI, this will save ~50 seconds every time MadGraph is called.
151 self.configCardDict.update({'notification_center':'False'})
152
153 # Add some custom settings based on the device requests
154 if MADGRAPH_DEVICES is not None:
155 if MADGRAPH_DEVICES.lower()=='madevent_simd':
156 self.runCardDict['cudacpp_backend'] = 'cppauto'
157 elif MADGRAPH_DEVICES.lower()=='madevent_gpu':
158 self.runCardDict['cudacpp_backend'] = 'cuda'
159 # In case we have "too new" a gcc version for the nvcc version on the node, which should be ok
160 # This patch should be temporary, but is fine while we are validating things at least
161 os.environ['ALLOW_UNSUPPORTED_COMPILER_IN_CUDA'] = 'Y'
162 elif MADGRAPH_DEVICES.lower() == 'max':
163 self.mglog.warning('Not fully implemented yet; setting avx')
164 self.runCardDict['cudacpp_backend'] = 'cppauto'
165
166 # Make sure we store the resultant directory
167 self.MADGRAPH_COMMAND_STACK += ['export MGaMC_PROCESS_DIR='+os.path.basename(self.process_dir)]
168
169
170
172 # Addition for models directory
173
174 if 'PYTHONPATH' in os.environ:
175 if not any( [('Generators/madgraph/models' in x) for x in os.environ['PYTHONPATH'].split(':') ]):
176 os.environ['PYTHONPATH'] += ':/cvmfs/atlas.cern.ch/repo/sw/Generators/madgraph/models/latest'
177 self.MADGRAPH_COMMAND_STACK += ['export PYTHONPATH=${PYTHONPATH}:/cvmfs/atlas.cern.ch/repo/sw/Generators/madgraph/models/latest']
178 # Make sure that gfortran doesn't write to somewhere it shouldn't
179 if 'GFORTRAN_TMPDIR' in os.environ:
180 return
181 if 'TMPDIR' in os.environ:
182 os.environ['GFORTRAN_TMPDIR']=os.environ['TMPDIR']
183 self.MADGRAPH_COMMAND_STACK += ['export GFORTRAN_TMPDIR=${TMPDIR}']
184 return
185 if 'TMP' in os.environ:
186 os.environ['GFORTRAN_TMPDIR']=os.environ['TMP']
187 self.MADGRAPH_COMMAND_STACK += ['export GFORTRAN_TMPDIR=${TMP}']
188
189 def getRunCardDict(self,lowercase=False):
190 """Builds a dictionary from the run card.
191 This function takes in the card location and saves the contents as a dictionary object in the MGControl class.
192 """
193 run_card = self.process_dir + '/Cards/run_card.dat'
194
195 if os.access(run_card,os.R_OK):
196 mglog.info('Copying default run_card.dat from '+str(run_card))
197 else:
198 run_card = self.process_dir+'/Cards/run_card_default.dat'
199 if os.access(run_card,os.R_OK):
200 mglog.info('Copying default run_card.dat from '+str(run_card))
201 else:
202 raise RuntimeError('Cannot find default run_card.dat or run_card_default.dat! I was looking here: %s'%run_card)
203
204 card = open(run_card)
205 self.runCardDict = {} # Define the dictionary object
206 for line in iter(card):
207 if not line.strip().startswith('#'): # Ignores line commented out
208 command = line.split('!', 1)[0]
209 if '=' in command:
210 setting = command.split('=')[-1].strip() #saves the setting
211 value = '='.join(command.split('=')[:-1]).strip() #saves the value associated with the setting
212 if lowercase:
213 value = value.lower()
214 setting = setting.lower()
215 self.runCardDict[setting] = value #adds setting and value to the dictionary
216 card.close()
217
218
220 """Gets the config card location and determines if the process is LO or NLO
221 This function takes in the process diectory as an input and uses it to find the configuration.
222 Using the path to the config path, we can determine if the process will require a LO or NLO configuration.
223 """
224 self.isNLO = None
225 #Defining the possible config paths
226 lo_config_card = self.process_dir+'/Cards/me5_configuration.txt'
227 nlo_config_card = self.process_dir+'/Cards/amcatnlo_configuration.txt'
228
229 if os.access(lo_config_card,os.R_OK) and not os.access(nlo_config_card,os.R_OK): #Process is LO
230 self.config_path = lo_config_card
231 self.isNLO = False
232 elif os.access(nlo_config_card,os.R_OK) and not os.access(lo_config_card,os.R_OK): #Process is NLO
233 self.config_path = nlo_config_card
234 self.isNLO = True
235 elif os.access(nlo_config_card,os.R_OK) and os.access(lo_config_card,os.R_OK): #Process has two config cards
236 mglog.error('Found both types of config card in '+str(self.process_dir))
237 raise RuntimeError('Unable to locate configuration card')
238 else: # No config Card
239 mglog.error('No config card in '+str(self.process_dir))
240 raise RuntimeError('Unable to locate configuration card')
241
242
243 def getConfigFromPath(self, card_loc, lowercase=False):
244 """Builds a dictionary from the config card.
245 This function creates a dictionary object configCardDict from the config card.
246 Using the config card location, we copy over th settings to the dictionary.
247 Note: This function is works in the same way as self.getRunCardDict() however with small changes based on how the card is written.
248 """
249 card = open(card_loc)
250 #define the configCardDict object
252 for line in iter(card):
253 if not line.strip().startswith('#'): # Ignore lines that are commented out
254 command = line.split('!', 1)[0]
255 if '=' in command:
256 # Here is where we differ from self.getRunCardDict(), the config card has the setting to the left of the '=' and value to the right
257 value = command.split('=')[-1].strip()
258 setting = '='.join(command.split('=')[:-1]).strip()
259
260 if lowercase:
261 value = value.lower()
262 setting = setting.lower()
263 self.configCardDict[setting] = value # adds setting to the configCardDict
264 card.close()
265
266
267 def get_runArgs_info(self,runArgs):
268 """This function gets the beam energy and random seed from the runArguments
269 """
270
271 if runArgs is None:
272 raise RuntimeError('runArgs must be provided!')
273 #Get Beam Energy
274 if hasattr(runArgs,'ecmEnergy'):
275 self.beamEnergy = runArgs.ecmEnergy / 2.
276 else:
277 raise RuntimeError("No center of mass energy found in runArgs.")
278 #Get random seed
279 if hasattr(runArgs,'randomSeed'):
280 self.random_seed = runArgs.randomSeed
281 else:
282 raise RuntimeError("No random seed found in runArgs.")
283
284
285 def add_runArgs(self, runArgs=None):
286 """This function adds run arguments to the self.runCardDict.
287 If the runArgs argument is left blank, the function will get the runArgs information before adding to the dictionary
288 """
289 if runArgs is not None:
290 self.get_runArgs_info(runArgs) # Use get_runArgs_info function to retrieve runArgs
291
292 # Check if the runArgs are already implemented.
293 if 'iseed' not in self.runCardDict: #if there is no setting in self.runCardDict for iseed
294 self.runCardDict['iseed'] = self.random_seed
295 if not self.isNLO and 'python_seed' not in self.runCardDict: #If the process is LO and there is no 'python_seed' setting in self.runCardDict
296 self.runCardDict['python_seed'] = self.random_seed
297 if 'beamenergy' in self.runCardDict: #if the beam energy is defined in self.runCardDict
298 raise RuntimeError('Do not set beamenergy in the run card. Use runArgs instead.')
299
300 if 'ebeam1' not in self.runCardDict or self.beamEnergy != self.runCardDict['ebeam1']: # if there is no setting 'ebeam1' in self.runCardDict
301 self.runCardDict['ebeam1'] = self.beamEnergy
302 if 'ebeam2' not in self.runCardDict or self.beamEnergy != self.runCardDict['ebeam2']: #if there is no setting 'ebeam2' in self.runCardDict
303 self.runCardDict['ebeam2'] = self.beamEnergy
304
305
306 def write_runCard(self, runArgs=None):
307 """Build a new run_card.dat from a run card dictionary.
308 This function can get a fresh run card from the runCardDict object.
309 Before writing the dictionary to the run card, we require to check a few things first
310 """
311
312 # Get info from runArgs
313 self.add_runArgs(runArgs)
314
315 # Make sure that nevents is integer
316 if 'nevents' in self.runCardDict:
317 self.runCardDict['nevents'] = int(self.runCardDict['nevents'])
318
319 # Normalise custom_fcts early so the rewritten run_card uses the full path
320 if 'custom_fcts' in self.runCardDict and self.runCardDict['custom_fcts']:
321 raw_name = str(self.runCardDict['custom_fcts']).split()[0]
322 # Determine jobConfig directory
323 if runArgs is not None and hasattr(runArgs, 'jobConfig'):
324 cfgdir = runArgs.jobConfig[0] if isinstance(runArgs.jobConfig, (list, tuple)) else runArgs.jobConfig
325 # Build full path and make absolute
326 full_path = os.path.join(cfgdir, raw_name)
327 self.runCardDict['custom_fcts'] = os.path.abspath(full_path)
328 mglog.info(f"Using custom function(s), specified in custom_fcts with path: {self.runCardDict['custom_fcts']}")
329 else:
330 # For internal tests, where jobConfig is not set
331 self.runCardDict['custom_fcts'] = os.path.abspath(raw_name)
332
333 # to avoid writing over the old run card, we rename the old card
334 runCard_old = self.process_dir+'/Cards/run_card.dat.old_to_be_deleted'
335 os.rename(self.process_dir+'/Cards/run_card.dat', runCard_old)
336
337 listSettings = []
338
339 # Read in old run card, we want to copy over the comments
340 # Then create a new run card in the same location as the old card
341 with open(runCard_old) as oldCard, open(self.process_dir+'/Cards/run_card.dat', 'w') as newCard:
342 for line in iter(oldCard):
343 #if the line starts with a '#' (ie. is a comment) copy it straight over
344 if line.strip().startswith('#'):
345 newCard.write(line)
346 else: #if not we want to grab the comment after the '!' as well as the associated command (before '!')
347 command= line.split('!',1)[0]
348 if len(line.split('!',1)) > 1:
349 comment= line.split('!',1)[1]
350 else:
351 comment = '\n'
352 if '=' in command:
353 setting = command.split('=')[-1].strip()
354 # Check if the setting is in the dictionary and then print with the comment and the updated value
355 if setting in self.runCardDict:
356 newCard.write( ' '+str(self.runCardDict[setting])+' = '+str(setting)+' ! '+ comment)
357 listSettings.append(str(setting))
358 else:
359 raise RuntimeError('Could not find '+str(setting)+' in the Run Card Dictionary!')
360 else:
361 newCard.write(line)
362 # Add a commented region
363 newCard.write("""#***********************************************************************
364# Any Additional settings can be added here *
365#***********************************************************************
366""")
367
368 #check that all settings have been writen
369 for setting in self.runCardDict:
370 if setting not in listSettings:
371 newCard.write( ' '+str(self.runCardDict[setting])+' = '+str(setting)+'\n')
372
373 # Check whether mcatnlo_delta is applied to setup pythia8 path
374 if 'mcatnlo_delta' in self.runCardDict:
375 if self.runCardDict['mcatnlo_delta'] == 'True':
376 self.configCardDict['pythia8_path'] = os.getenv("PY8PATH")
377 # TODO: this will require our writing out the config card again
378
379 # Tidy up after ourselves
380 mglog.info('Finished writing to run card.')
381 os.unlink(runCard_old) # delete old backup
382
384 """Build a new configuration from a config card dictionary.
385 This function can get a fresh runcard from the configCardDict object.
386 This function behaves similaraly to self.write_runCard()
387 """
388 mglog.info('Writing config card in '+self.process_dir)
389
390 #change name of old config card to avoid writing over
391 config_pathOLD = self.config_path+'.old_to_be_deleted'
392 os.rename(self.config_path, config_pathOLD) # change name of original card
393
394 # create new config card
395 newCard = open(self.config_path, 'w')
396 for setting in self.configCardDict:
397 if self.configCardDict[setting] is None: # ignore empty settings
398 continue
399 mglog.info('Writing option '+setting+' to the config card. Adding a setting to '+str(self.configCardDict[setting]))
400 newCard.write(' '+str(setting)+' = '+str(self.configCardDict[setting])+'\n') #writing config card in the format setting = value
401
402 # close file
403 newCard.close()
404
405 mglog.info('Finished writing to config card.')
406
407 os.unlink(config_pathOLD) # delete old file
408
409
410
412 """This function checks that the casing in the run card dictionary is the same as the default run card.
413 It checks if the default setting appears, with the correct casing, in the updated card
414 If it isn't in the run card, if then checks if the default setting (in lower case) appears in the lowered (updated) card
415 Assuming that any inconsistencies have just lowered the casing of the setting, the function then attempts to resolve the inconsistency
416 """
417 # Put the run card aside for the moment
418 temp_run_card = self.runCardDict
419 # Make a list with all lower case settings
420 lower_card = [key.lower() for key in self.runCardDict]
421
422 # Get the default run card to compare to
423 self.getRunCardDict()
424
425 #check for all the default settings in the default run card
426 for default_setting in self.runCardDict:
427 #if the default setting appears in the updated run card (with the same casing), we skip
428 if default_setting in temp_run_card:
429 continue
430 elif default_setting.lower() in lower_card: # If the default setting isn't in the updated run card but is in the lower case dictionary
431 mglog.warning(f"The casing in the run card seems to be wrong for {default_setting}. We will try fix this now.")
432
433 try: #want to try fixing this so we will assume that the settings has accidently been made lower-case
434 temp_run_card[default_setting] = temp_run_card[default_setting.lower()]
435 temp_run_card.pop(default_setting.lower())
436 except KeyError: #if that doesn't work we raise an error
437 self.runCardDict = temp_run_card #(just to make it easier to find the updated run card
438 raise RuntimeError("Run Card Dictionary casing is inconsistent")
439 else:
440 continue
441 # finally, lets put the run card back
442 self.runCardDict = temp_run_card
443 mglog.info('Run card casing looks good!')
444
446 """Checks the consistency of runCardDict.
447 This function should be called before writing runCardDict to disk to ensure that the run card is consistent and has appropriate settings.
448 """
450
451 # We should always use event_norm = average [AGENE-1725] otherwise Pythia cross sections are wrong
452 # Modification: average or bias is ok; sum is incorrect. Change the test to set sum to average
453 if self.runCardDict.get('event_norm',None) =='sum':
454 self.runCardDict['event_norm'] = 'average'
455 mglog.warning("setting event_norm to average, there is basically no use case where event_norm=sum is a good idea")
456
457 if not self.isNLO:
458 #Check CKKW-L setting
459 if 'ktdurham' in self.runCardDict and float(self.runCardDict['ktdurham']) > 0 and int(self.runCardDict['ickkw']) != 0:
460 log='Bad combination of settings for CKKW-L merging! ktdurham=%s and ickkw=%s.'%(self.runCardDict['ktdurham'],self.runCardDict['ickkw'])
461 mglog.error(log)
462 raise RuntimeError(log)
463
464 # Check if user is trying to use deprecated syscalc arguments with the other systematics script
465 if 'systematics_program' not in self.runCardDict or self.runCardDict['systematics_program']=='systematics': #if systematics are not set
466 syscalc_settings = ['sys_pdf', 'sys_scalefact', 'sys_alpsfact', 'sys_matchscale']
467 found_syscalc_setting = False
468 for s in syscalc_settings:
469 if s in self.runCardDict: #searches for the systematic setting in the runCard
470 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)')
471 found_syscalc_setting = True
472 if found_syscalc_setting: #if a systematic setting was found
473 syst_arguments = convertSysCalcArguments(self.runCardDict)
474 mglog.info('Converted syscalc arguments to systematics arguments: '+syst_arguments)
475 syst_settings_update = {'systematics_arguments':syst_arguments} # save the system arguments to a dictionary
476 for s in syscalc_settings:
477 syst_settings_update[s] = None
478 self.runCardDict.update(syst_settings_update) #update the systematic settings
479
480 # Check pdf and systematics
481 mglog.info('Checking PDF and systematics settings')
482 if not self.base_fragment_setup_check(MADGRAPH_PDFSETTING,self.runCardDict,self.isNLO): #if the base fragment has not been setup
483 # still need to set pdf and systematics
484 syst_settings = get_pdf_and_systematic_settings(MADGRAPH_PDFSETTING,self.isNLO) # get the pdf and systemetatic settings as a dictionary
485 self.runCardDict.update(syst_settings) # update the settings in self.runCardDict
486
487 if 'systematics_arguments' in self.runCardDict:# if there are systematics set in the dictionary
488 systematics_arguments = parse_systematics_arguments(self.runCardDict['systematics_arguments'])
489 if 'weight_info' not in systematics_arguments: #if there is no event weighting information in the system arguments
490 mglog.info('Enforcing systematic weight name convention')
491 dyn = None
492 if '--dyn' in systematics_arguments or ' dyn' in systematics_arguments: #check if dynamics are set in the system arguments and sets dyn to that value.
493 if '--dyn' in systematics_arguments:
494 dyn = systematics_arguments.split('--dyn')[1]
495 if ' dyn' in systematics_arguments:
496 dyn = systematics_arguments.split(' dyn')[1]
497 dyn = dyn.replace('\'',' ').replace('=',' ').split()[0]
498 if dyn is not None and len(dyn.split(','))>1: #if there are dynamics defined, set event weights to acordingly
499 systematics_arguments['weight_info'] = SYSTEMATICS_WEIGHT_INFO_ALTDYNSCALES
500 else:
501 systematics_arguments['weight_info'] = SYSTEMATICS_WEIGHT_INFO
502 self.runCardDict['systematics_arguments'] = write_systematics_arguments(systematics_arguments)
503 # If the rocess is LO, we want to set a 'python_seed' in self.runCarDict
504 if not self.isNLO:
505 if 'python_seed' not in self.runCardDict:
506 mglog.warning('No python seed set in run_card -- adding one with same value as iseed')
507 self.runCardDict['python_seed'] = self.runCardDict['iseed'] # if there is no python_seed defined, set it to the same value as 'iseed'
508
509
510 # consistency check of 4/5 flavour shceme settings
511 FS_updates={}
512 proton_5flav = False
513 jet_5flav = False
514 with open(self.process_dir+'/Cards/proc_card_mg5.dat', 'r') as file: # This will be updated at a later point when we have added a proc_card_mg5.dat dictionary
515 content = file.readlines()
516 #we want to read int he proc_card to determine if it is a 4 or 5 flavour scheme
517 for rawline in content:
518 line = rawline.split('#')[0] #ignore commented lines
519 if line.startswith("define p"): # if we define the quarks in a proton
520 if ('b' in line.split() and 'b~' in line.split()) or ('5' in line.split() and '-5' in line.split()):
521 #if there a b and anti b-quarks defined with p we set proton 5flavour scheme to be true
522 proton_5flav = True
523 if 'j' in line.split() and jet_5flav: #if jet is defined in the proton, set proton 5 flavour to be true
524 proton_5flav = True
525 if line.startswith("define j"): # if we are defining jets
526 if ('b' in line.split() and 'b~' in line.split()) or ('5' in line.split() and '-5' in line.split()):
527 # if b and anti b-quarks are defined in jets, set jet 5 flavour scheme to be true.
528 jet_5flav = True
529 if 'p' in line.split() and proton_5flav: # if p is defined in jets and proton has been set to 5 flavour scheme then jet_5flav = True
530 jet_5flav = True
531 if proton_5flav or jet_5flav: #If either of the proton or jet have been set to the 5 flavour scheme, set asrwgtflavour dictionary entry to 5
532 FS_updates['asrwgtflavor'] = 5
533 # Before continuing, we must ensure that both proton and jet have the same colour scheme. If they are inconsistent, we assume 5 flavour scheme.
534 if not proton_5flav:
535 mglog.warning('Found 5-flavour jets but 4-flavour proton. This is inconsistent - please pick one.')
536 mglog.warning('Will proceed assuming 5-flavour scheme.')
537 if not jet_5flav:
538 mglog.warning('Found 5-flavour protons but 4-flavour jets. This is inconsistent - please pick one.')
539 mglog.warning('Will proceed assuming 5-flavour scheme.')
540 else: # otherwise set to 4 flavour scheme
541 FS_updates['asrwgtflavor'] = 4
542
543 if len(FS_updates)==0: #if we cannot determine the flavour scheme
544 mglog.warning(f'Could not identify 4- or 5-flavor scheme from process card {self.process_dir}/Cards/proc_card_mg5.dat')
545
546 #check if there is a setting in self.runCardDict for flavour scheme
547 if 'asrwgtflavor' in self.runCardDict or 'maxjetflavor' in self.runCardDict or 'pdgs_for_merging_cut' in self.runCardDict:
548 if FS_updates['asrwgtflavor'] == 5:
549 # Process card says we are in the five-flavor scheme
550 if ('asrwgtflavor' in self.runCardDict and int(self.runCardDict['asrwgtflavor']) != 5) or ('maxjetflavor' in self.runCardDict and int(self.runCardDict['maxjetflavor']) != 5) or ('pdgs_for_merging_cut' in self.runCardDict and '5' not in self.runCardDict['pdgs_for_merging_cut']):
551 # Inconsistent setting detected; warn the users and correct the settings
552 mglog.warning('b and b~ included in p and j for 5-flavor scheme but run card settings are inconsistent; adjusting run card')
553 run_card_updates = {'asrwgtflavor': 5, 'maxjetflavor': 5, 'pdgs_for_merging_cut': '1, 2, 3, 4, 5, 21'}
554 #If there is an inconsistency, update to be consistent with Process card
555 self.runCardDict.update( run_card_updates )
556 modify_param_card(process_dir=self.process_dir, params={'MASS': {'5': '0.000000e+00'}})
557 else:
558 mglog.debug('Consistent 5-flavor scheme setup detected.')
559
560 if FS_updates['asrwgtflavor'] == 4:
561 # Process card says we are in the four-flavor scheme
562 if ('asrwgtflavor' in self.runCardDict and int(self.runCardDict['asrwgtflavor']) != 4) or ('maxjetflavor' in self.runCardDict and int(self.runCardDict['maxjetflavor']) != 4) or ('pdgs_for_merging_cut' in self.runCardDict and '5' in self.runCardDict['pdgs_for_merging_cut']):
563 # Inconsistent setting detected; warn the users and correct the settings
564 mglog.warning('b and b~ not included in p and j (4-flavor scheme) but run card settings are inconsistent; adjusting run card')
565 run_card_updates = {'asrwgtflavor': 4, 'maxjetflavor': 4, 'pdgs_for_merging_cut': '1, 2, 3, 4, 21'}
566 #update cards to be consistent with Process Card
567 self.runCardDict.update( run_card_updates )
568 modify_param_card(process_dir=self.process_dir, params={'MASS': {'5': '4.700000e+00'}})
569 else:
570 mglog.debug('Consistent 4-flavor scheme setup detected.')
571 else:
572 # Flavor scheme setup is missing, adding by hand
573 if FS_updates['asrwgtflavor'] == 4:
574 # Warn the users and add the settings according to process card
575 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.')
576 run_card_updates = {'asrwgtflavor': 4, 'maxjetflavor': 4, 'pdgs_for_merging_cut': '1, 2, 3, 4, 21'}
577 self.runCardDict.update( run_card_updates )
578 modify_param_card(process_dir=self.process_dir, params={'MASS': {'5': '4.700000e+00'}})
579 elif FS_updates['asrwgtflavor'] == 5:
580 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.')
581 run_card_updates = {'asrwgtflavor': 5, 'maxjetflavor': 5, 'pdgs_for_merging_cut': '1, 2, 3, 4, 5, 21'}
582 self.runCardDict.update( run_card_updates )
583 modify_param_card(process_dir=self.process_dir, params={'MASS': {'5': '0.000000e+00'}})
584
585 mglog.info('Finished checking run card - All OK!')
586
587
588 #==================================================================================
589 # check whether a configuration is in agreement with base fragment
590 # true if nothing needs to be done
591 # false if still needs setup
592 # error if inconsistent config
593 def base_fragment_setup_check(self,the_base_fragment,extras,isNLO):
594 # no include: allow it (with warning), as long as lhapdf is used
595 # if not (e.g. because no choice was made and the internal pdf ise used): error
596 if the_base_fragment is None:
597 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 !!!')
598 if not extras.get('pdlabel', None) == 'lhapdf' or 'lhaid' not in extras:
599 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 !!!')
600 #TODO: in the future this should be an error
601 #raise RuntimeError('No pdf base fragment was included in your job options and you did not specify a LHAPDF yourself')
602 return True
603 else:
604 # if setting is already exactly as it should be -- great!
605 correct_settings=get_pdf_and_systematic_settings(the_base_fragment,isNLO)
606
607 allgood=True
608 for s in correct_settings:
609 if s is None and s in extras:
610 allgood=False
611 break
612 if s not in extras or extras[s]!=correct_settings[s]:
613 allgood=False
614 break
615 if allgood:
616 return True
617 # no error but also nothing set
618 return False
write_configCard(self)
Definition MGC.py:383
compare_runCardCasing(self)
Definition MGC.py:411
get_config_cardloc(self)
Definition MGC.py:219
list run_card_params
Definition MGC.py:48
write_runCard(self, runArgs=None)
Definition MGC.py:306
get_runArgs_info(self, runArgs)
Definition MGC.py:267
setup_path_protection(self)
Definition MGC.py:171
base_fragment_setup_check(self, the_base_fragment, extras, isNLO)
Definition MGC.py:593
__init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False)
Definition MGC.py:37
list MADGRAPH_COMMAND_STACK
Definition MGC.py:83
add_runArgs(self, runArgs=None)
Definition MGC.py:285
dict configCardDict
Definition MGC.py:251
run_card_consistency_check(self)
Definition MGC.py:445
getConfigFromPath(self, card_loc, lowercase=False)
Definition MGC.py:243
getRunCardDict(self, lowercase=False)
Definition MGC.py:189
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177