Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:12:54

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 #pragma once
0010 
0011 #include <boost/test/unit_test.hpp>
0012 
0013 #include "Acts/Definitions/TrackParametrization.hpp"
0014 #include "Acts/Definitions/Units.hpp"
0015 #include "Acts/EventData/MultiTrajectory.hpp"
0016 #include "Acts/EventData/ProxyAccessor.hpp"
0017 #include "Acts/EventData/SourceLink.hpp"
0018 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0019 #include "Acts/EventData/VectorTrackContainer.hpp"
0020 #include "Acts/EventData/detail/TestSourceLink.hpp"
0021 #include "Acts/Geometry/TrackingGeometry.hpp"
0022 #include "Acts/MagneticField/ConstantBField.hpp"
0023 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0024 #include "Acts/Propagator/Navigator.hpp"
0025 #include "Acts/Propagator/StraightLineStepper.hpp"
0026 #include "Acts/Surfaces/CurvilinearSurface.hpp"
0027 #include "Acts/Tests/CommonHelpers/CubicTrackingGeometry.hpp"
0028 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0029 #include "Acts/Tests/CommonHelpers/MeasurementsCreator.hpp"
0030 #include "Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp"
0031 #include "Acts/Utilities/CalibrationContext.hpp"
0032 #include "Acts/Utilities/Logger.hpp"
0033 
0034 #include <iterator>
0035 
0036 using namespace Acts::UnitLiterals;
0037 using namespace Acts::Test;
0038 
0039 /// Find outliers using plain distance for testing purposes.
0040 ///
0041 /// In a real setup, the outlier classification can be much more involved, e.g.
0042 /// by computing the weighted distance/ local chi2 value. Here, the purpose is
0043 /// to test that the basic principle works using simplified, synthetic data.
0044 /// Thus, the simplest possible implementation should do.
0045 struct TestOutlierFinder {
0046   double distanceMax = std::numeric_limits<double>::max();
0047 
0048   /// Classify a measurement as a valid one or an outlier.
0049   ///
0050   /// @tparam track_state_t Type of the track state
0051   /// @param state The track state to classify
0052   /// @retval False if the measurement is not an outlier
0053   /// @retval True if the measurement is an outlier
0054   template <typename traj_t>
0055   bool operator()(typename traj_t::ConstTrackStateProxy state) const {
0056     // can't determine an outlier w/o a measurement or predicted parameters
0057     if (!state.hasCalibrated() || !state.hasPredicted()) {
0058       return false;
0059     }
0060     auto subspaceHelper = state.projectorSubspaceHelper();
0061     auto projector =
0062         subspaceHelper.fullProjector()
0063             .topLeftCorner(state.calibratedSize(), Acts::eBoundSize)
0064             .eval();
0065     auto residuals =
0066         (state.effectiveCalibrated() - projector * state.predicted()).eval();
0067     auto distance = residuals.norm();
0068     return (distanceMax <= distance);
0069   }
0070 };
0071 
0072 /// Determine if the smoothing of a track should be done with or without reverse
0073 /// filtering
0074 struct TestReverseFilteringLogic {
0075   double momentumMax = std::numeric_limits<double>::max();
0076 
0077   /// Classify a measurement as a valid one or an outlier.
0078   ///
0079   /// @param trackState The trackState of the last measurement
0080   /// @retval False if we don't use the reverse filtering for the smoothing of the track
0081   /// @retval True if we use the reverse filtering for the smoothing of the track
0082   template <typename traj_t>
0083   bool operator()(typename traj_t::ConstTrackStateProxy state) const {
0084     // can't determine an outlier w/o a measurement or predicted parameters
0085     auto momentum = std::abs(1 / state.filtered()[Acts::eBoundQOverP]);
0086     std::cout << "momentum : " << momentum << std::endl;
0087     return (momentum <= momentumMax);
0088   }
0089 };
0090 
0091 // Construct a straight-line propagator.
0092 auto makeStraightPropagator(std::shared_ptr<const Acts::TrackingGeometry> geo) {
0093   Acts::Navigator::Config cfg{std::move(geo)};
0094   cfg.resolvePassive = false;
0095   cfg.resolveMaterial = true;
0096   cfg.resolveSensitive = true;
0097   Acts::Navigator navigator(
0098       cfg, Acts::getDefaultLogger("Navigator", Acts::Logging::INFO));
0099   Acts::StraightLineStepper stepper;
0100   return Acts::Propagator<Acts::StraightLineStepper, Acts::Navigator>(
0101       stepper, std::move(navigator));
0102 }
0103 
0104 // Construct a propagator using a constant magnetic field along z.
0105 template <typename stepper_t>
0106 auto makeConstantFieldPropagator(
0107     std::shared_ptr<const Acts::TrackingGeometry> geo, double bz) {
0108   Acts::Navigator::Config cfg{std::move(geo)};
0109   cfg.resolvePassive = false;
0110   cfg.resolveMaterial = true;
0111   cfg.resolveSensitive = true;
0112   Acts::Navigator navigator(
0113       cfg, Acts::getDefaultLogger("Navigator", Acts::Logging::INFO));
0114   auto field =
0115       std::make_shared<Acts::ConstantBField>(Acts::Vector3(0.0, 0.0, bz));
0116   stepper_t stepper(std::move(field));
0117   return Acts::Propagator<decltype(stepper), Acts::Navigator>(
0118       std::move(stepper), std::move(navigator));
0119 }
0120 
0121 // Put all this in a struct to avoid that all these objects are exposed as
0122 // global objects in the header
0123 struct FitterTester {
0124   using Rng = std::default_random_engine;
0125 
0126   // Context objects
0127   Acts::GeometryContext geoCtx;
0128   Acts::MagneticFieldContext magCtx;
0129   Acts::CalibrationContext calCtx;
0130 
0131   // detector geometry
0132   CubicTrackingGeometry geometryStore{geoCtx};
0133   std::shared_ptr<const Acts::TrackingGeometry> geometry = geometryStore();
0134 
0135   Acts::detail::Test::TestSourceLink::SurfaceAccessor surfaceAccessor{
0136       *geometry};
0137 
0138   // expected number of measurements for the given detector
0139   constexpr static std::size_t nMeasurements = 6u;
0140 
0141   // detector resolutions
0142   MeasurementResolution resPixel = {MeasurementType::eLoc01, {25_um, 50_um}};
0143   MeasurementResolution resStrip0 = {MeasurementType::eLoc0, {100_um}};
0144   MeasurementResolution resStrip1 = {MeasurementType::eLoc1, {150_um}};
0145   MeasurementResolutionMap resolutions = {
0146       {Acts::GeometryIdentifier().setVolume(2), resPixel},
0147       {Acts::GeometryIdentifier().setVolume(3).setLayer(2), resStrip0},
0148       {Acts::GeometryIdentifier().setVolume(3).setLayer(4), resStrip1},
0149       {Acts::GeometryIdentifier().setVolume(3).setLayer(6), resStrip0},
0150       {Acts::GeometryIdentifier().setVolume(3).setLayer(8), resStrip1},
0151   };
0152 
0153   // simulation propagator
0154   Acts::Propagator<Acts::StraightLineStepper, Acts::Navigator> simPropagator =
0155       makeStraightPropagator(geometry);
0156 
0157   static std::vector<Acts::SourceLink> prepareSourceLinks(
0158       const std::vector<Acts::detail::Test::TestSourceLink>& sourceLinks) {
0159     std::vector<Acts::SourceLink> result;
0160     std::transform(sourceLinks.begin(), sourceLinks.end(),
0161                    std::back_inserter(result),
0162                    [](const auto& sl) { return Acts::SourceLink{sl}; });
0163     return result;
0164   }
0165 
0166   //////////////////////////
0167   // The testing functions
0168   //////////////////////////
0169 
0170   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0171   void test_ZeroFieldNoSurfaceForward(const fitter_t& fitter,
0172                                       fitter_options_t options,
0173                                       const parameters_t& start, Rng& rng,
0174                                       const bool expected_reversed,
0175                                       const bool expected_smoothed,
0176                                       const bool doDiag) const {
0177     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0178                                            resolutions, rng);
0179 
0180     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0181     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0182 
0183     // this is the default option. set anyway for consistency
0184     options.referenceSurface = nullptr;
0185 
0186     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0187     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0188 
0189     auto doTest = [&](bool diag) {
0190       Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0191                                   Acts::VectorMultiTrajectory{}};
0192       if (diag) {
0193         tracks.addColumn<bool>("reversed");
0194         tracks.addColumn<bool>("smoothed");
0195 
0196         BOOST_CHECK(tracks.hasColumn("reversed"));
0197         BOOST_CHECK(tracks.hasColumn("smoothed"));
0198       }
0199 
0200       auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0201                             options, tracks);
0202       BOOST_REQUIRE(res.ok());
0203 
0204       const auto track = res.value();
0205       BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0206       BOOST_CHECK(!track.hasReferenceSurface());
0207       BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0208       BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0209 
0210       if (diag) {
0211         // check the output status flags
0212         BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0213         BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0214       }
0215     };
0216 
0217     if (doDiag) {
0218       doTest(true);
0219     }  // with reversed & smoothed columns
0220     doTest(false);  // without the extra columns
0221   }
0222 
0223   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0224   void test_ZeroFieldWithSurfaceForward(const fitter_t& fitter,
0225                                         fitter_options_t options,
0226                                         const parameters_t& start, Rng& rng,
0227                                         const bool expected_reversed,
0228                                         const bool expected_smoothed,
0229                                         const bool doDiag) const {
0230     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0231                                            resolutions, rng);
0232     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0233     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0234 
0235     // initial fitter options configured for backward filtering mode
0236     // backward filtering requires a reference surface
0237     options.referenceSurface = &start.referenceSurface();
0238     // this is the default option. set anyway for consistency
0239     options.propagatorPlainOptions.direction = Acts::Direction::Forward();
0240 
0241     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0242                                 Acts::VectorMultiTrajectory{}};
0243     tracks.addColumn<bool>("reversed");
0244     tracks.addColumn<bool>("smoothed");
0245 
0246     auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0247                           options, tracks);
0248     BOOST_REQUIRE(res.ok());
0249 
0250     const auto& track = res.value();
0251     BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0252     BOOST_CHECK(track.hasReferenceSurface());
0253     BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0254     BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0255 
0256     BOOST_CHECK(tracks.hasColumn("reversed"));
0257     BOOST_CHECK(tracks.hasColumn("smoothed"));
0258 
0259     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0260     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0261 
0262     // check the output status flags
0263     if (doDiag) {
0264       BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0265       BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0266     }
0267 
0268     // count the number of `smoothed` states
0269     if (expected_reversed && expected_smoothed) {
0270       std::size_t nSmoothed = 0;
0271       for (const auto ts : track.trackStatesReversed()) {
0272         nSmoothed += ts.hasSmoothed();
0273       }
0274       BOOST_CHECK_EQUAL(nSmoothed, sourceLinks.size());
0275     }
0276   }
0277 
0278   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0279   void test_ZeroFieldWithSurfaceBackward(const fitter_t& fitter,
0280                                          fitter_options_t options,
0281                                          const parameters_t& start, Rng& rng,
0282                                          const bool expected_reversed,
0283                                          const bool expected_smoothed,
0284                                          const bool doDiag) const {
0285     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0286                                            resolutions, rng);
0287     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0288     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0289 
0290     // create a track near the tracker exit for outward->inward filtering
0291     Acts::Vector4 posOuter = start.fourPosition(geoCtx);
0292     posOuter[Acts::ePos0] = 3_m;
0293     Acts::CurvilinearTrackParameters startOuter(
0294         posOuter, start.direction(), start.qOverP(), start.covariance(),
0295         Acts::ParticleHypothesis::pion());
0296 
0297     options.referenceSurface = &startOuter.referenceSurface();
0298     options.propagatorPlainOptions.direction = Acts::Direction::Backward();
0299 
0300     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0301                                 Acts::VectorMultiTrajectory{}};
0302     tracks.addColumn<bool>("reversed");
0303     tracks.addColumn<bool>("smoothed");
0304 
0305     auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), startOuter,
0306                           options, tracks);
0307     BOOST_CHECK(res.ok());
0308 
0309     const auto& track = res.value();
0310     BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0311     BOOST_CHECK(track.hasReferenceSurface());
0312     BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0313     BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0314 
0315     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0316     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0317     // check the output status flags
0318     if (doDiag) {
0319       BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0320       BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0321     }
0322 
0323     // count the number of `smoothed` states
0324     if (expected_reversed && expected_smoothed) {
0325       std::size_t nSmoothed = 0;
0326       for (const auto ts : track.trackStatesReversed()) {
0327         nSmoothed += ts.hasSmoothed();
0328       }
0329       BOOST_CHECK_EQUAL(nSmoothed, sourceLinks.size());
0330     }
0331   }
0332 
0333   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0334   void test_ZeroFieldWithSurfaceAtExit(const fitter_t& fitter,
0335                                        fitter_options_t options,
0336                                        const parameters_t& start, Rng& rng,
0337                                        const bool expected_reversed,
0338                                        const bool expected_smoothed,
0339                                        const bool doDiag) const {
0340     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0341                                            resolutions, rng);
0342     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0343     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0344 
0345     // create a boundless target surface near the tracker exit
0346     Acts::Vector3 center(3._m, 0., 0.);
0347     Acts::Vector3 normal(1., 0., 0.);
0348     auto targetSurface =
0349         Acts::CurvilinearSurface(center, normal).planeSurface();
0350 
0351     options.referenceSurface = targetSurface.get();
0352 
0353     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0354                                 Acts::VectorMultiTrajectory{}};
0355     tracks.addColumn<bool>("reversed");
0356     tracks.addColumn<bool>("smoothed");
0357 
0358     auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0359                           options, tracks);
0360     BOOST_REQUIRE(res.ok());
0361 
0362     const auto& track = res.value();
0363     BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0364     BOOST_CHECK(track.hasReferenceSurface());
0365     BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0366     BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0367 
0368     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0369     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0370 
0371     // check the output status flags
0372     if (doDiag) {
0373       BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0374       BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0375     }
0376   }
0377 
0378   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0379   void test_ZeroFieldShuffled(const fitter_t& fitter, fitter_options_t options,
0380                               const parameters_t& start, Rng& rng,
0381                               const bool expected_reversed,
0382                               const bool expected_smoothed,
0383                               const bool doDiag) const {
0384     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0385                                            resolutions, rng);
0386     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0387     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0388 
0389     options.referenceSurface = &start.referenceSurface();
0390 
0391     Acts::BoundVector parameters = Acts::BoundVector::Zero();
0392 
0393     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0394                                 Acts::VectorMultiTrajectory{}};
0395     tracks.addColumn<bool>("reversed");
0396     tracks.addColumn<bool>("smoothed");
0397 
0398     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0399     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0400 
0401     // fit w/ all hits in order
0402     {
0403       auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0404                             options, tracks);
0405       BOOST_REQUIRE(res.ok());
0406 
0407       const auto& track = res.value();
0408       BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0409       BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0410       BOOST_REQUIRE(track.hasReferenceSurface());
0411       parameters = track.parameters();
0412       BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0413 
0414       // check the output status flags
0415       if (doDiag) {
0416         BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0417         BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0418       }
0419     }
0420     // fit w/ all hits in random order
0421     {
0422       decltype(sourceLinks) shuffledSourceLinks = sourceLinks;
0423       std::shuffle(shuffledSourceLinks.begin(), shuffledSourceLinks.end(), rng);
0424       auto res = fitter.fit(shuffledSourceLinks.begin(),
0425                             shuffledSourceLinks.end(), start, options, tracks);
0426       BOOST_REQUIRE(res.ok());
0427 
0428       const auto& track = res.value();
0429       BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0430       BOOST_REQUIRE(track.hasReferenceSurface());
0431       // check consistency w/ un-shuffled measurements
0432       CHECK_CLOSE_ABS(track.parameters(), parameters, 1e-5);
0433       BOOST_CHECK_EQUAL(track.nMeasurements(), sourceLinks.size());
0434       // check the output status flags
0435       if (doDiag) {
0436         BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0437         BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0438       }
0439     }
0440   }
0441 
0442   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0443   void test_ZeroFieldWithHole(const fitter_t& fitter, fitter_options_t options,
0444                               const parameters_t& start, Rng& rng,
0445                               const bool expected_reversed,
0446                               const bool expected_smoothed,
0447                               const bool doDiag) const {
0448     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0449                                            resolutions, rng);
0450     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0451     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0452 
0453     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0454                                 Acts::VectorMultiTrajectory{}};
0455     tracks.addColumn<bool>("reversed");
0456     tracks.addColumn<bool>("smoothed");
0457 
0458     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0459     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0460 
0461     // always keep the first and last measurement. leaving those in seems to not
0462     // count the respective surfaces as holes.
0463     for (std::size_t i = 1u; (i + 1u) < sourceLinks.size(); ++i) {
0464       // remove the i-th measurement
0465       auto withHole = sourceLinks;
0466       withHole.erase(std::next(withHole.begin(), i));
0467       BOOST_REQUIRE_EQUAL(withHole.size() + 1u, sourceLinks.size());
0468       BOOST_TEST_INFO("Removed measurement " << i);
0469 
0470       auto res =
0471           fitter.fit(withHole.begin(), withHole.end(), start, options, tracks);
0472       BOOST_REQUIRE(res.ok());
0473 
0474       const auto& track = res.value();
0475       BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0476       BOOST_REQUIRE(!track.hasReferenceSurface());
0477       BOOST_CHECK_EQUAL(track.nMeasurements(), withHole.size());
0478       // check the output status flags
0479       if (doDiag) {
0480         BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0481         BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0482       }
0483       BOOST_CHECK_EQUAL(track.nHoles(), 1u);
0484     }
0485     BOOST_CHECK_EQUAL(tracks.size(), sourceLinks.size() - 2);
0486   }
0487 
0488   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0489   void test_ZeroFieldWithOutliers(const fitter_t& fitter,
0490                                   fitter_options_t options,
0491                                   const parameters_t& start, Rng& rng,
0492                                   const bool expected_reversed,
0493                                   const bool expected_smoothed,
0494                                   const bool doDiag) const {
0495     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0496                                            resolutions, rng);
0497     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0498     auto outlierSourceLinks =
0499         prepareSourceLinks(measurements.outlierSourceLinks);
0500     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0501     BOOST_REQUIRE_EQUAL(outlierSourceLinks.size(), nMeasurements);
0502 
0503     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0504                                 Acts::VectorMultiTrajectory{}};
0505     tracks.addColumn<bool>("reversed");
0506     tracks.addColumn<bool>("smoothed");
0507 
0508     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0509     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0510 
0511     for (std::size_t i = 0; i < sourceLinks.size(); ++i) {
0512       // replace the i-th measurement with an outlier
0513       auto withOutlier = sourceLinks;
0514       withOutlier[i] = outlierSourceLinks[i];
0515       BOOST_REQUIRE_EQUAL(withOutlier.size(), sourceLinks.size());
0516       BOOST_TEST_INFO("Replaced measurement " << i << " with outlier");
0517 
0518       auto res = fitter.fit(withOutlier.begin(), withOutlier.end(), start,
0519                             options, tracks);
0520       BOOST_REQUIRE(res.ok());
0521 
0522       const auto& track = res.value();
0523       BOOST_CHECK_NE(track.tipIndex(), Acts::MultiTrajectoryTraits::kInvalid);
0524       // count the number of outliers
0525       std::size_t nOutliers = 0;
0526       for (const auto state : track.trackStatesReversed()) {
0527         nOutliers += state.typeFlags().test(Acts::TrackStateFlag::OutlierFlag);
0528       }
0529       BOOST_CHECK_EQUAL(nOutliers, 1u);
0530       BOOST_REQUIRE(!track.hasReferenceSurface());
0531       BOOST_CHECK_EQUAL(track.nMeasurements(), withOutlier.size() - 1u);
0532       // check the output status flags
0533       if (doDiag) {
0534         BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0535         BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0536       }
0537       BOOST_CHECK_EQUAL(track.nHoles(), 0u);
0538     }
0539     BOOST_CHECK_EQUAL(tracks.size(), sourceLinks.size());
0540   }
0541 
0542   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0543   void test_ZeroFieldWithReverseFiltering(const fitter_t& fitter,
0544                                           fitter_options_t options,
0545                                           const parameters_t& start, Rng& rng,
0546                                           const bool expected_reversed,
0547                                           const bool expected_smoothed,
0548                                           const bool doDiag) const {
0549     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0550                                            resolutions, rng);
0551 
0552     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0553                                 Acts::VectorMultiTrajectory{}};
0554     tracks.addColumn<bool>("reversed");
0555     tracks.addColumn<bool>("smoothed");
0556 
0557     Acts::ConstProxyAccessor<bool> reversed{"reversed"};
0558     Acts::ConstProxyAccessor<bool> smoothed{"smoothed"};
0559 
0560     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0561 
0562     const auto& outlierSourceLinks = measurements.outlierSourceLinks;
0563     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0564     BOOST_REQUIRE_EQUAL(outlierSourceLinks.size(), nMeasurements);
0565 
0566     // create a boundless target surface near the tracker entry
0567     Acts::Vector3 center(-3._m, 0., 0.);
0568     Acts::Vector3 normal(1., 0., 0.);
0569     auto targetSurface =
0570         Acts::CurvilinearSurface(center, normal).planeSurface();
0571 
0572     options.referenceSurface = targetSurface.get();
0573 
0574     auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0575                           options, tracks);
0576     BOOST_REQUIRE(res.ok());
0577     const auto& track = res.value();
0578 
0579     // Track of 1 GeV with a threshold set at 0.1 GeV, reversed filtering should
0580     // not be used
0581     if (doDiag) {
0582       BOOST_CHECK_EQUAL(smoothed(track), expected_smoothed);
0583       BOOST_CHECK_EQUAL(reversed(track), expected_reversed);
0584     }
0585   }
0586 
0587   // TODO this is not really Kalman fitter specific. is probably better tested
0588   // with a synthetic trajectory.
0589   template <typename fitter_t, typename fitter_options_t, typename parameters_t>
0590   void test_GlobalCovariance(const fitter_t& fitter, fitter_options_t options,
0591                              const parameters_t& start, Rng& rng) const {
0592     auto measurements = createMeasurements(simPropagator, geoCtx, magCtx, start,
0593                                            resolutions, rng);
0594     auto sourceLinks = prepareSourceLinks(measurements.sourceLinks);
0595     BOOST_REQUIRE_EQUAL(sourceLinks.size(), nMeasurements);
0596 
0597     Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0598                                 Acts::VectorMultiTrajectory{}};
0599 
0600     auto res = fitter.fit(sourceLinks.begin(), sourceLinks.end(), start,
0601                           options, tracks);
0602     BOOST_REQUIRE(res.ok());
0603 
0604     // Calculate global track parameters covariance matrix
0605     const auto& track = res.value();
0606     auto [trackParamsCov, stateRowIndices] =
0607         Acts::detail::globalTrackParametersCovariance(
0608             tracks.trackStateContainer(), track.tipIndex());
0609     BOOST_CHECK_EQUAL(trackParamsCov.rows(),
0610                       sourceLinks.size() * Acts::eBoundSize);
0611     BOOST_CHECK_EQUAL(stateRowIndices.size(), sourceLinks.size());
0612     // Each smoothed track state will have eBoundSize rows/cols in the global
0613     // covariance. stateRowIndices is a map of the starting row/index with the
0614     // state tip as the key. Thus, the last track state (i.e. the state
0615     // corresponding track.tipIndex()) has a starting row/index =
0616     // eBoundSize * (nMeasurements - 1), i.e. 6*(6-1) = 30.
0617     BOOST_CHECK_EQUAL(stateRowIndices.at(track.tipIndex()),
0618                       Acts::eBoundSize * (nMeasurements - 1));
0619   }
0620 };