2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
5#include "AsgMessaging/MessageCheck.h"
8namespace TrigCompositeUtils {
10 ANA_MSG_HEADER (msgFindLink)
13 * @brief Creates and right away records the Container CONT with the key.
15 * Returns the WriteHandle.
16 * If possible provide the context that comes via an argument to execute otherwise it will default to looking it up which is slower.
19 SG::WriteHandle<CONT> createAndStoreNoAux( const SG::WriteHandleKey<CONT>& key, const EventContext& ctx ) {
20 SG::WriteHandle<CONT> handle( key, ctx );
21 auto data = std::make_unique<CONT>() ;
22 if (handle.record( std::move( data ) ).isFailure()) {
23 throw std::runtime_error( "ERROR in TrigCompositeUtils::createAndStoreNoAux Unable to record " + key.key());
29 * @brief Creates and right away records the Container CONT with the key.
31 * Returns the WriteHandle.
32 * If possible provide the context that comes via an argument to execute otherwise it will default to looking it up which is slower.
34 template<class CONT, class AUX>
35 SG::WriteHandle<CONT> createAndStoreWithAux( const SG::WriteHandleKey<CONT>& key, const EventContext& ctx ) {
36 SG::WriteHandle<CONT> handle( key, ctx );
37 auto data = std::make_unique<CONT>() ;
38 auto aux = std::make_unique<AUX>() ;
39 data->setStore( aux.get() );
40 if (handle.record( std::move( data ), std::move( aux ) ).isFailure()) {
41 throw std::runtime_error( "ERROR in TrigCompositeUtils::createAndStoreWithAux Unable to record " + key.key());
48 findLinks(const EventContext& ctx, const Decision* start, const std::string& linkName, std::vector<LinkInfo<T>>& links, unsigned int behaviour, std::set<const Decision*>* fullyExploredFrom) {
49 using namespace msgFindLink;
50 std::vector<ElementLink<T>> featureLinks;
51 if (start->hasObjectCollectionLinks(linkName, ClassID_traits<T>::ID())) {
52 featureLinks = start->objectCollectionLinks<T>(linkName);
54 if (start->hasObjectLink(linkName, ClassID_traits<T>::ID())) {
55 featureLinks.push_back(start->objectLink<T>(linkName));
57 const bool linkFound = (featureLinks.size() > 0);
58 const std::vector<DecisionID> &ids = decisionIDs(start);
59 for (const ElementLink<T>& featureLink : featureLinks) {
60 // Check for duplicates
61 bool duplicate = false;
62 for (LinkInfo<T> &info : links)
64 if (info.link == featureLink) {
65 const auto A = decisionToElementLink(start, ctx);
66 const auto& B = info.link;
67 if (info.source != start
68 && linkName != initialRoIString() // Allow multiple nodes to reference the same initial ROI
69 && linkName != roiString() // Allow multiple nodes to reference the same updated ROI
70 && linkName != "l2cbroi" // Allow multiple nodes to reference the same l2cb ROI (muon specific use case)
71 && linkName != viewString() // Allow multiple nodes to reference the same view instance (i.e. re-use of EventViews)
72 && A.dataID().find("_probe") == std::string::npos // Allow re-use if one of the two locations is a tag-and-probe style probe component
73 && B.dataID().find("_probe") == std::string::npos
75 ANA_MSG_WARNING ("Found '" << linkName << "' by multiple paths, but "
76 << "the links are coming from different places in the navigation graph. " << std::endl
77 << "From A:" << A.dataID() << ":" << A.index() << " Dump:" << std::endl << *start << std::endl
78 << "From B:" << B.dataID() << ":" << B.index() << " Dump:" << std::endl << *(info.source));
80 if (behaviour & TrigDefs::fillDecisions) {
81 if (info.decisions.has_value())
82 info.decisions->insert(ids.begin(), ids.end());
84 info.decisions.emplace(ids.begin(), ids.end());
90 links.emplace_back(start, featureLink);
93 if (linkFound && (behaviour & TrigDefs::lastFeatureOfType)) {
96 // If not Early Exit, then recurse
97 for (const auto& seed : getLinkToPrevious(start)) {
98#if TRIGCOMPUTILS_ENABLE_EARLY_EXIT == 1
99 if (fullyExploredFrom != nullptr) {
100 // We only need to recursivly explore back from each node in the graph once.
101 // We can keep a record of nodes which we have already explored, these we can safely skip over.
102 if (fullyExploredFrom->count(*seed) == 1) {
107 findLinks<T>(ctx, *seed, linkName, links, behaviour, fullyExploredFrom);
109 // Fully explored this node
110 if (fullyExploredFrom != nullptr) {
111 fullyExploredFrom->insert(start);
116 std::vector<LinkInfo<T>>
117 findLinks(const EventContext& ctx, const Decision* start, const std::string& linkName, unsigned int behaviour) {
118 std::vector<LinkInfo<T>> links;
119 std::set<const Decision*> fullyExploredFrom;
120 findLinks(ctx, start, linkName, links, behaviour, &fullyExploredFrom);
126 findLink(const EventContext& ctx, const Decision* start, const std::string& linkName, const bool suppressMultipleLinksWarning) {
127 using namespace msgFindLink;
128 // We use findLink in cases where there is only one link to be found, or if there are multiple then we
129 // only want the most recent.
130 // Hence we can supply TrigDefs::lastFeatureOfType. /--> parent3(link)
131 // We can still have more then one link found if there is a branch in the navigation. E.g. start --> parent1 --> parent2(link)
132 // If both parent2 and parent3 posessed an admisable ElementLink, then the warning below will trigger, and only one of the
133 // links will be returned (whichever of parent2 or parent3 happened to be the first seed of parent1).
134 std::vector<LinkInfo<T>> links = findLinks<T>(ctx, start, linkName, TrigDefs::lastFeatureOfType);
135 if (links.size() > 1 && !suppressMultipleLinksWarning) {
136 ANA_MSG_WARNING (links.size() << " links found for " << linkName
137 << " returning the first link, consider using findLinks.");
139 if (links.size() > 0) {
142 return LinkInfo<T>(); // invalid link
147 template<class CONTAINER>
148 void filterLinkVectorByContainerKey(const std::string& containerSGKey, std::vector<ElementLink<CONTAINER>>& vector) {
149 if (containerSGKey.empty()) {
152 auto it = std::remove_if(vector.begin(), vector.end(), [&](const ElementLink<CONTAINER>& el) {
153 return !std::regex_match( el.dataID(), std::regex(containerSGKey) );
155 // Collection has been re-ordered to put the bad elements at the end
156 vector.erase(it, vector.end());
160 template<class CONTAINER>
161 const std::vector< LinkInfo<CONTAINER> > recursiveGetFeaturesOfType(
162 const NavGraph& navGraph,
163 const std::string& containerSGKey,
164 const unsigned int behaviour,
165 const std::string& navElementLinkKey,
166 const DecisionIDContainer& chainIDs) {
168 std::vector< LinkInfo<CONTAINER> > features; // The return vector
169 std::set<const NavGraphNode*> fullyExploredFrom; // Lets us navigate more efficiently
171 // For each starting point through the navigation for a given chain-group
172 for (const NavGraphNode* finalNode : navGraph.finalNodes()) {
173 recursiveGetFeaturesOfTypeInternal<CONTAINER>(features,
186 template<class CONTAINER>
187 void recursiveGetFeaturesOfTypeInternal(
188 std::vector< LinkInfo<CONTAINER> >& features,
189 std::set<const NavGraphNode*>& fullyExploredFrom,
190 const NavGraphNode* navGraphNode,
191 const std::string& containerSGKey,
192 const unsigned int behaviour,
193 const std::string& navElementLinkKey,
194 const DecisionIDContainer& chainIDs) {
196 const Decision* decisionObj = navGraphNode->node();
197 const Decision* decisionObjChild = (navGraphNode->children().size() == 1 ? navGraphNode->children().at(0)->node() : nullptr);
198 const std::vector<DecisionID> &ids = decisionIDs(decisionObj);
199 std::vector<ElementLink<CONTAINER>> featureLinks;
201 // Look up what named links are available in the Decision Object
202 std::vector<std::string> availableLinkNames;
203 if (navElementLinkKey == "") {
204 const std::vector<std::string> getSingleLinkNames = decisionObj->getObjectNames<CONTAINER>();
205 const std::vector<std::string> getCollectionLinkNames = decisionObj->getObjectCollectionNames<CONTAINER>();
206 std::copy(getSingleLinkNames.begin(), getSingleLinkNames.end(), std::back_inserter(availableLinkNames));
207 std::copy(getCollectionLinkNames.begin(), getCollectionLinkNames.end(), std::back_inserter(availableLinkNames));
208 } else { // Just looking for an explicitly named feature
209 availableLinkNames.push_back( navElementLinkKey );
212 // Fetch the named links that we're interested in
213 for (const std::string& featureNameToGet : availableLinkNames) {
214 // This try block protects against ExcCLIDMismatch throws from
215 // features which do not derive from IParticle, when an IParticle interface is requested.
216#ifndef XAOD_STANDALONE
219 // Slices may have added link collections. These links may have been to objects in different containers.
220 if (decisionObj->hasObjectCollectionLinks(featureNameToGet, ClassID_traits< CONTAINER >::ID())) {
221 std::vector<ElementLink<CONTAINER>> collectionLinks = decisionObj->objectCollectionLinks<CONTAINER>( featureNameToGet );
222 filterLinkVectorByContainerKey<CONTAINER>(containerSGKey, collectionLinks);
223 std::copy(collectionLinks.begin(), collectionLinks.end(), std::back_inserter(featureLinks));
225#ifndef XAOD_STANDALONE
226 } catch (SG::ExcCLIDMismatch&) {
227 // This is in place to catch the exception caused by non-IParticle features when an IParticle interface is requested.
228 // We're fine to catch this silently and cary on looking at the next Decision object in the graph
232 // Slices may have added single links. Note: the framework-specified "feature" link is always a single link.
233 if (decisionObj->hasObjectLink(featureNameToGet, ClassID_traits< CONTAINER >::ID())) {
234 std::vector<ElementLink<CONTAINER>> singleEntryVector; // Filtering function operates on a vector
235 singleEntryVector.push_back( decisionObj->objectLink<CONTAINER>( featureNameToGet ) );
236 filterLinkVectorByContainerKey<CONTAINER>(containerSGKey, singleEntryVector);
237 std::copy(singleEntryVector.begin(), singleEntryVector.end(), std::back_inserter(featureLinks));
239#ifndef XAOD_STANDALONE
240 } catch (SG::ExcCLIDMismatch&) {
241 // Silently. As above.
246 // Check if the Decsision object is active for a specific set of Chains-of-interest (as supplied by the TDT)
247 ActiveState state = ActiveState::UNSET;
248 if (chainIDs.size() > 0) {
249 // If we were given a list of chains to consider then we start assuming none passed this decisionObj
250 state = ActiveState::INACTIVE;
251 for (DecisionID id : chainIDs) {
252 if (std::count(decisionObj->decisions().begin(), decisionObj->decisions().end(), id) == 1) {
253 state = ActiveState::ACTIVE;
257 // But consider also the ComboHypo, this will have run immidiately after the Hypo and could have failed this chain due to combinatorics.
258 // This statement will only activate for the case that we are looking for "feature" edges (which is the default).
259 if (state == ActiveState::ACTIVE && decisionObjChild && decisionObjChild->name() == comboHypoAlgNodeName()) {
260 state = ActiveState::INACTIVE;
261 for (DecisionID id : chainIDs) {
262 if (std::count(decisionObjChild->decisions().begin(), decisionObjChild->decisions().end(), id) == 1) {
263 state = ActiveState::ACTIVE;
270 // Copy the fetched links into the return vector
271 for (const ElementLink<CONTAINER>& featureLink : featureLinks) {
272 typename std::vector<LinkInfo<CONTAINER>>::iterator vecIt = std::find_if(features.begin(), features.end(), [&](const auto& li) { return li.link == featureLink; } );
273 if (vecIt == features.end()) {
274 // Link did not already exist - add it to the output
275 features.emplace_back( decisionObj, featureLink, state);
277 // Link already existed - if the link's state in the return vector is INACTIVE but is ACTIVE here,
278 // then we need to change it to ACTIVE as this denotes one-or-more of the requested chains were active for the object.
279 if (vecIt->state == ActiveState::INACTIVE and state == ActiveState::ACTIVE) {
280 vecIt->state = ActiveState::ACTIVE;
282 if (behaviour & TrigDefs::fillDecisions) {
283 // Update the set of passed IDs
284 if (vecIt->decisions.has_value())
285 vecIt->decisions->insert(ids.begin(), ids.end());
287 vecIt->decisions.emplace(ids.begin(), ids.end());
292 // Stop processing this path through the navigation if the lastFeatureOfType flag is set
293 if (featureLinks.size() && (behaviour & TrigDefs::lastFeatureOfType)) {
297 // Recurse to decisionObj's seeds
298 for (const NavGraphNode* seedNavNode : navGraphNode->seeds()) {
299#if TRIGCOMPUTILS_ENABLE_EARLY_EXIT == 1
300 if (fullyExploredFrom.count(seedNavNode) == 1) {
301 continue; // Already explored down from here
304 recursiveGetFeaturesOfTypeInternal<CONTAINER>(features,
313 // If we encounter this node again in the future, we don't need to explore it again as it's now fully explored.
315 fullyExploredFrom.insert( navGraphNode );
321 DecisionIDContainer passedDecisionIDs( const Decision* d, const T& required ) {
322 DecisionIDContainer passed;
323 for ( DecisionID id : decisionIDs( d ) ) {
324 if ( required.find(id) != required.end() ) {