Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-12 08:02:50

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/TrackParametrization.hpp"
0014 #include "Acts/Definitions/Units.hpp"
0015 #include "Acts/Geometry/GeometryContext.hpp"
0016 #include "Acts/Geometry/GeometryIdentifier.hpp"
0017 #include "Acts/MagneticField/ConstantBField.hpp"
0018 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0019 #include "Acts/Propagator/ActorList.hpp"
0020 #include "Acts/Propagator/ConstrainedStep.hpp"
0021 #include "Acts/Propagator/EigenStepper.hpp"
0022 #include "Acts/Propagator/EigenStepperDenseExtension.hpp"
0023 #include "Acts/Propagator/Propagator.hpp"
0024 #include "Acts/Propagator/StraightLineStepper.hpp"
0025 #include "Acts/Propagator/VoidNavigator.hpp"
0026 #include "Acts/Surfaces/CurvilinearSurface.hpp"
0027 #include "Acts/Surfaces/CylinderBounds.hpp"
0028 #include "Acts/Surfaces/CylinderSurface.hpp"
0029 #include "Acts/Surfaces/PlaneSurface.hpp"
0030 #include "Acts/Surfaces/Surface.hpp"
0031 #include "Acts/Utilities/Logger.hpp"
0032 #include "Acts/Utilities/Result.hpp"
0033 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0034 
0035 #include <cmath>
0036 #include <cstddef>
0037 #include <limits>
0038 #include <memory>
0039 #include <numbers>
0040 #include <optional>
0041 #include <random>
0042 #include <type_traits>
0043 #include <utility>
0044 
0045 namespace bdata = boost::unit_test::data;
0046 
0047 using namespace Acts;
0048 using namespace Acts::UnitLiterals;
0049 using Acts::VectorHelpers::makeVector4;
0050 using Acts::VectorHelpers::perp;
0051 
0052 namespace ActsTests {
0053 
0054 // Create a test context
0055 GeometryContext tgContext = GeometryContext::dangerouslyDefaultConstruct();
0056 MagneticFieldContext mfContext = MagneticFieldContext();
0057 
0058 using Covariance = BoundMatrix;
0059 
0060 /// An observer that measures the perpendicular distance
0061 struct PerpendicularMeasure {
0062   /// Simple result struct to be returned
0063   struct this_result {
0064     double distance = std::numeric_limits<double>::max();
0065   };
0066 
0067   using result_type = this_result;
0068 
0069   PerpendicularMeasure() = default;
0070 
0071   template <typename propagator_state_t, typename stepper_t,
0072             typename navigator_t>
0073   void operator()(propagator_state_t& state, const stepper_t& stepper,
0074                   const navigator_t& /*navigator*/, result_type& result) const {
0075     result.distance = perp(stepper.position(state.stepping));
0076   }
0077 };
0078 
0079 /// An observer that measures the perpendicular distance
0080 template <typename Surface>
0081 struct SurfaceObserver {
0082   // the surface to be intersected
0083   const Surface* surface = nullptr;
0084   // the tolerance for intersection
0085   double tolerance = 1e-5;
0086 
0087   // TODO https://github.com/acts-project/acts/issues/2738
0088   /// Distance limit to discard intersections "behind us"
0089   /// @note this is only necessary because some surfaces have more than one
0090   ///       intersection
0091   double nearLimit = -100 * UnitConstants::um;
0092 
0093   /// Simple result struct to be returned
0094   struct this_result {
0095     std::size_t surfaces_passed = 0;
0096     double surface_passed_r = std::numeric_limits<double>::max();
0097   };
0098 
0099   using result_type = this_result;
0100 
0101   template <typename propagator_state_t, typename stepper_t,
0102             typename navigator_t>
0103   Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0104                    const navigator_t& /*navigator*/, result_type& result,
0105                    const Logger& logger) const {
0106     if (surface == nullptr || result.surfaces_passed != 0) {
0107       return Result<void>::success();
0108     }
0109 
0110     // calculate the intersections with the surface
0111     const auto multiIntersection = surface->intersect(
0112         state.geoContext, stepper.position(state.stepping),
0113         state.options.direction * stepper.direction(state.stepping),
0114         BoundaryTolerance::None());
0115 
0116     const double farLimit = std::numeric_limits<double>::max();
0117 
0118     if (const auto intersectionIt = std::ranges::find_if(
0119             multiIntersection,
0120             [&](const auto& intersection) {
0121               return intersection.isValid() &&
0122                      detail::checkPathLength(intersection.pathLength(),
0123                                              nearLimit, farLimit, logger);
0124             });
0125         intersectionIt != multiIntersection.end()) {
0126       // Adjust the step size so that we cannot cross the target surface
0127       state.stepping.stepSize.release(ConstrainedStep::Type::Actor);
0128       state.stepping.stepSize.update(intersectionIt->pathLength(),
0129                                      ConstrainedStep::Type::Actor);
0130 
0131       // return true if we fall below tolerance
0132       if (std::abs(intersectionIt->pathLength()) <= tolerance) {
0133         ++result.surfaces_passed;
0134         result.surface_passed_r = perp(stepper.position(state.stepping));
0135         state.stepping.stepSize.release(ConstrainedStep::Type::Actor);
0136       }
0137     }
0138 
0139     return Result<void>::success();
0140   }
0141 
0142   template <typename propagator_state_t, typename stepper_t,
0143             typename navigator_t>
0144   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0145                   const navigator_t& /*navigator*/, result_type& result,
0146                   const Logger& /*logger*/) const {
0147     return result.surfaces_passed != 0;
0148   }
0149 };
0150 
0151 // Global definitions
0152 using BFieldType = ConstantBField;
0153 using EigenStepperType = EigenStepper<>;
0154 using EigenPropagatorType = Propagator<EigenStepperType>;
0155 
0156 const double Bz = 2_T;
0157 auto bField = std::make_shared<BFieldType>(Vector3{0, 0, Bz});
0158 EigenStepperType estepper(bField);
0159 EigenPropagatorType epropagator(std::move(estepper), VoidNavigator());
0160 
0161 auto mCylinder = std::make_shared<CylinderBounds>(10_mm, 1000_mm);
0162 auto mSurface =
0163     Surface::makeShared<CylinderSurface>(Transform3::Identity(), mCylinder);
0164 auto cCylinder = std::make_shared<CylinderBounds>(150_mm, 1000_mm);
0165 auto cSurface =
0166     Surface::makeShared<CylinderSurface>(Transform3::Identity(), cCylinder);
0167 
0168 const int ntests = 5;
0169 
0170 BOOST_AUTO_TEST_SUITE(PropagatorSuite)
0171 
0172 // This tests the Options
0173 BOOST_AUTO_TEST_CASE(PropagatorOptions_) {
0174   using NullOptionsType = EigenPropagatorType::Options<>;
0175   NullOptionsType null_options(tgContext, mfContext);
0176 
0177   using ActorList = ActorList<PerpendicularMeasure>;
0178   using OptionsType = EigenPropagatorType::Options<ActorList>;
0179   OptionsType options(tgContext, mfContext);
0180 }
0181 
0182 BOOST_DATA_TEST_CASE(
0183     cylinder_passage_observer_,
0184     bdata::random((bdata::engine = std::mt19937(), bdata::seed = 0,
0185                    bdata::distribution = std::uniform_real_distribution<double>(
0186                        0.4_GeV, 10_GeV))) ^
0187         bdata::random(
0188             (bdata::engine = std::mt19937(), bdata::seed = 1,
0189              bdata::distribution = std::uniform_real_distribution<double>(
0190                  -std::numbers::pi, std::numbers::pi))) ^
0191         bdata::random(
0192             (bdata::engine = std::mt19937(), bdata::seed = 2,
0193              bdata::distribution = std::uniform_real_distribution<double>(
0194                  1., std::numbers::pi - 1.))) ^
0195         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 3,
0196                        bdata::distribution =
0197                            std::uniform_int_distribution<std::uint8_t>(0, 1))) ^
0198         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 4,
0199                        bdata::distribution =
0200                            std::uniform_real_distribution<double>(-1_ns,
0201                                                                   1_ns))) ^
0202         bdata::xrange(ntests),
0203     pT, phi, theta, charge, time, index) {
0204   double dcharge = -1 + 2 * charge;
0205   static_cast<void>(index);
0206 
0207   using CylinderObserver = SurfaceObserver<CylinderSurface>;
0208   using ActorList = ActorList<CylinderObserver>;
0209 
0210   // setup propagation options
0211   EigenPropagatorType::Options<ActorList> options(tgContext, mfContext);
0212 
0213   options.pathLimit = 20_m;
0214   options.stepping.maxStepSize = 1_cm;
0215 
0216   // set the surface to be passed by
0217   options.actorList.get<CylinderObserver>().surface = mSurface.get();
0218 
0219   using so_result = typename CylinderObserver::result_type;
0220 
0221   // define start parameters
0222   double x = 0;
0223   double y = 0;
0224   double z = 0;
0225   double px = pT * std::cos(phi);
0226   double py = pT * std::sin(phi);
0227   double pz = pT / std::tan(theta);
0228   double q = dcharge;
0229   Vector3 pos(x, y, z);
0230   Vector3 mom(px, py, pz);
0231   BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0232       makeVector4(pos, time), mom.normalized(), q / mom.norm(), std::nullopt,
0233       ParticleHypothesis::pion());
0234   // propagate to the cylinder surface
0235   const auto& result = epropagator.propagate(start, options).value();
0236   auto& sor = result.get<so_result>();
0237 
0238   BOOST_CHECK_EQUAL(sor.surfaces_passed, 1u);
0239   CHECK_CLOSE_ABS(sor.surface_passed_r, 10., 1e-5);
0240 }
0241 
0242 BOOST_DATA_TEST_CASE(
0243     curvilinear_additive_,
0244     bdata::random((bdata::engine = std::mt19937(), bdata::seed = 0,
0245                    bdata::distribution = std::uniform_real_distribution<double>(
0246                        0.4_GeV, 10_GeV))) ^
0247         bdata::random(
0248             (bdata::engine = std::mt19937(), bdata::seed = 1,
0249              bdata::distribution = std::uniform_real_distribution<double>(
0250                  -std::numbers::pi, std::numbers::pi))) ^
0251         bdata::random(
0252             (bdata::engine = std::mt19937(), bdata::seed = 2,
0253              bdata::distribution = std::uniform_real_distribution<double>(
0254                  1., std::numbers::pi - 1.))) ^
0255         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 3,
0256                        bdata::distribution =
0257                            std::uniform_int_distribution<std::uint8_t>(0, 1))) ^
0258         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 4,
0259                        bdata::distribution =
0260                            std::uniform_real_distribution<double>(-1_ns,
0261                                                                   1_ns))) ^
0262         bdata::xrange(ntests),
0263     pT, phi, theta, charge, time, index) {
0264   double dcharge = -1 + 2 * charge;
0265   static_cast<void>(index);
0266 
0267   // setup propagation options - the tow step options
0268   EigenPropagatorType::Options<> options_2s(tgContext, mfContext);
0269   options_2s.pathLimit = 50_cm;
0270   options_2s.stepping.maxStepSize = 1_cm;
0271 
0272   // define start parameters
0273   double x = 0;
0274   double y = 0;
0275   double z = 0;
0276   double px = pT * std::cos(phi);
0277   double py = pT * std::sin(phi);
0278   double pz = pT / std::tan(theta);
0279   double q = dcharge;
0280   Vector3 pos(x, y, z);
0281   Vector3 mom(px, py, pz);
0282   /// a covariance matrix to transport
0283   Covariance cov;
0284   // take some major correlations (off-diagonals)
0285   cov << 10_mm, 0, 0.123, 0, 0.5, 0, 0, 10_mm, 0, 0.162, 0, 0, 0.123, 0, 0.1, 0,
0286       0, 0, 0, 0.162, 0, 0.1, 0, 0, 0.5, 0, 0, 0, 1. / (10_GeV), 0, 0, 0, 0, 0,
0287       0, 0;
0288   BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0289       makeVector4(pos, time), mom.normalized(), q / mom.norm(), cov,
0290       ParticleHypothesis::pion());
0291   // propagate to a path length of 100 with two steps of 50
0292   const auto& mid_parameters =
0293       epropagator.propagate(start, options_2s).value().endParameters;
0294   const auto& end_parameters_2s =
0295       epropagator.propagate(*mid_parameters, options_2s).value().endParameters;
0296 
0297   // setup propagation options - the one step options
0298   EigenPropagatorType::Options<> options_1s(tgContext, mfContext);
0299   options_1s.pathLimit = 100_cm;
0300   options_1s.stepping.maxStepSize = 1_cm;
0301   // propagate to a path length of 100 in one step
0302   const auto& end_parameters_1s =
0303       epropagator.propagate(start, options_1s).value().endParameters;
0304 
0305   // test that the propagation is additive
0306   CHECK_CLOSE_REL(end_parameters_1s->position(tgContext),
0307                   end_parameters_2s->position(tgContext), 0.001);
0308 
0309   BOOST_CHECK(end_parameters_1s->covariance().has_value());
0310   const auto& cov_1s = *(end_parameters_1s->covariance());
0311   BOOST_CHECK(end_parameters_2s->covariance().has_value());
0312   const auto& cov_2s = *(end_parameters_2s->covariance());
0313 
0314   // CHECK_CLOSE_COVARIANCE(cov_1s, cov_2s, 0.001);
0315   for (unsigned int i = 0; i < cov_1s.rows(); i++) {
0316     for (unsigned int j = 0; j < cov_1s.cols(); j++) {
0317       CHECK_CLOSE_OR_SMALL(cov_1s(i, j), cov_2s(i, j), 0.001, 1e-6);
0318     }
0319   }
0320 }
0321 
0322 BOOST_DATA_TEST_CASE(
0323     cylinder_additive_,
0324     bdata::random((bdata::engine = std::mt19937(), bdata::seed = 0,
0325                    bdata::distribution = std::uniform_real_distribution<double>(
0326                        0.4_GeV, 10_GeV))) ^
0327         bdata::random(
0328             (bdata::engine = std::mt19937(), bdata::seed = 1,
0329              bdata::distribution = std::uniform_real_distribution<double>(
0330                  -std::numbers::pi, std::numbers::pi))) ^
0331         bdata::random(
0332             (bdata::engine = std::mt19937(), bdata::seed = 2,
0333              bdata::distribution = std::uniform_real_distribution<double>(
0334                  1., std::numbers::pi - 1.))) ^
0335         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 3,
0336                        bdata::distribution =
0337                            std::uniform_int_distribution<std::uint8_t>(0, 1))) ^
0338         bdata::random((bdata::engine = std::mt19937(), bdata::seed = 4,
0339                        bdata::distribution =
0340                            std::uniform_real_distribution<double>(-1_ns,
0341                                                                   1_ns))) ^
0342         bdata::xrange(ntests),
0343     pT, phi, theta, charge, time, index) {
0344   double dcharge = -1 + 2 * charge;
0345   static_cast<void>(index);
0346 
0347   // setup propagation options - 2 setp options
0348   EigenPropagatorType::Options<> options_2s(tgContext, mfContext);
0349   options_2s.pathLimit = 10_m;
0350   options_2s.stepping.maxStepSize = 1_cm;
0351 
0352   // define start parameters
0353   double x = 0;
0354   double y = 0;
0355   double z = 0;
0356   double px = pT * std::cos(phi);
0357   double py = pT * std::sin(phi);
0358   double pz = pT / std::tan(theta);
0359   double q = dcharge;
0360   Vector3 pos(x, y, z);
0361   Vector3 mom(px, py, pz);
0362   /// a covariance matrix to transport
0363   Covariance cov;
0364   // take some major correlations (off-diagonals)
0365   cov << 10_mm, 0, 0.123, 0, 0.5, 0, 0, 10_mm, 0, 0.162, 0, 0, 0.123, 0, 0.1, 0,
0366       0, 0, 0, 0.162, 0, 0.1, 0, 0, 0.5, 0, 0, 0, 1. / (10_GeV), 0, 0, 0, 0, 0,
0367       0, 0;
0368   BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0369       makeVector4(pos, time), mom.normalized(), q / mom.norm(), cov,
0370       ParticleHypothesis::pion());
0371   // propagate to a final surface with one stop in between
0372   const auto& mid_parameters =
0373       epropagator.propagate(start, *mSurface, options_2s).value().endParameters;
0374 
0375   const auto& end_parameters_2s =
0376       epropagator.propagate(*mid_parameters, *cSurface, options_2s)
0377           .value()
0378           .endParameters;
0379 
0380   // setup propagation options - one step options
0381   EigenPropagatorType::Options<> options_1s(tgContext, mfContext);
0382   options_1s.pathLimit = 10_m;
0383   options_1s.stepping.maxStepSize = 1_cm;
0384   // propagate to a final surface in one stop
0385   const auto& end_parameters_1s =
0386       epropagator.propagate(start, *cSurface, options_1s).value().endParameters;
0387 
0388   // test that the propagation is additive
0389   CHECK_CLOSE_REL(end_parameters_1s->position(tgContext),
0390                   end_parameters_2s->position(tgContext), 0.001);
0391 
0392   BOOST_CHECK(end_parameters_1s->covariance().has_value());
0393   const auto& cov_1s = (*(end_parameters_1s->covariance()));
0394   BOOST_CHECK(end_parameters_2s->covariance().has_value());
0395   const auto& cov_2s = (*(end_parameters_2s->covariance()));
0396 
0397   // CHECK_CLOSE_COVARIANCE(cov_1s, cov_2s, 0.001);
0398   for (unsigned int i = 0; i < cov_1s.rows(); i++) {
0399     for (unsigned int j = 0; j < cov_1s.cols(); j++) {
0400       CHECK_CLOSE_OR_SMALL(cov_1s(i, j), cov_2s(i, j), 0.001, 1e-6);
0401     }
0402   }
0403 }
0404 
0405 BOOST_AUTO_TEST_CASE(BasicPropagatorInterface) {
0406   auto field = std::make_shared<ConstantBField>(Vector3{0, 0, 2_T});
0407   EigenStepper<> eigenStepper{field};
0408   VoidNavigator navigator{};
0409 
0410   std::shared_ptr<PlaneSurface> startSurface =
0411       CurvilinearSurface(Vector3::Zero(), Vector3::UnitX()).planeSurface();
0412   std::shared_ptr<PlaneSurface> targetSurface =
0413       CurvilinearSurface(Vector3::UnitX() * 20_mm, Vector3::UnitX())
0414           .planeSurface();
0415 
0416   BoundVector startPars;
0417   startPars << 0, 0, 0, std::numbers::pi / 2., 1 / 1_GeV, 0;
0418 
0419   BoundTrackParameters startParameters{startSurface, startPars, std::nullopt,
0420                                        ParticleHypothesis::pion()};
0421 
0422   BoundTrackParameters startCurv = BoundTrackParameters::createCurvilinear(
0423       Vector4::Zero(), Vector3::UnitX(), 1. / 1_GeV, std::nullopt,
0424       ParticleHypothesis::pion());
0425 
0426   auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0427   MagneticFieldContext mctx;
0428 
0429   {
0430     EigenPropagatorType::Options<> options{gctx, mctx};
0431 
0432     Propagator propagator{eigenStepper, navigator};
0433     static_assert(std::is_base_of_v<BasePropagator, decltype(propagator)>,
0434                   "Propagator does not inherit from BasePropagator");
0435     const BasePropagator* base =
0436         static_cast<const BasePropagator*>(&propagator);
0437 
0438     // Ensure the propagation does the same thing
0439     auto result =
0440         propagator.propagate(startParameters, *targetSurface, options);
0441     BOOST_REQUIRE(result.ok());
0442     BOOST_CHECK_EQUAL(&result.value().endParameters.value().referenceSurface(),
0443                       targetSurface.get());
0444 
0445     auto resultBase =
0446         base->propagateToSurface(startParameters, *targetSurface,
0447                                  static_cast<PropagatorPlainOptions>(options));
0448 
0449     BOOST_REQUIRE(resultBase.ok());
0450     BOOST_CHECK_EQUAL(&resultBase.value().referenceSurface(),
0451                       targetSurface.get());
0452 
0453     BOOST_CHECK_EQUAL(result.value().endParameters.value().parameters(),
0454                       resultBase.value().parameters());
0455 
0456     // Propagation call with curvilinear also works
0457     auto resultCurv =
0458         base->propagateToSurface(startCurv, *targetSurface,
0459                                  static_cast<PropagatorPlainOptions>(options));
0460     BOOST_CHECK(resultCurv.ok());
0461   }
0462 
0463   StraightLineStepper slStepper{};
0464   {
0465     Propagator<StraightLineStepper>::Options<> options{gctx, mctx};
0466 
0467     Propagator propagator{slStepper, navigator};
0468     static_assert(std::is_base_of_v<BasePropagator, decltype(propagator)>,
0469                   "Propagator does not inherit from BasePropagator");
0470     const BasePropagator* base =
0471         static_cast<const BasePropagator*>(&propagator);
0472 
0473     // Ensure the propagation does the same thing
0474     auto result =
0475         propagator.propagate(startParameters, *targetSurface, options);
0476     BOOST_REQUIRE(result.ok());
0477     BOOST_CHECK_EQUAL(&result.value().endParameters.value().referenceSurface(),
0478                       targetSurface.get());
0479 
0480     auto resultBase =
0481         base->propagateToSurface(startParameters, *targetSurface,
0482                                  static_cast<PropagatorPlainOptions>(options));
0483 
0484     BOOST_REQUIRE(resultBase.ok());
0485     BOOST_CHECK_EQUAL(&resultBase.value().referenceSurface(),
0486                       targetSurface.get());
0487 
0488     BOOST_CHECK_EQUAL(result.value().endParameters.value().parameters(),
0489                       resultBase.value().parameters());
0490 
0491     // Propagation call with curvilinear also works
0492     auto resultCurv =
0493         base->propagateToSurface(startCurv, *targetSurface,
0494                                  static_cast<PropagatorPlainOptions>(options));
0495     BOOST_CHECK(resultCurv.ok());
0496   }
0497 
0498   EigenStepper<EigenStepperDenseExtension> denseEigenStepper{field};
0499 
0500   {
0501     Propagator propagator{denseEigenStepper, navigator};
0502     static_assert(!std::is_base_of_v<BasePropagator, decltype(propagator)>,
0503                   "Propagator unexpectedly inherits from BasePropagator");
0504   }
0505 }
0506 
0507 BOOST_AUTO_TEST_SUITE_END()
0508 
0509 }  // namespace ActsTests