ATLAS Offline Software
AsgAnalysisConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2 
3 # AnaAlgorithm import(s):
4 from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
5 from AnalysisAlgorithmsConfig.ConfigSequence import groupBlocks
6 from AthenaConfiguration.Enums import LHCPeriod
7 from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType, ExpertModeWarning
8 from enum import Enum
9 import warnings
10 
11 try:
12  from AthenaCommon.Logging import logging
13 except ImportError:
14  import logging
15 
17  JETS = ['JET_']
18  JER = ['JET_JER']
19  ELECTRONS = ['EG_', 'EL_']
20  MUONS = ['MUON_']
21  PHOTONS = ['EG_', 'PH_']
22  TAUS = ['TAUS_']
23  MET = ['MET_']
24  TRACKS = ['TRK_']
25  EVENT = ['GEN_', 'PRW_']
26  FTAG = ['FT_']
27 
28 class CommonServicesConfig (ConfigBlock) :
29  """the ConfigBlock for common services
30 
31  The idea here is that all algorithms need some common services, and I should
32  provide configuration blocks for those. For now there is just a single
33  block, but in the future I might break out e.g. the systematics service.
34  """
35 
36  def __init__ (self) :
37  super (CommonServicesConfig, self).__init__ ()
38  self.addOption ('runSystematics', None, type=bool,
39  info="whether to turn on the computation of systematic variations. "
40  "The default is to run them on MC.")
41  self.addOption ('filterSystematics', None, type=str,
42  info="a regexp string against which the systematics names will be "
43  "matched. Only positive matches are retained and used in the evaluation "
44  "of the various algorithms.")
45  self.addOption ('onlySystematicsCategories', None, type=list,
46  info="a list of strings defining categories of systematics to enable "
47  "(only recommended for studies / partial ntuple productions). Choose amongst: "
48  "jets, electrons, muons, photons, taus, met, tracks, ftag, event. This option is overridden "
49  "by 'filterSystematics'.")
50  self.addOption ('systematicsHistogram', None , type=str,
51  info="the name (string) of the histogram to which a list of executed "
52  "systematics will be printed. The default is None (don't write out "
53  "the histogram).")
54  self.addOption ('separateWeightSystematics', False, type=bool,
55  info="if 'systematicsHistogram' is enabled, whether to create a separate "
56  "histogram holding only the names of weight-based systematics. This is useful "
57  "to help make histogramming frameworks more efficient by knowing in advance which "
58  "systematics need to recompute the observable and which don't.")
59  self.addOption ('enableExpertMode', False, type=bool,
60  info="allows CP experts and CPAlgorithm devs to use non-recommended configurations. "
61  "DO NOT USE FOR ANALYSIS.")
62 
63  def instanceName (self) :
64  """Return the instance name for this block"""
65  return '' # no instance name, this is a singleton
66 
67  def makeAlgs (self, config) :
68 
69  sysService = config.createService( 'CP::SystematicsSvc', 'SystematicsSvc' )
70 
71  if self.runSystematics is False :
72  runSystematics = self.runSystematics
73  elif config.noSystematics() is not None :
74  # if option not set:
75  # check to see if set in config accumulator
76  self.runSystematics = not config.noSystematics()
77  runSystematics = self.runSystematics
78  else :
79  runSystematics = True
80 
81  if runSystematics :
82  sysService.sigmaRecommended = 1
83  if config.dataType() is DataType.Data:
84  # Only one type of allowed systematics on data: the JER variations!
86  if self.onlySystematicsCategories is not None:
87  # Convert strings to enums and validate
88  requested_categories = []
89  for category_str in self.onlySystematicsCategories:
90  try:
91  category_enum = SystematicsCategories[category_str.upper()]
92  requested_categories += category_enum.value
93  except KeyError:
94  raise ValueError(f"Invalid systematics category passed to option 'onlySystematicsCategories': {category_str}. Must be one of {', '.join(category.name for category in SystematicsCategories)}")
95  # Construct regex pattern as logical-OR of category names
96  if len(requested_categories):
97  sysService.systematicsRegex = "^(?=.*(" + "|".join(requested_categories) + ")|$).*"
98  if self.filterSystematics is not None:
99  sysService.systematicsRegex = self.filterSystematics
100  config.createService( 'CP::SelectionNameSvc', 'SelectionNameSvc')
101 
102  if self.systematicsHistogram is not None:
103  # print out all systematics
104  allSysDumper = config.createAlgorithm( 'CP::SysListDumperAlg', 'SystematicsPrinter' )
105  allSysDumper.histogramName = self.systematicsHistogram
106 
107  if self.separateWeightSystematics:
108  # print out only the weight systematics (for more efficient histogramming down the line)
109  weightSysDumper = config.createAlgorithm( 'CP::SysListDumperAlg', 'OnlyWeightSystematicsPrinter' )
110  weightSysDumper.histogramName = f"{self.systematicsHistogram}OnlyWeights"
111  weightSysDumper.systematicsRegex = "^(GEN_|EL_EFF_|MUON_EFF_|PH_EFF_|TAUS_TRUEHADTAU_EFF_|FT_EFF_|extrapolation_pt_|JET_.*JvtEfficiency_|PRW_).*"
112 
113  if self.enableExpertMode and config._pass == 0:
114  # set any expert-mode errors to be ignored instead
115  warnings.simplefilter('ignore', ExpertModeWarning)
116  # just warning users they might be doing something dangerous
117  log = logging.getLogger('CommonServices')
118  bold = "\033[1m"
119  red = "\033[91m"
120  yellow = "\033[93m"
121  reset = "\033[0m"
122  log.warning(red +r"""
123  ________ _______ ______ _____ _______ __ __ ____ _____ ______ ______ _ _ ____ _ ______ _____
124  | ____\ \ / / __ \| ____| __ \__ __| | \/ |/ __ \| __ \| ____| | ____| \ | | /\ | _ \| | | ____| __ \
125  | |__ \ V /| |__) | |__ | |__) | | | | \ / | | | | | | | |__ | |__ | \| | / \ | |_) | | | |__ | | | |
126  | __| > < | ___/| __| | _ / | | | |\/| | | | | | | | __| | __| | . ` | / /\ \ | _ <| | | __| | | | |
127  | |____ / . \| | | |____| | \ \ | | | | | | |__| | |__| | |____ | |____| |\ |/ ____ \| |_) | |____| |____| |__| |
128  |______/_/ \_\_| |______|_| \_\ |_| |_| |_|\____/|_____/|______| |______|_| \_/_/ \_\____/|______|______|_____/
129 
130 """
131  +reset)
132  log.warning(f"{bold}{yellow}These settings are not recommended for analysis. Make sure you know what you're doing, or disable them with `enableExpertMode: False` in `CommonServices`.{reset}")
133 
134  import time
135  time.sleep(2)
136 
137 
138 @groupBlocks
139 def CommonServices(seq):
140  seq.append(CommonServicesConfig())
141  from AsgAnalysisAlgorithms.TruthCollectionsFixerConfig import TruthCollectionsFixerBlock
142  seq.append(TruthCollectionsFixerBlock())
143 
144 class IOStatsBlock(ConfigBlock):
145  """Print what branches are used in analysis"""
146 
147  def __init__(self):
148  super(IOStatsBlock, self).__init__()
149  self.addOption("printOption", "Summary", type=str,
150  info='option to pass the standard ROOT printing function. Can be "Summary", "ByEntries" or "ByBytes".')
151 
152  def instanceName (self) :
153  """Return the instance name for this block"""
154  return '' # no instance name, this is a singleton
155 
156  def makeAlgs(self, config):
157  alg = config.createAlgorithm('CP::IOStatsAlg', 'IOStatsAlg')
158  alg.printOption = self.printOption
159 
160 
161 class PileupReweightingBlock (ConfigBlock):
162  """the ConfigBlock for pileup reweighting"""
163 
164  def __init__ (self) :
165  super (PileupReweightingBlock, self).__init__ ()
166  self.addOption ('campaign', None, type=None,
167  info="the MC campaign for the PRW auto-configuration.")
168  self.addOption ('files', None, type=None,
169  info="the input files being processed (list of strings). "
170  "Alternative to auto-configuration.")
171  self.addOption ('useDefaultConfig', True, type=bool,
172  info="whether to use the central PRW files. The default is True.")
173  self.addOption ('userLumicalcFiles', None, type=None,
174  info="user-provided lumicalc files (list of strings). Alternative "
175  "to auto-configuration.")
176  self.addOption ('userLumicalcFilesPerCampaign', None, type=None,
177  info="user-provided lumicalc files (dictionary of list of strings, "
178  "with MC campaigns as the keys). Alternative to auto-configuration.")
179  self.addOption ('userPileupConfigs', None, type=None,
180  info="user-provided PRW files (list of strings). Alternative to "
181  "auto-configuration. Alternative to auto-configuration.")
182  self.addOption ('userPileupConfigsPerCampaign', None, type=None,
183  info="user-provided PRW files (dictionary of list of strings, with "
184  "MC campaigns as the keys)")
185  self.addOption ('postfix', '', type=str,
186  info="a postfix to apply to decorations and algorithm names. "
187  "Typically not needed unless several instances of PileupReweighting are scheduled.")
188  self.addOption ('alternativeConfig', False, type=bool,
189  info="whether this is used as an additional alternative config for PileupReweighting. "
190  "Will only store the alternative pile up weight in that case.")
191  self.addOption ('writeColumnarToolVariables', False, type=bool,
192  info="whether to add EventInfo variables needed for running the columnar tool(s) on the output n-tuple. (EXPERIMENTAL)",
193  expertMode=True)
194 
195  def instanceName (self) :
196  """Return the instance name for this block"""
197  return self.postfix
198 
199  def makeAlgs (self, config) :
200 
201  from Campaigns.Utils import Campaign
202 
203  log = logging.getLogger('makePileupAnalysisSequence')
204 
205  eventInfoVar = ['runNumber', 'eventNumber', 'actualInteractionsPerCrossing', 'averageInteractionsPerCrossing']
206  if config.dataType() is not DataType.Data:
207  eventInfoVar += ['mcChannelNumber']
208  if self.writeColumnarToolVariables:
209  # This is not strictly necessary, as the columnar users
210  # could recreate this, but it is also a single constant int,
211  # that should compress exceedingly well.
212  eventInfoVar += ['eventTypeBitmask']
213 
214  if config.isPhyslite() and not self.alternativeConfig:
215  # PHYSLITE already has these variables defined, just need to copy them to the output
216  log.info(f'Physlite does not need pileup reweighting. Variables will be copied from input instead. {config.isPhyslite}')
217  for var in eventInfoVar:
218  config.addOutputVar ('EventInfo', var, var, noSys=True)
219 
220  if config.dataType() is not DataType.Data:
221  config.addOutputVar ('EventInfo', 'PileupWeight_%SYS%', 'weight_pileup')
222  if config.geometry() is LHCPeriod.Run2:
223  config.addOutputVar ('EventInfo', 'beamSpotWeight', 'weight_beamspot', noSys=True)
224  return
225 
226  # check files from flags
227  if self.files is None and config.flags is not None:
228  self.files = config.flags.Input.Files
229 
230  campaign = self.campaign
231  # if user didn't explicitly configure campaign, let's try setting it from metadata
232  # only needed on MC
233  if config.dataType() is not DataType.Data and self.campaign is None:
234  # if we used flags, campaign is auto-determined
235  if config.campaign() is not None and config.campaign() is not Campaign.Unknown:
236  campaign = config.campaign()
237  log.info(f'Auto-configuring campaign for PRW from flags: {campaign.value}')
238  else:
239  # we try to determine campaign from files if above failed
240  if self.files is not None:
241  from Campaigns.Utils import getMCCampaign
242  campaign = getMCCampaign(self.files)
243  if campaign and campaign is not Campaign.Unknown:
244  log.info(f'Auto-configuring campaign for PRW from files: {campaign.value}')
245  else:
246  log.info('Campaign could not be determined.')
247 
248 
249  toolConfigFiles = []
250  toolLumicalcFiles = []
251 
252  # PRW config files should only be configured if we run on MC
253  # Run 4 not supported yet
254  if (config.dataType() is not DataType.Data and
255  config.geometry() is not LHCPeriod.Run4):
256  # check if user provides per-campaign pileup config list
257  if self.userPileupConfigs is not None and self.userPileupConfigsPerCampaign is not None:
258  raise ValueError('Both userPileupConfigs and userPileupConfigsPerCampaign specified, '
259  'use only one of the options!')
260  if self.userPileupConfigsPerCampaign is not None:
261  if not campaign:
262  raise Exception('userPileupConfigsPerCampaign requires campaign to be configured!')
263  if campaign is Campaign.Unknown:
264  raise Exception('userPileupConfigsPerCampaign used, but campaign = Unknown!')
265  try:
266  toolConfigFiles = self.userPileupConfigsPerCampaign[campaign.value][:]
267  log.info('Using user provided per-campaign PRW configuration')
268  except KeyError as e:
269  raise KeyError(f'Unconfigured campaign {e} for userPileupConfigsPerCampaign!')
270 
271  elif self.userPileupConfigs is not None:
272  toolConfigFiles = self.userPileupConfigs[:]
273  log.info('Using user provided PRW configuration')
274 
275  else:
276  if self.useDefaultConfig and self.files is None:
277  raise ValueError('useDefaultConfig requires files to be configured! '
278  'Either pass them as an option or use flags.')
279 
280  from PileupReweighting.AutoconfigurePRW import getConfigurationFiles
281  if campaign and campaign is not Campaign.Unknown:
282  toolConfigFiles = getConfigurationFiles(campaign=campaign,
283  files=self.files,
284  useDefaultConfig=self.useDefaultConfig,
285  data_type=config.dataType())
286  if self.useDefaultConfig:
287  log.info('Auto-configuring universal/default PRW config')
288  else:
289  log.info('Auto-configuring per-sample PRW config files based on input files')
290  else:
291  log.info('No campaign specified, no PRW config files configured')
292 
293  # check if user provides per-campaign lumical config list
294  if self.userLumicalcFilesPerCampaign is not None and self.userLumicalcFiles is not None:
295  raise ValueError('Both userLumicalcFiles and userLumicalcFilesYear specified, '
296  'use only one of the options!')
297  if self.userLumicalcFilesPerCampaign is not None:
298  try:
299  toolLumicalcFiles = self.userLumicalcFilesPerCampaign[campaign.value][:]
300  log.info('Using user-provided per-campaign lumicalc files')
301  except KeyError as e:
302  raise KeyError(f'Unconfigured campaign {e} for userLumicalcFilesPerCampaign!')
303  elif self.userLumicalcFiles is not None:
304  toolLumicalcFiles = self.userLumicalcFiles[:]
305  log.info('Using user-provided lumicalc files')
306  else:
307  if campaign and campaign is not Campaign.Unknown:
308  from PileupReweighting.AutoconfigurePRW import getLumicalcFiles
309  toolLumicalcFiles = getLumicalcFiles(campaign)
310  log.info('Using auto-configured lumicalc files')
311  else:
312  log.info('No campaign specified, no lumicalc files configured for PRW')
313  else:
314  log.info('Data needs no lumicalc and PRW configuration files')
315 
316  # Set up the only algorithm of the sequence:
317  if config.geometry() is LHCPeriod.Run4:
318  log.warning ('Pileup reweighting is not yet supported for Run 4 geometry')
319  alg = config.createAlgorithm( 'CP::EventDecoratorAlg', 'EventDecoratorAlg' )
320  alg.uint32Decorations = { 'RandomRunNumber' :
321  config.flags.Input.RunNumbers[0] }
322 
323  else:
324  alg = config.createAlgorithm( 'CP::PileupReweightingAlg',
325  'PileupReweightingAlg' )
326  config.addPrivateTool( 'pileupReweightingTool', 'CP::PileupReweightingTool' )
327  alg.pileupReweightingTool.ConfigFiles = toolConfigFiles
328  if not toolConfigFiles and config.dataType() is not DataType.Data:
329  log.info("No PRW config files provided. Disabling reweighting")
330  # Setting the weight decoration to the empty string disables the reweighting
331  alg.pileupWeightDecoration = ""
332  else:
333  alg.pileupWeightDecoration = "PileupWeight" + self.postfix + "_%SYS%"
334  alg.pileupReweightingTool.LumiCalcFiles = toolLumicalcFiles
335 
336  if not self.alternativeConfig:
337  for var in eventInfoVar:
338  config.addOutputVar ('EventInfo', var, var, noSys=True)
339 
340  if config.dataType() is not DataType.Data and config.geometry() is LHCPeriod.Run2:
341  config.addOutputVar ('EventInfo', 'beamSpotWeight', 'weight_beamspot', noSys=True)
342 
343  if config.dataType() is not DataType.Data and toolConfigFiles:
344  config.addOutputVar ('EventInfo', 'PileupWeight' + self.postfix + '_%SYS%',
345  'weight_pileup'+self.postfix)
346 
347 
348 class GeneratorAnalysisBlock (ConfigBlock):
349  """the ConfigBlock for generator algorithms"""
350 
351  def __init__ (self) :
352  super (GeneratorAnalysisBlock, self).__init__ ()
353  self.addOption ('saveCutBookkeepers', True, type=bool,
354  info="whether to save the cut bookkeepers information into the "
355  "output file. The default is True.")
356  self.addOption ('runNumber', None, type=int,
357  info="the MC runNumber (int). The default is None (autoconfigure "
358  "from metadata).")
359  self.addOption ('cutBookkeepersSystematics', None, type=bool,
360  info="whether to also save the cut bookkeepers systematics. The "
361  "default is None (follows the global systematics flag). Set to "
362  "False or True to override.")
363  self.addOption ('histPattern', None, type=str,
364  info="the histogram name pattern for the cut-bookkeeper histogram names")
365  self.addOption ('streamName', 'ANALYSIS', type=str,
366  info="name of the output stream to save the cut bookkeeper in. "
367  "The default is ANALYSIS.")
368  self.addOption ('detailedPDFinfo', False, type=bool,
369  info="save the necessary information to run the LHAPDF tool offline. "
370  "The default is False.")
371  self.addOption ('doHFProdFracReweighting', False, type=bool,
372  info="whether to apply HF production fraction reweighting. "
373  "The default is False.")
374  self.addOption ('truthParticleContainer', 'TruthParticles', type=str,
375  info="the name of the truth particle container to use for HF production fraction reweighting. "
376  "The default is 'TruthParticles'. ")
377  def instanceName (self) :
378  """Return the instance name for this block"""
379  return self.streamName
380 
381  def makeAlgs (self, config) :
382 
383  if config.dataType() is DataType.Data:
384  # there are no generator weights in data!
385  return
386  log = logging.getLogger('makeGeneratorAnalysisSequence')
387 
388  if self.runNumber is None:
389  self.runNumber = config.runNumber()
390 
391  if self.saveCutBookkeepers and not self.runNumber:
392  raise ValueError ("invalid run number: " + str(self.runNumber))
393 
394  # Set up the CutBookkeepers algorithm:
395  if self.saveCutBookkeepers:
396  alg = config.createAlgorithm('CP::AsgCutBookkeeperAlg', 'CutBookkeeperAlg')
397  alg.RootStreamName = self.streamName
398  alg.runNumber = self.runNumber
399  if self.cutBookkeepersSystematics is None:
400  alg.enableSystematics = not config.noSystematics()
401  else:
402  alg.enableSystematics = self.cutBookkeepersSystematics
403  if self.histPattern:
404  alg.histPattern = self.histPattern
405  config.addPrivateTool( 'truthWeightTool', 'PMGTools::PMGTruthWeightTool' )
406 
407  # Set up the weights algorithm:
408  alg = config.createAlgorithm( 'CP::PMGTruthWeightAlg', 'PMGTruthWeightAlg' )
409  config.addPrivateTool( 'truthWeightTool', 'PMGTools::PMGTruthWeightTool' )
410  alg.decoration = 'generatorWeight_%SYS%'
411  config.addOutputVar ('EventInfo', 'generatorWeight_%SYS%', 'weight_mc')
412 
413  if self.detailedPDFinfo:
414  alg = config.createAlgorithm( 'CP::PDFinfoAlg', 'PDFinfoAlg', reentrant=True )
415  for var in ["PDFID1","PDFID2","PDGID1","PDGID2","Q","X1","X2","XF1","XF2"]:
416  config.addOutputVar ('EventInfo', var, 'PDFinfo_' + var, noSys=True)
417 
418  if self.doHFProdFracReweighting:
419  generatorInfo = config.flags.Input.GeneratorsInfo
420  log.info(f"Loaded generator info: {generatorInfo}")
421 
422  DSID = "000000"
423 
424  if not generatorInfo:
425  log.warning("No generator info found.")
426  DSID = "000000"
427  elif isinstance(generatorInfo, dict):
428  if "Pythia8" in generatorInfo:
429  DSID = "410470"
430  elif "Sherpa" in generatorInfo and "2.2.8" in generatorInfo["Sherpa"]:
431  DSID = "421152"
432  elif "Sherpa" in generatorInfo and "2.2.10" in generatorInfo["Sherpa"]:
433  DSID = "700122"
434  elif "Sherpa" in generatorInfo and "2.2.11" in generatorInfo["Sherpa"]:
435  log.warning("HF production fraction reweighting is not configured for Sherpa 2.2.11. Using weights for Sherpa 2.2.10 instead.")
436  DSID = "700122"
437  elif "Sherpa" in generatorInfo and "2.2.12" in generatorInfo["Sherpa"]:
438  log.warning("HF production fraction reweighting is not configured for Sherpa 2.2.12. Using weights for Sherpa 2.2.10 instead.")
439  DSID = "700122"
440  elif "Sherpa" in generatorInfo and "2.2.14" in generatorInfo["Sherpa"]:
441  log.warning("HF production fraction reweighting is not configured for Sherpa 2.2.14. New weights need to be calculated.")
442  DSID = "000000"
443  elif "Sherpa" in generatorInfo and "2.2.1" in generatorInfo["Sherpa"]:
444  DSID = "410250"
445  elif "Herwig7" in generatorInfo and "7.1.3" in generatorInfo["Herwig7"]:
446  DSID = "411233"
447  elif "Herwig7" in generatorInfo and "7.2.1" in generatorInfo["Herwig7"]:
448  DSID = "600666"
449  elif "Herwig7" in generatorInfo and "7." in generatorInfo["Herwig7"]:
450  DSID = "410558"
451  elif "amc@NLO" in generatorInfo:
452  DSID = "410464"
453  else:
454  log.warning(f"HF production fraction reweighting is not configured for this generator: {generatorInfo}")
455  log.warning("New weights need to be calculated.")
456  DSID = "000000"
457  else:
458  log.warning("Failed to determine generator from metadata")
459  DSID = "000000"
460 
461  log.info(f"Using HF production fraction weights calculated using DSID {DSID}")
462  if DSID == "000000":
463  log.warning("HF production fraction reweighting will return dummy weights of 1.0")
464 
465  alg = config.createAlgorithm( 'CP::SysTruthWeightAlg', 'SysTruthWeightAlg' + self.streamName )
466  config.addPrivateTool( 'sysTruthWeightTool', 'PMGTools::PMGHFProductionFractionTool' )
467  alg.decoration = 'prodFracWeight_%SYS%'
468  alg.TruthParticleContainer = self.truthParticleContainer
469  alg.sysTruthWeightTool.ShowerGenerator = DSID
470  config.addOutputVar ('EventInfo', 'prodFracWeight_%SYS%', 'weight_HF_prod_frac')
471 
472 class PtEtaSelectionBlock (ConfigBlock):
473  """the ConfigBlock for a pt-eta selection"""
474 
475  def __init__ (self) :
476  super (PtEtaSelectionBlock, self).__init__ ()
477  self.addOption ('containerName', '', type=str,
478  noneAction='error',
479  info="the name of the input container.")
480  self.addOption ('selectionName', '', type=str,
481  noneAction='error',
482  info="the name of the selection to append this to. The default is "
483  "'' (empty string), meaning that the cuts are applied to every "
484  "object within the container. Specifying a name (e.g. loose) "
485  "applies the cut only to those object who also pass that selection.")
486  self.addOption ('minPt', None, type=float,
487  info="minimum pT value to cut on, in MeV. No default value.")
488  self.addOption ('maxPt', None, type=float,
489  info="maximum pT value to cut on, in MeV. No default value.")
490  self.addOption ('minEta', None, type=float,
491  info="minimum |eta| value to cut on. No default value.")
492  self.addOption ('maxEta', None, type=float,
493  info="maximum |eta| value to cut on. No default value.")
494  self.addOption ('maxRapidity', None, type=float,
495  info="maximum rapidity value to cut on. No default value.")
496  self.addOption ('etaGapLow', None, type=float,
497  info="low end of the |eta| gap. No default value.")
498  self.addOption ('etaGapHigh', None, type=float,
499  info="high end of the |eta| gap. No default value.")
500  self.addOption ('selectionDecoration', None, type=str,
501  info="the name of the decoration to set. If 'None', will be set "
502  "to 'selectPtEta' followed by the selection name.")
503  self.addOption ('useClusterEta', False, type=bool,
504  info="whether to use the cluster eta (etaBE(2)) instead of the object "
505  "eta (for electrons and photons). The default is False.")
506  self.addOption ('useDressedProperties', False, type=bool,
507  info="whether to use the dressed kinematic properties "
508  "(for truth particles only). The default is False.")
509 
510  def instanceName (self) :
511  """Return the instance name for this block"""
512  return self.containerName + "_" + self.selectionName
513 
514  def makeAlgs (self, config) :
515 
516  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'PtEtaSelectionAlg' )
517  config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
518  if self.minPt is not None :
519  alg.selectionTool.minPt = self.minPt
520  if self.maxPt is not None:
521  alg.selectionTool.maxPt = self.maxPt
522  if self.minEta is not None:
523  alg.selectionTool.minEta = self.minEta
524  if self.maxEta is not None :
525  alg.selectionTool.maxEta = self.maxEta
526  if self.maxRapidity is not None :
527  alg.selectionTool.maxRapidity = self.maxRapidity
528  if self.etaGapLow is not None:
529  alg.selectionTool.etaGapLow = self.etaGapLow
530  if self.etaGapHigh is not None:
531  alg.selectionTool.etaGapHigh = self.etaGapHigh
532  if self.selectionDecoration is None:
533  self.selectionDecoration = 'selectPtEta' + (f'_{self.selectionName}' if self.selectionName else '')
534  alg.selectionTool.useClusterEta = self.useClusterEta
535  alg.selectionTool.useDressedProperties = self.useDressedProperties
536  alg.selectionDecoration = self.selectionDecoration
537  alg.particles = config.readName (self.containerName)
538  alg.preselection = config.getPreselection (self.containerName, '')
539  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration)
540 
541 
542 
543 class ObjectCutFlowBlock (ConfigBlock):
544  """the ConfigBlock for an object cutflow"""
545 
546  def __init__ (self) :
547  super (ObjectCutFlowBlock, self).__init__ ()
548  self.addOption ('containerName', '', type=str,
549  noneAction='error',
550  info="the name of the input container.")
551  self.addOption ('selectionName', '', type=str,
552  noneAction='error',
553  info="the name of the selection to perform the cutflow for. The "
554  "default is '' (empty string), meaning that the cutflow is "
555  "performed for every object within the container. Specifying a "
556  "name (e.g. loose) generates the cutflow only for those object "
557  "that also pass that selection.")
558  self.addOption ('forceCutSequence', False, type=bool,
559  info="whether to force the cut sequence and not accept objects "
560  "if previous cuts failed. The default is False.")
561 
562  def instanceName (self) :
563  """Return the instance name for this block"""
564  return self.containerName + '_' + self.selectionName
565 
566  def makeAlgs (self, config) :
567 
568  alg = config.createAlgorithm( 'CP::ObjectCutFlowHistAlg', 'CutFlowDumperAlg' )
569  alg.histPattern = 'cflow_' + self.containerName + "_" + self.selectionName + '_%SYS%'
570  alg.selections = config.getSelectionCutFlow (self.containerName, self.selectionName)
571  alg.input = config.readName (self.containerName)
572  alg.histTitle = "Object Cutflow: " + self.containerName + "." + self.selectionName
573  alg.forceCutSequence = self.forceCutSequence
574 
575 
576 class EventCutFlowBlock (ConfigBlock):
577  """the ConfigBlock for an event-level cutflow"""
578 
579  def __init__ (self) :
580  super (EventCutFlowBlock, self).__init__ ()
581  self.addOption ('containerName', '', type=str,
582  noneAction='error',
583  info="the name of the input container, typically EventInfo.")
584  self.addOption ('selectionName', '', type=str,
585  noneAction='error',
586  info="the name of an optional selection decoration to use.")
587  self.addOption ('customSelections', [], type=None,
588  info="the selections for which to generate cutflow histograms. If "
589  "a single string, corresponding to a particular event selection, "
590  "the event cutflow for that selection will be looked up. If a list "
591  "of strings, will use explicitly those selections. If left blank, "
592  "all selections attached to the container will be looked up.")
593  self.addOption ('postfix', '', type=str,
594  info="a postfix to apply in the naming of cutflow histograms. Set "
595  "it when defining multiple cutflows.")
596 
597  def instanceName (self) :
598  """Return the instance name for this block"""
599  return self.containerName + '_' + self.selectionName + self.postfix
600 
601  def makeAlgs (self, config) :
602 
603  postfix = self.postfix
604  if postfix != '' and postfix[0] != '_' :
605  postfix = '_' + postfix
606 
607  alg = config.createAlgorithm( 'CP::EventCutFlowHistAlg', 'CutFlowDumperAlg' )
608  alg.histPattern = 'cflow_' + self.containerName + "_" + self.selectionName + postfix + '_%SYS%'
609  # find out which selection decorations to use
610  if isinstance(self.customSelections, str):
611  # user provides a dynamic reference to selections, corresponding to an EventSelection alg
612  alg.selections = config.getEventCutFlow(self.customSelections)
613  elif len(self.customSelections) > 0:
614  # user provides a list of hardcoded selections
615  alg.selections = self.customSelections
616  else:
617  # user provides nothing: get all available selections from EventInfo directly
618  alg.selections = config.getSelectionCutFlow (self.containerName, self.selectionName)
619  alg.selections = [sel+',as_char' for sel in alg.selections]
620  if self.selectionName:
621  alg.preselection = self.selectionName + '_%SYS%'
622  alg.eventInfo = config.readName (self.containerName)
623  alg.histTitle = "Event Cutflow: " + self.containerName + "." + self.selectionName
624 
625 
626 class OutputThinningBlock (ConfigBlock):
627  """the ConfigBlock for output thinning"""
628 
629  def __init__ (self) :
630  super (OutputThinningBlock, self).__init__ ()
631  self.addOption ('containerName', '', type=str,
632  noneAction='error',
633  info="the name of the input container.")
634  self.addOption ('postfix', '', type=str,
635  info="a postfix to apply to decorations and algorithm names. "
636  "Typically not needed here.")
637  self.addOption ('selection', '', type=str,
638  info="the name of an optional selection decoration to use.")
639  self.addOption ('selectionName', '', type=str,
640  info="the name of the selection to append this to. The default is "
641  "'' (empty string), meaning that the cuts are applied to every "
642  "object within the container. Specifying a name (e.g. loose) "
643  "applies the cut only to those object who also pass that selection.")
644  self.addOption ('outputName', None, type=str,
645  info="an optional name for the output container.")
646  # TODO: add info string
647  self.addOption ('deepCopy', False, type=bool,
648  info="")
649  self.addOption ('sortPt', False, type=bool,
650  info="whether to sort objects in pt")
651  # TODO: add info string
652  self.addOption ('noUniformSelection', False, type=bool,
653  info="")
654 
655  def instanceName (self) :
656  """Return the instance name for this block"""
657  return self.containerName + '_' + self.selectionName + self.postfix
658 
659  def makeAlgs (self, config) :
660 
661  postfix = self.postfix
662  if postfix != '' and postfix[0] != '_' :
663  postfix = '_' + postfix
664 
665  selection = config.getFullSelection (self.containerName, self.selectionName)
666  if selection == '' :
667  selection = self.selection
668  elif self.selection != '' :
669  selection = selection + '&&' + self.selection
670 
671  if selection != '' and not self.noUniformSelection :
672  alg = config.createAlgorithm( 'CP::AsgUnionSelectionAlg', 'UnionSelectionAlg')
673  alg.preselection = selection
674  alg.particles = config.readName (self.containerName)
675  alg.selectionDecoration = 'outputSelect' + postfix
676  config.addSelection (self.containerName, alg.selectionDecoration, selection)
677  selection = 'outputSelect' + postfix
678 
679  alg = config.createAlgorithm( 'CP::AsgViewFromSelectionAlg', 'DeepCopyAlg' )
680  alg.input = config.readName (self.containerName)
681  if self.outputName is not None :
682  alg.output = self.outputName + '_%SYS%'
683  config.addOutputContainer (self.containerName, self.outputName)
684  else :
685  alg.output = config.copyName (self.containerName)
686  if selection != '' :
687  alg.selection = [selection]
688  else :
689  alg.selection = []
690  alg.deepCopy = self.deepCopy
691  if self.sortPt and not config.noSystematics() :
692  raise ValueError ("Sorting by pt is not supported with systematics")
693  alg.sortPt = self.sortPt
694 
695 
696 class IFFLeptonDecorationBlock (ConfigBlock):
697  """the ConfigBlock for the IFF classification of leptons"""
698 
699  def __init__ (self) :
700  super (IFFLeptonDecorationBlock, self).__init__()
701  self.addOption ('containerName', '', type=str,
702  noneAction='error',
703  info="the name of the input electron or muon container.")
704  self.addOption ('separateChargeFlipElectrons', True, type=bool,
705  info="whether to consider charged-flip electrons as a separate class. "
706  "The default is True (recommended).")
707  self.addOption ('decoration', 'IFFClass_%SYS%', type=str,
708  info="the name (str) of the decoration set by the IFF "
709  "TruthClassificationTool. The default is 'IFFClass_%SYS%'.")
710  # Always skip on data
711  self.setOptionValue('skipOnData', True)
712 
713  def instanceName (self) :
714  """Return the instance name for this block"""
715  return self.containerName
716 
717  def makeAlgs (self, config) :
718  particles = config.readName(self.containerName)
719 
720  alg = config.createAlgorithm( 'CP::AsgClassificationDecorationAlg', 'IFFClassifierAlg' )
721  # the IFF classification tool
722  config.addPrivateTool( 'tool', 'TruthClassificationTool')
723  # label charge-flipped electrons as such
724  alg.tool.separateChargeFlipElectrons = self.separateChargeFlipElectrons
725  alg.decoration = self.decoration
726  alg.particles = particles
727 
728  # write the decoration only once to the output
729  config.addOutputVar(self.containerName, alg.decoration, alg.decoration.split("_%SYS%")[0], noSys=True)
730 
731 
732 class MCTCLeptonDecorationBlock (ConfigBlock):
733 
734  def __init__ (self) :
735  super (MCTCLeptonDecorationBlock, self).__init__ ()
736 
737  self.addOption ("containerName", '', type=str,
738  noneAction='error',
739  info="the input lepton container, with a possible selection, "
740  "in the format container or container.selection.")
741  self.addOption ("prefix", 'MCTC_', type=str,
742  info="the prefix (str) of the decorations based on the MCTC "
743  "classification. The default is 'MCTC_'.")
744  # Always skip on data
745  self.setOptionValue('skipOnData', True)
746 
747  def instanceName (self) :
748  """Return the instance name for this block"""
749  return self.containerName
750 
751  def makeAlgs (self, config) :
752  particles, selection = config.readNameAndSelection(self.containerName)
753 
754  alg = config.createAlgorithm ("CP::MCTCDecorationAlg", "MCTCDecorationAlg")
755  alg.particles = particles
756  alg.preselection = selection
757  alg.affectingSystematicsFilter = '.*'
758  config.addOutputVar (self.containerName, "MCTC_isPrompt", f"{self.prefix}isPrompt", noSys=True)
759  config.addOutputVar (self.containerName, "MCTC_fromHadron", f"{self.prefix}fromHadron", noSys=True)
760  config.addOutputVar (self.containerName, "MCTC_fromBSM", f"{self.prefix}fromBSM", noSys=True)
761  config.addOutputVar (self.containerName, "MCTC_fromTau", f"{self.prefix}fromTau", noSys=True)
762 
763 
764 class PerEventSFBlock (ConfigBlock):
765  """the ConfigBlock for the AsgEventScaleFactorAlg"""
766 
767  def __init__ (self):
768  super(PerEventSFBlock, self).__init__()
769  self.addOption('algoName', None, type=str,
770  info="unique name given to the underlying algorithm computing the "
771  "per-event scale factors")
772  self.addOption('particles', '', type=str,
773  info="the input object container, with a possible selection, in the "
774  "format container or container.selection.")
775  self.addOption('objectSF', '', type=str,
776  info="the name of the per-object SF decoration to be used.")
777  self.addOption('eventSF', '', type=str,
778  info="the name of the per-event SF decoration.")
779 
780  def instanceName (self) :
781  """Return the instance name for this block"""
782  return self.particles + '_' + self.objectSF + '_' + self.eventSF
783 
784  def makeAlgs(self, config):
785  if config.dataType() is DataType.Data:
786  return
787  particles, selection = config.readNameAndSelection(self.particles)
788  alg = config.createAlgorithm('CP::AsgEventScaleFactorAlg', self.algoName if self.algoName else 'AsgEventScaleFactorAlg')
789  alg.particles = particles
790  alg.preselection = selection
791  alg.scaleFactorInputDecoration = self.objectSF
792  alg.scaleFactorOutputDecoration = self.eventSF
793 
794  config.addOutputVar('EventInfo', alg.scaleFactorOutputDecoration,
795  alg.scaleFactorOutputDecoration.split("_%SYS%")[0])
796 
797 
798 class SelectionDecorationBlock (ConfigBlock):
799  """the ConfigBlock to add selection decoration to a container"""
800 
801  def __init__ (self) :
802  super (SelectionDecorationBlock, self).__init__ ()
803  # TODO: add info string
804  self.addOption('containers', [], type=list,
805  noneAction='error',
806  info="")
807 
808  def instanceName (self) :
809  """Return the instance name for this block"""
810  return ''
811 
812  def makeAlgs(self, config):
813  for container in self.containers:
814  originContainerName = config.getOutputContainerOrigin(container)
815  selectionNames = config.getSelectionNames(originContainerName)
816  for selectionName in selectionNames:
817  # skip default selection
818  if selectionName == '':
819  continue
820  alg = config.createAlgorithm(
821  'CP::AsgSelectionAlg',
822  f'SelectionDecoration_{originContainerName}_{selectionName}')
823  selectionDecoration = f'baselineSelection_{selectionName}_%SYS%'
824  alg.selectionDecoration = f'{selectionDecoration},as_char'
825  alg.particles = config.readName (originContainerName)
826  alg.preselection = config.getFullSelection (originContainerName,
827  selectionName)
828  config.addOutputVar(
829  originContainerName, selectionDecoration, selectionName)
830 
831 def makeEventCutFlowConfig(seq, containerName,
832  *, postfix=None, selectionName, customSelections=None):
833  """Create an event-level cutflow config
834 
835  Keyword arguments:
836  containerName -- name of the container
837  postfix -- a postfix to apply to decorations and algorithm names.
838  selectionName -- the name of the selection to do the cutflow for
839  customSelections -- a list of decorations to use in the cutflow, to override the retrieval of all decorations
840  """
841 
842  config = EventCutFlowBlock()
843  config.setOptionValue('containerName', containerName)
844  config.setOptionValue('selectionName', selectionName)
845  config.setOptionValue('postfix', postfix)
846  config.setOptionValue('customSelections', customSelections)
847  seq.append(config)
python.AsgAnalysisConfig.PerEventSFBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:767
python.AsgAnalysisConfig.GeneratorAnalysisBlock
Definition: AsgAnalysisConfig.py:348
python.AsgAnalysisConfig.PileupReweightingBlock
Definition: AsgAnalysisConfig.py:161
python.AsgAnalysisConfig.IOStatsBlock
Definition: AsgAnalysisConfig.py:144
python.AsgAnalysisConfig.PtEtaSelectionBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:514
python.AsgAnalysisConfig.SelectionDecorationBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:808
python.AsgAnalysisConfig.PtEtaSelectionBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:475
python.AsgAnalysisConfig.PtEtaSelectionBlock.selectionDecoration
selectionDecoration
Definition: AsgAnalysisConfig.py:533
python.AsgAnalysisConfig.OutputThinningBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:629
python.AsgAnalysisConfig.makeEventCutFlowConfig
def makeEventCutFlowConfig(seq, containerName, *postfix=None, selectionName, customSelections=None)
Definition: AsgAnalysisConfig.py:831
python.AsgAnalysisConfig.MCTCLeptonDecorationBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:734
python.AsgAnalysisConfig.ObjectCutFlowBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:566
python.AsgAnalysisConfig.ObjectCutFlowBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:562
python.AsgAnalysisConfig.SelectionDecorationBlock
Definition: AsgAnalysisConfig.py:798
python.AsgAnalysisConfig.EventCutFlowBlock
Definition: AsgAnalysisConfig.py:576
python.AsgAnalysisConfig.PerEventSFBlock
Definition: AsgAnalysisConfig.py:764
python.AsgAnalysisConfig.CommonServicesConfig.runSystematics
runSystematics
Definition: AsgAnalysisConfig.py:76
python.AsgAnalysisConfig.IOStatsBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:156
python.AsgAnalysisConfig.SystematicsCategories
Definition: AsgAnalysisConfig.py:16
python.AsgAnalysisConfig.IFFLeptonDecorationBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:699
python.AsgAnalysisConfig.OutputThinningBlock
Definition: AsgAnalysisConfig.py:626
python.AsgAnalysisConfig.GeneratorAnalysisBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:381
python.AsgAnalysisConfig.OutputThinningBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:659
python.AsgAnalysisConfig.PileupReweightingBlock.files
files
Definition: AsgAnalysisConfig.py:228
python.AsgAnalysisConfig.CommonServicesConfig.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:36
python.AsgAnalysisConfig.PileupReweightingBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:195
python.AutoconfigurePRW.getConfigurationFiles
def getConfigurationFiles(campaign=None, dsid=None, data_type=None, files=None, useDefaultConfig=False)
Definition: AutoconfigurePRW.py:127
python.AsgAnalysisConfig.PerEventSFBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:784
python.AsgAnalysisConfig.OutputThinningBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:655
python.AsgAnalysisConfig.GeneratorAnalysisBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:351
python.AsgAnalysisConfig.PileupReweightingBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:164
python.AsgAnalysisConfig.ObjectCutFlowBlock
Definition: AsgAnalysisConfig.py:543
python.AsgAnalysisConfig.ObjectCutFlowBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:546
python.AsgAnalysisConfig.IFFLeptonDecorationBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:713
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.AsgAnalysisConfig.SelectionDecorationBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:801
python.AsgAnalysisConfig.EventCutFlowBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:597
python.AsgAnalysisConfig.CommonServicesConfig
Definition: AsgAnalysisConfig.py:28
python.AsgAnalysisConfig.MCTCLeptonDecorationBlock
Definition: AsgAnalysisConfig.py:732
python.AsgAnalysisConfig.IOStatsBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:147
python.AsgAnalysisConfig.CommonServicesConfig.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:67
python.AsgAnalysisConfig.EventCutFlowBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:601
python.AsgAnalysisConfig.MCTCLeptonDecorationBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:747
python.AsgAnalysisConfig.CommonServices
def CommonServices(seq)
Definition: AsgAnalysisConfig.py:139
python.AsgAnalysisConfig.EventCutFlowBlock.__init__
def __init__(self)
Definition: AsgAnalysisConfig.py:579
python.AsgAnalysisConfig.IFFLeptonDecorationBlock
Definition: AsgAnalysisConfig.py:696
python.AsgAnalysisConfig.GeneratorAnalysisBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:377
python.AsgAnalysisConfig.IFFLeptonDecorationBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:717
python.AsgAnalysisConfig.IOStatsBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:152
python.AsgAnalysisConfig.CommonServicesConfig.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:63
str
Definition: BTagTrackIpAccessor.cxx:11
python.AsgAnalysisConfig.GeneratorAnalysisBlock.runNumber
runNumber
Definition: AsgAnalysisConfig.py:389
python.AutoconfigurePRW.getLumicalcFiles
def getLumicalcFiles(campaign)
Definition: AutoconfigurePRW.py:6
python.AsgAnalysisConfig.PerEventSFBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:780
python.AsgAnalysisConfig.PileupReweightingBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:199
python.AsgAnalysisConfig.SelectionDecorationBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:812
python.AsgAnalysisConfig.MCTCLeptonDecorationBlock.makeAlgs
def makeAlgs(self, config)
Definition: AsgAnalysisConfig.py:751
python.AsgAnalysisConfig.PtEtaSelectionBlock.instanceName
def instanceName(self)
Definition: AsgAnalysisConfig.py:510
python.AsgAnalysisConfig.CommonServicesConfig.onlySystematicsCategories
onlySystematicsCategories
Definition: AsgAnalysisConfig.py:85
python.Utils.getMCCampaign
def getMCCampaign(files)
Definition: Tools/Campaigns/python/Utils.py:38
python.AsgAnalysisConfig.PtEtaSelectionBlock
Definition: AsgAnalysisConfig.py:472