Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-20 08:38:47

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/AdaptiveMultiVertexFitter.hpp"
0010 
0011 #include "Acts/Surfaces/PerigeeSurface.hpp"
0012 #include "Acts/Utilities/Helpers.hpp"
0013 #include "Acts/Vertexing/KalmanVertexUpdater.hpp"
0014 #include "Acts/Vertexing/VertexingError.hpp"
0015 
0016 namespace Acts {
0017 
0018 AdaptiveMultiVertexFitter::AdaptiveMultiVertexFitter(
0019     Config cfg, std::unique_ptr<const Logger> logger)
0020     : m_cfg(std::move(cfg)), m_logger(std::move(logger)) {
0021   if (!m_cfg.extractParameters.connected()) {
0022     throw std::invalid_argument(
0023         "AdaptiveMultiVertexFitter: No function to extract parameters "
0024         "from InputTrack_t provided.");
0025   }
0026 
0027   if (!m_cfg.trackLinearizer.connected()) {
0028     throw std::invalid_argument(
0029         "AdaptiveMultiVertexFitter: No track linearizer provided.");
0030   }
0031 }
0032 
0033 AdaptiveMultiVertexFitter::VertexScratch& AdaptiveMultiVertexFitter::scratchFor(
0034     const VertexFitProblem& problem, Vertex* vtx, Cache& cache) {
0035   auto [it, inserted] = cache.vertexScratch.try_emplace(vtx);
0036   if (inserted) {
0037     // Seed the linearization point and the previous position with the seed
0038     // position of the vertex, so that the first iteration measures the
0039     // distance to the seed rather than to the origin. A vertex without a
0040     // candidate entry keeps the zero-initialised default, matching the
0041     // previously default-constructed VertexInfo.
0042     if (auto candidateIt = problem.candidates.find(vtx);
0043         candidateIt != problem.candidates.end()) {
0044       it->second.linPoint = candidateIt->second.seedPosition;
0045       it->second.oldPosition = candidateIt->second.seedPosition;
0046     }
0047   }
0048   return it->second;
0049 }
0050 
0051 Result<void> AdaptiveMultiVertexFitter::fit(
0052     VertexFitProblem& problem, const VertexingOptions& vertexingOptions,
0053     Cache& cache) const {
0054   // Reset annealing tool
0055   cache.annealingState = AnnealingUtility::State();
0056 
0057   // Boolean indicating whether any of the vertices has moved more than
0058   // m_cfg.maxRelativeShift during the last iteration. We will keep iterating
0059   // until the equilibrium (i.e., the lowest temperature) is reached in
0060   // the annealing procedure and isSmallShift is true (or until the maximum
0061   // number of iterations is exceeded).
0062   bool isSmallShift = true;
0063 
0064   // Number of iterations counter
0065   unsigned int nIter = 0;
0066 
0067   // Start iterating
0068   while (nIter < m_cfg.maxIterations &&
0069          (!cache.annealingState.equilibriumReached || !isSmallShift)) {
0070     // Initial loop over all vertices in problem.vertices
0071     for (auto vtx : problem.vertices) {
0072       VertexScratch& scratch = scratchFor(problem, vtx, cache);
0073       scratch.relinearize = false;
0074       // Store old position of vertex, i.e. seed position
0075       // in case of first iteration or position determined
0076       // in previous iteration afterwards
0077       scratch.oldPosition = vtx->fullPosition();
0078 
0079       // Calculate the x-y-distance between the current vertex position
0080       // and the linearization point of the tracks. If it is too large,
0081       // we relinearize the tracks and recalculate their 3D impact
0082       // parameters.
0083       Vector2 xyDiff = scratch.oldPosition.template head<2>() -
0084                        scratch.linPoint.template head<2>();
0085       if (xyDiff.norm() > m_cfg.maxDistToLinPoint) {
0086         // Set flag for relinearization
0087         scratch.relinearize = true;
0088         // Recalculate the track impact parameters at the current vertex
0089         // position
0090         auto prepareVertexResult =
0091             prepareVertexForFit(problem, vtx, vertexingOptions, cache);
0092         if (!prepareVertexResult.ok()) {
0093           // Print vertices and associated tracks if logger is in debug mode
0094           if (logger().doPrint(Logging::DEBUG)) {
0095             logDebugData(problem, vertexingOptions.geoContext, cache);
0096           }
0097           return prepareVertexResult.error();
0098         }
0099       }
0100 
0101       // setWeightsAndUpdate re-adds every track, so each iteration is a full
0102       // refit and the vertex has to be reset to its prior first.
0103       Vertex& prior = problem.candidates[vtx].constraint;
0104       if (prior.fullCovariance() == SquareMatrix4::Zero()) {
0105         if (vtx->fullCovariance() == SquareMatrix4::Zero()) {
0106           return VertexingError::NoCovariance;
0107         }
0108         prior.setFullPosition(vtx->fullPosition());
0109         prior.setFitQuality(vtx->fitQuality());
0110         prior.setFullCovariance(vtx->fullCovariance());
0111       }
0112       vtx->setFullPosition(prior.fullPosition());
0113       vtx->setFitQuality(prior.fitQuality());
0114       vtx->setFullCovariance(prior.fullCovariance());
0115 
0116       // Set vertexCompatibility for all TrackAtVertex objects
0117       // at the current vertex
0118       auto setCompatibilitiesResult =
0119           setAllVertexCompatibilities(problem, vtx, vertexingOptions, cache);
0120       if (!setCompatibilitiesResult.ok()) {
0121         // Print vertices and associated tracks if logger is in debug mode
0122         if (logger().doPrint(Logging::DEBUG)) {
0123           logDebugData(problem, vertexingOptions.geoContext, cache);
0124         }
0125         return setCompatibilitiesResult.error();
0126       }
0127     }  // End loop over vertex collection
0128 
0129     // Recalculate all track weights and update vertices
0130     auto setWeightsResult =
0131         setWeightsAndUpdate(problem, vertexingOptions, cache);
0132     if (!setWeightsResult.ok()) {
0133       // Print vertices and associated tracks if logger is in debug mode
0134       if (logger().doPrint(Logging::DEBUG)) {
0135         logDebugData(problem, vertexingOptions.geoContext, cache);
0136       }
0137       return setWeightsResult.error();
0138     }
0139 
0140     // Cool the system down, i.e., reduce the temperature parameter. At lower
0141     // temperatures, outlying tracks are downweighted more.
0142     if (!cache.annealingState.equilibriumReached) {
0143       m_cfg.annealingTool.anneal(cache.annealingState);
0144     }
0145 
0146     isSmallShift = checkSmallShift(problem, cache);
0147     ++nIter;
0148   }
0149   // Multivertex fit is finished
0150 
0151   // Check if smoothing is required
0152   if (m_cfg.doSmoothing) {
0153     doVertexSmoothing(problem);
0154   }
0155 
0156   return {};
0157 }
0158 
0159 Result<void> AdaptiveMultiVertexFitter::addVtxToFit(
0160     VertexFitProblem& problem, const std::vector<Vertex*>& newVertices,
0161     const VertexingOptions& vertexingOptions, Cache& cache) const {
0162   for (const auto& newVertex : newVertices) {
0163     if (problem.candidates[newVertex].trackLinks.empty()) {
0164       ACTS_ERROR(
0165           "newVertex does not have any associated tracks (i.e., its trackLinks "
0166           "are empty).");
0167       return VertexingError::EmptyInput;
0168     }
0169   }
0170 
0171   std::vector<Vertex*> verticesToFit = newVertices;
0172 
0173   // List of vertices added in last iteration
0174   std::vector<Vertex*> lastIterAddedVertices = newVertices;
0175   // List of vertices added in current iteration
0176   std::vector<Vertex*> currentIterAddedVertices;
0177 
0178   // Fill verticesToFit with vertices that are connected to newVertex (via
0179   // tracks and/or other vertices).
0180   while (!lastIterAddedVertices.empty()) {
0181     for (auto& lastIterAddedVertex : lastIterAddedVertices) {
0182       // Loop over all tracks at lastIterAddedVertex
0183       const std::vector<InputTrack>& trks =
0184           problem.candidates[lastIterAddedVertex].trackLinks;
0185       for (const auto& trk : trks) {
0186         // Range of vertices that are associated with trk. The range is
0187         // represented via its bounds: begin refers to the first iterator of the
0188         // range; end refers to the iterator after the last iterator of the
0189         // range.
0190         auto [begin, end] = problem.trackToVertices.equal_range(trk);
0191 
0192         for (auto it = begin; it != end; ++it) {
0193           // it->first corresponds to trk, it->second to one of its associated
0194           // vertices
0195           auto vtxToFit = it->second;
0196           // Add vertex to the fit if it is not already included
0197           if (!isAlreadyInList(vtxToFit, verticesToFit)) {
0198             verticesToFit.push_back(vtxToFit);
0199 
0200             // Collect vertices that were added this iteration
0201             if (vtxToFit != lastIterAddedVertex) {
0202               currentIterAddedVertices.push_back(vtxToFit);
0203             }
0204           }
0205         }  // End for loop over range of associated vertices
0206       }  // End loop over trackLinks
0207     }  // End loop over lastIterAddedVertices
0208 
0209     lastIterAddedVertices = currentIterAddedVertices;
0210     currentIterAddedVertices.clear();
0211   }  // End while loop
0212 
0213   problem.vertices = verticesToFit;
0214 
0215   for (Vertex* newVertexPtr : newVertices) {
0216     // Save the 3D impact parameters of all tracks associated with newVertex.
0217     auto res =
0218         prepareVertexForFit(problem, newVertexPtr, vertexingOptions, cache);
0219     if (!res.ok()) {
0220       // Print vertices and associated tracks if logger is in debug mode
0221       if (logger().doPrint(Logging::DEBUG)) {
0222         logDebugData(problem, vertexingOptions.geoContext, cache);
0223       }
0224       return res.error();
0225     }
0226   }
0227 
0228   // Perform fit on all added vertices
0229   auto fitRes = fit(problem, vertexingOptions, cache);
0230   if (!fitRes.ok()) {
0231     return fitRes.error();
0232   }
0233 
0234   return {};
0235 }
0236 
0237 bool AdaptiveMultiVertexFitter::isAlreadyInList(
0238     Vertex* vtx, const std::vector<Vertex*>& vertices) const {
0239   return rangeContainsValue(vertices, vtx);
0240 }
0241 
0242 Result<void> AdaptiveMultiVertexFitter::prepareVertexForFit(
0243     const VertexFitProblem& problem, Vertex* vtx,
0244     const VertexingOptions& vertexingOptions, Cache& cache) const {
0245   // Vertex seed position
0246   const Vector3& seedPos =
0247       problem.candidates.at(vtx).seedPosition.template head<3>();
0248   VertexScratch& scratch = scratchFor(problem, vtx, cache);
0249 
0250   // Loop over all tracks at the vertex
0251   for (const auto& trk : problem.candidates.at(vtx).trackLinks) {
0252     auto res = m_cfg.ipEst.estimate3DImpactParameters(
0253         vertexingOptions.geoContext, vertexingOptions.magFieldContext,
0254         m_cfg.extractParameters(trk), seedPos, cache.ipState);
0255     if (!res.ok()) {
0256       return res.error();
0257     }
0258     // Save 3D impact parameters of the track
0259     scratch.impactParams3D.try_emplace(trk, res.value());
0260   }
0261   return {};
0262 }
0263 
0264 Result<void> AdaptiveMultiVertexFitter::setAllVertexCompatibilities(
0265     VertexFitProblem& problem, Vertex* vtx,
0266     const VertexingOptions& vertexingOptions, Cache& cache) const {
0267   const VertexFitCandidate& candidate = problem.candidates[vtx];
0268   VertexScratch& scratch = scratchFor(problem, vtx, cache);
0269 
0270   // Loop over all tracks that are associated with vtx and estimate their
0271   // compatibility
0272   for (const auto& trk : candidate.trackLinks) {
0273     auto& trkAtVtx = problem.tracksAtVertices.at(std::make_pair(trk, vtx));
0274     // Recover from cases where linearization point != 0 but
0275     // more tracks were added later on
0276     if (!scratch.impactParams3D.contains(trk)) {
0277       auto res = m_cfg.ipEst.estimate3DImpactParameters(
0278           vertexingOptions.geoContext, vertexingOptions.magFieldContext,
0279           m_cfg.extractParameters(trk),
0280           VectorHelpers::position(scratch.linPoint), cache.ipState);
0281       if (!res.ok()) {
0282         return res.error();
0283       }
0284       // Set impactParams3D for current trackAtVertex
0285       scratch.impactParams3D.try_emplace(trk, res.value());
0286     }
0287     // Set compatibility with current vertex
0288     Result<double> compatibilityResult(0.);
0289     if (m_cfg.useTime) {
0290       compatibilityResult = m_cfg.ipEst.getVertexCompatibility(
0291           vertexingOptions.geoContext, &(scratch.impactParams3D.at(trk)),
0292           scratch.oldPosition);
0293     } else {
0294       Vector3 vertexPosOnly = VectorHelpers::position(scratch.oldPosition);
0295       compatibilityResult = m_cfg.ipEst.getVertexCompatibility(
0296           vertexingOptions.geoContext, &(scratch.impactParams3D.at(trk)),
0297           vertexPosOnly);
0298     }
0299 
0300     if (!compatibilityResult.ok()) {
0301       return compatibilityResult.error();
0302     }
0303     trkAtVtx.vertexCompatibility = *compatibilityResult;
0304   }
0305   return {};
0306 }
0307 
0308 Result<void> AdaptiveMultiVertexFitter::setWeightsAndUpdate(
0309     VertexFitProblem& problem, const VertexingOptions& vertexingOptions,
0310     Cache& cache) const {
0311   for (auto vtx : problem.vertices) {
0312     VertexScratch& scratch = scratchFor(problem, vtx, cache);
0313 
0314     if (scratch.relinearize) {
0315       scratch.linPoint = scratch.oldPosition;
0316     }
0317 
0318     const std::shared_ptr<PerigeeSurface> vtxPerigeeSurface =
0319         Surface::makeShared<PerigeeSurface>(
0320             VectorHelpers::position(scratch.linPoint));
0321 
0322     for (const auto& trk : problem.candidates[vtx].trackLinks) {
0323       auto& trkAtVtx = problem.tracksAtVertices.at(std::make_pair(trk, vtx));
0324 
0325       // Set trackWeight for current track
0326       trkAtVtx.trackWeight = m_cfg.annealingTool.getWeight(
0327           cache.annealingState, trkAtVtx.vertexCompatibility,
0328           collectTrackToVertexCompatibilities(problem, trk));
0329 
0330       if (trkAtVtx.trackWeight > m_cfg.minWeight) {
0331         // Check if track is already linearized and whether we need to
0332         // relinearize
0333         if (!trkAtVtx.isLinearized || scratch.relinearize) {
0334           auto result = m_cfg.trackLinearizer(
0335               m_cfg.extractParameters(trk), scratch.linPoint[3],
0336               *vtxPerigeeSurface, vertexingOptions.geoContext,
0337               vertexingOptions.magFieldContext, cache.fieldCache);
0338           if (!result.ok()) {
0339             return result.error();
0340           }
0341 
0342           trkAtVtx.linearizedState = *result;
0343           trkAtVtx.isLinearized = true;
0344         }
0345         // Update the vertex with the new track. The second template
0346         // argument corresponds to the number of fitted vertex dimensions
0347         // (i.e., 3 if we only fit spatial coordinates and 4 if we also fit
0348         // time).
0349         KalmanVertexUpdater::updateVertexWithTrack(*vtx, trkAtVtx,
0350                                                    m_cfg.useTime ? 4 : 3);
0351       } else {
0352         ACTS_VERBOSE("Track weight too low. Skip track.");
0353       }
0354     }  // End loop over tracks at vertex
0355     ACTS_VERBOSE("New vertex position: " << vtx->fullPosition().transpose());
0356   }  // End loop over vertex collection
0357 
0358   return {};
0359 }
0360 
0361 std::vector<double>
0362 AdaptiveMultiVertexFitter::collectTrackToVertexCompatibilities(
0363     const VertexFitProblem& problem, const InputTrack& trk) const {
0364   // Compatibilities of trk wrt all of its associated vertices
0365   std::vector<double> trkToVtxCompatibilities;
0366 
0367   // Range of vertices that are associated with trk. The range is
0368   // represented via its bounds: begin refers to the first iterator of the
0369   // range; end refers to the iterator after the last iterator of the range.
0370   auto [begin, end] = problem.trackToVertices.equal_range(trk);
0371   // Allocate space in memory for the vector of compatibilities
0372   trkToVtxCompatibilities.reserve(std::distance(begin, end));
0373 
0374   for (auto it = begin; it != end; ++it) {
0375     // it->first corresponds to trk, it->second to one of its associated
0376     // vertices
0377     trkToVtxCompatibilities.push_back(
0378         problem.tracksAtVertices.at(std::make_pair(trk, it->second))
0379             .vertexCompatibility);
0380   }
0381 
0382   return trkToVtxCompatibilities;
0383 }
0384 
0385 bool AdaptiveMultiVertexFitter::checkSmallShift(const VertexFitProblem& problem,
0386                                                 Cache& cache) const {
0387   for (auto* vtx : problem.vertices) {
0388     Vector3 diff =
0389         scratchFor(problem, vtx, cache).oldPosition.template head<3>() -
0390         vtx->position();
0391     const SquareMatrix3& vtxCov = vtx->covariance();
0392     double relativeShift = diff.dot(vtxCov.inverse() * diff);
0393     if (relativeShift > m_cfg.maxRelativeShift) {
0394       return false;
0395     }
0396   }
0397   return true;
0398 }
0399 
0400 void AdaptiveMultiVertexFitter::doVertexSmoothing(
0401     VertexFitProblem& problem) const {
0402   for (const auto vtx : problem.vertices) {
0403     for (const auto& trk : problem.candidates[vtx].trackLinks) {
0404       auto& trkAtVtx = problem.tracksAtVertices.at(std::make_pair(trk, vtx));
0405       if (trkAtVtx.trackWeight > m_cfg.minWeight) {
0406         // Update the new track under the assumption that it originates at the
0407         // vertex. The second template argument corresponds to the number of
0408         // fitted vertex dimensions (i.e., 3 if we only fit spatial coordinates
0409         // and 4 if we also fit time).
0410         KalmanVertexUpdater::updateTrackWithVertex(trkAtVtx, *vtx,
0411                                                    m_cfg.useTime ? 4 : 3);
0412       }
0413     }
0414   }
0415 }
0416 
0417 void AdaptiveMultiVertexFitter::logDebugData(const VertexFitProblem& problem,
0418                                              const GeometryContext& geoContext,
0419                                              Cache& cache) const {
0420   ACTS_DEBUG("Encountered an error when fitting the following "
0421              << problem.vertices.size() << " vertices:");
0422   for (std::size_t vtxInd = 0; vtxInd < problem.vertices.size(); ++vtxInd) {
0423     auto vtx = problem.vertices[vtxInd];
0424     ACTS_DEBUG("Position of " << vtxInd << ". vertex seed:\n"
0425                               << problem.candidates.at(vtx).seedPosition);
0426     ACTS_DEBUG("Position of said vertex after the last fitting step:\n"
0427                << scratchFor(problem, vtx, cache).oldPosition);
0428     ACTS_DEBUG("Associated tracks:");
0429     const auto& trks = problem.candidates.at(vtx).trackLinks;
0430     for (std::size_t trkInd = 0; trkInd < trks.size(); ++trkInd) {
0431       const auto& trkAtVtx =
0432           problem.tracksAtVertices.at(std::make_pair(trks[trkInd], vtx));
0433       const auto& trkParams = m_cfg.extractParameters(trkAtVtx.originalParams);
0434       ACTS_DEBUG(trkInd << ". track parameters:\n" << trkParams.parameters());
0435       ACTS_DEBUG(trkInd << ". track covariance matrix:\n"
0436                         << trkParams.covariance().value());
0437       ACTS_DEBUG("Origin of corresponding reference surface:\n"
0438                  << trkParams.referenceSurface().center(geoContext));
0439     }
0440   }
0441 }
0442 
0443 void AdaptiveMultiVertexFitter::decomposeState(State& state,
0444                                                VertexFitProblem& problem,
0445                                                Cache& cache) {
0446   problem.vertices = std::move(state.vertexCollection);
0447   problem.trackToVertices = std::move(state.trackToVerticesMultiMap);
0448   problem.tracksAtVertices = std::move(state.tracksAtVerticesMap);
0449 
0450   cache.annealingState = state.annealingState;
0451 
0452   for (auto& [vtx, info] : state.vtxInfoMap) {
0453     VertexFitCandidate candidate;
0454     candidate.constraint = std::move(info.constraint);
0455     candidate.seedPosition = info.seedPosition;
0456     candidate.trackLinks = std::move(info.trackLinks);
0457     problem.candidates.try_emplace(vtx, std::move(candidate));
0458 
0459     VertexScratch scratch;
0460     scratch.linPoint = info.linPoint;
0461     scratch.oldPosition = info.oldPosition;
0462     scratch.relinearize = info.relinearize;
0463     // swap rather than move-assign: the mapped type is const, so the map is
0464     // not assignable
0465     scratch.impactParams3D.swap(info.impactParams3D);
0466     cache.vertexScratch.try_emplace(vtx, std::move(scratch));
0467   }
0468 
0469   state.vtxInfoMap.clear();
0470 }
0471 
0472 void AdaptiveMultiVertexFitter::recomposeState(State& state,
0473                                                VertexFitProblem& problem,
0474                                                Cache& cache) {
0475   state.vertexCollection = std::move(problem.vertices);
0476   state.trackToVerticesMultiMap = std::move(problem.trackToVertices);
0477   state.tracksAtVerticesMap = std::move(problem.tracksAtVertices);
0478 
0479   state.annealingState = cache.annealingState;
0480   state.ipState = std::move(cache.ipState);
0481   state.fieldCache = std::move(cache.fieldCache);
0482 
0483   state.vtxInfoMap.clear();
0484   for (auto& [vtx, candidate] : problem.candidates) {
0485     VertexInfo info;
0486     info.constraint = std::move(candidate.constraint);
0487     info.seedPosition = candidate.seedPosition;
0488     info.trackLinks = std::move(candidate.trackLinks);
0489 
0490     // A vertex the fit never touched has no scratch entry yet. The old state
0491     // seeded those fields from the seed position at construction, which is
0492     // what scratchFor would do on first access.
0493     info.linPoint = candidate.seedPosition;
0494     info.oldPosition = candidate.seedPosition;
0495     if (auto it = cache.vertexScratch.find(vtx);
0496         it != cache.vertexScratch.end()) {
0497       info.linPoint = it->second.linPoint;
0498       info.oldPosition = it->second.oldPosition;
0499       info.relinearize = it->second.relinearize;
0500       info.impactParams3D.swap(it->second.impactParams3D);
0501     }
0502 
0503     state.vtxInfoMap.try_emplace(vtx, std::move(info));
0504   }
0505 }
0506 
0507 Result<void> AdaptiveMultiVertexFitter::addVtxToFit(
0508     State& state, const std::vector<Vertex*>& newVertices,
0509     const VertexingOptions& vertexingOptions) const {
0510   VertexFitProblem problem;
0511   Cache cache(std::move(state.ipState), std::move(state.fieldCache));
0512   decomposeState(state, problem, cache);
0513 
0514   auto res = addVtxToFit(problem, newVertices, vertexingOptions, cache);
0515 
0516   recomposeState(state, problem, cache);
0517   return res;
0518 }
0519 
0520 Result<void> AdaptiveMultiVertexFitter::fit(
0521     State& state, const VertexingOptions& vertexingOptions) const {
0522   VertexFitProblem problem;
0523   Cache cache(std::move(state.ipState), std::move(state.fieldCache));
0524   decomposeState(state, problem, cache);
0525 
0526   auto res = fit(problem, vertexingOptions, cache);
0527 
0528   recomposeState(state, problem, cache);
0529   return res;
0530 }
0531 
0532 }  // namespace Acts