Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-15 09:42:15

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