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