Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
VertexPerformanceWriter.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file VertexPerformanceWriter.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2019-2023 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 
29 
30 #include <algorithm>
31 #include <cmath>
32 #include <cstddef>
33 #include <ios>
34 #include <limits>
35 #include <map>
36 #include <memory>
37 #include <ostream>
38 #include <set>
39 #include <stdexcept>
40 #include <utility>
41 
42 #include <TFile.h>
43 #include <TTree.h>
44 
45 namespace ActsExamples {
46 struct AlgorithmContext;
47 } // namespace ActsExamples
48 
53 
57  : WriterT(config.inputVertices, "VertexPerformanceWriter", level),
58  m_cfg(config) {
59  if (m_cfg.filePath.empty()) {
60  throw std::invalid_argument("Missing output filename");
61  }
62  if (m_cfg.treeName.empty()) {
63  throw std::invalid_argument("Missing tree name");
64  }
65  if (m_cfg.inputAllTruthParticles.empty()) {
66  throw std::invalid_argument("Collection with all truth particles missing");
67  }
68  if (m_cfg.inputSelectedTruthParticles.empty()) {
69  throw std::invalid_argument(
70  "Collection with selected truth particles missing");
71  }
72 
75 
76  if (m_cfg.useTracks) {
77  if (!m_cfg.inputAssociatedTruthParticles.empty()) {
80  if (!m_cfg.inputTrackParameters.empty()) {
82  } else {
84  }
85  } else {
89  }
90  }
91 
92  // Setup ROOT I/O
93  auto path = m_cfg.filePath;
94  m_outputFile = TFile::Open(path.c_str(), m_cfg.fileMode.c_str());
95  if (m_outputFile == nullptr) {
96  throw std::ios_base::failure("Could not open '" + path);
97  }
98  m_outputFile->cd();
99  m_outputTree = new TTree(m_cfg.treeName.c_str(), m_cfg.treeName.c_str());
100  if (m_outputTree == nullptr) {
101  throw std::bad_alloc();
102  } else {
103  // I/O parameters.
104  // Branches related to the 4D vertex position
105  m_outputTree->Branch("truthX", &m_truthX);
106  m_outputTree->Branch("truthY", &m_truthY);
107  m_outputTree->Branch("truthZ", &m_truthZ);
108  m_outputTree->Branch("truthT", &m_truthT);
109 
110  m_outputTree->Branch("recoX", &m_recoX);
111  m_outputTree->Branch("recoY", &m_recoY);
112  m_outputTree->Branch("recoZ", &m_recoZ);
113  m_outputTree->Branch("recoT", &m_recoT);
114 
115  m_outputTree->Branch("resX", &m_resX);
116  m_outputTree->Branch("resY", &m_resY);
117  m_outputTree->Branch("resZ", &m_resZ);
118  m_outputTree->Branch("resT", &m_resT);
119 
120  m_outputTree->Branch("pullX", &m_pullX);
121  m_outputTree->Branch("pullY", &m_pullY);
122  m_outputTree->Branch("pullZ", &m_pullZ);
123  m_outputTree->Branch("pullT", &m_pullT);
124 
125  m_outputTree->Branch("covXX", &m_covXX);
126  m_outputTree->Branch("covYY", &m_covYY);
127  m_outputTree->Branch("covZZ", &m_covZZ);
128  m_outputTree->Branch("covTT", &m_covTT);
129  m_outputTree->Branch("covXY", &m_covXY);
130  m_outputTree->Branch("covXZ", &m_covXZ);
131  m_outputTree->Branch("covXT", &m_covXT);
132  m_outputTree->Branch("covYZ", &m_covYZ);
133  m_outputTree->Branch("covYT", &m_covYT);
134  m_outputTree->Branch("covZT", &m_covZT);
135 
136  // Branches related to track momenta at vertex
137  m_outputTree->Branch("trk_truthPhi", &m_truthPhi);
138  m_outputTree->Branch("trk_truthTheta", &m_truthTheta);
139  m_outputTree->Branch("trk_truthQOverP", &m_truthQOverP);
140 
141  m_outputTree->Branch("trk_recoPhi", &m_recoPhi);
142  m_outputTree->Branch("trk_recoPhiFitted", &m_recoPhiFitted);
143  m_outputTree->Branch("trk_recoTheta", &m_recoTheta);
144  m_outputTree->Branch("trk_recoThetaFitted", &m_recoThetaFitted);
145  m_outputTree->Branch("trk_recoQOverP", &m_recoQOverP);
146  m_outputTree->Branch("trk_recoQOverPFitted", &m_recoQOverPFitted);
147 
148  m_outputTree->Branch("trk_resPhi", &m_resPhi);
149  m_outputTree->Branch("trk_resPhiFitted", &m_resPhiFitted);
150  m_outputTree->Branch("trk_resTheta", &m_resTheta);
151  m_outputTree->Branch("trk_resThetaFitted", &m_resThetaFitted);
152  m_outputTree->Branch("trk_resQOverP", &m_resQOverP);
153  m_outputTree->Branch("trk_resQOverPFitted", &m_resQOverPFitted);
154  m_outputTree->Branch("trk_momOverlap", &m_momOverlap);
155  m_outputTree->Branch("trk_momOverlapFitted", &m_momOverlapFitted);
156 
157  m_outputTree->Branch("trk_pullPhi", &m_pullPhi);
158  m_outputTree->Branch("trk_pullPhiFitted", &m_pullPhiFitted);
159  m_outputTree->Branch("trk_pullTheta", &m_pullTheta);
160  m_outputTree->Branch("trk_pullThetaFitted", &m_pullThetaFitted);
161  m_outputTree->Branch("trk_pullQOverP", &m_pullQOverP);
162  m_outputTree->Branch("trk_pullQOverPFitted", &m_pullQOverPFitted);
163 
164  m_outputTree->Branch("nTracksTruthVtx", &m_nTracksOnTruthVertex);
165  m_outputTree->Branch("nTracksRecoVtx", &m_nTracksOnRecoVertex);
166 
167  m_outputTree->Branch("trkVtxMatch", &m_trackVtxMatchFraction);
168 
169  m_outputTree->Branch("nRecoVtx", &m_nRecoVtx);
170  m_outputTree->Branch("nTrueVtx", &m_nTrueVtx);
171  m_outputTree->Branch("nVtxDetectorAcceptance", &m_nVtxDetAcceptance);
172  m_outputTree->Branch("nVtxReconstructable", &m_nVtxReconstructable);
173  }
174 }
175 
177  if (m_outputFile != nullptr) {
178  m_outputFile->Close();
179  }
180 }
181 
183  m_outputFile->cd();
184  m_outputTree->Write();
185  m_outputFile->Close();
186 
187  return ProcessCode::SUCCESS;
188 }
189 
191  const SimParticleContainer& collection) const {
192  // map for finding frequency
193  std::map<int, int> fmap;
194 
195  std::vector<int> reconstructableTruthVertices;
196 
197  // traverse the array for frequency
198  for (const auto& p : collection) {
199  int secVtxId = p.particleId().vertexSecondary();
200  if (secVtxId != 0) {
201  // truthparticle from secondary vtx
202  continue;
203  }
204  int priVtxId = p.particleId().vertexPrimary();
205  fmap[priVtxId]++;
206  }
207 
208  // iterate over the map
209  for (auto it : fmap) {
210  // Require at least 2 tracks
211  if (it.second > 1) {
212  reconstructableTruthVertices.push_back(it.first);
213  }
214  }
215 
216  return reconstructableTruthVertices.size();
217 }
218 
220  const SimParticleContainer& collection) const {
221  // Vector to store indices of all primary vertices
222  std::set<int> allPriVtxIds;
223  for (const auto& p : collection) {
224  int priVtxId = p.particleId().vertexPrimary();
225  int secVtxId = p.particleId().vertexSecondary();
226  if (secVtxId != 0) {
227  // truthparticle from secondary vtx
228  continue;
229  }
230  // Insert to set, removing duplicates
231  allPriVtxIds.insert(priVtxId);
232  }
233  // Size of set corresponds to total number of primary vertices
234  return allPriVtxIds.size();
235 }
236 
238  const AlgorithmContext& ctx,
240  // Exclusive access to the tree while writing
241  std::lock_guard<std::mutex> lock(m_writeMutex);
242 
243  m_nRecoVtx = vertices.size();
244 
245  ACTS_DEBUG("Number of reco vertices in event: " << m_nRecoVtx);
246 
247  // Read truth particle input collection
248  const auto& allTruthParticles = m_inputAllTruthParticles(ctx);
249  // Get number of generated true primary vertices
250  m_nTrueVtx = getNumberOfTruePriVertices(allTruthParticles);
251 
252  ACTS_VERBOSE("Total number of generated truth particles in event : "
253  << allTruthParticles.size());
254  ACTS_VERBOSE(
255  "Total number of generated truth primary vertices : " << m_nTrueVtx);
256 
257  // Read selected truth particle input collection
258  const auto& selectedTruthParticles = m_inputSelectedTruthParticles(ctx);
259  // Get number of detector-accepted true primary vertices
260  m_nVtxDetAcceptance = getNumberOfTruePriVertices(selectedTruthParticles);
261 
262  ACTS_VERBOSE("Total number of selected truth particles in event : "
263  << selectedTruthParticles.size());
264  ACTS_VERBOSE("Total number of detector-accepted truth primary vertices : "
265  << m_nVtxDetAcceptance);
266 
267  std::vector<Acts::BoundTrackParameters> trackParameters;
268  std::vector<SimParticle> associatedTruthParticles;
269 
270  // The i-th entry in associatedTruthParticles corresponds to the i-th entry in
271  // trackParameters. If we know the truth particles associated to the track
272  // parameters a priori:
273  if (m_cfg.useTracks) {
274  if (!m_cfg.inputAssociatedTruthParticles.empty()) {
275  if (!m_cfg.inputTrackParameters.empty()) {
276  trackParameters = m_inputTrackParameters(ctx);
277  } else {
278  const auto& inputTrajectories = m_inputTrajectories(ctx);
279 
280  for (const auto& trajectories : inputTrajectories) {
281  for (auto tip : trajectories.tips()) {
282  if (!trajectories.hasTrackParameters(tip)) {
283  continue;
284  }
285  trackParameters.push_back(trajectories.trackParameters(tip));
286  }
287  }
288  }
289 
290  // Read track-associated truth particle input collection
291  associatedTruthParticles =
292  std::vector<SimParticle>(m_inputAssociatedTruthParticles(ctx).begin(),
293  m_inputAssociatedTruthParticles(ctx).end());
294 
295  auto mismatchMsg = [&](auto level, const auto& extra) {
296  ACTS_LOG(
297  level,
298  "Number of fitted tracks and associated truth particles do not "
299  "match. ("
300  << trackParameters.size()
301  << " != " << associatedTruthParticles.size()
302  << ") Not able to match fitted tracks at reconstructed "
303  "vertex to truth vertex."
304  << extra);
305  };
306 
307  if (associatedTruthParticles.size() < trackParameters.size()) {
308  mismatchMsg(Acts::Logging::ERROR,
309  " Switch to hit based truth matching.");
310  } else if (associatedTruthParticles.size() > trackParameters.size()) {
311  mismatchMsg(Acts::Logging::INFO,
312  " This is likely due to track efficiency < 1");
313  }
314  }
315  // If we don't know which truth particle corresponds to which track a
316  // priori, we check how many hits particles and tracks share. We match the
317  // particle to the track if a fraction of more than truthMatchProbMin of
318  // hits that contribute to the track come from the particle. Note that not
319  // all tracksatVertex have matching parameters in trackParameters in this
320  // case. Equivalently, one could say that not all tracksAtVertex will be
321  // assigned to a truth particle.
322  else {
323  // Get active tips
324  const auto& inputTrajectories = m_inputTrajectories(ctx);
325 
326  std::vector<ParticleHitCount> particleHitCounts;
327 
328  const auto& hitParticlesMap = m_inputMeasurementParticlesMap(ctx);
329 
330  for (const auto& trajectories : inputTrajectories) {
331  for (auto tip : trajectories.tips()) {
332  if (!trajectories.hasTrackParameters(tip)) {
333  continue;
334  }
335 
336  identifyContributingParticles(hitParticlesMap, trajectories, tip,
337  particleHitCounts);
338  ActsFatras::Barcode majorityParticleId =
339  particleHitCounts.front().particleId;
340  size_t nMajorityHits = particleHitCounts.front().hitCount;
341 
343  trajectories.multiTrajectory(), tip);
344 
345  if (nMajorityHits * 1. / trajState.nMeasurements <
346  m_cfg.truthMatchProbMin) {
347  continue;
348  }
349 
350  auto it = std::find_if(allTruthParticles.begin(),
351  allTruthParticles.end(), [&](const auto& tp) {
352  return tp.particleId() == majorityParticleId;
353  });
354 
355  if (it == allTruthParticles.end()) {
356  continue;
357  }
358 
359  const auto& majorityParticle = *it;
360  trackParameters.push_back(trajectories.trackParameters(tip));
361  associatedTruthParticles.push_back(majorityParticle);
362  }
363  }
364  }
365  } else {
366  // if not using tracks, then all truth particles
367  // are associated with the vertex
368  associatedTruthParticles =
369  std::vector<SimParticle>(m_inputAllTruthParticles(ctx).begin(),
370  m_inputAllTruthParticles(ctx).end());
371  }
372 
373  // Get number of track-associated true primary vertices
374  m_nVtxReconstructable =
375  getNumberOfReconstructableVertices(SimParticleContainer(
376  associatedTruthParticles.begin(), associatedTruthParticles.end()));
377 
378  ACTS_INFO(
379  "Total number of reconstructed tracks : " << trackParameters.size());
380  ACTS_INFO("Total number of reco track-associated truth particles in event : "
381  << associatedTruthParticles.size());
382  ACTS_INFO("Total number of reco track-associated truth primary vertices : "
383  << m_nVtxReconstructable);
384 
385  // Loop over all reco vertices and find associated truth particles
386  std::vector<SimParticleContainer> truthParticlesAtVtxContainer;
387 
388  // We compare the reconstructed momenta to the true momenta at the vertex. For
389  // this, we propagate the reconstructed tracks to the PCA of the true vertex
390  // position. Setting up propagator:
393  auto propagator = std::make_shared<Propagator>(stepper);
394 
395  // Loop over reconstructed vertices and see if they can be matched to a true
396  // vertex.
397  for (const auto& vtx : vertices) {
398  // Reconstructed tracks that contribute to the reconstructed vertex
399  const auto tracksAtVtx = vtx.tracks();
400 
401  // Containers for storing truth particles and truth vertices that contribute
402  // to the reconstructed vertex
403  SimParticleContainer particleAtVtx;
404  std::vector<int> contributingTruthVertices;
405 
406  if (m_cfg.useTracks) {
407  for (const auto& trk : tracksAtVtx) {
408  // Track parameters before the vertex fit
409  Acts::BoundTrackParameters origTrack = *(trk.originalParams);
410 
411  // Finding the matching parameters in the container of all track
412  // parameters. This allows us to identify the corresponding particle,
413  // since we expect trackParameters and associatedTruthParticles to
414  // align.
415  bool foundMatchingParams = false;
416  for (std::size_t i = 0; i < trackParameters.size(); ++i) {
417  const auto& params = trackParameters[i].parameters();
418 
419  if (origTrack.parameters() == params) {
420  // We expect that the i-th associated truth particle corresponds to
421  // the i-th track parameters
422  const auto& particle = associatedTruthParticles[i];
423  particleAtVtx.insert(particle);
424  int priVtxId = particle.particleId().vertexPrimary();
425  contributingTruthVertices.push_back(priVtxId);
426  foundMatchingParams = true;
427  break;
428  }
429  }
430  if (!foundMatchingParams) {
431  ACTS_VERBOSE("Track has no matching truth particle.");
432  }
433  } // end loop tracksAtVtx
434  } else {
435  for (const auto& particle : allTruthParticles) {
436  int priVtxId = particle.particleId().vertexPrimary();
437  contributingTruthVertices.push_back(priVtxId);
438  }
439  }
440 
441  // Find true vertex that contributes most to the reconstructed vertex
442  std::map<int, int> fmap;
443  for (int priVtxId : contributingTruthVertices) {
444  fmap[priVtxId]++;
445  }
446  int maxOccurrence = -1;
447  int maxOccurrenceId = -1;
448  for (auto it : fmap) {
449  if (it.second > maxOccurrence) {
450  maxOccurrenceId = it.first;
451  maxOccurrence = it.second;
452  }
453  }
454 
455  // Count number of reconstructible tracks on truth vertex
456  int nTracksOnTruthVertex = 0;
457  for (const auto& particle : associatedTruthParticles) {
458  int priVtxId = particle.particleId().vertexPrimary();
459  if (priVtxId == maxOccurrenceId) {
460  ++nTracksOnTruthVertex;
461  }
462  }
463 
464  // Match reconstructed and truth vertex if the tracks of the truth vertex
465  // make up at least minTrackVtxMatchFraction of the tracks at the
466  // reconstructed vertex.
467  double trackVtxMatchFraction =
468  (m_cfg.useTracks ? (double)fmap[maxOccurrenceId] / tracksAtVtx.size()
469  : 1.0);
470  if (trackVtxMatchFraction > m_cfg.minTrackVtxMatchFraction) {
471  int count = 0;
472  // Get references to inner vectors where all track variables corresponding
473  // to the current vertex will be saved
474  auto& innerTruthPhi = m_truthPhi.emplace_back();
475  auto& innerTruthTheta = m_truthTheta.emplace_back();
476  auto& innerTruthQOverP = m_truthQOverP.emplace_back();
477 
478  auto& innerRecoPhi = m_recoPhi.emplace_back();
479  auto& innerRecoTheta = m_recoTheta.emplace_back();
480  auto& innerRecoQOverP = m_recoQOverP.emplace_back();
481 
482  auto& innerRecoPhiFitted = m_recoPhiFitted.emplace_back();
483  auto& innerRecoThetaFitted = m_recoThetaFitted.emplace_back();
484  auto& innerRecoQOverPFitted = m_recoQOverPFitted.emplace_back();
485 
486  auto& innerResPhi = m_resPhi.emplace_back();
487  auto& innerResTheta = m_resTheta.emplace_back();
488  auto& innerResQOverP = m_resQOverP.emplace_back();
489 
490  auto& innerResPhiFitted = m_resPhiFitted.emplace_back();
491  auto& innerResThetaFitted = m_resThetaFitted.emplace_back();
492  auto& innerResQOverPFitted = m_resQOverPFitted.emplace_back();
493 
494  auto& innerMomOverlap = m_momOverlap.emplace_back();
495  auto& innerMomOverlapFitted = m_momOverlapFitted.emplace_back();
496 
497  auto& innerPullPhi = m_pullPhi.emplace_back();
498  auto& innerPullTheta = m_pullTheta.emplace_back();
499  auto& innerPullQOverP = m_pullQOverP.emplace_back();
500 
501  auto& innerPullPhiFitted = m_pullPhiFitted.emplace_back();
502  auto& innerPullThetaFitted = m_pullThetaFitted.emplace_back();
503  auto& innerPullQOverPFitted = m_pullQOverPFitted.emplace_back();
504 
505  for (std::size_t j = 0; j < associatedTruthParticles.size(); ++j) {
506  const auto& particle = associatedTruthParticles[j];
507  int priVtxId = particle.particleId().vertexPrimary();
508  int secVtxId = particle.particleId().vertexSecondary();
509 
510  if (secVtxId != 0) {
511  // truthparticle from secondary vtx
512  continue;
513  }
514  if (priVtxId == maxOccurrenceId) {
515  // Vertex found, fill variables
516 
517  // Helper function for computing the pull
518  auto pull = [&](const Acts::ActsScalar& diff,
519  const Acts::ActsScalar& variance,
520  const std::string& variableStr,
521  const bool& afterFit = true) {
522  if (variance <= 0) {
523  std::string tempStr;
524  if (afterFit) {
525  tempStr = "after";
526  } else {
527  tempStr = "before";
528  }
529  ACTS_WARNING("Nonpositive variance "
530  << tempStr << " vertex fit: Var(" << variableStr
531  << ") = " << variance << " <= 0.");
532  return std::numeric_limits<double>::quiet_NaN();
533  }
534  double std = std::sqrt(variance);
535  return diff / std;
536  };
537 
538  const Acts::ActsVector<4>& truePos = particle.fourPosition();
539  // Save reconstructed/true vertex position only in the first iteration
540  // to avoid duplicates
541  if (count == 0) {
542  m_truthX.push_back(truePos[Acts::FreeIndices::eFreePos0]);
543  m_truthY.push_back(truePos[Acts::FreeIndices::eFreePos1]);
544  m_truthZ.push_back(truePos[Acts::FreeIndices::eFreePos2]);
545  m_truthT.push_back(truePos[Acts::FreeIndices::eFreeTime]);
546 
547  m_recoX.push_back(vtx.fullPosition()[Acts::FreeIndices::eFreePos0]);
548  m_recoY.push_back(vtx.fullPosition()[Acts::FreeIndices::eFreePos1]);
549  m_recoZ.push_back(vtx.fullPosition()[Acts::FreeIndices::eFreePos2]);
550  m_recoT.push_back(vtx.fullPosition()[Acts::FreeIndices::eFreeTime]);
551 
552  const Acts::ActsVector<4> diffPos = vtx.fullPosition() - truePos;
553  m_resX.push_back(diffPos[Acts::FreeIndices::eFreePos0]);
554  m_resY.push_back(diffPos[Acts::FreeIndices::eFreePos1]);
555  m_resZ.push_back(diffPos[Acts::FreeIndices::eFreePos2]);
556  m_resT.push_back(diffPos[Acts::FreeIndices::eFreeTime]);
557 
558  Acts::ActsScalar varX = vtx.fullCovariance()(
560  Acts::ActsScalar varY = vtx.fullCovariance()(
562  Acts::ActsScalar varZ = vtx.fullCovariance()(
564  Acts::ActsScalar varTime = vtx.fullCovariance()(
566  m_pullX.push_back(
567  pull(diffPos[Acts::FreeIndices::eFreePos0], varX, "X"));
568  m_pullY.push_back(
569  pull(diffPos[Acts::FreeIndices::eFreePos1], varY, "Y"));
570  m_pullZ.push_back(
571  pull(diffPos[Acts::FreeIndices::eFreePos2], varZ, "Z"));
572  m_pullT.push_back(
573  pull(diffPos[Acts::FreeIndices::eFreeTime], varTime, "T"));
574 
575  m_covXX.push_back(varX);
576  m_covYY.push_back(varY);
577  m_covZZ.push_back(varZ);
578  m_covTT.push_back(varTime);
579  m_covXY.push_back(vtx.fullCovariance()(
581  m_covXZ.push_back(vtx.fullCovariance()(
583  m_covXT.push_back(vtx.fullCovariance()(
585  m_covYZ.push_back(vtx.fullCovariance()(
587  m_covYT.push_back(vtx.fullCovariance()(
589  m_covZT.push_back(vtx.fullCovariance()(
591 
592  m_nTracksOnTruthVertex.push_back(nTracksOnTruthVertex);
593  m_nTracksOnRecoVertex.push_back(tracksAtVtx.size());
594 
595  m_trackVtxMatchFraction.push_back(trackVtxMatchFraction);
596  }
597 
598  // Saving the reconstructed/truth momenta. The reconstructed momenta
599  // are taken at the PCA to the truth vertex position -> we need to
600  // perform a propagation.
601 
602  // Perigee at the true vertex position
603  const std::shared_ptr<Acts::PerigeeSurface> perigeeSurface =
604  Acts::Surface::makeShared<Acts::PerigeeSurface>(truePos.head(3));
605  // Setting the geometry/magnetic field context for the event
607  // Lambda for propagating the tracks to the PCA
608  auto propagateToVtx = [&](const auto& params)
609  -> std::optional<Acts::BoundTrackParameters> {
610  auto intersection =
611  perigeeSurface
612  ->intersect(ctx.geoContext, params.position(ctx.geoContext),
613  params.direction(), false)
614  .closest();
615  pOptions.direction = Acts::Direction::fromScalarZeroAsPositive(
616  intersection.pathLength());
617 
618  auto result =
619  propagator->propagate(params, *perigeeSurface, pOptions);
620  if (not result.ok()) {
621  ACTS_ERROR("Propagation to true vertex position failed.");
622  return std::nullopt;
623  }
624  auto& paramsAtVtx = *result->endParameters;
625  return std::make_optional(paramsAtVtx);
626  };
627  // Get the reconstructed track parameters corresponding to the
628  // particle
629  const auto& params = trackParameters[j].parameters();
630  // Check if they correspond to a track that contributed to the vertex.
631  // We save the momenta if we find a match.
632  for (const auto& trk : tracksAtVtx) {
633  if (trk.originalParams->parameters() == params) {
634  const auto& trueUnitDir = particle.direction();
635  Acts::ActsVector<3> trueMom;
636  trueMom.head(2) = Acts::makePhiThetaFromDirection(trueUnitDir);
637  trueMom[2] = particle.qOverP();
638  innerTruthPhi.push_back(trueMom[0]);
639  innerTruthTheta.push_back(trueMom[1]);
640  innerTruthQOverP.push_back(trueMom[2]);
641 
642  // Save track parameters before the vertex fit
643  const auto paramsAtVtx = propagateToVtx(*(trk.originalParams));
644  if (paramsAtVtx != std::nullopt) {
645  Acts::ActsVector<3> recoMom =
646  paramsAtVtx->parameters().segment(Acts::eBoundPhi, 3);
647  const Acts::ActsMatrix<3, 3>& momCov =
648  paramsAtVtx->covariance()->block<3, 3>(Acts::eBoundPhi,
650  innerRecoPhi.push_back(recoMom[0]);
651  innerRecoTheta.push_back(recoMom[1]);
652  innerRecoQOverP.push_back(recoMom[2]);
653 
654  Acts::ActsVector<3> diffMom = recoMom - trueMom;
655  // Accounting for the periodicity of phi. We overwrite the
656  // previously computed value for better readability.
658  recoMom(0), trueMom(0), 2 * M_PI);
659  innerResPhi.push_back(diffMom[0]);
660  innerResTheta.push_back(diffMom[1]);
661  innerResQOverP.push_back(diffMom[2]);
662 
663  innerPullPhi.push_back(
664  pull(diffMom[0], momCov(0, 0), "phi", false));
665  innerPullTheta.push_back(
666  pull(diffMom[1], momCov(1, 1), "theta", false));
667  innerPullQOverP.push_back(
668  pull(diffMom[2], momCov(2, 2), "q/p", false));
669 
670  const auto& recoUnitDir = paramsAtVtx->direction();
671  double overlap = trueUnitDir.dot(recoUnitDir);
672  innerMomOverlap.push_back(overlap);
673  }
674 
675  // Save track parameters after the vertex fit
676  const auto paramsAtVtxFitted = propagateToVtx(trk.fittedParams);
677  if (paramsAtVtxFitted != std::nullopt) {
678  Acts::ActsVector<3> recoMomFitted =
679  paramsAtVtxFitted->parameters().segment(Acts::eBoundPhi, 3);
680  const Acts::ActsMatrix<3, 3>& momCovFitted =
681  paramsAtVtxFitted->covariance()->block<3, 3>(
683  innerRecoPhiFitted.push_back(recoMomFitted[0]);
684  innerRecoThetaFitted.push_back(recoMomFitted[1]);
685  innerRecoQOverPFitted.push_back(recoMomFitted[2]);
686 
687  Acts::ActsVector<3> diffMomFitted = recoMomFitted - trueMom;
688  // Accounting for the periodicity of phi. We overwrite the
689  // previously computed value for better readability.
690  diffMomFitted[0] = Acts::detail::difference_periodic(
691  recoMomFitted(0), trueMom(0), 2 * M_PI);
692  innerResPhiFitted.push_back(diffMomFitted[0]);
693  innerResThetaFitted.push_back(diffMomFitted[1]);
694  innerResQOverPFitted.push_back(diffMomFitted[2]);
695 
696  innerPullPhiFitted.push_back(
697  pull(diffMomFitted[0], momCovFitted(0, 0), "phi"));
698  innerPullThetaFitted.push_back(
699  pull(diffMomFitted[1], momCovFitted(1, 1), "theta"));
700  innerPullQOverPFitted.push_back(
701  pull(diffMomFitted[2], momCovFitted(2, 2), "q/p"));
702 
703  const auto& recoUnitDirFitted = paramsAtVtxFitted->direction();
704  double overlapFitted = trueUnitDir.dot(recoUnitDirFitted);
705  innerMomOverlapFitted.push_back(overlapFitted);
706  }
707  }
708  }
709  count++;
710  }
711  }
712  }
713  } // end loop vertices
714 
715  // fill the variables
716  m_outputTree->Fill();
717 
718  m_truthX.clear();
719  m_truthY.clear();
720  m_truthZ.clear();
721  m_truthT.clear();
722  m_recoX.clear();
723  m_recoY.clear();
724  m_recoZ.clear();
725  m_recoT.clear();
726  m_resX.clear();
727  m_resY.clear();
728  m_resZ.clear();
729  m_resT.clear();
730  m_pullX.clear();
731  m_pullY.clear();
732  m_pullZ.clear();
733  m_pullT.clear();
734  m_covXX.clear();
735  m_covYY.clear();
736  m_covZZ.clear();
737  m_covTT.clear();
738  m_covXY.clear();
739  m_covXZ.clear();
740  m_covXT.clear();
741  m_covYZ.clear();
742  m_covYT.clear();
743  m_covZT.clear();
744  m_truthPhi.clear();
745  m_truthTheta.clear();
746  m_truthQOverP.clear();
747  m_recoPhi.clear();
748  m_recoPhiFitted.clear();
749  m_recoTheta.clear();
750  m_recoThetaFitted.clear();
751  m_recoQOverP.clear();
752  m_recoQOverPFitted.clear();
753  m_resPhi.clear();
754  m_resPhiFitted.clear();
755  m_resTheta.clear();
756  m_resThetaFitted.clear();
757  m_resQOverP.clear();
758  m_resQOverPFitted.clear();
759  m_momOverlap.clear();
760  m_momOverlapFitted.clear();
761  m_pullPhi.clear();
762  m_pullPhiFitted.clear();
763  m_pullTheta.clear();
764  m_pullThetaFitted.clear();
765  m_pullQOverP.clear();
766  m_pullQOverPFitted.clear();
767 
768  m_nTracksOnTruthVertex.clear();
769  m_nTracksOnRecoVertex.clear();
770 
771  m_trackVtxMatchFraction.clear();
772 
773  return ProcessCode::SUCCESS;
774 }