Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-18 07:49:24

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 "Acts/Definitions/Direction.hpp"
0012 #include "Acts/Definitions/Units.hpp"
0013 #include "Acts/Geometry/CuboidVolumeBuilder.hpp"
0014 #include "Acts/Geometry/GeometryContext.hpp"
0015 #include "Acts/Geometry/TrackingGeometry.hpp"
0016 #include "Acts/Geometry/TrackingGeometryBuilder.hpp"
0017 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0018 #include "Acts/Material/HomogeneousVolumeMaterial.hpp"
0019 #include "Acts/Material/Interactions.hpp"
0020 #include "Acts/Material/Material.hpp"
0021 #include "Acts/Surfaces/CylinderSurface.hpp"
0022 #include "Acts/Surfaces/DiscSurface.hpp"
0023 #include "Acts/Surfaces/PlaneSurface.hpp"
0024 #include "Acts/Surfaces/RectangleBounds.hpp"
0025 #include "Acts/Surfaces/StrawSurface.hpp"
0026 #include "Acts/Utilities/Logger.hpp"
0027 #include "Acts/Utilities/MathHelpers.hpp"
0028 #include "Acts/Utilities/UnitVectors.hpp"
0029 #include "Acts/Utilities/detail/periodic.hpp"
0030 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0031 #include "ActsTests/CommonHelpers/PredefinedMaterials.hpp"
0032 
0033 #include <utility>
0034 
0035 inline std::shared_ptr<const Acts::TrackingGeometry> createDenseBlock(
0036     const Acts::GeometryContext& geoCtx) {
0037   using namespace Acts;
0038   using namespace ActsTests;
0039   using namespace UnitLiterals;
0040 
0041   CuboidVolumeBuilder::VolumeConfig vConf;
0042   vConf.position = {0., 0., 0.};
0043   vConf.length = {4_m, 4_m, 4_m};
0044   vConf.volumeMaterial =
0045       std::make_shared<const HomogeneousVolumeMaterial>(makeBeryllium());
0046   CuboidVolumeBuilder::Config conf;
0047   conf.volumeCfg.push_back(vConf);
0048   conf.position = {0., 0., 0.};
0049   conf.length = {4_m, 4_m, 4_m};
0050   CuboidVolumeBuilder cvb(conf);
0051   TrackingGeometryBuilder::Config tgbCfg;
0052   tgbCfg.trackingVolumeBuilders.push_back(
0053       [=](const auto& context, const auto& inner, const auto&) {
0054         return cvb.trackingVolume(context, inner, nullptr);
0055       });
0056 
0057   return TrackingGeometryBuilder(tgbCfg).trackingGeometry(geoCtx);
0058 }
0059 
0060 inline std::tuple<std::shared_ptr<const Acts::TrackingGeometry>,
0061                   std::vector<const Acts::Surface*>>
0062 createDenseTelescope(const Acts::GeometryContext& geoCtx,
0063                      Acts::Material material, double thickness) {
0064   using namespace Acts;
0065   using namespace UnitLiterals;
0066 
0067   CuboidVolumeBuilder::Config conf;
0068   conf.position = {0., 0., 0.};
0069   conf.length = {4_m, 2_m, 2_m};
0070 
0071   {
0072     CuboidVolumeBuilder::VolumeConfig start;
0073     start.position = {-1_m, 0, 0};
0074     start.length = {1_m, 1_m, 1_m};
0075     start.name = "start";
0076 
0077     conf.volumeCfg.push_back(start);
0078   }
0079 
0080   if (thickness < 1_m) {
0081     CuboidVolumeBuilder::VolumeConfig gap;
0082     gap.position = {-0.25 * (1_m + thickness), 0, 0};
0083     gap.length = {0.5 * (1_m - thickness), 1_m, 1_m};
0084     gap.name = "gap1";
0085 
0086     conf.volumeCfg.push_back(gap);
0087   }
0088 
0089   {
0090     CuboidVolumeBuilder::VolumeConfig dense;
0091     dense.position = {0, 0, 0};
0092     dense.length = {thickness, 1_m, 1_m};
0093     dense.volumeMaterial =
0094         std::make_shared<const HomogeneousVolumeMaterial>(material);
0095     dense.name = "dense";
0096 
0097     conf.volumeCfg.push_back(dense);
0098   }
0099 
0100   if (thickness < 1_m) {
0101     CuboidVolumeBuilder::VolumeConfig gap;
0102     gap.position = {0.25 * (1_m + thickness), 0, 0};
0103     gap.length = {0.5 * (1_m - thickness), 1_m, 1_m};
0104     gap.name = "gap2";
0105 
0106     conf.volumeCfg.push_back(gap);
0107   }
0108 
0109   {
0110     CuboidVolumeBuilder::SurfaceConfig surface;
0111     surface.position = {1.5_m, 0, 0};
0112     surface.rotation =
0113         Eigen::AngleAxisd(std::numbers::pi / 2, Eigen::Vector3d::UnitY())
0114             .matrix();
0115     surface.rBounds = std::make_shared<RectangleBounds>(1_m, 1_m);
0116 
0117     CuboidVolumeBuilder::LayerConfig layer;
0118     layer.surfaceCfg.push_back(surface);
0119 
0120     CuboidVolumeBuilder::VolumeConfig end;
0121     end.position = {1_m, 0, 0};
0122     end.length = {1_m, 1_m, 1_m};
0123     end.layerCfg.push_back(layer);
0124     end.name = "end";
0125 
0126     conf.volumeCfg.push_back(end);
0127   }
0128 
0129   CuboidVolumeBuilder cvb(conf);
0130 
0131   TrackingGeometryBuilder::Config tgbCfg;
0132   tgbCfg.trackingVolumeBuilders.push_back(
0133       [=](const auto& context, const auto& inner, const auto&) {
0134         return cvb.trackingVolume(context, inner, nullptr);
0135       });
0136   auto detector = TrackingGeometryBuilder(tgbCfg).trackingGeometry(geoCtx);
0137 
0138   std::vector<const Surface*> surfaces;
0139   detector->visitSurfaces(
0140       [&](const Surface* surface) { surfaces.push_back(surface); });
0141 
0142   return {std::move(detector), std::move(surfaces)};
0143 }
0144 
0145 // parameter construction helpers
0146 
0147 /// Construct (initial) curvilinear parameters.
0148 inline Acts::BoundTrackParameters makeParametersCurvilinear(double phi,
0149                                                             double theta,
0150                                                             double absMom,
0151                                                             double charge) {
0152   using namespace Acts;
0153   using namespace UnitLiterals;
0154 
0155   Vector4 pos4 = Vector4::Zero();
0156   auto particleHypothesis = ParticleHypothesis::pionLike(std::abs(charge));
0157   return BoundTrackParameters::createCurvilinear(
0158       pos4, phi, theta, particleHypothesis.qOverP(absMom, charge), std::nullopt,
0159       particleHypothesis);
0160 }
0161 
0162 /// Construct (initial) curvilinear parameters with covariance.
0163 inline Acts::BoundTrackParameters makeParametersCurvilinearWithCovariance(
0164     double phi, double theta, double absMom, double charge) {
0165   using namespace Acts;
0166   using namespace UnitLiterals;
0167 
0168   BoundVector stddev = BoundVector::Zero();
0169   // TODO use momentum-dependent resolutions
0170   stddev[eBoundLoc0] = 15_um;
0171   stddev[eBoundLoc1] = 80_um;
0172   stddev[eBoundTime] = 20_ps;
0173   stddev[eBoundPhi] = 20_mrad;
0174   stddev[eBoundTheta] = 30_mrad;
0175   stddev[eBoundQOverP] = 1_e / 10_GeV;
0176   BoundMatrix corr = BoundMatrix::Identity();
0177   corr(eBoundLoc0, eBoundLoc1) = corr(eBoundLoc1, eBoundLoc0) = 0.125;
0178   corr(eBoundLoc0, eBoundPhi) = corr(eBoundPhi, eBoundLoc0) = 0.25;
0179   corr(eBoundLoc1, eBoundTheta) = corr(eBoundTheta, eBoundLoc1) = -0.25;
0180   corr(eBoundTime, eBoundQOverP) = corr(eBoundQOverP, eBoundTime) = 0.125;
0181   corr(eBoundPhi, eBoundTheta) = corr(eBoundTheta, eBoundPhi) = -0.25;
0182   corr(eBoundPhi, eBoundQOverP) = corr(eBoundPhi, eBoundQOverP) = -0.125;
0183   corr(eBoundTheta, eBoundQOverP) = corr(eBoundTheta, eBoundQOverP) = 0.5;
0184   BoundMatrix cov = stddev.asDiagonal() * corr * stddev.asDiagonal();
0185 
0186   Vector4 pos4 = Vector4::Zero();
0187   auto particleHypothesis = ParticleHypothesis::pionLike(std::abs(charge));
0188   return BoundTrackParameters::createCurvilinear(
0189       pos4, phi, theta, particleHypothesis.qOverP(absMom, charge), cov,
0190       particleHypothesis);
0191 }
0192 
0193 /// Construct (initial) neutral curvilinear parameters.
0194 inline Acts::BoundTrackParameters makeParametersCurvilinearNeutral(
0195     double phi, double theta, double absMom) {
0196   using namespace Acts;
0197   using namespace UnitLiterals;
0198 
0199   Vector4 pos4 = Vector4::Zero();
0200   return BoundTrackParameters::createCurvilinear(
0201       pos4, phi, theta, 1 / absMom, std::nullopt, ParticleHypothesis::pion0());
0202 }
0203 
0204 // helpers to compare track parameters
0205 
0206 /// Check that two parameters object are consistent within the tolerances.
0207 ///
0208 /// \warning Does not check that they are defined on the same surface.
0209 inline void checkParametersConsistency(const Acts::BoundTrackParameters& cmp,
0210                                        const Acts::BoundTrackParameters& ref,
0211                                        const Acts::GeometryContext& geoCtx,
0212                                        double epsPos, double epsTime,
0213                                        double epsDir, double epsMom) {
0214   using namespace Acts;
0215 
0216   // check stored parameters
0217   CHECK_CLOSE_ABS(cmp.template get<eBoundLoc0>(),
0218                   ref.template get<eBoundLoc0>(), epsPos);
0219   CHECK_CLOSE_ABS(cmp.template get<eBoundLoc1>(),
0220                   ref.template get<eBoundLoc1>(), epsPos);
0221   CHECK_CLOSE_ABS(cmp.template get<eBoundTime>(),
0222                   ref.template get<eBoundTime>(), epsTime);
0223   // check phi equivalence with circularity
0224   CHECK_SMALL(detail::radian_sym(cmp.template get<eBoundPhi>() -
0225                                  ref.template get<eBoundPhi>()),
0226               epsDir);
0227   CHECK_CLOSE_ABS(cmp.template get<eBoundTheta>(),
0228                   ref.template get<eBoundTheta>(), epsDir);
0229   CHECK_CLOSE_ABS(cmp.template get<eBoundQOverP>(),
0230                   ref.template get<eBoundQOverP>(), epsMom);
0231   // check derived parameters
0232   CHECK_CLOSE_ABS(cmp.position(geoCtx), ref.position(geoCtx), epsPos);
0233   CHECK_CLOSE_ABS(cmp.time(), ref.time(), epsTime);
0234   CHECK_CLOSE_ABS(cmp.direction(), ref.direction(), epsDir);
0235   CHECK_CLOSE_ABS(cmp.absoluteMomentum(), ref.absoluteMomentum(), epsMom);
0236   // charge should be identical not just similar
0237   BOOST_CHECK_EQUAL(cmp.charge(), ref.charge());
0238 }
0239 
0240 /// Check that two parameters covariances are consistent within the tolerances.
0241 ///
0242 /// \warning Does not check that the parameters value itself are consistent.
0243 inline void checkCovarianceConsistency(const Acts::BoundTrackParameters& cmp,
0244                                        const Acts::BoundTrackParameters& ref,
0245                                        double relativeTolerance) {
0246   // either both or none have covariance set
0247   if (cmp.covariance().has_value()) {
0248     // comparison parameters have covariance but the reference does not
0249     BOOST_CHECK(ref.covariance().has_value());
0250   }
0251   if (ref.covariance().has_value()) {
0252     // reference parameters have covariance but the comparison does not
0253     BOOST_CHECK(cmp.covariance().has_value());
0254   }
0255   if (cmp.covariance().has_value() && ref.covariance().has_value()) {
0256     CHECK_CLOSE_COVARIANCE(cmp.covariance().value(), ref.covariance().value(),
0257                            relativeTolerance);
0258   }
0259 }
0260 
0261 // helpers to construct target surfaces from track states
0262 
0263 /// Construct the transformation from the curvilinear to the global coordinates.
0264 inline Acts::Transform3 createCurvilinearTransform(
0265     const Acts::BoundTrackParameters& params,
0266     const Acts::GeometryContext& geoCtx) {
0267   using namespace Acts;
0268 
0269   Vector3 unitW = params.direction();
0270   auto [unitU, unitV] = createCurvilinearUnitVectors(unitW);
0271 
0272   RotationMatrix3 rotation = RotationMatrix3::Zero();
0273   rotation.col(0) = unitU;
0274   rotation.col(1) = unitV;
0275   rotation.col(2) = unitW;
0276   Translation3 offset(params.position(geoCtx));
0277   Transform3 toGlobal = offset * rotation;
0278 
0279   return toGlobal;
0280 }
0281 
0282 /// Construct a z-cylinder centered at zero with the track on its surface.
0283 struct ZCylinderSurfaceBuilder {
0284   std::shared_ptr<Acts::CylinderSurface> operator()(
0285       const Acts::BoundTrackParameters& params,
0286       const Acts::GeometryContext& geoCtx) {
0287     using namespace Acts;
0288 
0289     auto radius = params.position(geoCtx).template head<2>().norm();
0290     auto halfz = std::numeric_limits<double>::max();
0291     return Surface::makeShared<CylinderSurface>(Transform3::Identity(), radius,
0292                                                 halfz);
0293   }
0294 };
0295 
0296 /// Construct a disc at track position with plane normal along track tangent.
0297 struct DiscSurfaceBuilder {
0298   std::shared_ptr<Acts::DiscSurface> operator()(
0299       const Acts::BoundTrackParameters& params,
0300       const Acts::GeometryContext& geoCtx) {
0301     using namespace Acts;
0302     using namespace UnitLiterals;
0303 
0304     auto cl = createCurvilinearTransform(params, geoCtx);
0305     // shift the origin of the plane so the local particle position does not
0306     // sit directly at the rho=0,phi=undefined singularity
0307     // TODO this is a hack do avoid issues with the numerical covariance
0308     //      transport that does not work well at rho=0,
0309     Vector3 localOffset = Vector3::Zero();
0310     localOffset[ePos0] = 1_cm;
0311     localOffset[ePos1] = -1_cm;
0312     Vector3 globalOriginDelta = cl.linear() * localOffset;
0313     cl.pretranslate(globalOriginDelta);
0314 
0315     return Surface::makeShared<DiscSurface>(cl);
0316   }
0317 };
0318 
0319 /// Construct a plane at track position with plane normal along track tangent.
0320 struct PlaneSurfaceBuilder {
0321   std::shared_ptr<Acts::PlaneSurface> operator()(
0322       const Acts::BoundTrackParameters& params,
0323       const Acts::GeometryContext& geoCtx) {
0324     using namespace Acts;
0325 
0326     return Surface::makeShared<PlaneSurface>(
0327         createCurvilinearTransform(params, geoCtx));
0328   }
0329 };
0330 
0331 /// Construct a z-straw at the track position.
0332 struct ZStrawSurfaceBuilder {
0333   std::shared_ptr<Acts::StrawSurface> operator()(
0334       const Acts::BoundTrackParameters& params,
0335       const Acts::GeometryContext& geoCtx) {
0336     using namespace Acts;
0337 
0338     return Surface::makeShared<StrawSurface>(
0339         Transform3(Translation3(params.position(geoCtx))));
0340   }
0341 };
0342 
0343 // helper functions to run the propagation with additional checks
0344 
0345 /// Propagate the initial parameters for the given pathlength in space.
0346 ///
0347 /// Use a negative path length to indicate backward propagation.
0348 template <typename propagator_t,
0349           typename options_t = typename propagator_t::template Options<>>
0350 inline std::pair<Acts::BoundTrackParameters, double> transportFreely(
0351     const propagator_t& propagator, const Acts::GeometryContext& geoCtx,
0352     const Acts::MagneticFieldContext& magCtx,
0353     const Acts::BoundTrackParameters& initialParams, double pathLength) {
0354   using namespace Acts;
0355   using namespace UnitLiterals;
0356 
0357   // setup propagation options
0358   options_t options(geoCtx, magCtx);
0359   options.direction = Direction::fromScalar(pathLength);
0360   options.pathLimit = pathLength;
0361 
0362   auto result = propagator.propagate(initialParams, options);
0363   BOOST_CHECK(result.ok());
0364   BOOST_CHECK(result.value().endParameters);
0365 
0366   return {*result.value().endParameters, result.value().pathLength};
0367 }
0368 
0369 /// Propagate the initial parameters to the target surface.
0370 template <typename propagator_t,
0371           typename options_t = typename propagator_t::template Options<>>
0372 inline std::pair<Acts::BoundTrackParameters, double> transportToSurface(
0373     const propagator_t& propagator, const Acts::GeometryContext& geoCtx,
0374     const Acts::MagneticFieldContext& magCtx,
0375     const Acts::BoundTrackParameters& initialParams,
0376     const Acts::Surface& targetSurface, double pathLimit) {
0377   using namespace Acts;
0378   using namespace UnitLiterals;
0379 
0380   // setup propagation options
0381   options_t options(geoCtx, magCtx);
0382   options.direction = Direction::Forward();
0383   options.pathLimit = pathLimit;
0384 
0385   auto result = propagator.propagate(initialParams, targetSurface, options);
0386   BOOST_CHECK(result.ok());
0387   BOOST_CHECK(result.value().endParameters);
0388 
0389   return {*result.value().endParameters, result.value().pathLength};
0390 }
0391 
0392 // self-consistency tests for a single propagator
0393 
0394 /// Propagate the initial parameters the given path length along its
0395 /// trajectory and then propagate the final parameters back. Verify that the
0396 /// propagated parameters match the initial ones.
0397 template <typename propagator_t,
0398           typename options_t = typename propagator_t::template Options<>>
0399 inline void runForwardBackwardTest(
0400     const propagator_t& propagator, const Acts::GeometryContext& geoCtx,
0401     const Acts::MagneticFieldContext& magCtx,
0402     const Acts::BoundTrackParameters& initialParams, double pathLength,
0403     double epsPos, double epsTime, double epsDir, double epsMom) {
0404   // propagate parameters Acts::Direction::Forward()
0405   auto [fwdParams, fwdPathLength] = transportFreely<propagator_t, options_t>(
0406       propagator, geoCtx, magCtx, initialParams, pathLength);
0407   CHECK_CLOSE_ABS(fwdPathLength, pathLength, epsPos);
0408   // propagate propagated parameters back again
0409   auto [bwdParams, bwdPathLength] = transportFreely<propagator_t, options_t>(
0410       propagator, geoCtx, magCtx, fwdParams, -pathLength);
0411   CHECK_CLOSE_ABS(bwdPathLength, -pathLength, epsPos);
0412   // check that initial and back-propagated parameters match
0413   checkParametersConsistency(initialParams, bwdParams, geoCtx, epsPos, epsTime,
0414                              epsDir, epsMom);
0415 }
0416 
0417 /// Propagate the initial parameters once for the given path length and
0418 /// use the propagated parameters to define a target surface. Propagate the
0419 /// initial parameters again to the target surface. Verify that the surface has
0420 /// been found and the parameters are consistent.
0421 template <typename propagator_t, typename surface_builder_t,
0422           typename options_t = typename propagator_t::template Options<>>
0423 inline void runToSurfaceTest(const propagator_t& propagator,
0424                              const Acts::GeometryContext& geoCtx,
0425                              const Acts::MagneticFieldContext& magCtx,
0426                              const Acts::BoundTrackParameters& initialParams,
0427                              double pathLength,
0428                              surface_builder_t&& buildTargetSurface,
0429                              double epsPos, double epsTime, double epsDir,
0430                              double epsMom) {
0431   // free propagation for the given path length
0432   auto [freeParams, freePathLength] = transportFreely<propagator_t, options_t>(
0433       propagator, geoCtx, magCtx, initialParams, pathLength);
0434   CHECK_CLOSE_ABS(freePathLength, pathLength, epsPos);
0435   // build a target surface at the propagated position
0436   auto surface = buildTargetSurface(freeParams, geoCtx);
0437   BOOST_CHECK(surface);
0438 
0439   // bound propagation onto the target surface
0440   // increase path length limit to ensure the surface can be reached
0441   auto [surfParams, surfPathLength] =
0442       transportToSurface<propagator_t, options_t>(propagator, geoCtx, magCtx,
0443                                                   initialParams, *surface,
0444                                                   1.5 * pathLength);
0445   CHECK_CLOSE_ABS(surfPathLength, pathLength, epsPos);
0446 
0447   // check that the to-surface propagation matches the defining free parameters
0448   CHECK_CLOSE_ABS(surfParams.position(geoCtx), freeParams.position(geoCtx),
0449                   epsPos);
0450   CHECK_CLOSE_ABS(surfParams.time(), freeParams.time(), epsTime);
0451   CHECK_CLOSE_ABS(surfParams.direction(), freeParams.direction(), epsDir);
0452   CHECK_CLOSE_ABS(surfParams.absoluteMomentum(), freeParams.absoluteMomentum(),
0453                   epsMom);
0454   CHECK_CLOSE_ABS(surfPathLength, freePathLength, epsPos);
0455 }
0456 
0457 // consistency tests between two propagators
0458 
0459 /// Propagate the initial parameters along their trajectory for the given path
0460 /// length using two different propagators and verify consistent output.
0461 template <typename cmp_propagator_t, typename ref_propagator_t>
0462 inline void runForwardComparisonTest(
0463     const cmp_propagator_t& cmpPropagator,
0464     const ref_propagator_t& refPropagator, const Acts::GeometryContext& geoCtx,
0465     const Acts::MagneticFieldContext& magCtx,
0466     const Acts::BoundTrackParameters& initialParams, double pathLength,
0467     double epsPos, double epsTime, double epsDir, double epsMom,
0468     double tolCov) {
0469   // propagate twice using the two different propagators
0470   auto [cmpParams, cmpPath] = transportFreely<cmp_propagator_t>(
0471       cmpPropagator, geoCtx, magCtx, initialParams, pathLength);
0472   auto [refParams, refPath] = transportFreely<ref_propagator_t>(
0473       refPropagator, geoCtx, magCtx, initialParams, pathLength);
0474   // check parameter comparison
0475   checkParametersConsistency(cmpParams, refParams, geoCtx, epsPos, epsTime,
0476                              epsDir, epsMom);
0477   checkCovarianceConsistency(cmpParams, refParams, tolCov);
0478   CHECK_CLOSE_ABS(cmpPath, pathLength, epsPos);
0479   CHECK_CLOSE_ABS(refPath, pathLength, epsPos);
0480   CHECK_CLOSE_ABS(cmpPath, refPath, epsPos);
0481 }
0482 
0483 /// Propagate the initial parameters along their trajectory for the given path
0484 /// length using the reference propagator. Use the propagated track parameters
0485 /// to define a target plane. Propagate the initial parameters using two
0486 /// different propagators and verify consistent output.
0487 template <typename cmp_propagator_t, typename ref_propagator_t,
0488           typename surface_builder_t>
0489 inline void runToSurfaceComparisonTest(
0490     const cmp_propagator_t& cmpPropagator,
0491     const ref_propagator_t& refPropagator, const Acts::GeometryContext& geoCtx,
0492     const Acts::MagneticFieldContext& magCtx,
0493     const Acts::BoundTrackParameters& initialParams, double pathLength,
0494     surface_builder_t&& buildTargetSurface, double epsPos, double epsTime,
0495     double epsDir, double epsMom, double tolCov) {
0496   // free propagation with the reference propagator for the given path length
0497   auto [freeParams, freePathLength] = transportFreely<ref_propagator_t>(
0498       refPropagator, geoCtx, magCtx, initialParams, pathLength);
0499   CHECK_CLOSE_ABS(freePathLength, pathLength, epsPos);
0500 
0501   // build a target surface at the propagated position
0502   auto surface = buildTargetSurface(freeParams, geoCtx);
0503   BOOST_CHECK(surface);
0504 
0505   // propagate twice to the surface using the two different propagators
0506   // increase path length limit to ensure the surface can be reached
0507   auto [cmpParams, cmpPath] = transportToSurface<cmp_propagator_t>(
0508       cmpPropagator, geoCtx, magCtx, initialParams, *surface, 1.5 * pathLength);
0509   auto [refParams, refPath] = transportToSurface<ref_propagator_t>(
0510       refPropagator, geoCtx, magCtx, initialParams, *surface, 1.5 * pathLength);
0511   // check parameter comparison
0512   checkParametersConsistency(cmpParams, refParams, geoCtx, epsPos, epsTime,
0513                              epsDir, epsMom);
0514   checkCovarianceConsistency(cmpParams, refParams, tolCov);
0515   CHECK_CLOSE_ABS(cmpPath, pathLength, epsPos);
0516   CHECK_CLOSE_ABS(refPath, pathLength, epsPos);
0517   CHECK_CLOSE_ABS(cmpPath, refPath, epsPos);
0518 }
0519 
0520 template <typename propagator_t>
0521 inline void runDenseForwardTest(
0522     const propagator_t& propagator, const Acts::GeometryContext& geoCtx,
0523     const Acts::MagneticFieldContext& magCtx, double p, double q,
0524     const Acts::Surface& targetSurface, const Acts::Material& material,
0525     double thickness, const Acts::Logger& logger = Acts::getDummyLogger()) {
0526   using namespace Acts;
0527   using namespace UnitLiterals;
0528 
0529   const auto particleHypothesis = ParticleHypothesis::muon();
0530   const float mass = particleHypothesis.mass();
0531   const float absQ = std::abs(q);
0532   const double initialQOverP = particleHypothesis.qOverP(p, q);
0533   const auto initialParams = BoundTrackParameters::createCurvilinear(
0534       Vector4(-1.5_m, 0, 0, 0), Vector3::UnitX(), initialQOverP,
0535       BoundVector::Constant(1e-16).asDiagonal(), particleHypothesis);
0536 
0537   typename propagator_t::template Options<> options(geoCtx, magCtx);
0538   options.maxSteps = 10000;
0539   options.stepping.maxStepSize = 100_mm;
0540   options.stepping.dense.meanEnergyLoss = true;
0541   options.stepping.dense.momentumCutOff = 1_MeV;
0542 
0543   const auto result =
0544       propagator.propagate(initialParams, targetSurface, options);
0545 
0546   BOOST_CHECK(result.ok());
0547   CHECK_CLOSE_REL(3_m, result->pathLength, 1e-6);
0548   BOOST_CHECK(result->endParameters);
0549 
0550   BoundTrackParameters endParams = result->endParameters.value();
0551 
0552   BOOST_CHECK(endParams.covariance());
0553   CHECK_CLOSE_ABS(initialParams.position(geoCtx) + Acts::Vector3(3_m, 0, 0),
0554                   endParams.position(geoCtx), 1e-6);
0555   CHECK_CLOSE_ABS(initialParams.direction(), endParams.direction(), 1e-6);
0556 
0557   const auto& cov = endParams.covariance().value();
0558 
0559   const double endQOverP = endParams.qOverP();
0560   const double endP = endParams.absoluteMomentum();
0561   const double endEloss =
0562       Acts::fastHypot(p, mass) - Acts::fastHypot(endP, mass);
0563   const double endErrX = std::sqrt(cov(eBoundLoc0, eBoundLoc0));
0564   const double endErrY = std::sqrt(cov(eBoundLoc1, eBoundLoc1));
0565   const double endErrQOverP = std::sqrt(cov(eBoundQOverP, eBoundQOverP));
0566   const double endErrP =
0567       (absQ / std::pow(endParams.qOverP(), 2)) * endErrQOverP;
0568   const double endErrE = (p / Acts::fastHypot(p, mass)) * endErrP;
0569   const double endErrTheta = std::sqrt(cov(eBoundTheta, eBoundTheta));
0570   const double endErrPhi = std::sqrt(cov(eBoundPhi, eBoundPhi));
0571 
0572   ACTS_VERBOSE("input q/p = " << initialQOverP);
0573   ACTS_VERBOSE("input p = " << p);
0574   ACTS_VERBOSE("output q/p = " << endQOverP);
0575   ACTS_VERBOSE("output p = " << endP);
0576   ACTS_VERBOSE("output t = " << endParams.time());
0577   ACTS_VERBOSE("output eloss = " << endEloss);
0578   ACTS_VERBOSE("output err x = " << endErrX);
0579   ACTS_VERBOSE("output err y = " << endErrY);
0580   ACTS_VERBOSE("output err q/p = " << endErrQOverP);
0581   ACTS_VERBOSE("output err p = " << endErrP);
0582   ACTS_VERBOSE("output err E = " << endErrE);
0583   ACTS_VERBOSE("output err theta = " << endErrTheta);
0584   ACTS_VERBOSE("output err phi = " << endErrPhi);
0585 
0586   BOOST_CHECK_CLOSE(endErrX, endErrY, 1e-6);
0587   BOOST_CHECK_CLOSE(endErrTheta, endErrPhi, 1e-6);
0588 
0589   const float elossInitial = computeEnergyLossMean(
0590       MaterialSlab(material, thickness), particleHypothesis.absolutePdg(), mass,
0591       initialQOverP, absQ);
0592   const float elossFinal = computeEnergyLossMean(
0593       MaterialSlab(material, thickness), particleHypothesis.absolutePdg(), mass,
0594       endQOverP, absQ);
0595 
0596   ACTS_VERBOSE("ref eloss initial = " << elossInitial);
0597   ACTS_VERBOSE("ref eloss final = " << elossFinal);
0598 
0599   // energy loss will be smaller than elossInitial because of the increasing
0600   // radiation losses
0601   BOOST_CHECK_LT(endEloss, elossInitial);
0602   BOOST_CHECK_GT(endEloss, elossFinal);
0603 
0604   const float theta0Initial = computeMultipleScatteringTheta0(
0605       MaterialSlab(material, thickness), particleHypothesis.absolutePdg(), mass,
0606       initialQOverP, absQ);
0607   const float theta0Final = computeMultipleScatteringTheta0(
0608       MaterialSlab(material, thickness), particleHypothesis.absolutePdg(), mass,
0609       endQOverP, absQ);
0610 
0611   ACTS_VERBOSE("ref theta0 initial = " << theta0Initial);
0612   ACTS_VERBOSE("ref theta0 final = " << theta0Final);
0613 
0614   // angle errors will be larger than theta0Initial because of the energy loss
0615   BOOST_CHECK_GE(endErrTheta, theta0Initial);
0616   BOOST_CHECK_LT(endErrTheta, theta0Final);
0617   CHECK_CLOSE_REL(endErrTheta, 0.5 * (theta0Initial + theta0Final), 0.1);
0618 
0619   // estimates the positional uncertainty after crossing the material
0620   // block by integrating the angle variance over substeps
0621   const auto estimateVarX = [&](double qOverP, std::size_t substeps) {
0622     double previousTheta0 = 0;
0623     double varAngle = 0;
0624     double varPosition = 0;
0625     double covAnglePosition = 0;
0626 
0627     const auto step = [&](double substep, double theta0) {
0628       double deltaVarTheta = square(theta0) - square(previousTheta0);
0629       double deltaVarPos = varAngle * square(substep) +
0630                            2 * covAnglePosition * substep +
0631                            deltaVarTheta * (square(substep) / 3);
0632       double deltaCovAnglePosition =
0633           varAngle * substep + deltaVarTheta * substep / 2;
0634       previousTheta0 = theta0;
0635       varAngle += deltaVarTheta;
0636       varPosition += deltaVarPos;
0637       covAnglePosition += deltaCovAnglePosition;
0638     };
0639 
0640     // step through the material block
0641     for (std::size_t i = 0; i < substeps; ++i) {
0642       const float theta0 = computeMultipleScatteringTheta0(
0643           MaterialSlab(material, (i + 1) * thickness / substeps),
0644           particleHypothesis.absolutePdg(), mass, qOverP, absQ);
0645       step(thickness / substeps, theta0);
0646     }
0647 
0648     // step to the target surface
0649     step(1_m, previousTheta0);
0650 
0651     return varPosition;
0652   };
0653 
0654   // as a lower bound for sigma_x we use the initial theta0
0655   const double lowerBoundVarX = estimateVarX(initialQOverP, 10);
0656   // as an upper bound for sigma_x we use the final theta0
0657   const double upperBoundVarX = estimateVarX(endQOverP, 10);
0658 
0659   const double lowerBoundSigmaX = std::sqrt(lowerBoundVarX);
0660   const double upperBoundSigmaX = std::sqrt(upperBoundVarX);
0661 
0662   ACTS_VERBOSE("ref lower bound sigma x = " << lowerBoundSigmaX);
0663   ACTS_VERBOSE("ref upper bound sigma x = " << upperBoundSigmaX);
0664 
0665   BOOST_CHECK_GT(endErrX, lowerBoundSigmaX);
0666   BOOST_CHECK_LT(endErrX, upperBoundSigmaX);
0667   CHECK_CLOSE_REL(endErrX, 0.5 * (lowerBoundSigmaX + upperBoundSigmaX), 0.2);
0668 
0669   const float qOverPSigma = computeEnergyLossLandauSigmaQOverP(
0670       MaterialSlab(material, thickness), mass, initialQOverP, absQ);
0671 
0672   ACTS_VERBOSE("ref sigma q/p = " << qOverPSigma);
0673 
0674   CHECK_CLOSE_REL(endErrQOverP, qOverPSigma, 1e-4);
0675 }