Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:33

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         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::ranges::find_if(seedTracks, [&params, this](const auto seedTrk) {
0226           return params == m_cfg.extractParameters(seedTrk);
0227         });
0228     if (foundIter != seedTracks.end()) {
0229       // Remove track from seed tracks
0230       seedTracks.erase(foundIter);
0231     } else {
0232       ACTS_WARNING("Track to be removed not found in seed tracks.");
0233     }
0234   }
0235 }
0236 
0237 Acts::Result<double> Acts::IterativeVertexFinder::getCompatibility(
0238     const BoundTrackParameters& params, const Vertex& vertex,
0239     const Surface& perigeeSurface, const VertexingOptions& vertexingOptions,
0240     State& state) const {
0241   // Linearize track
0242   auto result =
0243       m_cfg.trackLinearizer(params, vertex.fullPosition()[3], perigeeSurface,
0244                             vertexingOptions.geoContext,
0245                             vertexingOptions.magFieldContext, state.fieldCache);
0246   if (!result.ok()) {
0247     return result.error();
0248   }
0249 
0250   auto linTrack = std::move(*result);
0251 
0252   // Calculate reduced weight
0253   SquareMatrix2 weightReduced =
0254       linTrack.covarianceAtPCA.template block<2, 2>(0, 0);
0255 
0256   SquareMatrix2 errorVertexReduced =
0257       (linTrack.positionJacobian *
0258        (vertex.fullCovariance() * linTrack.positionJacobian.transpose()))
0259           .template block<2, 2>(0, 0);
0260   weightReduced += errorVertexReduced;
0261   weightReduced = weightReduced.inverse().eval();
0262 
0263   // Calculate compatibility / chi2
0264   Vector2 trackParameters2D =
0265       linTrack.parametersAtPCA.template block<2, 1>(0, 0);
0266   double compatibility =
0267       trackParameters2D.dot(weightReduced * trackParameters2D);
0268 
0269   return compatibility;
0270 }
0271 
0272 Acts::Result<void> Acts::IterativeVertexFinder::removeUsedCompatibleTracks(
0273     Vertex& vertex, std::vector<InputTrack>& tracksToFit,
0274     std::vector<InputTrack>& seedTracks,
0275     const VertexingOptions& vertexingOptions, State& state) const {
0276   std::vector<TrackAtVertex> tracksAtVertex = vertex.tracks();
0277 
0278   for (const auto& trackAtVtx : tracksAtVertex) {
0279     // Check compatibility
0280     if (trackAtVtx.trackWeight < m_cfg.cutOffTrackWeight) {
0281       // Do not remove track here, since it is not compatible with the vertex
0282       continue;
0283     }
0284     // Find and remove track from seedTracks
0285     auto foundSeedIter =
0286         std::ranges::find(seedTracks, trackAtVtx.originalParams);
0287     if (foundSeedIter != seedTracks.end()) {
0288       seedTracks.erase(foundSeedIter);
0289     } else {
0290       ACTS_WARNING("Track trackAtVtx not found in seedTracks!");
0291     }
0292 
0293     // Find and remove track from tracksToFit
0294     auto foundFitIter =
0295         std::ranges::find(tracksToFit, trackAtVtx.originalParams);
0296     if (foundFitIter != tracksToFit.end()) {
0297       tracksToFit.erase(foundFitIter);
0298     } else {
0299       ACTS_WARNING("Track trackAtVtx not found in tracksToFit!");
0300     }
0301   }  // end iteration over tracksAtVertex
0302 
0303   ACTS_DEBUG("After removal of tracks belonging to vertex, "
0304              << seedTracks.size() << " seed tracks left.");
0305 
0306   // Now start considering outliers
0307   // tracksToFit that are left here were below
0308   // m_cfg.cutOffTrackWeight threshold and are hence outliers
0309   ACTS_DEBUG("Number of outliers: " << tracksToFit.size());
0310 
0311   const std::shared_ptr<PerigeeSurface> vertexPerigeeSurface =
0312       Surface::makeShared<PerigeeSurface>(
0313           VectorHelpers::position(vertex.fullPosition()));
0314 
0315   for (const auto& trk : tracksToFit) {
0316     // calculate chi2 w.r.t. last fitted vertex
0317     auto result =
0318         getCompatibility(m_cfg.extractParameters(trk), vertex,
0319                          *vertexPerigeeSurface, vertexingOptions, state);
0320 
0321     if (!result.ok()) {
0322       return result.error();
0323     }
0324 
0325     double chi2 = *result;
0326 
0327     // check if sufficiently compatible with last fitted vertex
0328     // (quite loose constraint)
0329     if (chi2 < m_cfg.maximumChi2cutForSeeding) {
0330       auto foundIter = std::ranges::find(seedTracks, trk);
0331       if (foundIter != seedTracks.end()) {
0332         // Remove track from seed tracks
0333         seedTracks.erase(foundIter);
0334       }
0335 
0336     } else {
0337       // Track not compatible with vertex
0338       // Remove track from current vertex
0339       auto foundIter = std::ranges::find_if(
0340           tracksAtVertex,
0341           [&trk](auto trkAtVtx) { return trk == trkAtVtx.originalParams; });
0342       if (foundIter != tracksAtVertex.end()) {
0343         // Remove track from seed tracks
0344         tracksAtVertex.erase(foundIter);
0345       }
0346     }
0347   }
0348 
0349   // set updated (possibly with removed outliers) tracksAtVertex to vertex
0350   vertex.setTracksAtVertex(tracksAtVertex);
0351 
0352   return {};
0353 }
0354 
0355 Acts::Result<void> Acts::IterativeVertexFinder::fillTracksToFit(
0356     const std::vector<InputTrack>& seedTracks, const Vertex& seedVertex,
0357     std::vector<InputTrack>& tracksToFitOut,
0358     std::vector<InputTrack>& tracksToFitSplitVertexOut,
0359     const VertexingOptions& vertexingOptions, State& state) const {
0360   int numberOfTracks = seedTracks.size();
0361 
0362   // Count how many tracks are used for fit
0363   int count = 0;
0364   // Fill tracksToFit vector with tracks compatible with seed
0365   for (const auto& sTrack : seedTracks) {
0366     // If there are only few tracks left, add them to fit regardless of their
0367     // position:
0368     if (numberOfTracks <= 2) {
0369       tracksToFitOut.push_back(sTrack);
0370       ++count;
0371     } else if (numberOfTracks <= 4 && !m_cfg.createSplitVertices) {
0372       tracksToFitOut.push_back(sTrack);
0373       ++count;
0374     } else if (numberOfTracks <= 4 * m_cfg.splitVerticesTrkInvFraction &&
0375                m_cfg.createSplitVertices) {
0376       if (count % m_cfg.splitVerticesTrkInvFraction != 0) {
0377         tracksToFitOut.push_back(sTrack);
0378         ++count;
0379       } else {
0380         tracksToFitSplitVertexOut.push_back(sTrack);
0381         ++count;
0382       }
0383     }
0384     // If a large amount of tracks is available, we check their compatibility
0385     // with the vertex before adding them to the fit:
0386     else {
0387       const BoundTrackParameters& sTrackParams =
0388           m_cfg.extractParameters(sTrack);
0389       auto distanceRes = m_cfg.ipEst.calculateDistance(
0390           vertexingOptions.geoContext, sTrackParams, seedVertex.position(),
0391           state.ipState);
0392       if (!distanceRes.ok()) {
0393         return distanceRes.error();
0394       }
0395 
0396       if (!sTrackParams.covariance()) {
0397         return VertexingError::NoCovariance;
0398       }
0399 
0400       // sqrt(sigma(d0)^2+sigma(z0)^2), where sigma(d0)^2 is the variance of d0
0401       double hypotVariance =
0402           sqrt((*(sTrackParams.covariance()))(eBoundLoc0, eBoundLoc0) +
0403                (*(sTrackParams.covariance()))(eBoundLoc1, eBoundLoc1));
0404 
0405       if (hypotVariance == 0.) {
0406         ACTS_WARNING(
0407             "Track impact parameter covariances are zero. Track was not "
0408             "assigned to vertex.");
0409         continue;
0410       }
0411 
0412       if (*distanceRes / hypotVariance < m_cfg.significanceCutSeeding) {
0413         if (!m_cfg.createSplitVertices ||
0414             count % m_cfg.splitVerticesTrkInvFraction != 0) {
0415           tracksToFitOut.push_back(sTrack);
0416           ++count;
0417         } else {
0418           tracksToFitSplitVertexOut.push_back(sTrack);
0419           ++count;
0420         }
0421       }
0422     }
0423   }
0424   return {};
0425 }
0426 
0427 Acts::Result<bool> Acts::IterativeVertexFinder::reassignTracksToNewVertex(
0428     std::vector<Vertex>& vertexCollection, Vertex& currentVertex,
0429     std::vector<InputTrack>& tracksToFit, std::vector<InputTrack>& seedTracks,
0430     const std::vector<InputTrack>& /* origTracks */,
0431     const VertexingOptions& vertexingOptions, State& state) const {
0432   int numberOfAddedTracks = 0;
0433 
0434   const std::shared_ptr<PerigeeSurface> currentVertexPerigeeSurface =
0435       Surface::makeShared<PerigeeSurface>(
0436           VectorHelpers::position(currentVertex.fullPosition()));
0437 
0438   // iterate over all vertices and check if tracks need to be reassigned
0439   // to new (current) vertex
0440   for (auto& vertexIt : vertexCollection) {
0441     // tracks at vertexIt
0442     std::vector<TrackAtVertex> tracksAtVertex = vertexIt.tracks();
0443     auto tracksBegin = tracksAtVertex.begin();
0444     auto tracksEnd = tracksAtVertex.end();
0445 
0446     const std::shared_ptr<PerigeeSurface> vertexItPerigeeSurface =
0447         Surface::makeShared<PerigeeSurface>(
0448             VectorHelpers::position(vertexIt.fullPosition()));
0449 
0450     for (auto tracksIter = tracksBegin; tracksIter != tracksEnd;) {
0451       // consider only tracks that are not too tightly assigned to other
0452       // vertex
0453       if (tracksIter->trackWeight > m_cfg.cutOffTrackWeightReassign) {
0454         tracksIter++;
0455         continue;
0456       }
0457       // use original perigee parameters
0458       BoundTrackParameters origParams =
0459           m_cfg.extractParameters(tracksIter->originalParams);
0460 
0461       // compute compatibility
0462       auto resultNew = getCompatibility(origParams, currentVertex,
0463                                         *currentVertexPerigeeSurface,
0464                                         vertexingOptions, state);
0465       if (!resultNew.ok()) {
0466         return Result<bool>::failure(resultNew.error());
0467       }
0468       double chi2NewVtx = *resultNew;
0469 
0470       auto resultOld =
0471           getCompatibility(origParams, vertexIt, *vertexItPerigeeSurface,
0472                            vertexingOptions, state);
0473       if (!resultOld.ok()) {
0474         return Result<bool>::failure(resultOld.error());
0475       }
0476       double chi2OldVtx = *resultOld;
0477 
0478       ACTS_DEBUG("Compatibility to new vs old vertex: " << chi2NewVtx << " vs "
0479                                                         << chi2OldVtx);
0480 
0481       if (chi2NewVtx < chi2OldVtx) {
0482         tracksToFit.push_back(tracksIter->originalParams);
0483         // origTrack was already deleted from seedTracks previously
0484         // (when assigned to old vertex)
0485         // add it now back to seedTracks to be able to consistently
0486         // delete it later
0487         // when all tracks used to fit current vertex are deleted
0488         seedTracks.push_back(tracksIter->originalParams);
0489         // seedTracks.push_back(*std::ranges::find_if(
0490         //     origTracks,
0491         //     [&origParams, this](auto origTrack) {
0492         //       return origParams == m_extractParameters(*origTrack);
0493         //     }));
0494 
0495         numberOfAddedTracks += 1;
0496 
0497         // remove track from old vertex
0498         tracksIter = tracksAtVertex.erase(tracksIter);
0499         tracksBegin = tracksAtVertex.begin();
0500         tracksEnd = tracksAtVertex.end();
0501 
0502       }  // end chi2NewVtx < chi2OldVtx
0503 
0504       else {
0505         // go and check next track
0506         ++tracksIter;
0507       }
0508     }  // end loop over tracks at old vertexIt
0509 
0510     vertexIt.setTracksAtVertex(tracksAtVertex);
0511   }  // end loop over all vertices
0512 
0513   ACTS_DEBUG("Added " << numberOfAddedTracks
0514                       << " tracks from old (other) vertices for new fit");
0515 
0516   // override current vertex with new fit
0517   // set first to default vertex to be able to check if still good vertex
0518   // later
0519   currentVertex = Vertex();
0520   if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
0521     auto fitResult =
0522         m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0523     if (fitResult.ok()) {
0524       currentVertex = std::move(*fitResult);
0525     } else {
0526       return Result<bool>::success(false);
0527     }
0528   } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
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   }
0537 
0538   // Number degrees of freedom
0539   double ndf = currentVertex.fitQuality().second;
0540 
0541   // Number of significant tracks
0542   int nTracksAtVertex = countSignificantTracks(currentVertex);
0543 
0544   bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0545                         nTracksAtVertex >= 2) ||
0546                        (vertexingOptions.useConstraintInFit && ndf > 3 &&
0547                         nTracksAtVertex >= 2));
0548 
0549   if (!isGoodVertex) {
0550     removeTracks(tracksToFit, seedTracks);
0551 
0552     ACTS_DEBUG("Going to new iteration with "
0553                << seedTracks.size() << "seed tracks after BAD vertex.");
0554   }
0555 
0556   return Result<bool>::success(isGoodVertex);
0557 }
0558 
0559 int Acts::IterativeVertexFinder::countSignificantTracks(
0560     const Vertex& vtx) const {
0561   return std::count_if(vtx.tracks().begin(), vtx.tracks().end(),
0562                        [this](const TrackAtVertex& trk) {
0563                          return trk.trackWeight > m_cfg.cutOffTrackWeight;
0564                        });
0565 }