CMS 3D CMS Logo

FWFileEntry.cc
Go to the documentation of this file.
1 #include <boost/regex.hpp>
2 
3 #include "TFile.h"
4 #include "TEveTreeTools.h"
5 #include "TError.h"
6 #include "TMath.h"
7 #include "TEnv.h"
8 
14 
17 
23 
25 
27 
28 #include <boost/bind.hpp>
29 
30 FWFileEntry::FWFileEntry(const std::string& name, bool checkVersion)
31  : m_name(name),
32  m_file(nullptr),
33  m_eventTree(nullptr),
34  m_event(nullptr),
35  m_needUpdate(true),
36  m_globalEventList(nullptr) {
37  openFile(checkVersion);
38 }
39 
41  for (std::list<Filter*>::iterator i = m_filterEntries.begin(); i != m_filterEntries.end(); ++i)
42  delete (*i)->m_eventList;
43 
44  delete m_globalEventList;
45 }
46 
47 void FWFileEntry::openFile(bool checkVersion) {
48  gErrorIgnoreLevel = 3000; // suppress warnings about missing dictionaries
49 
50  TFile* newFile = TFile::Open(m_name.c_str());
51 
52  if (newFile == nullptr || newFile->IsZombie() || !newFile->Get("Events")) {
53  // std::cout << "Invalid file. Ignored." << std::endl;
54  // return false;
55  throw std::runtime_error("Invalid file. Ignored.");
56  }
57 
58  m_file = newFile;
59 
60  gErrorIgnoreLevel = -1;
61 
62  // check CMSSW relese version for compatibility
63  if (checkVersion) {
64  typedef std::vector<edm::ProcessHistory> provList;
65 
66  TTree* metaData = dynamic_cast<TTree*>(m_file->Get("MetaData"));
67  TBranch* b = metaData->GetBranch("ProcessHistory");
68  provList* x = nullptr;
69  b->SetAddress(&x);
70  b->GetEntry(0);
71 
72  const edm::ProcessConfiguration* dd = nullptr;
73  int latestVersion = 0;
74  int currentVersionArr[] = {0, 0, 0};
75  for (auto const& processHistory : *x) {
76  for (auto const& processConfiguration : processHistory) {
77  // std::cout << processConfiguration.releaseVersion() << " " << processConfiguration.processName() << std::endl;
78  TString dcv = processConfiguration.releaseVersion();
79  fireworks::getDecomposedVersion(dcv, currentVersionArr);
80  int nvv = currentVersionArr[0] * 100 + currentVersionArr[1] * 10 + currentVersionArr[2];
81  if (nvv > latestVersion) {
82  latestVersion = nvv;
83  dd = &processConfiguration;
84  }
85  }
86  }
87 
88  if (latestVersion) {
89  fwLog(fwlog::kInfo) << "Checking process history. " << m_name.c_str() << " latest process \"" << dd->processName()
90  << "\", version " << dd->releaseVersion() << std::endl;
91 
92  b->SetAddress(nullptr);
93  TString v = dd->releaseVersion();
96  TString msg = Form(
97  "incompatible data: Process version does not mactch major data formats version. File produced with %s. "
98  "Data formats version \"CMSSW_%d_%d_%d\".\n",
99  dd->releaseVersion().c_str(),
100  di[0],
101  di[1],
102  di[2]);
103  msg += "Use --no-version-check option if you still want to view the file.\n";
104  throw std::runtime_error(msg.Data());
105  }
106  } else {
107  TString msg = "No process history available\n";
108  msg += "Use --no-version-check option if you still want to view the file.\n";
109  throw std::runtime_error(msg.Data());
110  }
111  }
112 
113  m_eventTree = dynamic_cast<TTree*>(m_file->Get("Events"));
114 
115  if (m_eventTree == nullptr) {
116  throw std::runtime_error("Cannot find TTree 'Events' in the data file");
117  }
118 
119  // Initialize caching, this helps also in the case of local file.
121  printf("FWFileEntry::openFile enabling FWTTreeCache for file class '%s'.", m_file->ClassName());
122 
123  auto tc = new FWTTreeCache(m_eventTree, FWTTreeCache::GetDefaultCacheSize());
124  m_file->SetCacheRead(tc, m_eventTree);
125  tc->SetEnablePrefetching(FWTTreeCache::IsPrefetching());
126  tc->SetLearnEntries(20);
127  tc->SetLearnPrefill(TTreeCache::kAllBranches);
128  tc->StartLearningPhase();
129 
130  // load event, set DataGetterHelper callback for branch access
131  m_event = new fwlite::Event(m_file, false, [tc](TBranch const& b) { tc->BranchAccessCallIn(&b); });
132 
133  // Connect to collection add/remove signals
135  eiMng->newItem_.connect(boost::bind(&FWFileEntry::NewEventItemCallIn, this, _1));
136  eiMng->removingItem_.connect(boost::bind(&FWFileEntry::RemovingEventItemCallIn, this, _1));
137  // no need to connect to goingToClearItems_ ... individual removes are emitted.
138 
139  if (m_event->size() == 0)
140  throw std::runtime_error("fwlite::Event size == 0");
141 }
142 
144  if (m_file) {
145  printf("Reading %lld bytes in %d transactions.\n", m_file->GetBytesRead(), m_file->GetReadCalls());
146  delete m_file->GetCacheRead(m_eventTree);
147 
148  m_file->Close();
149  delete m_file;
150  }
151  if (m_event)
152  delete m_event;
153 }
154 
155 //______________________________________________________________________________
156 
157 bool FWFileEntry::isEventSelected(int tree_entry) {
158  int idx = m_globalEventList->GetIndex(tree_entry);
159  return idx >= 0;
160 }
161 
162 bool FWFileEntry::hasSelectedEvents() { return m_globalEventList->GetN() > 0; }
163 
165  if (m_globalEventList->GetN() > 0) {
166  return m_globalEventList->GetEntry(0);
167  } else {
168  return -1;
169  }
170 }
171 
173  if (m_globalEventList->GetN() > 0)
174  return m_globalEventList->GetEntry(m_globalEventList->GetN() - 1);
175  else
176  return -1;
177 }
178 
179 int FWFileEntry::nextSelectedEvent(int tree_entry) {
180  // Find next selected event after the current one.
181  // This returns the index in the selected event list.
182  // If none exists -1 is returned.
183 
184  const Long64_t* list = m_globalEventList->GetList();
185  Long64_t val = tree_entry;
186  Long64_t idx = TMath::BinarySearch(m_globalEventList->GetN(), list, val);
187  ++idx;
188  if (idx >= m_globalEventList->GetN() || idx < 0)
189  return -1;
190  return list[idx];
191 }
192 
194  // Find first selected event before current one.
195  // This returns the index in the selected event list.
196  // If none exists -1 is returned.
197 
198  const Long64_t* list = m_globalEventList->GetList();
199  Long64_t val = tree_entry;
200  Long64_t idx = TMath::BinarySearch(m_globalEventList->GetN(), list, val);
201  if (list[idx] == val)
202  --idx;
203  if (idx >= 0)
204  return list[idx];
205  else
206  return -1;
207 }
208 
209 //______________________________________________________________________________
211  for (std::list<Filter*>::iterator it = m_filterEntries.begin(); it != m_filterEntries.end(); ++it) {
212  if ((*it)->m_selector->m_enabled)
213  return true;
214  }
215 
216  return false;
217 }
218 
219 //______________________________________________________________________________
220 void FWFileEntry::updateFilters(const FWEventItemsManager* eiMng, bool globalOR) {
221  if (!m_needUpdate)
222  return;
223 
224  if (m_globalEventList)
225  m_globalEventList->Reset();
226  else
228 
229  for (std::list<Filter*>::iterator it = m_filterEntries.begin(); it != m_filterEntries.end(); ++it) {
230  if ((*it)->m_selector->m_enabled && (*it)->m_needsUpdate) {
231  runFilter(*it, eiMng);
232  }
233  // Need to re-check if enabled after filtering as it can be set to false
234  // in runFilter().
235  if ((*it)->m_selector->m_enabled) {
236  if ((*it)->hasSelectedEvents()) {
237  if (globalOR || m_globalEventList->GetN() == 0) {
238  m_globalEventList->Add((*it)->m_eventList);
239  } else {
240  m_globalEventList->Intersect((*it)->m_eventList);
241  }
242  } else if (!globalOR) {
243  m_globalEventList->Reset();
244  break;
245  }
246  }
247  }
248 
249  fwLog(fwlog::kDebug) << "FWFileEntry::updateFilters in [" << m_file->GetName() << "] global selection ["
250  << m_globalEventList->GetN() << "/" << m_eventTree->GetEntries() << "]" << std::endl;
251 
252  m_needUpdate = false;
253 }
254 
255 //_____________________________________________________________________________
257  if (!filter->m_selector->m_triggerProcess.empty()) {
259  return;
260  }
261 
262  // parse selection for known Fireworks expressions
263  std::string interpretedSelection = filter->m_selector->m_expression;
264  // list of branch names to be added to tree-cache
265  std::vector<std::string> branch_names;
266 
267  for (FWEventItemsManager::const_iterator i = eiMng->begin(), end = eiMng->end(); i != end; ++i) {
268  FWEventItem* item = *i;
269  if (item == nullptr)
270  continue;
271  // FIXME: hack to get full branch name filled
272  if (!item->hasEvent()) {
273  item->setEvent(m_event);
274  item->getPrimaryData();
275  item->setEvent(nullptr);
276  }
277 
278  boost::regex re(std::string("\\$") + (*i)->name());
279 
280  if (boost::regex_search(interpretedSelection, re)) {
281  const edm::TypeWithDict elementType(const_cast<TClass*>(item->type()));
282  const edm::TypeWithDict wrapperType = edm::TypeWithDict::byName(edm::wrappedClassName(elementType.name()));
283  std::string fullBranchName = m_event->getBranchNameFor(wrapperType.typeInfo(),
284  item->moduleLabel().c_str(),
285  item->productInstanceLabel().c_str(),
286  item->processName().c_str());
287 
288  interpretedSelection = boost::regex_replace(interpretedSelection, re, fullBranchName + ".obj");
289 
290  branch_names.push_back(fullBranchName);
291 
292  // printf("selection after applying s/%s/%s/: %s\n",
293  // (std::string("\\$") + (*i)->name()).c_str(),
294  // ((*i)->m_fullBranchName + ".obj").c_str(),
295  // interpretedSelection.c_str());
296  }
297  }
298 
299  std::size_t found = interpretedSelection.find('$');
300  if (found != std::string::npos) {
301  fwLog(fwlog::kError) << "FWFileEntry::RunFilter invalid expression " << interpretedSelection << std::endl;
302  filter->m_needsUpdate = false;
303  return;
304  }
305 
306  m_file->cd();
307  m_eventTree->SetEventList(nullptr);
308 
309  auto prevCache = m_file->GetCacheRead(m_eventTree);
310 
311  auto interCache = new TTreeCache(m_eventTree, 10 * 1024 * 1024);
312  // Do not disconnect the cache, it will be reattached after filtering.
313  m_file->SetCacheRead(interCache, m_eventTree, TFile::kDoNotDisconnect);
314  interCache->SetEnablePrefetching(FWTTreeCache::IsPrefetching());
315  for (auto& b : branch_names)
316  interCache->AddBranch(b.c_str(), true);
317  interCache->StopLearningPhase();
318 
319  // Since ROOT will leave any TBranches used in the filtering at the last event,
320  // we need to be able to reset them to what fwlite::Event expects them to be.
321  // We do this by holding onto the old buffers and create temporary new ones.
322 
323  std::map<TBranch*, void*> prevAddrs;
324 
325  {
326  TObjArray* branches = m_eventTree->GetListOfBranches();
327  std::unique_ptr<TIterator> pIt(branches->MakeIterator());
328  while (TObject* branchObj = pIt->Next()) {
329  TBranch* b = dynamic_cast<TBranch*>(branchObj);
330  if (nullptr != b) {
331  const char* name = b->GetName();
332  unsigned int length = strlen(name);
333  if (length > 1 && name[length - 1] != '.') {
334  // This is not a data branch so we should ignore it.
335  continue;
336  }
337  if (nullptr != b->GetAddress()) {
338  if (prevAddrs.find(b) != prevAddrs.end()) {
339  fwLog(fwlog::kWarning) << "FWFileEntry::runFilter branch is already in the map!\n";
340  }
341  prevAddrs.insert(std::make_pair(b, b->GetAddress()));
342 
343  // std::cout <<"Zeroing branch: "<< b->GetName() <<" "<< (void*) b->GetAddress() <<std::endl;
344  b->SetAddress(nullptr);
345  }
346  }
347  }
348  }
349 
350  if (filter->m_eventList)
351  filter->m_eventList->Reset();
352  else
353  filter->m_eventList = new FWTEventList;
354 
355  fwLog(fwlog::kInfo) << "FWFileEntry::runFilter Running filter " << interpretedSelection << "' "
356  << "for file '" << m_file->GetName() << "'.\n";
357 
358  TEveSelectorToEventList stoelist(filter->m_eventList, interpretedSelection.c_str());
359  Long64_t result = m_eventTree->Process(&stoelist);
360 
361  if (result < 0)
362  fwLog(fwlog::kWarning) << "FWFileEntry::runFilter in file [" << m_file->GetName() << "] filter ["
363  << filter->m_selector->m_expression << "] is invalid." << std::endl;
364  else
365  fwLog(fwlog::kDebug) << "FWFileEntry::runFilter is file [" << m_file->GetName() << "], filter ["
366  << filter->m_selector->m_expression << "] has [" << filter->m_eventList->GetN()
367  << "] events selected" << std::endl;
368 
369  // Set back the old branch buffers.
370  {
371  for (auto i : prevAddrs) {
372  // std::cout <<"Resetting branch: "<< i.first->GetName() <<" "<< i.second <<std::endl;
373  i.first->SetAddress(i.second);
374  }
375  }
376 
377  m_file->SetCacheRead(prevCache, m_eventTree);
378  delete interCache;
379 
380  filter->m_needsUpdate = false;
381 }
382 
383 //______________________________________________________________________________
384 
387 
388  boost::regex re_spaces("\\s+");
389  selection = boost::regex_replace(selection, re_spaces, "");
390  if (selection.find("&&") != std::string::npos && selection.find("||") != std::string::npos) {
391  // Combination of && and || operators not supported.
392  return false;
393  }
394 
395  fwlite::Handle<edm::TriggerResults> hTriggerResults;
396  edm::TriggerNames const* triggerNames(nullptr);
397  try {
398  hTriggerResults.getByLabel(*m_event, "TriggerResults", "", filterEntry->m_selector->m_triggerProcess.c_str());
399  triggerNames = &(m_event->triggerNames(*hTriggerResults));
400  } catch (...) {
401  fwLog(fwlog::kWarning) << " failed to get trigger results with process name "
402  << filterEntry->m_selector->m_triggerProcess << std::endl;
403  return false;
404  }
405 
406  // std::cout << "Number of trigger names: " << triggerNames->size() << std::endl;
407  // for (unsigned int i=0; i<triggerNames->size(); ++i)
408  // std::cout << " " << triggerNames->triggerName(i);
409  //std::cout << std::endl;
410 
411  bool junction_mode = true; // AND
412  if (selection.find("||") != std::string::npos)
413  junction_mode = false; // OR
414 
415  boost::regex re("\\&\\&|\\|\\|");
416 
417  boost::sregex_token_iterator i(selection.begin(), selection.end(), re, -1);
418  boost::sregex_token_iterator j;
419 
420  // filters and how they enter in the logical expression
421  std::vector<std::pair<unsigned int, bool> > filters;
422 
423  while (i != j) {
424  std::string filter = *i++;
425  bool flag = true;
426  if (filter[0] == '!') {
427  flag = false;
428  filter.erase(filter.begin());
429  }
430  unsigned int index = triggerNames->triggerIndex(filter);
431  if (index == triggerNames->size()) {
432  // Trigger name not found.
433  return false;
434  }
435  filters.push_back(std::make_pair(index, flag));
436  }
437  if (filters.empty())
438  return false;
439 
440  if (filterEntry->m_eventList)
441  filterEntry->m_eventList->Reset();
442  else
443  filterEntry->m_eventList = new FWTEventList();
444  FWTEventList* list = filterEntry->m_eventList;
445 
446  // loop over events
447  edm::EventID currentEvent = m_event->id();
448  unsigned int iEvent = 0;
449 
450  for (m_event->toBegin(); !m_event->atEnd(); ++(*m_event)) {
451  hTriggerResults.getByLabel(*m_event, "TriggerResults", "", filterEntry->m_selector->m_triggerProcess.c_str());
452  std::vector<std::pair<unsigned int, bool> >::const_iterator filter = filters.begin();
453  bool passed = hTriggerResults->accept(filter->first) == filter->second;
454  while (++filter != filters.end()) {
455  if (junction_mode)
456  passed &= hTriggerResults->accept(filter->first) == filter->second;
457  else
458  passed |= hTriggerResults->accept(filter->first) == filter->second;
459  }
460  if (passed)
461  list->Enter(iEvent);
462  ++iEvent;
463  }
464  m_event->to(currentEvent);
465 
466  filterEntry->m_needsUpdate = false;
467 
468  fwLog(fwlog::kDebug) << "FWFile::filterEventsWithCustomParser file [" << m_file->GetName() << "], filter ["
469  << filterEntry->m_selector->m_expression << "], selected [" << list->GetN() << "]" << std::endl;
470 
471  return true;
472 }
473 
474 //------------------------------------------------------------------------------
475 
477  FWTTreeCache* tc = dynamic_cast<FWTTreeCache*>(m_file->GetCacheRead(m_eventTree));
478  assert(tc != nullptr && "FWFileEntry::treeCache can not access TTreeCache");
479  return tc;
480 }
481 
483  const edm::TypeWithDict elementType(const_cast<TClass*>(it->type()));
484  const edm::TypeWithDict wrapperType = edm::TypeWithDict::byName(edm::wrappedClassName(elementType.name()));
485  return m_event->getBranchNameFor(
486  wrapperType.typeInfo(), it->moduleLabel().c_str(), it->productInstanceLabel().c_str(), it->processName().c_str());
487 }
488 
490  auto tc = fwTreeCache();
491 
493  printf("FWFileEntry:NewEventItemCallIn FWEventItem %s, learning=%d\n", getBranchName(it).c_str(), tc->IsLearning());
494 
495  tc->AddBranchTopLevel(getBranchName(it).c_str());
496 }
497 
499  auto tc = fwTreeCache();
500 
502  printf("FWFileEntry:RemovingEventItemCallIn FWEventItem %s, learning=%d\n",
503  getBranchName(it).c_str(),
504  tc->IsLearning());
505 
506  tc->DropBranchTopLevel(getBranchName(it).c_str());
507 }
fireworks::Context * getContext()
Definition: FWGUIManager.h:152
void closeFile()
Definition: FWFileEntry.cc:143
FWEventSelector * m_selector
Definition: FWFileEntry.h:40
Event const & toBegin() override
Go to the very first Event.
Definition: Event.cc:217
void openFile(bool)
Definition: FWFileEntry.cc:47
int lastSelectedEvent()
Definition: FWFileEntry.cc:172
const FWEventItemsManager * eventItemsManager() const
Definition: Context.h:56
void setEvent(const edm::EventBase *iEvent)
Definition: FWEventItem.cc:111
void RemovingEventItemCallIn(const FWEventItem *it)
Definition: FWFileEntry.cc:498
bool hasSelectedEvents()
Definition: FWFileEntry.cc:162
bool accept() const
Has at least one path accepted the event?
#define nullptr
selection
main part
Definition: corrVsCorr.py:100
std::string getBranchName(const FWEventItem *it) const
Definition: FWFileEntry.cc:482
FWTEventList * m_eventList
Definition: FWFileEntry.h:39
void getPrimaryData() const
Definition: FWEventItem.cc:396
std::string m_triggerProcess
FWFileEntry(const std::string &name, bool checkVersion)
Definition: FWFileEntry.cc:30
void getDecomposedVersion(const TString &s, int *out)
Definition: fwPaths.cc:26
Strings::size_type size() const
Definition: TriggerNames.cc:31
const std::string & processName() const
Definition: FWEventItem.cc:444
std::string const getBranchNameFor(std::type_info const &, char const *iModuleLabel, char const *iProductInstanceLabel, char const *iProcessName) const override
Return the branch name in the TFile which contains the data.
Definition: Event.cc:268
Long64_t size() const
Returns number of events in the file.
Definition: Event.cc:245
bool isEventSelected(int event)
Definition: FWFileEntry.cc:157
static TypeWithDict byName(std::string const &name)
Definition: TypeWithDict.cc:74
std::string const & processName() const
void getByLabel(const P &iP, const char *iModuleLabel, const char *iProductInstanceLabel=0, const char *iProcessLabel=0)
Definition: Handle.h:100
bool atEnd() const override
Definition: Event.cc:254
int iEvent
Definition: GenABIO.cc:224
const std::string & productInstanceLabel() const
Definition: FWEventItem.cc:442
std::list< Filter * > m_filterEntries
Definition: FWFileEntry.h:100
int * supportedDataFormatsVersion()
Definition: fwPaths.cc:35
unsigned int triggerIndex(std::string const &name) const
Definition: TriggerNames.cc:24
std::string name() const
static bool IsLogging()
Definition: FWTTreeCache.cc:23
std::string m_expression
const TClass * type() const
Definition: FWEventItem.cc:437
static bool IsPrefetching()
Definition: FWTTreeCache.cc:27
sigc::signal< void, FWEventItem * > newItem_
bool filterEventsWithCustomParser(Filter *filter)
Definition: FWFileEntry.cc:385
bool to(Long64_t iIndex)
Go to the event at index iIndex.
Definition: Event.cc:195
edm::TriggerNames const & triggerNames(edm::TriggerResults const &triggerResults) const override
Definition: Event.cc:381
std::type_info const & typeInfo() const
#define end
Definition: vmac.h:39
std::list< Filter * > & filters()
Definition: FWFileEntry.h:58
static int GetDefaultCacheSize()
Definition: FWTTreeCache.cc:30
TFile * m_file
Definition: FWFileEntry.h:94
virtual ~FWFileEntry()
Definition: FWFileEntry.cc:40
bool m_needUpdate
Definition: FWFileEntry.h:98
TTree * m_eventTree
Definition: FWFileEntry.h:95
fwlite::Event * m_event
Definition: FWFileEntry.h:96
const_iterator begin() const
NOTE: iterator is allowed to return a null object for items that have been removed.
int firstSelectedEvent()
Definition: FWFileEntry.cc:164
static FWGUIManager * getGUIManager()
#define fwLog(_level_)
Definition: fwLog.h:45
ReleaseVersion const & releaseVersion() const
std::string wrappedClassName(std::string const &iFullName)
bool hasEvent() const
Definition: FWEventItem.h:128
void runFilter(Filter *fe, const FWEventItemsManager *eiMng)
Definition: FWFileEntry.cc:256
double b
Definition: hdecay.h:118
tuple msg
Definition: mps_check.py:285
gErrorIgnoreLevel
Definition: utils.py:28
int nextSelectedEvent(int event)
Definition: FWFileEntry.cc:179
Definition: Filter.py:1
bool acceptDataFormatsVersion(TString &n)
Definition: fwPaths.cc:60
edm::EventID id() const
Definition: EventBase.h:59
void updateFilters(const FWEventItemsManager *eiMng, bool isOR)
Definition: FWFileEntry.cc:220
FWTTreeCache * fwTreeCache()
Definition: FWFileEntry.cc:476
void Enter(Long64_t entry) override
Definition: FWTEventList.cc:57
FWTEventList * m_globalEventList
Definition: FWFileEntry.h:101
bool hasActiveFilters()
Definition: FWFileEntry.cc:210
std::string m_name
Definition: FWFileEntry.h:93
int previousSelectedEvent(int event)
Definition: FWFileEntry.cc:193
void Add(const TEventList *list) override
Definition: FWTEventList.cc:7
std::vector< FWEventItem * >::const_iterator const_iterator
void NewEventItemCallIn(const FWEventItem *it)
Definition: FWFileEntry.cc:489
const std::string & moduleLabel() const
Definition: FWEventItem.cc:441
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run
const_iterator end() const