45 namespace ActsExamples {
46 struct AlgorithmContext;
57 :
WriterT(config.inputVertices,
"VertexPerformanceWriter", level),
60 throw std::invalid_argument(
"Missing output filename");
63 throw std::invalid_argument(
"Missing tree name");
66 throw std::invalid_argument(
"Collection with all truth particles missing");
69 throw std::invalid_argument(
70 "Collection with selected truth particles missing");
96 throw std::ios_base::failure(
"Could not open '" +
path);
101 throw std::bad_alloc();
177 if (m_outputFile !=
nullptr) {
178 m_outputFile->Close();
184 m_outputTree->Write();
185 m_outputFile->Close();
193 std::map<int, int> fmap;
195 std::vector<int> reconstructableTruthVertices;
198 for (
const auto&
p : collection) {
199 int secVtxId =
p.particleId().vertexSecondary();
204 int priVtxId =
p.particleId().vertexPrimary();
209 for (
auto it : fmap) {
212 reconstructableTruthVertices.push_back(
it.first);
216 return reconstructableTruthVertices.size();
222 std::set<int> allPriVtxIds;
223 for (
const auto&
p : collection) {
224 int priVtxId =
p.particleId().vertexPrimary();
225 int secVtxId =
p.particleId().vertexSecondary();
231 allPriVtxIds.insert(priVtxId);
234 return allPriVtxIds.size();
241 std::lock_guard<std::mutex> lock(m_writeMutex);
245 ACTS_DEBUG(
"Number of reco vertices in event: " << m_nRecoVtx);
248 const auto& allTruthParticles = m_inputAllTruthParticles(ctx);
250 m_nTrueVtx = getNumberOfTruePriVertices(allTruthParticles);
252 ACTS_VERBOSE(
"Total number of generated truth particles in event : "
253 << allTruthParticles.size());
255 "Total number of generated truth primary vertices : " << m_nTrueVtx);
258 const auto& selectedTruthParticles = m_inputSelectedTruthParticles(ctx);
260 m_nVtxDetAcceptance = getNumberOfTruePriVertices(selectedTruthParticles);
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);
267 std::vector<Acts::BoundTrackParameters> trackParameters;
268 std::vector<SimParticle> associatedTruthParticles;
273 if (
m_cfg.useTracks) {
274 if (!
m_cfg.inputAssociatedTruthParticles.empty()) {
275 if (!
m_cfg.inputTrackParameters.empty()) {
276 trackParameters = m_inputTrackParameters(ctx);
281 for (
auto tip : trajectories.tips()) {
282 if (!trajectories.hasTrackParameters(tip)) {
285 trackParameters.push_back(trajectories.trackParameters(tip));
291 associatedTruthParticles =
292 std::vector<SimParticle>(m_inputAssociatedTruthParticles(ctx).begin(),
293 m_inputAssociatedTruthParticles(ctx).end());
295 auto mismatchMsg = [&](
auto level,
const auto& extra) {
298 "Number of fitted tracks and associated truth particles do not "
300 << trackParameters.size()
301 <<
" != " << associatedTruthParticles.size()
302 <<
") Not able to match fitted tracks at reconstructed "
303 "vertex to truth vertex."
307 if (associatedTruthParticles.size() < trackParameters.size()) {
309 " Switch to hit based truth matching.");
310 }
else if (associatedTruthParticles.size() > trackParameters.size()) {
312 " This is likely due to track efficiency < 1");
326 std::vector<ParticleHitCount> particleHitCounts;
328 const auto& hitParticlesMap = m_inputMeasurementParticlesMap(ctx);
331 for (
auto tip : trajectories.tips()) {
332 if (!trajectories.hasTrackParameters(tip)) {
339 particleHitCounts.front().particleId;
340 size_t nMajorityHits = particleHitCounts.front().hitCount;
343 trajectories.multiTrajectory(), tip);
345 if (nMajorityHits * 1. / trajState.nMeasurements <
346 m_cfg.truthMatchProbMin) {
350 auto it = std::find_if(allTruthParticles.begin(),
351 allTruthParticles.end(), [&](
const auto&
tp) {
352 return tp.particleId() == majorityParticleId;
355 if (
it == allTruthParticles.end()) {
359 const auto& majorityParticle = *
it;
360 trackParameters.push_back(trajectories.trackParameters(tip));
361 associatedTruthParticles.push_back(majorityParticle);
368 associatedTruthParticles =
369 std::vector<SimParticle>(m_inputAllTruthParticles(ctx).begin(),
370 m_inputAllTruthParticles(ctx).end());
374 m_nVtxReconstructable =
376 associatedTruthParticles.begin(), associatedTruthParticles.end()));
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);
386 std::vector<SimParticleContainer> truthParticlesAtVtxContainer;
393 auto propagator = std::make_shared<Propagator>(
stepper);
399 const auto tracksAtVtx = vtx.tracks();
404 std::vector<int> contributingTruthVertices;
406 if (
m_cfg.useTracks) {
407 for (
const auto& trk : tracksAtVtx) {
415 bool foundMatchingParams =
false;
416 for (std::size_t
i = 0;
i < trackParameters.size(); ++
i) {
417 const auto& params = trackParameters[
i].parameters();
422 const auto&
particle = associatedTruthParticles[
i];
424 int priVtxId =
particle.particleId().vertexPrimary();
425 contributingTruthVertices.push_back(priVtxId);
426 foundMatchingParams =
true;
430 if (!foundMatchingParams) {
435 for (
const auto&
particle : allTruthParticles) {
436 int priVtxId =
particle.particleId().vertexPrimary();
437 contributingTruthVertices.push_back(priVtxId);
442 std::map<int, int> fmap;
443 for (
int priVtxId : contributingTruthVertices) {
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;
456 int nTracksOnTruthVertex = 0;
457 for (
const auto&
particle : associatedTruthParticles) {
458 int priVtxId =
particle.particleId().vertexPrimary();
459 if (priVtxId == maxOccurrenceId) {
460 ++nTracksOnTruthVertex;
467 double trackVtxMatchFraction =
468 (
m_cfg.useTracks ? (
double)fmap[maxOccurrenceId] / tracksAtVtx.size()
470 if (trackVtxMatchFraction >
m_cfg.minTrackVtxMatchFraction) {
474 auto& innerTruthPhi = m_truthPhi.emplace_back();
475 auto& innerTruthTheta = m_truthTheta.emplace_back();
476 auto& innerTruthQOverP = m_truthQOverP.emplace_back();
478 auto& innerRecoPhi = m_recoPhi.emplace_back();
479 auto& innerRecoTheta = m_recoTheta.emplace_back();
480 auto& innerRecoQOverP = m_recoQOverP.emplace_back();
482 auto& innerRecoPhiFitted = m_recoPhiFitted.emplace_back();
483 auto& innerRecoThetaFitted = m_recoThetaFitted.emplace_back();
484 auto& innerRecoQOverPFitted = m_recoQOverPFitted.emplace_back();
486 auto& innerResPhi = m_resPhi.emplace_back();
487 auto& innerResTheta = m_resTheta.emplace_back();
488 auto& innerResQOverP = m_resQOverP.emplace_back();
490 auto& innerResPhiFitted = m_resPhiFitted.emplace_back();
491 auto& innerResThetaFitted = m_resThetaFitted.emplace_back();
492 auto& innerResQOverPFitted = m_resQOverPFitted.emplace_back();
494 auto& innerMomOverlap = m_momOverlap.emplace_back();
495 auto& innerMomOverlapFitted = m_momOverlapFitted.emplace_back();
497 auto& innerPullPhi = m_pullPhi.emplace_back();
498 auto& innerPullTheta = m_pullTheta.emplace_back();
499 auto& innerPullQOverP = m_pullQOverP.emplace_back();
501 auto& innerPullPhiFitted = m_pullPhiFitted.emplace_back();
502 auto& innerPullThetaFitted = m_pullThetaFitted.emplace_back();
503 auto& innerPullQOverPFitted = m_pullQOverPFitted.emplace_back();
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();
514 if (priVtxId == maxOccurrenceId) {
521 const bool& afterFit =
true) {
530 << tempStr <<
" vertex fit: Var(" << variableStr
531 <<
") = " << variance <<
" <= 0.");
532 return std::numeric_limits<double>::quiet_NaN();
534 double std = std::sqrt(variance);
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]);
567 pull(diffPos[Acts::FreeIndices::eFreePos0], varX,
"X"));
569 pull(diffPos[Acts::FreeIndices::eFreePos1], varY,
"Y"));
571 pull(diffPos[Acts::FreeIndices::eFreePos2], varZ,
"Z"));
573 pull(diffPos[Acts::FreeIndices::eFreeTime], varTime,
"T"));
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()(
592 m_nTracksOnTruthVertex.push_back(nTracksOnTruthVertex);
593 m_nTracksOnRecoVertex.push_back(tracksAtVtx.size());
595 m_trackVtxMatchFraction.push_back(trackVtxMatchFraction);
603 const std::shared_ptr<Acts::PerigeeSurface> perigeeSurface =
604 Acts::Surface::makeShared<Acts::PerigeeSurface>(truePos.head(3));
608 auto propagateToVtx = [&](
const auto& params)
609 -> std::optional<Acts::BoundTrackParameters> {
613 params.direction(),
false)
616 intersection.pathLength());
619 propagator->propagate(params, *perigeeSurface, pOptions);
620 if (not result.ok()) {
621 ACTS_ERROR(
"Propagation to true vertex position failed.");
624 auto& paramsAtVtx = *result->endParameters;
625 return std::make_optional(paramsAtVtx);
629 const auto& params = trackParameters[
j].parameters();
632 for (
const auto& trk : tracksAtVtx) {
633 if (trk.originalParams->parameters() == params) {
634 const auto& trueUnitDir =
particle.direction();
638 innerTruthPhi.push_back(trueMom[0]);
639 innerTruthTheta.push_back(trueMom[1]);
640 innerTruthQOverP.push_back(trueMom[2]);
643 const auto paramsAtVtx = propagateToVtx(*(trk.originalParams));
644 if (paramsAtVtx != std::nullopt) {
650 innerRecoPhi.push_back(recoMom[0]);
651 innerRecoTheta.push_back(recoMom[1]);
652 innerRecoQOverP.push_back(recoMom[2]);
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]);
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));
670 const auto& recoUnitDir = paramsAtVtx->direction();
671 double overlap = trueUnitDir.dot(recoUnitDir);
672 innerMomOverlap.push_back(overlap);
676 const auto paramsAtVtxFitted = propagateToVtx(trk.fittedParams);
677 if (paramsAtVtxFitted != std::nullopt) {
681 paramsAtVtxFitted->covariance()->block<3, 3>(
683 innerRecoPhiFitted.push_back(recoMomFitted[0]);
684 innerRecoThetaFitted.push_back(recoMomFitted[1]);
685 innerRecoQOverPFitted.push_back(recoMomFitted[2]);
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]);
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"));
703 const auto& recoUnitDirFitted = paramsAtVtxFitted->direction();
704 double overlapFitted = trueUnitDir.dot(recoUnitDirFitted);
705 innerMomOverlapFitted.push_back(overlapFitted);
716 m_outputTree->Fill();
745 m_truthTheta.clear();
746 m_truthQOverP.clear();
748 m_recoPhiFitted.clear();
750 m_recoThetaFitted.clear();
751 m_recoQOverP.clear();
752 m_recoQOverPFitted.clear();
754 m_resPhiFitted.clear();
756 m_resThetaFitted.clear();
758 m_resQOverPFitted.clear();
759 m_momOverlap.clear();
760 m_momOverlapFitted.clear();
762 m_pullPhiFitted.clear();
764 m_pullThetaFitted.clear();
765 m_pullQOverP.clear();
766 m_pullQOverPFitted.clear();
768 m_nTracksOnTruthVertex.clear();
769 m_nTracksOnRecoVertex.clear();
771 m_trackVtxMatchFraction.clear();