ATLAS Offline Software
ConfigSequence.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
2 
3 from AnaAlgorithm.Logging import logging
4 logCPAlgCfgSeq = logging.getLogger('CPAlgCfgSeq')
5 
6 from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType
7 from functools import wraps
8 from random import randrange
9 import re
10 def groupBlocks(func):
11  """
12  Decorates a configSequence or function with 'seq' as a
13  arguement.
14 
15  Sets groupName to the name of the decorated funtion or
16  calss plus and integer for each ConfigBlock in the configSequence.
17 
18  Blocks with the same groupName can be configured together.
19  """
20  @wraps(func)
21  def wrapper(**kwargs):
22  func(**kwargs)
23  groupName = f"{func.__name__}_{randrange(10**8):08}"
24  for block in kwargs['seq']:
25  block.setOptionValue('groupName', groupName)
26  return wrapper
27 
28 def filter_dsids (filterList, config) :
29  """check whether the sample being run passes a"""
30  """possible DSID filter on the block"""
31  if len(filterList) == 0:
32  return True
33  for dsid_filter in filterList:
34  # Check if the pattern is enclosed in regex delimiters (e.g., starts with '^' or contains regex metacharacters)
35  if any(char in dsid_filter for char in "^$*+?.()|[]{}\\"):
36  pattern = re.compile(dsid_filter)
37  if pattern.match(str(config.dsid())):
38  return True
39  else:
40  # Otherwise it's an exact DSID (but could be int or string)
41  if str(dsid_filter) == str(config.dsid()):
42  return True
43  return False
44 
46  """a sequence of ConfigBlock objects
47 
48  This could in principle just be a simple python list, and maybe we
49  change it to that at some point (10 Mar 22). Having it as its own
50  class allows to implement some helper functions.
51 
52  This implements an interface similar to ConfigBlock, but it
53  doesn't derive from it, as ConfigBlock will likely gain
54  functionality in the future that wouldn't work for a sequence (or
55  wouldn't work in the same way).
56 
57  """
58 
59  def __init__ (self) :
60  self._blocks = []
61 
62 
63  def append (self, block) :
64  """append a configuration block to the sequence"""
65  self._blocks.append (block)
66 
67 
68  def makeAlgs (self, config) :
69  """call makeAlgs() on all blocks
70 
71  This will create the actual algorithm configurables based on
72  how the blocks are configured right now.
73  """
74  for block in self._blocks:
75  if block.skipOnData and config.dataType() is DataType.Data:
76  continue
77  if block.skipOnMC and config.dataType() is not DataType.Data:
78  continue
79  if not filter_dsids(block.onlyForDSIDs, config):
80  continue
81  block.makeAlgs (config)
82 
83  def reorderAlgs(self):
84  """
85  Check for blocks with dependencies.
86 
87  If a block required another block that is not present, will
88  throw an error; Otherwise, will move block immediately after
89  required block. If dependency is not required, will move
90  after other block, if it is present.
91 
92  Note: this implementation can only move blocks forward.
93  """
94  def moveBlock(blocks):
95  for i, block in enumerate(blocks):
96  # the 'ignoreDependencies' option is added with a dep.
97  ignore = block.getOptionValue('ignoreDependencies')
98  if block.hasDependencies():
99  depIdx = i
100  for dep in block.getDependencies():
101  if dep in ignore:
102  continue
103  # find dep with largest idx
104  if dep in blocks:
105  lastIdx = max(index for index,value in enumerate(blocks) if value == dep.blockName)
106  if lastIdx > depIdx:
107  depIdx = lastIdx
108  elif dep.required:
109  raise ValueError(f"{dep} block is required"
110  f" for {block} but was not found.")
111  # check to see if block is already infront of deps
112  if depIdx > i:
113  logCPAlgCfgSeq.info(f"Moving {block} after {blocks[depIdx]}")
114  # depIdx > i so after pop, depIdx -= 1 -> depIdx is after dep
115  blocks.insert(depIdx, blocks.pop(i))
116  return False
117  # nothing to move
118  return True
119  MAXTRIES = 1000
120  for _ in range(MAXTRIES):
121  if moveBlock(self._blocks):
122  # sorted
123  break
124  else:
125  raise Exception("Could not order blocks based on dependencies"
126  f" in {MAXTRIES} moves.")
127 
128 
129  def fullConfigure (self, config) :
130  """do the full configuration on this sequence
131 
132  This sequence needs to be the only sequence, i.e. it needs to
133  contain all the blocks that will be configured, as it will
134  perform all configuration steps at once.
135  """
136  self.reorderAlgs()
137  self.makeAlgs (config)
138  config.nextPass ()
139  self.makeAlgs (config)
140 
141 
142  def setOptionValue (self, name, value, **kwargs) :
143  """set the given option on the sequence
144 
145  The name should generally be of the form
146  "groupName.optionName" to identify what group the option
147  belongs to.
148 
149  For simplicity I also allow a ".optionName" here, which will
150  then set the property in the last group added. That makes it
151  fairly straightforward to add new blocks, set options on them,
152  and then move on to the next blocks. Please note that this
153  mechanism ought to be viewed as strictly as a temporary
154  convenience, and this short cut may go away once better
155  alternatives are available.
156 
157  WARNING: The backend to option handling is slated to be
158  replaced at some point. This particular function may change
159  behavior, interface or be removed/replaced entirely.
160  """
161  names = name.split('.')
162  # <optionName>
163  optionName = names.pop(-1)
164  # <groupName>.<optionName>, or
165  # .<optionName> (backwards compatability)
166  groupName = names.pop(0) if names else ''
167  if names:
168  raise ValueError(f'Option name can be either <groupName>.<optionName>'
169  f' or <optionName> not {name}')
170  blocks = self._blocks
171  # check if last block added has an instance name
172  if not groupName:
173  groupName = blocks[-1].getOptionValue('groupName')
174  if groupName:
175  used = False
176  # set optionName for all blocks with groupName
177  for block in blocks:
178  if ( block.getOptionValue('groupName') == groupName
179  and block.hasOption(optionName) ):
180  block.setOptionValue (optionName, value, **kwargs)
181  used = True
182  if not used:
183  raise ValueError(f'{optionName} not found in blocks with '
184  f'group name {groupName}')
185  else:
186  # set opyion for last added block
187  blocks[-1].setOptionValue (optionName, value, **kwargs)
188 
189 
190  def printOptions(self, verbose=False):
191  """
192  Prints options and their values for each config block in a config sequence
193  """
194  for config in self:
195  logCPAlgCfgSeq.info(config.__class__.__name__)
196  config.printOptions(verbose=verbose)
197 
198 
199  def getOptions(self):
200  """get information on options for last block in sequence"""
201  # get groupName for last added block
202  groupName = self._blocks[-1].getOptionValue('groupName')
203  blocks = [self._blocks[-1]]
204  # get all blocks with the same groupName
205  if groupName:
206  for block in self._blocks[:-1]:
207  if block.getOptionValue('groupName') == groupName:
208  blocks.append(block)
209  options = []
210  # get options for all blocks with same groupName
211  for block in blocks:
212  for name, o in block.getOptions().items():
213  val = getattr(block, name)
214  valDefault = o.default
215  valType = o.type
216  valRequired = o.required
217  noneAction = o.noneAction
218  options.append({'name': name, 'defaultValue': valDefault,
219  'type': valType, 'required': valRequired,
220  'noneAction': noneAction, 'value': val})
221  return options
222 
223 
224  def setOptions(self, options):
225  """Set options for a ConfigBlock"""
226  algOptions = self.getOptions()
227  for opt in algOptions:
228  name = opt['name']
229  if name in options:
230  self.setOptionValue (f'.{name}', options[name])
231  logCPAlgCfgSeq.info(f" {name}: {options[name]}")
232  else:
233  if opt['required']:
234  raise ValueError(f'{name} is required but not included in config')
235  # add default used to config
236  defaultVal = opt['defaultValue']
237  # do not overwright groupName unless set by user
238  if name != 'groupName':
239  options[name] = defaultVal
240  logCPAlgCfgSeq.info(f" {name}: {defaultVal}")
241  return algOptions
242 
243 
244  def groupBlocks(self, groupName=''):
245  """
246  Assigns all blocks in configSequence groupName. If no name is
247  provided, the name is set to group_ plus an integer.
248 
249  Blocks with the same groupName can be configured together.
250  """
251  if not groupName:
252  groupName = f"group_{randrange(10**8):08}"
253  for block in self._blocks:
254  block.setOptionValue('groupName', groupName)
255 
256 
257  def __iadd__( self, sequence, index = None ):
258  """Add another sequence to this one
259 
260  This function is used to add another sequence to this sequence
261  using the '+=' operator.
262  """
263  # Check that the received object is of the right type:
264  if not isinstance( sequence, ConfigSequence ):
265  raise TypeError( 'The received object is not of type ConfigSequence' )
266 
267  for block in sequence._blocks :
268  self._blocks.append (block)
269 
270  # Return the modified object:
271  return self
272 
273 
274  def __iter__( self ):
275  """Create an iterator over all the configurations in this sequence
276 
277  This is to allow for a Python-like iteration over all
278  configuration blocks that are part of the sequence.
279  """
280  # Create the iterator to process the internal list of algorithms:
281  return self._blocks.__iter__()
python.ConfigSequence.filter_dsids
def filter_dsids(filterList, config)
Definition: ConfigSequence.py:28
python.ConfigSequence.ConfigSequence.fullConfigure
def fullConfigure(self, config)
Definition: ConfigSequence.py:129
python.ConfigSequence.ConfigSequence.append
def append(self, block)
Definition: ConfigSequence.py:63
max
constexpr double max()
Definition: ap_fixedTest.cxx:33
python.ConfigSequence.ConfigSequence._blocks
_blocks
Definition: ConfigSequence.py:60
python.ConfigSequence.ConfigSequence.reorderAlgs
def reorderAlgs(self)
Definition: ConfigSequence.py:83
python.ConfigSequence.ConfigSequence.__iter__
def __iter__(self)
Definition: ConfigSequence.py:274
python.ConfigSequence.ConfigSequence.setOptionValue
def setOptionValue(self, name, value, **kwargs)
Definition: ConfigSequence.py:142
python.ConfigSequence.ConfigSequence.makeAlgs
def makeAlgs(self, config)
Definition: ConfigSequence.py:68
python.ConfigSequence.groupBlocks
def groupBlocks(func)
Definition: ConfigSequence.py:10
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
python.ConfigSequence.ConfigSequence.printOptions
def printOptions(self, verbose=False)
Definition: ConfigSequence.py:190
python.ConfigSequence.ConfigSequence.setOptions
def setOptions(self, options)
Definition: ConfigSequence.py:224
python.ConfigSequence.ConfigSequence.__init__
def __init__(self)
Definition: ConfigSequence.py:59
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.ConfigSequence.ConfigSequence.getOptions
def getOptions(self)
Definition: ConfigSequence.py:199
python.ConfigSequence.ConfigSequence.__iadd__
def __iadd__(self, sequence, index=None)
Definition: ConfigSequence.py:257
str
Definition: BTagTrackIpAccessor.cxx:11
python.ConfigSequence.ConfigSequence.groupBlocks
def groupBlocks(self, groupName='')
Definition: ConfigSequence.py:244
python.ConfigSequence.ConfigSequence
Definition: ConfigSequence.py:45