Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-13 08:19:28

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 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 https://mozilla.org/MPL/2.0/.
0008 
0009 #include "Acts/Vertexing/IterativeVertexFinder.hpp"
0010 
0011 #include "Acts/Surfaces/PerigeeSurface.hpp"
0012 #include "Acts/Vertexing/VertexingError.hpp"
0013 
0014 Acts::IterativeVertexFinder::IterativeVertexFinder(
0015     Config cfg, std::unique_ptr<const Logger> logger)
0016     : m_cfg(std::move(cfg)), m_logger(std::move(logger)) {
0017   if (!m_cfg.extractParameters.connected()) {
0018     throw std::invalid_argument(
0019         "IterativeVertexFinder: "
0020         "No function to extract parameters "
0021         "provided.");
0022   }
0023 
0024   if (!m_cfg.trackLinearizer.connected()) {
0025     throw std::invalid_argument(
0026         "IterativeVertexFinder: "
0027         "No track linearizer provided.");
0028   }
0029 
0030   if (!m_cfg.seedFinder) {
0031     throw std::invalid_argument(
0032         "IterativeVertexFinder: "
0033         "No seed finder provided.");
0034   }
0035 
0036   if (!m_cfg.field) {
0037     throw std::invalid_argument(
0038         "IterativeVertexFinder: "
0039         "No magnetic field provider provided.");
0040   }
0041 }
0042 
0043 auto Acts::IterativeVertexFinder::find(
0044     const std::vector<InputTrack>& trackVector,
0045     const VertexingOptions& vertexingOptions,
0046     IVertexFinder::State& anyState) const -> Result<std::vector<Vertex>> {
0047   auto& state = anyState.as<State>();
0048   // Original tracks
0049   const std::vector<InputTrack>& origTracks = trackVector;
0050   // Tracks for seeding
0051   std::vector<InputTrack> seedTracks = trackVector;
0052 
0053   // List of vertices to be filled below
0054   std::vector<Vertex> vertexCollection;
0055 
0056   int nInterations = 0;
0057   // begin iterating
0058   while (seedTracks.size() > 1 && nInterations < m_cfg.maxVertices) {
0059     /// Do seeding
0060     auto seedRes = getVertexSeed(state, seedTracks, vertexingOptions);
0061 
0062     if (!seedRes.ok()) {
0063       return seedRes.error();
0064     }
0065     const auto& seedOptional = *seedRes;
0066 
0067     if (!seedOptional.has_value()) {
0068       ACTS_DEBUG("No more seed found. Break and stop primary vertex finding.");
0069       break;
0070     }
0071     const auto& seedVertex = *seedOptional;
0072 
0073     /// End seeding
0074     /// Now take only tracks compatible with current seed
0075     // Tracks used for the fit in this iteration
0076     std::vector<InputTrack> tracksToFit;
0077     std::vector<InputTrack> tracksToFitSplitVertex;
0078 
0079     // Fill vector with tracks to fit, only compatible with seed:
0080     auto res = fillTracksToFit(seedTracks, seedVertex, tracksToFit,
0081                                tracksToFitSplitVertex, vertexingOptions, state);
0082 
0083     if (!res.ok()) {
0084       return res.error();
0085     }
0086 
0087     ACTS_DEBUG("Number of tracks used for fit: " << tracksToFit.size());
0088 
0089     /// Begin vertex fit
0090     Vertex currentVertex;
0091     Vertex currentSplitVertex;
0092 
0093     if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
0094       auto fitResult = m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions,
0095                                               state.fieldCache);
0096       if (fitResult.ok()) {
0097         currentVertex = std::move(*fitResult);
0098       } else {
0099         return fitResult.error();
0100       }
0101     } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
0102       auto fitResult = m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions,
0103                                               state.fieldCache);
0104       if (fitResult.ok()) {
0105         currentVertex = std::move(*fitResult);
0106       } else {
0107         return fitResult.error();
0108       }
0109     }
0110     if (m_cfg.createSplitVertices && tracksToFitSplitVertex.size() > 1) {
0111       auto fitResult = m_cfg.vertexFitter.fit(
0112           tracksToFitSplitVertex, vertexingOptions, state.fieldCache);
0113       if (fitResult.ok()) {
0114         currentSplitVertex = std::move(*fitResult);
0115       } else {
0116         return fitResult.error();
0117       }
0118     }
0119     /// End vertex fit
0120     ACTS_DEBUG("Vertex position after fit: "
0121                << currentVertex.fullPosition().transpose());
0122 
0123     // Number degrees of freedom
0124     double ndf = currentVertex.fitQuality().second;
0125     double ndfSplitVertex = currentSplitVertex.fitQuality().second;
0126 
0127     // Number of significant tracks
0128     int nTracksAtVertex = countSignificantTracks(currentVertex);
0129     int nTracksAtSplitVertex = countSignificantTracks(currentSplitVertex);
0130 
0131     bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0132                           nTracksAtVertex >= 2) ||
0133                          (vertexingOptions.useConstraintInFit && ndf > 3 &&
0134                           nTracksAtVertex >= 2));
0135 
0136     if (!isGoodVertex) {
0137       removeTracks(tracksToFit, seedTracks);
0138     } else {
0139       if (m_cfg.reassignTracksAfterFirstFit && (!m_cfg.createSplitVertices)) {
0140         // vertex is good vertex here
0141         // but add tracks which may have been missed
0142 
0143         auto result = reassignTracksToNewVertex(
0144             vertexCollection, currentVertex, tracksToFit, seedTracks,
0145             origTracks, vertexingOptions, state);
0146         if (!result.ok()) {
0147           return result.error();
0148         }
0149         isGoodVertex = *result;
0150 
0151       }  // end reassignTracksAfterFirstFit case
0152          // still good vertex? might have changed in the meanwhile
0153       if (isGoodVertex) {
0154         Result<void> removeRes = removeUsedCompatibleTracks(
0155             currentVertex, tracksToFit, seedTracks, vertexingOptions, state);
0156         if (!removeRes.ok()) {
0157           return removeRes.error();
0158         }
0159 
0160         ACTS_DEBUG(
0161             "Number of seed tracks after removal of compatible tracks "
0162             "and outliers: "
0163             << seedTracks.size());
0164       }
0165     }  // end case if good vertex
0166 
0167     // now splitvertex
0168     bool isGoodSplitVertex = false;
0169     if (m_cfg.createSplitVertices) {
0170       isGoodSplitVertex = (ndfSplitVertex > 0 && nTracksAtSplitVertex >= 2);
0171 
0172       if (!isGoodSplitVertex) {
0173         removeTracks(tracksToFitSplitVertex, seedTracks);
0174       } else {
0175         Result<void> removeRes = removeUsedCompatibleTracks(
0176             currentSplitVertex, tracksToFitSplitVertex, seedTracks,
0177             vertexingOptions, state);
0178         if (!removeRes.ok()) {
0179           return removeRes.error();
0180         }
0181       }
0182     }
0183     // Now fill vertex collection with vertex
0184     if (isGoodVertex) {
0185       vertexCollection.push_back(currentVertex);
0186     }
0187     if (isGoodSplitVertex && m_cfg.createSplitVertices) {
0188       vertexCollection.push_back(currentSplitVertex);
0189     }
0190 
0191     nInterations++;
0192   }  // end while loop
0193 
0194   return vertexCollection;
0195 }
0196 
0197 auto Acts::IterativeVertexFinder::getVertexSeed(
0198     State& state, const std::vector<InputTrack>& seedTracks,
0199     const VertexingOptions& vertexingOptions) const
0200     -> Result<std::optional<Vertex>> {
0201   auto finderState = m_cfg.seedFinder->makeState(state.magContext);
0202   auto res = m_cfg.seedFinder->find(seedTracks, vertexingOptions, finderState);
0203 
0204   if (!res.ok()) {
0205     ACTS_ERROR("Internal seeding error. Number of input tracks: "
0206                << seedTracks.size());
0207     return VertexingError::SeedingError;
0208   }
0209   const auto& seedVector = *res;
0210 
0211   ACTS_DEBUG("Found " << seedVector.size() << " seeds");
0212 
0213   if (seedVector.empty()) {
0214     return std::nullopt;
0215   }
0216   const Vertex& seedVertex = seedVector.back();
0217 
0218   ACTS_DEBUG("Use " << seedTracks.size() << " tracks for vertex seed finding.");
0219   ACTS_DEBUG(
0220       "Found seed at position: " << seedVertex.fullPosition().transpose());
0221 
0222   return seedVertex;
0223 }
0224 
0225 inline void Acts::IterativeVertexFinder::removeTracks(
0226     const std::vector<InputTrack>& tracksToRemove,
0227     std::vector<InputTrack>& seedTracks) const {
0228   for (const auto& trk : tracksToRemove) {
0229     const BoundTrackParameters& params = m_cfg.extractParameters(trk);
0230     // Find track in seedTracks
0231     auto foundIter =
0232         std::ranges::find_if(seedTracks, [&params, this](const auto seedTrk) {
0233           return params == m_cfg.extractParameters(seedTrk);
0234         });
0235     if (foundIter != seedTracks.end()) {
0236       // Remove track from seed tracks
0237       seedTracks.erase(foundIter);
0238     } else {
0239       ACTS_WARNING("Track to be removed not found in seed tracks.");
0240     }
0241   }
0242 }
0243 
0244 Acts::Result<double> Acts::IterativeVertexFinder::getCompatibility(
0245     const BoundTrackParameters& params, const Vertex& vertex,
0246     const Surface& perigeeSurface, const VertexingOptions& vertexingOptions,
0247     State& state) const {
0248   // Linearize track
0249   auto result =
0250       m_cfg.trackLinearizer(params, vertex.fullPosition()[3], perigeeSurface,
0251                             vertexingOptions.geoContext,
0252                             vertexingOptions.magFieldContext, state.fieldCache);
0253   if (!result.ok()) {
0254     return result.error();
0255   }
0256 
0257   auto linTrack = std::move(*result);
0258 
0259   // Calculate reduced weight
0260   SquareMatrix2 weightReduced =
0261       linTrack.covarianceAtPCA.template block<2, 2>(0, 0);
0262 
0263   SquareMatrix2 errorVertexReduced =
0264       (linTrack.positionJacobian *
0265        (vertex.fullCovariance() * linTrack.positionJacobian.transpose()))
0266           .template block<2, 2>(0, 0);
0267   weightReduced += errorVertexReduced;
0268   weightReduced = weightReduced.inverse().eval();
0269 
0270   // Calculate compatibility / chi2
0271   Vector2 trackParameters2D =
0272       linTrack.parametersAtPCA.template block<2, 1>(0, 0);
0273   double compatibility =
0274       trackParameters2D.dot(weightReduced * trackParameters2D);
0275 
0276   return compatibility;
0277 }
0278 
0279 Acts::Result<void> Acts::IterativeVertexFinder::removeUsedCompatibleTracks(
0280     Vertex& vertex, std::vector<InputTrack>& tracksToFit,
0281     std::vector<InputTrack>& seedTracks,
0282     const VertexingOptions& vertexingOptions, State& state) const {
0283   std::vector<TrackAtVertex> tracksAtVertex = vertex.tracks();
0284 
0285   for (const auto& trackAtVtx : tracksAtVertex) {
0286     // Check compatibility
0287     if (trackAtVtx.trackWeight < m_cfg.cutOffTrackWeight) {
0288       // Do not remove track here, since it is not compatible with the vertex
0289       continue;
0290     }
0291     // Find and remove track from seedTracks
0292     auto foundSeedIter =
0293         std::ranges::find(seedTracks, trackAtVtx.originalParams);
0294     if (foundSeedIter != seedTracks.end()) {
0295       seedTracks.erase(foundSeedIter);
0296     } else {
0297       ACTS_WARNING("Track trackAtVtx not found in seedTracks!");
0298     }
0299 
0300     // Find and remove track from tracksToFit
0301     auto foundFitIter =
0302         std::ranges::find(tracksToFit, trackAtVtx.originalParams);
0303     if (foundFitIter != tracksToFit.end()) {
0304       tracksToFit.erase(foundFitIter);
0305     } else {
0306       ACTS_WARNING("Track trackAtVtx not found in tracksToFit!");
0307     }
0308   }  // end iteration over tracksAtVertex
0309 
0310   ACTS_DEBUG("After removal of tracks belonging to vertex, "
0311              << seedTracks.size() << " seed tracks left.");
0312 
0313   // Now start considering outliers
0314   // tracksToFit that are left here were below
0315   // m_cfg.cutOffTrackWeight threshold and are hence outliers
0316   ACTS_DEBUG("Number of outliers: " << tracksToFit.size());
0317 
0318   const std::shared_ptr<PerigeeSurface> vertexPerigeeSurface =
0319       Surface::makeShared<PerigeeSurface>(
0320           VectorHelpers::position(vertex.fullPosition()));
0321 
0322   for (const auto& trk : tracksToFit) {
0323     // calculate chi2 w.r.t. last fitted vertex
0324     auto result =
0325         getCompatibility(m_cfg.extractParameters(trk), vertex,
0326                          *vertexPerigeeSurface, vertexingOptions, state);
0327 
0328     if (!result.ok()) {
0329       return result.error();
0330     }
0331 
0332     double chi2 = *result;
0333 
0334     // check if sufficiently compatible with last fitted vertex
0335     // (quite loose constraint)
0336     if (chi2 < m_cfg.maximumChi2cutForSeeding) {
0337       auto foundIter = std::ranges::find(seedTracks, trk);
0338       if (foundIter != seedTracks.end()) {
0339         // Remove track from seed tracks
0340         seedTracks.erase(foundIter);
0341       }
0342 
0343     } else {
0344       // Track not compatible with vertex
0345       // Remove track from current vertex
0346       auto foundIter =
0347           std::ranges::find_if(tracksAtVertex, [&trk](const auto& trkAtVtx) {
0348             return trk == trkAtVtx.originalParams;
0349           });
0350       if (foundIter != tracksAtVertex.end()) {
0351         // Remove track from seed tracks
0352         tracksAtVertex.erase(foundIter);
0353       }
0354     }
0355   }
0356 
0357   // set updated (possibly with removed outliers) tracksAtVertex to vertex
0358   vertex.setTracksAtVertex(tracksAtVertex);
0359 
0360   return {};
0361 }
0362 
0363 Acts::Result<void> Acts::IterativeVertexFinder::fillTracksToFit(
0364     const std::vector<InputTrack>& seedTracks, const Vertex& seedVertex,
0365     std::vector<InputTrack>& tracksToFitOut,
0366     std::vector<InputTrack>& tracksToFitSplitVertexOut,
0367     const VertexingOptions& vertexingOptions, State& state) const {
0368   int numberOfTracks = seedTracks.size();
0369 
0370   // Count how many tracks are used for fit
0371   int count = 0;
0372   // Fill tracksToFit vector with tracks compatible with seed
0373   for (const auto& sTrack : seedTracks) {
0374     // If there are only few tracks left, add them to fit regardless of their
0375     // position:
0376     if (numberOfTracks <= 2) {
0377       tracksToFitOut.push_back(sTrack);
0378       ++count;
0379     } else if (numberOfTracks <= 4 && !m_cfg.createSplitVertices) {
0380       tracksToFitOut.push_back(sTrack);
0381       ++count;
0382     } else if (numberOfTracks <= 4 * m_cfg.splitVerticesTrkInvFraction &&
0383                m_cfg.createSplitVertices) {
0384       if (count % m_cfg.splitVerticesTrkInvFraction != 0) {
0385         tracksToFitOut.push_back(sTrack);
0386         ++count;
0387       } else {
0388         tracksToFitSplitVertexOut.push_back(sTrack);
0389         ++count;
0390       }
0391     }
0392     // If a large amount of tracks is available, we check their compatibility
0393     // with the vertex before adding them to the fit:
0394     else {
0395       const BoundTrackParameters& sTrackParams =
0396           m_cfg.extractParameters(sTrack);
0397       auto distanceRes = m_cfg.ipEst.calculateDistance(
0398           vertexingOptions.geoContext, sTrackParams, seedVertex.position(),
0399           state.ipState);
0400       if (!distanceRes.ok()) {
0401         return distanceRes.error();
0402       }
0403 
0404       if (!sTrackParams.covariance()) {
0405         return VertexingError::NoCovariance;
0406       }
0407 
0408       // sqrt(sigma(d0)^2+sigma(z0)^2), where sigma(d0)^2 is the variance of d0
0409       double hypotVariance =
0410           std::sqrt((*(sTrackParams.covariance()))(eBoundLoc0, eBoundLoc0) +
0411                     (*(sTrackParams.covariance()))(eBoundLoc1, eBoundLoc1));
0412 
0413       if (hypotVariance == 0.) {
0414         ACTS_WARNING(
0415             "Track impact parameter covariances are zero. Track was not "
0416             "assigned to vertex.");
0417         continue;
0418       }
0419 
0420       if (*distanceRes / hypotVariance < m_cfg.significanceCutSeeding) {
0421         if (!m_cfg.createSplitVertices ||
0422             count % m_cfg.splitVerticesTrkInvFraction != 0) {
0423           tracksToFitOut.push_back(sTrack);
0424           ++count;
0425         } else {
0426           tracksToFitSplitVertexOut.push_back(sTrack);
0427           ++count;
0428         }
0429       }
0430     }
0431   }
0432   return {};
0433 }
0434 
0435 Acts::Result<bool> Acts::IterativeVertexFinder::reassignTracksToNewVertex(
0436     std::vector<Vertex>& vertexCollection, Vertex& currentVertex,
0437     std::vector<InputTrack>& tracksToFit, std::vector<InputTrack>& seedTracks,
0438     const std::vector<InputTrack>& /* origTracks */,
0439     const VertexingOptions& vertexingOptions, State& state) const {
0440   int numberOfAddedTracks = 0;
0441 
0442   const std::shared_ptr<PerigeeSurface> currentVertexPerigeeSurface =
0443       Surface::makeShared<PerigeeSurface>(
0444           VectorHelpers::position(currentVertex.fullPosition()));
0445 
0446   // iterate over all vertices and check if tracks need to be reassigned
0447   // to new (current) vertex
0448   for (auto& vertexIt : vertexCollection) {
0449     // tracks at vertexIt
0450     std::vector<TrackAtVertex> tracksAtVertex = vertexIt.tracks();
0451     auto tracksBegin = tracksAtVertex.begin();
0452     auto tracksEnd = tracksAtVertex.end();
0453 
0454     const std::shared_ptr<PerigeeSurface> vertexItPerigeeSurface =
0455         Surface::makeShared<PerigeeSurface>(
0456             VectorHelpers::position(vertexIt.fullPosition()));
0457 
0458     for (auto tracksIter = tracksBegin; tracksIter != tracksEnd;) {
0459       // consider only tracks that are not too tightly assigned to other
0460       // vertex
0461       if (tracksIter->trackWeight > m_cfg.cutOffTrackWeightReassign) {
0462         tracksIter++;
0463         continue;
0464       }
0465       // use original perigee parameters
0466       BoundTrackParameters origParams =
0467           m_cfg.extractParameters(tracksIter->originalParams);
0468 
0469       // compute compatibility
0470       auto resultNew = getCompatibility(origParams, currentVertex,
0471                                         *currentVertexPerigeeSurface,
0472                                         vertexingOptions, state);
0473       if (!resultNew.ok()) {
0474         return Result<bool>::failure(resultNew.error());
0475       }
0476       double chi2NewVtx = *resultNew;
0477 
0478       auto resultOld =
0479           getCompatibility(origParams, vertexIt, *vertexItPerigeeSurface,
0480                            vertexingOptions, state);
0481       if (!resultOld.ok()) {
0482         return Result<bool>::failure(resultOld.error());
0483       }
0484       double chi2OldVtx = *resultOld;
0485 
0486       ACTS_DEBUG("Compatibility to new vs old vertex: " << chi2NewVtx << " vs "
0487                                                         << chi2OldVtx);
0488 
0489       if (chi2NewVtx < chi2OldVtx) {
0490         tracksToFit.push_back(tracksIter->originalParams);
0491         // origTrack was already deleted from seedTracks previously
0492         // (when assigned to old vertex)
0493         // add it now back to seedTracks to be able to consistently
0494         // delete it later
0495         // when all tracks used to fit current vertex are deleted
0496         seedTracks.push_back(tracksIter->originalParams);
0497         // seedTracks.push_back(*std::ranges::find_if(
0498         //     origTracks,
0499         //     [&origParams, this](auto origTrack) {
0500         //       return origParams == m_extractParameters(*origTrack);
0501         //     }));
0502 
0503         numberOfAddedTracks += 1;
0504 
0505         // remove track from old vertex
0506         tracksIter = tracksAtVertex.erase(tracksIter);
0507         tracksBegin = tracksAtVertex.begin();
0508         tracksEnd = tracksAtVertex.end();
0509 
0510       }  // end chi2NewVtx < chi2OldVtx
0511 
0512       else {
0513         // go and check next track
0514         ++tracksIter;
0515       }
0516     }  // end loop over tracks at old vertexIt
0517 
0518     vertexIt.setTracksAtVertex(tracksAtVertex);
0519   }  // end loop over all vertices
0520 
0521   ACTS_DEBUG("Added " << numberOfAddedTracks
0522                       << " tracks from old (other) vertices for new fit");
0523 
0524   // override current vertex with new fit
0525   // set first to default vertex to be able to check if still good vertex
0526   // later
0527   currentVertex = Vertex();
0528   if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
0529     auto fitResult =
0530         m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0531     if (fitResult.ok()) {
0532       currentVertex = std::move(*fitResult);
0533     } else {
0534       return Result<bool>::success(false);
0535     }
0536   } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
0537     auto fitResult =
0538         m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0539     if (fitResult.ok()) {
0540       currentVertex = std::move(*fitResult);
0541     } else {
0542       return Result<bool>::success(false);
0543     }
0544   }
0545 
0546   // Number degrees of freedom
0547   double ndf = currentVertex.fitQuality().second;
0548 
0549   // Number of significant tracks
0550   int nTracksAtVertex = countSignificantTracks(currentVertex);
0551 
0552   bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0553                         nTracksAtVertex >= 2) ||
0554                        (vertexingOptions.useConstraintInFit && ndf > 3 &&
0555                         nTracksAtVertex >= 2));
0556 
0557   if (!isGoodVertex) {
0558     removeTracks(tracksToFit, seedTracks);
0559 
0560     ACTS_DEBUG("Going to new iteration with "
0561                << seedTracks.size() << "seed tracks after BAD vertex.");
0562   }
0563 
0564   return Result<bool>::success(isGoodVertex);
0565 }
0566 
0567 int Acts::IterativeVertexFinder::countSignificantTracks(
0568     const Vertex& vtx) const {
0569   return std::count_if(vtx.tracks().begin(), vtx.tracks().end(),
0570                        [this](const TrackAtVertex& trk) {
0571                          return trk.trackWeight > m_cfg.cutOffTrackWeight;
0572                        });
0573 }