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