Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-28 07:46:35

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/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Direction.hpp"
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Geometry/Blueprint.hpp"
0015 #include "Acts/Geometry/BlueprintOptions.hpp"
0016 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Geometry/NavigationPolicyFactory.hpp"
0019 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0020 #include "Acts/Geometry/TrackingGeometry.hpp"
0021 #include "Acts/Geometry/TrackingVolume.hpp"
0022 #include "Acts/MagneticField/ConstantBField.hpp"
0023 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0024 #include "Acts/Navigation/INavigationPolicy.hpp"
0025 #include "Acts/Navigation/NavigationStream.hpp"
0026 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0027 #include "Acts/Propagator/ActorList.hpp"
0028 #include "Acts/Propagator/EigenStepper.hpp"
0029 #include "Acts/Propagator/NavigationTarget.hpp"
0030 #include "Acts/Propagator/Navigator.hpp"
0031 #include "Acts/Propagator/Propagator.hpp"
0032 #include "Acts/Surfaces/Surface.hpp"
0033 #include "Acts/Utilities/Logger.hpp"
0034 #include "Acts/Utilities/Result.hpp"
0035 #include "Acts/Visualization/ObjVisualization3D.hpp"
0036 #include "ActsTests/CommonHelpers/TemporaryDirectory.hpp"
0037 
0038 #include <atomic>
0039 #include <cmath>
0040 #include <cstdlib>
0041 #include <format>
0042 #include <memory>
0043 #include <numbers>
0044 #include <string>
0045 #include <vector>
0046 
0047 using namespace Acts;
0048 using namespace Acts::UnitLiterals;
0049 using Experimental::Blueprint;
0050 using Experimental::BlueprintOptions;
0051 using Experimental::StaticBlueprintNode;
0052 
0053 namespace ActsTests {
0054 
0055 GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0056 MagneticFieldContext mfContext = MagneticFieldContext();
0057 
0058 void step(Vector3& pos, const Vector3& dir, const Surface& surface) {
0059   Intersection3D intersection =
0060       surface.intersect(gctx, pos, dir).closestForward();
0061   pos += intersection.pathLength() * dir;
0062 }
0063 
0064 void step(Vector3& pos, const Vector3& dir, const NavigationTarget& target) {
0065   step(pos, dir, target.surface());
0066 }
0067 
0068 // ---------------------------------------------------------------------------
0069 // Custom Policy 1: AlternatingInvalidPolicy
0070 //
0071 // Returns isValid()=false every other call. Does NOT add navigation
0072 // candidates -- composed with TryAllNavigationPolicy via the factory.
0073 // ---------------------------------------------------------------------------
0074 
0075 class AlternatingInvalidPolicy final : public INavigationPolicy {
0076  public:
0077   struct State {
0078     int counter = 0;
0079   };
0080 
0081   AlternatingInvalidPolicy(const GeometryContext& /*gctx*/,
0082                            const TrackingVolume& /*volume*/,
0083                            const Logger& /*logger*/) {}
0084 
0085   void initializeCandidates(const GeometryContext& /*gctx*/,
0086                             const NavigationArguments& /*args*/,
0087                             NavigationPolicyState& /*state*/,
0088                             AppendOnlyNavigationStream& /*stream*/,
0089                             const Logger& /*logger*/) const {
0090     // No-op: TryAllNavigationPolicy provides the candidates.
0091   }
0092 
0093   void connect(NavigationDelegate& delegate) const override {
0094     connectDefault<AlternatingInvalidPolicy>(delegate);
0095   }
0096 
0097   bool isValid(const GeometryContext& /*gctx*/,
0098                const NavigationArguments& /*args*/,
0099                NavigationPolicyState& state,
0100                const Logger& logger) const override {
0101     auto& s = state.as<State>();
0102     s.counter++;
0103     bool valid = (s.counter % 2 == 0);
0104     ACTS_VERBOSE("AlternatingInvalidPolicy isValid: counter="
0105                  << s.counter << " valid=" << valid);
0106     return valid;
0107   }
0108 
0109   void createState(const GeometryContext& /*gctx*/,
0110                    const NavigationArguments& /*args*/,
0111                    NavigationPolicyStateManager& stateManager,
0112                    const Logger& logger) const override {
0113     ACTS_VERBOSE("AlternatingInvalidPolicy createState");
0114     stateManager.pushState<State>();
0115   }
0116 
0117   void popState(NavigationPolicyStateManager& stateManager,
0118                 const Logger& logger) const override {
0119     ACTS_VERBOSE("AlternatingInvalidPolicy popState");
0120     stateManager.popState();
0121   }
0122 };
0123 
0124 static_assert(NavigationPolicyConcept<AlternatingInvalidPolicy>);
0125 
0126 // ---------------------------------------------------------------------------
0127 // Custom Policy 2: ConeValidityPolicy
0128 //
0129 // Stores the direction at createState time. isValid returns false when
0130 // the current direction deviates from the stored direction by more than
0131 // a configurable cone angle (dot-product check).
0132 // ---------------------------------------------------------------------------
0133 
0134 class ConeValidityPolicy final : public INavigationPolicy {
0135  public:
0136   struct State {
0137     Vector3 initialDirection = Vector3::Zero();
0138   };
0139 
0140   ConeValidityPolicy(const GeometryContext& /*gctx*/,
0141                      const TrackingVolume& /*volume*/, const Logger& /*logger*/,
0142                      double coneAngle,
0143                      std::shared_ptr<std::atomic<int>> reresolutionCounter)
0144       : m_cosConeAngle(std::cos(coneAngle)),
0145         m_reresolutionCounter(std::move(reresolutionCounter)) {}
0146 
0147   void initializeCandidates(const GeometryContext& /*gctx*/,
0148                             const NavigationArguments& args,
0149                             NavigationPolicyState& state,
0150                             AppendOnlyNavigationStream& /*stream*/,
0151                             const Logger& logger) const {
0152     // Re-center the cone on the current direction after re-resolution.
0153     auto& s = state.as<State>();
0154     s.initialDirection = args.direction.normalized();
0155     ACTS_VERBOSE("ConeValidityPolicy initializeCandidates: re-centered to "
0156                  << s.initialDirection.transpose());
0157   }
0158 
0159   void connect(NavigationDelegate& delegate) const override {
0160     connectDefault<ConeValidityPolicy>(delegate);
0161   }
0162 
0163   bool isValid(const GeometryContext& /*gctx*/, const NavigationArguments& args,
0164                NavigationPolicyState& state,
0165                const Logger& logger) const override {
0166     auto& s = state.as<State>();
0167     double dot = s.initialDirection.dot(args.direction.normalized());
0168     bool valid = (dot >= m_cosConeAngle);
0169     if (!valid) {
0170       ++(*m_reresolutionCounter);
0171       ACTS_VERBOSE("ConeValidityPolicy isValid: INVALID dot="
0172                    << dot << " threshold=" << m_cosConeAngle);
0173     } else {
0174       ACTS_VERBOSE("ConeValidityPolicy isValid: VALID dot="
0175                    << dot << " threshold=" << m_cosConeAngle);
0176     }
0177     return valid;
0178   }
0179 
0180   void createState(const GeometryContext& /*gctx*/,
0181                    const NavigationArguments& args,
0182                    NavigationPolicyStateManager& stateManager,
0183                    const Logger& logger) const override {
0184     ACTS_VERBOSE("ConeValidityPolicy createState: direction="
0185                  << args.direction.transpose());
0186     auto& s = stateManager.pushState<State>();
0187     s.initialDirection = args.direction.normalized();
0188   }
0189 
0190   void popState(NavigationPolicyStateManager& stateManager,
0191                 const Logger& logger) const override {
0192     ACTS_VERBOSE("ConeValidityPolicy popState");
0193     stateManager.popState();
0194   }
0195 
0196  private:
0197   double m_cosConeAngle;
0198   std::shared_ptr<std::atomic<int>> m_reresolutionCounter;
0199 };
0200 
0201 static_assert(NavigationPolicyConcept<ConeValidityPolicy>);
0202 
0203 // ---------------------------------------------------------------------------
0204 // StepCollector actor (records trajectory positions for visualization)
0205 // ---------------------------------------------------------------------------
0206 
0207 struct StepCollector {
0208   struct this_result {
0209     std::vector<Vector3> position;
0210     std::vector<const Surface*> surfaces;
0211   };
0212 
0213   using result_type = this_result;
0214 
0215   template <typename propagator_state_t, typename stepper_t,
0216             typename navigator_t>
0217   Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0218                    const navigator_t& /*navigator*/, result_type& result,
0219                    const Logger& /*logger*/) const {
0220     result.position.push_back(stepper.position(state.stepping));
0221     if (state.navigation.currentSurface != nullptr) {
0222       result.surfaces.push_back(state.navigation.currentSurface);
0223     }
0224     return Result<void>::success();
0225   }
0226 
0227   template <typename propagator_state_t, typename stepper_t,
0228             typename navigator_t>
0229   bool checkAbort(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
0230                   const navigator_t& /*navigator*/, result_type& /*result*/,
0231                   const Logger& /*logger*/) const {
0232     return false;
0233   }
0234 };
0235 
0236 // ---------------------------------------------------------------------------
0237 // Geometry builder: outer cuboid with three inner cuboids
0238 // ---------------------------------------------------------------------------
0239 
0240 std::unique_ptr<TrackingGeometry> buildBoxGeometry(const BlueprintOptions& opts,
0241                                                    const Logger& logger) {
0242   Blueprint::Config cfg;
0243   cfg.envelope = ExtentEnvelope{{
0244       .x = {20_mm, 20_mm},
0245       .y = {20_mm, 20_mm},
0246       .z = {20_mm, 20_mm},
0247   }};
0248 
0249   Blueprint root{cfg};
0250 
0251   // Outer parent volume: 2m x 2m x 2m at origin
0252   auto outerBounds = std::make_shared<CuboidVolumeBounds>(1_m, 1_m, 1_m);
0253   auto outerVol = std::make_unique<TrackingVolume>(Transform3::Identity(),
0254                                                    outerBounds, "Outer");
0255 
0256   auto outerNode = std::make_shared<StaticBlueprintNode>(std::move(outerVol));
0257 
0258   // Box1: left side along -X
0259   auto box1Bounds =
0260       std::make_shared<CuboidVolumeBounds>(300_mm, 300_mm, 300_mm);
0261   auto box1Vol = std::make_unique<TrackingVolume>(
0262       Transform3(Translation3(-500_mm, 0, 0)), box1Bounds, "BoxLeft");
0263 
0264   auto box1Node = std::make_shared<StaticBlueprintNode>(std::move(box1Vol));
0265 
0266   // Box2: right side along +X, centered vertically
0267   auto box2Bounds =
0268       std::make_shared<CuboidVolumeBounds>(300_mm, 300_mm, 300_mm);
0269   auto box2Vol = std::make_unique<TrackingVolume>(
0270       Transform3(Translation3(500_mm, 0, 0)), box2Bounds, "BoxRight");
0271 
0272   auto box2Node = std::make_shared<StaticBlueprintNode>(std::move(box2Vol));
0273 
0274   // Box3: above Box2, shifted in +Y
0275   auto box3Bounds =
0276       std::make_shared<CuboidVolumeBounds>(200_mm, 200_mm, 200_mm);
0277   auto box3Vol = std::make_unique<TrackingVolume>(
0278       Transform3(Translation3(500_mm, 600_mm, 0)), box3Bounds, "BoxTop");
0279 
0280   auto box3Node = std::make_shared<StaticBlueprintNode>(std::move(box3Vol));
0281 
0282   outerNode->addChild(box1Node);
0283   outerNode->addChild(box2Node);
0284   outerNode->addChild(box3Node);
0285 
0286   root.addChild(outerNode);
0287 
0288   return root.construct(opts, gctx, logger);
0289 }
0290 
0291 // ---------------------------------------------------------------------------
0292 // Tests
0293 // ---------------------------------------------------------------------------
0294 
0295 BOOST_AUTO_TEST_SUITE(NavigationPolicyStateSuite)
0296 
0297 // ===== Test 1: AlternatingInvalidPolicy with straight-line stepping =========
0298 
0299 BOOST_AUTO_TEST_CASE(AlternatingInvalidPolicy_StraightLine) {
0300   TemporaryDirectory tmp{};
0301   auto logger = getDefaultLogger("AlternatingTest", Logging::VERBOSE);
0302 
0303   // Build geometry with AlternatingInvalidPolicy on all volumes
0304   BlueprintOptions opts;
0305   opts.defaultNavigationPolicyFactory = NavigationPolicyFactory{}
0306                                             .add<TryAllNavigationPolicy>()
0307                                             .add<AlternatingInvalidPolicy>()
0308                                             .asUniquePtr();
0309 
0310   auto trackingGeometry = buildBoxGeometry(opts, *logger);
0311   BOOST_REQUIRE(trackingGeometry);
0312   BOOST_CHECK(trackingGeometry->geometryVersion() ==
0313               TrackingGeometry::GeometryVersion::Gen3);
0314 
0315   // Write OBJ output
0316   ObjVisualization3D vis;
0317   trackingGeometry->visualize(vis, gctx);
0318   vis.write(tmp.path() / "alternating_invalid_policy_geometry.obj");
0319 
0320   // Set up Navigator
0321   auto sharedGeometry =
0322       std::shared_ptr<const TrackingGeometry>(std::move(trackingGeometry));
0323 
0324   Navigator::Config navCfg;
0325   navCfg.trackingGeometry = sharedGeometry;
0326   navCfg.resolveSensitive = true;
0327   navCfg.resolveMaterial = true;
0328   navCfg.resolvePassive = false;
0329   Navigator navigator(navCfg, logger->clone("Navigator"));
0330 
0331   // Start at left side of outer volume, propagate along +X
0332   Navigator::Options options(gctx);
0333   Navigator::State state = navigator.makeState(options);
0334 
0335   Vector3 position{-900_mm, 0, 0};
0336   Vector3 direction = Vector3::UnitX();
0337 
0338   Result<void> result =
0339       navigator.initialize(state, position, direction, Direction::Forward());
0340   BOOST_REQUIRE(result.ok());
0341   BOOST_REQUIRE(state.currentVolume != nullptr);
0342 
0343   // Step through geometry, track visited volumes
0344   std::vector<std::string> visitedVolumes;
0345   visitedVolumes.push_back(state.currentVolume->volumeName());
0346 
0347   int maxSteps = 200;
0348   for (int i = 0; i < maxSteps; ++i) {
0349     NavigationTarget target = navigator.nextTarget(state, position, direction);
0350 
0351     if (target.isNone()) {
0352       break;
0353     }
0354 
0355     step(position, direction, target);
0356     navigator.handleSurfaceReached(state, position, direction,
0357                                    target.surface());
0358 
0359     if (state.currentVolume != nullptr &&
0360         (visitedVolumes.empty() ||
0361          visitedVolumes.back() != state.currentVolume->volumeName())) {
0362       visitedVolumes.push_back(state.currentVolume->volumeName());
0363     }
0364 
0365     if (state.navigationBreak) {
0366       break;
0367     }
0368   }
0369 
0370   // Navigation should have completed (left the world)
0371   BOOST_CHECK(state.navigationBreak || state.currentVolume == nullptr);
0372 
0373   // Should have visited at least: Outer -> BoxLeft -> Outer -> BoxRight ->
0374   // Outer
0375   BOOST_CHECK_GE(visitedVolumes.size(), 3u);
0376 
0377   // Print visited volumes for diagnostics
0378   for (const auto& name : visitedVolumes) {
0379     BOOST_TEST_MESSAGE("Visited: " << name);
0380   }
0381 }
0382 
0383 // ===== Test 2: ConeValidityPolicy with magnetic field ======================
0384 
0385 BOOST_AUTO_TEST_CASE(ConeValidityPolicy_MagneticField) {
0386   TemporaryDirectory tmp{};
0387   auto logger = getDefaultLogger("ConeTest", Logging::VERBOSE);
0388 
0389   const double coneAngle = 10.0 * std::numbers::pi / 180.0;
0390 
0391   using EigenStepperType = EigenStepper<>;
0392   using EigenPropagatorType = Propagator<EigenStepperType, Navigator>;
0393 
0394   // Helper lambda to run a single sub-case
0395   auto runSubCase = [&](double bFieldZ, const std::string& label) -> int {
0396     auto counter = std::make_shared<std::atomic<int>>(0);
0397 
0398     BlueprintOptions opts;
0399     opts.defaultNavigationPolicyFactory =
0400         NavigationPolicyFactory{}
0401             .add<TryAllNavigationPolicy>()
0402             .add<ConeValidityPolicy>(coneAngle, counter)
0403             .asUniquePtr();
0404 
0405     auto trackingGeometry = buildBoxGeometry(opts, *logger);
0406     BOOST_REQUIRE(trackingGeometry);
0407 
0408     ObjVisualization3D vis;
0409     trackingGeometry->visualize(vis, gctx);
0410     vis.write(tmp.path() / "cone_validity_geometry.obj");
0411 
0412     auto sharedGeometry =
0413         std::shared_ptr<const TrackingGeometry>(std::move(trackingGeometry));
0414 
0415     auto bField = std::make_shared<ConstantBField>(Vector3{0, 0, bFieldZ});
0416     EigenStepperType stepper(bField);
0417 
0418     Navigator::Config navCfg;
0419     navCfg.trackingGeometry = sharedGeometry;
0420     navCfg.resolveSensitive = true;
0421     navCfg.resolveMaterial = true;
0422     navCfg.resolvePassive = false;
0423     Navigator navigator(navCfg, logger->clone("Navigator"));
0424 
0425     EigenPropagatorType propagator(std::move(stepper), std::move(navigator),
0426                                    logger->clone("Propagator"));
0427 
0428     // 1 GeV pion starting at left side, direction +X
0429     Vector3 startPos{-900_mm, 0, 0};
0430     Vector3 startDir = Vector3::UnitX();
0431 
0432     BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0433         Vector4(startPos.x(), startPos.y(), startPos.z(), 0), startDir,
0434         1.0 / 1_GeV, std::nullopt, ParticleHypothesis::pion());
0435 
0436     using MyActorList = ActorList<StepCollector, EndOfWorldReached>;
0437     EigenPropagatorType::Options<MyActorList> propOpts(gctx, mfContext);
0438     propOpts.pathLimit = 5_m;
0439     propOpts.stepping.maxStepSize = 100_mm;
0440 
0441     auto result = propagator.propagate(start, propOpts);
0442     BOOST_REQUIRE(result.ok());
0443 
0444     const auto& info =
0445         result.value().template get<StepCollector::this_result>();
0446 
0447     const auto& positions = info.position;
0448     if (!positions.empty()) {
0449       ObjVisualization3D trajVis;
0450       for (std::size_t i = 0; i + 1 < positions.size(); ++i) {
0451         trajVis.line(positions[i], positions[i + 1]);
0452       }
0453       trajVis.write(tmp.path() / std::format("trajectory_{}.obj", label));
0454     }
0455 
0456     ObjVisualization3D srfVis;
0457     if (!info.surfaces.empty()) {
0458       for (const auto* srf : info.surfaces) {
0459         std::cout << srf->toString(gctx) << std::endl;
0460         if (srf->type() != Surface::Plane ||
0461             srf->bounds().type() != SurfaceBounds::eRectangle) {
0462           continue;
0463         }
0464         srf->visualize(srfVis, gctx);
0465       }
0466       srfVis.write(tmp.path() / std::format("surfaces_{}.obj", label));
0467     }
0468 
0469     return counter->load();
0470   };
0471 
0472   // Sub-case a: strong field
0473   int strongReresolutions = runSubCase(-1.6_T, "strong");
0474   BOOST_TEST_MESSAGE("Strong field re-resolutions: " << strongReresolutions);
0475   BOOST_CHECK_GT(strongReresolutions, 0);
0476 
0477   // Sub-case b: weak field
0478   int weakReresolutions = runSubCase(0.001_T, "weak");
0479   BOOST_TEST_MESSAGE("Weak field re-resolutions: " << weakReresolutions);
0480 
0481   // Strong field must cause more re-resolutions than weak field
0482   BOOST_CHECK_GT(strongReresolutions, weakReresolutions);
0483 }
0484 
0485 BOOST_AUTO_TEST_SUITE_END()
0486 
0487 }  // namespace ActsTests