diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py index ea79db4187c6e473012fc67e7c1144a2e41a2e07..65d66d639294faaa6a8d2534dbfae9073bfd79e6 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py @@ -1,6 +1,7 @@ # Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration import AnaAlgorithm.DualUseConfig as DualUseConfig +import re def mapUserName (name) : @@ -87,6 +88,7 @@ class ConfigAccumulator : self._pass = 0 self._algorithms = {} self._currentAlg = None + self._selectionNameExpr = re.compile ('[A-Za-z_][A-Za-z_0-9]+') def dataType (self) : @@ -262,6 +264,8 @@ class ConfigAccumulator : """get the preselection string for the given selection on the given container """ + if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) : + raise ValueError ('invalid selection name: ' + selectionName) if containerName not in self._containerConfig : return "" config = self._containerConfig[containerName] @@ -273,17 +277,58 @@ class ConfigAccumulator : return '&&'.join (decorations) - def getFullSelection (self, containerName, selectionName) : + def getFullSelection (self, containerName, selectionName, + *, skipBase = False) : """get the preselection string for the given selection on the given container + + This can handle both individual selections or selection + expressions (e.g. `loose||tight`) with the later being + properly expanded. Either way the base selection (i.e. the + selection without a name) will always be applied on top. + + containerName --- the container the selection is defined on + selectionName --- the name of the selection, or a selection + expression based on multiple named selections + skipBase --- will avoid the base selection, and should normally + not be used by the end-user. + """ if containerName not in self._containerConfig : return "" + + # Check if this is actually a selection expression, + # e.g. `A||B` and if so translate it into a complex expression + # for the user. I'm not trying to do any complex syntax + # recognition, but instead just produce an expression that the + # C++ parser ought to be able to read. + if selectionName != '' and \ + not self._selectionNameExpr.fullmatch (selectionName) : + result = '' + while selectionName != '' : + match = self._selectionNameExpr.match (selectionName) + if not match : + result += selectionName[0] + selectionName = selectionName[1:] + else : + subname = match.group(0) + subresult = self.getFullSelection (containerName, subname, skipBase = True) + if subresult != '' : + result += '(' + subresult + ')' + else : + result += 'true' + selectionName = selectionName[len(subname):] + subresult = self.getFullSelection (containerName, '') + if subresult != '' : + result = subresult + '&&(' + result + ')' + return result + config = self._containerConfig[containerName] decorations = [] for selection in config.selections : - if (selection.name == '' or selection.name == selectionName) : + if ((selection.name == '' and not skipBase) or + selection.name == selectionName) : decorations += [selection.decoration] return '&&'.join (decorations) @@ -295,6 +340,8 @@ class ConfigAccumulator : This also takes the number of bits in the selection decoration, which is needed to make object cut flows.""" + if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) : + raise ValueError ('invalid selection name: ' + selectionName) if containerName not in self._containerConfig : self._containerConfig[containerName] = ContainerConfig (containerName, containerName) config = self._containerConfig[containerName] diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py index 754efbc93d8a5a9015506e0dcfd2f1f27cbe0093..de893a1589a53ab348fddc59763205000b5663d7 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py @@ -614,11 +614,17 @@ def makeSequenceBlocks (dataType, algSeq, vars, forCompare, isPhyslite, noPhysli # Include, and then set up the met analysis algorithm config: from MetAnalysisAlgorithms.MetAnalysisConfig import makeMetAnalysisConfig + # Note that the configuration for the muons is not what you'd + # normally do. This is specifically here because this is a unit + # test and I wanted to make sure that selection expressions work. + # For an actual analysis that would just be `AnaMuons.medium`, but + # since `tight` is a strict subset of `medium` it doesn't matter + # if we do an "or" of the two. makeMetAnalysisConfig (configSeq, containerName = 'AnaMET', jets = 'AnaJets', taus = 'AnaTauJets.tight', - muons = 'AnaMuons.medium', + muons = 'AnaMuons.medium || tight', electrons = 'AnaElectrons.loose', photons = 'AnaPhotons.tight') vars += [ @@ -635,7 +641,7 @@ def makeSequenceBlocks (dataType, algSeq, vars, forCompare, isPhyslite, noPhysli makeOverlapAnalysisConfig( configSeq, electrons = 'AnaElectrons.loose', photons = 'AnaPhotons.tight', - muons = 'AnaMuons.medium', + muons = 'AnaMuons.medium||tight', jets = 'AnaJets', taus = 'AnaTauJets.tight', inputLabel = 'preselectOR',