Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /acts/Tests/UnitTests/Core/Navigation/NavigationPolicyTests.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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/Tolerance.hpp"
0014 #include "Acts/Definitions/Units.hpp"
0015 #include "Acts/Geometry/CylinderPortalShell.hpp"
0016 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Geometry/NavigationPolicyFactory.hpp"
0019 #include "Acts/Geometry/TrackingVolume.hpp"
0020 #include "Acts/Navigation/CylinderNavigationPolicy.hpp"
0021 #include "Acts/Navigation/INavigationPolicy.hpp"
0022 #include "Acts/Navigation/MultiNavigationPolicy.hpp"
0023 #include "Acts/Navigation/NavigationDelegate.hpp"
0024 #include "Acts/Navigation/NavigationStream.hpp"
0025 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0026 #include "Acts/Utilities/Logger.hpp"
0027 
0028 #include <boost/algorithm/string/join.hpp>
0029 
0030 using namespace Acts;
0031 using namespace Acts::UnitLiterals;
0032 namespace bdata = boost::unit_test::data;
0033 
0034 namespace ActsTests {
0035 
0036 BOOST_AUTO_TEST_SUITE(NavigationSuite)
0037 
0038 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0039 auto logger = getDefaultLogger("NavigationPolicyTests", Logging::VERBOSE);
0040 
0041 struct APolicy : public INavigationPolicy {
0042   APolicy(const GeometryContext& /*gctx*/, const TrackingVolume& /*volume*/,
0043           const Logger& /*logger*/) {}
0044 
0045   void initializeCandidates(const GeometryContext& /*unused*/,
0046                             const NavigationArguments& /*unused*/,
0047                             NavigationPolicyState& /*unused*/,
0048                             AppendOnlyNavigationStream& /*unused*/,
0049                             const Logger& /*unused*/) const {
0050     const_cast<APolicy*>(this)->executed = true;
0051   }
0052 
0053   void connect(NavigationDelegate& delegate) const override {
0054     connectDefault<APolicy>(delegate);
0055   }
0056 
0057   bool executed = false;
0058 };
0059 
0060 struct BPolicy : public INavigationPolicy {
0061   struct Config {
0062     int value;
0063   };
0064 
0065   BPolicy(const GeometryContext& /*gctx*/, const TrackingVolume& /*volume*/,
0066           const Logger& /*logger*/, Config config)
0067       : m_config(config) {}
0068 
0069   void connect(NavigationDelegate& delegate) const override {
0070     connectDefault<BPolicy>(delegate);
0071   }
0072 
0073   void initializeCandidates(const GeometryContext& /*unused*/,
0074                             const NavigationArguments& /*unused*/,
0075                             NavigationPolicyState& /*unused*/,
0076                             AppendOnlyNavigationStream& /*unused*/,
0077                             const Logger& /*unused*/) const {
0078     const_cast<BPolicy*>(this)->executed = true;
0079     const_cast<BPolicy*>(this)->value = m_config.value;
0080   }
0081 
0082   bool executed = false;
0083   int value = 0;
0084 
0085   Config m_config;
0086 };
0087 
0088 BOOST_AUTO_TEST_CASE(DirectTest) {
0089   TrackingVolume volume{
0090       Transform3::Identity(),
0091       std::make_shared<CylinderVolumeBounds>(250_mm, 400_mm, 310_mm),
0092       "PixelLayer3"};
0093 
0094   MultiNavigationPolicy policy{
0095       std::make_unique<APolicy>(gctx, volume, *logger),
0096       std::make_unique<BPolicy>(gctx, volume, *logger,
0097                                 BPolicy::Config{.value = 4242})};
0098 
0099   NavigationDelegate delegate;
0100   policy.connect(delegate);
0101 
0102   NavigationStream main;
0103   AppendOnlyNavigationStream stream{main};
0104   NavigationArguments args{.position = Vector3::Zero(),
0105                            .direction = Vector3::Zero()};
0106   NavigationPolicyStateManager stateManager;
0107   stateManager.pushState<MultiNavigationPolicy::State>();
0108   policy.createState(gctx, args, stateManager, *logger);
0109   auto policyState = stateManager.currentState();
0110   delegate(gctx, args, policyState, stream, *logger);
0111 
0112   BOOST_REQUIRE_EQUAL(policy.policies().size(), 2);
0113   const auto& policyA = dynamic_cast<const APolicy&>(*policy.policies()[0]);
0114   const auto& policyB = dynamic_cast<const BPolicy&>(*policy.policies()[1]);
0115 
0116   BOOST_CHECK(policyA.executed);
0117   BOOST_CHECK(policyB.executed);
0118   BOOST_CHECK_EQUAL(policyB.value, 4242);
0119 }
0120 
0121 BOOST_AUTO_TEST_CASE(FactoryTest) {
0122   TrackingVolume volume{
0123       Transform3::Identity(),
0124       std::make_shared<CylinderVolumeBounds>(250_mm, 400_mm, 310_mm),
0125       "PixelLayer3"};
0126 
0127   BPolicy::Config config{.value = 42};
0128 
0129   std::function<std::unique_ptr<INavigationPolicy>(
0130       const GeometryContext&, const TrackingVolume&, const Logger&)>
0131       factory = NavigationPolicyFactory{}
0132                     .add<APolicy>()         // no arguments
0133                     .add<BPolicy>(config);  // config struct as argument
0134 
0135   auto policyBase = factory(gctx, volume, *logger);
0136   auto policyBase2 = factory(gctx, volume, *logger);
0137 
0138   auto& policy = dynamic_cast<MultiNavigationPolicy&>(*policyBase);
0139 
0140   NavigationDelegate delegate;
0141   policy.connect(delegate);
0142 
0143   NavigationStream main;
0144   AppendOnlyNavigationStream stream{main};
0145   NavigationArguments args{.position = Vector3::Zero(),
0146                            .direction = Vector3::Zero()};
0147   NavigationPolicyStateManager stateManager;
0148   stateManager.pushState<MultiNavigationPolicy::State>();
0149   policy.createState(gctx, args, stateManager, *logger);
0150   auto policyState = stateManager.currentState();
0151   delegate(gctx, args, policyState, stream, *logger);
0152 
0153   BOOST_REQUIRE_EQUAL(policy.policies().size(), 2);
0154   const auto& policyA = dynamic_cast<const APolicy&>(*policy.policies()[0]);
0155   const auto& policyB = dynamic_cast<const BPolicy&>(*policy.policies()[1]);
0156 
0157   BOOST_CHECK(policyA.executed);
0158   BOOST_CHECK(policyB.executed);
0159   BOOST_CHECK_EQUAL(policyB.value, 42);
0160 
0161   auto& policy2 = dynamic_cast<MultiNavigationPolicy&>(*policyBase2);
0162 
0163   NavigationDelegate delegate2;
0164   policyBase2->connect(delegate2);
0165 
0166   NavigationPolicyStateManager stateManager2;
0167   stateManager2.pushState<MultiNavigationPolicy::State>();
0168   policy2.createState(gctx, args, stateManager2, *logger);
0169   auto policyState2 = stateManager2.currentState();
0170   delegate2(gctx, args, policyState2, stream, *logger);
0171 
0172   BOOST_REQUIRE_EQUAL(policy2.policies().size(), 2);
0173   const auto& policy2A = dynamic_cast<const APolicy&>(*policy2.policies()[0]);
0174   const auto& policy2B = dynamic_cast<const BPolicy&>(*policy2.policies()[1]);
0175 
0176   BOOST_CHECK(policy2A.executed);
0177   BOOST_CHECK(policy2B.executed);
0178   BOOST_CHECK_EQUAL(policy2B.value, 42);
0179 }
0180 
0181 BOOST_AUTO_TEST_CASE(AsUniquePtrTest) {
0182   TrackingVolume volume{
0183       Transform3::Identity(),
0184       std::make_shared<CylinderVolumeBounds>(250_mm, 400_mm, 310_mm),
0185       "PixelLayer3"};
0186 
0187   std::unique_ptr<NavigationPolicyFactory> factory =
0188       NavigationPolicyFactory{}.add<APolicy>().asUniquePtr();
0189 
0190   auto policyBase = factory->build(gctx, volume, *logger);
0191   auto& policy = dynamic_cast<MultiNavigationPolicy&>(*policyBase);
0192 
0193   NavigationDelegate delegate;
0194   policyBase->connect(delegate);
0195 
0196   NavigationStream main;
0197   AppendOnlyNavigationStream stream{main};
0198   NavigationArguments args{.position = Vector3::Zero(),
0199                            .direction = Vector3::Zero()};
0200   NavigationPolicyStateManager stateManager;
0201   stateManager.pushState<MultiNavigationPolicy::State>();
0202   policy.createState(gctx, args, stateManager, *logger);
0203   auto policyState = stateManager.currentState();
0204   delegate(gctx, args, policyState, stream, *logger);
0205 
0206   BOOST_REQUIRE_EQUAL(policy.policies().size(), 1);
0207   BOOST_CHECK(dynamic_cast<const APolicy&>(*policy.policies()[0]).executed);
0208 }
0209 
0210 struct CPolicy : public INavigationPolicy {};
0211 
0212 template <typename T>
0213 struct CPolicySpecialized : public CPolicy {
0214   struct Config {
0215     T value;
0216   };
0217 
0218   CPolicySpecialized(const TrackingVolume& /*volume*/, Config config)
0219       : m_config(config) {}
0220 
0221   void connect(NavigationDelegate& delegate) const override {
0222     connectDefault<CPolicySpecialized<T>>(delegate);
0223   }
0224 
0225   void initializeCandidates(const GeometryContext& /*unused*/,
0226                             const NavigationArguments& /*unused*/,
0227                             NavigationPolicyState& /*unused*/,
0228                             AppendOnlyNavigationStream& /*stream*/,
0229                             const Logger& /*logger*/) const {
0230     auto* self = const_cast<CPolicySpecialized<int>*>(this);
0231     self->executed = true;
0232     self->value = m_config.value;
0233   }
0234 
0235   bool executed = false;
0236   int value = 0;
0237 
0238   Config m_config;
0239 };
0240 
0241 struct IsolatedConfig {
0242   int value;
0243 };
0244 
0245 auto makeCPolicy(const GeometryContext& /*gctx*/, const TrackingVolume& volume,
0246                  const Logger& /*logger*/, IsolatedConfig config) {
0247   // I can do arbitrary stuff here
0248   CPolicySpecialized<int>::Config config2{.value = config.value};
0249   return CPolicySpecialized<int>(volume, config2);
0250 }
0251 
0252 BOOST_AUTO_TEST_CASE(IsolatedFactory) {
0253   TrackingVolume volume{
0254       Transform3::Identity(),
0255       std::make_shared<CylinderVolumeBounds>(250_mm, 400_mm, 310_mm),
0256       "PixelLayer3"};
0257 
0258   IsolatedConfig config{.value = 44};
0259   auto factory =
0260       NavigationPolicyFactory{}.add<APolicy>().add(makeCPolicy, config);
0261 
0262   auto factory2 =
0263       NavigationPolicyFactory{}.add(makeCPolicy, config).add<APolicy>();
0264 
0265   auto policyBase = factory(gctx, volume, *logger);
0266   auto& policy = dynamic_cast<MultiNavigationPolicy&>(*policyBase);
0267 
0268   NavigationDelegate delegate;
0269   policyBase->connect(delegate);
0270 
0271   NavigationStream main;
0272   AppendOnlyNavigationStream stream{main};
0273   NavigationArguments args{.position = Vector3::Zero(),
0274                            .direction = Vector3::Zero()};
0275   NavigationPolicyStateManager stateManager;
0276   stateManager.pushState<MultiNavigationPolicy::State>();
0277   policy.createState(gctx, args, stateManager, *logger);
0278   auto policyState = stateManager.currentState();
0279   delegate(gctx, args, policyState, stream, *logger);
0280 
0281   BOOST_REQUIRE_EQUAL(policy.policies().size(), 2);
0282 
0283   const auto& policyA = dynamic_cast<const APolicy&>(*policy.policies()[0]);
0284   const auto& cPolicy =
0285       dynamic_cast<const CPolicySpecialized<int>&>(*policy.policies()[1]);
0286 
0287   BOOST_CHECK(policyA.executed);
0288   BOOST_CHECK(cPolicy.executed);
0289   BOOST_CHECK_EQUAL(cPolicy.value, 44);
0290 }
0291 
0292 namespace {
0293 
0294 std::vector<const Portal*> getTruth(const Vector3& position,
0295                                     const Vector3& direction,
0296                                     const Transform3& transform,
0297                                     const TrackingVolume& cylVolume,
0298                                     SingleCylinderPortalShell& shell,
0299                                     const Logger& logger, bool posOnly = true) {
0300   Vector3 gpos = transform * position;
0301   Vector3 gdir = transform.linear() * direction;
0302   TryAllNavigationPolicy tryAll(gctx, cylVolume, logger);
0303   NavigationArguments args{.position = gpos, .direction = gdir};
0304   NavigationStream main;
0305   AppendOnlyNavigationStream stream{main};
0306   auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0307   NavigationPolicyStateManager stateManager;
0308   tryAll.createState(gctx, args, stateManager, logger);
0309   auto policyState = stateManager.currentState();
0310   tryAll.initializeCandidates(gctx, args, policyState, stream, logger);
0311   main.initialize(gctx, {gpos, gdir}, BoundaryTolerance::None());
0312   std::vector<const Portal*> portals;
0313   for (auto& candidate : main.candidates()) {
0314     if (!candidate.intersection().isValid()) {
0315       continue;
0316     }
0317 
0318     if (main.candidates().size() > 1 && posOnly &&
0319         !detail::checkPathLength(candidate.intersection().pathLength(),
0320                                  s_onSurfaceTolerance,
0321                                  std::numeric_limits<double>::max(), logger)) {
0322       continue;
0323     }
0324 
0325     portals.push_back(&candidate.portal());
0326   }
0327 
0328   // Find portal types
0329   const Portal* outerCylinder = nullptr;
0330   const Portal* innerCylinder = nullptr;
0331   const Portal* positiveDisc = nullptr;
0332   const Portal* negativeDisc = nullptr;
0333 
0334   for (const Portal* portal : portals) {
0335     if (portal ==
0336         shell.portal(CylinderVolumeBounds::Face::OuterCylinder).get()) {
0337       outerCylinder = portal;
0338     } else if (portal ==
0339                shell.portal(CylinderVolumeBounds::Face::InnerCylinder).get()) {
0340       innerCylinder = portal;
0341     } else if (portal ==
0342                shell.portal(CylinderVolumeBounds::Face::PositiveDisc).get()) {
0343       positiveDisc = portal;
0344     } else if (portal ==
0345                shell.portal(CylinderVolumeBounds::Face::NegativeDisc).get()) {
0346       negativeDisc = portal;
0347     }
0348   }
0349 
0350   // Build new filtered list
0351   std::vector<const Portal*> filteredPortals;
0352 
0353   // Apply existing filter: remove outer cylinder if both inner and outer are
0354   // present
0355   if ((innerCylinder != nullptr) && (outerCylinder != nullptr)) {
0356     // Keep inner, discard outer
0357     filteredPortals.push_back(innerCylinder);
0358   } else {
0359     // Keep whichever one exists
0360     if (innerCylinder != nullptr) {
0361       filteredPortals.push_back(innerCylinder);
0362     }
0363     if (outerCylinder != nullptr) {
0364       filteredPortals.push_back(outerCylinder);
0365     }
0366   }
0367 
0368   // Apply CylinderNavigationPolicy optimization: if inner cylinder is present,
0369   // assume any discs are blocked by it (based on failing test pattern)
0370   if (innerCylinder == nullptr) {
0371     // No inner cylinder, so discs are not blocked
0372     if (positiveDisc != nullptr) {
0373       filteredPortals.push_back(positiveDisc);
0374     }
0375     if (negativeDisc != nullptr) {
0376       filteredPortals.push_back(negativeDisc);
0377     }
0378   }
0379   // If inner cylinder is present, discs are omitted (blocked)
0380 
0381   return filteredPortals;
0382 }
0383 
0384 std::vector<const Portal*> getSmart(const Vector3& position,
0385                                     const Vector3& direction,
0386                                     const Transform3& transform,
0387                                     CylinderNavigationPolicy& policy) {
0388   Vector3 gpos = transform * position;
0389   Vector3 gdir = transform.linear() * direction;
0390   NavigationArguments args{.position = gpos, .direction = gdir};
0391   NavigationStream main;
0392   auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0393   AppendOnlyNavigationStream stream{main};
0394   NavigationPolicyStateManager stateManager;
0395   policy.createState(gctx, args, stateManager, *logger);
0396   auto policyState = stateManager.currentState();
0397   policy.initializeCandidates(gctx, args, policyState, stream, *logger);
0398 
0399   std::vector<const Portal*> portals;
0400   // We don't filter here, because we want to test the candidates as they come
0401   // out of the policy
0402   for (auto& candidate : main.candidates()) {
0403     portals.push_back(&candidate.portal());
0404   }
0405   return portals;
0406 }
0407 
0408 void checkEqual(const std::vector<const Portal*>& exp,
0409                 const std::vector<const Portal*>& act,
0410                 SingleCylinderPortalShell& shell) {
0411   auto which = [&](const Portal* p) -> std::string {
0412     if (p == shell.portal(CylinderVolumeBounds::Face::InnerCylinder).get()) {
0413       return "InnerCylinder";
0414     }
0415     if (p == shell.portal(CylinderVolumeBounds::Face::OuterCylinder).get()) {
0416       return "OuterCylinder";
0417     }
0418     if (p == shell.portal(CylinderVolumeBounds::Face::PositiveDisc).get()) {
0419       return "PositiveDisc";
0420     }
0421     if (p == shell.portal(CylinderVolumeBounds::Face::NegativeDisc).get()) {
0422       return "NegativeDisc";
0423     }
0424     BOOST_FAIL("Unknown portal");
0425     return "";  // unreachable
0426   };
0427 
0428   std::set<const Portal*> expSet;
0429   std::set<const Portal*> actSet;
0430 
0431   std::ranges::copy(exp, std::inserter(expSet, expSet.begin()));
0432   std::ranges::copy(act, std::inserter(actSet, actSet.begin()));
0433 
0434   if (expSet != actSet) {
0435     BOOST_ERROR([&]() -> std::string {
0436       std::vector<std::string> exps;
0437       for (auto& p : exp) {
0438         exps.push_back(which(p));
0439       }
0440       std::vector<std::string> acts;
0441       for (auto& p : act) {
0442         acts.push_back(which(p));
0443       }
0444       return "[" + boost::algorithm::join(exps, ", ") + "] != [" +
0445              boost::algorithm::join(acts, ", ") + "]";
0446     }());
0447   }
0448 }
0449 
0450 }  // namespace
0451 
0452 BOOST_DATA_TEST_CASE(
0453     CylinderPolicyTest,
0454     (bdata::xrange(-135, 180, 45) *
0455      bdata::make(Vector3{0_mm, 0_mm, 0_mm}, Vector3{20_mm, 0_mm, 0_mm},
0456                  Vector3{0_mm, 20_mm, 0_mm}, Vector3{20_mm, 20_mm, 0_mm},
0457                  Vector3{0_mm, 0_mm, 20_mm})),
0458     angle, offset) {
0459   using enum CylinderVolumeBounds::Face;
0460 
0461   Transform3 transform = Transform3::Identity();
0462   transform *= AngleAxis3{angle * 1_degree, Vector3::UnitX()};
0463   transform *= Translation3{offset};
0464   auto cylBounds =
0465       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0466   auto cylVolume =
0467       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0468   SingleCylinderPortalShell shell{gctx, *cylVolume};
0469   shell.applyToVolume();
0470 
0471   {
0472     Vector3 position = Vector3::UnitX() * 150_mm;
0473     Vector3 direction = Vector3::UnitZ();
0474 
0475     auto exp =
0476         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0477 
0478     BOOST_CHECK(exp.size() == 1);
0479     BOOST_CHECK(exp.at(0) == shell.portal(PositiveDisc).get());
0480 
0481     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0482     auto act = getSmart(position, direction, transform, policy);
0483     checkEqual(exp, act, shell);
0484   }
0485 
0486   {
0487     Vector3 position = Vector3::UnitX() * 150_mm;
0488     Vector3 direction = Vector3{1, 1, 0}.normalized();
0489 
0490     auto exp =
0491         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0492 
0493     BOOST_CHECK(exp.size() == 1);
0494     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder).get());
0495 
0496     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0497     auto act = getSmart(position, direction, transform, policy);
0498     checkEqual(exp, act, shell);
0499   }
0500 
0501   {
0502     Vector3 position = Vector3::UnitX() * 150_mm;
0503     Vector3 direction = Vector3{-1, 0, 0}.normalized();
0504 
0505     auto exp =
0506         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0507 
0508     BOOST_CHECK(exp.size() == 1);
0509     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder).get());
0510 
0511     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0512     auto act = getSmart(position, direction, transform, policy);
0513     checkEqual(exp, act, shell);
0514   }
0515 
0516   {
0517     Vector3 position = Vector3::UnitX() * 150_mm;
0518     Vector3 direction = -Vector3::UnitZ();
0519 
0520     auto exp =
0521         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0522 
0523     BOOST_CHECK(exp.size() == 1);
0524     BOOST_CHECK(exp.at(0) == shell.portal(NegativeDisc).get());
0525 
0526     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0527     auto act = getSmart(position, direction, transform, policy);
0528     checkEqual(exp, act, shell);
0529   }
0530 
0531   {
0532     Vector3 position{50, -200, 0};
0533     Vector3 direction = Vector3{0, 1.5, 1}.normalized();
0534 
0535     auto exp =
0536         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0537 
0538     BOOST_CHECK(exp.size() == 1);
0539     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder).get());
0540 
0541     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0542     auto act = getSmart(position, direction, transform, policy);
0543     checkEqual(exp, act, shell);
0544   }
0545 
0546   {
0547     Vector3 position{50, -200, 0};
0548     Vector3 direction = Vector3{0, 1.2, 1}.normalized();
0549 
0550     auto exp =
0551         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0552 
0553     BOOST_CHECK(exp.size() == 1);
0554     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder).get());
0555 
0556     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0557     auto act = getSmart(position, direction, transform, policy);
0558     checkEqual(exp, act, shell);
0559   }
0560 
0561   {
0562     Vector3 position{50, -200, 0};
0563     Vector3 direction = Vector3{0, 0.9, 1}.normalized();
0564 
0565     auto exp =
0566         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0567 
0568     BOOST_CHECK(exp.size() == 1);
0569     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder).get());
0570 
0571     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0572     auto act = getSmart(position, direction, transform, policy);
0573     checkEqual(exp, act, shell);
0574   }
0575 
0576   {
0577     Vector3 position{20, -200, 0};
0578     Vector3 direction = Vector3{0.45, 0.9, 1}.normalized();
0579 
0580     auto exp =
0581         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0582 
0583     BOOST_CHECK(exp.size() == 1);
0584     BOOST_CHECK(exp.at(0) == shell.portal(PositiveDisc).get());
0585 
0586     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0587     auto act = getSmart(position, direction, transform, policy);
0588     checkEqual(exp, act, shell);
0589   }
0590 
0591   {
0592     Vector3 position{20, -200, 0};
0593     Vector3 direction = Vector3{0.45, 0.9, -1}.normalized();
0594 
0595     auto exp =
0596         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0597 
0598     BOOST_CHECK(exp.size() == 1);
0599     BOOST_CHECK(exp.at(0) == shell.portal(NegativeDisc).get());
0600 
0601     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0602     auto act = getSmart(position, direction, transform, policy);
0603     checkEqual(exp, act, shell);
0604   }
0605 
0606   {
0607     Vector3 position{400 * std::cos(std::numbers::pi / 4),
0608                      400 * std::sin(std::numbers::pi / 4), 0};
0609     Vector3 direction = Vector3{0.45, -0.9, -0.1}.normalized();
0610 
0611     // We're sitting ON the outer cylinder here and missing the disc and inner
0612     // cylinder: need to return the outer cylinder
0613     auto exp = getTruth(position, direction, transform, *cylVolume, shell,
0614                         *logger, false);
0615 
0616     BOOST_CHECK(exp.size() == 1);
0617     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder).get());
0618 
0619     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0620     auto act = getSmart(position, direction, transform, policy);
0621     checkEqual(exp, act, shell);
0622   }
0623 
0624   {
0625     Vector3 position{400 * std::cos(std::numbers::pi / 4),
0626                      400 * std::sin(std::numbers::pi / 4), 0};
0627     Vector3 direction = Vector3{-0.3, -0.9, -0.1}.normalized();
0628 
0629     // We're sitting ON the outer cylinder here and missing the disc and inner
0630     // cylinder: need to return the outer cylinder
0631     auto exp = getTruth(position, direction, transform, *cylVolume, shell,
0632                         *logger, false);
0633 
0634     BOOST_CHECK(exp.size() == 1);
0635     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder).get());
0636 
0637     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0638     auto act = getSmart(position, direction, transform, policy);
0639     checkEqual(exp, act, shell);
0640   }
0641 
0642   {
0643     Vector3 position{100 * std::cos(std::numbers::pi / 4),
0644                      100 * std::sin(std::numbers::pi / 4), 0};
0645     double dangle = 0.1;
0646     Vector3 direction = Vector3{std::cos(dangle), std::sin(dangle), 0.01};
0647 
0648     // We're sitting ON the Inner cylinder here and  pointing outwards
0649     auto exp =
0650         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0651 
0652     BOOST_CHECK(exp.size() == 1);
0653     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder).get());
0654 
0655     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0656     auto act = getSmart(position, direction, transform, policy);
0657     checkEqual(exp, act, shell);
0658   }
0659 
0660   {
0661     Vector3 position{200 * std::cos(std::numbers::pi / 4),
0662                      200 * std::sin(std::numbers::pi / 4), 0};
0663     Vector3 target{150 * std::cos(std::numbers::pi * 5 / 4),
0664                    150 * std::sin(std::numbers::pi * 5 / 4), 300};
0665     Vector3 direction = (target - position).normalized();
0666 
0667     auto exp =
0668         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0669 
0670     BOOST_CHECK_EQUAL(exp.size(), 1);
0671     BOOST_CHECK_EQUAL(exp.at(0), shell.portal(InnerCylinder).get());
0672 
0673     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0674     auto act = getSmart(position, direction, transform, policy);
0675     checkEqual(exp, act, shell);
0676   }
0677 }
0678 
0679 namespace {
0680 
0681 std::mt19937 engine;
0682 
0683 unsigned long seed() {
0684   static unsigned long s = 42;
0685   return s++;
0686 }
0687 
0688 std::uniform_real_distribution<double> rDistOffBoundary{
0689     100 + 2 * s_onSurfaceTolerance, 400 - 2 * s_onSurfaceTolerance};
0690 std::uniform_real_distribution<double> zDistOffBoundary{
0691     -300_mm + 2 * s_onSurfaceTolerance, 300_mm - 2 * s_onSurfaceTolerance};
0692 std::uniform_real_distribution<double> phiDist{-std::numbers::pi,
0693                                                std::numbers::pi};
0694 std::uniform_real_distribution<double> thetaDist{0, std::numbers::pi};
0695 
0696 }  // namespace
0697 
0698 BOOST_DATA_TEST_CASE(
0699     CylinderPolicyTestOffBoundary,
0700     bdata::random((bdata::engine = engine, bdata::seed = seed(),
0701                    bdata::distribution = rDistOffBoundary)) ^
0702         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0703                        bdata::distribution = zDistOffBoundary)) ^
0704         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0705                        bdata::distribution = phiDist)) ^
0706         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0707                        bdata::distribution = phiDist)) ^
0708         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0709                        bdata::distribution = thetaDist)) ^
0710         bdata::xrange(100),
0711     r, z, phiPos, phiDir, theta, index) {
0712   static_cast<void>(index);
0713 
0714   Transform3 transform = Transform3::Identity();
0715   auto cylBounds =
0716       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0717   auto cylVolume =
0718       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0719   SingleCylinderPortalShell shell{gctx, *cylVolume};
0720   shell.applyToVolume();
0721 
0722   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos), z};
0723   Vector3 direction{std::sin(theta) * std::cos(phiDir),
0724                     std::sin(theta) * std::sin(phiDir), std::cos(theta)};
0725 
0726   BOOST_CHECK(cylBounds->inside(position));
0727 
0728   auto exp =
0729       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0730 
0731   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0732   auto act = getSmart(position, direction, transform, policy);
0733   checkEqual(exp, act, shell);
0734 }
0735 
0736 BOOST_DATA_TEST_CASE(
0737     CylinderPolicyTestOnRBoundary,
0738     bdata::make(100, 400) *
0739         (bdata::random((bdata::engine = engine, bdata::seed = seed(),
0740                         bdata::distribution = zDistOffBoundary)) ^
0741          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0742                         bdata::distribution = phiDist)) ^
0743          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0744                         bdata::distribution = phiDist)) ^
0745          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0746                         bdata::distribution = zDistOffBoundary)) ^
0747          bdata::xrange(100)),
0748     r, z, phiPos, phiTarget, zTarget, index) {
0749   static_cast<void>(index);
0750 
0751   Transform3 transform = Transform3::Identity();
0752   auto cylBounds =
0753       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0754   auto cylVolume =
0755       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0756   SingleCylinderPortalShell shell{gctx, *cylVolume};
0757   shell.applyToVolume();
0758 
0759   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos), z};
0760   Vector3 target{r * std::cos(phiTarget), r * std::sin(phiTarget), zTarget};
0761   Vector3 direction = (target - position).normalized();
0762 
0763   auto exp =
0764       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0765 
0766   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0767   auto act = getSmart(position, direction, transform, policy);
0768   checkEqual(exp, act, shell);
0769 }
0770 
0771 BOOST_DATA_TEST_CASE(
0772     CylinderPolicyTestOnZBoundary,
0773     bdata::make(-300, 300) *
0774         (bdata::random((bdata::engine = engine, bdata::seed = seed(),
0775                         bdata::distribution = rDistOffBoundary)) ^
0776          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0777                         bdata::distribution = phiDist)) ^
0778          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0779                         bdata::distribution = phiDist)) ^
0780          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0781                         bdata::distribution = zDistOffBoundary)) ^
0782          bdata::xrange(100)),
0783     z, r, phiPos, phiTarget, zTarget, index) {
0784   static_cast<void>(index);
0785   Transform3 transform = Transform3::Identity();
0786   auto cylBounds =
0787       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0788   auto cylVolume =
0789       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0790   SingleCylinderPortalShell shell{gctx, *cylVolume};
0791   shell.applyToVolume();
0792 
0793   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos),
0794                    static_cast<double>(z)};
0795   Vector3 target{r * std::cos(phiTarget), r * std::sin(phiTarget), zTarget};
0796   Vector3 direction = (target - position).normalized();
0797 
0798   BOOST_CHECK(cylBounds->inside(position));
0799 
0800   auto exp =
0801       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0802 
0803   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0804   auto act = getSmart(position, direction, transform, policy);
0805   checkEqual(exp, act, shell);
0806 }
0807 
0808 BOOST_AUTO_TEST_CASE(CylinderPolicyZeroInnerRadiusTest) {
0809   // Test that CylinderNavigationPolicy rejects volumes with zero inner radius
0810   Transform3 transform = Transform3::Identity();
0811 
0812   // Create cylinder volume bounds with zero inner radius (rMin = 0)
0813   auto cylBounds = std::make_shared<CylinderVolumeBounds>(0_mm, 400_mm, 300_mm);
0814   auto cylVolume = std::make_shared<TrackingVolume>(transform, cylBounds,
0815                                                     "ZeroInnerRadiusVolume");
0816 
0817   // CylinderNavigationPolicy constructor should throw std::invalid_argument
0818   // for volumes with zero inner radius
0819   {
0820     Acts::Logging::ScopedFailureThreshold log{Logging::FATAL};
0821     BOOST_CHECK_THROW(CylinderNavigationPolicy(gctx, *cylVolume, *logger),
0822                       std::invalid_argument);
0823   }
0824 }
0825 
0826 BOOST_AUTO_TEST_SUITE_END()
0827 
0828 }  // namespace ActsTests