Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:09:48

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2020-2024 CERN for the benefit of the Acts project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
0008 
0009 #include "ActsExamples/TrackFinding/TrackFindingAlgorithm.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Direction.hpp"
0013 #include "Acts/Definitions/TrackParametrization.hpp"
0014 #include "Acts/EventData/MultiTrajectory.hpp"
0015 #include "Acts/EventData/ProxyAccessor.hpp"
0016 #include "Acts/EventData/SourceLink.hpp"
0017 #include "Acts/EventData/TrackContainer.hpp"
0018 #include "Acts/EventData/TrackParameters.hpp"
0019 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0020 #include "Acts/EventData/VectorTrackContainer.hpp"
0021 #include "Acts/Geometry/GeometryIdentifier.hpp"
0022 #include "Acts/Propagator/AbortList.hpp"
0023 #include "Acts/Propagator/EigenStepper.hpp"
0024 #include "Acts/Propagator/MaterialInteractor.hpp"
0025 #include "Acts/Propagator/Navigator.hpp"
0026 #include "Acts/Propagator/Propagator.hpp"
0027 #include "Acts/Propagator/StandardAborters.hpp"
0028 #include "Acts/Surfaces/PerigeeSurface.hpp"
0029 #include "Acts/Surfaces/Surface.hpp"
0030 #include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
0031 #include "Acts/TrackFitting/GainMatrixSmoother.hpp"
0032 #include "Acts/TrackFitting/GainMatrixUpdater.hpp"
0033 #include "Acts/TrackFitting/KalmanFitter.hpp"
0034 #include "Acts/Utilities/Delegate.hpp"
0035 #include "Acts/Utilities/Enumerate.hpp"
0036 #include "Acts/Utilities/Logger.hpp"
0037 #include "Acts/Utilities/TrackHelpers.hpp"
0038 #include "ActsExamples/EventData/IndexSourceLink.hpp"
0039 #include "ActsExamples/EventData/Measurement.hpp"
0040 #include "ActsExamples/EventData/MeasurementCalibration.hpp"
0041 #include "ActsExamples/EventData/SimSeed.hpp"
0042 #include "ActsExamples/EventData/Track.hpp"
0043 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0044 #include "ActsExamples/Framework/ProcessCode.hpp"
0045 
0046 #include <cmath>
0047 #include <functional>
0048 #include <memory>
0049 #include <optional>
0050 #include <ostream>
0051 #include <stdexcept>
0052 #include <system_error>
0053 #include <unordered_map>
0054 #include <utility>
0055 
0056 #include <boost/functional/hash.hpp>
0057 
0058 // Specialize std::hash for SeedIdentifier
0059 // This is required to use SeedIdentifier as a key in an `std::unordered_map`.
0060 template <class T, std::size_t N>
0061 struct std::hash<std::array<T, N>> {
0062   std::size_t operator()(const std::array<T, N>& array) const {
0063     std::hash<T> hasher;
0064     std::size_t result = 0;
0065     for (auto&& element : array) {
0066       boost::hash_combine(result, hasher(element));
0067     }
0068     return result;
0069   }
0070 };
0071 
0072 namespace ActsExamples {
0073 
0074 namespace {
0075 
0076 class MeasurementSelector {
0077  public:
0078   using Traj = Acts::VectorMultiTrajectory;
0079 
0080   explicit MeasurementSelector(Acts::MeasurementSelector selector)
0081       : m_selector(std::move(selector)) {}
0082 
0083   void setSeed(const std::optional<SimSeed>& seed) { m_seed = seed; }
0084 
0085   Acts::Result<std::pair<std::vector<Traj::TrackStateProxy>::iterator,
0086                          std::vector<Traj::TrackStateProxy>::iterator>>
0087   select(std::vector<Traj::TrackStateProxy>& candidates, bool& isOutlier,
0088          const Acts::Logger& logger) const {
0089     if (m_seed.has_value()) {
0090       std::vector<Traj::TrackStateProxy> newCandidates;
0091 
0092       for (const auto& candidate : candidates) {
0093         if (isSeedCandidate(candidate)) {
0094           newCandidates.push_back(candidate);
0095         }
0096       }
0097 
0098       if (!newCandidates.empty()) {
0099         candidates = std::move(newCandidates);
0100       }
0101     }
0102 
0103     return m_selector.select<Acts::VectorMultiTrajectory>(candidates, isOutlier,
0104                                                           logger);
0105   }
0106 
0107  private:
0108   Acts::MeasurementSelector m_selector;
0109   std::optional<SimSeed> m_seed;
0110 
0111   bool isSeedCandidate(const Traj::TrackStateProxy& candidate) const {
0112     assert(candidate.hasUncalibratedSourceLink());
0113 
0114     const Acts::SourceLink& sourceLink = candidate.getUncalibratedSourceLink();
0115 
0116     for (const auto& sp : m_seed->sp()) {
0117       for (const auto& sl : sp->sourceLinks()) {
0118         if (sourceLink.get<IndexSourceLink>() == sl.get<IndexSourceLink>()) {
0119           return true;
0120         }
0121       }
0122     }
0123 
0124     return false;
0125   }
0126 };
0127 
0128 /// Source link indices of the bottom, middle, top measurements.
0129 /// In case of strip seeds only the first source link of the pair is used.
0130 using SeedIdentifier = std::array<Index, 3>;
0131 
0132 /// Build a seed identifier from a seed.
0133 ///
0134 /// @param seed The seed to build the identifier from.
0135 /// @return The seed identifier.
0136 SeedIdentifier makeSeedIdentifier(const SimSeed& seed) {
0137   SeedIdentifier result;
0138 
0139   for (const auto& [i, sp] : Acts::enumerate(seed.sp())) {
0140     const Acts::SourceLink& firstSourceLink = sp->sourceLinks().front();
0141     result.at(i) = firstSourceLink.get<IndexSourceLink>().index();
0142   }
0143 
0144   return result;
0145 }
0146 
0147 /// Visit all possible seed identifiers of a track.
0148 ///
0149 /// @param track The track to visit the seed identifiers of.
0150 /// @param visitor The visitor to call for each seed identifier.
0151 template <typename Visitor>
0152 void visitSeedIdentifiers(const TrackProxy& track, Visitor visitor) {
0153   // first we collect the source link indices of the track states
0154   std::vector<Index> sourceLinkIndices;
0155   sourceLinkIndices.reserve(track.nMeasurements());
0156   for (const auto& trackState : track.trackStatesReversed()) {
0157     if (!trackState.hasUncalibratedSourceLink()) {
0158       continue;
0159     }
0160     const Acts::SourceLink& sourceLink = trackState.getUncalibratedSourceLink();
0161     sourceLinkIndices.push_back(sourceLink.get<IndexSourceLink>().index());
0162   }
0163 
0164   // then we iterate over all possible triplets and form seed identifiers
0165   for (std::size_t i = 0; i < sourceLinkIndices.size(); ++i) {
0166     for (std::size_t j = i + 1; j < sourceLinkIndices.size(); ++j) {
0167       for (std::size_t k = j + 1; k < sourceLinkIndices.size(); ++k) {
0168         // Putting them into reverse order (k, j, i) to compensate for the
0169         // `trackStatesReversed` above.
0170         visitor({sourceLinkIndices.at(k), sourceLinkIndices.at(j),
0171                  sourceLinkIndices.at(i)});
0172       }
0173     }
0174   }
0175 }
0176 
0177 class BranchStopper {
0178  public:
0179   using Config =
0180       std::optional<std::variant<Acts::TrackSelector::Config,
0181                                  Acts::TrackSelector::EtaBinnedConfig>>;
0182 
0183   mutable std::atomic<std::size_t> m_nStoppedBranches{0};
0184 
0185   explicit BranchStopper(const Config& config) : m_config(config) {}
0186 
0187   bool operator()(
0188       const Acts::CombinatorialKalmanFilterTipState& tipState,
0189       Acts::VectorMultiTrajectory::TrackStateProxy& trackState) const {
0190     if (!m_config.has_value()) {
0191       return false;
0192     }
0193 
0194     const Acts::TrackSelector::Config* singleConfig = std::visit(
0195         [&](const auto& config) -> const Acts::TrackSelector::Config* {
0196           using T = std::decay_t<decltype(config)>;
0197           if constexpr (std::is_same_v<T, Acts::TrackSelector::Config>) {
0198             return &config;
0199           } else if constexpr (std::is_same_v<
0200                                    T, Acts::TrackSelector::EtaBinnedConfig>) {
0201             double theta = trackState.parameters()[Acts::eBoundTheta];
0202             double eta = -std::log(std::tan(0.5 * theta));
0203             return config.hasCuts(eta) ? &config.getCuts(eta) : nullptr;
0204           }
0205         },
0206         *m_config);
0207 
0208     if (singleConfig == nullptr) {
0209       ++m_nStoppedBranches;
0210       return true;
0211     }
0212 
0213     // Continue if the number of holes is below the maximum
0214     if (tipState.nHoles <= singleConfig->maxHoles) {
0215       return false;
0216     }
0217 
0218     // Continue if the number of outliers is below the maximum
0219     if (tipState.nOutliers <= singleConfig->maxOutliers) {
0220       return false;
0221     }
0222 
0223     // If there are not enough measurements but more holes than allowed we stop
0224     if (tipState.nMeasurements < singleConfig->minMeasurements) {
0225       ++m_nStoppedBranches;
0226       return true;
0227     }
0228 
0229     // Getting another measurement guarantees that the holes are in the middle
0230     // of the track
0231     if (trackState.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) {
0232       ++m_nStoppedBranches;
0233       return true;
0234     }
0235 
0236     // We cannot be sure if the holes are just at the end of the track so we
0237     // have to keep going
0238     return false;
0239   }
0240 
0241  private:
0242   Config m_config;
0243 };
0244 
0245 }  // namespace
0246 
0247 TrackFindingAlgorithm::TrackFindingAlgorithm(Config config,
0248                                              Acts::Logging::Level level)
0249     : IAlgorithm("TrackFindingAlgorithm", level), m_cfg(std::move(config)) {
0250   if (m_cfg.inputMeasurements.empty()) {
0251     throw std::invalid_argument("Missing measurements input collection");
0252   }
0253   if (m_cfg.inputSourceLinks.empty()) {
0254     throw std::invalid_argument("Missing source links input collection");
0255   }
0256   if (m_cfg.inputInitialTrackParameters.empty()) {
0257     throw std::invalid_argument(
0258         "Missing initial track parameters input collection");
0259   }
0260   if (m_cfg.outputTracks.empty()) {
0261     throw std::invalid_argument("Missing tracks output collection");
0262   }
0263 
0264   if (m_cfg.seedDeduplication && m_cfg.inputSeeds.empty()) {
0265     throw std::invalid_argument(
0266         "Missing seeds input collection. This is "
0267         "required for seed deduplication.");
0268   }
0269   if (m_cfg.stayOnSeed && m_cfg.inputSeeds.empty()) {
0270     throw std::invalid_argument(
0271         "Missing seeds input collection. This is "
0272         "required for staying on seed.");
0273   }
0274 
0275   if (m_cfg.trackSelectorCfg.has_value()) {
0276     m_trackSelector = std::visit(
0277         [](const auto& cfg) -> std::optional<Acts::TrackSelector> {
0278           return {cfg};
0279         },
0280         m_cfg.trackSelectorCfg.value());
0281   }
0282 
0283   m_inputMeasurements.initialize(m_cfg.inputMeasurements);
0284   m_inputSourceLinks.initialize(m_cfg.inputSourceLinks);
0285   m_inputInitialTrackParameters.initialize(m_cfg.inputInitialTrackParameters);
0286   m_inputSeeds.maybeInitialize(m_cfg.inputSeeds);
0287   m_outputTracks.initialize(m_cfg.outputTracks);
0288 }
0289 
0290 ProcessCode TrackFindingAlgorithm::execute(const AlgorithmContext& ctx) const {
0291   // Read input data
0292   const auto& measurements = m_inputMeasurements(ctx);
0293   const auto& sourceLinks = m_inputSourceLinks(ctx);
0294   const auto& initialParameters = m_inputInitialTrackParameters(ctx);
0295   const SimSeedContainer* seeds = nullptr;
0296 
0297   if (m_inputSeeds.isInitialized()) {
0298     seeds = &m_inputSeeds(ctx);
0299 
0300     if (initialParameters.size() != seeds->size()) {
0301       ACTS_ERROR("Number of initial parameters and seeds do not match. "
0302                  << initialParameters.size() << " != " << seeds->size());
0303     }
0304   }
0305 
0306   // Construct a perigee surface as the target surface
0307   auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(
0308       Acts::Vector3{0., 0., 0.});
0309 
0310   PassThroughCalibrator pcalibrator;
0311   MeasurementCalibratorAdapter calibrator(pcalibrator, measurements);
0312   Acts::GainMatrixUpdater kfUpdater;
0313   MeasurementSelector measSel{
0314       Acts::MeasurementSelector(m_cfg.measurementSelectorCfg)};
0315 
0316   using Extensions =
0317       Acts::CombinatorialKalmanFilterExtensions<Acts::VectorMultiTrajectory>;
0318 
0319   BranchStopper branchStopper(m_cfg.trackSelectorCfg);
0320 
0321   Extensions extensions;
0322   extensions.calibrator.connect<&MeasurementCalibratorAdapter::calibrate>(
0323       &calibrator);
0324   extensions.updater.connect<
0325       &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
0326       &kfUpdater);
0327   extensions.measurementSelector.connect<&MeasurementSelector::select>(
0328       &measSel);
0329   extensions.branchStopper.connect<&BranchStopper::operator()>(&branchStopper);
0330 
0331   IndexSourceLinkAccessor slAccessor;
0332   slAccessor.container = &sourceLinks;
0333   Acts::SourceLinkAccessorDelegate<IndexSourceLinkAccessor::Iterator>
0334       slAccessorDelegate;
0335   slAccessorDelegate.connect<&IndexSourceLinkAccessor::range>(&slAccessor);
0336 
0337   Acts::PropagatorPlainOptions firstPropOptions;
0338   firstPropOptions.maxSteps = m_cfg.maxSteps;
0339   firstPropOptions.direction = Acts::Direction::Forward;
0340 
0341   Acts::PropagatorPlainOptions secondPropOptions;
0342   secondPropOptions.maxSteps = m_cfg.maxSteps;
0343   secondPropOptions.direction = firstPropOptions.direction.invert();
0344 
0345   // Set the CombinatorialKalmanFilter options
0346   TrackFindingAlgorithm::TrackFinderOptions firstOptions(
0347       ctx.geoContext, ctx.magFieldContext, ctx.calibContext, slAccessorDelegate,
0348       extensions, firstPropOptions);
0349 
0350   TrackFindingAlgorithm::TrackFinderOptions secondOptions(
0351       ctx.geoContext, ctx.magFieldContext, ctx.calibContext, slAccessorDelegate,
0352       extensions, secondPropOptions);
0353   secondOptions.targetSurface = pSurface.get();
0354 
0355   Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator> extrapolator(
0356       Acts::EigenStepper<>(m_cfg.magneticField),
0357       Acts::Navigator({m_cfg.trackingGeometry},
0358                       logger().cloneWithSuffix("Navigator")),
0359       logger().cloneWithSuffix("Propagator"));
0360 
0361   Acts::PropagatorOptions<Acts::ActionList<Acts::MaterialInteractor>,
0362                           Acts::AbortList<Acts::EndOfWorldReached>>
0363       extrapolationOptions(ctx.geoContext, ctx.magFieldContext);
0364 
0365   // Perform the track finding for all initial parameters
0366   ACTS_DEBUG("Invoke track finding with " << initialParameters.size()
0367                                           << " seeds.");
0368 
0369   auto trackContainer = std::make_shared<Acts::VectorTrackContainer>();
0370   auto trackStateContainer = std::make_shared<Acts::VectorMultiTrajectory>();
0371 
0372   auto trackContainerTemp = std::make_shared<Acts::VectorTrackContainer>();
0373   auto trackStateContainerTemp =
0374       std::make_shared<Acts::VectorMultiTrajectory>();
0375 
0376   TrackContainer tracks(trackContainer, trackStateContainer);
0377   TrackContainer tracksTemp(trackContainerTemp, trackStateContainerTemp);
0378 
0379   tracks.addColumn<unsigned int>("trackGroup");
0380   tracksTemp.addColumn<unsigned int>("trackGroup");
0381   Acts::ProxyAccessor<unsigned int> seedNumber("trackGroup");
0382 
0383   unsigned int nSeed = 0;
0384 
0385   // A map indicating whether a seed has been discovered already
0386   std::unordered_map<SeedIdentifier, bool> discoveredSeeds;
0387 
0388   auto addTrack = [&](const TrackProxy& track) {
0389     ++m_nFoundTracks;
0390 
0391     // flag seeds which are covered by the track
0392     visitSeedIdentifiers(track, [&](const SeedIdentifier& seedIdentifier) {
0393       if (auto it = discoveredSeeds.find(seedIdentifier);
0394           it != discoveredSeeds.end()) {
0395         it->second = true;
0396       }
0397     });
0398 
0399     if (m_trackSelector.has_value() && !m_trackSelector->isValidTrack(track)) {
0400       return;
0401     }
0402 
0403     ++m_nSelectedTracks;
0404 
0405     auto destProxy = tracks.makeTrack();
0406     // make sure we copy track states!
0407     destProxy.copyFrom(track, true);
0408   };
0409 
0410   if (seeds != nullptr && m_cfg.seedDeduplication) {
0411     // Index the seeds for deduplication
0412     for (const auto& seed : *seeds) {
0413       SeedIdentifier seedIdentifier = makeSeedIdentifier(seed);
0414       discoveredSeeds.emplace(seedIdentifier, false);
0415     }
0416   }
0417 
0418   for (std::size_t iSeed = 0; iSeed < initialParameters.size(); ++iSeed) {
0419     m_nTotalSeeds++;
0420 
0421     if (seeds != nullptr) {
0422       const SimSeed& seed = seeds->at(iSeed);
0423 
0424       if (m_cfg.seedDeduplication) {
0425         SeedIdentifier seedIdentifier = makeSeedIdentifier(seed);
0426         // check if the seed has been discovered already
0427         if (auto it = discoveredSeeds.find(seedIdentifier);
0428             it != discoveredSeeds.end() && it->second) {
0429           m_nDeduplicatedSeeds++;
0430           ACTS_VERBOSE("Skipping seed " << iSeed << " due to deduplication.");
0431           continue;
0432         }
0433       }
0434 
0435       if (m_cfg.stayOnSeed) {
0436         measSel.setSeed(seed);
0437       }
0438     }
0439 
0440     // Clear trackContainerTemp and trackStateContainerTemp
0441     tracksTemp.clear();
0442 
0443     const Acts::BoundTrackParameters& firstInitialParameters =
0444         initialParameters.at(iSeed);
0445 
0446     auto firstResult =
0447         (*m_cfg.findTracks)(firstInitialParameters, firstOptions, tracksTemp);
0448     nSeed++;
0449 
0450     if (!firstResult.ok()) {
0451       m_nFailedSeeds++;
0452       ACTS_WARNING("Track finding failed for seed " << iSeed << " with error"
0453                                                     << firstResult.error());
0454       continue;
0455     }
0456 
0457     auto& firstTracksForSeed = firstResult.value();
0458     for (auto& firstTrack : firstTracksForSeed) {
0459       // TODO a copy of the track should not be necessary but is the safest way
0460       //      with the current EDM
0461       // TODO a lightweight copy without copying all the track state components
0462       //      might be a solution
0463       auto trackCandidate = tracksTemp.makeTrack();
0464       trackCandidate.copyFrom(firstTrack, true);
0465 
0466       auto firstSmoothingResult =
0467           Acts::smoothTrack(ctx.geoContext, trackCandidate, logger());
0468       if (!firstSmoothingResult.ok()) {
0469         m_nFailedSmoothing++;
0470         ACTS_ERROR("First smoothing for seed "
0471                    << iSeed << " and track " << firstTrack.index()
0472                    << " failed with error " << firstSmoothingResult.error());
0473         continue;
0474       }
0475 
0476       // number of second tracks found
0477       std::size_t nSecond = 0;
0478 
0479       // Set the seed number, this number decrease by 1 since the seed number
0480       // has already been updated
0481       seedNumber(trackCandidate) = nSeed - 1;
0482 
0483       if (m_cfg.twoWay) {
0484         std::optional<Acts::VectorMultiTrajectory::TrackStateProxy>
0485             firstMeasurement;
0486         for (auto trackState : trackCandidate.trackStatesReversed()) {
0487           bool isMeasurement = trackState.typeFlags().test(
0488               Acts::TrackStateFlag::MeasurementFlag);
0489           bool isOutlier =
0490               trackState.typeFlags().test(Acts::TrackStateFlag::OutlierFlag);
0491           // We are excluding non measurement states and outlier here. Those can
0492           // decrease resolution because only the smoothing corrected the very
0493           // first prediction as filtering is not possible.
0494           if (isMeasurement && !isOutlier) {
0495             firstMeasurement = trackState;
0496           }
0497         }
0498 
0499         if (firstMeasurement.has_value()) {
0500           Acts::BoundTrackParameters secondInitialParameters =
0501               trackCandidate.createParametersFromState(*firstMeasurement);
0502 
0503           auto secondResult = (*m_cfg.findTracks)(secondInitialParameters,
0504                                                   secondOptions, tracksTemp);
0505 
0506           if (!secondResult.ok()) {
0507             ACTS_WARNING("Second track finding failed for seed "
0508                          << iSeed << " with error" << secondResult.error());
0509           } else {
0510             auto firstState =
0511                 *std::next(trackCandidate.trackStatesReversed().begin(),
0512                            trackCandidate.nTrackStates() - 1);
0513             assert(firstState.previous() == Acts::kTrackIndexInvalid);
0514 
0515             auto& secondTracksForSeed = secondResult.value();
0516             for (auto& secondTrack : secondTracksForSeed) {
0517               if (secondTrack.nTrackStates() < 2) {
0518                 continue;
0519               }
0520 
0521               // TODO a copy of the track should not be necessary but is the
0522               //      safest way with the current EDM
0523               // TODO a lightweight copy without copying all the track state
0524               //      components might be a solution
0525               auto secondTrackCopy = tracksTemp.makeTrack();
0526               secondTrackCopy.copyFrom(secondTrack, true);
0527 
0528               // Note that this is only valid if there are no branches
0529               // We disallow this by breaking this look after a second track was
0530               // processed
0531               secondTrackCopy.reverseTrackStates(true);
0532 
0533               firstState.previous() =
0534                   (*std::next(secondTrackCopy.trackStatesReversed().begin()))
0535                       .index();
0536 
0537               Acts::calculateTrackQuantities(trackCandidate);
0538 
0539               // TODO This extrapolation should not be necessary
0540               // TODO The CKF is targeting this surface and should communicate
0541               //      the resulting parameters
0542               // TODO Removing this requires changes in the core CKF
0543               //      implementation
0544               auto secondExtrapolationResult =
0545                   Acts::extrapolateTrackToReferenceSurface(
0546                       trackCandidate, *pSurface, extrapolator,
0547                       extrapolationOptions, m_cfg.extrapolationStrategy,
0548                       logger());
0549               if (!secondExtrapolationResult.ok()) {
0550                 m_nFailedExtrapolation++;
0551                 ACTS_ERROR("Second extrapolation for seed "
0552                            << iSeed << " and track " << secondTrack.index()
0553                            << " failed with error "
0554                            << secondExtrapolationResult.error());
0555                 continue;
0556               }
0557 
0558               addTrack(trackCandidate);
0559 
0560               ++nSecond;
0561             }
0562 
0563             // restore `trackCandidate` to its original state in case we need it
0564             // again
0565             firstState.previous() = Acts::kTrackIndexInvalid;
0566             Acts::calculateTrackQuantities(trackCandidate);
0567           }
0568         }
0569       }
0570 
0571       // if no second track was found, we will use only the first track
0572       if (nSecond == 0) {
0573         auto firstExtrapolationResult =
0574             Acts::extrapolateTrackToReferenceSurface(
0575                 trackCandidate, *pSurface, extrapolator, extrapolationOptions,
0576                 m_cfg.extrapolationStrategy, logger());
0577         if (!firstExtrapolationResult.ok()) {
0578           m_nFailedExtrapolation++;
0579           ACTS_ERROR("Extrapolation for seed "
0580                      << iSeed << " and track " << firstTrack.index()
0581                      << " failed with error "
0582                      << firstExtrapolationResult.error());
0583           continue;
0584         }
0585 
0586         addTrack(trackCandidate);
0587       }
0588     }
0589   }
0590 
0591   // Compute shared hits from all the reconstructed tracks
0592   if (m_cfg.computeSharedHits) {
0593     computeSharedHits(sourceLinks, tracks);
0594   }
0595 
0596   ACTS_DEBUG("Finalized track finding with " << tracks.size()
0597                                              << " track candidates.");
0598 
0599   m_nStoppedBranches += branchStopper.m_nStoppedBranches;
0600 
0601   m_memoryStatistics.local().hist +=
0602       tracks.trackStateContainer().statistics().hist;
0603 
0604   auto constTrackStateContainer =
0605       std::make_shared<Acts::ConstVectorMultiTrajectory>(
0606           std::move(*trackStateContainer));
0607 
0608   auto constTrackContainer = std::make_shared<Acts::ConstVectorTrackContainer>(
0609       std::move(*trackContainer));
0610 
0611   ConstTrackContainer constTracks{constTrackContainer,
0612                                   constTrackStateContainer};
0613 
0614   m_outputTracks(ctx, std::move(constTracks));
0615   return ProcessCode::SUCCESS;
0616 }
0617 
0618 ProcessCode TrackFindingAlgorithm::finalize() {
0619   ACTS_INFO("TrackFindingAlgorithm statistics:");
0620   ACTS_INFO("- total seeds: " << m_nTotalSeeds);
0621   ACTS_INFO("- deduplicated seeds: " << m_nDeduplicatedSeeds);
0622   ACTS_INFO("- failed seeds: " << m_nFailedSeeds);
0623   ACTS_INFO("- failed smoothing: " << m_nFailedSmoothing);
0624   ACTS_INFO("- failed extrapolation: " << m_nFailedExtrapolation);
0625   ACTS_INFO("- failure ratio seeds: " << static_cast<double>(m_nFailedSeeds) /
0626                                              m_nTotalSeeds);
0627   ACTS_INFO("- found tracks: " << m_nFoundTracks);
0628   ACTS_INFO("- selected tracks: " << m_nSelectedTracks);
0629   ACTS_INFO("- stopped branches: " << m_nStoppedBranches);
0630 
0631   auto memoryStatistics =
0632       m_memoryStatistics.combine([](const auto& a, const auto& b) {
0633         Acts::VectorMultiTrajectory::Statistics c;
0634         c.hist = a.hist + b.hist;
0635         return c;
0636       });
0637   std::stringstream ss;
0638   memoryStatistics.toStream(ss);
0639   ACTS_DEBUG("Track State memory statistics (averaged):\n" << ss.str());
0640   return ProcessCode::SUCCESS;
0641 }
0642 
0643 }  // namespace ActsExamples