Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-09 09:28:43

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/unit_test.hpp>
0010 
0011 #include "Acts/Geometry/Blueprint.hpp"
0012 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0013 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0014 #include "Acts/Geometry/LinearVolumeLookup.hpp"
0015 #include "Acts/Geometry/RzGridVolumeLookup.hpp"
0016 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0017 #include "Acts/Geometry/TrackingGeometry.hpp"
0018 #include "Acts/Geometry/TrackingVolume.hpp"
0019 #include "Acts/Geometry/VolumePlacementBase.hpp"
0020 
0021 #include <limits>
0022 #include <random>
0023 #include <sstream>
0024 #include <type_traits>
0025 
0026 using namespace Acts;
0027 
0028 static_assert(!std::is_copy_constructible_v<TrackingGeometry>);
0029 static_assert(!std::is_copy_assignable_v<TrackingGeometry>);
0030 namespace {
0031 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0032 std::shared_ptr<TrackingVolume> makeWorld() {
0033   auto world = std::make_shared<TrackingVolume>(
0034       Transform3::Identity(),
0035       std::make_shared<CylinderVolumeBounds>(0., 100., 100.), "world");
0036   // Overlapping siblings and gaps exercise priority and parent fallback.
0037   for (int i = 0; i < 16; ++i) {
0038     auto child = std::make_unique<TrackingVolume>(
0039         Transform3::Identity(),
0040         std::make_shared<CylinderVolumeBounds>(i * 4., i * 4. + 6., 20. + i),
0041         "shell");
0042     if (i == 2) {
0043       child->addVolume(std::make_unique<TrackingVolume>(
0044           Transform3::Identity(),
0045           std::make_shared<CylinderVolumeBounds>(9., 10., 5.), "nested"));
0046     }
0047     world->addVolume(std::move(child));
0048   }
0049   unsigned id = 0;
0050   world->apply([&](TrackingVolume& v) {
0051     v.assignGeometryId(GeometryIdentifier{}.withVolume(++id));
0052   });
0053   return world;
0054 }
0055 class MovingPlacement : public VolumePlacementBase {
0056  public:
0057   Transform3 transform = Transform3::Identity();
0058   Transform3 inverse = Transform3::Identity();
0059   const Transform3& localToGlobalTransform(
0060       const GeometryContext& /*gctx*/) const override {
0061     return transform;
0062   }
0063   const Transform3& portalLocalToGlobal(
0064       const GeometryContext& /*gctx*/,
0065       std::size_t /*portalIdx*/) const override {
0066     return transform;
0067   }
0068   const Transform3& globalToLocalTransform(
0069       const GeometryContext& /*gctx*/) const override {
0070     return inverse;
0071   }
0072 };
0073 }  // namespace
0074 
0075 BOOST_AUTO_TEST_CASE(RzLookupMatchesLinear) {
0076   auto world = makeWorld();
0077   const LinearVolumeLookup linear(*world, {});
0078   TrackingGeometry geometry(world, nullptr, {}, getDummyLogger(), false);
0079   std::mt19937 rng(42);
0080   std::uniform_real_distribution<double> uniform(-110., 110.);
0081   {
0082     RzGridVolumeLookup::Config cfg;
0083     geometry.highestTrackingVolume()->setVolumeLookup(
0084         std::make_unique<RzGridVolumeLookup>(gctx, *world, cfg));
0085     for (double tolerance : {-1e-4, 0., 1e-6, cfg.maxTolerance, 0.5}) {
0086       for (int i = 0; i < 3000; ++i) {
0087         Vector3 p(uniform(rng), uniform(rng), uniform(rng));
0088         BOOST_CHECK(*geometry.resolveLowestTrackingVolume(gctx, p, std::nullopt,
0089                                                           nullptr, tolerance) ==
0090                     linear.findLowest(gctx, p, tolerance));
0091       }
0092       for (int i = 0; i < 16; ++i) {
0093         for (double delta : {-2e-3, -1e-3, -1e-6, 0., 1e-6, 1e-3, 2e-3}) {
0094           for (Vector3 p : {Vector3(i * 4. + delta, 0., 0.),
0095                             Vector3(i * 4. + 6. + delta, 0., 0.),
0096                             Vector3(i * 4. + 2., 0., 20. + i + delta)}) {
0097             BOOST_CHECK(*geometry.resolveLowestTrackingVolume(
0098                             gctx, p, std::nullopt, nullptr, tolerance) ==
0099                         linear.findLowest(gctx, p, tolerance));
0100           }
0101         }
0102       }
0103     }
0104     // First sibling wins even when a later sibling has a deeper matching child.
0105     BOOST_CHECK(*geometry.resolveLowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0106                 &world->volumes().at(1));
0107     geometry.highestTrackingVolume()->setVolumeLookup(nullptr);
0108     BOOST_CHECK(*geometry.resolveLowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0109                 &world->volumes().at(1));
0110     BOOST_CHECK(geometry.highestTrackingVolume()->volumeLookup() == nullptr);
0111   }
0112 }
0113 
0114 BOOST_AUTO_TEST_CASE(RzLookupFallbacks) {
0115   {
0116     auto world = makeWorld();
0117     RzGridVolumeLookup::Config cfg;
0118     {
0119       cfg.maxGridCells = 1;  // Force bounded-memory fallback.
0120     }
0121     RzGridVolumeLookup limited(gctx, *world, cfg);
0122     BOOST_CHECK(limited.findLowest(gctx, {40., 0., 0.}, 0.) ==
0123                 world->lowestTrackingVolume(gctx, {40., 0., 0.}, 0.));
0124     world->addVolume(std::make_unique<TrackingVolume>(
0125         Transform3::Identity(),
0126         std::make_shared<CuboidVolumeBounds>(80., 80., 80.), "box"));
0127     RzGridVolumeLookup mixed(gctx, *world, cfg);
0128     BOOST_CHECK(mixed.findLowest(gctx, {70., 0., 0.}, 0.) ==
0129                 world->lowestTrackingVolume(gctx, {70., 0., 0.}, 0.));
0130 
0131     MovingPlacement placement;
0132     auto movingWorld = std::make_shared<TrackingVolume>(
0133         Transform3::Identity(),
0134         std::make_shared<CylinderVolumeBounds>(0., 100., 100.));
0135     movingWorld->addVolume(std::make_unique<TrackingVolume>(
0136         placement, std::make_shared<CylinderVolumeBounds>(0., 5., 5.)));
0137     RzGridVolumeLookup moving(gctx, *movingWorld, cfg);
0138     placement.transform.translation().x() = 50.;
0139     placement.inverse = placement.transform.inverse();
0140     BOOST_CHECK(moving.findLowest(gctx, {50., 0., 0.}, 0.) ==
0141                 &movingWorld->volumes().at(0));
0142   }
0143 }
0144 
0145 BOOST_AUTO_TEST_CASE(BlueprintSelectsVolumeLookup) {
0146   for (int type : {0, 1}) {
0147     Blueprint blueprint{Blueprint::Config{}};
0148     blueprint.addChild(
0149         std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0150             Transform3::Identity(),
0151             std::make_shared<CylinderVolumeBounds>(0., 10., 10.), "cylinder")));
0152     BlueprintOptions options;
0153     blueprint.setVolumeLookupFactory(
0154         [type](const GeometryContext& context, const TrackingVolume& volume,
0155                const Logger& logger) -> std::unique_ptr<IVolumeLookup> {
0156           if (type == 0) {
0157             return std::make_unique<LinearVolumeLookup>(
0158                 volume, LinearVolumeLookup::Config{}, logger);
0159           }
0160           return std::make_unique<RzGridVolumeLookup>(
0161               context, volume, RzGridVolumeLookup::Config{}, logger);
0162         });
0163     auto geometry = blueprint.construct(options, gctx);
0164     geometry->highestTrackingVolume()->apply([&](TrackingVolume& volume) {
0165       if (&volume != geometry->highestTrackingVolume()) {
0166         BOOST_CHECK(volume.volumeLookup() == nullptr);
0167         return;
0168       }
0169       BOOST_REQUIRE(volume.volumeLookup() != nullptr);
0170       BOOST_CHECK((dynamic_cast<const LinearVolumeLookup*>(
0171                        volume.volumeLookup()) != nullptr) == (type == 0));
0172       BOOST_CHECK((dynamic_cast<const RzGridVolumeLookup*>(
0173                        volume.volumeLookup()) != nullptr) == (type == 1));
0174     });
0175     BOOST_CHECK(*geometry->resolveLowestTrackingVolume(gctx, {5., 0., 0.}) !=
0176                 nullptr);
0177   }
0178 }
0179 
0180 BOOST_AUTO_TEST_CASE(InvalidVolumeLookupConfig) {
0181   auto world = makeWorld();
0182   RzGridVolumeLookup::Config cfg;
0183   cfg.maxTolerance = -1;
0184   BOOST_CHECK_THROW(RzGridVolumeLookup(gctx, *world, cfg),
0185                     std::invalid_argument);
0186   cfg.maxTolerance = 0;
0187   cfg.maxGridCells = 0;
0188   BOOST_CHECK_THROW(RzGridVolumeLookup(gctx, *world, cfg),
0189                     std::invalid_argument);
0190 }
0191 
0192 BOOST_AUTO_TEST_CASE(InvalidGridConfig) {
0193   auto world = makeWorld();
0194   RzGridVolumeLookup::Config cfg;
0195   cfg.maxGridCells = 0;
0196   BOOST_CHECK_THROW(RzGridVolumeLookup(gctx, *world, cfg),
0197                     std::invalid_argument);
0198 }
0199 
0200 BOOST_AUTO_TEST_CASE(LinearLookupWarnings) {
0201   // These assertions deliberately exercise warning diagnostics.
0202 #ifdef ACTS_ENABLE_LOG_FAILURE_THRESHOLD
0203   Logging::ScopedFailureThreshold threshold{Logging::ERROR};
0204 #endif
0205   auto world = makeWorld();
0206   std::ostringstream output;
0207   auto logger = getDefaultLogger("LookupTest", Logging::WARNING, &output);
0208   LinearVolumeLookup atThreshold(*world, LinearVolumeLookup::Config{16},
0209                                  *logger);
0210   BOOST_CHECK(output.str().empty());
0211   LinearVolumeLookup aboveThreshold(*world, LinearVolumeLookup::Config{15},
0212                                     *logger);
0213   BOOST_CHECK(output.str().find("scans up to 16 siblings") !=
0214               std::string::npos);
0215   output.str("");
0216   LinearVolumeLookup zeroThreshold(*world, LinearVolumeLookup::Config{0},
0217                                    *logger);
0218   BOOST_CHECK(output.str().find("scans up to 16 siblings") !=
0219               std::string::npos);
0220   output.str("");
0221   LinearVolumeLookup suppressed(
0222       *world, LinearVolumeLookup::Config{std::nullopt}, *logger);
0223   BOOST_CHECK(output.str().empty());
0224   for (int i = 16; i < 65; ++i) {
0225     world->addVolume(std::make_unique<TrackingVolume>(
0226         Transform3::Identity(),
0227         std::make_shared<CylinderVolumeBounds>(0., 5., 5.), "extra"));
0228   }
0229   unsigned id = 0;
0230   world->apply([&](TrackingVolume& v) {
0231     v.assignGeometryId(GeometryIdentifier{}.withVolume(++id));
0232   });
0233   // A configured accelerator does not emit a transient linear warning.
0234   auto lookup = std::make_unique<RzGridVolumeLookup>(
0235       gctx, *world, RzGridVolumeLookup::Config{}, *logger);
0236   world->setVolumeLookup(std::move(lookup));
0237   TrackingGeometry accelerated(world, nullptr, {}, *logger, false);
0238   BOOST_CHECK(output.str().empty());
0239   world->setVolumeLookup(nullptr);
0240   BOOST_CHECK(output.str().empty());
0241   BOOST_CHECK(world->volumeLookup() == nullptr);
0242   world->setVolumeLookup(std::make_unique<LinearVolumeLookup>(
0243       *world, LinearVolumeLookup::Config{}, *logger));
0244   BOOST_CHECK(output.str().find("scans up to 65 siblings") !=
0245               std::string::npos);
0246 }
0247 
0248 BOOST_AUTO_TEST_CASE(AcceleratorFallbackWarnings) {
0249   // These assertions deliberately exercise warning diagnostics.
0250 #ifdef ACTS_ENABLE_LOG_FAILURE_THRESHOLD
0251   Logging::ScopedFailureThreshold threshold{Logging::ERROR};
0252 #endif
0253   {
0254     std::ostringstream output;
0255     auto logger = getDefaultLogger("LookupTest", Logging::WARNING, &output);
0256     auto world = std::make_shared<TrackingVolume>(
0257         Transform3::Identity(),
0258         std::make_shared<CylinderVolumeBounds>(0., 100., 100.));
0259     auto container = std::make_unique<TrackingVolume>(
0260         Transform3::Identity(),
0261         std::make_shared<CylinderVolumeBounds>(0., 50., 50.));
0262     container->addVolume(std::make_unique<TrackingVolume>(
0263         Transform3::Identity(),
0264         std::make_shared<CuboidVolumeBounds>(2., 2., 2.)));
0265     world->addVolume(std::move(container));
0266     world->addVolume(std::make_unique<TrackingVolume>(
0267         Transform3::Identity(),
0268         std::make_shared<CylinderVolumeBounds>(50., 90., 50.)));
0269     RzGridVolumeLookup::Config cfg;
0270     cfg.fallbackWarningFraction = 0.3;
0271     RzGridVolumeLookup warns(gctx, *world, cfg, *logger);
0272     BOOST_CHECK(output.str().find("1/3 volumes in the indexed scope") !=
0273                 std::string::npos);
0274     BOOST_CHECK(output.str().find("unsupported shape/orientation=1") !=
0275                 std::string::npos);
0276     output.str("");
0277     cfg.fallbackWarningFraction = 1. / 3.;
0278     RzGridVolumeLookup equal(gctx, *world, cfg, *logger);
0279     BOOST_CHECK(output.str().empty());
0280     cfg.fallbackWarningFraction = 1.;
0281     RzGridVolumeLookup suppressed(gctx, world->volumes().at(0), cfg, *logger);
0282     BOOST_CHECK(output.str().empty());
0283 
0284     MovingPlacement placement;
0285     auto movingWorld = std::make_shared<TrackingVolume>(
0286         Transform3::Identity(),
0287         std::make_shared<CylinderVolumeBounds>(0., 100., 100.));
0288     movingWorld->addVolume(std::make_unique<TrackingVolume>(
0289         placement, std::make_shared<CylinderVolumeBounds>(0., 10., 10.)));
0290     cfg.fallbackWarningFraction = 0.;
0291     RzGridVolumeLookup moving(gctx, *movingWorld, cfg, *logger);
0292     BOOST_CHECK(output.str().find("1/1 volumes in the indexed scope") !=
0293                 std::string::npos);
0294     BOOST_CHECK(output.str().find("alignable=1") != std::string::npos);
0295     output.str("");
0296     {
0297       cfg.maxGridCells = 1;
0298       auto largeWorld = makeWorld();
0299       RzGridVolumeLookup limited(gctx, *largeWorld, cfg, *logger);
0300       BOOST_CHECK(output.str().find("grid cell limit=1") != std::string::npos);
0301     }
0302     for (double invalid :
0303          {-0.1, 1.1, std::numeric_limits<double>::quiet_NaN()}) {
0304       cfg.fallbackWarningFraction = invalid;
0305       BOOST_CHECK_THROW(RzGridVolumeLookup(gctx, *world, cfg, *logger),
0306                         std::invalid_argument);
0307     }
0308   }
0309 }
0310 
0311 BOOST_AUTO_TEST_CASE(BlueprintLookupWarningSettings) {
0312   // These assertions deliberately exercise warning diagnostics.
0313 #ifdef ACTS_ENABLE_LOG_FAILURE_THRESHOLD
0314   Logging::ScopedFailureThreshold threshold{Logging::ERROR};
0315 #endif
0316   std::ostringstream output;
0317   auto logger = getDefaultLogger("BlueprintTest", Logging::WARNING, &output);
0318   for (int type : {0, 1}) {
0319     Blueprint blueprint{Blueprint::Config{}};
0320     blueprint.addChild(
0321         std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0322             Transform3::Identity(),
0323             std::make_shared<CuboidVolumeBounds>(10., 10., 10.), "cube")));
0324     BlueprintOptions options;
0325     blueprint.setVolumeLookupFactory(
0326         [type](const GeometryContext& context, const TrackingVolume& volume,
0327                const Logger& factoryLogger) -> std::unique_ptr<IVolumeLookup> {
0328           if (type == 0) {
0329             return std::make_unique<LinearVolumeLookup>(
0330                 volume, LinearVolumeLookup::Config{std::nullopt},
0331                 factoryLogger);
0332           }
0333           RzGridVolumeLookup::Config config;
0334           config.fallbackWarningFraction = 1;
0335           return std::make_unique<RzGridVolumeLookup>(context, volume, config,
0336                                                       factoryLogger);
0337         });
0338     auto geometry = blueprint.construct(options, gctx, *logger);
0339     BOOST_CHECK(output.str().find("volume lookup") == std::string::npos);
0340     if (auto* linear = dynamic_cast<const LinearVolumeLookup*>(
0341             geometry->highestTrackingVolume()->volumeLookup())) {
0342       BOOST_CHECK(!linear->config().warningThreshold.has_value());
0343     }
0344     if (auto* grid = dynamic_cast<const RzGridVolumeLookup*>(
0345             geometry->highestTrackingVolume()->volumeLookup())) {
0346       BOOST_CHECK_EQUAL(grid->config().fallbackWarningFraction, 1);
0347     }
0348   }
0349 }
0350 
0351 BOOST_AUTO_TEST_CASE(RecursiveLookupUsesEachVolumesPolicy) {
0352   class CountingLookup final : public IVolumeLookup {
0353    public:
0354     CountingLookup(const TrackingVolume& root, unsigned& calls)
0355         : m_calls(calls), m_linear(root, {}) {}
0356     const TrackingVolume* findLowest(const GeometryContext& context,
0357                                      const Vector3& position,
0358                                      double tolerance) const override {
0359       ++m_calls;
0360       return m_linear.findLowest(context, position, tolerance);
0361     }
0362 
0363    private:
0364     unsigned& m_calls;
0365     LinearVolumeLookup m_linear;
0366   };
0367   {
0368     auto world = makeWorld();
0369     unsigned calls = 0;
0370     auto& child = world->volumes().at(1);
0371     child.setVolumeLookup(std::make_unique<CountingLookup>(child, calls));
0372     TrackingGeometry geometry(world, nullptr, {}, getDummyLogger(), false);
0373     BOOST_CHECK(*geometry.resolveLowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0374                 &child);
0375     BOOST_CHECK_EQUAL(calls, 1);
0376     BOOST_CHECK(world->lowestTrackingVolume(gctx, {9.5, 0., 0.}) == &child);
0377     BOOST_CHECK_EQUAL(calls, 2);
0378     world->setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0379         gctx, *world, RzGridVolumeLookup::Config{}));
0380     BOOST_CHECK(world->lowestTrackingVolume(gctx, {9.5, 0., 0.}) == &child);
0381     BOOST_CHECK_EQUAL(calls, 3);
0382     // An unsupported sibling is checked linearly alongside indexed candidates.
0383     // The chosen child's policy must still run. Reset only the parent, leaving
0384     // the child intact.
0385     world->addVolume(std::make_unique<TrackingVolume>(
0386         Transform3::Identity(),
0387         std::make_shared<CuboidVolumeBounds>(1., 1., 1.)));
0388     world->setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0389         gctx, *world, RzGridVolumeLookup::Config{}, getDummyLogger()));
0390     BOOST_CHECK(*geometry.resolveLowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0391                 &child);
0392     BOOST_CHECK_EQUAL(calls, 4);
0393     world->setVolumeLookup(nullptr);
0394     BOOST_CHECK(world->lowestTrackingVolume(gctx, {9.5, 0., 0.}) == &child);
0395     BOOST_CHECK_EQUAL(calls, 5);
0396     child.setVolumeLookup(nullptr);
0397     BOOST_CHECK(child.volumeLookup() == nullptr);
0398   }
0399 }
0400 
0401 BOOST_AUTO_TEST_CASE(LookupBindsToFinalVolumeAddress) {
0402   auto world = makeWorld();
0403   TrackingVolume moved(std::move(*world));
0404   moved.setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0405       gctx, moved, RzGridVolumeLookup::Config{}));
0406   BOOST_CHECK(moved.lowestTrackingVolume(gctx, {90., 0., 0.}) == &moved);
0407   BOOST_CHECK(moved.lowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0408               &moved.volumes().at(1));
0409   moved.setVolumeLookup(std::make_unique<LinearVolumeLookup>(
0410       moved, LinearVolumeLookup::Config{}));
0411   BOOST_CHECK(moved.lowestTrackingVolume(gctx, {90., 0., 0.}) == &moved);
0412   BOOST_CHECK(moved.lowestTrackingVolume(gctx, {9.5, 0., 0.}) ==
0413               &moved.volumes().at(1));
0414 }
0415 
0416 BOOST_AUTO_TEST_CASE(BlueprintLocalLookupFactories) {
0417   Blueprint::Config config;
0418   config.envelope[AxisDirection::AxisR] = {1., 1.};
0419   config.envelope[AxisDirection::AxisZ] = {1., 1.};
0420   Blueprint blueprint{config};
0421   auto region =
0422       std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0423           Transform3::Identity(),
0424           std::make_shared<CylinderVolumeBounds>(0., 20., 20.), "region"));
0425   region->addChild(
0426       std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0427           Transform3::Identity(),
0428           std::make_shared<CylinderVolumeBounds>(0., 10., 10.), "child")));
0429   blueprint.addChild(region);
0430   std::vector<std::string> calls;
0431   auto logger = getDefaultLogger("FactoryTest", Logging::WARNING);
0432   region->setVolumeLookupFactory([&](const GeometryContext& context,
0433                                      const TrackingVolume& volume,
0434                                      const Logger& passedLogger) {
0435     BOOST_CHECK(&context == &gctx);
0436     BOOST_CHECK(&passedLogger == logger.get());
0437     BOOST_CHECK_EQUAL(volume.volumes().size(), 1);
0438     BOOST_CHECK(volume.volumes().at(0).volumeLookup() == nullptr);
0439     calls.push_back(volume.volumeName());
0440     return std::make_unique<LinearVolumeLookup>(
0441         volume, LinearVolumeLookup::Config{}, passedLogger);
0442   });
0443   blueprint.setVolumeLookupFactory([&](const GeometryContext& context,
0444                                        const TrackingVolume& volume,
0445                                        const Logger& passedLogger) {
0446     BOOST_CHECK(&context == &gctx);
0447     BOOST_CHECK(&passedLogger == logger.get());
0448     BOOST_CHECK(dynamic_cast<const LinearVolumeLookup*>(
0449                     volume.volumes().at(0).volumeLookup()) != nullptr);
0450     calls.push_back(volume.volumeName());
0451     return std::make_unique<RzGridVolumeLookup>(
0452         context, volume, RzGridVolumeLookup::Config{}, passedLogger);
0453   });
0454   auto geometry = blueprint.construct(BlueprintOptions{}, gctx, *logger);
0455   BOOST_REQUIRE_EQUAL(calls.size(), 2);
0456   BOOST_CHECK_EQUAL(calls[0], "region");
0457   BOOST_CHECK_EQUAL(calls[1], geometry->highestTrackingVolume()->volumeName());
0458   const auto* result =
0459       *geometry->resolveLowestTrackingVolume(gctx, {5., 0., 0.});
0460   BOOST_REQUIRE(result != nullptr);
0461   BOOST_CHECK_EQUAL(result->volumeName(), "child");
0462 }
0463 
0464 BOOST_AUTO_TEST_CASE(BlueprintRejectsNullLookupFactoryResult) {
0465   for (bool onStatic : {false, true}) {
0466     Blueprint blueprint{Blueprint::Config{}};
0467     auto child =
0468         std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0469             Transform3::Identity(),
0470             std::make_shared<CylinderVolumeBounds>(0., 10., 10.), "child"));
0471     blueprint.addChild(child);
0472     VolumeLookupFactory factory = [](const GeometryContext&,
0473                                      const TrackingVolume&, const Logger&) {
0474       return std::unique_ptr<IVolumeLookup>{};
0475     };
0476     if (onStatic) {
0477       child->setVolumeLookupFactory(factory);
0478     } else {
0479       blueprint.setVolumeLookupFactory(factory);
0480     }
0481     BOOST_CHECK_THROW(blueprint.construct(BlueprintOptions{}, gctx),
0482                       std::invalid_argument);
0483   }
0484 }
0485 
0486 BOOST_AUTO_TEST_CASE(BlueprintDefaultLookupIsImplicit) {
0487   Blueprint blueprint{Blueprint::Config{}};
0488   blueprint.addChild(
0489       std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0490           Transform3::Identity(),
0491           std::make_shared<CylinderVolumeBounds>(0., 10., 10.), "child")));
0492   auto geometry = blueprint.construct(BlueprintOptions{}, gctx);
0493   geometry->highestTrackingVolume()->apply([](TrackingVolume& volume) {
0494     BOOST_CHECK(volume.volumeLookup() == nullptr);
0495   });
0496   BOOST_CHECK(*geometry->resolveLowestTrackingVolume(gctx, {5., 0., 0.}) !=
0497               nullptr);
0498 }
0499 
0500 BOOST_AUTO_TEST_CASE(SubtreeLookupPreservesSiblingPriority) {
0501   auto makeVolume = [](double lo, double hi, const std::string& name) {
0502     return std::make_unique<TrackingVolume>(
0503         Transform3::Identity(),
0504         std::make_shared<CylinderVolumeBounds>(lo, hi, 50.), name);
0505   };
0506   auto world = makeVolume(0., 100., "world");
0507   auto first = makeVolume(0., 20., "first");
0508   auto inner = makeVolume(5., 15., "inner");
0509   inner->addVolume(makeVolume(5., 10., "leaf"));
0510   first->addVolume(std::move(inner));
0511   world->addVolume(std::move(first));
0512   auto second = makeVolume(0., 50., "second");
0513   second->addVolume(makeVolume(10., 14., "later-deeper"));
0514   world->addVolume(std::move(second));
0515   const std::vector<std::pair<double, std::string>> points{{7., "leaf"},
0516                                                            {12., "inner"},
0517                                                            {18., "first"},
0518                                                            {30., "second"},
0519                                                            {80., "world"}};
0520   {
0521     world->setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0522         gctx, *world, RzGridVolumeLookup::Config{}));
0523     world->visitVolumes([&](const TrackingVolume* volume) {
0524       if (volume != world.get()) {
0525         BOOST_CHECK(volume->volumeLookup() == nullptr);
0526       }
0527     });
0528     for (const auto& [radius, name] : points) {
0529       const auto* found = world->lowestTrackingVolume(gctx, {radius, 0., 0.});
0530       BOOST_REQUIRE(found != nullptr);
0531       BOOST_CHECK_EQUAL(found->volumeName(), name);
0532     }
0533     BOOST_CHECK(world->lowestTrackingVolume(gctx, {120., 0., 0.}) == nullptr);
0534   }
0535 }
0536 
0537 BOOST_AUTO_TEST_CASE(SubtreeLookupDelegatesThroughLinearToAnotherIndex) {
0538   class CountingLinear final : public IVolumeLookup {
0539    public:
0540     CountingLinear(const TrackingVolume& root, unsigned& count)
0541         : m_count(count), m_linear(root, {}) {}
0542     const TrackingVolume* findLowest(const GeometryContext& context,
0543                                      const Vector3& position,
0544                                      double tolerance) const override {
0545       ++m_count;
0546       return m_linear.findLowest(context, position, tolerance);
0547     }
0548 
0549    private:
0550     unsigned& m_count;
0551     LinearVolumeLookup m_linear;
0552   };
0553   auto makeVolume = [](double lo, double hi) {
0554     return std::make_unique<TrackingVolume>(
0555         Transform3::Identity(),
0556         std::make_shared<CylinderVolumeBounds>(lo, hi, 50.));
0557   };
0558   auto world = makeVolume(0., 100.);
0559   auto region = makeVolume(0., 50.);
0560   auto nested = makeVolume(0., 30.);
0561   auto leaf = makeVolume(10., 20.);
0562   const auto* expected = leaf.get();
0563   nested->addVolume(std::move(leaf));
0564   nested->setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0565       gctx, *nested, RzGridVolumeLookup::Config{}));
0566   region->addVolume(std::move(nested));
0567   unsigned calls = 0;
0568   region->setVolumeLookup(std::make_unique<CountingLinear>(*region, calls));
0569   world->addVolume(std::move(region));
0570   {
0571     std::ostringstream output;
0572     auto logger = getDefaultLogger("DelegationTest", Logging::WARNING, &output);
0573     RzGridVolumeLookup::Config config;
0574     {
0575       // One boundary entry fits in one regular cell (nine with reserved
0576       // under-/overflow slots). Indexing through it would exceed
0577       // this cap and warn, even if fallback happened to return the same result.
0578       config.maxGridCells = 9;
0579     }
0580     world->setVolumeLookup(
0581         std::make_unique<RzGridVolumeLookup>(gctx, *world, config, *logger));
0582     BOOST_CHECK(output.str().empty());
0583     const auto before = calls;
0584     BOOST_CHECK(world->lowestTrackingVolume(gctx, {15., 0., 0.}) == expected);
0585     BOOST_CHECK_EQUAL(calls, before + 1);
0586     BOOST_CHECK(world->lowestTrackingVolume(gctx, {40., 0., 0.}) ==
0587                 &world->volumes().at(0));
0588     BOOST_CHECK_EQUAL(calls, before + 2);
0589     BOOST_CHECK(world->lowestTrackingVolume(gctx, {70., 0., 0.}) ==
0590                 world.get());
0591     BOOST_CHECK_EQUAL(calls, before + 2);
0592   }
0593 }
0594 
0595 BOOST_AUTO_TEST_CASE(StaticNodeCanAccelerateWithoutWorldPolicy) {
0596   Blueprint blueprint{Blueprint::Config{}};
0597   auto region =
0598       std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0599           Transform3::Identity(),
0600           std::make_shared<CylinderVolumeBounds>(0., 20., 20.), "region"));
0601   region->addChild(
0602       std::make_shared<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0603           Transform3::Identity(),
0604           std::make_shared<CylinderVolumeBounds>(0., 10., 10.), "leaf")));
0605   region->setVolumeLookupFactory([](const GeometryContext& context,
0606                                     const TrackingVolume& volume,
0607                                     const Logger& logger) {
0608     return std::make_unique<RzGridVolumeLookup>(
0609         context, volume, RzGridVolumeLookup::Config{}, logger);
0610   });
0611   blueprint.addChild(region);
0612   auto geometry = blueprint.construct(BlueprintOptions{}, gctx);
0613   BOOST_CHECK(geometry->highestTrackingVolume()->volumeLookup() == nullptr);
0614   const auto& builtRegion = geometry->highestTrackingVolume()->volumes().at(0);
0615   BOOST_CHECK(dynamic_cast<const RzGridVolumeLookup*>(
0616                   builtRegion.volumeLookup()) != nullptr);
0617   BOOST_CHECK(builtRegion.volumes().at(0).volumeLookup() == nullptr);
0618   const auto* found =
0619       *geometry->resolveLowestTrackingVolume(gctx, {5., 0., 0.});
0620   BOOST_REQUIRE(found != nullptr);
0621   BOOST_CHECK_EQUAL(found->volumeName(), "leaf");
0622 }
0623 
0624 BOOST_AUTO_TEST_CASE(LinearDiagnosticsRespectSubtreeBoundaries) {
0625   // These assertions deliberately exercise warning diagnostics.
0626 #ifdef ACTS_ENABLE_LOG_FAILURE_THRESHOLD
0627   Logging::ScopedFailureThreshold threshold{Logging::ERROR};
0628 #endif
0629   auto world = std::make_unique<TrackingVolume>(
0630       Transform3::Identity(),
0631       std::make_shared<CylinderVolumeBounds>(0., 100., 100.));
0632   auto region = std::make_unique<TrackingVolume>(
0633       Transform3::Identity(),
0634       std::make_shared<CylinderVolumeBounds>(0., 50., 50.));
0635   for (int i = 0; i < 65; ++i) {
0636     region->addVolume(std::make_unique<TrackingVolume>(
0637         Transform3::Identity(),
0638         std::make_shared<CylinderVolumeBounds>(0., 10., 10.)));
0639   }
0640   auto* regionPtr = region.get();
0641   world->addVolume(std::move(region));
0642   std::ostringstream output;
0643   auto logger = getDefaultLogger("LinearScopeTest", Logging::WARNING, &output);
0644   LinearVolumeLookup scanned(*world, LinearVolumeLookup::Config{}, *logger);
0645   BOOST_CHECK(output.str().find("scans up to 65 siblings") !=
0646               std::string::npos);
0647   output.str("");
0648   regionPtr->setVolumeLookup(std::make_unique<RzGridVolumeLookup>(
0649       gctx, *regionPtr, RzGridVolumeLookup::Config{}));
0650   LinearVolumeLookup delegated(*world, LinearVolumeLookup::Config{}, *logger);
0651   BOOST_CHECK(output.str().empty());
0652 }
0653 
0654 BOOST_AUTO_TEST_CASE(RzLookupCoincidentBounds) {
0655   auto world = std::make_shared<TrackingVolume>(
0656       Transform3::Identity(),
0657       std::make_shared<CylinderVolumeBounds>(0., 100., 100.));
0658   // Coincident rectangles share a cell and must retain sibling priority.
0659   for (int i = 0; i < 200; ++i) {
0660     world->addVolume(std::make_unique<TrackingVolume>(
0661         Transform3::Identity(),
0662         std::make_shared<CylinderVolumeBounds>(0., 10., 10.)));
0663   }
0664   {
0665     RzGridVolumeLookup lookup(gctx, *world, RzGridVolumeLookup::Config{});
0666     BOOST_CHECK(lookup.findLowest(gctx, {5., 0., 0.}, 0.) ==
0667                 &world->volumes().at(0));
0668     BOOST_CHECK(lookup.findLowest(gctx, {15., 0., 0.}, 0.) == world.get());
0669   }
0670 }
0671 
0672 BOOST_AUTO_TEST_CASE(RzLookupsRejectGen1Storage) {
0673   auto makeGen1 = [] {
0674     auto dense = std::make_shared<TrackingVolume>(
0675         Transform3::Identity(),
0676         std::make_shared<CylinderVolumeBounds>(0., 5., 5.));
0677     return std::make_unique<TrackingVolume>(
0678         Transform3::Identity(),
0679         std::make_shared<CylinderVolumeBounds>(0., 20., 20.), nullptr, nullptr,
0680         nullptr, MutableTrackingVolumeVector{dense}, "gen1");
0681   };
0682   {
0683     auto gen1 = makeGen1();
0684     BOOST_CHECK_THROW(
0685         (RzGridVolumeLookup(gctx, *gen1, RzGridVolumeLookup::Config{})),
0686         std::invalid_argument);
0687     // Implicit and explicit linear lookup still retain Gen1 semantics.
0688     const Vector3 point(2., 0., 0.);
0689     const auto* dense = gen1->denseVolumes().front().get();
0690     BOOST_CHECK(gen1->lowestTrackingVolume(gctx, point) == dense);
0691     gen1->setVolumeLookup(std::make_unique<LinearVolumeLookup>(
0692         *gen1, LinearVolumeLookup::Config{}));
0693     BOOST_CHECK(gen1->lowestTrackingVolume(gctx, point) == dense);
0694 
0695     auto world = makeWorld();
0696     auto* gen1Ptr = gen1.get();
0697     world->addVolume(std::move(gen1));
0698     // Also reject Gen1 storage at a descendant, even at a lookup boundary.
0699     BOOST_CHECK_THROW(
0700         (RzGridVolumeLookup(gctx, *world, RzGridVolumeLookup::Config{})),
0701         std::invalid_argument);
0702     gen1Ptr->setVolumeLookup(nullptr);
0703     BOOST_CHECK_THROW(
0704         (RzGridVolumeLookup(gctx, *world, RzGridVolumeLookup::Config{})),
0705         std::invalid_argument);
0706   }
0707 }
0708 
0709 BOOST_AUTO_TEST_CASE(RzGridNearIdentityRotation) {
0710   auto world = std::make_shared<TrackingVolume>(
0711       Transform3::Identity(),
0712       std::make_shared<CylinderVolumeBounds>(0., 100., 2000.));
0713   Transform3 transform = Transform3::Identity();
0714   transform.linear() = AngleAxis3(5e-13, Vector3::UnitY()).toRotationMatrix();
0715   auto child = std::make_unique<TrackingVolume>(
0716       transform, std::make_shared<CylinderVolumeBounds>(5., 20., 1000.));
0717   const auto* expected = child.get();
0718   world->addVolume(std::move(child));
0719   std::ostringstream output;
0720   auto logger = getDefaultLogger("RotationTest", Logging::WARNING, &output);
0721   RzGridVolumeLookup::Config config;
0722   config.fallbackWarningFraction = 0.;
0723   RzGridVolumeLookup lookup(gctx, *world, config, *logger);
0724   BOOST_CHECK(output.str().empty());
0725   for (double z : {-1000., 0., 1000.}) {
0726     for (double r : {5., 20.}) {
0727       const Vector3 point = transform * Vector3(r, 0., z);
0728       BOOST_CHECK(lookup.findLowest(gctx, point, 1e-6) == expected);
0729     }
0730   }
0731 }