Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:24:58

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 GeometryContext gctx;
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   GeometryContext gctx;
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 == shell.portal(CylinderVolumeBounds::Face::OuterCylinder)) {
0317       outerCylinder = portal;
0318     } else if (portal ==
0319                shell.portal(CylinderVolumeBounds::Face::InnerCylinder)) {
0320       innerCylinder = portal;
0321     } else if (portal ==
0322                shell.portal(CylinderVolumeBounds::Face::PositiveDisc)) {
0323       positiveDisc = portal;
0324     } else if (portal ==
0325                shell.portal(CylinderVolumeBounds::Face::NegativeDisc)) {
0326       negativeDisc = portal;
0327     }
0328   }
0329 
0330   // Build new filtered list
0331   std::vector<const Portal*> filteredPortals;
0332 
0333   // Apply existing filter: remove outer cylinder if both inner and outer are
0334   // present
0335   if ((innerCylinder != nullptr) && (outerCylinder != nullptr)) {
0336     // Keep inner, discard outer
0337     filteredPortals.push_back(innerCylinder);
0338   } else {
0339     // Keep whichever one exists
0340     if (innerCylinder != nullptr) {
0341       filteredPortals.push_back(innerCylinder);
0342     }
0343     if (outerCylinder != nullptr) {
0344       filteredPortals.push_back(outerCylinder);
0345     }
0346   }
0347 
0348   // Apply CylinderNavigationPolicy optimization: if inner cylinder is present,
0349   // assume any discs are blocked by it (based on failing test pattern)
0350   if (innerCylinder == nullptr) {
0351     // No inner cylinder, so discs are not blocked
0352     if (positiveDisc != nullptr) {
0353       filteredPortals.push_back(positiveDisc);
0354     }
0355     if (negativeDisc != nullptr) {
0356       filteredPortals.push_back(negativeDisc);
0357     }
0358   }
0359   // If inner cylinder is present, discs are omitted (blocked)
0360 
0361   return filteredPortals;
0362 }
0363 
0364 std::vector<const Portal*> getSmart(const Vector3& position,
0365                                     const Vector3& direction,
0366                                     const Transform3& transform,
0367                                     CylinderNavigationPolicy& policy) {
0368   Vector3 gpos = transform * position;
0369   Vector3 gdir = transform.linear() * direction;
0370   NavigationArguments args{.position = gpos, .direction = gdir};
0371   NavigationStream main;
0372   GeometryContext gctx;
0373   AppendOnlyNavigationStream stream{main};
0374   policy.initializeCandidates(gctx, args, stream, *logger);
0375 
0376   std::vector<const Portal*> portals;
0377   // We don't filter here, because we want to test the candidates as they come
0378   // out of the policy
0379   for (auto& candidate : main.candidates()) {
0380     portals.push_back(&candidate.portal());
0381   }
0382   return portals;
0383 }
0384 
0385 void checkEqual(const std::vector<const Portal*>& exp,
0386                 const std::vector<const Portal*>& act,
0387                 SingleCylinderPortalShell& shell) {
0388   auto which = [&](const Portal* p) -> std::string {
0389     if (p == shell.portal(CylinderVolumeBounds::Face::InnerCylinder)) {
0390       return "InnerCylinder";
0391     }
0392     if (p == shell.portal(CylinderVolumeBounds::Face::OuterCylinder)) {
0393       return "OuterCylinder";
0394     }
0395     if (p == shell.portal(CylinderVolumeBounds::Face::PositiveDisc)) {
0396       return "PositiveDisc";
0397     }
0398     if (p == shell.portal(CylinderVolumeBounds::Face::NegativeDisc)) {
0399       return "NegativeDisc";
0400     }
0401     BOOST_FAIL("Unknown portal");
0402     return "";  // unreachable
0403   };
0404 
0405   std::set<const Portal*> expSet;
0406   std::set<const Portal*> actSet;
0407 
0408   std::ranges::copy(exp, std::inserter(expSet, expSet.begin()));
0409   std::ranges::copy(act, std::inserter(actSet, actSet.begin()));
0410 
0411   if (expSet != actSet) {
0412     BOOST_ERROR([&]() -> std::string {
0413       std::vector<std::string> exps;
0414       for (auto& p : exp) {
0415         exps.push_back(which(p));
0416       }
0417       std::vector<std::string> acts;
0418       for (auto& p : act) {
0419         acts.push_back(which(p));
0420       }
0421       return "[" + boost::algorithm::join(exps, ", ") + "] != [" +
0422              boost::algorithm::join(acts, ", ") + "]";
0423     }());
0424   }
0425 }
0426 
0427 }  // namespace
0428 
0429 BOOST_DATA_TEST_CASE(
0430     CylinderPolicyTest,
0431     (bdata::xrange(-135, 180, 45) *
0432      bdata::make(Vector3{0_mm, 0_mm, 0_mm}, Vector3{20_mm, 0_mm, 0_mm},
0433                  Vector3{0_mm, 20_mm, 0_mm}, Vector3{20_mm, 20_mm, 0_mm},
0434                  Vector3{0_mm, 0_mm, 20_mm})),
0435     angle, offset) {
0436   using enum CylinderVolumeBounds::Face;
0437 
0438   Transform3 transform = Transform3::Identity();
0439   transform *= AngleAxis3{angle * 1_degree, Vector3::UnitX()};
0440   transform *= Translation3{offset};
0441   auto cylBounds =
0442       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0443   auto cylVolume =
0444       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0445   SingleCylinderPortalShell shell{*cylVolume};
0446   shell.applyToVolume();
0447 
0448   {
0449     Vector3 position = Vector3::UnitX() * 150_mm;
0450     Vector3 direction = Vector3::UnitZ();
0451 
0452     auto exp =
0453         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0454 
0455     BOOST_CHECK(exp.size() == 1);
0456     BOOST_CHECK(exp.at(0) == shell.portal(PositiveDisc));
0457 
0458     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0459     auto act = getSmart(position, direction, transform, policy);
0460     checkEqual(exp, act, shell);
0461   }
0462 
0463   {
0464     Vector3 position = Vector3::UnitX() * 150_mm;
0465     Vector3 direction = Vector3{1, 1, 0}.normalized();
0466 
0467     auto exp =
0468         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0469 
0470     BOOST_CHECK(exp.size() == 1);
0471     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder));
0472 
0473     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0474     auto act = getSmart(position, direction, transform, policy);
0475     checkEqual(exp, act, shell);
0476   }
0477 
0478   {
0479     Vector3 position = Vector3::UnitX() * 150_mm;
0480     Vector3 direction = Vector3{-1, 0, 0}.normalized();
0481 
0482     auto exp =
0483         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0484 
0485     BOOST_CHECK(exp.size() == 1);
0486     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder));
0487 
0488     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0489     auto act = getSmart(position, direction, transform, policy);
0490     checkEqual(exp, act, shell);
0491   }
0492 
0493   {
0494     Vector3 position = Vector3::UnitX() * 150_mm;
0495     Vector3 direction = -Vector3::UnitZ();
0496 
0497     auto exp =
0498         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0499 
0500     BOOST_CHECK(exp.size() == 1);
0501     BOOST_CHECK(exp.at(0) == shell.portal(NegativeDisc));
0502 
0503     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0504     auto act = getSmart(position, direction, transform, policy);
0505     checkEqual(exp, act, shell);
0506   }
0507 
0508   {
0509     Vector3 position{50, -200, 0};
0510     Vector3 direction = Vector3{0, 1.5, 1}.normalized();
0511 
0512     auto exp =
0513         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0514 
0515     BOOST_CHECK(exp.size() == 1);
0516     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder));
0517 
0518     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0519     auto act = getSmart(position, direction, transform, policy);
0520     checkEqual(exp, act, shell);
0521   }
0522 
0523   {
0524     Vector3 position{50, -200, 0};
0525     Vector3 direction = Vector3{0, 1.2, 1}.normalized();
0526 
0527     auto exp =
0528         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0529 
0530     BOOST_CHECK(exp.size() == 1);
0531     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder));
0532 
0533     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0534     auto act = getSmart(position, direction, transform, policy);
0535     checkEqual(exp, act, shell);
0536   }
0537 
0538   {
0539     Vector3 position{50, -200, 0};
0540     Vector3 direction = Vector3{0, 0.9, 1}.normalized();
0541 
0542     auto exp =
0543         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0544 
0545     BOOST_CHECK(exp.size() == 1);
0546     BOOST_CHECK(exp.at(0) == shell.portal(InnerCylinder));
0547 
0548     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0549     auto act = getSmart(position, direction, transform, policy);
0550     checkEqual(exp, act, shell);
0551   }
0552 
0553   {
0554     Vector3 position{20, -200, 0};
0555     Vector3 direction = Vector3{0.45, 0.9, 1}.normalized();
0556 
0557     auto exp =
0558         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0559 
0560     BOOST_CHECK(exp.size() == 1);
0561     BOOST_CHECK(exp.at(0) == shell.portal(PositiveDisc));
0562 
0563     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0564     auto act = getSmart(position, direction, transform, policy);
0565     checkEqual(exp, act, shell);
0566   }
0567 
0568   {
0569     Vector3 position{20, -200, 0};
0570     Vector3 direction = Vector3{0.45, 0.9, -1}.normalized();
0571 
0572     auto exp =
0573         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0574 
0575     BOOST_CHECK(exp.size() == 1);
0576     BOOST_CHECK(exp.at(0) == shell.portal(NegativeDisc));
0577 
0578     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0579     auto act = getSmart(position, direction, transform, policy);
0580     checkEqual(exp, act, shell);
0581   }
0582 
0583   {
0584     Vector3 position{400 * std::cos(std::numbers::pi / 4),
0585                      400 * std::sin(std::numbers::pi / 4), 0};
0586     Vector3 direction = Vector3{0.45, -0.9, -0.1}.normalized();
0587 
0588     // We're sitting ON the outer cylinder here and missing the disc and inner
0589     // cylinder: need to return the outer cylinder
0590     auto exp = getTruth(position, direction, transform, *cylVolume, shell,
0591                         *logger, false);
0592 
0593     BOOST_CHECK(exp.size() == 1);
0594     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder));
0595 
0596     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0597     auto act = getSmart(position, direction, transform, policy);
0598     checkEqual(exp, act, shell);
0599   }
0600 
0601   {
0602     Vector3 position{400 * std::cos(std::numbers::pi / 4),
0603                      400 * std::sin(std::numbers::pi / 4), 0};
0604     Vector3 direction = Vector3{-0.3, -0.9, -0.1}.normalized();
0605 
0606     // We're sitting ON the outer cylinder here and missing the disc and inner
0607     // cylinder: need to return the outer cylinder
0608     auto exp = getTruth(position, direction, transform, *cylVolume, shell,
0609                         *logger, false);
0610 
0611     BOOST_CHECK(exp.size() == 1);
0612     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder));
0613 
0614     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0615     auto act = getSmart(position, direction, transform, policy);
0616     checkEqual(exp, act, shell);
0617   }
0618 
0619   {
0620     Vector3 position{100 * std::cos(std::numbers::pi / 4),
0621                      100 * std::sin(std::numbers::pi / 4), 0};
0622     double dangle = 0.1;
0623     Vector3 direction = Vector3{std::cos(dangle), std::sin(dangle), 0.01};
0624 
0625     // We're sitting ON the Inner cylinder here and  pointing outwards
0626     auto exp =
0627         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0628 
0629     BOOST_CHECK(exp.size() == 1);
0630     BOOST_CHECK(exp.at(0) == shell.portal(OuterCylinder));
0631 
0632     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0633     auto act = getSmart(position, direction, transform, policy);
0634     checkEqual(exp, act, shell);
0635   }
0636 
0637   {
0638     Vector3 position{200 * std::cos(std::numbers::pi / 4),
0639                      200 * std::sin(std::numbers::pi / 4), 0};
0640     Vector3 target{150 * std::cos(std::numbers::pi * 5 / 4),
0641                    150 * std::sin(std::numbers::pi * 5 / 4), 300};
0642     Vector3 direction = (target - position).normalized();
0643 
0644     auto exp =
0645         getTruth(position, direction, transform, *cylVolume, shell, *logger);
0646 
0647     BOOST_CHECK_EQUAL(exp.size(), 1);
0648     BOOST_CHECK_EQUAL(exp.at(0), shell.portal(InnerCylinder));
0649 
0650     CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0651     auto act = getSmart(position, direction, transform, policy);
0652     checkEqual(exp, act, shell);
0653   }
0654 }
0655 
0656 namespace {
0657 
0658 std::mt19937 engine;
0659 
0660 unsigned long seed() {
0661   static unsigned long s = 42;
0662   return s++;
0663 }
0664 
0665 std::uniform_real_distribution<double> rDistOffBoundary{
0666     100 + 2 * s_onSurfaceTolerance, 400 - 2 * s_onSurfaceTolerance};
0667 std::uniform_real_distribution<double> zDistOffBoundary{
0668     -300_mm + 2 * s_onSurfaceTolerance, 300_mm - 2 * s_onSurfaceTolerance};
0669 std::uniform_real_distribution<double> phiDist{-std::numbers::pi,
0670                                                std::numbers::pi};
0671 std::uniform_real_distribution<double> thetaDist{0, std::numbers::pi};
0672 
0673 }  // namespace
0674 
0675 BOOST_DATA_TEST_CASE(
0676     CylinderPolicyTestOffBoundary,
0677     bdata::random((bdata::engine = engine, bdata::seed = seed(),
0678                    bdata::distribution = rDistOffBoundary)) ^
0679         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0680                        bdata::distribution = zDistOffBoundary)) ^
0681         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0682                        bdata::distribution = phiDist)) ^
0683         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0684                        bdata::distribution = phiDist)) ^
0685         bdata::random((bdata::engine = engine, bdata::seed = seed(),
0686                        bdata::distribution = thetaDist)) ^
0687         bdata::xrange(100),
0688     r, z, phiPos, phiDir, theta, index) {
0689   static_cast<void>(index);
0690 
0691   Transform3 transform = Transform3::Identity();
0692   auto cylBounds =
0693       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0694   auto cylVolume =
0695       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0696   SingleCylinderPortalShell shell{*cylVolume};
0697   shell.applyToVolume();
0698 
0699   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos), z};
0700   Vector3 direction{std::sin(theta) * std::cos(phiDir),
0701                     std::sin(theta) * std::sin(phiDir), std::cos(theta)};
0702 
0703   BOOST_CHECK(cylBounds->inside(position));
0704 
0705   auto exp =
0706       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0707 
0708   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0709   auto act = getSmart(position, direction, transform, policy);
0710   checkEqual(exp, act, shell);
0711 }
0712 
0713 BOOST_DATA_TEST_CASE(
0714     CylinderPolicyTestOnRBoundary,
0715     bdata::make(100, 400) *
0716         (bdata::random((bdata::engine = engine, bdata::seed = seed(),
0717                         bdata::distribution = zDistOffBoundary)) ^
0718          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0719                         bdata::distribution = phiDist)) ^
0720          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0721                         bdata::distribution = phiDist)) ^
0722          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0723                         bdata::distribution = zDistOffBoundary)) ^
0724          bdata::xrange(100)),
0725     r, z, phiPos, phiTarget, zTarget, index) {
0726   static_cast<void>(index);
0727 
0728   Transform3 transform = Transform3::Identity();
0729   auto cylBounds =
0730       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0731   auto cylVolume =
0732       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0733   SingleCylinderPortalShell shell{*cylVolume};
0734   shell.applyToVolume();
0735 
0736   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos), z};
0737   Vector3 target{r * std::cos(phiTarget), r * std::sin(phiTarget), zTarget};
0738   Vector3 direction = (target - position).normalized();
0739 
0740   auto exp =
0741       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0742 
0743   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0744   auto act = getSmart(position, direction, transform, policy);
0745   checkEqual(exp, act, shell);
0746 }
0747 
0748 BOOST_DATA_TEST_CASE(
0749     CylinderPolicyTestOnZBoundary,
0750     bdata::make(-300, 300) *
0751         (bdata::random((bdata::engine = engine, bdata::seed = seed(),
0752                         bdata::distribution = rDistOffBoundary)) ^
0753          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0754                         bdata::distribution = phiDist)) ^
0755          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0756                         bdata::distribution = phiDist)) ^
0757          bdata::random((bdata::engine = engine, bdata::seed = seed(),
0758                         bdata::distribution = zDistOffBoundary)) ^
0759          bdata::xrange(100)),
0760     z, r, phiPos, phiTarget, zTarget, index) {
0761   static_cast<void>(index);
0762   Transform3 transform = Transform3::Identity();
0763   auto cylBounds =
0764       std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 300_mm);
0765   auto cylVolume =
0766       std::make_shared<TrackingVolume>(transform, cylBounds, "CylinderVolume");
0767   SingleCylinderPortalShell shell{*cylVolume};
0768   shell.applyToVolume();
0769 
0770   Vector3 position{r * std::cos(phiPos), r * std::sin(phiPos),
0771                    static_cast<double>(z)};
0772   Vector3 target{r * std::cos(phiTarget), r * std::sin(phiTarget), zTarget};
0773   Vector3 direction = (target - position).normalized();
0774 
0775   BOOST_CHECK(cylBounds->inside(position));
0776 
0777   auto exp =
0778       getTruth(position, direction, transform, *cylVolume, shell, *logger);
0779 
0780   CylinderNavigationPolicy policy(gctx, *cylVolume, *logger);
0781   auto act = getSmart(position, direction, transform, policy);
0782   checkEqual(exp, act, shell);
0783 }
0784 
0785 BOOST_AUTO_TEST_CASE(CylinderPolicyZeroInnerRadiusTest) {
0786   // Test that CylinderNavigationPolicy rejects volumes with zero inner radius
0787   Transform3 transform = Transform3::Identity();
0788 
0789   // Create cylinder volume bounds with zero inner radius (rMin = 0)
0790   auto cylBounds = std::make_shared<CylinderVolumeBounds>(0_mm, 400_mm, 300_mm);
0791   auto cylVolume = std::make_shared<TrackingVolume>(transform, cylBounds,
0792                                                     "ZeroInnerRadiusVolume");
0793 
0794   // CylinderNavigationPolicy constructor should throw std::invalid_argument
0795   // for volumes with zero inner radius
0796   {
0797     Acts::Logging::ScopedFailureThreshold log{Logging::FATAL};
0798     BOOST_CHECK_THROW(CylinderNavigationPolicy(gctx, *cylVolume, *logger),
0799                       std::invalid_argument);
0800   }
0801 }
0802 
0803 BOOST_AUTO_TEST_SUITE_END()
0804 
0805 }  // namespace ActsTests