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