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