Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:07: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 #pragma once
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/Layer.hpp"
0013 #include "Acts/Geometry/TrackingGeometry.hpp"
0014 #include "Acts/Geometry/TrackingVolume.hpp"
0015 #include "Acts/Propagator/NavigationTarget.hpp"
0016 #include "Acts/Propagator/NavigatorError.hpp"
0017 #include "Acts/Propagator/NavigatorOptions.hpp"
0018 #include "Acts/Propagator/NavigatorStatistics.hpp"
0019 #include "Acts/Propagator/detail/NavigationHelpers.hpp"
0020 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0021 #include "Acts/Surfaces/Surface.hpp"
0022 #include "Acts/Utilities/Enumerate.hpp"
0023 #include "Acts/Utilities/Intersection.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025 #include "Acts/Utilities/StringHelpers.hpp"
0026 
0027 #include <algorithm>
0028 #include <cstdint>
0029 #include <limits>
0030 #include <memory>
0031 #include <vector>
0032 
0033 namespace Acts {
0034 
0035 /// @brief Captures the common functionality of the try-all navigators
0036 ///
0037 /// This class is not meant to be used directly, but to be inherited by the
0038 /// actual navigator implementations.
0039 ///
0040 class TryAllNavigatorBase {
0041  public:
0042   /// @brief Configuration for this Navigator
0043   struct Config {
0044     /// Tracking Geometry for this Navigator
0045     std::shared_ptr<const TrackingGeometry> trackingGeometry;
0046 
0047     /// stop at every sensitive surface (whether it has material or not)
0048     bool resolveSensitive = true;
0049     /// stop at every material surface (whether it is passive or not)
0050     bool resolveMaterial = true;
0051     /// stop at every surface regardless what it is
0052     bool resolvePassive = false;
0053 
0054     /// Which boundary checks to perform for surface approach
0055     BoundaryTolerance boundaryToleranceSurfaceApproach =
0056         BoundaryTolerance::None();
0057   };
0058 
0059   /// @brief Options for this Navigator
0060   struct Options : public NavigatorPlainOptions {
0061     /// @brief Constructor with geometry context
0062     /// @param gctx The geometry context for this navigator instance
0063     explicit Options(const GeometryContext& gctx)
0064         : NavigatorPlainOptions(gctx) {}
0065 
0066     /// The surface tolerance
0067     double surfaceTolerance = s_onSurfaceTolerance;
0068 
0069     /// The near limit to resolve surfaces
0070     double nearLimit = s_onSurfaceTolerance;
0071 
0072     /// The far limit to resolve surfaces
0073     double farLimit = std::numeric_limits<double>::max();
0074 
0075     /// @brief Set plain options from NavigatorPlainOptions
0076     /// @param options The plain options to copy
0077     void setPlainOptions(const NavigatorPlainOptions& options) {
0078       static_cast<NavigatorPlainOptions&>(*this) = options;
0079     }
0080   };
0081 
0082   /// @brief Nested State struct
0083   ///
0084   /// It acts as an internal state which is created for every propagation and
0085   /// meant to keep thread-local navigation information.
0086   struct State {
0087     /// @brief Constructor with options
0088     /// @param options_ The navigator options for this state
0089     explicit State(const Options& options_) : options(options_) {}
0090 
0091     /// Navigation options containing configuration for this propagation
0092     Options options;
0093 
0094     // Starting geometry information of the navigation which should only be set
0095     // while initialization. NOTE: This information is mostly used by actors to
0096     // check if we are on the starting surface (e.g. MaterialInteraction).
0097     /// Surface where the propagation started
0098     const Surface* startSurface = nullptr;
0099 
0100     // Target geometry information of the navigation which should only be set
0101     // while initialization. NOTE: This information is mostly used by actors to
0102     // check if we are on the target surface (e.g. MaterialInteraction).
0103     /// Surface that is the target of the propagation
0104     const Surface* targetSurface = nullptr;
0105 
0106     // Current geometry information of the navigation which is set during
0107     // initialization and potentially updated after each step.
0108     /// Currently active surface during propagation
0109     const Surface* currentSurface = nullptr;
0110     /// Currently active tracking volume during propagation
0111     const TrackingVolume* currentVolume = nullptr;
0112 
0113     /// The vector of navigation candidates to work through
0114     std::vector<detail::NavigationObjectCandidate> navigationCandidates;
0115 
0116     /// If a break has been detected
0117     bool navigationBreak = false;
0118 
0119     /// Navigation statistics
0120     NavigatorStatistics statistics;
0121   };
0122 
0123   /// Constructor with configuration object
0124   ///
0125   /// @param cfg The navigator configuration
0126   /// @param logger a logger instance
0127   TryAllNavigatorBase(Config cfg, std::unique_ptr<const Logger> logger)
0128       : m_cfg(std::move(cfg)), m_logger{std::move(logger)} {}
0129 
0130   /// @brief Get the current surface from the navigation state
0131   /// @param state The navigation state
0132   /// @return Pointer to the current surface, or nullptr if none
0133   const Surface* currentSurface(const State& state) const {
0134     return state.currentSurface;
0135   }
0136 
0137   /// @brief Get the current tracking volume from the navigation state
0138   /// @param state The navigation state
0139   /// @return Pointer to the current tracking volume, or nullptr if none
0140   const TrackingVolume* currentVolume(const State& state) const {
0141     return state.currentVolume;
0142   }
0143 
0144   /// @brief Get the material of the current tracking volume
0145   /// @param state The navigation state
0146   /// @return Pointer to the volume material, or nullptr if no volume or no material
0147   const IVolumeMaterial* currentVolumeMaterial(const State& state) const {
0148     if (state.currentVolume == nullptr) {
0149       return nullptr;
0150     }
0151     return state.currentVolume->volumeMaterial();
0152   }
0153 
0154   /// @brief Get the start surface from the navigation state
0155   /// @param state The navigation state
0156   /// @return Pointer to the start surface, or nullptr if none
0157   const Surface* startSurface(const State& state) const {
0158     return state.startSurface;
0159   }
0160 
0161   /// @brief Get the target surface from the navigation state
0162   /// @param state The navigation state
0163   /// @return Pointer to the target surface, or nullptr if none
0164   const Surface* targetSurface(const State& state) const {
0165     return state.targetSurface;
0166   }
0167 
0168   /// @brief Check if the end of the world has been reached
0169   /// @param state The navigation state
0170   /// @return True if no current volume is set (end of world reached)
0171   bool endOfWorldReached(State& state) const {
0172     return state.currentVolume == nullptr;
0173   }
0174 
0175   /// @brief Check if navigation has been interrupted
0176   /// @param state The navigation state
0177   /// @return True if navigation break flag is set
0178   bool navigationBreak(const State& state) const {
0179     return state.navigationBreak;
0180   }
0181 
0182   /// @brief Initialize the navigator
0183   ///
0184   /// This method initializes the navigator for a new propagation. It sets the
0185   /// current volume and surface to the start volume and surface, respectively.
0186   ///
0187   /// @param state The navigation state
0188   /// @param position The starting position
0189   /// @param direction The starting direction
0190   /// @param propagationDirection The propagation direction
0191   /// @return Result indicating success or failure of initialization
0192   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0193                                         const Vector3& direction,
0194                                         Direction propagationDirection) const {
0195     static_cast<void>(propagationDirection);
0196 
0197     ACTS_VERBOSE("initialize");
0198 
0199     state.startSurface = state.options.startSurface;
0200     state.targetSurface = state.options.targetSurface;
0201 
0202     const TrackingVolume* startVolume = nullptr;
0203 
0204     if (state.startSurface != nullptr &&
0205         state.startSurface->associatedLayer() != nullptr) {
0206       ACTS_VERBOSE(
0207           "Fast start initialization through association from Surface.");
0208       const auto* startLayer = state.startSurface->associatedLayer();
0209       startVolume = startLayer->trackingVolume();
0210     } else {
0211       ACTS_VERBOSE("Slow start initialization through search.");
0212       ACTS_VERBOSE("Starting from position " << toString(position)
0213                                              << " and direction "
0214                                              << toString(direction));
0215       startVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0216           state.options.geoContext, position);
0217     }
0218 
0219     // Initialize current volume, layer and surface
0220     {
0221       state.currentVolume = startVolume;
0222       if (state.currentVolume != nullptr) {
0223         ACTS_VERBOSE(volInfo(state) << "Start volume resolved.");
0224       } else {
0225         ACTS_DEBUG("Start volume not resolved.");
0226         state.navigationBreak = true;
0227         return NavigatorError::NoStartVolume;
0228       }
0229 
0230       state.currentSurface = state.startSurface;
0231       if (state.currentSurface != nullptr) {
0232         ACTS_VERBOSE(volInfo(state) << "Current surface set to start surface "
0233                                     << state.currentSurface->geometryId());
0234       } else {
0235         ACTS_VERBOSE(volInfo(state) << "No start surface set.");
0236       }
0237     }
0238 
0239     return Result<void>::success();
0240   }
0241 
0242  protected:
0243   /// Helper method to initialize navigation candidates for the current volume.
0244   /// @param state Navigation state to initialize candidates for
0245   void initializeVolumeCandidates(State& state) const {
0246     const TrackingVolume* volume = state.currentVolume;
0247     ACTS_VERBOSE(volInfo(state) << "Initialize volume");
0248 
0249     if (volume == nullptr) {
0250       state.navigationBreak = true;
0251       ACTS_VERBOSE(volInfo(state) << "No volume set. Good luck.");
0252       return;
0253     }
0254 
0255     emplaceAllVolumeCandidates(
0256         state.navigationCandidates, *volume, m_cfg.resolveSensitive,
0257         m_cfg.resolveMaterial, m_cfg.resolvePassive,
0258         m_cfg.boundaryToleranceSurfaceApproach, logger());
0259   }
0260 
0261   /// @brief Get volume information string for logging
0262   /// @param state The navigation state
0263   /// @return String containing volume name or "No Volume" followed by separator
0264   std::string volInfo(const State& state) const {
0265     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0266                                            : "No Volume") +
0267            " | ";
0268   }
0269 
0270   /// @brief Get the logger instance
0271   /// @return Reference to the logger instance
0272   const Logger& logger() const { return *m_logger; }
0273 
0274   /// Configuration object for this navigator
0275   Config m_cfg;
0276 
0277   /// Logger instance for this navigator
0278   std::unique_ptr<const Logger> m_logger;
0279 };
0280 
0281 /// @brief Alternative @c Navigator which tries all possible intersections
0282 ///
0283 /// See @c Navigator for more general information about the Navigator concept.
0284 ///
0285 /// This Navigator tries all possible intersections with all surfaces in the
0286 /// current volume. It does not use any information about the geometry to
0287 /// optimize the search. It is therefore very slow, but can be used as a
0288 /// reference implementation.
0289 ///
0290 class TryAllNavigator : public TryAllNavigatorBase {
0291  public:
0292   /// Type alias for navigator configuration
0293   using Config = TryAllNavigatorBase::Config;
0294   /// Type alias for navigator options
0295   using Options = TryAllNavigatorBase::Options;
0296 
0297   /// @brief Nested State struct
0298   struct State : public TryAllNavigatorBase::State {
0299     /// @brief Constructor for navigator state
0300     /// @param options_ Navigator options to initialize state with
0301     explicit State(const Options& options_)
0302         : TryAllNavigatorBase::State(options_) {}
0303 
0304     /// Current navigation candidates with intersection information
0305     std::vector<NavigationTarget> currentTargets;
0306   };
0307 
0308   /// Constructor with configuration object
0309   ///
0310   /// @param cfg The navigator configuration
0311   /// @param logger a logger instance
0312   explicit TryAllNavigator(Config cfg, std::unique_ptr<const Logger> logger =
0313                                            getDefaultLogger("TryAllNavigator",
0314                                                             Logging::INFO))
0315       : TryAllNavigatorBase(std::move(cfg), std::move(logger)) {}
0316 
0317   /// @brief Creates a new navigator state
0318   /// @param options Navigator options for state initialization
0319   /// @return Initialized navigator state with current candidates storage
0320   State makeState(const Options& options) const {
0321     State state(options);
0322     return state;
0323   }
0324 
0325   using TryAllNavigatorBase::currentSurface;
0326   using TryAllNavigatorBase::currentVolume;
0327   using TryAllNavigatorBase::currentVolumeMaterial;
0328   using TryAllNavigatorBase::endOfWorldReached;
0329   using TryAllNavigatorBase::navigationBreak;
0330   using TryAllNavigatorBase::startSurface;
0331   using TryAllNavigatorBase::targetSurface;
0332 
0333   /// @brief Initialize the navigator
0334   ///
0335   /// This method initializes the navigator for a new propagation. It sets the
0336   /// current volume and surface to the start volume and surface, respectively.
0337   ///
0338   /// @param state The navigation state
0339   /// @param position The starting position
0340   /// @param direction The starting direction
0341   /// @param propagationDirection The propagation direction
0342   /// @return Result indicating success or failure of initialization
0343   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0344                                         const Vector3& direction,
0345                                         Direction propagationDirection) const {
0346     auto baseRes = TryAllNavigatorBase::initialize(state, position, direction,
0347                                                    propagationDirection);
0348     if (!baseRes.ok()) {
0349       return baseRes.error();
0350     }
0351 
0352     // Initialize navigation candidates for the start volume
0353     reinitializeCandidates(state);
0354 
0355     return Result<void>::success();
0356   }
0357 
0358   /// @brief Get the next target surface
0359   ///
0360   /// This method gets the next target surface based on the current
0361   /// position and direction. It returns a none target if no target can be
0362   /// found.
0363   ///
0364   /// @param state The navigation state
0365   /// @param position The current position
0366   /// @param direction The current direction
0367   ///
0368   /// @return The next target surface
0369   NavigationTarget nextTarget(State& state, const Vector3& position,
0370                               const Vector3& direction) const {
0371     // Navigator preStep always resets the current surface
0372     state.currentSurface = nullptr;
0373 
0374     // Check if the navigator is inactive
0375     if (state.navigationBreak) {
0376       return NavigationTarget::None();
0377     }
0378 
0379     ACTS_VERBOSE(volInfo(state) << "nextTarget");
0380 
0381     double nearLimit = state.options.nearLimit;
0382     double farLimit = state.options.farLimit;
0383 
0384     // handle overstepping
0385     if (!state.currentTargets.empty()) {
0386       const NavigationTarget& previousTarget = state.currentTargets.front();
0387 
0388       const Surface& surface = previousTarget.surface();
0389       IntersectionIndex index = previousTarget.intersectionIndex();
0390       BoundaryTolerance boundaryTolerance = previousTarget.boundaryTolerance();
0391 
0392       auto intersection =
0393           surface
0394               .intersect(state.options.geoContext, position, direction,
0395                          boundaryTolerance, state.options.surfaceTolerance)
0396               .at(index);
0397 
0398       if (intersection.pathLength() < 0) {
0399         nearLimit = std::min(nearLimit, intersection.pathLength() -
0400                                             state.options.surfaceTolerance);
0401         farLimit = -state.options.surfaceTolerance;
0402 
0403         ACTS_VERBOSE(volInfo(state)
0404                      << "handle overstepping with nearLimit " << nearLimit
0405                      << " and farLimit " << farLimit);
0406       }
0407     }
0408 
0409     std::vector<NavigationTarget> nextTargets;
0410 
0411     // Find intersections with all candidates
0412     for (const auto& candidate : state.navigationCandidates) {
0413       auto intersections =
0414           candidate.intersect(state.options.geoContext, position, direction,
0415                               state.options.surfaceTolerance);
0416       for (auto [intersectionIndex, intersection] :
0417            Acts::enumerate(intersections)) {
0418         // exclude invalid intersections
0419         if (!intersection.isValid() ||
0420             !detail::checkPathLength(intersection.pathLength(), nearLimit,
0421                                      farLimit)) {
0422           continue;
0423         }
0424         // store candidate
0425         nextTargets.emplace_back(
0426             candidate.target(intersection, intersectionIndex));
0427       }
0428     }
0429 
0430     std::ranges::sort(nextTargets, NavigationTarget::pathLengthOrder);
0431 
0432     ACTS_VERBOSE(volInfo(state)
0433                  << "found " << nextTargets.size() << " intersections");
0434 
0435     NavigationTarget nextTarget = NavigationTarget::None();
0436     state.currentTargets.clear();
0437 
0438     for (const auto& target : nextTargets) {
0439       const Intersection3D& intersection = target.intersection();
0440 
0441       if (intersection.status() == IntersectionStatus::onSurface) {
0442         ACTS_ERROR(volInfo(state)
0443                    << "We are on surface " << target.surface().geometryId()
0444                    << " before trying to reach it. This should not happen. "
0445                       "Good luck.");
0446         continue;
0447       }
0448 
0449       if (intersection.status() == IntersectionStatus::reachable) {
0450         nextTarget = target;
0451         break;
0452       }
0453     }
0454 
0455     state.currentTargets = std::move(nextTargets);
0456 
0457     if (nextTarget.isNone()) {
0458       ACTS_VERBOSE(volInfo(state) << "no target found");
0459     } else {
0460       ACTS_VERBOSE(volInfo(state)
0461                    << "next target is " << nextTarget.surface().geometryId());
0462     }
0463 
0464     return nextTarget;
0465   }
0466 
0467   /// @brief Check if the target is still valid
0468   ///
0469   /// This method checks if the target is valid based on the current position
0470   /// and direction. It returns true if the target is still valid.
0471   ///
0472   /// For the TryAllNavigator, the target is always invalid since we do not want
0473   /// to assume any specific surface sequence over multiple steps.
0474   ///
0475   /// @param state The navigation state
0476   /// @param position The current position
0477   /// @param direction The current direction
0478   ///
0479   /// @return True if the target is still valid
0480   bool checkTargetValid(const State& state, const Vector3& position,
0481                         const Vector3& direction) const {
0482     static_cast<void>(state);
0483     static_cast<void>(position);
0484     static_cast<void>(direction);
0485 
0486     return false;
0487   }
0488 
0489   /// @brief Handle the surface reached
0490   ///
0491   /// This method is called when a surface is reached. It sets the current
0492   /// surface in the navigation state and updates the navigation candidates.
0493   ///
0494   /// @param state The navigation state
0495   /// @param position The current position
0496   /// @param direction The current direction
0497   void handleSurfaceReached(State& state, const Vector3& position,
0498                             const Vector3& direction,
0499                             const Surface& /*surface*/) const {
0500     // Check if the navigator is inactive
0501     if (state.navigationBreak) {
0502       return;
0503     }
0504 
0505     ACTS_VERBOSE(volInfo(state) << "handleSurfaceReached");
0506 
0507     if (state.currentTargets.empty()) {
0508       ACTS_VERBOSE(volInfo(state) << "No current target set.");
0509       return;
0510     }
0511 
0512     assert(state.currentSurface == nullptr && "Current surface must be reset.");
0513 
0514     // handle multiple surface intersections due to increased bounds
0515 
0516     std::vector<NavigationTarget> hitTargets;
0517 
0518     for (const auto& target : state.currentTargets) {
0519       std::uint8_t index = target.intersectionIndex();
0520       const Surface& surface = target.surface();
0521       BoundaryTolerance boundaryTolerance = BoundaryTolerance::None();
0522 
0523       Intersection3D intersection =
0524           surface
0525               .intersect(state.options.geoContext, position, direction,
0526                          boundaryTolerance, state.options.surfaceTolerance)
0527               .at(index);
0528 
0529       if (intersection.status() == IntersectionStatus::onSurface) {
0530         hitTargets.emplace_back(target);
0531       }
0532     }
0533 
0534     state.currentTargets.clear();
0535 
0536     ACTS_VERBOSE(volInfo(state)
0537                  << "Found " << hitTargets.size()
0538                  << " intersections on surface with bounds check.");
0539 
0540     if (hitTargets.empty()) {
0541       ACTS_VERBOSE(volInfo(state) << "No hit targets found.");
0542       return;
0543     }
0544 
0545     if (hitTargets.size() > 1) {
0546       ACTS_VERBOSE(volInfo(state)
0547                    << "Only using first intersection within bounds.");
0548     }
0549 
0550     // we can only handle a single surface hit so we pick the first one
0551     const auto target = hitTargets.front();
0552     const Surface& surface = target.surface();
0553 
0554     ACTS_VERBOSE(volInfo(state) << "Surface " << surface.geometryId()
0555                                 << " successfully hit, storing it.");
0556     state.currentSurface = &surface;
0557 
0558     if (target.isSurfaceTarget()) {
0559       ACTS_VERBOSE(volInfo(state) << "This is a surface");
0560     } else if (target.isLayerTarget()) {
0561       ACTS_VERBOSE(volInfo(state) << "This is a layer");
0562     } else if (target.isPortalTarget()) {
0563       ACTS_VERBOSE(volInfo(state)
0564                    << "This is a boundary. Reinitialize navigation");
0565 
0566       const BoundarySurface& boundary = target.boundarySurface();
0567 
0568       state.currentVolume = boundary.attachedVolume(state.options.geoContext,
0569                                                     position, direction);
0570 
0571       ACTS_VERBOSE(volInfo(state) << "Switched volume");
0572 
0573       reinitializeCandidates(state);
0574     } else {
0575       ACTS_ERROR(volInfo(state) << "Unknown intersection type");
0576     }
0577   }
0578 
0579  private:
0580   /// Helper method to reset and reinitialize the navigation candidates.
0581   void reinitializeCandidates(State& state) const {
0582     state.navigationCandidates.clear();
0583     state.currentTargets.clear();
0584 
0585     initializeVolumeCandidates(state);
0586   }
0587 };
0588 
0589 /// @brief Alternative @c Navigator which tries all possible intersections
0590 ///
0591 /// See @c Navigator for more general information about the Navigator concept.
0592 ///
0593 /// This Navigator tries all possible intersections with all surfaces in the
0594 /// current volume. It does not use any information about the geometry to
0595 /// optimize the search. It is therefore very slow, but can be used as a
0596 /// reference implementation.
0597 ///
0598 /// Different to @c TryAllNavigator, this Navigator discovers intersections by
0599 /// stepping forward blindly and then checking for intersections with all
0600 /// surfaces in the current volume. This is slower, but more robust against
0601 /// bent tracks.
0602 ///
0603 class TryAllOverstepNavigator : public TryAllNavigatorBase {
0604  public:
0605   /// Type alias for navigator configuration
0606   using Config = TryAllNavigatorBase::Config;
0607 
0608   /// Type alias for navigator options
0609   using Options = TryAllNavigatorBase::Options;
0610 
0611   /// @brief Nested State struct
0612   ///
0613   /// It acts as an internal state which is created for every propagation and
0614   /// meant to keep thread-local navigation information.
0615   struct State : public TryAllNavigatorBase::State {
0616     /// @brief Constructor for overstep navigator state
0617     /// @param options_ Navigator options to initialize state with
0618     explicit State(const Options& options_)
0619         : TryAllNavigatorBase::State(options_) {}
0620 
0621     /// The vector of active targets to work through
0622     std::vector<NavigationTarget> activeTargets;
0623     /// The current active target index of the navigation state
0624     int activeTargetIndex = -1;
0625 
0626     /// The position before the last step
0627     std::optional<Vector3> lastPosition;
0628 
0629     /// Provides easy access to the active intersection target
0630     /// @return Reference to the currently active intersection candidate
0631     const NavigationTarget& activeTarget() const {
0632       return activeTargets.at(activeTargetIndex);
0633     }
0634 
0635     /// @brief Check if all navigation candidates have been processed
0636     /// @return True if no more candidates are available for navigation
0637     bool endOfTargets() const {
0638       return activeTargetIndex >= static_cast<int>(activeTargets.size());
0639     }
0640   };
0641 
0642   /// Constructor with configuration object
0643   ///
0644   /// @param cfg The navigator configuration
0645   /// @param logger a logger instance
0646   explicit TryAllOverstepNavigator(
0647       Config cfg, std::unique_ptr<const Logger> logger = getDefaultLogger(
0648                       "TryAllOverstepNavigator", Logging::INFO))
0649       : TryAllNavigatorBase(std::move(cfg), std::move(logger)) {}
0650 
0651   /// @brief Creates a new overstep navigator state
0652   /// @param options Navigator options for state initialization
0653   /// @return Initialized navigator state with active candidates and position tracking
0654   State makeState(const Options& options) const {
0655     State state(options);
0656     return state;
0657   }
0658 
0659   using TryAllNavigatorBase::currentSurface;
0660   using TryAllNavigatorBase::currentVolume;
0661   using TryAllNavigatorBase::currentVolumeMaterial;
0662   using TryAllNavigatorBase::endOfWorldReached;
0663   using TryAllNavigatorBase::navigationBreak;
0664   using TryAllNavigatorBase::startSurface;
0665   using TryAllNavigatorBase::targetSurface;
0666 
0667   /// @brief Initialize the navigator
0668   ///
0669   /// This method initializes the navigator for a new propagation. It sets the
0670   /// current volume and surface to the start volume and surface, respectively.
0671   ///
0672   /// @param state The navigation state
0673   /// @param position The starting position
0674   /// @param direction The starting direction
0675   /// @param propagationDirection The propagation direction
0676   /// @return Result indicating success or failure of initialization
0677   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0678                                         const Vector3& direction,
0679                                         Direction propagationDirection) const {
0680     auto baseRes = TryAllNavigatorBase::initialize(state, position, direction,
0681                                                    propagationDirection);
0682     if (!baseRes.ok()) {
0683       return baseRes.error();
0684     }
0685 
0686     // Initialize navigation candidates for the start volume
0687     reinitializeCandidates(state);
0688 
0689     state.lastPosition.reset();
0690 
0691     return Result<void>::success();
0692   }
0693 
0694   /// @brief Get the next target surface
0695   ///
0696   /// This method gets the next target surface based on the current
0697   /// position and direction. It returns an invalid target if no target can be
0698   /// found.
0699   ///
0700   /// @param state The navigation state
0701   /// @param position The current position
0702   /// @param direction The current direction
0703   ///
0704   /// @return The next target surface
0705   NavigationTarget nextTarget(State& state, const Vector3& position,
0706                               const Vector3& direction) const {
0707     static_cast<void>(direction);
0708 
0709     // Navigator preStep always resets the current surface
0710     state.currentSurface = nullptr;
0711 
0712     // Check if the navigator is inactive
0713     if (state.navigationBreak) {
0714       return NavigationTarget::None();
0715     }
0716 
0717     ACTS_VERBOSE(volInfo(state) << "nextTarget");
0718 
0719     // We cannot do anything without a last position
0720     if (!state.lastPosition.has_value() && state.endOfTargets()) {
0721       ACTS_VERBOSE(
0722           volInfo(state)
0723           << "Initial position, nothing to do, blindly stepping forward.");
0724       state.lastPosition = position;
0725       return NavigationTarget::None();
0726     }
0727 
0728     if (state.endOfTargets()) {
0729       ACTS_VERBOSE(volInfo(state) << "evaluate blind step");
0730 
0731       Vector3 stepStart = state.lastPosition.value();
0732       Vector3 stepEnd = position;
0733       Vector3 step = stepEnd - stepStart;
0734       double stepDistance = step.norm();
0735       if (stepDistance < std::numeric_limits<double>::epsilon()) {
0736         ACTS_ERROR(volInfo(state) << "Step distance is zero. " << stepDistance);
0737       }
0738       Vector3 stepDirection = step.normalized();
0739 
0740       double nearLimit = -stepDistance + state.options.surfaceTolerance;
0741       double farLimit = 0;
0742 
0743       state.lastPosition.reset();
0744       state.activeTargets.clear();
0745       state.activeTargetIndex = -1;
0746 
0747       // Find intersections with all candidates
0748       for (const auto& candidate : state.navigationCandidates) {
0749         auto intersections =
0750             candidate.intersect(state.options.geoContext, stepEnd,
0751                                 stepDirection, state.options.surfaceTolerance);
0752         for (auto [intersectionIndex, intersection] :
0753              Acts::enumerate(intersections)) {
0754           // exclude invalid intersections
0755           if (!intersection.isValid() ||
0756               !detail::checkPathLength(intersection.pathLength(), nearLimit,
0757                                        farLimit)) {
0758             continue;
0759           }
0760           // store candidate
0761           state.activeTargets.emplace_back(
0762               candidate.target(intersection, intersectionIndex));
0763         }
0764       }
0765 
0766       std::ranges::sort(state.activeTargets, NavigationTarget::pathLengthOrder);
0767 
0768       ACTS_VERBOSE(volInfo(state) << "Found " << state.activeTargets.size()
0769                                   << " intersections");
0770 
0771       for (const auto& target : state.activeTargets) {
0772         ACTS_VERBOSE("found target " << target.surface().geometryId());
0773       }
0774     }
0775 
0776     ++state.activeTargetIndex;
0777 
0778     if (state.endOfTargets()) {
0779       ACTS_VERBOSE(volInfo(state)
0780                    << "No target found, blindly stepping forward.");
0781       state.lastPosition = position;
0782       return NavigationTarget::None();
0783     }
0784 
0785     ACTS_VERBOSE(volInfo(state) << "handle active candidates");
0786 
0787     ACTS_VERBOSE(volInfo(state)
0788                  << (state.activeTargets.size() - state.activeTargetIndex)
0789                  << " out of " << state.activeTargets.size()
0790                  << " surfaces remain to try.");
0791 
0792     const auto& target = state.activeTarget();
0793 
0794     ACTS_VERBOSE(volInfo(state) << "Next surface candidate will be "
0795                                 << target.surface().geometryId());
0796 
0797     return target;
0798   }
0799 
0800   /// @brief Check if the target is still valid
0801   ///
0802   /// This method checks if the target is valid based on the current position
0803   /// and direction. It returns true if the target is still valid.
0804   ///
0805   /// @param state The navigation state
0806   /// @param position The current position
0807   /// @param direction The current direction
0808   ///
0809   /// @return True if the target is still valid
0810   bool checkTargetValid(const State& state, const Vector3& position,
0811                         const Vector3& direction) const {
0812     static_cast<void>(state);
0813     static_cast<void>(position);
0814     static_cast<void>(direction);
0815 
0816     return true;
0817   }
0818 
0819   /// @brief Handle the surface reached
0820   ///
0821   /// This method is called when a surface is reached. It sets the current
0822   /// surface in the navigation state and updates the navigation candidates.
0823   ///
0824   /// @param state The navigation state
0825   /// @param position The current position
0826   /// @param direction The current direction
0827   void handleSurfaceReached(State& state, const Vector3& position,
0828                             const Vector3& direction,
0829                             const Surface& /*surface*/) const {
0830     if (state.navigationBreak) {
0831       return;
0832     }
0833 
0834     ACTS_VERBOSE(volInfo(state) << "handleSurfaceReached");
0835 
0836     assert(state.currentSurface == nullptr && "Current surface must be reset.");
0837 
0838     if (state.endOfTargets()) {
0839       ACTS_VERBOSE(volInfo(state) << "No active candidate set.");
0840       return;
0841     }
0842 
0843     std::vector<NavigationTarget> hitTargets;
0844 
0845     while (!state.endOfTargets()) {
0846       const auto& candidate = state.activeTarget();
0847       IntersectionIndex index = candidate.intersectionIndex();
0848       const Surface& surface = candidate.surface();
0849       BoundaryTolerance boundaryTolerance = candidate.boundaryTolerance();
0850 
0851       // first with boundary tolerance
0852       IntersectionStatus surfaceStatus =
0853           surface
0854               .intersect(state.options.geoContext, position, direction,
0855                          boundaryTolerance, state.options.surfaceTolerance)
0856               .at(index)
0857               .status();
0858 
0859       if (surfaceStatus != IntersectionStatus::onSurface) {
0860         break;
0861       }
0862 
0863       // now without boundary tolerance
0864       boundaryTolerance = BoundaryTolerance::None();
0865       surfaceStatus =
0866           surface
0867               .intersect(state.options.geoContext, position, direction,
0868                          boundaryTolerance, state.options.surfaceTolerance)
0869               .at(index)
0870               .status();
0871 
0872       if (surfaceStatus == IntersectionStatus::onSurface) {
0873         hitTargets.emplace_back(candidate);
0874       }
0875 
0876       ++state.activeTargetIndex;
0877       ACTS_VERBOSE("skip target " << surface.geometryId());
0878     }
0879 
0880     // we increased the target index one too many times
0881     --state.activeTargetIndex;
0882 
0883     ACTS_VERBOSE(volInfo(state)
0884                  << "Found " << hitTargets.size()
0885                  << " intersections on surface with bounds check.");
0886 
0887     if (hitTargets.empty()) {
0888       ACTS_VERBOSE(volInfo(state)
0889                    << "Surface successfully hit, but outside bounds.");
0890       return;
0891     }
0892 
0893     if (hitTargets.size() > 1) {
0894       ACTS_VERBOSE(volInfo(state)
0895                    << "Only using first intersection within bounds.");
0896     }
0897 
0898     // we can only handle a single surface hit so we pick the first one
0899     const auto& candidate = hitTargets.front();
0900     const Surface& surface = candidate.surface();
0901 
0902     ACTS_VERBOSE(volInfo(state) << "Surface successfully hit, storing it.");
0903     state.currentSurface = &surface;
0904 
0905     if (state.currentSurface != nullptr) {
0906       ACTS_VERBOSE(volInfo(state) << "Current surface set to surface "
0907                                   << surface.geometryId());
0908     }
0909 
0910     if (candidate.isSurfaceTarget()) {
0911       ACTS_VERBOSE(volInfo(state) << "This is a surface");
0912     } else if (candidate.isLayerTarget()) {
0913       ACTS_VERBOSE(volInfo(state) << "This is a layer");
0914     } else if (candidate.isPortalTarget()) {
0915       ACTS_VERBOSE(volInfo(state)
0916                    << "This is a portal. Reinitialize navigation");
0917 
0918       const BoundarySurface& boundary = candidate.boundarySurface();
0919 
0920       state.currentVolume = boundary.attachedVolume(state.options.geoContext,
0921                                                     position, direction);
0922 
0923       ACTS_VERBOSE(volInfo(state) << "Switched volume");
0924 
0925       reinitializeCandidates(state);
0926     } else {
0927       ACTS_ERROR(volInfo(state) << "Unknown intersection type");
0928     }
0929   }
0930 
0931  private:
0932   /// Helper method to reset and reinitialize the navigation candidates.
0933   void reinitializeCandidates(State& state) const {
0934     state.navigationCandidates.clear();
0935     state.activeTargets.clear();
0936     state.activeTargetIndex = -1;
0937 
0938     initializeVolumeCandidates(state);
0939   }
0940 };
0941 
0942 }  // namespace Acts