ATLAS Offline Software
ConfigBlock.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
2 
3 import textwrap
4 import inspect
5 
6 from AnaAlgorithm.Logging import logging
7 logCPAlgCfgBlock = logging.getLogger('CPAlgCfgBlock')
8 
9 
11  """the information for a single option on a configuration block"""
12 
13  def __init__ (self, type=None, info='', noneAction='ignore', required=False,
14  default=None) :
15  self.type = type
16  self.info = info
17  self.required = required
18  self.noneAction = noneAction
19  self.default = default
20 
21 
22 
24  """Class encoding a blocks dependence on other blocks."""
25 
26  def __init__(self, blockName, required=True):
27  self.blockName = blockName
28  self.required = required
29 
30 
31  def __eq__(self, name):
32  return self.blockName == name
33 
34 
35  def __str__(self):
36  return self.blockName
37 
38 
39  def __repr__(self):
40  return f'ConfigBlockDependency(blockName="{self.blockName}", required={self.required})'
41 
42 
44  """the base class for classes implementing individual blocks of
45  configuration
46 
47  A configuration block is a sequence of one or more algorithms that
48  should always be scheduled together, e.g. the muon four momentum
49  corrections could be a single block, muon selection could then be
50  another block. The blocks themselves generally have their own
51  configuration options/properties specific to the block, and will
52  perform a dynamic configuration based on those options as well as
53  the overall job.
54 
55  The actual configuration of the algorithms in the block will
56  depend on what other blocks are scheduled before and afterwards,
57  most importantly some algorithms will introduce shallow copies
58  that subsequent algorithms will need to run on, and some
59  algorithms will add selection decorations that subquent algorithms
60  should use as preselections.
61 
62  The algorithms get created in a multi-step process (that may be
63  extended in the future): As a first step each block retrieves
64  references to the containers it uses (essentially marking its spot
65  in the processing chain) and also registering any shallow copies
66  that will be made. In the second/last step each block then
67  creates the fully configured algorithms.
68 
69  One goal is that when the algorithms get created they will have
70  their final configuration and there needs to be no
71  meta-configuration data attached to the algorithms, essentially an
72  inversion of the approach in AnaAlgSequence in which the
73  algorithms got created first with associated meta-configuration
74  and then get modified in susequent configuration steps.
75 
76  For now this is mostly an empty base class, but another goal of
77  this approach is to make it easier to build another configuration
78  layer on top of this one, and this class will likely be extended
79  and get data members at that point.
80 
81  The child class needs to implement two methods,
82  `collectReferences` and `makeAlgs` which are each given a single
83  `ConfigAccumulator` type argument. The first is for the first
84  configuration step, and should only collect references to the
85  containers to be used. The second is for the second configuration
86  step, and should create the actual algorithms.
87 
88  """
89 
90  # Class-level dictionary to keep track of instance counts for each derived class
91  instance_counts = {}
92 
93  def __init__ (self) :
94  self._blockName = ''
95  self._dependencies = []
96  self._options = {}
97  # used with block configuration to set arbitrary option
98  self.addOption('groupName', '', type=str,
99  info=('Used to specify this block when setting an'
100  ' option at an arbitrary location.'))
101  self.addOption('skipOnData', False, type=bool,
102  info=('User option to prevent the block from running'
103  ' on data. This only affects blocks that are'
104  ' intended to run on data.'))
105  self.addOption('skipOnMC', False, type=bool,
106  info=('User option to prevent the block from running'
107  ' on MC. This only affects blocks that are'
108  ' intended to run on MC.'))
109  self.addOption('onlyForDSIDs', [], type=list,
110  info=('Used to specify which MC DSIDs to allow this'
111  ' block to run on. Each element of the list'
112  ' can be a full DSID (e.g. 410470), or a regex'
113  ' (e.g. 410.* to select all 410xxx DSIDs, or'
114  ' ^(?!410) to veto them). An empty list means no'
115  ' DSID restriction.'))
116  # Increment the instance count for the current class
117  cls = type(self) # Get the actual class of the instance (also derived!)
118  if cls not in ConfigBlock.instance_counts:
119  ConfigBlock.instance_counts[cls] = 0
120  # Note: we do need to check in the call stack that we are
121  # in a real makeConfig situation, and not e.g. printAlgs
122  stack = inspect.stack()
123  for frame_info in stack:
124  # Get the class name (if any) from the frame
125  parent_cls = frame_info.frame.f_locals.get('self', None)
126  if parent_cls is None or not isinstance(parent_cls, ConfigBlock):
127  # If the frame does not belong to an instance of ConfigBlock, it's an external caller
128  if frame_info.function == "makeConfig":
129  ConfigBlock.instance_counts[cls] += 1
130  break
131 
132 
133  def setBlockName(self, name):
134  """Set blockName"""
135  self._blockName = name
136 
137  def getBlockName(self):
138  """Get blockName"""
139  return self._blockName
140 
141  def addDependency(self, dependencyName, required=True):
142  """
143  Add a dependency for the block. Dependency is corresponds to the
144  blockName of another block. If required is True, will throw an
145  error if dependency is not present; otherwise will move this
146  block after the required block. If required is False, will do
147  nothing if required block is not present; otherwise, it will
148  move block after required block.
149  """
150  if not self.hasDependencies():
151  # add option to block ignore dependencies
152  self.addOption('ignoreDependencies', [], type=list,
153  info='List of dependencies defined in the ConfigBlock to ignore.')
154  self._dependencies.append(ConfigBlockDependency(dependencyName, required))
155 
156  def hasDependencies(self):
157  """Return True if there is a dependency."""
158  return bool(self._dependencies)
159 
160  def getDependencies(self):
161  """Return the list of dependencies. """
162  return self._dependencies
163 
164  def addOption (self, name, defaultValue, *,
165  type, info='', noneAction='ignore', required=False) :
166  """declare the given option on the configuration block
167 
168  This should only be called in the constructor of the
169  configuration block.
170 
171  NOTE: The backend to option handling is slated to be replaced
172  at some point. This particular function should essentially
173  stay the same, but some behavior may change.
174  """
175  if name in self._options :
176  raise KeyError (f'duplicate option: {name}')
177  if type not in [str, bool, int, float, list, None] :
178  raise TypeError (f'unknown option type: {type}')
179  noneActions = ['error', 'set', 'ignore']
180  if noneAction not in noneActions :
181  raise ValueError (f'invalid noneAction: {noneAction} [allowed values: {noneActions}]')
182  setattr (self, name, defaultValue)
183  self._options[name] = ConfigBlockOption(type=type, info=info,
184  noneAction=noneAction, required=required, default=defaultValue)
185 
186 
187  def setOptionValue (self, name, value) :
188  """set the given option on the configuration block
189 
190  NOTE: The backend to option handling is slated to be replaced
191  at some point. This particular function should essentially
192  stay the same, but some behavior may change.
193  """
194 
195  if name not in self._options :
196  raise KeyError (f'unknown option "{name}" in block "{self.__class__.__name__}"')
197  noneAction = self._options[name].noneAction
198  if value is not None or noneAction == 'set' :
199  # check type if specified
200  optType = self._options[name].type
201  # convert int to float to prevent crash
202  if optType is float and type(value) is int:
203  value = float(value)
204  if optType is not None and optType != type(value):
205  raise ValueError(f'{name} for block {self.__class__.__name__} should '
206  f'be of type {optType} not {type(value)}')
207  setattr (self, name, value)
208  elif noneAction == 'ignore' :
209  pass
210  elif noneAction == 'error' :
211  raise ValueError (f'passed None for setting option {name} with noneAction=error')
212 
213 
214  def getOptionValue(self, name):
215  """Returns config option value, if present; otherwise return None"""
216  if name in self._options:
217  return getattr(self, name)
218 
219 
220  def getOptions(self):
221  """Return a copy of the options associated with the block"""
222  return self._options.copy()
223 
224 
225  def printOptions(self, verbose=False, width=60, indent=" "):
226  """
227  Prints options and their values
228  """
229  def printWrap(text, width=60, indent=" "):
230  wrapper = textwrap.TextWrapper(width=width, initial_indent=indent,
231  subsequent_indent=indent)
232  for line in wrapper.wrap(text=text):
233  logCPAlgCfgBlock.info(line)
234 
235  for opt, vals in self.getOptions().items():
236  if verbose:
237  logCPAlgCfgBlock.info(indent + f"\033[4m{opt}\033[0m: {self.getOptionValue(opt)}")
238  logCPAlgCfgBlock.info(indent*2 + f"\033[4mtype\033[0m: {vals.type}")
239  logCPAlgCfgBlock.info(indent*2 + f"\033[4mdefault\033[0m: {vals.default}")
240  logCPAlgCfgBlock.info(indent*2 + f"\033[4mrequired\033[0m: {vals.required}")
241  logCPAlgCfgBlock.info(indent*2 + f"\033[4mnoneAction\033[0m: {vals.noneAction}")
242  printWrap(f"\033[4minfo\033[0m: {vals.info}", indent=indent*2)
243  else:
244  logCPAlgCfgBlock.info(indent + f"{ opt}: {self.getOptionValue(opt)}")
245 
246 
247  def hasOption (self, name) :
248  """whether the configuration block has the given option
249 
250  WARNING: The backend to option handling is slated to be
251  replaced at some point. This particular function may change
252  behavior, interface or be removed/replaced entirely.
253  """
254  return name in self._options
255 
256 
257  def __eq__(self, blockName):
258  """
259  Implementation of == operator. Used for seaching configSeque.
260  E.g. if blockName in configSeq:
261  """
262  return self._blockName == blockName
263 
264 
265  def __str__(self):
266  return self._blockName
267 
268 
269  @classmethod
271  # Access the current count for this class
272  return ConfigBlock.instance_counts.get(cls, 0)
python.ConfigBlock.ConfigBlock.hasOption
def hasOption(self, name)
Definition: ConfigBlock.py:247
python.ConfigBlock.ConfigBlockDependency
Definition: ConfigBlock.py:23
python.ConfigBlock.ConfigBlockOption.noneAction
noneAction
Definition: ConfigBlock.py:17
python.ConfigBlock.ConfigBlock.hasDependencies
def hasDependencies(self)
Definition: ConfigBlock.py:156
python.ConfigBlock.ConfigBlock.addDependency
def addDependency(self, dependencyName, required=True)
Definition: ConfigBlock.py:141
python.ConfigBlock.ConfigBlock.__str__
def __str__(self)
Definition: ConfigBlock.py:265
python.ConfigBlock.ConfigBlock.setOptionValue
def setOptionValue(self, name, value)
Definition: ConfigBlock.py:187
python.ConfigBlock.ConfigBlockOption.type
type
Definition: ConfigBlock.py:14
python.ConfigBlock.ConfigBlock.getDependencies
def getDependencies(self)
Definition: ConfigBlock.py:160
python.ConfigBlock.ConfigBlockOption.info
info
Definition: ConfigBlock.py:15
python.ConfigBlock.ConfigBlock.getOptions
def getOptions(self)
Definition: ConfigBlock.py:220
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
python.ConfigBlock.ConfigBlockDependency.__str__
def __str__(self)
Definition: ConfigBlock.py:35
python.ConfigBlock.ConfigBlockOption.required
required
Definition: ConfigBlock.py:16
python.ConfigBlock.ConfigBlockOption.default
default
Definition: ConfigBlock.py:18
python.ConfigBlock.ConfigBlockDependency.__eq__
def __eq__(self, name)
Definition: ConfigBlock.py:31
python.ConfigBlock.ConfigBlock._dependencies
_dependencies
Definition: ConfigBlock.py:95
python.ConfigBlock.ConfigBlockDependency.blockName
blockName
Definition: ConfigBlock.py:27
python.ConfigBlock.ConfigBlock.get_instance_count
def get_instance_count(cls)
Definition: ConfigBlock.py:270
python.ConfigBlock.ConfigBlock.getBlockName
def getBlockName(self)
Definition: ConfigBlock.py:137
python.ConfigBlock.ConfigBlockDependency.required
required
Definition: ConfigBlock.py:28
python.ConfigBlock.ConfigBlock.__init__
def __init__(self)
Definition: ConfigBlock.py:93
python.ConfigBlock.ConfigBlock.printOptions
def printOptions(self, verbose=False, width=60, indent=" ")
Definition: ConfigBlock.py:225
python.ConfigBlock.ConfigBlock.addOption
def addOption(self, name, defaultValue, *type, info='', noneAction='ignore', required=False)
Definition: ConfigBlock.py:164
python.ConfigBlock.ConfigBlock.getOptionValue
def getOptionValue(self, name)
Definition: ConfigBlock.py:214
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.ConfigBlock.ConfigBlock.__eq__
def __eq__(self, blockName)
Definition: ConfigBlock.py:257
python.ConfigBlock.ConfigBlock._blockName
_blockName
Definition: ConfigBlock.py:94
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
python.ConfigBlock.ConfigBlockDependency.__init__
def __init__(self, blockName, required=True)
Definition: ConfigBlock.py:26
python.ConfigBlock.ConfigBlockDependency.__repr__
def __repr__(self)
Definition: ConfigBlock.py:39
calibdata.copy
bool copy
Definition: calibdata.py:27
python.ConfigBlock.ConfigBlock._options
_options
Definition: ConfigBlock.py:96
python.ConfigBlock.ConfigBlockOption.__init__
def __init__(self, type=None, info='', noneAction='ignore', required=False, default=None)
Definition: ConfigBlock.py:13
xAOD::bool
setBGCode setTAP setLVL2ErrorBits bool
Definition: TrigDecision_v1.cxx:60
readCCLHist.float
float
Definition: readCCLHist.py:83
python.ConfigBlock.ConfigBlock
Definition: ConfigBlock.py:43
python.ConfigBlock.ConfigBlockOption
Definition: ConfigBlock.py:10
python.ConfigBlock.ConfigBlock.setBlockName
def setBlockName(self, name)
Definition: ConfigBlock.py:133