ATLAS Offline Software
powheg_control.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 
3 import collections
4 import os
5 from . import processes
6 from AthenaCommon import Logging
7 from .decorators import timed
8 from .algorithms import Scheduler
9 from .utility import HeartbeatTimer
10 
11 
12 logger = Logging.logging.getLogger("PowhegControl")
13 
14 
15 
17  '''
18  Helper function to format QCD scale value
19  to conform to the ATLAS weight variation
20  naming scheme if possible.
21  Given a scale factor as a float, it returns
22  a string that is:
23  - '0.5'
24  - '1'
25  - '2'
26  - or the float rounded to two digits after the decimal point if it is not equal to one of the above
27  '''
28  try:
29  return {0.5 : '0.5', 1.0 : '1', 2.0 : '2'}[factor]
30  except KeyError:
31  return '{0:.2f}'.format(factor)
32 
33 
34 
36  """! Provides PowhegConfig objects which are user-configurable in the jobOptions.
37 
38  All subprocesses inherit from this class.
39 
40  @author James Robinson <james.robinson@cern.ch>
41  """
42 
43  def __init__(self, process_name, run_args=None, run_opts=None):
44  """! Constructor.
45 
46  @param run_args Generate_tf run arguments
47  @param run_opts athena run options
48  """
49 
50  self.__run_directory = os.environ["PWD"]
51 
52 
53  self.__output_LHE_file = "PowhegOTF._1.events"
54 
55 
56  self.__event_weight_groups = collections.OrderedDict()
57 
58 
60 
61  # Load run arguments
62  process_kwargs = {"cores": int(os.environ.pop("ATHENA_PROC_NUMBER", 1))}
63  if run_args is None:
64  logger.warning("No run arguments found! Using defaults.")
65  else:
66  # Read values from run_args
67  if hasattr(run_args, "ecmEnergy"):
68  process_kwargs["beam_energy"] = 0.5 * run_args.ecmEnergy
69  if hasattr(run_args, "maxEvents") and run_args.maxEvents > 0:
70  if hasattr(run_args, "outputEVNTFile") or hasattr(run_args, "outputYODAFile"):
71  process_kwargs["nEvents"] = int(1.1 * run_args.maxEvents + 0.5)
72  else:# default nEvents value is maxEvents for lhe-only production
73  process_kwargs["nEvents"] = run_args.maxEvents
74  else:#default is 10k events if no --maxEvents was set
75  if hasattr(run_args, "outputEVNTFile") or hasattr(run_args, "outputYODAFile"):
76  process_kwargs["nEvents"] = 11000 # 10% safety factor if we shower the events
77  else:
78  process_kwargs["nEvents"] = 10000 # no safety factor for lhe-only production
79  if hasattr(run_args, "randomSeed"):
80  process_kwargs["random_seed"] = run_args.randomSeed
81  if hasattr(run_args, "outputTXTFile"):
82  for tarball_suffix in [x for x in [".tar.gz", ".tgz"] if x in run_args.outputTXTFile]:
83 
84  self.__output_LHE_file = run_args.outputTXTFile.split(tarball_suffix)[0] + ".events"
85  self.scheduler.add("output tarball preparer", self.__output_LHE_file, run_args.outputTXTFile)
86  # Set inputGeneratorFile to match output events file - otherwise Generate_tf check will fail
87  run_args.inputGeneratorFile = self.__output_LHE_file
88 
89  # Load correct process
90  self.process = getattr(processes.powheg, process_name)(os.environ["POWHEGPATH"].replace("POWHEG-BOX", ""), **process_kwargs)
91 
92  # check if pre-made integration grids will be used or not
93  self.process.check_using_integration_files()
94 
95  # Expose all keyword parameters as attributes of this config object
96  for parameter in self.process.parameters:
97  if parameter.is_visible:
98  setattr(self, parameter.name, parameter.value)
99 
100  # Expose all external parameters as attributes of this config object
101  for external in self.process.externals.values():
102  for parameter in external.parameters:
103  if parameter.is_visible:
104  setattr(self, parameter.name, parameter.value)
105 
106  # Add appropriate directory cleanup for this process
107  self.scheduler.add("directory cleaner", self.process)
108 
109  # Schedule correct output file naming
110  self.scheduler.add("output file renamer", self.__output_LHE_file)
111 
112  # Enable parallel mode if AthenaMP mode is enabled. Also force multistage mode for RES.
113  if self.process.cores == 1 and self.process.powheg_version != "RES":
114  self.scheduler.add("singlecore", self.process)
115  else:
116  if self.process.cores == 1:
117  logger.info("Configuring this POWHEG-BOX-RES process to run in multistage mode")
118  else:
119  # Try to modify the transform opts to suppress athenaMP mode
120  logger.info("This job is running with an athenaMP-like whole-node setup, requesting {} cores".format(self.process.cores))
121  if hasattr(run_opts, "nprocs"):
122  logger.info("Re-configuring to keep athena running serially while parallelising POWHEG-BOX generation.")
123  run_opts.nprocs = 0
124  else:
125  logger.warning("Running in multicore mode but no 'nprocs' option was provided!")
126  self.scheduler.add("multicore", self.process)
127  self.scheduler.add("merge output", self.process.cores, self.nEvents)
128  list(self.process.parameters_by_name("manyseeds"))[0].value = 1
129 
130  # Freeze the interface so that no new attributes can be added
131  self.interface_frozen = True
132 
133  # Print executable being used
134  logger.info("Configured for event generation with: {}".format(self.process.executable))
135 
136  def generate(self, create_run_card_only=False, save_integration_grids=True, use_external_run_card=False, remove_oldStyle_rwt_comments=False):
137  """! Run normal event generation.
138 
139  @param create_run_card_only Only generate the run card.
140  @param save_integration_grids Save the integration grids for future reuse.
141  @param use_external_run_card Use a user-provided Powheg run card (powheg.input).
142  @param remove_oldStyle_rwt_comments Removes old-style '#rwgt', '#pdf', '#new weight', '#matching', and ' #Random' comments in lhe files (kept by default despite using xml reweighting).
143  """
144  # we are now always using xml reweighting - set this to False if you still want the old style
145  self.process.use_XML_reweighting = True
146 
147  self.process.remove_oldStyle_rwt_comments = remove_oldStyle_rwt_comments
148 
149  # Schedule integration gridpack creator if requested
150  if save_integration_grids:
151  self.scheduler.add("integration gridpack creator", self.process)
152 
153  # Run appropriate generation functions
154  if not use_external_run_card:
155  self._generate_run_card()
156  else:
157  logger.warning("Using native Powheg run card (must be located at './powheg.input' in order for Powheg to find it!) to configure event generation, instead of PowhegControl configuration interface")
158  if not create_run_card_only:
159  self._generate_events()
160 
162  """! Initialise runcard with appropriate options."""
163  # Check that event generation is correctly set up
164  if (hasattr(self, "bornsuppfact") and self.bornsuppfact > 0.0) and (hasattr(self, "bornktmin") and self.bornktmin <= 0.0):
165  logger.warning("These settings: bornsuppfact = {} and bornktmin = {} cannot be used to generate events!".format(self.bornsuppfact, self.bornktmin))
166  logger.warning("Only fixed-order distributions can be produced with these settings!")
167 
168  # Scale-down number of events produced in each run if running in multicore mode
169  if self.process.cores > 1:
170  logger.info("Preparing to parallelise: running with {} jobs".format(self.process.cores))
171  self.process.prepare_to_parallelise(self.process.cores)
172 
173  # Validate any parameters which need validation/processing
174  self.process.validate_parameters()
175 
176  # Construct sorted list of configurable parameters for users - including those from external processes
177  parameters_unsorted = list(self.process.parameters)
178  for external in self.process.externals.values():
179  parameters_unsorted.extend(external.parameters)
180  parameters_sorted = [x[1] for x in sorted(dict((p.name.lower(), p) for p in parameters_unsorted).items(), key=lambda x: x[0])]
181 
182  # Print sorted list of configurable parameters
183  logger.info("=========================================================================================================")
184  logger.info("| User configurable parameters for this process |")
185  logger.info("=========================================================================================================")
186  logger.info("| Option name | ATLAS default | Description |")
187  logger.info("=========================================================================================================")
188  for parameter in [p for p in parameters_sorted if p.is_visible]:
189  _default_value = "default" if (parameter.default_value is None or parameter.default_value == "") else str(parameter.default_value)
190  logger.info("| {:<25} | {:>19} | {}".format(parameter.name, _default_value, parameter.description))
191  logger.info("========================================================================================================")
192 
193  # Print list of parameters that have been changed by the user
194  parameters_changed = [p for p in parameters_sorted if p.value is not p.default_value]
195  logger.info("In these jobOptions {} parameter(s) have been changed from their default value:".format(len(parameters_changed)))
196  for idx, parameter in enumerate(parameters_changed):
197  logger.info(" {:<3} {:<19} {:>15} => {}".format("{})".format(idx + 1), "{}:".format(parameter.name), str(parameter.default_value), parameter.value))
198 
199  # Check for parameters which can result in non-equal event weights being used
200  event_weight_options = []
201 
202  # Write out final runcard
203  run_card_path = "{}/powheg.input".format(self.__run_directory)
204  logger.info("Writing POWHEG-BOX runcard to {}".format(run_card_path))
205  with open(run_card_path, "w") as f_runcard:
206  for parameter in sorted(self.process.parameters, key=lambda p: p.keyword.lower()):
207  if parameter.name == "bornsuppfact" and parameter.value > 0:
208  event_weight_options.append(("Born-level suppression", "magnitude"))
209  if parameter.name == "withnegweights" and parameter.value > 0:
210  event_weight_options.append(("negative event weights", "sign"))
211  # PDF variations
212  if parameter.name == "PDF" and isinstance(parameter.value, collections.Iterable):
213  if "PDF_variation" not in self.__event_weight_groups.keys(): # skip if this group already exists
214  if len(parameter.value) < 2:
215  logger.error("Use 'PowhegConfig.PDF = {0}' rather than 'PowhegConfig.PDF = [{0}]'".format(parameter.value[0] if len(parameter.value) > 0 else "<value>"))
216  raise TypeError("Use 'PowhegConfig.PDF = {0}' rather than 'PowhegConfig.PDF = [{0}]'".format(parameter.value[0] if len(parameter.value) > 0 else "<value>"))
217  self.define_event_weight_group("PDF_variation", ["PDF"], combination_method="hessian")
218  for PDF in map(int, parameter.value[1:]):
219  self.add_weight_to_group("PDF_variation", "MUR1_MUF1_PDF{:d}".format(PDF), [PDF])
220  # Scale variations
221  if parameter.name in ["mu_F", "mu_R"] and isinstance(parameter.value, collections.Iterable):
222  pdfs = list(self.process.parameters_by_name("PDF"))[0].value
223  nominal_pdf = pdfs if isinstance(pdfs, int) or isinstance(pdfs, str) else pdfs[0]
224  if "scale_variation" not in self.__event_weight_groups.keys(): # skip if this group already exists
225  mu_Rs = list(self.process.parameters_by_name("mu_R"))[0].value
226  mu_Fs = list(self.process.parameters_by_name("mu_F"))[0].value
227  if len(parameter.value) < 2:
228  logger.error("Use 'PowhegConfig.{1} = {0}' rather than 'PowhegConfig.{1} = [{0}]'".format(parameter.value[0] if len(parameter.value) > 0 else "<value>", parameter.name))
229  raise TypeError("Use 'PowhegConfig.{1} = {0}' rather than 'PowhegConfig.{1} = [{0}]'".format(parameter.value[0] if len(parameter.value) > 0 else "<value>", parameter.name))
230  if not isinstance(mu_Rs, collections.Iterable) or not isinstance(mu_Fs, collections.Iterable) or len(mu_Rs) is not len(mu_Fs):
231  logger.error("Number of mu_R and mu_F variations must be the same.")
232  raise ValueError("Number of mu_R and mu_F variations must be the same.")
233  self.define_event_weight_group("scale_variation", ["mu_R", "mu_F"], combination_method="envelope")
234  for mu_R, mu_F in zip(map(float, mu_Rs[1:]), map(float, mu_Fs[1:])):
235  mu_R_text = _format_QCD_scale_text(mu_R)
236  mu_F_text = _format_QCD_scale_text(mu_F)
237  self.add_weight_to_group("scale_variation", "MUR{mur}_MUF{muf}_PDF{nominal_pdf}".format(mur=mu_R_text, muf=mu_F_text, nominal_pdf=nominal_pdf), [mu_R, mu_F])
238  f_runcard.write("{}\n".format(parameter))
239 
240  # Schedule cross-section_calculator
241  if len(event_weight_options) > 0:
242  self.scheduler.add("cross section calculator")
243  logger.warning("POWHEG-BOX has been configured to run with {}".format(" and ".join([x[0] for x in event_weight_options])))
244  logger.warning("This means that event weights will vary in {}.".format(" and ".join([x[1] for x in event_weight_options])))
245  logger.warning("The cross-section passed to the parton shower will be inaccurate.")
246  logger.warning("Please use the cross-section printed in the log file before showering begins.")
247 
248  # Schedule reweighting if more than the nominal weight is requested, or if for_reweighting is set to 1
249  doReweighting = False
250  if len(self.__event_weight_groups) >= 1:
251  doReweighting = True
252  elif len(list(self.process.parameters_by_keyword("for_reweighting"))) == 1:
253  if self.process.parameters_by_keyword("for_reweighting")[0].value == 1:
254  logger.warning ("No more than the nominal weight is requested, but for_reweighting is set to 1")
255  logger.warning ("Therefore, reweighting is enabled anyway, otherwise virtual corrections wouldn't be included")
256  doReweighting = True
257  if doReweighting:
258  # Change the order so that scale comes first and user-defined is last
259  __ordered_event_weight_groups_list = []
260  for __key in ["scale_variation", "PDF_variation"]:
261  if __key in self.__event_weight_groups.keys():
262  __ordered_event_weight_groups_list.append((__key, self.__event_weight_groups.pop(__key)))
263  for __item in self.__event_weight_groups.items():
264  __ordered_event_weight_groups_list.append(__item)
265  self.__event_weight_groups = collections.OrderedDict(__ordered_event_weight_groups_list)
266  for group_name, event_weight_group in self.__event_weight_groups.items():
267  _n_weights = len(event_weight_group) - 3 # there are always three entries: parameter_names, combination_method and keywords
268  # Sanitise weight groups, removing any with no entries
269  if _n_weights <= 0:
270  logger.warning("Ignoring weight group '{}' as it does not have any variations defined. Check your jobOptions!".format(group_name))
271  del self.__event_weight_groups[group_name] # this is allowed because items() makes a temporary copy of the dictionary
272  # Otherwise print weight group information for the user
273  else:
274  logger.info("Adding new weight group '{}' which contains {} weights defined by varying {} parameters".format(group_name, _n_weights, len(event_weight_group["parameter_names"])))
275  for parameter_name in event_weight_group["parameter_names"]:
276  logger.info("... {}".format(parameter_name))
277  if not self.process.has_parameter(parameter_name):
278  logger.warning("Parameter '{}' does not exist for this process!".format(parameter_name))
279  raise ValueError("Parameter '{}' does not exist for this process!".format(parameter_name))
280  # Add reweighting to scheduler
281  self.scheduler.add("reweighter", self.process, self.__event_weight_groups)
282 
283  @timed("Powheg LHE event generation")
284  def _generate_events(self):
285  """! Generate events according to the scheduler."""
286  # Setup heartbeat thread
287  heartbeat = HeartbeatTimer(600., "{}/eventLoopHeartBeat.txt".format(self.__run_directory))
288  heartbeat.setName("heartbeat thread")
289  heartbeat.daemon = True # Allow program to exit if this is the only live thread
290  heartbeat.start()
291 
292  # Print executable being used
293  logger.info("Using executable: {}".format(self.process.executable))
294 
295  # Additional arguments needed by some algorithms
296  extra_args = {"quark colour fixer": [self.process]}
297 
298  # Schedule external processes (eg. MadSpin, PHOTOS, NNLO reweighting)
299  for algorithm, external in self.process.externals.items():
300  if external.needs_scheduling(self.process):
301  self.scheduler.add(algorithm, external, *extra_args.get(algorithm, []))
302 
303  # Schedule additional algorithms (eg. quark colour fixer)
304  for algorithm in self.process.algorithms:
305  self.scheduler.add(algorithm, *extra_args.get(algorithm, []))
306 
307  if len(list(self.process.parameters_by_keyword("for_reweighting"))) == 1:
308  if list(self.process.parameters_by_keyword("for_reweighting"))[0].value == 1:
309  algorithm = "LHE file nominal weight updater"
310  self.scheduler.add(algorithm, *extra_args.get(algorithm, []))
311  logger.info ("Since parameter for_reweighting was set to 1, virtual corrections are added at the reweighting stage only.")
312  logger.info ("Will run LHE file nominal weight updater so that XWGTUP value is updated with value of reweighted nominal weight.")
313 
314  # Output the schedule
315  self.scheduler.print_structure()
316 
317  # Run pre-processing
318  self.scheduler.run_preprocessors()
319 
320  # Run event generation
321  self.scheduler.run_generators()
322 
323  # Run post-processing
324  self.scheduler.run_postprocessors()
325 
326  # Kill heartbeat thread
327  heartbeat.cancel()
328 
329  def define_event_weight_group(self, group_name, parameters_to_vary, combination_method="none"):
330  """! Add a new named group of event weights.
331 
332  @exceptions ValueError Raise a ValueError if reweighting is not supported.
333 
334  @param group_name Name of the group of weights.
335  @param parameters_to_vary Names of the parameters to vary.
336  @param combination_method Method for combining the weights.
337  """
338  if not self.process.is_reweightable:
339  logger.warning("Additional event weights cannot be added by this process! Remove reweighting lines from the jobOptions.")
340  raise ValueError("Additional event weights cannot be added by this process! Remove reweighting lines from the jobOptions.")
341  self.__event_weight_groups[group_name] = collections.OrderedDict()
342  self.__event_weight_groups[group_name]["parameter_names"] = parameters_to_vary
343  self.__event_weight_groups[group_name]["combination_method"] = combination_method
344  self.__event_weight_groups[group_name]["keywords"] = [[p.keyword for p in self.process.parameters_by_name(parameter)] for parameter in parameters_to_vary]
345 
346  def add_weight_to_group(self, group_name, weight_name, parameter_values):
347  """! Add a new event weight to an existing group.
348 
349  @param group_name Name of the group of weights that this weight belongs to.
350  @param weight_name Name of this event weight.
351  @param parameter_values Values of the parameters.
352  """
353  if group_name not in self.__event_weight_groups.keys():
354  raise ValueError("Weight group '{}' has not been defined.".format(group_name))
355  n_expected = len(self.__event_weight_groups[group_name]["parameter_names"])
356  if len(parameter_values) is not n_expected:
357  raise ValueError("Expected {} parameter values but only got {}".format(n_expected, len(parameter_values)))
358  self.__event_weight_groups[group_name][weight_name] = []
359  for parameter_name, value in zip(self.__event_weight_groups[group_name]["parameter_names"], parameter_values):
360  self.__event_weight_groups[group_name][weight_name].append((parameter_name, value))
361 
362  def __setattr__(self, key, value):
363  """! Override default attribute setting to stop users setting non-existent attributes.
364 
365  @exceptions AttributeError Raise an AttributeError if the interface is frozen
366 
367  @param key Attribute name.
368  @param value Value to set the attribute to.
369  """
370  # If this is a known parameter then keep the values in sync
371  if hasattr(self, "process"):
372  # ... for parameters of the process
373  for parameter in self.process.parameters_by_name(key):
374  parameter.ensure_default()
375  parameter.value = value
376  # ... and for parameters of any external processes
377  for external in self.process.externals.values():
378  for parameter in external.parameters_by_name(key):
379  parameter.ensure_default()
380  parameter.value = value
381  # If the interface is frozen then raise an attribute error
382  if hasattr(self, "interface_frozen") and self.interface_frozen:
383  if not hasattr(self, key):
384  raise AttributeError("This POWHEG-BOX process has no option '{}'".format(key))
385  object.__setattr__(self, key, value)
386 
387  def set_parameter_stage(self, parameterStageDict = {}):
388  logger.info("Setting parameters for the stages : {0}".format(parameterStageDict))
389 
390  self.process.parameterStageDict = parameterStageDict
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
python.powheg_control.PowhegControl.__setattr__
def __setattr__(self, key, value)
Override default attribute setting to stop users setting non-existent attributes.
Definition: powheg_control.py:362
python.powheg_control.PowhegControl.interface_frozen
interface_frozen
Definition: powheg_control.py:131
python.powheg_control.PowhegControl.__output_LHE_file
__output_LHE_file
Name of output LHE file used by Generate_tf for showering.
Definition: powheg_control.py:53
python.powheg_control.PowhegControl._generate_run_card
def _generate_run_card(self)
Initialise runcard with appropriate options.
Definition: powheg_control.py:161
python.decorators.timed.timed
def timed(name)
Decorator to output function execution time.
Definition: timed.py:12
python.powheg_control.PowhegControl.set_parameter_stage
def set_parameter_stage(self, parameterStageDict={})
Definition: powheg_control.py:387
python.powheg_control.PowhegControl.__run_directory
__run_directory
Current directory.
Definition: powheg_control.py:50
vtune_athena.format
format
Definition: vtune_athena.py:14
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.powheg_control.PowhegControl.__init__
def __init__(self, process_name, run_args=None, run_opts=None)
Constructor.
Definition: powheg_control.py:43
python.algorithms.scheduler.Scheduler
Schedule algorithms in appropriate order.
Definition: scheduler.py:14
python.powheg_control.PowhegControl.__event_weight_groups
__event_weight_groups
Dictionary of named groups of event weights.
Definition: powheg_control.py:56
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
python.powheg_control.PowhegControl.define_event_weight_group
def define_event_weight_group(self, group_name, parameters_to_vary, combination_method="none")
Add a new named group of event weights.
Definition: powheg_control.py:329
python.powheg_control.PowhegControl._generate_events
def _generate_events(self)
Generate events according to the scheduler.
Definition: powheg_control.py:284
python.powheg_control.PowhegControl.add_weight_to_group
def add_weight_to_group(self, group_name, weight_name, parameter_values)
Add a new event weight to an existing group.
Definition: powheg_control.py:346
python.powheg_control.PowhegControl.scheduler
scheduler
Scheduler to determine algorithm ordering.
Definition: powheg_control.py:59
add
bool add(const std::string &hname, TKey *tobj)
Definition: fastadd.cxx:55
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
python.powheg_control._format_QCD_scale_text
def _format_QCD_scale_text(factor)
Definition: powheg_control.py:16
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
python.powheg_control.PowhegControl
Provides PowhegConfig objects which are user-configurable in the jobOptions.
Definition: powheg_control.py:35
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
Trk::open
@ open
Definition: BinningType.h:40
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.Bindings.keys
keys
Definition: Control/AthenaPython/python/Bindings.py:790
python.powheg_control.PowhegControl.process
process
Definition: powheg_control.py:90
python.powheg_control.PowhegControl.generate
def generate(self, create_run_card_only=False, save_integration_grids=True, use_external_run_card=False, remove_oldStyle_rwt_comments=False)
Run normal event generation.
Definition: powheg_control.py:136