Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 08:04:05

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