File indexing completed on 2025-08-05 08:09:43
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 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 }
0163
0164
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
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 }
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
0224 auto foundIter =
0225 std::find_if(seedTracks.begin(), seedTracks.end(),
0226 [¶ms, this](const auto seedTrk) {
0227 return params == m_cfg.extractParameters(seedTrk);
0228 });
0229 if (foundIter != seedTracks.end()) {
0230
0231 seedTracks.erase(foundIter);
0232 } else {
0233 ACTS_WARNING("Track to be removed not found in seed tracks.")
0234 }
0235 }
0236 }
0237
0238 Acts::Result<double> Acts::IterativeVertexFinder::getCompatibility(
0239 const BoundTrackParameters& params, const Vertex& vertex,
0240 const Surface& perigeeSurface, const VertexingOptions& vertexingOptions,
0241 State& state) const {
0242
0243 auto result =
0244 m_cfg.trackLinearizer(params, vertex.fullPosition()[3], perigeeSurface,
0245 vertexingOptions.geoContext,
0246 vertexingOptions.magFieldContext, state.fieldCache);
0247 if (!result.ok()) {
0248 return result.error();
0249 }
0250
0251 auto linTrack = std::move(*result);
0252
0253
0254 SquareMatrix2 weightReduced =
0255 linTrack.covarianceAtPCA.template block<2, 2>(0, 0);
0256
0257 SquareMatrix2 errorVertexReduced =
0258 (linTrack.positionJacobian *
0259 (vertex.fullCovariance() * linTrack.positionJacobian.transpose()))
0260 .template block<2, 2>(0, 0);
0261 weightReduced += errorVertexReduced;
0262 weightReduced = weightReduced.inverse().eval();
0263
0264
0265 Vector2 trackParameters2D =
0266 linTrack.parametersAtPCA.template block<2, 1>(0, 0);
0267 double compatibility =
0268 trackParameters2D.dot(weightReduced * trackParameters2D);
0269
0270 return compatibility;
0271 }
0272
0273 Acts::Result<void> Acts::IterativeVertexFinder::removeUsedCompatibleTracks(
0274 Vertex& vertex, std::vector<InputTrack>& tracksToFit,
0275 std::vector<InputTrack>& seedTracks,
0276 const VertexingOptions& vertexingOptions, State& state) const {
0277 std::vector<TrackAtVertex> tracksAtVertex = vertex.tracks();
0278
0279 for (const auto& trackAtVtx : tracksAtVertex) {
0280
0281 if (trackAtVtx.trackWeight < m_cfg.cutOffTrackWeight) {
0282
0283 continue;
0284 }
0285
0286 auto foundSeedIter =
0287 std::find_if(seedTracks.begin(), seedTracks.end(),
0288 [&trackAtVtx](const auto& seedTrk) {
0289 return trackAtVtx.originalParams == seedTrk;
0290 });
0291 if (foundSeedIter != seedTracks.end()) {
0292 seedTracks.erase(foundSeedIter);
0293 } else {
0294 ACTS_WARNING("Track trackAtVtx not found in seedTracks!");
0295 }
0296
0297
0298 auto foundFitIter =
0299 std::find_if(tracksToFit.begin(), tracksToFit.end(),
0300 [&trackAtVtx](const auto& fitTrk) {
0301 return trackAtVtx.originalParams == fitTrk;
0302 });
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 =
0338 std::find_if(seedTracks.begin(), seedTracks.end(),
0339 [&trk](const auto& seedTrk) { return trk == seedTrk; });
0340 if (foundIter != seedTracks.end()) {
0341
0342 seedTracks.erase(foundIter);
0343 }
0344
0345 } else {
0346
0347
0348 auto foundIter = std::find_if(
0349 tracksAtVertex.begin(), tracksAtVertex.end(),
0350 [&trk](auto trkAtVtx) { return trk == trkAtVtx.originalParams; });
0351 if (foundIter != tracksAtVertex.end()) {
0352
0353 tracksAtVertex.erase(foundIter);
0354 }
0355 }
0356 }
0357
0358
0359 vertex.setTracksAtVertex(tracksAtVertex);
0360
0361 return {};
0362 }
0363
0364 Acts::Result<void> Acts::IterativeVertexFinder::fillTracksToFit(
0365 const std::vector<InputTrack>& seedTracks, const Vertex& seedVertex,
0366 std::vector<InputTrack>& tracksToFitOut,
0367 std::vector<InputTrack>& tracksToFitSplitVertexOut,
0368 const VertexingOptions& vertexingOptions, State& state) const {
0369 int numberOfTracks = seedTracks.size();
0370
0371
0372 int count = 0;
0373
0374 for (const auto& sTrack : seedTracks) {
0375
0376
0377 if (numberOfTracks <= 2) {
0378 tracksToFitOut.push_back(sTrack);
0379 ++count;
0380 } else if (numberOfTracks <= 4 && !m_cfg.createSplitVertices) {
0381 tracksToFitOut.push_back(sTrack);
0382 ++count;
0383 } else if (numberOfTracks <= 4 * m_cfg.splitVerticesTrkInvFraction &&
0384 m_cfg.createSplitVertices) {
0385 if (count % m_cfg.splitVerticesTrkInvFraction != 0) {
0386 tracksToFitOut.push_back(sTrack);
0387 ++count;
0388 } else {
0389 tracksToFitSplitVertexOut.push_back(sTrack);
0390 ++count;
0391 }
0392 }
0393
0394
0395 else {
0396 const BoundTrackParameters& sTrackParams =
0397 m_cfg.extractParameters(sTrack);
0398 auto distanceRes = m_cfg.ipEst.calculateDistance(
0399 vertexingOptions.geoContext, sTrackParams, seedVertex.position(),
0400 state.ipState);
0401 if (!distanceRes.ok()) {
0402 return distanceRes.error();
0403 }
0404
0405 if (!sTrackParams.covariance()) {
0406 return VertexingError::NoCovariance;
0407 }
0408
0409
0410 double hypotVariance =
0411 sqrt((*(sTrackParams.covariance()))(eBoundLoc0, eBoundLoc0) +
0412 (*(sTrackParams.covariance()))(eBoundLoc1, eBoundLoc1));
0413
0414 if (hypotVariance == 0.) {
0415 ACTS_WARNING(
0416 "Track impact parameter covariances are zero. Track was not "
0417 "assigned to vertex.");
0418 continue;
0419 }
0420
0421 if (*distanceRes / hypotVariance < m_cfg.significanceCutSeeding) {
0422 if (!m_cfg.createSplitVertices ||
0423 count % m_cfg.splitVerticesTrkInvFraction != 0) {
0424 tracksToFitOut.push_back(sTrack);
0425 ++count;
0426 } else {
0427 tracksToFitSplitVertexOut.push_back(sTrack);
0428 ++count;
0429 }
0430 }
0431 }
0432 }
0433 return {};
0434 }
0435
0436 Acts::Result<bool> Acts::IterativeVertexFinder::reassignTracksToNewVertex(
0437 std::vector<Vertex>& vertexCollection, Vertex& currentVertex,
0438 std::vector<InputTrack>& tracksToFit, std::vector<InputTrack>& seedTracks,
0439 const std::vector<InputTrack>& ,
0440 const VertexingOptions& vertexingOptions, State& state) const {
0441 int numberOfAddedTracks = 0;
0442
0443 const std::shared_ptr<PerigeeSurface> currentVertexPerigeeSurface =
0444 Surface::makeShared<PerigeeSurface>(
0445 VectorHelpers::position(currentVertex.fullPosition()));
0446
0447
0448
0449 for (auto& vertexIt : vertexCollection) {
0450
0451 std::vector<TrackAtVertex> tracksAtVertex = vertexIt.tracks();
0452 auto tracksBegin = tracksAtVertex.begin();
0453 auto tracksEnd = tracksAtVertex.end();
0454
0455 const std::shared_ptr<PerigeeSurface> vertexItPerigeeSurface =
0456 Surface::makeShared<PerigeeSurface>(
0457 VectorHelpers::position(vertexIt.fullPosition()));
0458
0459 for (auto tracksIter = tracksBegin; tracksIter != tracksEnd;) {
0460
0461
0462 if (tracksIter->trackWeight > m_cfg.cutOffTrackWeightReassign) {
0463 tracksIter++;
0464 continue;
0465 }
0466
0467 BoundTrackParameters origParams =
0468 m_cfg.extractParameters(tracksIter->originalParams);
0469
0470
0471 auto resultNew = getCompatibility(origParams, currentVertex,
0472 *currentVertexPerigeeSurface,
0473 vertexingOptions, state);
0474 if (!resultNew.ok()) {
0475 return Result<bool>::failure(resultNew.error());
0476 }
0477 double chi2NewVtx = *resultNew;
0478
0479 auto resultOld =
0480 getCompatibility(origParams, vertexIt, *vertexItPerigeeSurface,
0481 vertexingOptions, state);
0482 if (!resultOld.ok()) {
0483 return Result<bool>::failure(resultOld.error());
0484 }
0485 double chi2OldVtx = *resultOld;
0486
0487 ACTS_DEBUG("Compatibility to new vs old vertex: " << chi2NewVtx << " vs "
0488 << chi2OldVtx);
0489
0490 if (chi2NewVtx < chi2OldVtx) {
0491 tracksToFit.push_back(tracksIter->originalParams);
0492
0493
0494
0495
0496
0497 seedTracks.push_back(tracksIter->originalParams);
0498
0499
0500
0501
0502
0503
0504 numberOfAddedTracks += 1;
0505
0506
0507 tracksIter = tracksAtVertex.erase(tracksIter);
0508 tracksBegin = tracksAtVertex.begin();
0509 tracksEnd = tracksAtVertex.end();
0510
0511 }
0512
0513 else {
0514
0515 ++tracksIter;
0516 }
0517 }
0518
0519 vertexIt.setTracksAtVertex(tracksAtVertex);
0520 }
0521
0522 ACTS_DEBUG("Added " << numberOfAddedTracks
0523 << " tracks from old (other) vertices for new fit");
0524
0525
0526
0527
0528 currentVertex = Vertex();
0529 if (vertexingOptions.useConstraintInFit && !tracksToFit.empty()) {
0530 auto fitResult =
0531 m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0532 if (fitResult.ok()) {
0533 currentVertex = std::move(*fitResult);
0534 } else {
0535 return Result<bool>::success(false);
0536 }
0537 } else if (!vertexingOptions.useConstraintInFit && tracksToFit.size() > 1) {
0538 auto fitResult =
0539 m_cfg.vertexFitter.fit(tracksToFit, vertexingOptions, state.fieldCache);
0540 if (fitResult.ok()) {
0541 currentVertex = std::move(*fitResult);
0542 } else {
0543 return Result<bool>::success(false);
0544 }
0545 }
0546
0547
0548 double ndf = currentVertex.fitQuality().second;
0549
0550
0551 int nTracksAtVertex = countSignificantTracks(currentVertex);
0552
0553 bool isGoodVertex = ((!vertexingOptions.useConstraintInFit && ndf > 0 &&
0554 nTracksAtVertex >= 2) ||
0555 (vertexingOptions.useConstraintInFit && ndf > 3 &&
0556 nTracksAtVertex >= 2));
0557
0558 if (!isGoodVertex) {
0559 removeTracks(tracksToFit, seedTracks);
0560
0561 ACTS_DEBUG("Going to new iteration with "
0562 << seedTracks.size() << "seed tracks after BAD vertex.");
0563 }
0564
0565 return Result<bool>::success(isGoodVertex);
0566 }
0567
0568 int Acts::IterativeVertexFinder::countSignificantTracks(
0569 const Vertex& vtx) const {
0570 return std::count_if(vtx.tracks().begin(), vtx.tracks().end(),
0571 [this](const TrackAtVertex& trk) {
0572 return trk.trackWeight > m_cfg.cutOffTrackWeight;
0573 });
0574 }