File indexing completed on 2026-05-12 08:02:50
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/unit_test.hpp>
0011
0012 #include "Acts/Definitions/Algebra.hpp"
0013 #include "Acts/Definitions/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
0055 GeometryContext tgContext = GeometryContext::dangerouslyDefaultConstruct();
0056 MagneticFieldContext mfContext = MagneticFieldContext();
0057
0058 using Covariance = BoundMatrix;
0059
0060
0061 struct PerpendicularMeasure {
0062
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& , result_type& result) const {
0075 result.distance = perp(stepper.position(state.stepping));
0076 }
0077 };
0078
0079
0080 template <typename Surface>
0081 struct SurfaceObserver {
0082
0083 const Surface* surface = nullptr;
0084
0085 double tolerance = 1e-5;
0086
0087
0088
0089
0090
0091 double nearLimit = -100 * UnitConstants::um;
0092
0093
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& , result_type& result,
0105 const Logger& logger) const {
0106 if (surface == nullptr || result.surfaces_passed != 0) {
0107 return Result<void>::success();
0108 }
0109
0110
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
0127 state.stepping.stepSize.release(ConstrainedStep::Type::Actor);
0128 state.stepping.stepSize.update(intersectionIt->pathLength(),
0129 ConstrainedStep::Type::Actor);
0130
0131
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& , const stepper_t& ,
0145 const navigator_t& , result_type& result,
0146 const Logger& ) const {
0147 return result.surfaces_passed != 0;
0148 }
0149 };
0150
0151
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
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
0211 EigenPropagatorType::Options<ActorList> options(tgContext, mfContext);
0212
0213 options.pathLimit = 20_m;
0214 options.stepping.maxStepSize = 1_cm;
0215
0216
0217 options.actorList.get<CylinderObserver>().surface = mSurface.get();
0218
0219 using so_result = typename CylinderObserver::result_type;
0220
0221
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
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
0268 EigenPropagatorType::Options<> options_2s(tgContext, mfContext);
0269 options_2s.pathLimit = 50_cm;
0270 options_2s.stepping.maxStepSize = 1_cm;
0271
0272
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
0283 Covariance cov;
0284
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
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
0298 EigenPropagatorType::Options<> options_1s(tgContext, mfContext);
0299 options_1s.pathLimit = 100_cm;
0300 options_1s.stepping.maxStepSize = 1_cm;
0301
0302 const auto& end_parameters_1s =
0303 epropagator.propagate(start, options_1s).value().endParameters;
0304
0305
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
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
0348 EigenPropagatorType::Options<> options_2s(tgContext, mfContext);
0349 options_2s.pathLimit = 10_m;
0350 options_2s.stepping.maxStepSize = 1_cm;
0351
0352
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
0363 Covariance cov;
0364
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
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
0381 EigenPropagatorType::Options<> options_1s(tgContext, mfContext);
0382 options_1s.pathLimit = 10_m;
0383 options_1s.stepping.maxStepSize = 1_cm;
0384
0385 const auto& end_parameters_1s =
0386 epropagator.propagate(start, *cSurface, options_1s).value().endParameters;
0387
0388
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
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
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
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
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
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 }