Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-06 08:56:43

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/AdaptiveMultiVertexFinder.hpp"
0010 
0011 #include "Acts/Utilities/AlgebraHelpers.hpp"
0012 #include "Acts/Vertexing/IVertexFinder.hpp"
0013 #include "Acts/Vertexing/VertexingError.hpp"
0014 
0015 #include <algorithm>
0016 
0017 namespace Acts {
0018 
0019 Result<std::vector<Vertex>> AdaptiveMultiVertexFinder::find(
0020     const std::vector<InputTrack>& allTracks,
0021     const VertexingOptions& vertexingOptions,
0022     IVertexFinder::State& anyState) const {
0023   if (allTracks.empty()) {
0024     ACTS_ERROR("Empty track collection handed to find method");
0025     return VertexingError::EmptyInput;
0026   }
0027 
0028   State& state = anyState.template as<State>();
0029   IVertexFinder::State& seedFinderState = state.seedFinderState;
0030   VertexFitterState fitterState(*m_cfg.bField,
0031                                 vertexingOptions.magFieldContext);
0032 
0033   const std::vector<InputTrack>& origTracks = allTracks;
0034   std::vector<InputTrack> seedTracks = allTracks;
0035   std::vector<std::unique_ptr<Vertex>> allVertices;
0036   std::vector<Vertex*> allVerticesPtr;
0037   std::vector<Vertex*> newVerticesPtr;
0038 
0039   int iteration = 0;
0040   std::vector<InputTrack> removedSeedTracks;
0041   while (!seedTracks.empty() && iteration < m_cfg.maxIterations &&
0042          (m_cfg.addSingleTrackVertices || seedTracks.size() >= 2)) {
0043     Vertex currentConstraint = vertexingOptions.constraint;
0044 
0045     // Retrieve seed vertex from all remaining seedTracks
0046     auto seedResult = doSeeding(seedTracks, currentConstraint, vertexingOptions,
0047                                 seedFinderState, removedSeedTracks);
0048     if (!seedResult.ok()) {
0049       return seedResult.error();
0050     }
0051     auto& seedVector = *seedResult;
0052 
0053     if (seedVector.empty()) {
0054       ACTS_DEBUG(
0055           "No seed found anymore. Break and stop primary vertex finding.");
0056       break;
0057     }
0058 
0059     newVerticesPtr.clear();
0060     for (const auto& seedVertex : seedVector) {
0061       allVertices.push_back(std::make_unique<Vertex>(seedVertex));
0062       allVerticesPtr.push_back(allVertices.back().get());
0063       newVerticesPtr.push_back(allVertices.back().get());
0064     }
0065 
0066     // Clear the seed track collection that has been removed in last iteration
0067     // now after seed finding is done
0068     removedSeedTracks.clear();
0069 
0070     // Tracks that are used for searching compatible tracks near a vertex
0071     // candidate
0072     std::vector<InputTrack> searchTracks;
0073     if (m_cfg.doRealMultiVertex) {
0074       searchTracks = origTracks;
0075     } else {
0076       searchTracks = seedTracks;
0077     }
0078 
0079     bool preparationFailed = false;
0080     for (Vertex* vtxPtr : newVerticesPtr) {
0081       auto prepResult = canPrepareVertexForFit(searchTracks, seedTracks,
0082                                                *vtxPtr, currentConstraint,
0083                                                fitterState, vertexingOptions);
0084       if (!prepResult.ok()) {
0085         return prepResult.error();
0086       }
0087       if (!(*prepResult)) {
0088         preparationFailed = true;
0089         break;
0090       }
0091       // Update fitter state with all vertices
0092       fitterState.addVertexToMultiMap(*vtxPtr);
0093     }
0094     if (preparationFailed) {
0095       ACTS_DEBUG("Could not prepare for fit. Discarding the vertex candidate.");
0096       allVertices.erase(allVertices.end() - newVerticesPtr.size(),
0097                         allVertices.end());
0098       allVerticesPtr.erase(allVerticesPtr.end() - newVerticesPtr.size(),
0099                            allVerticesPtr.end());
0100       if (m_cfg.doNotBreakWhileSeeding) {
0101         continue;
0102       } else {
0103         break;
0104       }
0105     }
0106 
0107     // Perform the fit
0108     auto fitResult = m_cfg.vertexFitter.addVtxToFit(fitterState, newVerticesPtr,
0109                                                     vertexingOptions);
0110     if (!fitResult.ok()) {
0111       return fitResult.error();
0112     }
0113 
0114     for (Vertex* newVertexPtr : newVerticesPtr) {
0115       Vertex& vtxCandidate = *newVertexPtr;
0116 
0117       ACTS_DEBUG("Position of vertex candidate after the fit: "
0118                  << vtxCandidate.fullPosition().transpose());
0119       // Check if vertex is good vertex
0120       auto [nCompatibleTracks, isGoodVertex] =
0121           checkVertexAndCompatibleTracks(vtxCandidate, seedTracks, fitterState,
0122                                          vertexingOptions.useConstraintInFit);
0123 
0124       ACTS_DEBUG("Vertex is good vertex: " << isGoodVertex);
0125       if (nCompatibleTracks > 0) {
0126         removeCompatibleTracksFromSeedTracks(vtxCandidate, seedTracks,
0127                                              fitterState, removedSeedTracks);
0128       } else {
0129         auto removedIncompatibleTrack = removeTrackIfIncompatible(
0130             vtxCandidate, seedTracks, fitterState, removedSeedTracks,
0131             vertexingOptions.geoContext);
0132         if (!removedIncompatibleTrack.ok()) {
0133           return removedIncompatibleTrack.error();
0134         }
0135       }
0136       auto keepNewVertexResult =
0137           keepNewVertex(vtxCandidate, allVerticesPtr, fitterState);
0138       if (!keepNewVertexResult.ok()) {
0139         return keepNewVertexResult.error();
0140       }
0141       bool keepVertex = isGoodVertex && *keepNewVertexResult;
0142       ACTS_DEBUG("New vertex will be saved: " << keepVertex);
0143 
0144       // Delete vertex from allVertices list again if it's not kept
0145       if (!keepVertex) {
0146         auto deleteVertexResult =
0147             deleteLastVertex(vtxCandidate, allVertices, allVerticesPtr,
0148                              fitterState, vertexingOptions);
0149         if (!deleteVertexResult.ok()) {
0150           return deleteVertexResult.error();
0151         }
0152       }
0153     }
0154 
0155     iteration++;
0156   }  // end while loop
0157 
0158   return getVertexOutputList(allVerticesPtr, fitterState);
0159 }
0160 
0161 Result<std::vector<Vertex>> AdaptiveMultiVertexFinder::doSeeding(
0162     const std::vector<InputTrack>& trackVector, Vertex& currentConstraint,
0163     const VertexingOptions& vertexingOptions,
0164     IVertexFinder::State& seedFinderState,
0165     const std::vector<InputTrack>& removedSeedTracks) const {
0166   VertexingOptions seedOptions = vertexingOptions;
0167   seedOptions.constraint = currentConstraint;
0168 
0169   m_cfg.seedFinder->setTracksToRemove(seedFinderState, removedSeedTracks);
0170 
0171   // Run seed finder
0172   auto seedResult =
0173       m_cfg.seedFinder->find(trackVector, seedOptions, seedFinderState);
0174 
0175   if (!seedResult.ok()) {
0176     return seedResult.error();
0177   }
0178   auto& seedVector = *seedResult;
0179 
0180   ACTS_DEBUG("Found " << seedVector.size() << " seeds");
0181 
0182   for (auto& seedVertex : seedVector) {
0183     // Update constraints according to seed vertex
0184     setConstraintAfterSeeding(currentConstraint, seedOptions.useConstraintInFit,
0185                               seedVertex);
0186   }
0187 
0188   return std::move(seedVector);
0189 }
0190 
0191 void AdaptiveMultiVertexFinder::setConstraintAfterSeeding(
0192     Vertex& currentConstraint, bool useVertexConstraintInFit,
0193     Vertex& seedVertex) const {
0194   if (useVertexConstraintInFit) {
0195     if (!m_cfg.useSeedConstraint) {
0196       // Set seed vertex constraint to old constraint before seeding
0197       seedVertex.setFullCovariance(currentConstraint.fullCovariance());
0198     } else {
0199       // Use the constraint provided by the seed finder
0200       currentConstraint.setFullPosition(seedVertex.fullPosition());
0201       currentConstraint.setFullCovariance(seedVertex.fullCovariance());
0202     }
0203   } else {
0204     currentConstraint.setFullPosition(seedVertex.fullPosition());
0205     currentConstraint.setFullCovariance(m_cfg.initialVariances.asDiagonal());
0206     currentConstraint.setFitQuality(m_cfg.defaultConstrFitQuality);
0207   }
0208 }
0209 
0210 Result<double> AdaptiveMultiVertexFinder::getIPSignificance(
0211     const InputTrack& track, const Vertex& vtx,
0212     const VertexingOptions& vertexingOptions) const {
0213   // TODO: In original implementation the covariance of the given vertex is set
0214   // to zero. I did the same here now, but consider removing this and just
0215   // passing the vtx object to the estimator without changing its covariance.
0216   // After all, the vertex seed does have a non-zero convariance in general and
0217   // it probably should be used.
0218   Vertex newVtx = vtx;
0219   if (!m_cfg.useVertexCovForIPEstimation) {
0220     newVtx.setFullCovariance(SquareMatrix4::Zero());
0221   }
0222 
0223   auto estRes = m_cfg.ipEstimator.getImpactParameters(
0224       m_cfg.extractParameters(track), newVtx, vertexingOptions.geoContext,
0225       vertexingOptions.magFieldContext, m_cfg.useTime);
0226   if (!estRes.ok()) {
0227     return estRes.error();
0228   }
0229 
0230   ImpactParametersAndSigma ipas = *estRes;
0231 
0232   // TODO: throw error when encountering negative standard deviations
0233   double chi2Time = 0;
0234   if (m_cfg.useTime) {
0235     if (ipas.sigmaDeltaT.value() > 0) {
0236       chi2Time = std::pow(ipas.deltaT.value() / ipas.sigmaDeltaT.value(), 2);
0237     }
0238   }
0239 
0240   double significance = 0.;
0241   if (ipas.sigmaD0 > 0 && ipas.sigmaZ0 > 0) {
0242     significance = std::sqrt(std::pow(ipas.d0 / ipas.sigmaD0, 2) +
0243                              std::pow(ipas.z0 / ipas.sigmaZ0, 2) + chi2Time);
0244   }
0245 
0246   return significance;
0247 }
0248 
0249 Result<void> AdaptiveMultiVertexFinder::addCompatibleTracksToVertex(
0250     const std::vector<InputTrack>& tracks, Vertex& vtx,
0251     VertexFitterState& fitterState,
0252     const VertexingOptions& vertexingOptions) const {
0253   for (const auto& trk : tracks) {
0254     auto params = m_cfg.extractParameters(trk);
0255     auto pos = params.position(vertexingOptions.geoContext);
0256     // If track is too far away from vertex, do not consider checking the IP
0257     // significance
0258     if (m_cfg.tracksMaxZinterval < std::abs(pos[eZ] - vtx.position()[eZ])) {
0259       continue;
0260     }
0261     auto sigRes = getIPSignificance(trk, vtx, vertexingOptions);
0262     if (!sigRes.ok()) {
0263       return sigRes.error();
0264     }
0265     double ipSig = *sigRes;
0266     if (ipSig < m_cfg.tracksMaxSignificance) {
0267       // Create TrackAtVertex objects, unique for each (track, vertex) pair
0268       fitterState.tracksAtVerticesMap.emplace(std::make_pair(trk, &vtx),
0269                                               TrackAtVertex(params, trk));
0270 
0271       // Add the original track parameters to the list for vtx
0272       fitterState.vtxInfoMap[&vtx].trackLinks.push_back(trk);
0273     }
0274   }
0275   return {};
0276 }
0277 
0278 Result<bool> AdaptiveMultiVertexFinder::canRecoverFromNoCompatibleTracks(
0279     const std::vector<InputTrack>& allTracks,
0280     const std::vector<InputTrack>& seedTracks, Vertex& vtx,
0281     const Vertex& currentConstraint, VertexFitterState& fitterState,
0282     const VertexingOptions& vertexingOptions) const {
0283   // Recover from cases where no compatible tracks to vertex
0284   // candidate were found
0285   // TODO: This is for now how it's done in athena... this look a bit
0286   // nasty to me
0287   if (fitterState.vtxInfoMap[&vtx].trackLinks.empty()) {
0288     // Find nearest track to vertex candidate
0289     double smallestDeltaZ = std::numeric_limits<double>::max();
0290     double newZ = 0;
0291     bool nearTrackFound = false;
0292     for (const auto& trk : seedTracks) {
0293       auto pos =
0294           m_cfg.extractParameters(trk).position(vertexingOptions.geoContext);
0295       auto zDistance = std::abs(pos[eZ] - vtx.position()[eZ]);
0296       if (zDistance < smallestDeltaZ) {
0297         smallestDeltaZ = zDistance;
0298         nearTrackFound = true;
0299         newZ = pos[eZ];
0300       }
0301     }
0302     if (nearTrackFound) {
0303       vtx.setFullPosition(Vector4(0., 0., newZ, 0.));
0304 
0305       // Update vertex info for current vertex
0306       fitterState.vtxInfoMap[&vtx] =
0307           VertexInfo(currentConstraint, vtx.fullPosition());
0308 
0309       // Try to add compatible track with adapted vertex position
0310       auto res = addCompatibleTracksToVertex(allTracks, vtx, fitterState,
0311                                              vertexingOptions);
0312       if (!res.ok()) {
0313         return Result<bool>::failure(res.error());
0314       }
0315 
0316       if (fitterState.vtxInfoMap[&vtx].trackLinks.empty()) {
0317         ACTS_DEBUG(
0318             "No tracks near seed were found, while at least one was "
0319             "expected. Break.");
0320         return Result<bool>::success(false);
0321       }
0322 
0323     } else {
0324       ACTS_DEBUG("No nearest track to seed found. Break.");
0325       return Result<bool>::success(false);
0326     }
0327   }
0328 
0329   return Result<bool>::success(true);
0330 }
0331 
0332 Result<bool> AdaptiveMultiVertexFinder::canPrepareVertexForFit(
0333     const std::vector<InputTrack>& allTracks,
0334     const std::vector<InputTrack>& seedTracks, Vertex& vtx,
0335     const Vertex& currentConstraint, VertexFitterState& fitterState,
0336     const VertexingOptions& vertexingOptions) const {
0337   // Add vertex info to fitter state
0338   fitterState.vtxInfoMap[&vtx] =
0339       VertexInfo(currentConstraint, vtx.fullPosition());
0340 
0341   // Add all compatible tracks to vertex
0342   auto resComp = addCompatibleTracksToVertex(allTracks, vtx, fitterState,
0343                                              vertexingOptions);
0344   if (!resComp.ok()) {
0345     return Result<bool>::failure(resComp.error());
0346   }
0347 
0348   // Try to recover from cases where adding compatible track was not possible
0349   auto resRec = canRecoverFromNoCompatibleTracks(allTracks, seedTracks, vtx,
0350                                                  currentConstraint, fitterState,
0351                                                  vertexingOptions);
0352   if (!resRec.ok()) {
0353     return Result<bool>::failure(resRec.error());
0354   }
0355 
0356   return Result<bool>::success(*resRec);
0357 }
0358 
0359 std::pair<int, bool> AdaptiveMultiVertexFinder::checkVertexAndCompatibleTracks(
0360     Vertex& vtx, const std::vector<InputTrack>& seedTracks,
0361     VertexFitterState& fitterState, bool useVertexConstraintInFit) const {
0362   bool isGoodVertex = false;
0363   int nCompatibleTracks = 0;
0364   for (const auto& trk : fitterState.vtxInfoMap[&vtx].trackLinks) {
0365     const auto& trkAtVtx =
0366         fitterState.tracksAtVerticesMap.at(std::make_pair(trk, &vtx));
0367     if ((trkAtVtx.vertexCompatibility < m_cfg.maxVertexChi2 &&
0368          m_cfg.useFastCompatibility) ||
0369         (trkAtVtx.trackWeight > m_cfg.minWeight &&
0370          trkAtVtx.chi2Track < m_cfg.maxVertexChi2 &&
0371          !m_cfg.useFastCompatibility)) {
0372       // TODO: Understand why looking for compatible tracks only in seed tracks
0373       // and not also in all tracks
0374       if (rangeContainsValue(seedTracks, trk)) {
0375         nCompatibleTracks++;
0376         ACTS_DEBUG("Compatible track found.");
0377 
0378         if (m_cfg.addSingleTrackVertices && useVertexConstraintInFit) {
0379           isGoodVertex = true;
0380           break;
0381         }
0382         if (nCompatibleTracks > 1) {
0383           isGoodVertex = true;
0384           break;
0385         }
0386       }
0387     }
0388   }  // end loop over all tracks at vertex
0389 
0390   return {nCompatibleTracks, isGoodVertex};
0391 }
0392 
0393 void AdaptiveMultiVertexFinder::removeCompatibleTracksFromSeedTracks(
0394     Vertex& vtx, std::vector<InputTrack>& seedTracks,
0395     VertexFitterState& fitterState,
0396     std::vector<InputTrack>& removedSeedTracks) const {
0397   for (const auto& trk : fitterState.vtxInfoMap[&vtx].trackLinks) {
0398     const auto& trkAtVtx =
0399         fitterState.tracksAtVerticesMap.at(std::make_pair(trk, &vtx));
0400     if ((trkAtVtx.vertexCompatibility < m_cfg.maxVertexChi2 &&
0401          m_cfg.useFastCompatibility) ||
0402         (trkAtVtx.trackWeight > m_cfg.minWeight &&
0403          trkAtVtx.chi2Track < m_cfg.maxVertexChi2 &&
0404          !m_cfg.useFastCompatibility)) {
0405       // Find and remove track from seedTracks
0406       auto foundSeedIter = std::ranges::find(seedTracks, trk);
0407       if (foundSeedIter != seedTracks.end()) {
0408         seedTracks.erase(foundSeedIter);
0409         removedSeedTracks.push_back(trk);
0410       }
0411     }
0412   }
0413 }
0414 
0415 Result<void> AdaptiveMultiVertexFinder::removeTrackIfIncompatible(
0416     Vertex& vtx, std::vector<InputTrack>& seedTracks,
0417     VertexFitterState& fitterState, std::vector<InputTrack>& removedSeedTracks,
0418     const GeometryContext& geoCtx) const {
0419   // Try to find the track with highest compatibility
0420   double maxCompatibility = 0;
0421 
0422   auto maxCompSeedIt = seedTracks.end();
0423   std::optional<InputTrack> removedTrack = std::nullopt;
0424   for (const auto& trk : fitterState.vtxInfoMap[&vtx].trackLinks) {
0425     const auto& trkAtVtx =
0426         fitterState.tracksAtVerticesMap.at(std::make_pair(trk, &vtx));
0427     double compatibility = trkAtVtx.vertexCompatibility;
0428     if (compatibility > maxCompatibility) {
0429       // Try to find track in seed tracks
0430       auto foundSeedIter = std::ranges::find(seedTracks, trk);
0431       if (foundSeedIter != seedTracks.end()) {
0432         maxCompatibility = compatibility;
0433         maxCompSeedIt = foundSeedIter;
0434         removedTrack = trk;
0435       }
0436     }
0437   }
0438   if (maxCompSeedIt != seedTracks.end()) {
0439     // Remove track with highest compatibility from seed tracks
0440     seedTracks.erase(maxCompSeedIt);
0441     removedSeedTracks.push_back(removedTrack.value());
0442   } else {
0443     // Could not find any seed with compatibility > 0, use alternative
0444     // method to remove a track from seed tracks: Closest track in z to
0445     // vtx candidate
0446     double smallestDeltaZ = std::numeric_limits<double>::max();
0447     auto smallestDzSeedIter = seedTracks.end();
0448     for (unsigned int i = 0; i < seedTracks.size(); i++) {
0449       auto pos = m_cfg.extractParameters(seedTracks[i]).position(geoCtx);
0450       double zDistance = std::abs(pos[eZ] - vtx.position()[eZ]);
0451       if (zDistance < smallestDeltaZ) {
0452         smallestDeltaZ = zDistance;
0453         smallestDzSeedIter = seedTracks.begin() + i;
0454         removedTrack = seedTracks[i];
0455       }
0456     }
0457     if (smallestDzSeedIter != seedTracks.end()) {
0458       seedTracks.erase(smallestDzSeedIter);
0459       removedSeedTracks.push_back(removedTrack.value());
0460     } else {
0461       ACTS_ERROR("No track found to remove. Stop vertex finding now.");
0462       return Result<void>::failure(VertexingError::CouldNotRemoveTrack);
0463     }
0464   }
0465   return {};
0466 }
0467 
0468 Result<bool> AdaptiveMultiVertexFinder::keepNewVertex(
0469     Vertex& vtx, const std::vector<Vertex*>& allVertices,
0470     VertexFitterState& fitterState) const {
0471   double contamination = 0.;
0472   double contaminationNum = 0;
0473   double contaminationDeNom = 0;
0474   for (const auto& trk : fitterState.vtxInfoMap[&vtx].trackLinks) {
0475     const auto& trkAtVtx =
0476         fitterState.tracksAtVerticesMap.at(std::make_pair(trk, &vtx));
0477     double trackWeight = trkAtVtx.trackWeight;
0478     contaminationNum += trackWeight * (1. - trackWeight);
0479     // MARK: fpeMaskBegin(FLTUND, 1, #2590)
0480     contaminationDeNom += trackWeight * trackWeight;
0481     // MARK: fpeMaskEnd(FLTUND)
0482   }
0483   if (contaminationDeNom != 0) {
0484     contamination = contaminationNum / contaminationDeNom;
0485   }
0486   if (contamination > m_cfg.maximumVertexContamination) {
0487     return Result<bool>::success(false);
0488   }
0489 
0490   auto isMergedVertexResult = isMergedVertex(vtx, allVertices);
0491   if (!isMergedVertexResult.ok()) {
0492     return Result<bool>::failure(isMergedVertexResult.error());
0493   }
0494 
0495   if (*isMergedVertexResult) {
0496     return Result<bool>::success(false);
0497   }
0498 
0499   return Result<bool>::success(true);
0500 }
0501 
0502 Result<bool> AdaptiveMultiVertexFinder::isMergedVertex(
0503     const Vertex& vtx, const std::vector<Vertex*>& allVertices) const {
0504   const Vector4& candidatePos = vtx.fullPosition();
0505   const SquareMatrix4& candidateCov = vtx.fullCovariance();
0506 
0507   for (const auto otherVtx : allVertices) {
0508     if (&vtx == otherVtx) {
0509       continue;
0510     }
0511     const Vector4& otherPos = otherVtx->fullPosition();
0512     const SquareMatrix4& otherCov = otherVtx->fullCovariance();
0513 
0514     double significance = 0;
0515     if (!m_cfg.doFullSplitting) {
0516       if (m_cfg.useTime) {
0517         const Vector2 deltaZT = otherPos.tail<2>() - candidatePos.tail<2>();
0518         SquareMatrix2 sumCovZT = candidateCov.bottomRightCorner<2, 2>() +
0519                                  otherCov.bottomRightCorner<2, 2>();
0520         auto sumCovZTInverse = safeInverse(sumCovZT);
0521         if (!sumCovZTInverse) {
0522           ACTS_ERROR("Vertex z-t covariance matrix is singular.");
0523           ACTS_ERROR("sumCovZT:\n" << sumCovZT);
0524           return Result<bool>::failure(VertexingError::SingularMatrix);
0525         }
0526         significance = std::sqrt(deltaZT.dot(*sumCovZTInverse * deltaZT));
0527       } else {
0528         const double deltaZPos = otherPos[eZ] - candidatePos[eZ];
0529         const double sumVarZ = otherCov(eZ, eZ) + candidateCov(eZ, eZ);
0530         if (sumVarZ <= 0) {
0531           ACTS_ERROR("Variance of the vertex's z-coordinate is not positive.");
0532           ACTS_ERROR("sumVarZ:\n" << sumVarZ);
0533           return Result<bool>::failure(VertexingError::SingularMatrix);
0534         }
0535         // Use only z significance
0536         significance = std::abs(deltaZPos) / std::sqrt(sumVarZ);
0537       }
0538     } else {
0539       if (m_cfg.useTime) {
0540         // Use 4D information for significance
0541         const Vector4 deltaPos = otherPos - candidatePos;
0542         SquareMatrix4 sumCov = candidateCov + otherCov;
0543         auto sumCovInverse = safeInverse(sumCov);
0544         if (!sumCovInverse) {
0545           ACTS_ERROR("Vertex 4D covariance matrix is singular.");
0546           ACTS_ERROR("sumCov:\n" << sumCov);
0547           return Result<bool>::failure(VertexingError::SingularMatrix);
0548         }
0549         significance = std::sqrt(deltaPos.dot(*sumCovInverse * deltaPos));
0550       } else {
0551         // Use 3D information for significance
0552         const Vector3 deltaPos = otherPos.head<3>() - candidatePos.head<3>();
0553         SquareMatrix3 sumCov =
0554             candidateCov.topLeftCorner<3, 3>() + otherCov.topLeftCorner<3, 3>();
0555         auto sumCovInverse = safeInverse(sumCov);
0556         if (!sumCovInverse) {
0557           ACTS_ERROR("Vertex 3D covariance matrix is singular.");
0558           ACTS_ERROR("sumCov:\n" << sumCov);
0559           return Result<bool>::failure(VertexingError::SingularMatrix);
0560         }
0561         significance = std::sqrt(deltaPos.dot(*sumCovInverse * deltaPos));
0562       }
0563     }
0564     if (significance < 0.) {
0565       ACTS_ERROR(
0566           "Found a negative significance (i.e., a negative chi2) when checking "
0567           "if vertices are merged. This should never happen since the vertex "
0568           "covariance should be positive definite, and thus its inverse "
0569           "should be positive definite as well.");
0570       return Result<bool>::failure(VertexingError::MatrixNotPositiveDefinite);
0571     }
0572     if (significance < m_cfg.maxMergeVertexSignificance) {
0573       return Result<bool>::success(true);
0574     }
0575   }
0576   return Result<bool>::success(false);
0577 }
0578 
0579 Result<void> AdaptiveMultiVertexFinder::deleteLastVertex(
0580     Vertex& vtx, std::vector<std::unique_ptr<Vertex>>& allVertices,
0581     std::vector<Vertex*>& allVerticesPtr, VertexFitterState& fitterState,
0582     const VertexingOptions& vertexingOptions) const {
0583   allVertices.pop_back();
0584   allVerticesPtr.pop_back();
0585 
0586   // Update fitter state with removed vertex candidate
0587   fitterState.removeVertexFromMultiMap(vtx);
0588   // fitterState.vertexCollection contains all vertices that will be fit. When
0589   // we called addVtxToFit, vtx and all vertices that share tracks with vtx were
0590   // added to vertexCollection. Now, we want to refit the same set of vertices
0591   // excluding vx. Therefore, we remove vtx from vertexCollection.
0592   auto removeResult = fitterState.removeVertexFromCollection(vtx, logger());
0593   if (!removeResult.ok()) {
0594     return removeResult.error();
0595   }
0596 
0597   for (auto& [key, value] : fitterState.tracksAtVerticesMap) {
0598     // Delete all linearized tracks for current (bad) vertex
0599     if (key.second == &vtx) {
0600       value.isLinearized = false;
0601     }
0602   }
0603 
0604   // If no vertices share tracks with vtx we don't need to refit
0605   if (fitterState.vertexCollection.empty()) {
0606     return {};
0607   }
0608 
0609   // Do the fit with removed vertex
0610   auto fitResult = m_cfg.vertexFitter.fit(fitterState, vertexingOptions);
0611   if (!fitResult.ok()) {
0612     return fitResult.error();
0613   }
0614 
0615   return {};
0616 }
0617 
0618 Result<std::vector<Vertex>> AdaptiveMultiVertexFinder::getVertexOutputList(
0619     const std::vector<Vertex*>& allVerticesPtr,
0620     VertexFitterState& fitterState) const {
0621   std::vector<Vertex> outputVec;
0622   for (auto vtx : allVerticesPtr) {
0623     auto& outVtx = *vtx;
0624     std::vector<TrackAtVertex> tracksAtVtx;
0625     for (const auto& trk : fitterState.vtxInfoMap[vtx].trackLinks) {
0626       tracksAtVtx.push_back(
0627           fitterState.tracksAtVerticesMap.at(std::make_pair(trk, vtx)));
0628     }
0629     outVtx.setTracksAtVertex(std::move(tracksAtVtx));
0630     outputVec.push_back(outVtx);
0631   }
0632   return Result<std::vector<Vertex>>(outputVec);
0633 }
0634 
0635 }  // namespace Acts