ATLAS Offline Software
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 
11 import os,time,subprocess,glob,re,sys # noqa: F401
12 from AthenaCommon import Logging
13 from MadGraphControl.MadGraphUtilsHelpers import getDictFromCard,is_version_or_newer,error_check,setup_path_protection,is_NLO_run
14 from MadGraphControl.MadGraphParamHelpers import do_PMG_updates
15 mglog = Logging.logging.getLogger('MadGraphUtils')
16 
17 # Name of python executable
18 python='python'
19 # Magic name of gridpack directory
20 MADGRAPH_GRIDPACK_LOCATION='madevent'
21 # Name for the run (since we only have 1, just needs consistency)
22 MADGRAPH_RUN_NAME='run_01'
23 # For error handling
24 MADGRAPH_CATCH_ERRORS=True
25 # PDF setting (global setting)
26 MADGRAPH_PDFSETTING=None
27 
28 
32 MADGRAPH_DEVICES=None
33 
34 class MGControl:
35  def __init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False):
36  """ Generate a new process in madgraph.
37  Pass a process string.
38  Optionally request JPEGs to be kept and request for PMG settings to be used in the param card
39  Return the name of the process directory.
40  """
41  self.mglog = Logging.logging.getLogger('MadGraphUtils')
42  self.process = process
43  self.plugin = plugin
44  self.keepJpegs = keepJpegs
45  self.usePMGSettings = usePMGSettings
46  self.run_card_params = []
47  #is_gen_from gridpack
48  self.is_gen_from_gridpack = os.access(MADGRAPH_GRIDPACK_LOCATION,os.R_OK)
49  # Don't run if generating events from gridpack
50  if self.is_gen_from_gridpack:
51  self.process_dir = MADGRAPH_GRIDPACK_LOCATION
52  return
53  # Actually just sent the process card contents - let's make a card
54  card_loc='proc_card_mg5.dat'
55  mglog.info('Writing process card to '+card_loc)
56  a_card = open( card_loc , 'w' )
57  for l in process.split('\n'):
58  if 'output' not in l:
59  a_card.write(l+'\n')
60  else:
61  # Special handling for output line
62  outline = l.strip()
63  if '-nojpeg' not in l and not keepJpegs:
64  # We need to add -nojpeg somehow
65  if '#' in l:
66  outline = outline.split('#')[0]+' -nojpeg #'+outline.split('#')[1]
67  else:
68  outline = outline + ' -nojpeg'
69  # Special handling for devises
70  if MADGRAPH_DEVICES is not None:
71  if MADGRAPH_DEVICES.lower() in ['madevent_simd','madevent_gpu']:
72  outline = 'output '+MADGRAPH_DEVICES.lower()+' '+outline.split('output')[1]
73  elif MADGRAPH_DEVICES.lower() == 'max':
74  self.mglog.warning('Not fully implemented yet; setting avx')
75  outline = 'output madevent_simd '+outline.split('output')[1]
76  a_card.write(outline+'\n')
77  a_card.close()
78 
79  madpath=os.environ['MADPATH']
80  # Just in case
82 
83  # Check if we have a special output directory
84  process_dir = ''
85  for l in process.split('\n'):
86  # Look for an output line
87  if 'output' not in l.split('#')[0].split():
88  continue
89  # Check how many things before the options start
90  tmplist = l.split('#')[0].split(' -')[0]
91  # if two things, second is the directory
92  if len(tmplist.split())==2:
93  process_dir = tmplist.split()[1]
94  # if three things, third is the directory (second is the format)
95  elif len(tmplist.split())==3:
96  process_dir = tmplist.split()[2]
97  # See if we got a directory
98  if ''!=process_dir:
99  mglog.info('Saw that you asked for a special output directory: '+str(process_dir))
100  break
101 
102  mglog.info('Started process generation at '+str(time.asctime()))
103 
104  plugin_cmd = '--mode='+plugin if plugin is not None else ''
105 
106  # Note special handling here to explicitly print the process
107  self.MADGRAPH_COMMAND_STACK = [] #want to change to variable
108  self.MADGRAPH_COMMAND_STACK += ['# All jobs should start in a clean directory']
109  self.MADGRAPH_COMMAND_STACK += ['mkdir standalone_test; cd standalone_test']
110  self.MADGRAPH_COMMAND_STACK += [' '.join([python,madpath+'/bin/mg5_aMC '+plugin_cmd+' << EOF\n'+process+'\nEOF\n'])]
111  generate = subprocess.Popen([python,madpath+'/bin/mg5_aMC',plugin_cmd,card_loc],stdin=subprocess.PIPE,stderr=subprocess.PIPE if MADGRAPH_CATCH_ERRORS else None)
112  (out,err) = generate.communicate()
113  error_check(err,generate.returncode)
114 
115  mglog.info('Finished process generation at '+str(time.asctime()))
116 
117  # at this point process_dir is for sure defined - it's equal to '' in the worst case
118  if process_dir == '': # no user-defined value, need to find the directory created by MadGraph5
119  for adir in sorted(glob.glob( os.getcwd()+'/*PROC*' ),reverse=True):
120  if os.access('%s/SubProcesses/subproc.mg'%adir,os.R_OK):
121  if process_dir=='':
122  process_dir=adir
123  else:
124  mglog.warning('Additional possible process directory, '+adir+' found. Had '+process_dir)
125  mglog.warning('Likely this is because you did not run from a clean directory, and this may cause errors later.')
126  else: # user-defined directory
127  if not os.access('%s/SubProcesses/subproc.mg'%process_dir,os.R_OK):
128  raise RuntimeError('No diagrams for this process in user-define dir='+str(process_dir))
129  if process_dir=='':
130  raise RuntimeError('No diagrams for this process from list: '+str(sorted(glob.glob(os.getcwd()+'/*PROC*'),reverse=True)))
131 
132  # Special catch related to path setting and using afs
133  needed_options = ['ninja','collier','fastjet','lhapdf','syscalc_path']
134  in_config = open(os.environ['MADPATH']+'/input/mg5_configuration.txt','r')
135  option_paths = {}
136  for l in in_config.readlines():
137  for o in needed_options:
138  if o+' =' in l.split('#')[0] and 'MCGenerators' in l.split('#')[0]:
139  old_path = l.split('#')[0].split('=')[1].strip().split('MCGenerators')[1]
140  old_path = old_path[ old_path.find('/') : ]
141  if o =='lhapdf' and 'LHAPATH' in os.environ:
142  # Patch for LHAPDF version
143  version = os.environ['LHAPATH'].split('lhapdf/')[1].split('/')[0]
144  old_version = old_path.split('lhapdf/')[1].split('/')[0]
145  old_path = old_path.replace(old_version,version)
146  if o=='ninja':
147  # Patch for stupid naming problem
148  old_path.replace('gosam_contrib','gosam-contrib')
149  option_paths[o] = os.environ['MADPATH'].split('madgraph5amc')[0]+old_path
150  # Check to see if the option has been commented out
151  if o+' =' in l and o+' =' not in l.split('#')[0]:
152  mglog.info('Option '+o+' appears commented out in the config file')
153 
154  in_config.close()
155  for o in needed_options:
156  if o not in option_paths:
157  mglog.info('Path for option '+o+' not found in original config')
158 
159  mglog.info('Modifying config paths to avoid use of afs:')
160  mglog.info(option_paths)
161 
162  # Load up the run card dictionary
163  self.runCardDict = getDictFromCard(process_dir+'/Cards/run_card.dat')
164 
165  # Set the paths appropriately
166  self.change_config_card(process_dir=process_dir,settings=option_paths,set_commented=False)
167  # Done modifying paths
168 
169  # If requested, apply PMG default settings
170  if usePMGSettings:
171  do_PMG_updates(process_dir)
172 
173  # After 2.9.3, enforce the standard default sde_strategy, so that this won't randomly change on the user
174  if is_version_or_newer([2,9,3]) and not is_NLO_run(process_dir=process_dir):
175  mglog.info('Setting default sde_strategy to old default (1)')
176  self.runCardDict['sde_strategy']=1
177 
178  #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.
179  self.change_config_card(process_dir=process_dir,settings={'notification_center':'False'})
180 
181  # Add some custom settings based on the device requests
182  if MADGRAPH_DEVICES is not None:
183  if MADGRAPH_DEVICES.lower()=='madevent_simd':
184  self.runCardDict['cudacpp_backend'] = 'cppauto'
185  elif MADGRAPH_DEVICES.lower()=='madevent_gpu':
186  self.runCardDict['cudacpp_backend'] = 'cuda'
187  # In case we have "too new" a gcc version for the nvcc version on the node, which should be ok
188  # This patch should be temporary, but is fine while we are validating things at least
189  os.environ['ALLOW_UNSUPPORTED_COMPILER_IN_CUDA'] = 'Y'
190  elif MADGRAPH_DEVICES.lower() == 'max':
191  self.mglog.warning('Not fully implemented yet; setting avx')
192  self.runCardDict['cudacpp_backend'] = 'cppauto'
193 
194  # Make sure we store the resultant directory
195  self.MADGRAPH_COMMAND_STACK += ['export MGaMC_PROCESS_DIR='+os.path.basename(process_dir)]
196  self.process_dir = process_dir
197 
198  def change_run_card(self, run_card_input=None,run_card_backup=None,process_dir=MADGRAPH_GRIDPACK_LOCATION,runArgs=None,settings={},skipBaseFragment=False ):
199  self.mglog.warning('Do not call this function, just update self.run_card_params')
200  self.run_card_params += [run_card_input,run_card_backup,process_dir,runArgs,settings,skipBaseFragment ]
201 
202  def change_config_card(self, config_card_backup=None,process_dir=MADGRAPH_GRIDPACK_LOCATION,settings={},set_commented=True ):
203  self.mglog.warning('Do not call this function, just update self.run_card_params')
204  self.run_card_params += [ config_card_backup,process_dir,settings,set_commented ]
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename R::value_type > sorted(const R &r, PROJ proj={})
Helper function to create a sorted vector from an unsorted range.
python.MGC.MGControl.is_gen_from_gridpack
is_gen_from_gridpack
Definition: MGC.py:48
python.MGC.MGControl.plugin
plugin
Definition: MGC.py:43
python.MGC.MGControl.change_config_card
def change_config_card(self, config_card_backup=None, process_dir=MADGRAPH_GRIDPACK_LOCATION, settings={}, set_commented=True)
Definition: MGC.py:202
python.MadGraphParamHelpers.do_PMG_updates
def do_PMG_updates(process_dir)
Definition: MadGraphParamHelpers.py:144
python.MadGraphUtilsHelpers.is_NLO_run
def is_NLO_run(process_dir=MADGRAPH_GRIDPACK_LOCATION)
Definition: MadGraphUtilsHelpers.py:266
python.MGC.MGControl.process_dir
process_dir
Definition: MGC.py:51
python.MadGraphUtilsHelpers.setup_path_protection
def setup_path_protection()
Definition: MadGraphUtilsHelpers.py:232
python.MGC.MGControl.mglog
mglog
Definition: MGC.py:41
python.MGC.MGControl
Definition: MGC.py:34
python.MGC.MGControl.keepJpegs
keepJpegs
Definition: MGC.py:44
python.MGC.MGControl.usePMGSettings
usePMGSettings
Definition: MGC.py:45
python.MGC.MGControl.runCardDict
runCardDict
Definition: MGC.py:163
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.MadGraphUtilsHelpers.error_check
def error_check(errors_a, return_code)
Definition: MadGraphUtilsHelpers.py:119
Trk::open
@ open
Definition: BinningType.h:40
python.MGC.MGControl.MADGRAPH_COMMAND_STACK
MADGRAPH_COMMAND_STACK
Definition: MGC.py:107
str
Definition: BTagTrackIpAccessor.cxx:11
python.MGC.MGControl.change_run_card
def change_run_card(self, run_card_input=None, run_card_backup=None, process_dir=MADGRAPH_GRIDPACK_LOCATION, runArgs=None, settings={}, skipBaseFragment=False)
Definition: MGC.py:198
python.MGC.MGControl.run_card_params
run_card_params
Definition: MGC.py:46
Trk::split
@ split
Definition: LayerMaterialProperties.h:38
python.MGC.MGControl.process
process
Definition: MGC.py:42
python.MadGraphUtilsHelpers.getDictFromCard
def getDictFromCard(card_loc, lowercase=False)
Definition: MadGraphUtilsHelpers.py:14
python.MGC.MGControl.__init__
def __init__(self, process='generate p p > t t~\noutput -f', plugin=None, keepJpegs=False, usePMGSettings=False)
Definition: MGC.py:35
python.MadGraphUtilsHelpers.is_version_or_newer
def is_version_or_newer(args)
Definition: MadGraphUtilsHelpers.py:66