Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:08:48

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