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.checkExpertSettings (config)
62  block.makeAlgs (config)
63  config.setAlgPostfix('') # reset algPostfix after all blocks are configured
64 
65 
66  def applyConfigOverrides(self, config):
67  """
68  Apply any properties that were set in the block's
69  'propertyOverrides' option.
70  """
71  for block in self._blocks:
72  if not block.isUsedForConfig(config):
73  continue
74  config.setAlgPostfix(block.instanceName())
75  block.applyConfigOverrides(config)
76  config.setAlgPostfix('') # reset algPostfix after all blocks are configured
77 
78  def reorderAlgs(self):
79  """
80  Check for blocks with dependencies.
81 
82  If a block required another block that is not present, will
83  throw an error; Otherwise, will move block immediately after
84  required block. If dependency is not required, will move
85  after other block, if it is present.
86 
87  Note: this implementation can only move blocks forward.
88  """
89  def moveBlock(blocks):
90  for i, block in enumerate(blocks):
91  # the 'ignoreDependencies' option is added with a dep.
92  ignore = block.getOptionValue('ignoreDependencies')
93  if block.hasDependencies():
94  depIdx = i
95  for dep in block.getDependencies():
96  if dep in ignore:
97  continue
98  # find dep with largest idx
99  if dep in blocks:
100  lastIdx = max(index for index,value in enumerate(blocks) if value == dep.blockName)
101  if lastIdx > depIdx:
102  depIdx = lastIdx
103  elif dep.required:
104  raise ValueError(f"{dep} block is required"
105  f" for {block} but was not found.")
106  # check to see if block is already infront of deps
107  if depIdx > i:
108  logCPAlgCfgSeq.info(f"Moving {block} after {blocks[depIdx]}")
109  # depIdx > i so after pop, depIdx -= 1 -> depIdx is after dep
110  blocks.insert(depIdx, blocks.pop(i))
111  return False
112  # nothing to move
113  return True
114  MAXTRIES = 1000
115  for _ in range(MAXTRIES):
116  if moveBlock(self._blocks):
117  # sorted
118  break
119  else:
120  raise Exception("Could not order blocks based on dependencies"
121  f" in {MAXTRIES} moves.")
122 
123 
124  def fullConfigure (self, config) :
125  """do the full configuration on this sequence
126 
127  This sequence needs to be the only sequence, i.e. it needs to
128  contain all the blocks that will be configured, as it will
129  perform all configuration steps at once.
130  """
131  for block in self._blocks:
132  if re.compile ('^[_a-zA-Z0-9]*$').match (block.instanceName()) is None :
133  raise ValueError (f'invalid block instance name: {block.instanceName()} for {block.factoryName()}')
134 
135  self.reorderAlgs()
136  self.makeAlgs (config)
137  config.nextPass ()
138  self.makeAlgs (config)
139  self.applyConfigOverrides(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['value'] if opt['value'] else 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 setFactoryName(self, factoryName):
258  """
259  Set the factory name for all blocks in the sequence.
260 
261  This is used to set a common factory name for all blocks, which
262  can be useful for debugging or logging purposes.
263  """
264  if len(self._blocks) == 1:
265  self._blocks[0].setFactoryName(factoryName)
266  else:
267  # append [index] to factoryName for each block
268  for index, block in enumerate(self._blocks):
269  block.setFactoryName(f"{factoryName}[{index}:{block.__class__.__name__}]")
270 
271  def __iadd__( self, sequence, index = None ):
272  """Add another sequence to this one
273 
274  This function is used to add another sequence to this sequence
275  using the '+=' operator.
276  """
277  # Check that the received object is of the right type:
278  if not isinstance( sequence, ConfigSequence ):
279  raise TypeError( 'The received object is not of type ConfigSequence' )
280 
281  for block in sequence._blocks :
282  self._blocks.append (block)
283 
284  # Return the modified object:
285  return self
286 
287 
288  def __iter__( self ):
289  """Create an iterator over all the configurations in this sequence
290 
291  This is to allow for a Python-like iteration over all
292  configuration blocks that are part of the sequence.
293  """
294  # Create the iterator to process the internal list of algorithms:
295  return self._blocks.__iter__()
python.ConfigSequence.ConfigSequence.fullConfigure
def fullConfigure(self, config)
Definition: ConfigSequence.py:124
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:78
python.ConfigSequence.ConfigSequence.__iter__
def __iter__(self)
Definition: ConfigSequence.py:288
python.ConfigSequence.ConfigSequence.setOptionValue
def setOptionValue(self, name, value, **kwargs)
Definition: ConfigSequence.py:142
python.ConfigSequence.ConfigSequence.setFactoryName
def setFactoryName(self, factoryName)
Definition: ConfigSequence.py:257
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:190
python.ConfigSequence.ConfigSequence.setOptions
def setOptions(self, options)
Definition: ConfigSequence.py:224
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:199
python.ConfigSequence.ConfigSequence.__iadd__
def __iadd__(self, sequence, index=None)
Definition: ConfigSequence.py:271
python.ConfigSequence.ConfigSequence.applyConfigOverrides
def applyConfigOverrides(self, config)
Definition: ConfigSequence.py:66
python.ConfigSequence.ConfigSequence.groupBlocks
def groupBlocks(self, groupName='')
Definition: ConfigSequence.py:244
python.ConfigSequence.ConfigSequence
Definition: ConfigSequence.py:28