Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:13:02

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
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/Charge.hpp"
0018 #include "Acts/EventData/GenericBoundTrackParameters.hpp"
0019 #include "Acts/EventData/TrackParameters.hpp"
0020 #include "Acts/Geometry/GeometryContext.hpp"
0021 #include "Acts/Geometry/GeometryIdentifier.hpp"
0022 #include "Acts/MagneticField/ConstantBField.hpp"
0023 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0024 #include "Acts/MagneticField/MagneticFieldProvider.hpp"
0025 #include "Acts/MagneticField/NullBField.hpp"
0026 #include "Acts/Propagator/EigenStepper.hpp"
0027 #include "Acts/Propagator/Propagator.hpp"
0028 #include "Acts/Propagator/PropagatorOptions.hpp"
0029 #include "Acts/Propagator/StraightLineStepper.hpp"
0030 #include "Acts/Propagator/VoidNavigator.hpp"
0031 #include "Acts/Surfaces/PerigeeSurface.hpp"
0032 #include "Acts/Surfaces/PlaneSurface.hpp"
0033 #include "Acts/Surfaces/Surface.hpp"
0034 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0035 #include "Acts/Utilities/Logger.hpp"
0036 #include "Acts/Utilities/Result.hpp"
0037 #include "Acts/Vertexing/ImpactPointEstimator.hpp"
0038 #include "Acts/Vertexing/TrackAtVertex.hpp"
0039 #include "Acts/Vertexing/Vertex.hpp"
0040 
0041 #include <algorithm>
0042 #include <array>
0043 #include <cmath>
0044 #include <limits>
0045 #include <memory>
0046 #include <numbers>
0047 #include <optional>
0048 #include <tuple>
0049 #include <utility>
0050 #include <vector>
0051 
0052 namespace {
0053 
0054 namespace bd = boost::unit_test::data;
0055 
0056 using namespace Acts;
0057 using namespace Acts::UnitLiterals;
0058 using Acts::VectorHelpers::makeVector4;
0059 
0060 using MagneticField = Acts::ConstantBField;
0061 using StraightPropagator = Acts::Propagator<StraightLineStepper>;
0062 using Stepper = Acts::EigenStepper<>;
0063 using Propagator = Acts::Propagator<Stepper>;
0064 using Estimator = Acts::ImpactPointEstimator;
0065 using StraightLineEstimator = Acts::ImpactPointEstimator;
0066 
0067 const Acts::GeometryContext geoContext;
0068 const Acts::MagneticFieldContext magFieldContext;
0069 
0070 Acts::MagneticFieldProvider::Cache magFieldCache() {
0071   return NullBField{}.makeCache(magFieldContext);
0072 }
0073 
0074 // perigee track parameters dataset
0075 // only non-zero distances are tested
0076 auto d0s = bd::make({-25_um, 25_um});
0077 auto l0s = bd::make({-1_mm, 1_mm});
0078 auto t0s = bd::make({-2_ns, 2_ns});
0079 auto phis = bd::make({0_degree, -45_degree, 45_degree});
0080 auto thetas = bd::make({90_degree, 20_degree, 160_degree});
0081 auto ps = bd::make({0.4_GeV, 1_GeV, 10_GeV});
0082 auto qs = bd::make({-1_e, 1_e});
0083 // Cartesian products over all parameters
0084 auto tracksWithoutIPs = t0s * phis * thetas * ps * qs;
0085 auto IPs = d0s * l0s;
0086 auto tracks = IPs * tracksWithoutIPs;
0087 
0088 // vertex parameters dataset
0089 auto vx0s = bd::make({0_um, -10_um, 10_um});
0090 auto vy0s = bd::make({0_um, -10_um, 10_um});
0091 auto vz0s = bd::make({0_mm, -25_mm, 25_mm});
0092 auto vt0s = bd::make({0_ns, -2_ns, 2_ns});
0093 // Cartesian products over all parameters
0094 auto vertices = vx0s * vy0s * vz0s * vt0s;
0095 
0096 // Construct an impact point estimator for a constant bfield along z.
0097 Estimator makeEstimator(double bZ) {
0098   auto field = std::make_shared<MagneticField>(Vector3(0, 0, bZ));
0099   Stepper stepper(field);
0100   Estimator::Config cfg(field,
0101                         std::make_shared<Propagator>(
0102                             std::move(stepper), VoidNavigator(),
0103                             getDefaultLogger("Prop", Logging::Level::WARNING)));
0104   return Estimator(cfg);
0105 }
0106 
0107 // Construct a diagonal track covariance w/ reasonable values.
0108 Acts::BoundSquareMatrix makeBoundParametersCovariance(
0109     double stdDevTime = 30_ps) {
0110   BoundVector stddev;
0111   stddev[eBoundLoc0] = 15_um;
0112   stddev[eBoundLoc1] = 100_um;
0113   stddev[eBoundTime] = stdDevTime;
0114   stddev[eBoundPhi] = 1_degree;
0115   stddev[eBoundTheta] = 1_degree;
0116   stddev[eBoundQOverP] = 1_e / 100_GeV;
0117   return stddev.cwiseProduct(stddev).asDiagonal();
0118 }
0119 
0120 // Construct a diagonal vertex covariance w/ reasonable values.
0121 Acts::SquareMatrix4 makeVertexCovariance() {
0122   Vector4 stddev;
0123   stddev[ePos0] = 10_um;
0124   stddev[ePos1] = 10_um;
0125   stddev[ePos2] = 75_um;
0126   stddev[eTime] = 1_ns;
0127   return stddev.cwiseProduct(stddev).asDiagonal();
0128 }
0129 
0130 // random value between 0 and 1
0131 std::uniform_real_distribution<double> uniformDist(0.0, 1.0);
0132 // random sign
0133 std::uniform_real_distribution<double> signDist(-1, 1);
0134 }  // namespace
0135 
0136 BOOST_AUTO_TEST_SUITE(VertexingImpactPointEstimator)
0137 
0138 // Check `calculateDistance`, `estimate3DImpactParameters`, and
0139 // `getVertexCompatibility`.
0140 BOOST_DATA_TEST_CASE(SingleTrackDistanceParametersCompatibility3D, tracks, d0,
0141                      l0, t0, phi, theta, p, q) {
0142   auto particleHypothesis = ParticleHypothesis::pion();
0143 
0144   BoundVector par;
0145   par[eBoundLoc0] = d0;
0146   par[eBoundLoc1] = l0;
0147   par[eBoundTime] = t0;
0148   par[eBoundPhi] = phi;
0149   par[eBoundTheta] = theta;
0150   par[eBoundQOverP] = particleHypothesis.qOverP(p, q);
0151 
0152   Estimator ipEstimator = makeEstimator(2_T);
0153   Estimator::State state{magFieldCache()};
0154   // reference position and corresponding perigee surface
0155   Vector3 refPosition(0., 0., 0.);
0156   auto perigeeSurface = Surface::makeShared<PerigeeSurface>(refPosition);
0157   // create the track
0158   BoundTrackParameters myTrack(
0159       perigeeSurface, par, makeBoundParametersCovariance(), particleHypothesis);
0160 
0161   // initial distance to the reference position in the perigee frame
0162   double distT = std::hypot(d0, l0);
0163   double dist3 =
0164       ipEstimator.calculateDistance(geoContext, myTrack, refPosition, state)
0165           .value();
0166   // estimated 3D distance should be less than the 2d distance in the perigee
0167   // frame. it should be equal if the track is a transverse track w/ theta =
0168   // 90deg. in that case there might be numerical deviations and we need to
0169   // check that it is less or equal within the numerical tolerance.
0170   BOOST_CHECK((dist3 < distT) ||
0171               (theta == 90_degree && std::abs(dist3 - distT) < 1_nm));
0172 
0173   // estimate parameters at the closest point in 3d
0174   auto res = ipEstimator.estimate3DImpactParameters(
0175       geoContext, magFieldContext, myTrack, refPosition, state);
0176   BoundTrackParameters trackAtIP3d = *res;
0177   const auto& atPerigee = myTrack.parameters();
0178   const auto& atIp3d = trackAtIP3d.parameters();
0179 
0180   // all parameters except the helix invariants theta, q/p should be changed
0181   BOOST_CHECK_NE(atPerigee[eBoundLoc0], atIp3d[eBoundLoc0]);
0182   BOOST_CHECK_NE(atPerigee[eBoundLoc1], atIp3d[eBoundLoc1]);
0183   // BOOST_CHECK_NE(atPerigee[eBoundTime], atIp3d[eBoundTime]);
0184   // BOOST_CHECK_NE(atPerigee[eBoundPhi], atIp3d[eBoundPhi]);
0185   CHECK_CLOSE_ABS(atPerigee[eBoundTheta], atIp3d[eBoundTheta], 0.01_mrad);
0186   CHECK_CLOSE_REL(atPerigee[eBoundQOverP], atIp3d[eBoundQOverP],
0187                   std::numeric_limits<double>::epsilon());
0188 
0189   // check that we get sensible compatibility scores
0190   // this is a chi2-like value and should always be positive
0191   auto compatibility =
0192       ipEstimator.getVertexCompatibility(geoContext, &trackAtIP3d, refPosition)
0193           .value();
0194   BOOST_CHECK_GT(compatibility, 0);
0195 }
0196 
0197 BOOST_DATA_TEST_CASE(TimeAtPca, tracksWithoutIPs* vertices, t0, phi, theta, p,
0198                      q, vx0, vy0, vz0, vt0) {
0199   using Propagator = Acts::Propagator<Stepper>;
0200   using PropagatorOptions = Propagator::Options<>;
0201   using StraightPropagator = Acts::Propagator<StraightLineStepper>;
0202 
0203   // Set up quantities for constant B field
0204   auto field = std::make_shared<MagneticField>(Vector3(0, 0, 2_T));
0205   Stepper stepper(field);
0206   auto propagator = std::make_shared<Propagator>(std::move(stepper));
0207   Estimator::Config cfg(field, propagator);
0208   Estimator ipEstimator(cfg);
0209   Estimator::State ipState{magFieldCache()};
0210 
0211   // Set up quantities for B = 0
0212   auto zeroField = std::make_shared<MagneticField>(Vector3(0, 0, 0));
0213   StraightLineStepper straightLineStepper;
0214   auto straightLinePropagator =
0215       std::make_shared<StraightPropagator>(straightLineStepper);
0216   StraightLineEstimator::Config zeroFieldCfg(zeroField, straightLinePropagator);
0217   StraightLineEstimator zeroFieldIPEstimator(zeroFieldCfg);
0218   StraightLineEstimator::State zeroFieldIPState{magFieldCache()};
0219 
0220   // Vertex position and vertex object
0221   Vector4 vtxPos(vx0, vy0, vz0, vt0);
0222   Vertex vtx(vtxPos, makeVertexCovariance(), {});
0223 
0224   // Perigee surface at vertex position
0225   auto vtxPerigeeSurface =
0226       Surface::makeShared<PerigeeSurface>(vtxPos.head<3>());
0227 
0228   // Track parameter vector for a track that originates at the vertex.
0229   // Note that 2D and 3D PCA coincide since the track passes exactly through the
0230   // vertex.
0231   BoundVector paramVec;
0232   paramVec[eBoundLoc0] = 0.;
0233   paramVec[eBoundLoc1] = 0.;
0234   paramVec[eBoundTime] = t0;
0235   paramVec[eBoundPhi] = phi;
0236   paramVec[eBoundTheta] = theta;
0237   paramVec[eBoundQOverP] = q / p;
0238 
0239   BoundTrackParameters params(vtxPerigeeSurface, paramVec,
0240                               makeBoundParametersCovariance(),
0241                               ParticleHypothesis::pion());
0242 
0243   // Correct quantities for checking if IP estimation worked
0244   // Time of the track with respect to the vertex
0245   double corrTimeDiff = t0 - vt0;
0246 
0247   // Momentum direction at vertex (i.e., at 3D PCA)
0248   double cosPhi = std::cos(phi);
0249   double sinPhi = std::sin(phi);
0250   double sinTheta = std::sin(theta);
0251   Vector3 corrMomDir =
0252       Vector3(cosPhi * sinTheta, sinPhi * sinTheta, std::cos(theta));
0253 
0254   // Arbitrary reference point
0255   Vector3 refPoint(2_mm, -2_mm, -5_mm);
0256 
0257   // Perigee surface at vertex position
0258   auto refPerigeeSurface = Surface::makeShared<PerigeeSurface>(refPoint);
0259 
0260   // Set up the propagator options (they are the same with and without B field)
0261   PropagatorOptions pOptions(geoContext, magFieldContext);
0262   auto intersection =
0263       refPerigeeSurface
0264           ->intersect(geoContext, params.position(geoContext),
0265                       params.direction(), BoundaryTolerance::Infinite())
0266           .closest();
0267   pOptions.direction =
0268       Direction::fromScalarZeroAsPositive(intersection.pathLength());
0269 
0270   StraightPropagator::Options<> straightPOptions(geoContext, magFieldContext);
0271   straightPOptions.direction = pOptions.direction;
0272 
0273   // Propagate to the 2D PCA of the reference point in a constant B field
0274   auto result = propagator->propagate(params, *refPerigeeSurface, pOptions);
0275   BOOST_CHECK(result.ok());
0276   const auto& refParams = *result->endParameters;
0277 
0278   // Propagate to the 2D PCA of the reference point when B = 0
0279   auto zeroFieldResult = straightLinePropagator->propagate(
0280       params, *refPerigeeSurface, straightPOptions);
0281   BOOST_CHECK(zeroFieldResult.ok());
0282   const auto& zeroFieldRefParams = *zeroFieldResult->endParameters;
0283 
0284   BOOST_TEST_CONTEXT(
0285       "Check time at 2D PCA (i.e., function getImpactParameters) for helical "
0286       "tracks") {
0287     // Calculate impact parameters
0288     auto ipParams = ipEstimator
0289                         .getImpactParameters(refParams, vtx, geoContext,
0290                                              magFieldContext, true)
0291                         .value();
0292     // Spatial impact parameters should be 0 because the track passes through
0293     // the vertex
0294     CHECK_CLOSE_ABS(ipParams.d0, 0., 30_nm);
0295     CHECK_CLOSE_ABS(ipParams.z0, 0., 100_nm);
0296     // Time impact parameter should correspond to the time where the track
0297     // passes through the vertex
0298     CHECK_CLOSE_OR_SMALL(ipParams.deltaT.value(), std::abs(corrTimeDiff), 1e-5,
0299                          1e-3);
0300   }
0301 
0302   auto checkGetDistanceAndMomentum = [&vtxPos, &corrMomDir, corrTimeDiff](
0303                                          const auto& ipe, const auto& rParams,
0304                                          auto& state) {
0305     // Find 4D distance and momentum of the track at the vertex starting from
0306     // the perigee representation at the reference position
0307     auto distAndMom = ipe.template getDistanceAndMomentum<4>(
0308                              geoContext, rParams, vtxPos, state)
0309                           .value();
0310 
0311     Vector4 distVec = distAndMom.first;
0312     Vector3 momDir = distAndMom.second;
0313 
0314     // Check quantities:
0315     // Spatial distance should be 0 as track passes through the vertex
0316     double dist = distVec.head<3>().norm();
0317     CHECK_CLOSE_ABS(dist, 0., 30_nm);
0318     // Distance in time should correspond to the time of the track in a
0319     // coordinate system with the vertex as the origin since the track passes
0320     // exactly through the vertex
0321     CHECK_CLOSE_OR_SMALL(distVec[3], corrTimeDiff, 1e-5, 1e-4);
0322     // Momentum direction should correspond to the momentum direction at the
0323     // vertex
0324     CHECK_CLOSE_OR_SMALL(momDir, corrMomDir, 1e-5, 1e-4);
0325   };
0326 
0327   BOOST_TEST_CONTEXT(
0328       "Check time at 3D PCA (i.e., function getDistanceAndMomentum) for "
0329       "straight tracks") {
0330     checkGetDistanceAndMomentum(zeroFieldIPEstimator, zeroFieldRefParams,
0331                                 zeroFieldIPState);
0332   }
0333   BOOST_TEST_CONTEXT(
0334       "Check time at 3D PCA (i.e., function getDistanceAndMomentum) for "
0335       "helical tracks") {
0336     checkGetDistanceAndMomentum(ipEstimator, refParams, ipState);
0337   }
0338 }
0339 
0340 BOOST_DATA_TEST_CASE(VertexCompatibility4D, IPs* vertices, d0, l0, vx0, vy0,
0341                      vz0, vt0) {
0342   // Set up RNG
0343   int seed = 31415;
0344   std::mt19937 gen(seed);
0345 
0346   // Impact point estimator
0347   Estimator ipEstimator = makeEstimator(2_T);
0348 
0349   // Vertex position
0350   Vector4 vtxPos(vx0, vy0, vz0, vt0);
0351 
0352   // Dummy coordinate system with origin at vertex
0353   Transform3 coordinateSystem;
0354   // First three columns correspond to coordinate system axes
0355   coordinateSystem.matrix().block<3, 3>(0, 0) = ActsSquareMatrix<3>::Identity();
0356   // Fourth column corresponds to origin of the coordinate system
0357   coordinateSystem.matrix().block<3, 1>(0, 3) = vtxPos.head<3>();
0358 
0359   // Dummy plane surface
0360   std::shared_ptr<PlaneSurface> planeSurface =
0361       Surface::makeShared<PlaneSurface>(coordinateSystem);
0362 
0363   // Create two track parameter vectors that are alike except that one is closer
0364   // to the vertex in time. Note that momenta don't play a role in the
0365   // computation and we set the angles and q/p to 0.
0366   // Time offsets
0367   double timeDiffFactor = uniformDist(gen);
0368   double timeDiffClose = timeDiffFactor * 0.1_ps;
0369   double timeDiffFar = timeDiffFactor * 0.11_ps;
0370 
0371   // Different random signs for the time offsets
0372   double sgnClose = signDist(gen) < 0 ? -1. : 1.;
0373   double sgnFar = signDist(gen) < 0 ? -1. : 1.;
0374 
0375   BoundVector paramVecClose = BoundVector::Zero();
0376   paramVecClose[eBoundLoc0] = d0;
0377   paramVecClose[eBoundLoc1] = l0;
0378   paramVecClose[eBoundPhi] = 0;
0379   paramVecClose[eBoundTheta] = std::numbers::pi / 2;
0380   paramVecClose[eBoundQOverP] = 0;
0381   paramVecClose[eBoundTime] = vt0 + sgnClose * timeDiffClose;
0382 
0383   BoundVector paramVecFar = paramVecClose;
0384   paramVecFar[eBoundTime] = vt0 + sgnFar * timeDiffFar;
0385 
0386   // Track whose time is similar to the vertex time
0387   BoundTrackParameters paramsClose(planeSurface, paramVecClose,
0388                                    makeBoundParametersCovariance(30_ns),
0389                                    ParticleHypothesis::pion());
0390 
0391   // Track whose time is similar to the vertex time but with a larger time
0392   // variance
0393   BoundTrackParameters paramsCloseLargerCov(
0394       planeSurface, paramVecClose, makeBoundParametersCovariance(31_ns),
0395       ParticleHypothesis::pion());
0396 
0397   // Track whose time differs slightly more from the vertex time
0398   BoundTrackParameters paramsFar(planeSurface, paramVecFar,
0399                                  makeBoundParametersCovariance(30_ns),
0400                                  ParticleHypothesis::pion());
0401 
0402   // Calculate the 4D vertex compatibilities of the three tracks
0403   double compatibilityClose =
0404       ipEstimator.getVertexCompatibility(geoContext, &paramsClose, vtxPos)
0405           .value();
0406   double compatibilityCloseLargerCov =
0407       ipEstimator
0408           .getVertexCompatibility(geoContext, &paramsCloseLargerCov, vtxPos)
0409           .value();
0410   double compatibilityFar =
0411       ipEstimator.getVertexCompatibility(geoContext, &paramsFar, vtxPos)
0412           .value();
0413 
0414   // The track who is closer in time must have a better (i.e., smaller)
0415   // compatibility
0416   BOOST_CHECK_LT(compatibilityClose, compatibilityFar);
0417   // The track with the larger covariance must be the most compatible
0418   BOOST_CHECK_LT(compatibilityCloseLargerCov, compatibilityClose);
0419 }
0420 
0421 // Compare calculations w/ known good values from Athena.
0422 //
0423 // Checks the results for a single track with the same test values as in Athena
0424 // unit test algorithm
0425 //
0426 //   Tracking/TrkVertexFitter/TrkVertexFitterUtils/test/ImpactPointEstimator_test
0427 //
0428 BOOST_AUTO_TEST_CASE(SingleTrackDistanceParametersAthenaRegression) {
0429   Estimator ipEstimator = makeEstimator(1.9971546939_T);
0430   Estimator::State state{magFieldCache()};
0431 
0432   // Use same values as in Athena unit test
0433   Vector4 pos1(2_mm, 1_mm, -10_mm, 0_ns);
0434   Vector3 mom1(400_MeV, 600_MeV, 200_MeV);
0435   Vector3 vtxPos(1.2_mm, 0.8_mm, -7_mm);
0436 
0437   // Start creating some track parameters
0438   auto perigeeSurface =
0439       Surface::makeShared<PerigeeSurface>(pos1.segment<3>(ePos0));
0440   // Some fixed track parameter values
0441   auto params1 = BoundTrackParameters::create(
0442                      perigeeSurface, geoContext, pos1, mom1, 1_e / mom1.norm(),
0443                      BoundTrackParameters::CovarianceMatrix::Identity(),
0444                      ParticleHypothesis::pion())
0445                      .value();
0446 
0447   // Compare w/ desired result from Athena unit test
0448   auto distance =
0449       ipEstimator.calculateDistance(geoContext, params1, vtxPos, state).value();
0450   CHECK_CLOSE_ABS(distance, 3.10391_mm, 10_nm);
0451 
0452   auto res2 = ipEstimator.estimate3DImpactParameters(
0453       geoContext, magFieldContext, params1, vtxPos, state);
0454   BOOST_CHECK(res2.ok());
0455   BoundTrackParameters endParams = *res2;
0456   Vector3 surfaceCenter = endParams.referenceSurface().center(geoContext);
0457 
0458   BOOST_CHECK_EQUAL(surfaceCenter, vtxPos);
0459 }
0460 
0461 // Test the Impact3d Point estimator 2d and 3d lifetimes sign
0462 // on a single track.
0463 
0464 BOOST_AUTO_TEST_CASE(Lifetimes2d3d) {
0465   Estimator ipEstimator = makeEstimator(2_T);
0466 
0467   // Create a track from a decay
0468   BoundVector trk_par;
0469   trk_par[eBoundLoc0] = 200_um;
0470   trk_par[eBoundLoc1] = 300_um;
0471   trk_par[eBoundTime] = 1_ns;
0472   trk_par[eBoundPhi] = 45_degree;
0473   trk_par[eBoundTheta] = 45_degree;
0474   trk_par[eBoundQOverP] = 1_e / 10_GeV;
0475 
0476   Vector4 ip_pos{0., 0., 0., 0.};
0477   Vertex ip_vtx(ip_pos, makeVertexCovariance(), {});
0478 
0479   // Form the bound track parameters at the ip
0480   auto perigeeSurface = Surface::makeShared<PerigeeSurface>(ip_pos.head<3>());
0481   BoundTrackParameters track(perigeeSurface, trk_par,
0482                              makeBoundParametersCovariance(),
0483                              ParticleHypothesis::pion());
0484 
0485   Vector3 direction{0., 1., 0.};
0486   auto lifetimes_signs = ipEstimator.getLifetimeSignOfTrack(
0487       track, ip_vtx, direction, geoContext, magFieldContext);
0488 
0489   // Check if the result is OK
0490   BOOST_CHECK(lifetimes_signs.ok());
0491 
0492   // Check that d0 sign is positive
0493   BOOST_CHECK_GT((*lifetimes_signs).first, 0.);
0494 
0495   // Check that z0 sign is negative
0496   BOOST_CHECK_LT((*lifetimes_signs).second, 0.);
0497 
0498   // Check the 3d sign
0499 
0500   auto sign3d = ipEstimator.get3DLifetimeSignOfTrack(
0501       track, ip_vtx, direction, geoContext, magFieldContext);
0502 
0503   // Check result is OK
0504   BOOST_CHECK(sign3d.ok());
0505 
0506   // Check 3D sign (should be positive)
0507   BOOST_CHECK_GT((*sign3d), 0.);
0508 }
0509 
0510 // Check `.getImpactParameters`.
0511 BOOST_DATA_TEST_CASE(SingeTrackImpactParameters, tracks* vertices, d0, l0, t0,
0512                      phi, theta, p, q, vx0, vy0, vz0, vt0) {
0513   BoundVector par;
0514   par[eBoundLoc0] = d0;
0515   par[eBoundLoc1] = l0;
0516   par[eBoundTime] = t0;
0517   par[eBoundPhi] = phi;
0518   par[eBoundTheta] = theta;
0519   par[eBoundQOverP] = q / p;
0520   Vector4 vtxPos;
0521   vtxPos[ePos0] = vx0;
0522   vtxPos[ePos1] = vy0;
0523   vtxPos[ePos2] = vz0;
0524   vtxPos[eTime] = vt0;
0525 
0526   Estimator ipEstimator = makeEstimator(1_T);
0527   Estimator::State state{magFieldCache()};
0528 
0529   // reference position and corresponding perigee surface
0530   Vector3 refPosition(0., 0., 0.);
0531   auto perigeeSurface = Surface::makeShared<PerigeeSurface>(refPosition);
0532   // create track and vertex
0533   BoundTrackParameters track(perigeeSurface, par,
0534                              makeBoundParametersCovariance(),
0535                              ParticleHypothesis::pionLike(std::abs(q)));
0536   Vertex myConstraint(vtxPos, makeVertexCovariance(), {});
0537 
0538   // check that computed impact parameters are meaningful
0539   ImpactParametersAndSigma output =
0540       ipEstimator
0541           .getImpactParameters(track, myConstraint, geoContext, magFieldContext)
0542           .value();
0543   BOOST_CHECK_NE(output.d0, 0.);
0544   BOOST_CHECK_NE(output.z0, 0.);
0545   // TODO what about the other struct members? can the parameter space be
0546   // restricted further?
0547 }
0548 
0549 BOOST_AUTO_TEST_SUITE_END()