diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigBlock.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigBlock.py index 4ebcbfae7ebcac8a609ea0bdf872ab929a32b28d..f9b75e7c8ecf87f9d7f66a8b22025fece0e38aee 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigBlock.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigBlock.py @@ -10,6 +10,27 @@ class ConfigBlockOption: self.noneAction = noneAction + +class ConfigBlockDependency(): + """Class encoding a blocks dependence on other blocks.""" + + def __init__(self, blockName, required=True): + self.blockName = blockName + self.required = required + + + def __eq__(self, name): + return self.blockName == name + + + def __str__(self): + return self.blockName + + + def __repr__(self): + return f'ConfigBlcokDependency(blockName="{self.blockName}", required={self.required})' + + class ConfigBlock: """the base class for classes implementing individual blocks of configuration @@ -57,8 +78,9 @@ class ConfigBlock: """ - # groupName is only a placeholder here def __init__ (self) : + self._blockName = '' + self._dependencies = [] self._options = {} # used with block configuration to set arbitrary option self.addOption('groupName', '', type=str, @@ -66,6 +88,36 @@ class ConfigBlock: ' option at an arbitrary location.')) + def setBlockName(self, name): + """Set blockName""" + self._blockName = name + + def getBlockName(self, name): + """Get blockName""" + return self._blockName + + def addDependency(self, dependencyName, required=True): + """ + Add a dependency for the block. Dependency is corresponds to the + blockName of another block. If requried is True, will throw an + error if dependency is not present; otherwise will move this + block after the required block. If required is False, will do + nothing if required block is not present; otherwise, it will + move block after required block. + """ + self._dependencies.append(ConfigBlockDependency(dependencyName, required)) + # add option to block ignore dependencies + self.addOption('ignoreDependencies', [], type=list, + info='List of dependencies defined in the ConfigBlock to ignore.') + + def hasDependencies(self): + """Return True if there is a dependency.""" + return bool(self._dependencies) + + def getDependencies(self): + """Return the list of dependencies. """ + return self._dependencies + def addOption (self, name, defaultValue, *, type, info='', noneAction='ignore', required=False) : """declare the given option on the configuration block @@ -127,3 +179,15 @@ class ConfigBlock: behavior, interface or be removed/replaced entirely. """ return name in self._options + + + def __eq__(self, blockName): + """ + Implementation of == operator. Used for seaching configSeque. + E.g. if blockName in configSeq: + """ + return self._blockName == blockName + + + def __str__(self): + return self._blockName diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigFactory.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigFactory.py index 2b9bdcee4c3faf835ff078f9900f69a4b3dcdf0f..6a7d6969c26db0ab3da26e772ef08587835f8298 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigFactory.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigFactory.py @@ -31,14 +31,11 @@ def getDefaultArgs(func): def getFuncArgs(func): """return list of input parameters""" - signature = inspect.signature(func) - return list(signature.parameters.keys()) - - -def getClassArgs(func): - """return list of args used i=ton initialize class""" - args = list(inspect.signature(func.__init__).parameters.keys()) - args.remove('self') + if isinstance(func, dict): + args = list(inspect.signature(func.__init__).parameters.keys()) + args.remove('self') + else: + args = list(inspect.signature(func).parameters.keys()) return args @@ -52,7 +49,7 @@ class FactoryBlock(): self.options = options self.defaults = defaults if subAlgs is None: - self.subAlgs = [] + self.subAlgs = {} else: self.subAlgs = subAlgs @@ -121,10 +118,7 @@ class ConfigFactory(): """Add class to list of available algorithms""" if not callable(alg): raise ValueError(f"{algName} is not a callable.") - if isinstance(alg, type): - opts = getClassArgs(alg) - else: - opts = getFuncArgs(alg) + opts = getFuncArgs(alg) if superBlocks is None: superBlocks = [self.ROOTNAME] @@ -162,24 +156,25 @@ class ConfigFactory(): order.insert(order.index(pos), algName) else: raise ValueError(f"{pos} does not exit in already added config blocks") - return def printAlgs(self, printOpts=False): """Prints algorithms exposed to configuration""" - algs = self._algs - for alg, algInfo in algs.items(): - algName = algInfo.alg.__name__ - algOptions = algInfo.options - algDefaults = algInfo.defaults - print(f"{alg} -> {algName}") - if printOpts and algOptions: - for opt in algOptions: - if algDefaults and opt in algDefaults: - print(f" {opt}: {algDefaults[opt]}") - else: - print(f" {opt}") + def printAlg(algs): + for alg, algInfo in algs.items(): + algName = algInfo.alg.__name__ + algOptions = algInfo.options + algDefaults = algInfo.defaults + print(f"{alg} -> {algName}") + if printOpts and algOptions: + for opt in algOptions: + if algDefaults and opt in algDefaults: + print(f" {opt}: {algDefaults[opt]}") + else: + print(f" {opt}") + printAlg(algInfo.subAlgs) + printAlg(self._algs) return diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigSequence.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigSequence.py index 4216927d847e382b50183d392c6fbbc415bd5aba..583eb1ce927583d83a4dd3c0c0721385d9c0a56f 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigSequence.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigSequence.py @@ -53,6 +53,51 @@ class ConfigSequence: for block in self._blocks: block.makeAlgs (config) + def reorderAlgs(self): + """ + Check for blocks with dependencies. + + If a block requried another block that is not present, will + throw an error; Otherwise, will move block immediately after + required block. If dependency is not requried, will move + after other block, if it is present. + + Note: this implementation can only move blocks forward. + """ + def moveBlock(blocks): + for i, block in enumerate(blocks): + # the 'ignoreDependencies' option is added with a dep. + ignore = block.getOptionValue('ignoreDependencies') + if block.hasDependencies(): + depIdx = i + for dep in block.getDependencies(): + if dep in ignore: + continue + # find dep with largest idx + if dep in blocks: + tmpIdx = blocks.index(dep.blockName) + if tmpIdx > depIdx: + depIdx = tmpIdx + elif dep.required: + raise ValueError(f"{dep} block is required" + f" for {block} but was not found.") + # check to see if block is already infront of deps + if depIdx > i: + print(f"> Moving {block} after {blocks[depIdx]}") + # depIdx > i so after pop, depIdx -= 1 -> depIdx is after dep + blocks.insert(depIdx, blocks.pop(i)) + return False + # nothing to move + return True + MAXTRIES = 1000 + for _ in range(MAXTRIES): + if moveBlock(self._blocks): + # sorted + break + else: + raise Exception("Could not order blocks based on dependencies" + f" in {MAXTRIES} moves.") + def fullConfigure (self, config) : """do the full configuration on this sequence @@ -61,6 +106,7 @@ class ConfigSequence: contain all the blocks that will be configured, as it will perform all configuration steps at once. """ + self.reorderAlgs() self.makeAlgs (config) config.nextPass () self.makeAlgs (config) diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigText.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigText.py index c17ebdcea3b706ed823c0085cd419f8d38373cdf..7b4b383c1fc86809fbf3dc9ecc3df2952eb9da8e 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigText.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigText.py @@ -162,32 +162,32 @@ def makeSequence(configPath, dataType, algSeq, geometry=None, autoconfigFromFlag isPhyslite=False, noPhysliteBroken=False, noSystematics=None): """ """ - print(os.getcwd()) from AnalysisAlgorithmsConfig.ConfigAccumulator import ConfigAccumulator config = TextConfig(configPath) - print(">>> Configuration file read in:") + print("\n>>> Configuration file read in:") config.printConfig() - print(">>> Default algorithms") + print("\n>>> Default algorithms:") config.printAlgs(printOpts=True) - print(">>> Configuring algorithms based on YAML file") + print("\n>>> Configuring algorithms based on YAML file:") configSeq = config.configure() # defaults are added to config as algs are configured - print(">>> Configuration used:") + print("\n>>> Configuration used:") config.printConfig() - print(">>> ConfigBlocks and their configuration") - configSeq.printOptions - # compile configAccumulator = ConfigAccumulator(algSeq, dataType, isPhyslite=isPhyslite, geometry=geometry, autoconfigFromFlags=autoconfigFromFlags, noSystematics=noSystematics) configSeq.fullConfigure(configAccumulator) + # blocks can be reordered during configSeq.fullConfigure + print("\n>>> ConfigBlocks and their configuration:") + configSeq.printOptions() + from AnaAlgorithm.DualUseConfig import isAthena, useComponentAccumulator if isAthena and useComponentAccumulator: return configAccumulator.CA diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py index 5bfb5dafdce5297085617ae454e4e2b085c03915..e2ed28d90d4a8418985783843928fd929afb95b6 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py @@ -997,11 +997,12 @@ def makeSequenceBlocks (dataType, algSeq, forCompare, isPhyslite, disable_commands.append('disable el_select_loose.*') configSeq.setOptionValue ('.commands', disable_commands) - configSeq.printOptions() - configAccumulator = ConfigAccumulator (algSeq, dataType, isPhyslite, geometry, autoconfigFromFlags=autoconfigFromFlags, noSystematics=noSystematics) configSeq.fullConfigure (configAccumulator) + # order can change during fullConfigure + configSeq.printOptions() + from AnaAlgorithm.DualUseConfig import isAthena, useComponentAccumulator if isAthena and useComponentAccumulator: return configAccumulator.CA diff --git a/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py b/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py index 3e9a0997f1df098b1786f41ab022dafeb95e0c2e..b26bfd5fc6411628e4dcb70758500a585f0a4a7c 100644 --- a/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py +++ b/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py @@ -142,4 +142,4 @@ class OutputAnalysisConfig (ConfigBlock): alg.selectionDecoration = f'{selectionDecoration},as_char' alg.particles = config.readName (containerName) alg.preselection = config.getFullSelection (containerName, selectionName) - config.addOutputVar (containerName, selectionDecoration, self.selectionFlagPrefix + '_' + selectionName) \ No newline at end of file + config.addOutputVar (containerName, selectionDecoration, self.selectionFlagPrefix + '_' + selectionName)