File indexing completed on 2026-05-18 07:49:45
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/unit_test.hpp>
0011
0012 #include "Acts/Definitions/Algebra.hpp"
0013 #include "Acts/Definitions/Common.hpp"
0014 #include "Acts/Definitions/Direction.hpp"
0015 #include "Acts/Definitions/TrackParametrization.hpp"
0016 #include "Acts/Definitions/Units.hpp"
0017 #include "Acts/EventData/BoundTrackParameters.hpp"
0018 #include "Acts/Geometry/GeometryContext.hpp"
0019 #include "Acts/Geometry/GeometryIdentifier.hpp"
0020 #include "Acts/MagneticField/ConstantBField.hpp"
0021 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0022 #include "Acts/MagneticField/MagneticFieldProvider.hpp"
0023 #include "Acts/MagneticField/NullBField.hpp"
0024 #include "Acts/Propagator/EigenStepper.hpp"
0025 #include "Acts/Propagator/Propagator.hpp"
0026 #include "Acts/Propagator/StraightLineStepper.hpp"
0027 #include "Acts/Propagator/VoidNavigator.hpp"
0028 #include "Acts/Surfaces/PerigeeSurface.hpp"
0029 #include "Acts/Surfaces/PlaneSurface.hpp"
0030 #include "Acts/Surfaces/Surface.hpp"
0031 #include "Acts/Utilities/Intersection.hpp"
0032 #include "Acts/Utilities/Logger.hpp"
0033 #include "Acts/Utilities/Result.hpp"
0034 #include "Acts/Vertexing/ImpactPointEstimator.hpp"
0035 #include "Acts/Vertexing/Vertex.hpp"
0036 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0037
0038 #include <cmath>
0039 #include <limits>
0040 #include <memory>
0041 #include <numbers>
0042 #include <optional>
0043 #include <utility>
0044 #include <vector>
0045
0046 namespace ActsTests {
0047
0048 namespace bd = boost::unit_test::data;
0049
0050 using namespace Acts;
0051 using namespace Acts::UnitLiterals;
0052 using Acts::VectorHelpers::makeVector4;
0053
0054 using MagneticField = ConstantBField;
0055 using StraightPropagator = Propagator<StraightLineStepper>;
0056 using Stepper = EigenStepper<>;
0057 using Propagator = Acts::Propagator<Stepper>;
0058 using Estimator = ImpactPointEstimator;
0059 using StraightLineEstimator = ImpactPointEstimator;
0060
0061 const auto geoContext = GeometryContext::dangerouslyDefaultConstruct();
0062 const MagneticFieldContext magFieldContext;
0063
0064 MagneticFieldProvider::Cache magFieldCache() {
0065 return NullBField{}.makeCache(magFieldContext);
0066 }
0067
0068
0069
0070 auto d0s = bd::make({-25_um, 25_um});
0071 auto l0s = bd::make({-1_mm, 1_mm});
0072 auto t0s = bd::make({-2_ns, 2_ns});
0073 auto phis = bd::make({0_degree, -45_degree, 45_degree});
0074 auto thetas = bd::make({90_degree, 20_degree, 160_degree});
0075 auto ps = bd::make({0.4_GeV, 1_GeV, 10_GeV});
0076 auto qs = bd::make({-1_e, 1_e});
0077
0078 auto tracksWithoutIPs = t0s * phis * thetas * ps * qs;
0079 auto IPs = d0s * l0s;
0080 auto tracks = IPs * tracksWithoutIPs;
0081
0082
0083 auto vx0s = bd::make({0_um, -10_um, 10_um});
0084 auto vy0s = bd::make({0_um, -10_um, 10_um});
0085 auto vz0s = bd::make({0_mm, -25_mm, 25_mm});
0086 auto vt0s = bd::make({0_ns, -2_ns, 2_ns});
0087
0088 auto vertices = vx0s * vy0s * vz0s * vt0s;
0089
0090
0091 Estimator makeEstimator(double bZ) {
0092 auto field = std::make_shared<MagneticField>(Vector3(0, 0, bZ));
0093 Stepper stepper(field);
0094 Estimator::Config cfg(field,
0095 std::make_shared<Propagator>(
0096 std::move(stepper), VoidNavigator(),
0097 getDefaultLogger("Prop", Logging::Level::WARNING)));
0098 return Estimator(cfg);
0099 }
0100
0101
0102 BoundMatrix makeBoundParametersCovariance(double stdDevTime = 30_ps) {
0103 BoundVector stddev;
0104 stddev[eBoundLoc0] = 15_um;
0105 stddev[eBoundLoc1] = 100_um;
0106 stddev[eBoundTime] = stdDevTime;
0107 stddev[eBoundPhi] = 1_degree;
0108 stddev[eBoundTheta] = 1_degree;
0109 stddev[eBoundQOverP] = 1_e / 100_GeV;
0110 return stddev.cwiseProduct(stddev).asDiagonal();
0111 }
0112
0113
0114 SquareMatrix4 makeVertexCovariance() {
0115 Vector4 stddev;
0116 stddev[ePos0] = 10_um;
0117 stddev[ePos1] = 10_um;
0118 stddev[ePos2] = 75_um;
0119 stddev[eTime] = 1_ns;
0120 return stddev.cwiseProduct(stddev).asDiagonal();
0121 }
0122
0123
0124 std::uniform_real_distribution<double> uniformDist(0.0, 1.0);
0125
0126 std::uniform_real_distribution<double> signDist(-1, 1);
0127
0128 BOOST_AUTO_TEST_SUITE(VertexingSuite)
0129
0130
0131
0132 BOOST_DATA_TEST_CASE(SingleTrackDistanceParametersCompatibility3D, tracks, d0,
0133 l0, t0, phi, theta, p, q) {
0134 auto particleHypothesis = ParticleHypothesis::pion();
0135
0136 BoundVector par;
0137 par[eBoundLoc0] = d0;
0138 par[eBoundLoc1] = l0;
0139 par[eBoundTime] = t0;
0140 par[eBoundPhi] = phi;
0141 par[eBoundTheta] = theta;
0142 par[eBoundQOverP] = particleHypothesis.qOverP(p, q);
0143
0144 Estimator ipEstimator = makeEstimator(2_T);
0145 Estimator::State state{magFieldCache()};
0146
0147 Vector3 refPosition(0., 0., 0.);
0148 auto perigeeSurface = Surface::makeShared<PerigeeSurface>(refPosition);
0149
0150 BoundTrackParameters myTrack(
0151 perigeeSurface, par, makeBoundParametersCovariance(), particleHypothesis);
0152
0153
0154 double distT = std::hypot(d0, l0);
0155 double dist3 =
0156 ipEstimator.calculateDistance(geoContext, myTrack, refPosition, state)
0157 .value();
0158
0159
0160
0161
0162 BOOST_CHECK((dist3 < distT) ||
0163 (theta == 90_degree && std::abs(dist3 - distT) < 1_nm));
0164
0165
0166 auto res = ipEstimator.estimate3DImpactParameters(
0167 geoContext, magFieldContext, myTrack, refPosition, state);
0168 BoundTrackParameters trackAtIP3d = *res;
0169 const auto& atPerigee = myTrack.parameters();
0170 const auto& atIp3d = trackAtIP3d.parameters();
0171
0172
0173 BOOST_CHECK_NE(atPerigee[eBoundLoc0], atIp3d[eBoundLoc0]);
0174 BOOST_CHECK_NE(atPerigee[eBoundLoc1], atIp3d[eBoundLoc1]);
0175
0176
0177 CHECK_CLOSE_ABS(atPerigee[eBoundTheta], atIp3d[eBoundTheta], 0.01_mrad);
0178 CHECK_CLOSE_REL(atPerigee[eBoundQOverP], atIp3d[eBoundQOverP],
0179 std::numeric_limits<double>::epsilon());
0180
0181
0182
0183 auto compatibility =
0184 ipEstimator.getVertexCompatibility(geoContext, &trackAtIP3d, refPosition)
0185 .value();
0186 BOOST_CHECK_GT(compatibility, 0);
0187 }
0188
0189 BOOST_DATA_TEST_CASE(TimeAtPca, tracksWithoutIPs* vertices, t0, phi, theta, p,
0190 q, vx0, vy0, vz0, vt0) {
0191 using Propagator = Acts::Propagator<Stepper>;
0192 using PropagatorOptions = Propagator::Options<>;
0193 using StraightPropagator = Acts::Propagator<StraightLineStepper>;
0194
0195
0196 auto field = std::make_shared<MagneticField>(Vector3(0, 0, 2_T));
0197 Stepper stepper(field);
0198 auto propagator = std::make_shared<Propagator>(std::move(stepper));
0199 Estimator::Config cfg(field, propagator);
0200 Estimator ipEstimator(cfg);
0201 Estimator::State ipState{magFieldCache()};
0202
0203
0204 auto zeroField = std::make_shared<MagneticField>(Vector3(0, 0, 0));
0205 StraightLineStepper straightLineStepper;
0206 auto straightLinePropagator =
0207 std::make_shared<StraightPropagator>(straightLineStepper);
0208 StraightLineEstimator::Config zeroFieldCfg(zeroField, straightLinePropagator);
0209 StraightLineEstimator zeroFieldIPEstimator(zeroFieldCfg);
0210 StraightLineEstimator::State zeroFieldIPState{magFieldCache()};
0211
0212
0213 Vector4 vtxPos(vx0, vy0, vz0, vt0);
0214 Vertex vtx(vtxPos, makeVertexCovariance(), {});
0215
0216
0217 auto vtxPerigeeSurface =
0218 Surface::makeShared<PerigeeSurface>(vtxPos.head<3>());
0219
0220
0221
0222
0223 BoundVector paramVec;
0224 paramVec[eBoundLoc0] = 0.;
0225 paramVec[eBoundLoc1] = 0.;
0226 paramVec[eBoundTime] = t0;
0227 paramVec[eBoundPhi] = phi;
0228 paramVec[eBoundTheta] = theta;
0229 paramVec[eBoundQOverP] = q / p;
0230
0231 BoundTrackParameters params(vtxPerigeeSurface, paramVec,
0232 makeBoundParametersCovariance(),
0233 ParticleHypothesis::pion());
0234
0235
0236
0237 double corrTimeDiff = t0 - vt0;
0238
0239
0240 double cosPhi = std::cos(phi);
0241 double sinPhi = std::sin(phi);
0242 double sinTheta = std::sin(theta);
0243 Vector3 corrMomDir =
0244 Vector3(cosPhi * sinTheta, sinPhi * sinTheta, std::cos(theta));
0245
0246
0247 Vector3 refPoint(2_mm, -2_mm, -5_mm);
0248
0249
0250 auto refPerigeeSurface = Surface::makeShared<PerigeeSurface>(refPoint);
0251
0252
0253 PropagatorOptions pOptions(geoContext, magFieldContext);
0254 Intersection3D intersection =
0255 refPerigeeSurface
0256 ->intersect(geoContext, params.position(geoContext),
0257 params.direction(), BoundaryTolerance::Infinite())
0258 .closest();
0259 pOptions.direction =
0260 Direction::fromScalarZeroAsPositive(intersection.pathLength());
0261
0262 StraightPropagator::Options<> straightPOptions(geoContext, magFieldContext);
0263 straightPOptions.direction = pOptions.direction;
0264
0265
0266 auto result = propagator->propagate(params, *refPerigeeSurface, pOptions);
0267 BOOST_CHECK(result.ok());
0268 const auto& refParams = *result->endParameters;
0269
0270
0271 auto zeroFieldResult = straightLinePropagator->propagate(
0272 params, *refPerigeeSurface, straightPOptions);
0273 BOOST_CHECK(zeroFieldResult.ok());
0274 const auto& zeroFieldRefParams = *zeroFieldResult->endParameters;
0275
0276 BOOST_TEST_CONTEXT(
0277 "Check time at 2D PCA (i.e., function getImpactParameters) for helical "
0278 "tracks") {
0279
0280 auto ipParams = ipEstimator
0281 .getImpactParameters(refParams, vtx, geoContext,
0282 magFieldContext, true)
0283 .value();
0284
0285
0286 CHECK_CLOSE_ABS(ipParams.d0, 0., 30_nm);
0287 CHECK_CLOSE_ABS(ipParams.z0, 0., 100_nm);
0288
0289
0290 CHECK_CLOSE_OR_SMALL(ipParams.deltaT.value(), std::abs(corrTimeDiff), 1e-5,
0291 1e-3);
0292 }
0293
0294 auto checkGetDistanceAndMomentum = [&vtxPos, &corrMomDir, corrTimeDiff](
0295 const auto& ipe, const auto& rParams,
0296 auto& state) {
0297
0298
0299 auto distAndMom = ipe.template getDistanceAndMomentum<4>(
0300 geoContext, rParams, vtxPos, state)
0301 .value();
0302
0303 Vector4 distVec = distAndMom.first;
0304 Vector3 momDir = distAndMom.second;
0305
0306
0307
0308 double dist = distVec.head<3>().norm();
0309 CHECK_CLOSE_ABS(dist, 0., 30_nm);
0310
0311
0312
0313 CHECK_CLOSE_OR_SMALL(distVec[3], corrTimeDiff, 1e-5, 1e-4);
0314
0315
0316 CHECK_CLOSE_OR_SMALL(momDir, corrMomDir, 1e-5, 1e-4);
0317 };
0318
0319 BOOST_TEST_CONTEXT(
0320 "Check time at 3D PCA (i.e., function getDistanceAndMomentum) for "
0321 "straight tracks") {
0322 checkGetDistanceAndMomentum(zeroFieldIPEstimator, zeroFieldRefParams,
0323 zeroFieldIPState);
0324 }
0325 BOOST_TEST_CONTEXT(
0326 "Check time at 3D PCA (i.e., function getDistanceAndMomentum) for "
0327 "helical tracks") {
0328 checkGetDistanceAndMomentum(ipEstimator, refParams, ipState);
0329 }
0330 }
0331
0332 BOOST_DATA_TEST_CASE(VertexCompatibility4D, IPs* vertices, d0, l0, vx0, vy0,
0333 vz0, vt0) {
0334
0335 int seed = 31415;
0336 std::mt19937 gen(seed);
0337
0338
0339 Estimator ipEstimator = makeEstimator(2_T);
0340
0341
0342 Vector4 vtxPos(vx0, vy0, vz0, vt0);
0343
0344
0345 Transform3 coordinateSystem;
0346
0347 coordinateSystem.matrix().block<3, 3>(0, 0) = SquareMatrix<3>::Identity();
0348
0349 coordinateSystem.matrix().block<3, 1>(0, 3) = vtxPos.head<3>();
0350
0351
0352 std::shared_ptr<PlaneSurface> planeSurface =
0353 Surface::makeShared<PlaneSurface>(coordinateSystem);
0354
0355
0356
0357
0358
0359 double timeDiffFactor = uniformDist(gen);
0360 double timeDiffClose = timeDiffFactor * 0.1_ps;
0361 double timeDiffFar = timeDiffFactor * 0.11_ps;
0362
0363
0364 double sgnClose = std::copysign(1., signDist(gen));
0365 double sgnFar = std::copysign(1., signDist(gen));
0366
0367 BoundVector paramVecClose = BoundVector::Zero();
0368 paramVecClose[eBoundLoc0] = d0;
0369 paramVecClose[eBoundLoc1] = l0;
0370 paramVecClose[eBoundPhi] = 0;
0371 paramVecClose[eBoundTheta] = std::numbers::pi / 2;
0372 paramVecClose[eBoundQOverP] = 0;
0373 paramVecClose[eBoundTime] = vt0 + sgnClose * timeDiffClose;
0374
0375 BoundVector paramVecFar = paramVecClose;
0376 paramVecFar[eBoundTime] = vt0 + sgnFar * timeDiffFar;
0377
0378
0379 BoundTrackParameters paramsClose(planeSurface, paramVecClose,
0380 makeBoundParametersCovariance(30_ns),
0381 ParticleHypothesis::pion());
0382
0383
0384
0385 BoundTrackParameters paramsCloseLargerCov(
0386 planeSurface, paramVecClose, makeBoundParametersCovariance(31_ns),
0387 ParticleHypothesis::pion());
0388
0389
0390 BoundTrackParameters paramsFar(planeSurface, paramVecFar,
0391 makeBoundParametersCovariance(30_ns),
0392 ParticleHypothesis::pion());
0393
0394
0395 double compatibilityClose =
0396 ipEstimator.getVertexCompatibility(geoContext, ¶msClose, vtxPos)
0397 .value();
0398 double compatibilityCloseLargerCov =
0399 ipEstimator
0400 .getVertexCompatibility(geoContext, ¶msCloseLargerCov, vtxPos)
0401 .value();
0402 double compatibilityFar =
0403 ipEstimator.getVertexCompatibility(geoContext, ¶msFar, vtxPos)
0404 .value();
0405
0406
0407
0408 BOOST_CHECK_LT(compatibilityClose, compatibilityFar);
0409
0410 BOOST_CHECK_LT(compatibilityCloseLargerCov, compatibilityClose);
0411 }
0412
0413
0414
0415
0416
0417
0418
0419
0420 BOOST_AUTO_TEST_CASE(SingleTrackDistanceParametersAthenaRegression) {
0421 Estimator ipEstimator = makeEstimator(1.9971546939_T);
0422 Estimator::State state{magFieldCache()};
0423
0424
0425 Vector4 pos1(2_mm, 1_mm, -10_mm, 0_ns);
0426 Vector3 mom1(400_MeV, 600_MeV, 200_MeV);
0427 Vector3 vtxPos(1.2_mm, 0.8_mm, -7_mm);
0428
0429
0430 auto perigeeSurface =
0431 Surface::makeShared<PerigeeSurface>(pos1.segment<3>(ePos0));
0432
0433 auto params1 = BoundTrackParameters::create(
0434 geoContext, perigeeSurface, pos1, mom1, 1_e / mom1.norm(),
0435 BoundMatrix::Identity(), ParticleHypothesis::pion())
0436 .value();
0437
0438
0439 auto distance =
0440 ipEstimator.calculateDistance(geoContext, params1, vtxPos, state).value();
0441 CHECK_CLOSE_ABS(distance, 3.10391_mm, 10_nm);
0442
0443 auto res2 = ipEstimator.estimate3DImpactParameters(
0444 geoContext, magFieldContext, params1, vtxPos, state);
0445 BOOST_CHECK(res2.ok());
0446 BoundTrackParameters endParams = *res2;
0447 Vector3 surfaceCenter = endParams.referenceSurface().center(geoContext);
0448
0449 BOOST_CHECK_EQUAL(surfaceCenter, vtxPos);
0450 }
0451
0452
0453
0454
0455 BOOST_AUTO_TEST_CASE(Lifetimes2d3d) {
0456 Estimator ipEstimator = makeEstimator(2_T);
0457
0458
0459 BoundVector trk_par;
0460 trk_par[eBoundLoc0] = 200_um;
0461 trk_par[eBoundLoc1] = 300_um;
0462 trk_par[eBoundTime] = 1_ns;
0463 trk_par[eBoundPhi] = 45_degree;
0464 trk_par[eBoundTheta] = 45_degree;
0465 trk_par[eBoundQOverP] = 1_e / 10_GeV;
0466
0467 Vector4 ip_pos{0., 0., 0., 0.};
0468 Vertex ip_vtx(ip_pos, makeVertexCovariance(), {});
0469
0470
0471 auto perigeeSurface = Surface::makeShared<PerigeeSurface>(ip_pos.head<3>());
0472 BoundTrackParameters track(perigeeSurface, trk_par,
0473 makeBoundParametersCovariance(),
0474 ParticleHypothesis::pion());
0475
0476 Vector3 direction{0., 1., 0.};
0477 auto lifetimes_signs = ipEstimator.getLifetimeSignOfTrack(
0478 track, ip_vtx, direction, geoContext, magFieldContext);
0479
0480
0481 BOOST_CHECK(lifetimes_signs.ok());
0482
0483
0484 BOOST_CHECK_GT((*lifetimes_signs).first, 0.);
0485
0486
0487 BOOST_CHECK_LT((*lifetimes_signs).second, 0.);
0488
0489
0490
0491 auto sign3d = ipEstimator.get3DLifetimeSignOfTrack(
0492 track, ip_vtx, direction, geoContext, magFieldContext);
0493
0494
0495 BOOST_CHECK(sign3d.ok());
0496
0497
0498 BOOST_CHECK_GT((*sign3d), 0.);
0499 }
0500
0501
0502 BOOST_DATA_TEST_CASE(SingeTrackImpactParameters, tracks* vertices, d0, l0, t0,
0503 phi, theta, p, q, vx0, vy0, vz0, vt0) {
0504 BoundVector par;
0505 par[eBoundLoc0] = d0;
0506 par[eBoundLoc1] = l0;
0507 par[eBoundTime] = t0;
0508 par[eBoundPhi] = phi;
0509 par[eBoundTheta] = theta;
0510 par[eBoundQOverP] = q / p;
0511 Vector4 vtxPos;
0512 vtxPos[ePos0] = vx0;
0513 vtxPos[ePos1] = vy0;
0514 vtxPos[ePos2] = vz0;
0515 vtxPos[eTime] = vt0;
0516
0517 Estimator ipEstimator = makeEstimator(1_T);
0518 Estimator::State state{magFieldCache()};
0519
0520
0521 Vector3 refPosition(0., 0., 0.);
0522 auto perigeeSurface = Surface::makeShared<PerigeeSurface>(refPosition);
0523
0524 BoundTrackParameters track(perigeeSurface, par,
0525 makeBoundParametersCovariance(),
0526 ParticleHypothesis::pionLike(std::abs(q)));
0527 Vertex myConstraint(vtxPos, makeVertexCovariance(), {});
0528
0529
0530 ImpactParametersAndSigma output =
0531 ipEstimator
0532 .getImpactParameters(track, myConstraint, geoContext, magFieldContext)
0533 .value();
0534 BOOST_CHECK_NE(output.d0, 0.);
0535 BOOST_CHECK_NE(output.z0, 0.);
0536
0537
0538 }
0539
0540 BOOST_AUTO_TEST_SUITE_END()
0541
0542 }