diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt index ec08633e8e807f006210971c5d6d8746be5a4869..f7aca06d08f52a7a1b08c1f52d1d0afe0473cb61 100644 --- a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt @@ -12,11 +12,36 @@ atlas_depends_on_subdirs( PUBLIC PhysicsAnalysis/DerivationFramework/DerivationFrameworkInterfaces Trigger/TrigCost/EnhancedBiasWeighter Trigger/TrigAnalysis/TrigAnalysisInterfaces + Trigger/TrigAnalysis/TriggerMatchingTool + Trigger/TrigDecisionTool/TrigDecisionTool + Trigger/TrigEvent/TrigNavStructure + Event/xAOD/xAODBase + Event/xAOD/xAODCore + Event/xAOD/xAODTrigger + Event/xAOD/xAODEgamma + Event/xAOD/xAODCaloEvent + Event/FourMomUtils PRIVATE Event/xAOD/xAODEventInfo Tools/PathResolver External/AtlasROOT) +find_package( ROOT COMPONENTS Core Tree MathCore Hist RIO pthread ) + +atlas_add_library( + DerivationFrameworkTriggerLib + src/*.cxx + NO_PUBLIC_HEADERS + INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} + LINK_LIBRARIES ${ROOT_LIBRARIES} GaudiKernel AthenaBaseComps xAODBase TrigDecisionToolLib TrigNavStructure xAODCore xAODTrigger xAODCaloEvent FourMomUtils xAODEgamma +) + +atlas_add_component( + DerivationFrameworkTrigger + src/components/*.cxx + LINK_LIBRARIES DerivationFrameworkTriggerLib +) + # Install files from the package: atlas_install_python_modules( python/*.py ) atlas_install_joboptions( share/*.py ) diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py new file mode 100644 index 0000000000000000000000000000000000000000..8df9c400da694b09331723947ff13a5eea339cf7 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py @@ -0,0 +1,103 @@ +# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration + +from DerivationFrameworkTrigger.DerivationFrameworkTriggerConf import ( + DerivationFramework__TriggerMatchingTool) +from DerivationFrameworkCore.DerivationFrameworkCoreConf import ( + DerivationFramework__CommonAugmentation) +from DerivationFrameworkCore.DerivationFrameworkMaster import ( + DerivationFrameworkJob) +import AthenaCommon.AppMgr as AppMgr +from AthenaCommon.Configurable import Configurable +import copy +import logging +log = logging.getLogger(__name__) + +class TriggerMatchingHelper(object): + """ Helper class for setting up shared trigger matching. + + 'Shared' trigger matching uses a algorithm+tool that are shared across + several different derivation formats that can be run in the same trains + together. The tool can only be run once per event and sharing an algorithm + ensures that this happens correctly. + + This class can technically support multiple different tool + configurations (set by the kwargs in the __init__ function). However + right now we only really expect this to be used with its default + settings. + """ + # Register of tool and algorithm pairs + _register = {} + + @classmethod + def get_alg_and_tool(cls, **kwargs): + properties = copy.deepcopy( + DerivationFramework__TriggerMatchingTool.getDefaultProperties()) + if "name" not in kwargs or kwargs["name"] == Configurable.DefaultName: + kwargs["name"] = "DFTriggerMatchingTool" + properties.update(kwargs) + # We don't want to hash the ChainNames as they don't affect the physics + keys = sorted(k for k in properties if k != "ChainNames") + hashval = hash(tuple(str(properties[k]) for k in keys) ) + if hashval not in cls._register: + # We need to make a new alg and tool, but first we have to check + # that two arguments do not collide - the name and the output prefix. + # The name mustn't collide because otherwise athena will just return + # us the same object, the output prefix mustn't collide because + # otherwise we'll get errors trying to write containers + if any(t.name() == properties["name"] + for _, t in cls._register.itervalues() ): + raise ValueError("Attempting to create a new trigger matching tool with the same name as an existing one") + tool = DerivationFramework__TriggerMatchingTool(**properties) + AppMgr.ToolSvc += tool + alg = DerivationFramework__CommonAugmentation( + name=properties["name"]+"_Alg", + AugmentationTools = [tool]) + cls._register[hashval] = (alg, tool) + return cls._register[hashval] + + def __init__(self, trigger_list, add_to_df_job=False, **kwargs): + """ Create the helper + + The trigger_list is the list of triggers that *this* derivation + needs matching results for. + + kwargs can be used to pass extra properties to the underlying + DerivationFramework::TriggerMatchingTool. If you change *any* + properties you *must* also set the 'name' and + 'OutputContainerPrefix' keys. + + If this is a 'private' configuration (i.e. one that is to be used + *only* in your format) then these values should include your + format's name and you can just add self.tool to your kernel + algorithm. However, if you're using a shared algorithm and tool (one + that doesn't include your format's name) then you *must* either + include self.alg in your derivation's sequence, or set + 'add_to_df_job' to True, which will add the algorithm directly into + the main DF job. You should *never* set 'add_to_df_job' to True for + a private configuration! + + Doing anything else will likely cause the derivation framework to + fail when it runs multiple formats in a train. Unless you run a + train in your local tests, this will not show up in those tests and + will just break a nightly instead! + """ + self.alg, self.tool = self.get_alg_and_tool(**kwargs) + if add_to_df_job: + global DerivationFrameworkJob + if self.alg not in DerivationFrameworkJob: + DerivationFrameworkJob += self.alg + self.tool.ChainNames += trigger_list + self.trigger_list = trigger_list[:] + self.container_prefix = self.tool.OutputContainerPrefix + + def output_containers(self): + """ Get the full list of containers to be written out """ + return ["{0}{1}".format(self.container_prefix, chain).replace(".", "_") + for chain in self.trigger_list] + def add_to_slimming(self, slimming_helper): + """ Add the created containers to the output """ + slimming_helper.AllVariables += self.output_containers() + for cont in self.output_containers(): + slimming_helper.AppendToDictionary.update({ + cont : "xAOD::TrigCompositeContainer", + cont+"Aux" : "AOD::AuxContainerBase!"}) diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py deleted file mode 100644 index 74583d364ec2ca794156596c7254d9b234a940c6..0000000000000000000000000000000000000000 --- a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration - diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py new file mode 100644 index 0000000000000000000000000000000000000000..badb4e1280668d40abf9e61fee5ced201614c0bb --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py @@ -0,0 +1,48 @@ +#******************************************************************** +# TRIG6.py +# For removing empty collections from Trigger-Level Analysis AODs +# Modelled on EXOT2.py +# For running on DataScouting stream, disable AODFix since this attempts +# to initialise some BTagging which requires AntiKt4EMTopo jets: +# --preExec 'rec.doApplyAODFix.set_Value_and_Lock(False)' +#******************************************************************** +from DerivationFrameworkCore.DerivationFrameworkMaster import * + +trig6Seq = CfgMgr.AthSequencer("TRIG6Sequence") + + +#==================================================================== +# SET UP STREAM +#==================================================================== +from AthenaServices.Configurables import ThinningSvc, createThinningSvc + +streamName = derivationFlags.WriteDAOD_TRIG6Stream.StreamName +fileName = buildFileName( derivationFlags.WriteDAOD_TRIG6Stream ) +TRIG6Stream = MSMgr.NewPoolRootStream( streamName, fileName ) +TRIG6Stream.AcceptAlgs(["TRIG6Kernel"]) + + +#======================================= +# CREATE THE DERIVATION KERNEL ALGORITHM +#======================================= +# No skimming - keep all events + +from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel +DerivationFrameworkJob += trig6Seq +trig6Seq += CfgMgr.DerivationFramework__DerivationKernel(name = "TRIG6Kernel", + SkimmingTools = [], + ThinningTools = []) + + +#==================================================================== +# Add the DS container to the output stream - no slimming done +#==================================================================== +from DerivationFrameworkCore.SlimmingHelper import SlimmingHelper +TRIG6SlimmingHelper = SlimmingHelper("TRIG6SlimmingHelper") + +TRIG6SlimmingHelper.AllVariables = [ + "HLT_xAOD__JetContainer_TrigHLTJetDSSelectorCollection", + ] + +TRIG6SlimmingHelper.AppendContentToStream(TRIG6Stream) + diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py new file mode 100644 index 0000000000000000000000000000000000000000..97e40b0b87256c7b2180e57500e78b5b9720cf52 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py @@ -0,0 +1,247 @@ +from AthenaCommon.Constants import * +#==================================================================== +# TRIG7.py +# Purpose: investigate potential for using substructure in large-R jet trigger +# Skimming: require a high pT central ungroomed a10 jet and an HLT a10 jet trigger +# Content: +# AntiKt10Truth[Jets,TrimmedPtFrac5SmallR20Jets] +# AntiKt10LCTopo [Jets,TrimmedPtFrac5SmallR20Jets] +# AntiKt [2,4] PV0TrackJets - we have to have these to get trimmed jets, don't know why +# AntiKt4EMPFlow [Jets,TopoJets] - TJ asked for these +# CaloCalTopoClusters matched to ungroomed a10 - for playing with jet rec and jss +# Based loosely on JETM6, JETM15, JETM8,... +# reductionConf flag TRIG7 in Reco_tf.py +#==================================================================== + +# pt and eta cuts for jet selection +jetptmin='250.' +jetetamax='2.5' + + +from DerivationFrameworkCore.DerivationFrameworkMaster import * +from DerivationFrameworkJetEtMiss.JetCommon import * +from DerivationFrameworkJetEtMiss.ExtendedJetCommon import * + +#==================================================================== +# DEFAULT IS NO TRUTH - NEED TO ADD IT EXPLICITLY THEN THIN IT BELOW +#==================================================================== +#if DerivationFrameworkIsMonteCarlo: + +# from DerivationFrameworkMCTruth.MCTruthCommon import addStandardTruthContents +# addStandardTruthContents() + +#==================================================================== +# TRIGGER SKIMMING TOOL - REQUIRE A JET TRIGGER TO HAVE BEEN PASSED +#==================================================================== +from DerivationFrameworkJetEtMiss import TriggerLists +jetTriggers = TriggerLists.jetTrig() + +from DerivationFrameworkTools.DerivationFrameworkToolsConf import DerivationFramework__TriggerSkimmingTool +TRIG7TrigSkimmingTool = DerivationFramework__TriggerSkimmingTool(name = "TRIG7TrigSkimmingTool", TriggerListOR = jetTriggers) +ToolSvc += TRIG7TrigSkimmingTool + +#==================================================================== +# OFFLINE SKIMMING TOOL - REQUIRE A HIGH PT CENTRAL FAT JET +#==================================================================== +from DerivationFrameworkTools.DerivationFrameworkToolsConf import DerivationFramework__xAODStringSkimmingTool + +jetSelection = '(count( AntiKt10LCTopoJets.pt > '+jetptmin+'*GeV && abs(AntiKt10LCTopoJets.eta) < '+jetetamax+' ) >=1)' + +TRIG7OfflineSkimmingTool = DerivationFramework__xAODStringSkimmingTool( name = "TRIG7OfflineSkimmingTool", expression = jetSelection) +ToolSvc += TRIG7OfflineSkimmingTool + + +#======================================= +# CREATE PRIVATE SEQUENCE +#======================================= +trig7Seq = CfgMgr.AthSequencer("TRIG7Sequence") +DerivationFrameworkJob += trig7Seq + +#==================================================================== +# CREATE THE DERIVATION TRIG SKIM KERNEL ALGORITHM AND PASS TRIG SKIM TOOL +#==================================================================== +from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel +trig7Seq += CfgMgr.DerivationFramework__DerivationKernel(name="TRIG7TrigSkimKernel",SkimmingTools = [TRIG7TrigSkimmingTool]) + +#======================================= +# RESTORE AOD-REDUCED JET COLLECTIONS - THESE JETS ARE NOT IN THE INPUT AOD +#======================================= +reducedJetList = ["AntiKt2PV0TrackJets","AntiKt4PV0TrackJets","AntiKt10LCTopoJets","AntiKt10TruthJets"] +replaceAODReducedJets(reducedJetList,trig7Seq,"TRIG7") +addDefaultTrimmedJets(trig7Seq,"TRIG7") + + +#==================================================================== +# SET UP STREAM +#==================================================================== +# The base name (DAOD_TRIG7 here) must match the string in +# DerivationFrameworkProdFlags (in DerivationFrameworkCore) +streamName = derivationFlags.WriteDAOD_TRIG7Stream.StreamName +fileName = buildFileName( derivationFlags.WriteDAOD_TRIG7Stream ) +TRIG7Stream = MSMgr.NewPoolRootStream( streamName, fileName ) +TRIG7Stream.AcceptAlgs(["TRIG7MainKernel"]) + +#==================================================================== +# SET UP THINNING SERVICE +#==================================================================== +#from AthenaServices.Configurables import ThinningSvc, createThinningSvc +#augStream = MSMgr.GetStream( streamName ) +#evtStream = augStream.GetEventStream() +#svcMgr += createThinningSvc( svcName="TRIG7ThinningSvc", outStreams=[evtStream] ) + +#==================================================================== +# THINNING HELPER +#==================================================================== + +from DerivationFrameworkCore.ThinningHelper import ThinningHelper +TRIG7ThinningHelper = ThinningHelper( "TRIG7ThinningHelper" ) +### https://gitlab.cern.ch/atlas/athena/blob/21.2/PhysicsAnalysis/DerivationFramework/DerivationFrameworkExamples/share/TriggerContentExample.py +TRIG7ThinningHelper.TriggerChains = '^(?!.*_[0-9]*(mu|e|xe|tau|ht|xs|te))(?!HLT_j[0-9]+_[0-9]*j[0-9]+.*)HLT_j[0-9]+_a10.*' +TRIG7ThinningHelper.AppendToStream( TRIG7Stream ) + +thinningTools = [] + + +#==================================================================== +# CLUSTER THINNING TOOL +#==================================================================== +from DerivationFrameworkCalo.DerivationFrameworkCaloConf import DerivationFramework__JetCaloClusterThinning + +jetcuts = '(AntiKt10LCTopoJets.pt > '+jetptmin+'*GeV && abs(AntiKt10LCTopoJets.eta) < '+jetetamax+')' + +## save clusters matched to ungroomed jets with jetcuts selection +TRIG7ClusterThinningTool = DerivationFramework__JetCaloClusterThinning(name = "TRIG7ClusterThinningTool", + ThinningService = TRIG7ThinningHelper.ThinningSvc(), + SGKey = "AntiKt10LCTopoJets", + TopoClCollectionSGKey = "CaloCalTopoClusters", + SelectionString = jetcuts, + AdditionalClustersKey = ["LCOriginTopoClusters"]) + +ToolSvc += TRIG7ClusterThinningTool +thinningTools.append(TRIG7ClusterThinningTool) + +trigjetcuts = '(HLT_xAOD__JetContainer_a10tclcwsubjesFS.pt > '+jetptmin+'*GeV && abs(HLT_xAOD__JetContainer_a10tclcwsubjesFS.eta) < '+jetetamax+')' + +## save clusters matched to ungroomed trigger jets passing trigjetcuts +TRIG7TrigClusterThinningTool = DerivationFramework__JetCaloClusterThinning(name = "TRIG7TrigClusterThinningTool", + ThinningService = TRIG7ThinningHelper.ThinningSvc(), + SGKey = "HLT_xAOD__JetContainer_a10tclcwsubjesFS", + TopoClCollectionSGKey = "CaloCalTopoClusters", + SelectionString = trigjetcuts + ) +ToolSvc += TRIG7TrigClusterThinningTool +thinningTools.append(TRIG7TrigClusterThinningTool) + +#==================================================================== +# TRACK THINNING TOOL +#==================================================================== +""" +from DerivationFrameworkInDet.DerivationFrameworkInDetConf import DerivationFramework__JetTrackParticleThinning + +## save tracks matched to ungroomed jets passing jetcuts +TRIG7TrackThinningTool = DerivationFramework__JetTrackParticleThinning(name = "TRIG7TrackThinningTool", + ThinningService = TRIG7ThinningHelper.ThinningSvc(), + JetKey = "AntiKt10LCTopoJets", + InDetTrackParticlesKey = "InDetTrackParticles", + SelectionString = jetcuts) +ToolSvc += TRIG7TrackThinningTool +thinningTools.append(TRIG7TrackThinningTool) + +## save trackss matched to ungroomed trigger jets passing trigjets cuts +TRIG7TrigTrackThinningTool = DerivationFramework__JetTrackParticleThinning(name = "TRIG7TrigTrackThinningTool", + ThinningService = TRIG7ThinningHelper.ThinningSvc(), + JetKey = "HLT_xAOD__JetContainer_a10tclcwsubjesFS", + InDetTrackParticlesKey = "InDetTrackParticles", + SelectionString = trigjetcuts) + +ToolSvc += TRIG7TrigTrackThinningTool +thinningTools.append(TRIG7TrigTrackThinningTool) +""" +#==================================================================== +# TRUTH THINNING TOOL +#==================================================================== +doTruthThinning = False +from AthenaCommon.GlobalFlags import globalflags +if doTruthThinning and DerivationFrameworkIsMonteCarlo: + from DerivationFrameworkMCTruth.DerivationFrameworkMCTruthConf import DerivationFramework__MenuTruthThinning + TRIG7TruthThinningTool = DerivationFramework__MenuTruthThinning(name = "TRIG7TruthThinningTool", + ThinningService = TRIG7ThinningHelper.ThinningSvc(), + WriteAllStable = True) + ToolSvc += TRIG7TruthThinningTool + thinningTools.append(TRIG7TruthThinningTool) + + + +#==================================================================== +# CREATE THE OFFLINE SKIM KERNEL ALGORITHM AND PASS OFFLINE SKIM TOOL AND THINNING TOOLS +#==================================================================== +from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel +trig7Seq += CfgMgr.DerivationFramework__DerivationKernel( name = "TRIG7MainKernel", + AugmentationTools = [] , + SkimmingTools = [TRIG7OfflineSkimmingTool], + ThinningTools = thinningTools) + + + +#==================================================================== +# CONTENT LIST +#==================================================================== +from DerivationFrameworkCore.SlimmingHelper import SlimmingHelper + +TRIG7SlimmingHelper = SlimmingHelper("TRIG7SlimmingHelper") + +# can only put standard collections in here - anything else needs to be appended to dictionary +TRIG7SlimmingHelper.SmartCollections = ["PrimaryVertices", + "AntiKt4EMTopoJets","AntiKt4LCTopoJets","AntiKt4EMPFlowJets", +# "BTagging_AntiKt2Track","BTagging_AntiKt4EMTopo", + "AntiKt10LCTopoTrimmedPtFrac5SmallR20Jets"] +# "TauJets"] +# "InDetTrackParticles"] + + +TRIG7SlimmingHelper.AllVariables = ["Kt4EMTopoOriginEventShape","Kt4LCTopoOriginEventShape","Kt4EMPFlowEventShape"] +# "TruthVertices", +# "TruthEvents", +# "TruthParticles"] + +### these jets are on the fly +TRIG7SlimmingHelper.AppendToDictionary={"AntiKt10LCTopoJets" : "xAOD::JetContainer", + "AntiKt10LCTopoJetsAux." : " xAOD::JetAuxContainer", + "AntiKt10TruthJets" : "xAOD::JetContainer", + "AntiKt10TruthJetsAux." : " xAOD::JetAuxContainer", + "HLT_xAOD__JetContainer_a10ttclcwjesFS" : "xAOD::JetContainer", + "HLT_xAOD__JetContainer_a10ttclcwjesFSAux." : "xAOD::JetAuxContainer", + "HLT_xAOD__JetContainer_a10r_tcemsubjesISFS" : "xAOD::JetContainer" , + "HLT_xAOD__JetContainer_a10r_tcemsubjesISFSAux." : "xAOD::JetAuxContainer", + "HLT_xAOD__JetContainer_a10r_tcemsubjesFS" : "xAOD::JetContainer" , + "HLT_xAOD__JetContainer_a10r_tcemsubjesFSAux." : "xAOD::JetAuxContainer" } + +### want all vars for trigger jets +from DerivationFrameworkCore.JetTriggerContent import JetTriggerContent +for trig in JetTriggerContent: + if 'HLT' in trig and not 'Aux' in trig: + TRIG7SlimmingHelper.AllVariables += [trig] + +TRIG7SlimmingHelper.AllVariables += ["AntiKt10LCTopoJets"] +TRIG7SlimmingHelper.AllVariables += ["AntiKt10TruthJets"] +TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10ttclcwjesFS"] +TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10r_tcemsubjesISFS"] +TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10r_tcemsubjesFS"] + +TRIG7SlimmingHelper.AppendToDictionary["CaloCalTopoClusters"]='xAOD::CaloClusterContainer' +TRIG7SlimmingHelper.AppendToDictionary["CaloCalTopoClustersAux"]='xAOD::CaloClusterAuxContainer' +TRIG7SlimmingHelper.ExtraVariables = ["CaloCalTopoClusters.calEta.calPhi.CENTER_MAG.calE.calM.rawEta.rawPhi.rawE.rawM"] + + +addJetOutputs(TRIG7SlimmingHelper,["AntiKt10LCTopoJets", + "AntiKt10TruthJets", + "HLT_xAOD__JetContainer_a10ttclcwjesFS", + "HLT_xAOD__JetContainer_a10r_tcemsubjesISFS", + "HLT_xAOD__JetContainer_a10r_tcemsubjesFS"]) +# "TRIG7"]) + + +TRIG7SlimmingHelper.AppendContentToStream(TRIG7Stream) + +#TRIG7Stream.RemoveItem("xAOD::TrigNavigation#*") +#TRIG7Stream.RemoveItem("xAOD::TrigNavigationAuxInfo#*") diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h new file mode 100644 index 0000000000000000000000000000000000000000..4ca03239c254b8ef568e30341e0593df6dbe67a9 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H +#define DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H + +#include "RangedItr.h" + +namespace DerivationFramework { namespace TriggerMatchingUtils { + + /** + * @brief Helper function for inserting an element into a sorted vector + * @tparam T The type stored in the vector + * @param[out] vec The vector to insert into + * @param ele The element to insert + * @return Whether or not the element was inserted. + * The element will not be inserted if it already exists. + */ + template <typename T> + bool insertIntoSortedVector(std::vector<T>& vec, const T& ele); + + /** + * @brief Helper function to create a sorted vector from an unsorted one. + * @tparam T The type of the iterator + * @param begin The start of the unsorted range + * @param end The end of the unsorted range + */ + template <typename T> + std::vector<typename T::value_type> sorted(T begin, T end); + + /** + * @brief Get all valid, unique combinations of distinct elements from the + * input ranges. + * @param inputs The ranges over vectors of possible elements. + */ + // First, a note on the type of the argument. This is essentially a way of + // passing a vector of vectors but only with iterators over the vectors. + template <typename T> + std::vector<std::vector<T>> getAllDistinctCombinations( + std::vector<RangedItr<typename std::vector<T>::const_iterator>>& inputs); + +}} //> end namespace DerivationFramework::TriggerMatchingUtils +#include "BuildCombinations.icc" +#endif //> !DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc new file mode 100644 index 0000000000000000000000000000000000000000..92f6b1683b9b886d4b383c32325ce241ed5dd8d4 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc @@ -0,0 +1,97 @@ +/* + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration +*/ + +namespace DerivationFramework { namespace TriggerMatchingUtils { + + template <typename T> + bool insertIntoSortedVector(std::vector<T>& vec, const T& ele) + { + auto itr = std::lower_bound(vec.begin(), vec.end(), ele); + // if no lower bound is found or the found element doesn't equal ele then + // insert ele at this point + if (itr == vec.end() || *itr != ele) { + vec.insert(itr, ele); + return true; + } + else + return false; + } + + template <typename T> + std::vector<typename T::value_type> sorted(T begin, T end) + { + std::vector<typename T::value_type> output(begin, end); + std::sort(output.begin(), output.end() ); + return output; + } + + + template <typename T> + std::vector<std::vector<T>> getAllDistinctCombinations( + std::vector<RangedItr<typename std::vector<T>::const_iterator>>& inputs) + { + // This is where the output will go + std::vector<std::vector<T>> output; + + // If inputs is empty or if any of the ranges within inputs are empty then + // no combinations will be found! + if (inputs.empty() || std::any_of(inputs.begin(), inputs.end(), + [] (const auto& rangedItr) { return rangedItr.exhausted(); }) ) + return output; + + // This is the current output combination being built + std::vector<T> currentCombination; + + // This iterator points to where in the vector of ranged iterators we are + // looking. This can therefore often be acting as an iterator to and + // iterator! *itr is an iterator, **itr is a const T&! + // To disambiguate I will refer to itr as the outer iterator and *itr as the + // inner iterator. + auto itr = inputs.begin(); + while (true) { + // Have we exhausted our current inner iterator? + if (itr == inputs.end() || itr->exhausted() ) { + // If the outer iterator has returned to the beginning then we're done + // and every combination has been checked. + if (itr == inputs.begin() ) + break; + // otherwise we need to continue. To do this we + // - reset the current inner iterator. This is so that the next time we + // return to it we look through all the elements again + // - step the outer iterator back + // - remove the last element of the current combination. That + // element will be replaced by the next valid one from the list *now* + // pointed to by the outer iterator. + if (itr != inputs.end() ) + itr->restart(); + --itr; + currentCombination.pop_back(); + } //> end if inner iterator is exhausted + else { + // Check if the pointed-to element is already in the current combination + const T& currentCandidate = **itr; + // Now that we've accessed this value, step past it + ++*itr; + if (std::find( + currentCombination.begin(), + currentCombination.end(), + currentCandidate) == currentCombination.end() ) { + // If it isn't, then add it. + currentCombination.push_back(currentCandidate); + // Step the outer iterator forwards so we're looking at the next list + ++itr; + if (itr == inputs.end() ) { + // If we're at the end then this is a complete combination, add it + // to the output. + insertIntoSortedVector(output, sorted( + currentCombination.begin(), + currentCombination.end() ) ); + } + } //> end if currentCandidate is in currentCombination + } //> end else (if inner iterator is exhausted) + } //> end while loop + return output; + } //> end function getAllDistinctCombinations + +}} //> end namespace DerivationFramework::TriggerMatchingUtils diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h new file mode 100644 index 0000000000000000000000000000000000000000..a633ddd37925aeba8f0b64aa0acb244538819eaa --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H +#define DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H + +#include <vector> +#include <iterator> +#include <type_traits> + +namespace DerivationFramework { namespace TriggerMatchingUtils { +/** + * @brief utility class that acts wraps a bidirectional iterator. + * This class acts as both and range and an iterator. + */ +template <typename T> + class RangedItr { + protected: + using tr = std::iterator_traits<T>; + static_assert(std::is_base_of<std::bidirectional_iterator_tag, typename tr::iterator_category>::value, "Templated type must be a random access iterator"); + public: + // A full implementation of this idea would allow for any input + // iterator type. For this version, just require at least bidirectional + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename tr::value_type; + using reference = typename tr::reference; + using pointer = typename tr::pointer; + using difference_type = typename tr::difference_type; + + /// Default Constructor + RangedItr() = default; + + /// Construct from a beginning and an end + RangedItr(const T& begin, const T& end) : + RangedItr(begin, end, begin) {} + + /// Construct from a beginning, an end and a starting position + RangedItr(const T& begin, const T& end, const T& position) : + m_begin(begin), m_end(end), m_position(position) {} + + /// Iterator interface + reference operator*() { return m_position.operator*(); } + pointer operator->() { return m_position.operator->(); } + + RangedItr& operator++() { + ++m_position; + return *this; + } + RangedItr& operator++(int) { + RangedItr ret = *this; + ++m_position; + return ret; + } + + RangedItr& operator--() { + --m_position; + return *this; + } + RangedItr& operator--(int) { + RangedItr ret = *this; + --m_position; + return ret; + } + + bool operator==(const RangedItr& other) { + return m_begin == other.m_begin && + m_end == other.m_end && + m_position == other.m_position; + } + + bool operator!=(const RangedItr& other) { + return !(*this == other); + } + + /// Is this iterator exhausted? + bool exhausted() const { return m_position == m_end; } + + /// Reset this iterator to its start + void restart() { m_position = m_begin; } + + /// Make this act as a range + RangedItr begin() const { return RangedItr(m_begin, m_end, m_begin); } + RangedItr end() const { return RangedItr(m_begin, m_end, m_end); } + difference_type size() const { return std::distance(m_begin, m_end); } + + /// Allow conversion back to the original iterator type + operator T() const { return m_position; } + private: + T m_begin; + T m_end; + T m_position; + + }; //> end class RangedItr +} } //> end namespace DerivationFramework::TriggerMatchingUtils +#endif //> !DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx new file mode 100644 index 0000000000000000000000000000000000000000..03e6757b5a6e2b24284c72c074335ba79a01353f --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx @@ -0,0 +1,252 @@ +/* + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration +*/ + +#include "TriggerMatchingTool.h" +#include "BuildCombinations.h" +#include "GaudiKernel/ServiceHandle.h" +#include "GaudiKernel/IIncidentSvc.h" +#include "xAODCore/AuxContainerBase.h" +#include "xAODCaloEvent/CaloClusterContainer.h" +#include "FourMomUtils/xAODP4Helpers.h" +#include <memory> +#include <algorithm> + +namespace { + /// Helper typedefs for accessors/decorators, vectors of ele links + template <typename T> + using constAcc_t = SG::AuxElement::ConstAccessor<T>; + template <typename T> + using dec_t = SG::AuxElement::Decorator<T>; + template <typename T> + using vecLink_t = std::vector<ElementLink<T>>; +} //> end anonymous namespace + +namespace DerivationFramework { + + TriggerMatchingTool::TriggerMatchingTool( + const std::string& type, + const std::string& name, + const IInterface* pSvcLocator) : + AthAlgTool(type, name, pSvcLocator) + { + declareInterface<IAugmentationTool>(this); + declareProperty("ChainNames", m_chainNames, + "The list of trigger chains to match."); + declareProperty("OnlineParticleTool", m_trigParticleTool, + "The tool to retrieve online particles from the navigation."); + declareProperty("InputElectrons", + m_offlineInputs[xAOD::Type::Electron] = "Electrons", + "Offline electron candidates for matching."); + declareProperty("InputPhotons", + m_offlineInputs[xAOD::Type::Photon] = "Photons", + "Offline photon candidates for matching."); + declareProperty("InputMuons", + m_offlineInputs[xAOD::Type::Muon] = "Muons", + "Offline muon candidates for matching."); + declareProperty("InputTaus", + m_offlineInputs[xAOD::Type::Tau] = "TauJets", + "Offline tau candidates for matching."); + declareProperty("DRThreshold", m_drThreshold = 0.1, + "The maximum dR between an offline and an online particle to consider " + "a match between them."); + declareProperty("Rerun", m_rerun = false, + "Whether to match triggers in rerun mode."); + declareProperty("OutputContainerPrefix", m_outputPrefix="TrigMatch_", + "The prefix to add to the output containers."); + declareProperty("CheckEmptyChainGroups", m_checkEmptyChainGroups = true, + "If set, discard any empty chain groups. Otherwise these will cause " + "a job failure."); + declareProperty("InputDependentConfig", m_inputDependentConfig=false, + "Warn when a trigger is removed (if the configuration is dependent " + "on the inputs, removal is not expected)."); + } + + StatusCode TriggerMatchingTool::initialize() + { + ATH_MSG_INFO( "Initializing " << name() ); + + // Remove any duplicates from the list of chain names + std::sort(m_chainNames.begin(), m_chainNames.end() ); + m_chainNames.erase(std::unique(m_chainNames.begin(), m_chainNames.end() ), m_chainNames.end() ); + + ATH_CHECK( m_trigParticleTool.retrieve() ); + return StatusCode::SUCCESS; + } + + StatusCode TriggerMatchingTool::addBranches() const + { + if (m_firstEvent) { + m_firstEvent = false; + auto itr = m_chainNames.begin(); + while (itr != m_chainNames.end() ) { + const Trig::ChainGroup* cg = m_tdt->getChainGroup(*itr); + if (cg->getListOfTriggers().size() == 0){ + if (m_inputDependentConfig) + ATH_MSG_WARNING("Removing trigger " << (*itr) << " -- suggests a bad tool configuration (asking for triggers not in the menu)"); + itr = m_chainNames.erase(itr); + } else + ++itr; + } + } + + // Now, get all the possible offline candidates + std::map<xAOD::Type::ObjectType, particleVec_t> offlineCandidates; + for (const auto& p : m_offlineInputs) { + // Skip any that may have been disabled by providing an empty string to + // the corresponding property + if (p.second.empty() ) + continue; + const xAOD::IParticleContainer* cont(nullptr); + ATH_CHECK( evtStore()->retrieve(cont, p.second) ); + offlineCandidates.emplace(std::make_pair( + p.first, + particleVec_t(cont->begin(), cont->end() ) + ) ); + } + + // Store possible matches from online to offline particles here + // We do this as multiple chains may use the same online particles so we can + // reuse this information. + std::map<const xAOD::IParticle*, particleVec_t> matches; + + // Iterate through the chains to get the matchings + for (const std::string& chain : m_chainNames) { + // Create the output + xAOD::TrigCompositeContainer* container(nullptr); + ATH_CHECK( createOutputContainer(container, chain) ); + + // Get the list of online combinations + std::vector<particleVec_t> onlineCombinations; + ATH_CHECK( m_trigParticleTool->retrieveParticles(onlineCombinations, chain, m_rerun) ); + + ATH_MSG_DEBUG( + onlineCombinations.size() << " combinations found for chain" << chain); + + // If no combinations were found (because the trigger was not passed) then + // we can skip this trigger + if (onlineCombinations.size() == 0) + continue; + + using particleRange_t = TriggerMatchingUtils::RangedItr<typename particleVec_t::const_iterator>; + // Now build up the list of offline combinations; + std::vector<particleVec_t> offlineCombinations; + for (const particleVec_t& combination : onlineCombinations) { + // Here we store the possible candidates for the matching. We use the + // range type as a lightweight method to carry around a view of a vector + // without copying it (the RangedItr is essentially a combination of 3 + // iterators). + std::vector<particleRange_t> matchCandidates; + for (const xAOD::IParticle* part : combination) { + const particleVec_t& possibleMatches = getCandidateMatchesFor( + part, offlineCandidates, matches); + matchCandidates.emplace_back(possibleMatches.begin(), possibleMatches.end() ); + } //> end loop over particles + // Get all possible combinations of offline objects that could match to + // this particular online combination. + auto theseOfflineCombinations = + TriggerMatchingUtils::getAllDistinctCombinations<const xAOD::IParticle*>( + matchCandidates); + if (msgLvl(MSG::VERBOSE) ) { + // Spit out some verbose information + ATH_MSG_VERBOSE( + "Checking matching for chain " << chain + << " with " << matchCandidates.size() << " legs"); + std::size_t idx = 0; + for (const particleRange_t& range : matchCandidates) + ATH_MSG_VERBOSE( "Leg #" << idx++ << " has " << range.size() + << " offline candidates." ); + ATH_MSG_VERBOSE( + "Matching generated " << theseOfflineCombinations.size() + << " offline combinations"); + } + // Now push them back into the output. Use a specialised function for + // inserting into a sorted vector that ensures that we only output + // unique combinations + for (const particleVec_t& vec : theseOfflineCombinations) + TriggerMatchingUtils::insertIntoSortedVector(offlineCombinations, vec); + } //> end loop over combinations + + + // Decorate the found combinations onto trigger composites. + for (const particleVec_t& foundCombination : offlineCombinations) { + xAOD::TrigComposite* composite = new xAOD::TrigComposite(); + container->push_back(composite); + static dec_t<vecLink_t<xAOD::IParticleContainer>> dec_links( + "TrigMatchedObjects"); + vecLink_t<xAOD::IParticleContainer>& links = dec_links(*composite); + for (const xAOD::IParticle* part : foundCombination) { + // TODO This might be dangerous if the user provides the wrong types + // for the inputs or the input containers are not the owners (so that + // part->index() isn't the right value...). However I'm not sure what + // the best option is here... + links.emplace_back(m_offlineInputs.at(part->type()), part->index() ); + } + } //> end loop over the found combinations + } //> end loop over chains + return StatusCode::SUCCESS; + } + + StatusCode TriggerMatchingTool::createOutputContainer( + xAOD::TrigCompositeContainer*& container, + const std::string& chain) const + { + auto uniqueContainer = std::make_unique<xAOD::TrigCompositeContainer>(); + auto aux = std::make_unique<xAOD::AuxContainerBase>(); + uniqueContainer->setStore(aux.get() ); + container = uniqueContainer.get(); + std::string name = m_outputPrefix+chain; + // We have to replace '.' characters with '_' characters so that these are + // valid container names... + std::replace(name.begin(), name.end(), '.', '_'); + ATH_CHECK( evtStore()->record(std::move(uniqueContainer), name) ); + ATH_CHECK( evtStore()->record(std::move(aux), name+"Aux.") ); + return StatusCode::SUCCESS; + } + + const TriggerMatchingTool::particleVec_t& TriggerMatchingTool::getCandidateMatchesFor( + const xAOD::IParticle* part, + std::map<xAOD::Type::ObjectType, particleVec_t>& offlineParticles, + std::map<const xAOD::IParticle*, particleVec_t>& cache) const + { + // Build up all the possible matches between online and offline particles + auto cacheItr = cache.find(part); + if (cacheItr == cache.end() ) { + xAOD::Type::ObjectType type = part->type(); + particleVec_t candidates; + if (type == xAOD::Type::CaloCluster) { + // If it's a calo cluster then we need to get the cluster from the + // egamma types. + static constAcc_t<vecLink_t<xAOD::CaloClusterContainer>> acc_calo("caloClusterLinks"); + for (xAOD::Type::ObjectType egType : { + xAOD::Type::Electron, xAOD::Type::Photon}) + { + for (const xAOD::IParticle* cand : offlineParticles[egType]) { + const vecLink_t<xAOD::CaloClusterContainer>& links = acc_calo(*cand); + if (links.size() == 0 || !links.at(0).isValid() ) + continue; + const xAOD::CaloCluster* clus = *links.at(0); + if (matchParticles(part, clus) ) + candidates.push_back(cand); + } + } + } + else { + for (const xAOD::IParticle* cand : offlineParticles[type]) + if (matchParticles(part, cand) ) + candidates.push_back(cand); + } + cacheItr = cache.emplace( + std::make_pair(part, std::move(candidates) ) ).first; + } + return cacheItr->second; + } + + bool TriggerMatchingTool::matchParticles( + const xAOD::IParticle* lhs, + const xAOD::IParticle* rhs) const + { + return xAOD::P4Helpers::deltaR(lhs, rhs, false) < m_drThreshold; + } + +} //> end namespace DerivationFramework diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h new file mode 100644 index 0000000000000000000000000000000000000000..6e3be2757e7c03c469ebc6e6718474cecd9df620 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H +#define DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H + +#include "AthenaBaseComps/AthAlgTool.h" +#include "DerivationFrameworkInterfaces/IAugmentationTool.h" +#include "GaudiKernel/ToolHandle.h" +#include "TriggerMatchingTool/IIParticleRetrievalTool.h" +#include "TrigDecisionTool/TrigDecisionTool.h" +#include "xAODBase/IParticle.h" +#include "xAODBase/IParticleContainer.h" +#include "xAODTrigger/TrigCompositeContainer.h" +#include <vector> + +namespace DerivationFramework { + /** + * @brief Tool to perform trigger matching in the derivation framework. + * + * Trigger matching in the derivation framework has to take the reverse + * approach to matching in analysis code. There, the matching tool is provided + * with a list of analysis particles to match to a given trigger. The + * navigation then proceeds to search out all features that match the types of + * those particles. + * In the derivation framework however, we need to get the full list of all + * particles that a user *could* ask for in their analysis code. To do this, + * we ask the navigation for all the relevant xAOD objects and then use those + * to query the available offline objects. + * + * @author Jon Burr + */ + class TriggerMatchingTool final : + public AthAlgTool, + virtual public IAugmentationTool + { + public: + /// Helper typedefs + using particleVec_t = std::vector<const xAOD::IParticle*>; + /// Constructor + TriggerMatchingTool( + const std::string& type, + const std::string& name, + const IInterface* pSvcLocator); + + /// Initialize the tool + StatusCode initialize() override; + + /// Calculate the matchings + StatusCode addBranches() const override; + + private: + // Properties + /// The list of chain names to match + mutable std::vector<std::string> m_chainNames; + // This being mutable isn't exactly the greatest thing but we can't check + // whether the chains actually exist before the first event. A better + // solution would be to filter the chains by what is available in the + // input file but I don't know if that's possible. + + /// The tool to retrieve the online candidates + ToolHandle<Trig::IIParticleRetrievalTool> m_trigParticleTool + {"Trig::IParticleRetrievalTool/OnlineParticleTool"}; + + /// The input containers to use. These are keyed by xAOD object type + std::map<xAOD::Type::ObjectType, std::string> m_offlineInputs; + + /// The DR threshold to use + float m_drThreshold; + + /// Whether to match in rerun mode or not. + bool m_rerun; + + /// The prefix to place at the beginning of the output containers + std::string m_outputPrefix; + + /// If set, discard any triggers with empty chain groups (break the job + /// otherwise). + bool m_checkEmptyChainGroups; + + /// If using an input-file-dependent config then we warn when triggers + /// are removed + bool m_inputDependentConfig; + + /// The trig decision tool + ToolHandle<Trig::TrigDecisionTool> m_tdt + {"Trig::TrigDecisionTool/TrigDecisionTool"}; + + // Internal values + mutable bool m_firstEvent{true}; + + // Internal functions + /** + * @brief Create an output container for the named chain + * @param[out] container A pointer to the created container + * @param chain The name of the chain to create the container for + * The container will be recorded in the StoreGate using the name of the + * chain as a key. + */ + StatusCode createOutputContainer( + xAOD::TrigCompositeContainer*& container, + const std::string& chain) const; + + /** + * @brief Get all offline particles that could match a given online one. + * @param part The online particle to match against. + * @param offlineParticles The offline particles, key by xAOD type. + * @param cache Store past matches here to speed up future look ups. + */ + const particleVec_t& getCandidateMatchesFor( + const xAOD::IParticle* part, + std::map<xAOD::Type::ObjectType, particleVec_t>& offlineParticles, + std::map<const xAOD::IParticle*, particleVec_t>& cache) const; + + /** + * @brief Check if the dR between two particles is below threshold. + */ + bool matchParticles( + const xAOD::IParticle* lhs, + const xAOD::IParticle* rhs) const; + }; //> end class TriggerMatchingTool +} //> end namespace DerivationFramework + +#endif //> ! DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx new file mode 100644 index 0000000000000000000000000000000000000000..17463f8f4649a261871efa50116d44706ca5ee40 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx @@ -0,0 +1,3 @@ +#include "../TriggerMatchingTool.h" + +DECLARE_COMPONENT( DerivationFramework::TriggerMatchingTool )