From af92d1c9a3f209f2ec4a70a3d8c57423931b05eb Mon Sep 17 00:00:00 2001
From: Oliver Majersky <oliver.majersky@cern.ch>
Date: Wed, 21 Feb 2024 22:23:43 +0100
Subject: [PATCH] Move the aggregated selection flag to output block and
 automatize

---
 .../python/ConfigAccumulator.py               | 24 +++++++++++
 .../python/OutputAnalysisConfig.py            | 40 +++++++++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py
index 75aea2b18ab5..7a597ec81755 100644
--- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py
+++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/ConfigAccumulator.py
@@ -577,6 +577,17 @@ class ConfigAccumulator :
         self._outputContainers[outputContainerName] = containerName
 
 
+    def getOutputContainerOrigin (self, outputContainerName) :
+        """Get the name of the actual container, for which an output is registered"""
+        try:
+            return self._outputContainers[outputContainerName]
+        except KeyError:
+            try:
+                return self._containerConfig[outputContainerName].name
+            except KeyError:
+                raise KeyError ("output container unknown: " + outputContainerName)
+
+
     def addOutputVar (self, containerName, variableName, outputName,
                       *, noSys=False, enabled=True) :
         """add an output variable for the given container to the output
@@ -598,3 +609,16 @@ class ConfigAccumulator :
         if containerName not in self._containerConfig :
             raise KeyError ("unknown container for output: " + containerName)
         return self._containerConfig[containerName].outputs
+
+
+    def getSelectionNames (self, containerName) :
+        """Retrieve set of unique selections defined for a given container"""
+        if containerName not in self._containerConfig :
+            return []
+        config = self._containerConfig[containerName]
+        # because cuts are registered individually, selection names can repeat themselves
+        # but we are interested in unique names only
+        selectionNames = set()
+        for selection in config.selections:
+            selectionNames.add(selection.name)
+        return selectionNames
\ No newline at end of file
diff --git a/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py b/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py
index f2c5cdf7ebde..41b3a4dc0f8a 100644
--- a/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py
+++ b/PhysicsAnalysis/Algorithms/AsgAnalysisAlgorithms/python/OutputAnalysisConfig.py
@@ -16,6 +16,8 @@ class OutputAnalysisConfig (ConfigBlock):
         self.addOption ('containers', {}, type=None)
         self.addOption ('treeName', 'analysis', type=str)
         self.addOption ('metTermName', 'Final', type=str)
+        self.addOption ('storeSelectionFlags', True, type=bool)
+        self.addOption ('selectionFlagPrefix', 'passSelection', type=str)
         self.addOption ('systematicsHistogram', None , type=str)
         self.addOption ('commands', [], type=None,
                         info="a list of commands for branch selection/configuration")
@@ -23,6 +25,9 @@ class OutputAnalysisConfig (ConfigBlock):
 
     def makeAlgs (self, config) :
 
+        if self.storeSelectionFlags:
+            self.createSelectionFlagBranches(config)
+
         outputConfigs = {}
         for prefix in self.containers.keys() :
             containerName = self.containers[prefix]
@@ -107,3 +112,38 @@ class OutputAnalysisConfig (ConfigBlock):
         if self.systematicsHistogram is not None:
             sysDumper = config.createAlgorithm( 'CP::SysListDumperAlg', 'SystematicsPrinter' )
             sysDumper.histogramName = self.systematicsHistogram
+
+    def createSelectionFlagBranches(self, config):
+        """
+        For each container and for each selection, create a single pass variable in output NTuple,
+        which aggregates all the selections flag of the given selection. For example, this can include
+        pT, eta selections, some object ID selection, overlap removal, etc.
+        The goal is to have only one flag per object and working point in the output NTuple.
+        """
+        for prefix in self.containers.keys() :
+            outputContainerName = self.containers[prefix]
+            containerName = config.getOutputContainerOrigin(outputContainerName)
+
+            # EventInfo is one obvious example of a container that has no object selections
+            if containerName == 'EventInfo':
+                continue
+
+            selectionNames = config.getSelectionNames(containerName)
+            for selectionName in selectionNames:
+                # skip default selection
+                if selectionName == '':
+                    continue
+                self.makeSelectionSummaryAlg(config, containerName, selectionName)
+
+    def makeSelectionSummaryAlg(self, config, containerName, selectionName):
+        """
+        Schedule an algorithm to pick up all cut flags for a given selectionName.
+        The summary selection flag is written to output as selectionFlagPrefix_selectionName.
+        """
+        alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
+                                      f'ObjectSelectionSummary_{containerName}_{selectionName}')
+        selectionFlagName = self.selectionFlagPrefix + '_' + selectionName
+        alg.selectionDecoration = selectionFlagName
+        alg.particles = config.readName (containerName)
+        alg.preselection = config.getFullSelection (containerName, selectionName)
+        config.addOutputVar (containerName, selectionFlagName, selectionFlagName)
\ No newline at end of file
-- 
GitLab