File indexing completed on 2026-10-20 08:38:47
0001
0002
0003
0004
0005
0006
0007
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
0038
0039
0040
0041
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
0055 cache.annealingState = AnnealingUtility::State();
0056
0057
0058
0059
0060
0061
0062 bool isSmallShift = true;
0063
0064
0065 unsigned int nIter = 0;
0066
0067
0068 while (nIter < m_cfg.maxIterations &&
0069 (!cache.annealingState.equilibriumReached || !isSmallShift)) {
0070
0071 for (auto vtx : problem.vertices) {
0072 VertexScratch& scratch = scratchFor(problem, vtx, cache);
0073 scratch.relinearize = false;
0074
0075
0076
0077 scratch.oldPosition = vtx->fullPosition();
0078
0079
0080
0081
0082
0083 Vector2 xyDiff = scratch.oldPosition.template head<2>() -
0084 scratch.linPoint.template head<2>();
0085 if (xyDiff.norm() > m_cfg.maxDistToLinPoint) {
0086
0087 scratch.relinearize = true;
0088
0089
0090 auto prepareVertexResult =
0091 prepareVertexForFit(problem, vtx, vertexingOptions, cache);
0092 if (!prepareVertexResult.ok()) {
0093
0094 if (logger().doPrint(Logging::DEBUG)) {
0095 logDebugData(problem, vertexingOptions.geoContext, cache);
0096 }
0097 return prepareVertexResult.error();
0098 }
0099 }
0100
0101
0102
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
0117
0118 auto setCompatibilitiesResult =
0119 setAllVertexCompatibilities(problem, vtx, vertexingOptions, cache);
0120 if (!setCompatibilitiesResult.ok()) {
0121
0122 if (logger().doPrint(Logging::DEBUG)) {
0123 logDebugData(problem, vertexingOptions.geoContext, cache);
0124 }
0125 return setCompatibilitiesResult.error();
0126 }
0127 }
0128
0129
0130 auto setWeightsResult =
0131 setWeightsAndUpdate(problem, vertexingOptions, cache);
0132 if (!setWeightsResult.ok()) {
0133
0134 if (logger().doPrint(Logging::DEBUG)) {
0135 logDebugData(problem, vertexingOptions.geoContext, cache);
0136 }
0137 return setWeightsResult.error();
0138 }
0139
0140
0141
0142 if (!cache.annealingState.equilibriumReached) {
0143 m_cfg.annealingTool.anneal(cache.annealingState);
0144 }
0145
0146 isSmallShift = checkSmallShift(problem, cache);
0147 ++nIter;
0148 }
0149
0150
0151
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
0174 std::vector<Vertex*> lastIterAddedVertices = newVertices;
0175
0176 std::vector<Vertex*> currentIterAddedVertices;
0177
0178
0179
0180 while (!lastIterAddedVertices.empty()) {
0181 for (auto& lastIterAddedVertex : lastIterAddedVertices) {
0182
0183 const std::vector<InputTrack>& trks =
0184 problem.candidates[lastIterAddedVertex].trackLinks;
0185 for (const auto& trk : trks) {
0186
0187
0188
0189
0190 auto [begin, end] = problem.trackToVertices.equal_range(trk);
0191
0192 for (auto it = begin; it != end; ++it) {
0193
0194
0195 auto vtxToFit = it->second;
0196
0197 if (!isAlreadyInList(vtxToFit, verticesToFit)) {
0198 verticesToFit.push_back(vtxToFit);
0199
0200
0201 if (vtxToFit != lastIterAddedVertex) {
0202 currentIterAddedVertices.push_back(vtxToFit);
0203 }
0204 }
0205 }
0206 }
0207 }
0208
0209 lastIterAddedVertices = currentIterAddedVertices;
0210 currentIterAddedVertices.clear();
0211 }
0212
0213 problem.vertices = verticesToFit;
0214
0215 for (Vertex* newVertexPtr : newVertices) {
0216
0217 auto res =
0218 prepareVertexForFit(problem, newVertexPtr, vertexingOptions, cache);
0219 if (!res.ok()) {
0220
0221 if (logger().doPrint(Logging::DEBUG)) {
0222 logDebugData(problem, vertexingOptions.geoContext, cache);
0223 }
0224 return res.error();
0225 }
0226 }
0227
0228
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
0246 const Vector3& seedPos =
0247 problem.candidates.at(vtx).seedPosition.template head<3>();
0248 VertexScratch& scratch = scratchFor(problem, vtx, cache);
0249
0250
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
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
0271
0272 for (const auto& trk : candidate.trackLinks) {
0273 auto& trkAtVtx = problem.tracksAtVertices.at(std::make_pair(trk, vtx));
0274
0275
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
0285 scratch.impactParams3D.try_emplace(trk, res.value());
0286 }
0287
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
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
0332
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
0346
0347
0348
0349 KalmanVertexUpdater::updateVertexWithTrack(*vtx, trkAtVtx,
0350 m_cfg.useTime ? 4 : 3);
0351 } else {
0352 ACTS_VERBOSE("Track weight too low. Skip track.");
0353 }
0354 }
0355 ACTS_VERBOSE("New vertex position: " << vtx->fullPosition().transpose());
0356 }
0357
0358 return {};
0359 }
0360
0361 std::vector<double>
0362 AdaptiveMultiVertexFitter::collectTrackToVertexCompatibilities(
0363 const VertexFitProblem& problem, const InputTrack& trk) const {
0364
0365 std::vector<double> trkToVtxCompatibilities;
0366
0367
0368
0369
0370 auto [begin, end] = problem.trackToVertices.equal_range(trk);
0371
0372 trkToVtxCompatibilities.reserve(std::distance(begin, end));
0373
0374 for (auto it = begin; it != end; ++it) {
0375
0376
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
0407
0408
0409
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
0464
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
0491
0492
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 }