Back to home page

sPhenix code displayed by LXR

 
 

    


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

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2019-2023 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 "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         removeUsedCompatibleTracks(currentVertex, tracksToFit, seedTracks,
0155                                    vertexingOptions, state);
0156 
0157         ACTS_DEBUG(
0158             "Number of seed tracks after removal of compatible tracks "
0159             "and outliers: "
0160             << seedTracks.size());
0161       }
0162     }  // end case if good vertex
0163 
0164     // now splitvertex
0165     bool isGoodSplitVertex = false;
0166     if (m_cfg.createSplitVertices) {
0167       isGoodSplitVertex = (ndfSplitVertex > 0 && nTracksAtSplitVertex >= 2);
0168 
0169       if (!isGoodSplitVertex) {
0170         removeTracks(tracksToFitSplitVertex, seedTracks);
0171       } else {
0172         removeUsedCompatibleTracks(currentSplitVertex, tracksToFitSplitVertex,
0173                                    seedTracks, vertexingOptions, state);
0174       }
0175     }
0176     // Now fill vertex collection with vertex
0177     if (isGoodVertex) {
0178       vertexCollection.push_back(currentVertex);
0179     }
0180     if (isGoodSplitVertex && m_cfg.createSplitVertices) {
0181       vertexCollection.push_back(currentSplitVertex);
0182     }
0183 
0184     nInterations++;
0185   }  // end while loop
0186 
0187   return vertexCollection;
0188 }
0189 
0190 auto Acts::IterativeVertexFinder::getVertexSeed(
0191     State& state, const std::vector<InputTrack>& seedTracks,
0192     const VertexingOptions& vertexingOptions) const
0193     -> Result<std::optional<Vertex>> {
0194   auto finderState = m_cfg.seedFinder->makeState(state.magContext);
0195   auto res = m_cfg.seedFinder->find(seedTracks, vertexingOptions, finderState);
0196 
0197   if (!res.ok()) {
0198     ACTS_ERROR("Internal seeding error. Number of input tracks: "
0199                << seedTracks.size());
0200     return VertexingError::SeedingError;
0201   }
0202   const auto& seedVector = *res;
0203 
0204   ACTS_DEBUG("Found " << seedVector.size() << " seeds");
0205 
0206   if (seedVector.empty()) {
0207     return std::nullopt;
0208   }
0209   const Vertex& seedVertex = seedVector.back();
0210 
0211   ACTS_DEBUG("Use " << seedTracks.size() << " tracks for vertex seed finding.")
0212   ACTS_DEBUG(
0213       "Found seed at position: " << seedVertex.fullPosition().transpose());
0214 
0215   return seedVertex;
0216 }
0217 
0218 inline void Acts::IterativeVertexFinder::removeTracks(
0219     const std::vector<InputTrack>& tracksToRemove,
0220     std::vector<InputTrack>& seedTracks) const {
0221   for (const auto& trk : tracksToRemove) {
0222     const BoundTrackParameters& params = m_cfg.extractParameters(trk);
0223     // Find track in seedTracks
0224     auto foundIter =
0225         std::find_if(seedTracks.begin(), seedTracks.end(),
0226                      [&params, this](const auto seedTrk) {
0227                        return params == m_cfg.extractParameters(seedTrk);
0228                      });
0229     if (foundIter != seedTracks.end()) {
0230       // Remove track from seed tracks
0231       seedTracks.erase(foundIter);
0232     } else {
0233       ACTS_WARNING("Track to be removed not found in seed tracks.")
0234     }
0235   }
0236 }
0237 
0238 Acts::Result<double> Acts::IterativeVertexFinder::getCompatibility(
0239     const BoundTrackParameters& params, const Vertex& vertex,
0240     const Surface& perigeeSurface, const VertexingOptions& vertexingOptions,
0241     State& state) const {
0242   // Linearize track
0243   auto result =
0244       m_cfg.trackLinearizer(params, vertex.fullPosition()[3], perigeeSurface,
0245                             vertexingOptions.geoContext,
0246                             vertexingOptions.magFieldContext, state.fieldCache);
0247   if (!result.ok()) {
0248     return result.error();
0249   }
0250 
0251   auto linTrack = std::move(*result);
0252 
0253   // Calculate reduced weight
0254   SquareMatrix2 weightReduced =
0255       linTrack.covarianceAtPCA.template block<2, 2>(0, 0);
0256 
0257   SquareMatrix2 errorVertexReduced =
0258       (linTrack.positionJacobian *
0259        (vertex.fullCovariance() * linTrack.positionJacobian.transpose()))
0260           .template block<2, 2>(0, 0);
0261   weightReduced += errorVertexReduced;
0262   weightReduced = weightReduced.inverse().eval();
0263 
0264   // Calculate compatibility / chi2
0265   Vector2 trackParameters2D =
0266       linTrack.parametersAtPCA.template block<2, 1>(0, 0);
0267   double compatibility =
0268       trackParameters2D.dot(weightReduced * trackParameters2D);
0269 
0270   return compatibility;
0271 }
0272 
0273 Acts::Result<void> Acts::IterativeVertexFinder::removeUsedCompatibleTracks(
0274     Vertex& vertex, std::vector<InputTrack>& tracksToFit,
0275     std::vector<InputTrack>& seedTracks,
0276     const VertexingOptions& vertexingOptions, State& state) const {
0277   std::vector<TrackAtVertex> tracksAtVertex = vertex.tracks();
0278 
0279   for (const auto& trackAtVtx : tracksAtVertex) {
0280     // Check compatibility
0281     if (trackAtVtx.trackWeight < m_cfg.cutOffTrackWeight) {
0282       // Do not remove track here, since it is not compatible with the vertex
0283       continue;
0284     }
0285     // Find and remove track from seedTracks
0286     auto foundSeedIter =
0287         std::find_if(seedTracks.begin(), seedTracks.end(),
0288                      [&trackAtVtx](const auto& seedTrk) {
0289                        return trackAtVtx.originalParams == seedTrk;
0290                      });
0291     if (foundSeedIter != seedTracks.end()) {
0292       seedTracks.erase(foundSeedIter);
0293     } else {
0294       ACTS_WARNING("Track trackAtVtx not found in seedTracks!");
0295     }
0296 
0297     // Find and remove track from tracksToFit
0298     auto foundFitIter =
0299         std::find_if(tracksToFit.begin(), tracksToFit.end(),
0300                      [&trackAtVtx](const auto& fitTrk) {
0301                        return trackAtVtx.originalParams == fitTrk;
0302                      });
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 =
0338           std::find_if(seedTracks.begin(), seedTracks.end(),
0339                        [&trk](const auto& seedTrk) { return trk == seedTrk; });
0340       if (foundIter != seedTracks.end()) {
0341         // Remove track from seed tracks
0342         seedTracks.erase(foundIter);
0343       }
0344 
0345     } else {
0346       // Track not compatible with vertex
0347       // Remove track from current vertex
0348       auto foundIter = std::find_if(
0349           tracksAtVertex.begin(), tracksAtVertex.end(),
0350           [&trk](auto trkAtVtx) { return trk == trkAtVtx.originalParams; });
0351       if (foundIter != tracksAtVertex.end()) {
0352         // Remove track from seed tracks
0353         tracksAtVertex.erase(foundIter);
0354       }
0355     }
0356   }
0357 
0358   // set updated (possibly with removed outliers) tracksAtVertex to vertex
0359   vertex.setTracksAtVertex(tracksAtVertex);
0360 
0361   return {};
0362 }
0363 
0364 Acts::Result<void> Acts::IterativeVertexFinder::fillTracksToFit(
0365     const std::vector<InputTrack>& seedTracks, const Vertex& seedVertex,
0366     std::vector<InputTrack>& tracksToFitOut,
0367     std::vector<InputTrack>& tracksToFitSplitVertexOut,
0368     const VertexingOptions& vertexingOptions, State& state) const {
0369   int numberOfTracks = seedTracks.size();
0370 
0371   // Count how many tracks are used for fit
0372   int count = 0;
0373   // Fill tracksToFit vector with tracks compatible with seed
0374   for (const auto& sTrack : seedTracks) {
0375     // If there are only few tracks left, add them to fit regardless of their
0376     // position:
0377     if (numberOfTracks <= 2) {
0378       tracksToFitOut.push_back(sTrack);
0379       ++count;
0380     } else if (numberOfTracks <= 4 && !m_cfg.createSplitVertices) {
0381       tracksToFitOut.push_back(sTrack);
0382       ++count;
0383     } else if (numberOfTracks <= 4 * m_cfg.splitVerticesTrkInvFraction &&
0384                m_cfg.createSplitVertices) {
0385       if (count % m_cfg.splitVerticesTrkInvFraction != 0) {
0386         tracksToFitOut.push_back(sTrack);
0387         ++count;
0388       } else {
0389         tracksToFitSplitVertexOut.push_back(sTrack);
0390         ++count;
0391       }
0392     }
0393     // If a large amount of tracks is available, we check their compatibility
0394     // with the vertex before adding them to the fit:
0395     else {
0396       const BoundTrackParameters& sTrackParams =
0397           m_cfg.extractParameters(sTrack);
0398       auto distanceRes = m_cfg.ipEst.calculateDistance(
0399           vertexingOptions.geoContext, sTrackParams, seedVertex.position(),
0400           state.ipState);
0401       if (!distanceRes.ok()) {
0402         return distanceRes.error();
0403       }
0404 
0405       if (!sTrackParams.covariance()) {
0406         return VertexingError::NoCovariance;
0407       }
0408 
0409       // sqrt(sigma(d0)^2+sigma(z0)^2), where sigma(d0)^2 is the variance of d0
0410       double hypotVariance =
0411           sqrt((*(sTrackParams.covariance()))(eBoundLoc0, eBoundLoc0) +
0412                (*(sTrackParams.covariance()))(eBoundLoc1, eBoundLoc1));
0413 
0414       if (hypotVariance == 0.) {
0415         ACTS_WARNING(
0416             "Track impact parameter covariances are zero. Track was not "
0417             "assigned to vertex.");
0418         continue;
0419       }
0420 
0421       if (*distanceRes / hypotVariance < m_cfg.significanceCutSeeding) {
0422         if (!m_cfg.createSplitVertices ||
0423             count % m_cfg.splitVerticesTrkInvFraction != 0) {
0424           tracksToFitOut.push_back(sTrack);
0425           ++count;
0426         } else {
0427           tracksToFitSplitVertexOut.push_back(sTrack);
0428           ++count;
0429         }
0430       }
0431     }
0432   }
0433   return {};
0434 }
0435 
0436 Acts::Result<bool> Acts::IterativeVertexFinder::reassignTracksToNewVertex(
0437     std::vector<Vertex>& vertexCollection, Vertex& currentVertex,
0438     std::vector<InputTrack>& tracksToFit, std::vector<InputTrack>& seedTracks,
0439     const std::vector<InputTrack>& /* origTracks */,
0440     const VertexingOptions& vertexingOptions, State& state) const {
0441   int numberOfAddedTracks = 0;
0442 
0443   const std::shared_ptr<PerigeeSurface> currentVertexPerigeeSurface =
0444       Surface::makeShared<PerigeeSurface>(
0445           VectorHelpers::position(currentVertex.fullPosition()));
0446 
0447   // iterate over all vertices and check if tracks need to be reassigned
0448   // to new (current) vertex
0449   for (auto& vertexIt : vertexCollection) {
0450     // tracks at vertexIt
0451     std::vector<TrackAtVertex> tracksAtVertex = vertexIt.tracks();
0452     auto tracksBegin = tracksAtVertex.begin();
0453     auto tracksEnd = tracksAtVertex.end();
0454 
0455     const std::shared_ptr<PerigeeSurface> vertexItPerigeeSurface =
0456         Surface::makeShared<PerigeeSurface>(
0457             VectorHelpers::position(vertexIt.fullPosition()));
0458 
0459     for (auto tracksIter = tracksBegin; tracksIter != tracksEnd;) {
0460       // consider only tracks that are not too tightly assigned to other
0461       // vertex
0462       if (tracksIter->trackWeight > m_cfg.cutOffTrackWeightReassign) {
0463         tracksIter++;
0464         continue;
0465       }
0466       // use original perigee parameters
0467       BoundTrackParameters origParams =
0468           m_cfg.extractParameters(tracksIter->originalParams);
0469 
0470       // compute compatibility
0471       auto resultNew = getCompatibility(origParams, currentVertex,
0472                                         *currentVertexPerigeeSurface,
0473                                         vertexingOptions, state);
0474       if (!resultNew.ok()) {
0475         return Result<bool>::failure(resultNew.error());
0476       }
0477       double chi2NewVtx = *resultNew;
0478 
0479       auto resultOld =
0480           getCompatibility(origParams, vertexIt, *vertexItPerigeeSurface,
0481                            vertexingOptions, state);
0482       if (!resultOld.ok()) {
0483         return Result<bool>::failure(resultOld.error());
0484       }
0485       double chi2OldVtx = *resultOld;
0486 
0487       ACTS_DEBUG("Compatibility to new vs old vertex: " << chi2NewVtx << " vs "
0488                                                         << chi2OldVtx);
0489 
0490       if (chi2NewVtx < chi2OldVtx) {
0491         tracksToFit.push_back(tracksIter->originalParams);
0492         // origTrack was already deleted from seedTracks previously
0493         // (when assigned to old vertex)
0494         // add it now back to seedTracks to be able to consistently
0495         // delete it later
0496         // when all tracks used to fit current vertex are deleted
0497         seedTracks.push_back(tracksIter->originalParams);
0498         // seedTracks.push_back(*std::find_if(
0499         //     origTracks.begin(), origTracks.end(),
0500         //     [&origParams, this](auto origTrack) {
0501         //       return origParams == m_extractParameters(*origTrack);
0502         //     }));
0503 
0504         numberOfAddedTracks += 1;
0505 
0506         // remove track from old vertex
0507         tracksIter = tracksAtVertex.erase(tracksIter);
0508         tracksBegin = tracksAtVertex.begin();
0509         tracksEnd = tracksAtVertex.end();
0510 
0511       }  // end chi2NewVtx < chi2OldVtx
0512 
0513       else {
0514         // go and check next track
0515         ++tracksIter;
0516       }
0517     }  // end loop over tracks at old vertexIt
0518 
0519     vertexIt.setTracksAtVertex(tracksAtVertex);
0520   }  // end loop over all vertices
0521 
0522   ACTS_DEBUG("Added " << numberOfAddedTracks
0523                       << " tracks from old (other) vertices for new fit");
0524 
0525   // override current vertex with new fit
0526   // set first to default vertex to be able to check if still good vertex
0527   // later
0528   currentVertex = Vertex();
0529   if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
0530     auto fitResult =
0531         m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0532     if (fitResult.ok()) {
0533       currentVertex = std::move(*fitResult);
0534     } else {
0535       return Result<bool>::success(false);
0536     }
0537   } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
0538     auto fitResult =
0539         m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0540     if (fitResult.ok()) {
0541       currentVertex = std::move(*fitResult);
0542     } else {
0543       return Result<bool>::success(false);
0544     }
0545   }
0546 
0547   // Number degrees of freedom
0548   double ndf = currentVertex.fitQuality().second;
0549 
0550   // Number of significant tracks
0551   int nTracksAtVertex = countSignificantTracks(currentVertex);
0552 
0553   bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0554                         nTracksAtVertex >= 2) ||
0555                        (vertexingOptions.useConstraintInFit && ndf > 3 &&
0556                         nTracksAtVertex >= 2));
0557 
0558   if (!isGoodVertex) {
0559     removeTracks(tracksToFit, seedTracks);
0560 
0561     ACTS_DEBUG("Going to new iteration with "
0562                << seedTracks.size() << "seed tracks after BAD vertex.");
0563   }
0564 
0565   return Result<bool>::success(isGoodVertex);
0566 }
0567 
0568 int Acts::IterativeVertexFinder::countSignificantTracks(
0569     const Vertex& vtx) const {
0570   return std::count_if(vtx.tracks().begin(), vtx.tracks().end(),
0571                        [this](const TrackAtVertex& trk) {
0572                          return trk.trackWeight > m_cfg.cutOffTrackWeight;
0573                        });
0574 }