Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CKFPerformanceWriter.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file CKFPerformanceWriter.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2020 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
10 
19 
20 #include <algorithm>
21 #include <cstddef>
22 #include <map>
23 #include <memory>
24 #include <ostream>
25 #include <stdexcept>
26 #include <utility>
27 
28 #include <TFile.h>
29 #include <TVectorFfwd.h>
30 #include <TVectorT.h>
31 
32 namespace ActsExamples {
33 struct AlgorithmContext;
34 } // namespace ActsExamples
35 
38  : WriterT(cfg.inputTrajectories, "CKFPerformanceWriter", lvl),
39  m_cfg(std::move(cfg)),
40  m_effPlotTool(m_cfg.effPlotToolConfig, lvl),
41  m_fakeRatePlotTool(m_cfg.fakeRatePlotToolConfig, lvl),
42  m_duplicationPlotTool(m_cfg.duplicationPlotToolConfig, lvl),
43  m_trackSummaryPlotTool(m_cfg.trackSummaryPlotToolConfig, lvl) {
44  // trajectories collection name is already checked by base ctor
45  if (m_cfg.inputParticles.empty()) {
46  throw std::invalid_argument("Missing particles input collection");
47  }
48  if (m_cfg.inputMeasurementParticlesMap.empty()) {
49  throw std::invalid_argument("Missing hit-particles map input collection");
50  }
51  if (m_cfg.filePath.empty()) {
52  throw std::invalid_argument("Missing output filename");
53  }
54 
57 
58  // the output file can not be given externally since TFile accesses to the
59  // same file from multiple threads are unsafe.
60  // must always be opened internally
61  m_outputFile = TFile::Open(m_cfg.filePath.c_str(), m_cfg.fileMode.c_str());
62  if (m_outputFile == nullptr) {
63  throw std::invalid_argument("Could not open '" + m_cfg.filePath + "'");
64  }
65 
66  // initialize the plot tools
71 }
72 
74  m_effPlotTool.clear(m_effPlotCache);
75  m_fakeRatePlotTool.clear(m_fakeRatePlotCache);
76  m_duplicationPlotTool.clear(m_duplicationPlotCache);
77  m_trackSummaryPlotTool.clear(m_trackSummaryPlotCache);
78  if (m_outputFile != nullptr) {
79  m_outputFile->Close();
80  }
81 }
82 
84  float eff_tracks = float(m_nTotalMatchedTracks) / m_nTotalTracks;
85  float fakeRate_tracks = float(m_nTotalFakeTracks) / m_nTotalTracks;
86  float duplicationRate_tracks =
87  float(m_nTotalDuplicateTracks) / m_nTotalTracks;
88 
89  float eff_particle = float(m_nTotalMatchedParticles) / m_nTotalParticles;
90  float fakeRate_particle = float(m_nTotalFakeParticles) / m_nTotalParticles;
91  float duplicationRate_particle =
92  float(m_nTotalDuplicateParticles) / m_nTotalParticles;
93 
94  ACTS_DEBUG("nTotalTracks = " << m_nTotalTracks);
95  ACTS_DEBUG("nTotalMatchedTracks = " << m_nTotalMatchedTracks);
96  ACTS_DEBUG("nTotalDuplicateTracks = " << m_nTotalDuplicateTracks);
97  ACTS_DEBUG("nTotalFakeTracks = " << m_nTotalFakeTracks);
98 
99  ACTS_INFO(
100  "Efficiency with tracks (nMatchedTracks/ nAllTracks) = " << eff_tracks);
101  ACTS_INFO(
102  "Fake rate with tracks (nFakeTracks/nAllTracks) = " << fakeRate_tracks);
103  ACTS_INFO("Duplicate rate with tracks (nDuplicateTracks/nAllTracks) = "
104  << duplicationRate_tracks);
105  ACTS_INFO("Efficiency with particles (nMatchedParticles/nTrueParticles) = "
106  << eff_particle);
107  ACTS_INFO("Fake rate with particles (nFakeParticles/nTrueParticles) = "
108  << fakeRate_particle);
109  ACTS_INFO(
110  "Duplicate rate with particles (nDuplicateParticles/nTrueParticles) = "
111  << duplicationRate_particle);
112 
113  auto write_float = [&](float f, const char* name) {
114  TVectorF v(1);
115  v[0] = f;
116  m_outputFile->WriteObject(&v, name);
117  };
118 
119  if (m_outputFile != nullptr) {
120  m_outputFile->cd();
121  m_effPlotTool.write(m_effPlotCache);
122  m_fakeRatePlotTool.write(m_fakeRatePlotCache);
123  m_duplicationPlotTool.write(m_duplicationPlotCache);
124  m_trackSummaryPlotTool.write(m_trackSummaryPlotCache);
125  write_float(eff_tracks, "eff_tracks");
126  write_float(fakeRate_tracks, "fakerate_tracks");
127  write_float(duplicationRate_tracks, "duplicaterate_tracks");
128  write_float(eff_particle, "eff_particles");
129  write_float(fakeRate_particle, "fakerate_particles");
130  write_float(duplicationRate_particle, "duplicaterate_particles");
131  ACTS_INFO("Wrote performance plots to '" << m_outputFile->GetPath() << "'");
132  }
133  return ProcessCode::SUCCESS;
134 }
135 
137  const AlgorithmContext& ctx, const TrajectoriesContainer& trajectories) {
138  // The number of majority particle hits and fitted track parameters
139  using RecoTrackInfo = std::pair<size_t, Acts::BoundTrackParameters>;
141 
142  // Read truth input collections
143  const auto& particles = m_inputParticles(ctx);
144  const auto& hitParticlesMap = m_inputMeasurementParticlesMap(ctx);
145 
146  std::map<ActsFatras::Barcode, std::size_t> particleTruthHitCount;
147  for (const auto& [_, pid] : hitParticlesMap) {
148  particleTruthHitCount[pid]++;
149  }
150 
151  // Counter of truth-matched reco tracks
152  std::map<ActsFatras::Barcode, std::vector<RecoTrackInfo>> matched;
153  // Counter of truth-unmatched reco tracks
154  std::map<ActsFatras::Barcode, size_t> unmatched;
155  // For each particle within a track, how many hits did it contribute
156  std::vector<ParticleHitCount> particleHitCounts;
157 
158  // Exclusive access to the tree while writing
159  std::lock_guard<std::mutex> lock(m_writeMutex);
160 
161  // Vector of input features for neural network classification
162  std::vector<float> inputFeatures(3);
163 
164  // Loop over all trajectories
165  for (std::size_t iTraj = 0; iTraj < trajectories.size(); ++iTraj) {
166  const auto& traj = trajectories[iTraj];
167  const auto& mj = traj.multiTrajectory();
168  for (auto trackTip : traj.tips()) {
169  // @TODO: Switch to using this directly from the track
170  auto trajState =
172 
173  // Check if the reco track has fitted track parameters
174  if (not traj.hasTrackParameters(trackTip)) {
175  ACTS_WARNING(
176  "No fitted track parameters for trajectory with entry index = "
177  << trackTip);
178  continue;
179  }
180  const auto& fittedParameters = traj.trackParameters(trackTip);
181  // Fill the trajectory summary info
182  m_trackSummaryPlotTool.fill(m_trackSummaryPlotCache, fittedParameters,
183  trajState.nStates, trajState.nMeasurements,
184  trajState.nOutliers, trajState.nHoles,
185  trajState.nSharedHits);
186 
187  // Get the majority truth particle to this track
188  identifyContributingParticles(hitParticlesMap, traj, trackTip,
189  particleHitCounts);
190  if (particleHitCounts.empty()) {
191  ACTS_DEBUG(
192  "No truth particle associated with this trajectory with entry "
193  "index = "
194  << trackTip);
195  continue;
196  }
197  // Get the majority particleId and majority particle counts
198  // Note that the majority particle might be not in the truth seeds
199  // collection
200  ActsFatras::Barcode majorityParticleId =
201  particleHitCounts.front().particleId;
202  size_t nMajorityHits = particleHitCounts.front().hitCount;
203 
204  // Check if the trajectory is matched with truth.
205  // If not, it will be classified as 'fake'
206  const bool recoMatched =
207  static_cast<float>(nMajorityHits) / trajState.nMeasurements >=
208  m_cfg.truthMatchProbMin;
209  const bool truthMatched =
210  static_cast<float>(nMajorityHits) /
211  particleTruthHitCount.at(majorityParticleId) >=
212  m_cfg.truthMatchProbMin;
213 
214  bool isFake = false;
215  if (not m_cfg.doubleMatching and recoMatched) {
216  matched[majorityParticleId].push_back(
217  {nMajorityHits, fittedParameters});
218  } else if (m_cfg.doubleMatching and recoMatched and truthMatched) {
219  matched[majorityParticleId].push_back(
220  {nMajorityHits, fittedParameters});
221  } else {
222  isFake = true;
223  unmatched[majorityParticleId]++;
224  }
225 
226  // Fill fake rate plots
227  m_fakeRatePlotTool.fill(m_fakeRatePlotCache, fittedParameters, isFake);
228 
229  // Use neural network classification for duplication rate plots
230  // Currently, the network used for this example can only handle
231  // good/duplicate classification, so need to manually exclude fake tracks
232  if (m_cfg.duplicatedPredictor && !isFake) {
233  inputFeatures[0] = trajState.nMeasurements;
234  inputFeatures[1] = trajState.nOutliers;
235  inputFeatures[2] = trajState.chi2Sum * 1.0 / trajState.NDF;
236  // predict if current trajectory is 'duplicate'
237  bool isDuplicated = m_cfg.duplicatedPredictor(inputFeatures);
238  // Add to number of duplicated particles
239  if (isDuplicated) {
240  m_nTotalDuplicateTracks++;
241  }
242  // Fill the duplication rate
243  m_duplicationPlotTool.fill(m_duplicationPlotCache, fittedParameters,
244  isDuplicated);
245  }
246  // Counting number of total trajectories
247  m_nTotalTracks++;
248  }
249  }
250 
251  // Use truth-based classification for duplication rate plots
252  if (!m_cfg.duplicatedPredictor) {
253  // Loop over all truth-matched reco tracks for duplication rate plots
254  for (auto& [particleId, matchedTracks] : matched) {
255  // Sort the reco tracks matched to this particle by the number of majority
256  // hits
257  std::sort(matchedTracks.begin(), matchedTracks.end(),
258  [](const RecoTrackInfo& lhs, const RecoTrackInfo& rhs) {
259  return lhs.first > rhs.first;
260  });
261  for (size_t itrack = 0; itrack < matchedTracks.size(); itrack++) {
262  const auto& [nMajorityHits, fittedParameters] =
263  matchedTracks.at(itrack);
264  // The tracks with maximum number of majority hits is taken as the
265  // 'real' track; others are as 'duplicated'
266  bool isDuplicated = (itrack != 0);
267  // the track is associated to the same particle
268  if (isDuplicated) {
269  m_nTotalDuplicateTracks++;
270  }
271  // Fill the duplication rate
272  m_duplicationPlotTool.fill(m_duplicationPlotCache, fittedParameters,
273  isDuplicated);
274  }
275  }
276  }
277 
278  // Loop over all truth particle seeds for efficiency plots and reco details.
279  // These are filled w.r.t. truth particle seed info
280  for (const auto& particle : particles) {
281  auto particleId = particle.particleId();
282  // Investigate the truth-matched tracks
283  size_t nMatchedTracks = 0;
284  bool isReconstructed = false;
285  auto imatched = matched.find(particleId);
286  if (imatched != matched.end()) {
287  nMatchedTracks = imatched->second.size();
288  // Add number for total matched tracks here
289  m_nTotalMatchedTracks += nMatchedTracks;
290  m_nTotalMatchedParticles += 1;
291  // Check if the particle has more than one matched track for the duplicate
292  // rate
293  if (nMatchedTracks > 1) {
294  m_nTotalDuplicateParticles += 1;
295  }
296  isReconstructed = true;
297  }
298  // Fill efficiency plots
299  m_effPlotTool.fill(m_effPlotCache, particle, isReconstructed);
300  // Fill number of duplicated tracks for this particle
301  m_duplicationPlotTool.fill(m_duplicationPlotCache, particle,
302  nMatchedTracks - 1);
303 
304  // Investigate the fake (i.e. truth-unmatched) tracks
305  size_t nFakeTracks = 0;
306  auto ifake = unmatched.find(particleId);
307  if (ifake != unmatched.end()) {
308  nFakeTracks = ifake->second;
309  m_nTotalFakeTracks += nFakeTracks;
310  // unmatched is a map of majority particle id to # of tracks associated
311  // with that particle
312  m_nTotalFakeParticles += 1;
313  }
314  // Fill number of reconstructed/truth-matched/fake tracks for this particle
315  m_fakeRatePlotTool.fill(m_fakeRatePlotCache, particle, nMatchedTracks,
316  nFakeTracks);
317  m_nTotalParticles += 1;
318  } // end all truth particles
319 
320  return ProcessCode::SUCCESS;
321 }