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