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