File indexing completed on 2026-09-22 07:58:35
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Vertexing/ImpactPointEstimator.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Propagator/PropagatorOptions.hpp"
0013 #include "Acts/Surfaces/PerigeeSurface.hpp"
0014 #include "Acts/Surfaces/PointSurface.hpp"
0015 #include "Acts/Utilities/AngleHelpers.hpp"
0016 #include "Acts/Utilities/Intersection.hpp"
0017 #include "Acts/Utilities/MathHelpers.hpp"
0018 #include "Acts/Vertexing/VertexingError.hpp"
0019
0020 namespace Acts {
0021
0022 namespace {
0023 template <typename vector_t>
0024 Result<double> getVertexCompatibilityImpl(const GeometryContext& gctx,
0025 const BoundTrackParameters* trkParams,
0026 const vector_t& vertexPos) {
0027 static constexpr int nDim = vector_t::RowsAtCompileTime;
0028 static_assert(nDim == 3 || nDim == 4,
0029 "The number of dimensions nDim must be either 3 or 4.");
0030
0031 static_assert(vector_t::RowsAtCompileTime == nDim,
0032 "The dimension of the vertex position vector must match nDim.");
0033
0034 if (trkParams == nullptr) {
0035 return VertexingError::EmptyInput;
0036 }
0037
0038
0039
0040 if (!trkParams->covariance().has_value()) {
0041 return VertexingError::NoCovariance;
0042 }
0043 SquareMatrix<nDim - 1> subCovMat;
0044 if constexpr (nDim == 3) {
0045 subCovMat = trkParams->spatialImpactParameterCovariance().value();
0046 } else {
0047 subCovMat = trkParams->impactParameterCovariance().value();
0048 }
0049 SquareMatrix<nDim - 1> weight = subCovMat.inverse();
0050
0051
0052
0053
0054
0055
0056
0057
0058 RotationMatrix3 surfaceAxes = trkParams->referenceSurface().referenceFrame(
0059 gctx, trkParams->position(gctx), trkParams->direction());
0060
0061 Vector3 surfaceOrigin = trkParams->referenceSurface().center(gctx);
0062
0063
0064 Vector3 xAxis = surfaceAxes.col(0);
0065 Vector3 yAxis = surfaceAxes.col(1);
0066
0067
0068
0069
0070
0071
0072 Vector3 originToVertex = vertexPos.template head<3>() - surfaceOrigin;
0073
0074
0075
0076 Vector<nDim - 1> localVertexCoords;
0077 localVertexCoords.template head<2>() =
0078 Vector2(originToVertex.dot(xAxis), originToVertex.dot(yAxis));
0079
0080 Vector<nDim - 1> localTrackCoords;
0081 localTrackCoords.template head<2>() =
0082 Vector2(trkParams->parameters()[eX], trkParams->parameters()[eY]);
0083
0084
0085 if constexpr (nDim == 4) {
0086 localVertexCoords(2) = vertexPos(3);
0087 localTrackCoords(2) = trkParams->parameters()[eBoundTime];
0088 }
0089
0090
0091 Vector<nDim - 1> residual = localTrackCoords - localVertexCoords;
0092
0093
0094 return residual.dot(weight * residual);
0095 }
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105
0106
0107
0108
0109 Result<double> performNewtonOptimization(
0110 const Vector3& helixCenter, const Vector3& vtxPos, double phi, double theta,
0111 double rho, const ImpactPointEstimator::Config& cfg, const Logger& logger) {
0112 double sinPhi = std::sin(phi);
0113 double cosPhi = std::cos(phi);
0114
0115 int nIter = 0;
0116 bool hasConverged = false;
0117
0118 double cotTheta = 1. / std::tan(theta);
0119
0120 double xO = helixCenter.x();
0121 double yO = helixCenter.y();
0122 double zO = helixCenter.z();
0123
0124 double xVtx = vtxPos.x();
0125 double yVtx = vtxPos.y();
0126 double zVtx = vtxPos.z();
0127
0128
0129
0130 while (!hasConverged && nIter < cfg.maxIterations) {
0131 double derivative = rho * ((xVtx - xO) * cosPhi + (yVtx - yO) * sinPhi +
0132 (zVtx - zO + rho * phi * cotTheta) * cotTheta);
0133 double secDerivative = rho * (-(xVtx - xO) * sinPhi + (yVtx - yO) * cosPhi +
0134 rho * cotTheta * cotTheta);
0135
0136 if (secDerivative < 0.) {
0137 ACTS_ERROR(
0138 "Encountered negative second derivative during Newton "
0139 "optimization.");
0140 return VertexingError::NumericFailure;
0141 }
0142
0143 double deltaPhi = -derivative / secDerivative;
0144
0145 phi += deltaPhi;
0146 sinPhi = std::sin(phi);
0147 cosPhi = std::cos(phi);
0148
0149 nIter += 1;
0150
0151 if (std::abs(deltaPhi) < cfg.precision) {
0152 hasConverged = true;
0153 }
0154 }
0155
0156 if (!hasConverged) {
0157 ACTS_ERROR("Newton optimization did not converge.");
0158 return VertexingError::NotConverged;
0159 }
0160 return phi;
0161 }
0162
0163
0164 template <typename vector_t>
0165 Result<std::pair<Vector4, Vector3>> getDistanceAndMomentumImpl(
0166 const GeometryContext& gctx, const BoundTrackParameters& trkParams,
0167 const vector_t& vtxPos, const ImpactPointEstimator::Config& cfg,
0168 ImpactPointEstimator::State& state, const Logger& logger) {
0169 static constexpr int nDim = vector_t::RowsAtCompileTime;
0170 static_assert(nDim == 3 || nDim == 4,
0171 "The number of dimensions nDim must be either 3 or 4.");
0172
0173
0174 Vector3 refPoint = trkParams.referenceSurface().center(gctx);
0175
0176
0177 double absoluteCharge = trkParams.particleHypothesis().absoluteCharge();
0178 double qOvP = trkParams.parameters()[BoundIndices::eBoundQOverP];
0179
0180
0181
0182 auto fieldRes = cfg.bField->getField(refPoint, state.fieldCache);
0183 if (!fieldRes.ok()) {
0184 ACTS_ERROR("In getDistanceAndMomentum, the B field at\n"
0185 << refPoint << "\ncould not be retrieved.");
0186 return fieldRes.error();
0187 }
0188 double bZ = (*fieldRes)[eZ];
0189
0190
0191
0192
0193 if (absoluteCharge == 0. || bZ == 0.) {
0194
0195 Vector3 momDirStraightTrack = trkParams.direction();
0196
0197
0198 Vector3 positionOnTrack = trkParams.position(gctx);
0199
0200
0201 double distanceToPca =
0202 (vtxPos.template head<3>() - positionOnTrack).dot(momDirStraightTrack);
0203
0204
0205 Vector<nDim> pcaStraightTrack;
0206 pcaStraightTrack.template head<3>() =
0207 positionOnTrack + distanceToPca * momDirStraightTrack;
0208 if constexpr (nDim == 4) {
0209
0210 double timeOnTrack = trkParams.parameters()[BoundIndices::eBoundTime];
0211
0212 double m0 = trkParams.particleHypothesis().mass();
0213 double p = trkParams.particleHypothesis().extractMomentum(qOvP);
0214
0215
0216 double beta = p / fastHypot(p, m0);
0217
0218 pcaStraightTrack[3] = timeOnTrack + distanceToPca / beta;
0219 }
0220
0221
0222 Vector4 deltaRStraightTrack{Vector4::Zero()};
0223 deltaRStraightTrack.head<nDim>() = pcaStraightTrack - vtxPos;
0224
0225 return std::pair(deltaRStraightTrack, momDirStraightTrack);
0226 }
0227
0228
0229
0230
0231
0232
0233 double d0 = trkParams.parameters()[BoundIndices::eBoundLoc0];
0234 double z0 = trkParams.parameters()[BoundIndices::eBoundLoc1];
0235
0236 double phiP = trkParams.parameters()[BoundIndices::eBoundPhi];
0237 double theta = trkParams.parameters()[BoundIndices::eBoundTheta];
0238
0239 double sinTheta = std::sin(theta);
0240 double cotTheta = 1. / std::tan(theta);
0241
0242
0243
0244 double phi = phiP;
0245
0246
0247 double rho = sinTheta * (1. / qOvP) / bZ;
0248
0249
0250
0251
0252
0253 Vector3 helixCenter =
0254 refPoint + Vector3(-(d0 - rho) * std::sin(phi),
0255 (d0 - rho) * std::cos(phi), z0 + rho * phi * cotTheta);
0256
0257
0258
0259 auto res = performNewtonOptimization(helixCenter, vtxPos.template head<3>(),
0260 phi, theta, rho, cfg, logger);
0261 if (!res.ok()) {
0262 return res.error();
0263 }
0264
0265 phi = *res;
0266
0267 double cosPhi = std::cos(phi);
0268 double sinPhi = std::sin(phi);
0269
0270
0271
0272
0273 Vector3 momDir =
0274 Vector3(cosPhi * sinTheta, sinPhi * sinTheta, std::cos(theta));
0275
0276
0277
0278
0279 Vector<nDim> pca;
0280 pca.template head<3>() =
0281 helixCenter + rho * Vector3(-sinPhi, cosPhi, -cotTheta * phi);
0282
0283 if constexpr (nDim == 4) {
0284
0285 double tP = trkParams.parameters()[BoundIndices::eBoundTime];
0286
0287 double m0 = trkParams.particleHypothesis().mass();
0288 double p = trkParams.particleHypothesis().extractMomentum(qOvP);
0289
0290
0291 double beta = p / fastHypot(p, m0);
0292
0293 pca[3] = tP - rho / (beta * sinTheta) * (phi - phiP);
0294 }
0295
0296 Vector4 deltaR{Vector4::Zero()};
0297 deltaR.head<nDim>() = pca - vtxPos;
0298
0299 return std::pair(deltaR, momDir);
0300 }
0301
0302 }
0303
0304 Result<double> ImpactPointEstimator::calculateDistance(
0305 const GeometryContext& gctx, const BoundTrackParameters& trkParams,
0306 const Vector3& vtxPos, State& state) const {
0307 auto res = getDistanceAndMomentumImpl(gctx, trkParams, vtxPos, m_cfg, state,
0308 *m_logger);
0309
0310 if (!res.ok()) {
0311 return res.error();
0312 }
0313
0314
0315
0316 return res.value().first.template head<3>().norm();
0317 }
0318
0319 Result<BoundTrackParameters> ImpactPointEstimator::estimate3DImpactParameters(
0320 const GeometryContext& gctx, const MagneticFieldContext& mctx,
0321 const BoundTrackParameters& trkParams, const Vector3& vtxPos,
0322 State& ) const {
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332
0333
0334
0335 std::shared_ptr<PointSurface> pointSurface =
0336 Surface::makeShared<PointSurface>(vtxPos);
0337
0338
0339 PropagatorPlainOptions pOptions(gctx, mctx);
0340
0341
0342 Intersection3D intersection =
0343 pointSurface
0344 ->intersect(gctx, trkParams.position(gctx), trkParams.direction(),
0345 BoundaryTolerance::Infinite())
0346 .closest();
0347 pOptions.direction =
0348 Direction::fromScalarZeroAsPositive(intersection.pathLength());
0349
0350
0351 auto result =
0352 m_cfg.propagator->propagateToSurface(trkParams, *pointSurface, pOptions);
0353 if (result.ok()) {
0354 return *result;
0355 } else {
0356 ACTS_ERROR("Error during propagation in estimate3DImpactParameters.");
0357 ACTS_DEBUG(
0358 "The point surface to which we tried to propagate has its origin at\n"
0359 << vtxPos);
0360 return result.error();
0361 }
0362 }
0363
0364 Result<double> ImpactPointEstimator::getVertexCompatibility(
0365 const GeometryContext& gctx, const BoundTrackParameters* trkParams,
0366 Eigen::Map<const DynamicVector> vertexPos) const {
0367 if (vertexPos.size() == 3) {
0368 return getVertexCompatibilityImpl(gctx, trkParams,
0369 vertexPos.template head<3>());
0370 } else if (vertexPos.size() == 4) {
0371 return getVertexCompatibilityImpl(gctx, trkParams,
0372 vertexPos.template head<4>());
0373 } else {
0374 return VertexingError::InvalidInput;
0375 }
0376 }
0377
0378 Result<std::pair<Acts::Vector4, Acts::Vector3>>
0379 ImpactPointEstimator::getDistanceAndMomentum(
0380 const GeometryContext& gctx, const BoundTrackParameters& trkParams,
0381 Eigen::Map<const DynamicVector> vtxPos, State& state) const {
0382 if (vtxPos.size() == 3) {
0383 return getDistanceAndMomentumImpl(
0384 gctx, trkParams, vtxPos.template head<3>(), m_cfg, state, *m_logger);
0385 } else if (vtxPos.size() == 4) {
0386 return getDistanceAndMomentumImpl(
0387 gctx, trkParams, vtxPos.template head<4>(), m_cfg, state, *m_logger);
0388 } else {
0389 return VertexingError::InvalidInput;
0390 }
0391 }
0392
0393 Result<ImpactParametersAndSigma> ImpactPointEstimator::getImpactParameters(
0394 const BoundTrackParameters& track, const Vertex& vtx,
0395 const GeometryContext& gctx, const MagneticFieldContext& mctx,
0396 bool calculateTimeIP) const {
0397 const std::shared_ptr<PerigeeSurface> perigeeSurface =
0398 Surface::makeShared<PerigeeSurface>(vtx.position());
0399
0400
0401 PropagatorPlainOptions pOptions(gctx, mctx);
0402 Intersection3D intersection =
0403 perigeeSurface
0404 ->intersect(gctx, track.position(gctx), track.direction(),
0405 BoundaryTolerance::Infinite())
0406 .closest();
0407 pOptions.direction =
0408 Direction::fromScalarZeroAsPositive(intersection.pathLength());
0409
0410
0411 auto result =
0412 m_cfg.propagator->propagateToSurface(track, *perigeeSurface, pOptions);
0413
0414 if (!result.ok()) {
0415 ACTS_ERROR("Error during propagation in getImpactParameters.");
0416 ACTS_DEBUG(
0417 "The Perigee surface to which we tried to propagate has its origin "
0418 "at\n"
0419 << vtx.position());
0420 return result.error();
0421 }
0422
0423 const auto& params = *result;
0424
0425
0426 if (!params.covariance().has_value()) {
0427 return VertexingError::NoCovariance;
0428 }
0429
0430
0431 auto impactParams = params.impactParameters();
0432 auto impactParamCovariance = params.impactParameterCovariance().value();
0433
0434
0435
0436
0437
0438 double vtxVarX = vtx.covariance()(eX, eX);
0439 double vtxVarY = vtx.covariance()(eY, eY);
0440 double vtxVarZ = vtx.covariance()(eZ, eZ);
0441
0442 ImpactParametersAndSigma ipAndSigma;
0443
0444 ipAndSigma.d0 = impactParams[0];
0445
0446 double vtxVar2DExtent = std::max(vtxVarX, vtxVarY);
0447
0448
0449
0450 if (vtxVar2DExtent > 0) {
0451 ipAndSigma.sigmaD0 =
0452 std::sqrt(vtxVar2DExtent + impactParamCovariance(0, 0));
0453 } else {
0454 ipAndSigma.sigmaD0 = std::sqrt(impactParamCovariance(0, 0));
0455 }
0456
0457 ipAndSigma.z0 = impactParams[1];
0458 if (vtxVarZ > 0) {
0459 ipAndSigma.sigmaZ0 = std::sqrt(vtxVarZ + impactParamCovariance(1, 1));
0460 } else {
0461 ipAndSigma.sigmaZ0 = std::sqrt(impactParamCovariance(1, 1));
0462 }
0463
0464 if (calculateTimeIP) {
0465 ipAndSigma.deltaT = std::abs(vtx.time() - impactParams[2]);
0466 double vtxVarT = vtx.fullCovariance()(eTime, eTime);
0467 if (vtxVarT > 0) {
0468 ipAndSigma.sigmaDeltaT = std::sqrt(vtxVarT + impactParamCovariance(2, 2));
0469 } else {
0470 ipAndSigma.sigmaDeltaT = std::sqrt(impactParamCovariance(2, 2));
0471 }
0472 }
0473
0474 return ipAndSigma;
0475 }
0476
0477 Result<std::pair<double, double>> ImpactPointEstimator::getLifetimeSignOfTrack(
0478 const BoundTrackParameters& track, const Vertex& vtx,
0479 const Vector3& direction, const GeometryContext& gctx,
0480 const MagneticFieldContext& mctx) const {
0481 const std::shared_ptr<PerigeeSurface> perigeeSurface =
0482 Surface::makeShared<PerigeeSurface>(vtx.position());
0483
0484
0485 PropagatorPlainOptions pOptions(gctx, mctx);
0486 pOptions.direction = Direction::Backward();
0487
0488
0489 auto result =
0490 m_cfg.propagator->propagateToSurface(track, *perigeeSurface, pOptions);
0491
0492 if (!result.ok()) {
0493 return result.error();
0494 }
0495
0496 const auto& params = (*result).parameters();
0497 const double d0 = params[BoundIndices::eBoundLoc0];
0498 const double z0 = params[BoundIndices::eBoundLoc1];
0499 const double phi = params[BoundIndices::eBoundPhi];
0500 const double theta = params[BoundIndices::eBoundTheta];
0501
0502 double vs = std::sin(std::atan2(direction[1], direction[0]) - phi) * d0;
0503 double eta = AngleHelpers::etaFromTheta(theta);
0504 double dir_eta = VectorHelpers::eta(direction);
0505
0506 double zs = (dir_eta - eta) * z0;
0507
0508 std::pair<double, double> vszs;
0509
0510 vszs.first = vs >= 0. ? 1. : -1.;
0511 vszs.second = zs >= 0. ? 1. : -1.;
0512
0513 return vszs;
0514 }
0515
0516 Result<double> ImpactPointEstimator::get3DLifetimeSignOfTrack(
0517 const BoundTrackParameters& track, const Vertex& vtx,
0518 const Vector3& direction, const GeometryContext& gctx,
0519 const MagneticFieldContext& mctx) const {
0520 const std::shared_ptr<PerigeeSurface> perigeeSurface =
0521 Surface::makeShared<PerigeeSurface>(vtx.position());
0522
0523
0524 PropagatorPlainOptions pOptions(gctx, mctx);
0525 pOptions.direction = Direction::Backward();
0526
0527
0528 auto result =
0529 m_cfg.propagator->propagateToSurface(track, *perigeeSurface, pOptions);
0530
0531 if (!result.ok()) {
0532 return result.error();
0533 }
0534
0535 const auto& params = *result;
0536 const Vector3 trkpos = params.position(gctx);
0537 const Vector3 trkmom = params.momentum();
0538
0539 double sign =
0540 (direction.cross(trkmom)).dot(trkmom.cross(vtx.position() - trkpos));
0541
0542 return sign >= 0. ? 1. : -1.;
0543 }
0544
0545 }