File indexing completed on 2026-08-05 08:23:23
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Validation/PatternRecognitionPerformanceCollector.hpp"
0010
0011 #include "Acts/EventData/BoundTrackParameters.hpp"
0012 #include "Acts/Utilities/Logger.hpp"
0013 #include "Acts/Utilities/VectorHelpers.hpp"
0014
0015 #include <format>
0016 #include <utility>
0017
0018 namespace ActsExamples {
0019
0020 PatternRecognitionPerformanceCollector::PatternRecognitionPerformanceCollector(
0021 Config cfg, std::unique_ptr<const Acts::Logger> logger)
0022 : m_cfg(std::move(cfg)),
0023 m_logger(std::move(logger)),
0024 m_effPlotTool([&]() {
0025 auto c = m_cfg.effPlotToolConfig;
0026 c.label = m_cfg.label;
0027 return EffPlotTool(c, m_logger->level());
0028 }()),
0029 m_fakePlotTool([&]() {
0030 auto c = m_cfg.fakePlotToolConfig;
0031 c.label = m_cfg.label;
0032 return FakePlotTool(c, m_logger->level());
0033 }()),
0034 m_duplicationPlotTool([&]() {
0035 auto c = m_cfg.duplicationPlotToolConfig;
0036 c.label = m_cfg.label;
0037 return DuplicationPlotTool(c, m_logger->level());
0038 }()),
0039 m_trackSummaryPlotTool(m_cfg.trackSummaryPlotToolConfig,
0040 m_logger->level()),
0041 m_trackQualityPlotTool(m_cfg.trackQualityPlotToolConfig,
0042 m_logger->level()) {
0043 for (const auto& [key, _] : m_cfg.subDetectorTrackSummaryVolumes) {
0044 TrackSummaryPlotTool::Config subConfig = m_cfg.trackSummaryPlotToolConfig;
0045 subConfig.prefix = key;
0046 m_subDetectorSummaryTools.emplace(
0047 std::piecewise_construct, std::forward_as_tuple(key),
0048 std::forward_as_tuple(subConfig, m_logger->level()));
0049 }
0050 }
0051
0052 void PatternRecognitionPerformanceCollector::fill(
0053 const Acts::GeometryContext& geoContext, const ConstTrackContainer& tracks,
0054 const SimParticleContainer& particles,
0055 const TrackParticleMatching& trackParticleMatching,
0056 const ParticleTrackMatching& particleTrackMatching,
0057 const InverseMultimap<SimBarcode>& particleMeasurementsMap) {
0058 std::size_t unmatched = 0;
0059 std::size_t missingRefSurface = 0;
0060
0061 std::string labelPlural = std::format("{}s", m_cfg.label);
0062
0063 for (const auto& track : tracks) {
0064 m_stats.nTotalTracks++;
0065
0066 if (!track.hasReferenceSurface()) {
0067 missingRefSurface++;
0068 continue;
0069 }
0070
0071 Acts::BoundTrackParameters fittedParameters =
0072 track.createParametersAtReference();
0073
0074 m_trackSummaryPlotTool.fill(fittedParameters, track.nTrackStates(),
0075 track.nMeasurements(), track.nOutliers(),
0076 track.nHoles(), track.nSharedHits());
0077
0078 for (const auto& [key, volumes] : m_cfg.subDetectorTrackSummaryVolumes) {
0079 std::size_t nTrackStates{};
0080 std::size_t nMeasurements{};
0081 std::size_t nOutliers{};
0082 std::size_t nHoles{};
0083 std::size_t nSharedHits{};
0084
0085 for (auto state : track.trackStatesReversed()) {
0086 if (!state.hasReferenceSurface() ||
0087 !volumes.contains(state.referenceSurface().geometryId().volume())) {
0088 continue;
0089 }
0090 nTrackStates++;
0091 nMeasurements +=
0092 static_cast<std::size_t>(state.typeFlags().isMeasurement());
0093 nOutliers += static_cast<std::size_t>(state.typeFlags().isOutlier());
0094 nHoles += static_cast<std::size_t>(state.typeFlags().isHole());
0095 nSharedHits +=
0096 static_cast<std::size_t>(state.typeFlags().isSharedHit());
0097 }
0098 m_subDetectorSummaryTools.at(key).fill(fittedParameters, nTrackStates,
0099 nMeasurements, nOutliers, nHoles,
0100 nSharedHits);
0101 }
0102
0103 auto imatched = trackParticleMatching.find(track.index());
0104 if (imatched == trackParticleMatching.end()) {
0105 unmatched++;
0106 continue;
0107 }
0108
0109 const auto& particleMatch = imatched->second;
0110
0111 if (particleMatch.classification == TrackMatchClassification::Fake) {
0112 m_stats.nTotalFakeTracks++;
0113 }
0114 if (particleMatch.classification == TrackMatchClassification::Duplicate) {
0115 m_stats.nTotalDuplicateTracks++;
0116 }
0117
0118 m_fakePlotTool.fill(fittedParameters, particleMatch.classification ==
0119 TrackMatchClassification::Fake);
0120 m_duplicationPlotTool.fill(
0121 fittedParameters,
0122 particleMatch.classification == TrackMatchClassification::Duplicate);
0123
0124 if (particleMatch.particle.has_value() &&
0125 particleMeasurementsMap.contains(particleMatch.particle.value())) {
0126 const auto measurements =
0127 particleMeasurementsMap.equal_range(particleMatch.particle.value());
0128
0129 std::size_t nTrackMeasurements =
0130 track.nMeasurements() + track.nOutliers();
0131 std::size_t nMatchedHits =
0132 particleMatch.contributingParticles.front().hitCount;
0133 std::size_t nParticleHits =
0134 std::distance(measurements.first, measurements.second);
0135
0136 double completeness = static_cast<double>(nMatchedHits) / nParticleHits;
0137 double purity = static_cast<double>(nMatchedHits) / nTrackMeasurements;
0138
0139 m_trackQualityPlotTool.fill(fittedParameters, completeness, purity);
0140 }
0141 }
0142
0143 if (unmatched > 0) {
0144 ACTS_VERBOSE("No matching information found for " << unmatched << " "
0145 << labelPlural);
0146 }
0147 if (missingRefSurface > 0) {
0148 ACTS_VERBOSE("Reference surface was missing for " << missingRefSurface
0149 << " " << labelPlural);
0150 }
0151
0152 for (const auto& particle : particles) {
0153 auto particleId = particle.particleId();
0154
0155 std::size_t nMatchedTracks = 0;
0156 std::size_t nFakeTracks = 0;
0157 bool isReconstructed = false;
0158 if (auto imatched = particleTrackMatching.find(particleId);
0159 imatched != particleTrackMatching.end()) {
0160 isReconstructed = imatched->second.track.has_value();
0161 nMatchedTracks = (isReconstructed ? 1 : 0) + imatched->second.duplicates;
0162
0163 m_stats.nTotalMatchedTracks += nMatchedTracks;
0164 m_stats.nTotalMatchedParticles += isReconstructed ? 1 : 0;
0165
0166 if (nMatchedTracks > 1) {
0167 m_stats.nTotalDuplicateParticles += 1;
0168 }
0169
0170 nFakeTracks = imatched->second.fakes;
0171 if (nFakeTracks > 0) {
0172 m_stats.nTotalFakeParticles += 1;
0173 }
0174 }
0175
0176 double minDeltaR = -1;
0177 for (const auto& closeParticle : particles) {
0178 if (closeParticle.particleId() == particleId) {
0179 continue;
0180 }
0181 double distance = Acts::VectorHelpers::deltaR(particle.direction(),
0182 closeParticle.direction());
0183 if (minDeltaR == -1 || distance < minDeltaR) {
0184 minDeltaR = distance;
0185 }
0186 }
0187
0188 m_effPlotTool.fill(geoContext, particle.initialState(), minDeltaR,
0189 isReconstructed);
0190 m_duplicationPlotTool.fill(particle.initialState(), nMatchedTracks);
0191 m_fakePlotTool.fill(particle.initialState(), nMatchedTracks, nFakeTracks);
0192
0193 m_stats.nTotalParticles += 1;
0194 }
0195 }
0196
0197 void PatternRecognitionPerformanceCollector::logSummary() const {
0198 std::string labelPlural = std::format("{}s", m_cfg.label);
0199
0200 const Acts::Logger& log = *m_logger;
0201 float eff_tracks =
0202 static_cast<float>(m_stats.nTotalMatchedTracks) / m_stats.nTotalTracks;
0203 float fakeRatio_tracks =
0204 static_cast<float>(m_stats.nTotalFakeTracks) / m_stats.nTotalTracks;
0205 float duplicationRatio_tracks =
0206 static_cast<float>(m_stats.nTotalDuplicateTracks) / m_stats.nTotalTracks;
0207
0208 float eff_particle = static_cast<float>(m_stats.nTotalMatchedParticles) /
0209 m_stats.nTotalParticles;
0210 float fakeRatio_particle =
0211 static_cast<float>(m_stats.nTotalFakeParticles) / m_stats.nTotalParticles;
0212 float duplicationRatio_particle =
0213 static_cast<float>(m_stats.nTotalDuplicateParticles) /
0214 m_stats.nTotalParticles;
0215
0216 ACTS_LOG_WITH_LOGGER(
0217 log, Acts::Logging::DEBUG,
0218 "nTotal" << m_cfg.label << "s = " << m_stats.nTotalTracks);
0219 ACTS_LOG_WITH_LOGGER(log, Acts::Logging::DEBUG,
0220 "nTotalMatched" << m_cfg.label << "s = "
0221 << m_stats.nTotalMatchedTracks);
0222 ACTS_LOG_WITH_LOGGER(log, Acts::Logging::DEBUG,
0223 "nTotalDuplicate" << m_cfg.label << "s = "
0224 << m_stats.nTotalDuplicateTracks);
0225 ACTS_LOG_WITH_LOGGER(log, Acts::Logging::DEBUG,
0226 "nTotalFake" << m_cfg.label << "s = "
0227 << m_stats.nTotalFakeTracks);
0228
0229 ACTS_LOG_WITH_LOGGER(log, Acts::Logging::INFO,
0230 "Efficiency with "
0231 << labelPlural << " (nMatched" << m_cfg.label
0232 << "s/nAll" << m_cfg.label << "s) = " << eff_tracks);
0233 ACTS_LOG_WITH_LOGGER(
0234 log, Acts::Logging::INFO,
0235 "Fake ratio with " << labelPlural << " (nFake" << m_cfg.label << "s/nAll"
0236 << m_cfg.label << "s) = " << fakeRatio_tracks);
0237 ACTS_LOG_WITH_LOGGER(log, Acts::Logging::INFO,
0238 "Duplicate ratio with "
0239 << labelPlural << " (nDuplicate" << m_cfg.label
0240 << "s/nAll" << m_cfg.label
0241 << "s) = " << duplicationRatio_tracks);
0242 ACTS_LOG_WITH_LOGGER(
0243 log, Acts::Logging::INFO,
0244 "Efficiency with particles (nMatchedParticles/nTrueParticles) = "
0245 << eff_particle);
0246 ACTS_LOG_WITH_LOGGER(
0247 log, Acts::Logging::INFO,
0248 "Fake ratio with particles (nFakeParticles/nTrueParticles) = "
0249 << fakeRatio_particle);
0250 ACTS_LOG_WITH_LOGGER(
0251 log, Acts::Logging::INFO,
0252 "Duplicate ratio with particles (nDuplicateParticles/nTrueParticles) = "
0253 << duplicationRatio_particle);
0254 }
0255
0256 }