Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:10:59

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