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