File indexing completed on 2026-09-13 08:19:28
0001
0002
0003
0004
0005
0006
0007
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
0049 const std::vector<InputTrack>& origTracks = trackVector;
0050
0051 std::vector<InputTrack> seedTracks = trackVector;
0052
0053
0054 std::vector<Vertex> vertexCollection;
0055
0056 int nInterations = 0;
0057
0058 while (seedTracks.size() > 1 && nInterations < m_cfg.maxVertices) {
0059
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
0074
0075
0076 std::vector<InputTrack> tracksToFit;
0077 std::vector<InputTrack> tracksToFitSplitVertex;
0078
0079
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
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
0120 ACTS_DEBUG("Vertex position after fit: "
0121 << currentVertex.fullPosition().transpose());
0122
0123
0124 double ndf = currentVertex.fitQuality().second;
0125 double ndfSplitVertex = currentSplitVertex.fitQuality().second;
0126
0127
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
0141
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 }
0152
0153 if (isGoodVertex) {
0154 Result<void> removeRes = removeUsedCompatibleTracks(
0155 currentVertex, tracksToFit, seedTracks, vertexingOptions, state);
0156 if (!removeRes.ok()) {
0157 return removeRes.error();
0158 }
0159
0160 ACTS_DEBUG(
0161 "Number of seed tracks after removal of compatible tracks "
0162 "and outliers: "
0163 << seedTracks.size());
0164 }
0165 }
0166
0167
0168 bool isGoodSplitVertex = false;
0169 if (m_cfg.createSplitVertices) {
0170 isGoodSplitVertex = (ndfSplitVertex > 0 && nTracksAtSplitVertex >= 2);
0171
0172 if (!isGoodSplitVertex) {
0173 removeTracks(tracksToFitSplitVertex, seedTracks);
0174 } else {
0175 Result<void> removeRes = removeUsedCompatibleTracks(
0176 currentSplitVertex, tracksToFitSplitVertex, seedTracks,
0177 vertexingOptions, state);
0178 if (!removeRes.ok()) {
0179 return removeRes.error();
0180 }
0181 }
0182 }
0183
0184 if (isGoodVertex) {
0185 vertexCollection.push_back(currentVertex);
0186 }
0187 if (isGoodSplitVertex && m_cfg.createSplitVertices) {
0188 vertexCollection.push_back(currentSplitVertex);
0189 }
0190
0191 nInterations++;
0192 }
0193
0194 return vertexCollection;
0195 }
0196
0197 auto Acts::IterativeVertexFinder::getVertexSeed(
0198 State& state, const std::vector<InputTrack>& seedTracks,
0199 const VertexingOptions& vertexingOptions) const
0200 -> Result<std::optional<Vertex>> {
0201 auto finderState = m_cfg.seedFinder->makeState(state.magContext);
0202 auto res = m_cfg.seedFinder->find(seedTracks, vertexingOptions, finderState);
0203
0204 if (!res.ok()) {
0205 ACTS_ERROR("Internal seeding error. Number of input tracks: "
0206 << seedTracks.size());
0207 return VertexingError::SeedingError;
0208 }
0209 const auto& seedVector = *res;
0210
0211 ACTS_DEBUG("Found " << seedVector.size() << " seeds");
0212
0213 if (seedVector.empty()) {
0214 return std::nullopt;
0215 }
0216 const Vertex& seedVertex = seedVector.back();
0217
0218 ACTS_DEBUG("Use " << seedTracks.size() << " tracks for vertex seed finding.");
0219 ACTS_DEBUG(
0220 "Found seed at position: " << seedVertex.fullPosition().transpose());
0221
0222 return seedVertex;
0223 }
0224
0225 inline void Acts::IterativeVertexFinder::removeTracks(
0226 const std::vector<InputTrack>& tracksToRemove,
0227 std::vector<InputTrack>& seedTracks) const {
0228 for (const auto& trk : tracksToRemove) {
0229 const BoundTrackParameters& params = m_cfg.extractParameters(trk);
0230
0231 auto foundIter =
0232 std::ranges::find_if(seedTracks, [¶ms, this](const auto seedTrk) {
0233 return params == m_cfg.extractParameters(seedTrk);
0234 });
0235 if (foundIter != seedTracks.end()) {
0236
0237 seedTracks.erase(foundIter);
0238 } else {
0239 ACTS_WARNING("Track to be removed not found in seed tracks.");
0240 }
0241 }
0242 }
0243
0244 Acts::Result<double> Acts::IterativeVertexFinder::getCompatibility(
0245 const BoundTrackParameters& params, const Vertex& vertex,
0246 const Surface& perigeeSurface, const VertexingOptions& vertexingOptions,
0247 State& state) const {
0248
0249 auto result =
0250 m_cfg.trackLinearizer(params, vertex.fullPosition()[3], perigeeSurface,
0251 vertexingOptions.geoContext,
0252 vertexingOptions.magFieldContext, state.fieldCache);
0253 if (!result.ok()) {
0254 return result.error();
0255 }
0256
0257 auto linTrack = std::move(*result);
0258
0259
0260 SquareMatrix2 weightReduced =
0261 linTrack.covarianceAtPCA.template block<2, 2>(0, 0);
0262
0263 SquareMatrix2 errorVertexReduced =
0264 (linTrack.positionJacobian *
0265 (vertex.fullCovariance() * linTrack.positionJacobian.transpose()))
0266 .template block<2, 2>(0, 0);
0267 weightReduced += errorVertexReduced;
0268 weightReduced = weightReduced.inverse().eval();
0269
0270
0271 Vector2 trackParameters2D =
0272 linTrack.parametersAtPCA.template block<2, 1>(0, 0);
0273 double compatibility =
0274 trackParameters2D.dot(weightReduced * trackParameters2D);
0275
0276 return compatibility;
0277 }
0278
0279 Acts::Result<void> Acts::IterativeVertexFinder::removeUsedCompatibleTracks(
0280 Vertex& vertex, std::vector<InputTrack>& tracksToFit,
0281 std::vector<InputTrack>& seedTracks,
0282 const VertexingOptions& vertexingOptions, State& state) const {
0283 std::vector<TrackAtVertex> tracksAtVertex = vertex.tracks();
0284
0285 for (const auto& trackAtVtx : tracksAtVertex) {
0286
0287 if (trackAtVtx.trackWeight < m_cfg.cutOffTrackWeight) {
0288
0289 continue;
0290 }
0291
0292 auto foundSeedIter =
0293 std::ranges::find(seedTracks, trackAtVtx.originalParams);
0294 if (foundSeedIter != seedTracks.end()) {
0295 seedTracks.erase(foundSeedIter);
0296 } else {
0297 ACTS_WARNING("Track trackAtVtx not found in seedTracks!");
0298 }
0299
0300
0301 auto foundFitIter =
0302 std::ranges::find(tracksToFit, trackAtVtx.originalParams);
0303 if (foundFitIter != tracksToFit.end()) {
0304 tracksToFit.erase(foundFitIter);
0305 } else {
0306 ACTS_WARNING("Track trackAtVtx not found in tracksToFit!");
0307 }
0308 }
0309
0310 ACTS_DEBUG("After removal of tracks belonging to vertex, "
0311 << seedTracks.size() << " seed tracks left.");
0312
0313
0314
0315
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
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
0335
0336 if (chi2 < m_cfg.maximumChi2cutForSeeding) {
0337 auto foundIter = std::ranges::find(seedTracks, trk);
0338 if (foundIter != seedTracks.end()) {
0339
0340 seedTracks.erase(foundIter);
0341 }
0342
0343 } else {
0344
0345
0346 auto foundIter =
0347 std::ranges::find_if(tracksAtVertex, [&trk](const auto& trkAtVtx) {
0348 return trk == trkAtVtx.originalParams;
0349 });
0350 if (foundIter != tracksAtVertex.end()) {
0351
0352 tracksAtVertex.erase(foundIter);
0353 }
0354 }
0355 }
0356
0357
0358 vertex.setTracksAtVertex(tracksAtVertex);
0359
0360 return {};
0361 }
0362
0363 Acts::Result<void> Acts::IterativeVertexFinder::fillTracksToFit(
0364 const std::vector<InputTrack>& seedTracks, const Vertex& seedVertex,
0365 std::vector<InputTrack>& tracksToFitOut,
0366 std::vector<InputTrack>& tracksToFitSplitVertexOut,
0367 const VertexingOptions& vertexingOptions, State& state) const {
0368 int numberOfTracks = seedTracks.size();
0369
0370
0371 int count = 0;
0372
0373 for (const auto& sTrack : seedTracks) {
0374
0375
0376 if (numberOfTracks <= 2) {
0377 tracksToFitOut.push_back(sTrack);
0378 ++count;
0379 } else if (numberOfTracks <= 4 && !m_cfg.createSplitVertices) {
0380 tracksToFitOut.push_back(sTrack);
0381 ++count;
0382 } else if (numberOfTracks <= 4 * m_cfg.splitVerticesTrkInvFraction &&
0383 m_cfg.createSplitVertices) {
0384 if (count % m_cfg.splitVerticesTrkInvFraction != 0) {
0385 tracksToFitOut.push_back(sTrack);
0386 ++count;
0387 } else {
0388 tracksToFitSplitVertexOut.push_back(sTrack);
0389 ++count;
0390 }
0391 }
0392
0393
0394 else {
0395 const BoundTrackParameters& sTrackParams =
0396 m_cfg.extractParameters(sTrack);
0397 auto distanceRes = m_cfg.ipEst.calculateDistance(
0398 vertexingOptions.geoContext, sTrackParams, seedVertex.position(),
0399 state.ipState);
0400 if (!distanceRes.ok()) {
0401 return distanceRes.error();
0402 }
0403
0404 if (!sTrackParams.covariance()) {
0405 return VertexingError::NoCovariance;
0406 }
0407
0408
0409 double hypotVariance =
0410 std::sqrt((*(sTrackParams.covariance()))(eBoundLoc0, eBoundLoc0) +
0411 (*(sTrackParams.covariance()))(eBoundLoc1, eBoundLoc1));
0412
0413 if (hypotVariance == 0.) {
0414 ACTS_WARNING(
0415 "Track impact parameter covariances are zero. Track was not "
0416 "assigned to vertex.");
0417 continue;
0418 }
0419
0420 if (*distanceRes / hypotVariance < m_cfg.significanceCutSeeding) {
0421 if (!m_cfg.createSplitVertices ||
0422 count % m_cfg.splitVerticesTrkInvFraction != 0) {
0423 tracksToFitOut.push_back(sTrack);
0424 ++count;
0425 } else {
0426 tracksToFitSplitVertexOut.push_back(sTrack);
0427 ++count;
0428 }
0429 }
0430 }
0431 }
0432 return {};
0433 }
0434
0435 Acts::Result<bool> Acts::IterativeVertexFinder::reassignTracksToNewVertex(
0436 std::vector<Vertex>& vertexCollection, Vertex& currentVertex,
0437 std::vector<InputTrack>& tracksToFit, std::vector<InputTrack>& seedTracks,
0438 const std::vector<InputTrack>& ,
0439 const VertexingOptions& vertexingOptions, State& state) const {
0440 int numberOfAddedTracks = 0;
0441
0442 const std::shared_ptr<PerigeeSurface> currentVertexPerigeeSurface =
0443 Surface::makeShared<PerigeeSurface>(
0444 VectorHelpers::position(currentVertex.fullPosition()));
0445
0446
0447
0448 for (auto& vertexIt : vertexCollection) {
0449
0450 std::vector<TrackAtVertex> tracksAtVertex = vertexIt.tracks();
0451 auto tracksBegin = tracksAtVertex.begin();
0452 auto tracksEnd = tracksAtVertex.end();
0453
0454 const std::shared_ptr<PerigeeSurface> vertexItPerigeeSurface =
0455 Surface::makeShared<PerigeeSurface>(
0456 VectorHelpers::position(vertexIt.fullPosition()));
0457
0458 for (auto tracksIter = tracksBegin; tracksIter != tracksEnd;) {
0459
0460
0461 if (tracksIter->trackWeight > m_cfg.cutOffTrackWeightReassign) {
0462 tracksIter++;
0463 continue;
0464 }
0465
0466 BoundTrackParameters origParams =
0467 m_cfg.extractParameters(tracksIter->originalParams);
0468
0469
0470 auto resultNew = getCompatibility(origParams, currentVertex,
0471 *currentVertexPerigeeSurface,
0472 vertexingOptions, state);
0473 if (!resultNew.ok()) {
0474 return Result<bool>::failure(resultNew.error());
0475 }
0476 double chi2NewVtx = *resultNew;
0477
0478 auto resultOld =
0479 getCompatibility(origParams, vertexIt, *vertexItPerigeeSurface,
0480 vertexingOptions, state);
0481 if (!resultOld.ok()) {
0482 return Result<bool>::failure(resultOld.error());
0483 }
0484 double chi2OldVtx = *resultOld;
0485
0486 ACTS_DEBUG("Compatibility to new vs old vertex: " << chi2NewVtx << " vs "
0487 << chi2OldVtx);
0488
0489 if (chi2NewVtx < chi2OldVtx) {
0490 tracksToFit.push_back(tracksIter->originalParams);
0491
0492
0493
0494
0495
0496 seedTracks.push_back(tracksIter->originalParams);
0497
0498
0499
0500
0501
0502
0503 numberOfAddedTracks += 1;
0504
0505
0506 tracksIter = tracksAtVertex.erase(tracksIter);
0507 tracksBegin = tracksAtVertex.begin();
0508 tracksEnd = tracksAtVertex.end();
0509
0510 }
0511
0512 else {
0513
0514 ++tracksIter;
0515 }
0516 }
0517
0518 vertexIt.setTracksAtVertex(tracksAtVertex);
0519 }
0520
0521 ACTS_DEBUG("Added " << numberOfAddedTracks
0522 << " tracks from old (other) vertices for new fit");
0523
0524
0525
0526
0527 currentVertex = Vertex();
0528 if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
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 } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
0537 auto fitResult =
0538 m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0539 if (fitResult.ok()) {
0540 currentVertex = std::move(*fitResult);
0541 } else {
0542 return Result<bool>::success(false);
0543 }
0544 }
0545
0546
0547 double ndf = currentVertex.fitQuality().second;
0548
0549
0550 int nTracksAtVertex = countSignificantTracks(currentVertex);
0551
0552 bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0553 nTracksAtVertex >= 2) ||
0554 (vertexingOptions.useConstraintInFit && ndf > 3 &&
0555 nTracksAtVertex >= 2));
0556
0557 if (!isGoodVertex) {
0558 removeTracks(tracksToFit, seedTracks);
0559
0560 ACTS_DEBUG("Going to new iteration with "
0561 << seedTracks.size() << "seed tracks after BAD vertex.");
0562 }
0563
0564 return Result<bool>::success(isGoodVertex);
0565 }
0566
0567 int Acts::IterativeVertexFinder::countSignificantTracks(
0568 const Vertex& vtx) const {
0569 return std::count_if(vtx.tracks().begin(), vtx.tracks().end(),
0570 [this](const TrackAtVertex& trk) {
0571 return trk.trackWeight > m_cfg.cutOffTrackWeight;
0572 });
0573 }