Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-10-16 08:02:16

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/Tolerance.hpp"
0012 #include "Acts/Geometry/GeometryIdentifier.hpp"
0013 #include "Acts/Geometry/Layer.hpp"
0014 #include "Acts/Geometry/TrackingGeometry.hpp"
0015 #include "Acts/Geometry/TrackingVolume.hpp"
0016 #include "Acts/Navigation/NavigationStream.hpp"
0017 #include "Acts/Propagator/NavigationTarget.hpp"
0018 #include "Acts/Propagator/NavigatorError.hpp"
0019 #include "Acts/Propagator/NavigatorOptions.hpp"
0020 #include "Acts/Propagator/NavigatorStatistics.hpp"
0021 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0022 #include "Acts/Surfaces/Surface.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 <map>
0029 #include <optional>
0030 #include <sstream>
0031 #include <string>
0032 
0033 #include <boost/container/small_vector.hpp>
0034 
0035 namespace Acts {
0036 
0037 /// @brief The navigation options for the tracking geometry
0038 ///
0039 /// @tparam object_t Type of the object for navigation to check against
0040 template <typename object_t>
0041 struct NavigationOptions {
0042   /// The boundary check directive
0043   BoundaryTolerance boundaryTolerance = BoundaryTolerance::None();
0044 
0045   // How to resolve the geometry
0046   /// Always look for sensitive
0047   bool resolveSensitive = true;
0048   /// Always look for material
0049   bool resolveMaterial = true;
0050   /// always look for passive
0051   bool resolvePassive = false;
0052 
0053   /// Hint for start object
0054   const object_t* startObject = nullptr;
0055   /// Hint for end object
0056   const object_t* endObject = nullptr;
0057 
0058   /// External surface identifier for which the boundary check is ignored
0059   std::vector<GeometryIdentifier> externalSurfaces = {};
0060 
0061   /// The minimum distance for a surface to be considered
0062   double nearLimit = 0;
0063   /// The maximum distance for a surface to be considered
0064   double farLimit = std::numeric_limits<double>::max();
0065 };
0066 
0067 /// @brief Steers the propagation through the geometry by providing the next
0068 /// surface to be targeted.
0069 ///
0070 /// The Navigator is part of the propagation and responsible for steering
0071 /// the surface sequence to encounter all the relevant surfaces which are
0072 /// intersected by the trajectory.
0073 ///
0074 /// The current navigation stage is cached in the state struct and updated
0075 /// when necessary. If any surface in the extrapolation flow is hit, it is
0076 /// set to the navigation state, such that other actors can deal with it.
0077 ///
0078 /// The current target surface is referenced by an index which points into
0079 /// the navigation candidates. The navigation candidates are ordered by the
0080 /// path length to the surface. If a surface is hit, the
0081 /// `state.currentSurface` pointer is set. This actors to observe
0082 /// that we are on a surface.
0083 ///
0084 class Navigator {
0085  public:
0086   /// Type alias for navigation surface candidates container
0087   using NavigationSurfaces =
0088       boost::container::small_vector<NavigationTarget, 10>;
0089 
0090   /// Type alias for navigation layer candidates container
0091   using NavigationLayers = boost::container::small_vector<NavigationTarget, 10>;
0092 
0093   /// Type alias for navigation boundary candidates container
0094   using NavigationBoundaries =
0095       boost::container::small_vector<NavigationTarget, 4>;
0096 
0097   /// Type alias for generic navigation candidates container
0098   using NavigationCandidates =
0099       boost::container::small_vector<NavigationTarget, 10>;
0100 
0101   using ExternalSurfaces = std::multimap<std::uint64_t, GeometryIdentifier>;
0102 
0103   /// Type alias for geometry version enumeration
0104   using GeometryVersion = TrackingGeometry::GeometryVersion;
0105 
0106   /// The navigation stage
0107   enum struct Stage : int {
0108     initial = 0,
0109     surfaceTarget = 1,
0110     layerTarget = 2,
0111     boundaryTarget = 3,
0112   };
0113 
0114   /// The navigator configuration
0115   struct Config {
0116     /// Tracking Geometry for this Navigator
0117     std::shared_ptr<const TrackingGeometry> trackingGeometry{nullptr};
0118 
0119     /// stop at every sensitive surface (whether it has material or not)
0120     bool resolveSensitive = true;
0121     /// stop at every material surface (whether it is passive or not)
0122     bool resolveMaterial = true;
0123     /// stop at every surface regardless what it is
0124     bool resolvePassive = false;
0125   };
0126 
0127   /// The navigator options
0128   struct Options : public NavigatorPlainOptions {
0129     /// Constructor with geometry context
0130     /// @param gctx The geometry context for the navigation
0131     explicit Options(const GeometryContext& gctx)
0132         : NavigatorPlainOptions(gctx) {}
0133 
0134     /// The surface tolerance
0135     double surfaceTolerance = s_onSurfaceTolerance;
0136 
0137     /// The near limit to resolve surfaces
0138     double nearLimit = s_onSurfaceTolerance;
0139 
0140     /// The far limit to resolve surfaces
0141     double farLimit = std::numeric_limits<double>::max();
0142 
0143     /// Externally provided surfaces - these are tried to be hit
0144     ExternalSurfaces externalSurfaces = {};
0145 
0146     /// Insert an external surface to be considered during navigation
0147     /// @param geoid Geometry identifier of the surface to insert
0148     void insertExternalSurface(GeometryIdentifier geoid) {
0149       externalSurfaces.insert(
0150           std::pair<std::uint64_t, GeometryIdentifier>(geoid.layer(), geoid));
0151     }
0152 
0153     /// Set the plain navigation options
0154     /// @param options The plain navigator options to set
0155     void setPlainOptions(const NavigatorPlainOptions& options) {
0156       static_cast<NavigatorPlainOptions&>(*this) = options;
0157     }
0158   };
0159 
0160   /// @brief Nested State struct
0161   ///
0162   /// It acts as an internal state which is created for every propagation and
0163   /// meant to keep thread-local navigation information.
0164   struct State {
0165     /// Constructor with navigation options
0166     /// @param options_ The navigation options for this state
0167     explicit State(const Options& options_) : options(options_) {}
0168 
0169     /// Navigation options configuration
0170     Options options;
0171 
0172     // Navigation on surface level
0173     /// the vector of navigation surfaces to work through
0174     NavigationSurfaces navSurfaces = {};
0175     /// the current surface index of the navigation state
0176     std::optional<std::size_t> navSurfaceIndex;
0177 
0178     // Navigation on layer level
0179     /// the vector of navigation layers to work through
0180     NavigationLayers navLayers = {};
0181     /// the current layer index of the navigation state
0182     std::optional<std::size_t> navLayerIndex;
0183 
0184     // Navigation on volume level
0185     /// the vector of boundary surfaces to work through
0186     NavigationBoundaries navBoundaries = {};
0187     /// the current boundary index of the navigation state
0188     std::optional<std::size_t> navBoundaryIndex;
0189 
0190     // Navigation candidates(portals and surfaces together)
0191     /// the vector of navigation candidates to work through
0192     NavigationCandidates navCandidates = {};
0193     /// the current candidate index of the navigation state
0194     std::optional<std::size_t> navCandidateIndex;
0195 
0196     NavigationTarget& navSurface() {
0197       return navSurfaces.at(navSurfaceIndex.value());
0198     }
0199 
0200     /// Get reference to current navigation layer
0201     /// @return Reference to current layer intersection
0202     NavigationTarget& navLayer() { return navLayers.at(navLayerIndex.value()); }
0203 
0204     /// Get reference to current navigation boundary
0205     /// @return Reference to current boundary intersection
0206     NavigationTarget& navBoundary() {
0207       return navBoundaries.at(navBoundaryIndex.value());
0208     }
0209 
0210     /// Get reference to current navigation candidate
0211     /// @return Reference to current boundary intersection
0212     NavigationTarget& navCandidate() {
0213       return navCandidates.at(navCandidateIndex.value());
0214     }
0215 
0216     const TrackingVolume* startVolume = nullptr;
0217     /// Layer where the navigation started
0218     const Layer* startLayer = nullptr;
0219     /// Surface where the navigation started
0220     const Surface* startSurface = nullptr;
0221     /// Current volume during navigation
0222     const TrackingVolume* currentVolume = nullptr;
0223     /// Current layer during navigation
0224     const Layer* currentLayer = nullptr;
0225     /// Current surface during navigation
0226     const Surface* currentSurface = nullptr;
0227     /// Target surface for navigation
0228     const Surface* targetSurface = nullptr;
0229 
0230     /// Flag to break navigation loop
0231     bool navigationBreak = false;
0232     /// Current navigation stage in the state machine
0233     Stage navigationStage = Stage::initial;
0234 
0235     /// Statistics collection for navigation performance
0236     NavigatorStatistics statistics;
0237 
0238     /// Stream for navigation debugging and monitoring
0239     NavigationStream stream;
0240 
0241     /// Reset navigation state after switching layers
0242     void resetAfterLayerSwitch() {
0243       navSurfaces.clear();
0244       navSurfaceIndex.reset();
0245     }
0246 
0247     /// Reset navigation state after switching volumes
0248     void resetAfterVolumeSwitch() {
0249       resetAfterLayerSwitch();
0250 
0251       navLayers.clear();
0252       navLayerIndex.reset();
0253       navBoundaries.clear();
0254       navBoundaryIndex.reset();
0255       navCandidates.clear();
0256       navCandidateIndex.reset();
0257 
0258       currentLayer = nullptr;
0259     }
0260 
0261     /// Completely reset navigation state to initial conditions
0262     void reset() {
0263       resetAfterVolumeSwitch();
0264 
0265       currentVolume = nullptr;
0266       currentSurface = nullptr;
0267 
0268       navigationBreak = false;
0269       navigationStage = Stage::initial;
0270     }
0271   };
0272 
0273   /// Constructor with configuration object
0274   ///
0275   /// @param cfg The navigator configuration
0276   /// @param _logger a logger instance
0277   explicit Navigator(Config cfg,
0278                      std::shared_ptr<const Logger> _logger =
0279                          getDefaultLogger("Navigator", Logging::Level::INFO))
0280       : m_cfg{std::move(cfg)}, m_logger{std::move(_logger)} {
0281     if (m_cfg.trackingGeometry == nullptr) {
0282       throw std::invalid_argument("Navigator: No tracking geometry provided.");
0283     }
0284     m_geometryVersion = m_cfg.trackingGeometry->geometryVersion();
0285   }
0286 
0287   /// Create a navigation state from options
0288   /// @param options The navigation options
0289   /// @return A new navigation state
0290   State makeState(const Options& options) const {
0291     State state(options);
0292     return state;
0293   }
0294 
0295   /// Get the current surface from navigation state
0296   /// @param state The navigation state
0297   /// @return Pointer to current surface, or nullptr if none
0298   const Surface* currentSurface(const State& state) const {
0299     return state.currentSurface;
0300   }
0301 
0302   /// Get the current volume from navigation state
0303   /// @param state The navigation state
0304   /// @return Pointer to current volume, or nullptr if none
0305   const TrackingVolume* currentVolume(const State& state) const {
0306     return state.currentVolume;
0307   }
0308 
0309   /// Get material properties of the current volume
0310   /// @param state The navigation state
0311   /// @return Pointer to volume material, or nullptr if no volume or material
0312   const IVolumeMaterial* currentVolumeMaterial(const State& state) const {
0313     if (state.currentVolume == nullptr) {
0314       return nullptr;
0315     }
0316     return state.currentVolume->volumeMaterial();
0317   }
0318 
0319   /// Get the starting surface from navigation state
0320   /// @param state The navigation state
0321   /// @return Pointer to start surface, or nullptr if none
0322   const Surface* startSurface(const State& state) const {
0323     return state.startSurface;
0324   }
0325 
0326   /// Get the target surface from navigation state
0327   /// @param state The navigation state
0328   /// @return Pointer to target surface, or nullptr if none
0329   const Surface* targetSurface(const State& state) const {
0330     return state.targetSurface;
0331   }
0332 
0333   /// Check if navigation has reached the end of the world (no current volume)
0334   /// @param state The navigation state
0335   /// @return True if end of world is reached
0336   bool endOfWorldReached(const State& state) const {
0337     return state.currentVolume == nullptr;
0338   }
0339 
0340   /// Check if navigation should be interrupted
0341   /// @param state The navigation state
0342   /// @return True if navigation break flag is set
0343   bool navigationBreak(const State& state) const {
0344     return state.navigationBreak;
0345   }
0346 
0347   /// @brief Initialize the navigator state
0348   ///
0349   /// This function initializes the navigator state for a new propagation.
0350   ///
0351   /// @param state The navigation state
0352   /// @param position The start position
0353   /// @param direction The start direction
0354   /// @param propagationDirection The propagation direction
0355   ///
0356   /// @return Indication if the initialization was successful
0357   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0358                                         const Vector3& direction,
0359                                         Direction propagationDirection) const {
0360     (void)propagationDirection;
0361 
0362     ACTS_VERBOSE(volInfo(state) << "Initialization.");
0363 
0364     auto printGeometryVersion = [](auto ver) {
0365       using enum TrackingGeometry::GeometryVersion;
0366       switch (ver) {
0367         case Gen1:
0368           return "Gen1";
0369         case Gen3:
0370           return "Gen3";
0371         default:
0372           throw std::runtime_error("Unknown geometry version.");
0373       }
0374     };
0375     ACTS_VERBOSE(volInfo(state) << "Geometry version is: "
0376                                 << printGeometryVersion(m_geometryVersion));
0377 
0378     state.reset();
0379 
0380     if (m_geometryVersion == GeometryVersion::Gen3) {
0381       // Empirical pre-allocation of candidates for the next navigation
0382       // iteration.
0383       // @TODO: Make this user configurable through the configuration
0384       state.stream.candidates().reserve(50);
0385     }
0386 
0387     state.startSurface = state.options.startSurface;
0388     state.targetSurface = state.options.targetSurface;
0389 
0390     // @TODO: Implement fast initialization with Gen3. This requires the volume lookup to work properly
0391 
0392     // Fast Navigation initialization for start condition:
0393     // - short-cut through object association, saves navigation in the
0394     // - geometry and volume tree search for the lowest volume
0395     if (state.startSurface != nullptr &&
0396         state.startSurface->associatedLayer() != nullptr) {
0397       ACTS_VERBOSE(
0398           volInfo(state)
0399           << "Fast start initialization through association from Surface.");
0400 
0401       state.startLayer = state.startSurface->associatedLayer();
0402       state.startVolume = state.startLayer->trackingVolume();
0403     } else if (state.startVolume != nullptr) {
0404       ACTS_VERBOSE(
0405           volInfo(state)
0406           << "Fast start initialization through association from Volume.");
0407 
0408       state.startLayer = state.startVolume->associatedLayer(
0409           state.options.geoContext, position);
0410     } else {
0411       ACTS_VERBOSE(volInfo(state)
0412                    << "Slow start initialization through search.");
0413       ACTS_VERBOSE(volInfo(state)
0414                    << "Starting from position " << toString(position)
0415                    << " and direction " << toString(direction));
0416 
0417       // current volume and layer search through global search
0418       state.startVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0419           state.options.geoContext, position);
0420 
0421       if (state.startVolume != nullptr) {
0422         state.startLayer = state.startVolume->associatedLayer(
0423             state.options.geoContext, position);
0424       } else {
0425         ACTS_ERROR(volInfo(state)
0426                    << "No start volume resolved. Nothing left to do.");
0427         state.navigationBreak = true;
0428       }
0429     }
0430 
0431     state.currentVolume = state.startVolume;
0432     state.currentLayer = state.startLayer;
0433     state.currentSurface = state.startSurface;
0434 
0435     if (state.currentVolume != nullptr) {
0436       ACTS_VERBOSE(volInfo(state) << "Start volume resolved "
0437                                   << state.currentVolume->geometryId());
0438 
0439       if (!state.currentVolume->inside(position,
0440                                        state.options.surfaceTolerance)) {
0441         ACTS_DEBUG(
0442             volInfo(state)
0443             << "We did not end up inside the expected volume. position = "
0444             << position.transpose());
0445 
0446         return Result<void>::failure(NavigatorError::NotInsideExpectedVolume);
0447       }
0448     }
0449     if (state.currentLayer != nullptr) {
0450       ACTS_VERBOSE(volInfo(state) << "Start layer resolved "
0451                                   << state.currentLayer->geometryId());
0452     }
0453     if (state.currentSurface != nullptr) {
0454       ACTS_VERBOSE(volInfo(state) << "Start surface resolved "
0455                                   << state.currentSurface->geometryId());
0456 
0457       if (!state.currentSurface->isOnSurface(
0458               state.options.geoContext, position, direction,
0459               BoundaryTolerance::Infinite(), state.options.surfaceTolerance)) {
0460         ACTS_DEBUG(volInfo(state)
0461                    << "We did not end up on the expected surface. surface = "
0462                    << state.currentSurface->geometryId()
0463                    << " position = " << position.transpose()
0464                    << " direction = " << direction.transpose());
0465 
0466         return Result<void>::failure(NavigatorError::NotOnExpectedSurface);
0467       }
0468     }
0469 
0470     return Result<void>::success();
0471   }
0472 
0473   /// @brief Get the next target surface
0474   ///
0475   /// This function gets the next target surface for the propagation.
0476   ///
0477   /// @param state The navigation state
0478   /// @param position The current position
0479   /// @param direction The current direction
0480   ///
0481   /// @return The next target surface
0482   NavigationTarget nextTarget(State& state, const Vector3& position,
0483                               const Vector3& direction) const {
0484     // Reset the current surface
0485     state.currentSurface = nullptr;
0486 
0487     if (inactive(state)) {
0488       return NavigationTarget::None();
0489     }
0490 
0491     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::nextTarget.");
0492 
0493     NavigationTarget nextTarget = tryGetNextTarget(state, position, direction);
0494     if (!nextTarget.isNone()) {
0495       return nextTarget;
0496     }
0497 
0498     state.reset();
0499     ++state.statistics.nRenavigations;
0500 
0501     // We might have punched through a boundary and entered another volume
0502     // so we have to reinitialize
0503     state.currentVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0504         state.options.geoContext, position);
0505 
0506     if (state.currentVolume == nullptr) {
0507       ACTS_VERBOSE(volInfo(state) << "No volume found, stop navigation.");
0508       state.navigationBreak = true;
0509       return NavigationTarget::None();
0510     }
0511 
0512     state.currentLayer = state.currentVolume->associatedLayer(
0513         state.options.geoContext, position);
0514 
0515     ACTS_VERBOSE(volInfo(state) << "Resolved volume and layer.");
0516 
0517     // Rerun the targeting
0518     nextTarget = tryGetNextTarget(state, position, direction);
0519     if (!nextTarget.isNone()) {
0520       return nextTarget;
0521     }
0522 
0523     ACTS_VERBOSE(
0524         volInfo(state)
0525         << "No targets found again, we got really lost! Stop navigation.");
0526     state.navigationBreak = true;
0527     return NavigationTarget::None();
0528   }
0529 
0530   /// @brief Check if the current target is still valid
0531   ///
0532   /// This function checks if the target is valid.
0533   ///
0534   /// @param state The navigation state
0535   /// @param position The current position
0536   /// @param direction The current direction
0537   ///
0538   /// @return True if the target is valid
0539   bool checkTargetValid(const State& state, const Vector3& position,
0540                         const Vector3& direction) const {
0541     (void)position;
0542     (void)direction;
0543 
0544     return state.navigationStage != Stage::initial;
0545   }
0546 
0547   /// @brief Handle the surface reached
0548   ///
0549   /// This function handles the surface reached.
0550   ///
0551   /// @param state The navigation state
0552   /// @param position The current position
0553   /// @param direction The current direction
0554   /// @param surface The surface reached
0555   void handleSurfaceReached(State& state, const Vector3& position,
0556                             const Vector3& direction,
0557                             const Surface& surface) const {
0558     if (inactive(state)) {
0559       return;
0560     }
0561 
0562     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::handleSurfaceReached.");
0563 
0564     state.currentSurface = &surface;
0565 
0566     ACTS_VERBOSE(volInfo(state)
0567                  << "Current surface: " << state.currentSurface->geometryId());
0568 
0569     // handling portals in gen3 configuration
0570     if (m_geometryVersion == GeometryVersion::Gen3) {
0571       if (state.navCandidate().isPortalTarget() &&
0572           &state.navCandidate().surface() == &surface) {
0573         ACTS_VERBOSE(volInfo(state) << "Handling portal status.");
0574 
0575         // Switch to the next volume using the portal
0576         const Portal* portal = &state.navCandidate().portal();
0577         auto res = portal->resolveVolume(state.options.geoContext, position,
0578                                          direction);
0579         if (!res.ok()) {
0580           ACTS_ERROR(volInfo(state)
0581                      << "Failed to resolve volume through portal: "
0582                      << res.error().message());
0583           return;
0584         }
0585 
0586         state.currentVolume = res.value();
0587 
0588         // partial reset
0589         state.resetAfterVolumeSwitch();
0590 
0591         if (state.currentVolume != nullptr) {
0592           ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0593 
0594           // this is set only for the check target validity since gen3 does not
0595           // care
0596           state.navigationStage = Stage::surfaceTarget;
0597         } else {
0598           ACTS_VERBOSE(
0599               volInfo(state)
0600               << "No more volume to progress to, stopping navigation.");
0601           state.navigationBreak = true;
0602         }
0603       }
0604       return;
0605     }
0606 
0607     if (state.navigationStage == Stage::surfaceTarget &&
0608         &state.navSurface().surface() == &surface) {
0609       ACTS_VERBOSE(volInfo(state) << "Handling surface status.");
0610 
0611       return;
0612     }
0613 
0614     if (state.navigationStage == Stage::layerTarget &&
0615         &state.navLayer().surface() == &surface) {
0616       ACTS_VERBOSE(volInfo(state) << "Handling layer status.");
0617 
0618       // Switch to the next layer
0619       state.currentLayer = &state.navLayer().layer();
0620       state.navigationStage = Stage::surfaceTarget;
0621 
0622       // partial reset
0623       state.resetAfterLayerSwitch();
0624 
0625       return;
0626     }
0627 
0628     if (state.navigationStage == Stage::boundaryTarget &&
0629         &state.navBoundary().surface() == &surface) {
0630       ACTS_VERBOSE(volInfo(state) << "Handling boundary status.");
0631 
0632       // Switch to the next volume using the boundary
0633       const BoundarySurface* boundary = &state.navBoundary().boundarySurface();
0634       assert(boundary != nullptr && "Retrieved boundary surface is nullptr");
0635       state.currentVolume = boundary->attachedVolume(state.options.geoContext,
0636                                                      position, direction);
0637 
0638       // partial reset
0639       state.resetAfterVolumeSwitch();
0640 
0641       if (state.currentVolume != nullptr) {
0642         ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0643         state.navigationStage = Stage::layerTarget;
0644       } else {
0645         ACTS_VERBOSE(volInfo(state)
0646                      << "No more volume to progress to, stopping navigation.");
0647         state.navigationBreak = true;
0648       }
0649 
0650       return;
0651     }
0652 
0653     ACTS_ERROR(volInfo(state) << "Surface reached but unknown state.");
0654   }
0655 
0656  private:
0657   /// @brief NextTarget helper function for Gen1 geometry configuration
0658   ///
0659   /// @param state The navigation state
0660   /// @param position The current position
0661   /// @param direction The current direction
0662   NavigationTarget getNextTargetGen1(State& state, const Vector3& position,
0663                                      const Vector3& direction) const {
0664     // Try targeting the surfaces - then layers - then boundaries
0665     if (state.navigationStage == Stage::surfaceTarget) {
0666       if (!state.navSurfaceIndex.has_value()) {
0667         // First time, resolve the surfaces
0668         resolveSurfaces(state, position, direction);
0669         state.navSurfaceIndex = 0;
0670       } else {
0671         ++state.navSurfaceIndex.value();
0672       }
0673       if (state.navSurfaceIndex.value() < state.navSurfaces.size()) {
0674         ACTS_VERBOSE(volInfo(state) << "Target set to next surface.");
0675         return state.navSurface();
0676       } else {
0677         // This was the last surface, switch to layers
0678         ACTS_VERBOSE(volInfo(state) << "Target layers.");
0679         state.navigationStage = Stage::layerTarget;
0680       }
0681     }
0682 
0683     if (state.navigationStage == Stage::layerTarget) {
0684       if (!state.navLayerIndex.has_value()) {
0685         // First time, resolve the layers
0686         resolveLayers(state, position, direction);
0687         state.navLayerIndex = 0;
0688       } else {
0689         ++state.navLayerIndex.value();
0690       }
0691       if (state.navLayerIndex.value() < state.navLayers.size()) {
0692         ACTS_VERBOSE(volInfo(state) << "Target set to next layer.");
0693         return state.navLayer();
0694       } else {
0695         // This was the last layer, switch to boundaries
0696         ACTS_VERBOSE(volInfo(state) << "Target boundaries.");
0697         state.navigationStage = Stage::boundaryTarget;
0698       }
0699     }
0700 
0701     if (state.navigationStage == Stage::boundaryTarget) {
0702       if (!state.navBoundaryIndex.has_value()) {
0703         // First time, resolve the boundaries
0704         resolveBoundaries(state, position, direction);
0705         state.navBoundaryIndex = 0;
0706       } else {
0707         ++state.navBoundaryIndex.value();
0708       }
0709       if (state.navBoundaryIndex.value() < state.navBoundaries.size()) {
0710         ACTS_VERBOSE(volInfo(state) << "Target set to next boundary.");
0711         return state.navBoundary();
0712       } else {
0713         // This was the last boundary, we have to leave the volume somehow,
0714         // renavigate
0715         ACTS_VERBOSE(volInfo(state)
0716                      << "Boundary targets exhausted. Renavigate.");
0717         return NavigationTarget::None();
0718       }
0719     }
0720 
0721     ACTS_VERBOSE(volInfo(state)
0722                  << "Unknown state. No target found. Renavigate.");
0723     return NavigationTarget::None();
0724   }
0725 
0726   /// @brief NextTarget helper function for Gen3 geometry configuration
0727   ///
0728   /// @param state The navigation state
0729   /// @param position The current position
0730   /// @param direction The current direction
0731   NavigationTarget getNextTargetGen3(State& state, const Vector3& position,
0732                                      const Vector3& direction) const {
0733     if (!state.navCandidateIndex.has_value()) {
0734       // first time, resolve the candidates
0735       resolveCandidates(state, position, direction);
0736       state.navCandidateIndex = 0;
0737     } else {
0738       ++state.navCandidateIndex.value();
0739     }
0740     if (state.navCandidateIndex.value() < state.navCandidates.size()) {
0741       ACTS_VERBOSE(volInfo(state) << "Target set to next candidate.");
0742       return state.navCandidate();
0743     } else {
0744       ACTS_VERBOSE(volInfo(state)
0745                    << "Candidate targets exhausted. Renavigate.");
0746       return NavigationTarget::None();
0747     }
0748   }
0749 
0750   /// @brief NextTarget helper function
0751   /// This function is called for returning the next target
0752   /// and checks gen1/gen3 case in order to sub-call the proper functions
0753   ///
0754   /// @param state The navigation state
0755   /// @param position The current position
0756   /// @param direction The current direction
0757   NavigationTarget tryGetNextTarget(State& state, const Vector3& position,
0758                                     const Vector3& direction) const {
0759     // Try different approach to get navigation target for gen1 and gen3
0760     // configuration
0761 
0762     // This is common, in gen1 we start by surfaces and in gen3 we always look
0763     // for surfaces
0764     if (state.navigationStage == Stage::initial) {
0765       ACTS_VERBOSE(volInfo(state) << "Target surfaces.");
0766       state.navigationStage = Stage::surfaceTarget;
0767     }
0768 
0769     if (m_geometryVersion == GeometryVersion::Gen1) {
0770       return getNextTargetGen1(state, position, direction);
0771 
0772     } else {  // gen3 handling of the next target
0773 
0774       return getNextTargetGen3(state, position, direction);
0775     }
0776   }
0777 
0778   /// @brief Resolve compatible candidates (surfaces or portals) for gen3 navigation
0779   ///
0780   /// This function is called when gen3 configuration is found and it resolves
0781   /// at the same time for portals and surfaces
0782   /// @param state The navigation state
0783   /// @param position The current position
0784   /// @param direction The current direction
0785 
0786   void resolveCandidates(State& state, const Vector3& position,
0787                          const Vector3& direction) const {
0788     if (state.currentVolume == nullptr) {
0789       ACTS_VERBOSE(volInfo(state) << "No volume to resolve candidates.");
0790       return;
0791     }
0792     ACTS_VERBOSE(volInfo(state) << "Searching for compatible candidates.");
0793 
0794     state.stream.reset();
0795     AppendOnlyNavigationStream appendOnly{state.stream};
0796     NavigationArguments args;
0797     args.position = position;
0798     args.direction = direction;
0799     state.currentVolume->initializeNavigationCandidates(args, appendOnly,
0800                                                         logger());
0801 
0802     ACTS_VERBOSE(volInfo(state) << "Found " << state.stream.candidates().size()
0803                                 << " navigation candidates.");
0804 
0805     state.stream.initialize(state.options.geoContext, {position, direction},
0806                             BoundaryTolerance::None(),
0807                             state.options.surfaceTolerance);
0808 
0809     ACTS_VERBOSE(volInfo(state)
0810                  << "Now " << state.stream.candidates().size()
0811                  << " navigation candidates after initialization");
0812 
0813     state.navCandidates.clear();
0814 
0815     for (auto& candidate : state.stream.candidates()) {
0816       if (!detail::checkPathLength(candidate.intersection().pathLength(),
0817                                    state.options.nearLimit,
0818                                    state.options.farLimit, logger())) {
0819         continue;
0820       }
0821 
0822       state.navCandidates.emplace_back(candidate);
0823     }
0824 
0825     // Sort the candidates with the path length
0826     std::ranges::sort(state.navCandidates, [](const auto& a, const auto& b) {
0827       return a.intersection().pathLength() < b.intersection().pathLength();
0828     });
0829 
0830     // Print the navigation candidates
0831 
0832     if (logger().doPrint(Logging::VERBOSE)) {
0833       std::ostringstream os;
0834       os << "Navigation candidates: " << state.navCandidates.size() << "\n";
0835       for (auto& candidate : state.navCandidates) {
0836         os << "Candidate: " << candidate.surface().geometryId()
0837            << " at path length: " << candidate.intersection().pathLength()
0838            << "  ";
0839       }
0840 
0841       logger().log(Logging::VERBOSE, os.str());
0842     }
0843   }
0844 
0845   /// @brief Resolve compatible surfaces
0846   ///
0847   /// This function resolves the compatible surfaces for the navigation.
0848   ///
0849   /// @param state The navigation state
0850   /// @param position The current position
0851   /// @param direction The current direction
0852   void resolveSurfaces(State& state, const Vector3& position,
0853                        const Vector3& direction) const {
0854     ACTS_VERBOSE(volInfo(state) << "Searching for compatible surfaces.");
0855 
0856     const Layer* currentLayer = state.currentLayer;
0857 
0858     if (currentLayer == nullptr) {
0859       ACTS_VERBOSE(volInfo(state) << "No layer to resolve surfaces.");
0860       return;
0861     }
0862 
0863     const Surface* layerSurface = &currentLayer->surfaceRepresentation();
0864 
0865     NavigationOptions<Surface> navOpts;
0866     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0867     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0868     navOpts.resolvePassive = m_cfg.resolvePassive;
0869     navOpts.startObject = state.currentSurface;
0870     navOpts.endObject = state.targetSurface;
0871     navOpts.nearLimit = state.options.nearLimit;
0872     navOpts.farLimit = state.options.farLimit;
0873 
0874     if (!state.options.externalSurfaces.empty()) {
0875       auto layerId = layerSurface->geometryId().layer();
0876       auto externalSurfaceRange =
0877           state.options.externalSurfaces.equal_range(layerId);
0878       navOpts.externalSurfaces.reserve(
0879           state.options.externalSurfaces.count(layerId));
0880       for (auto itSurface = externalSurfaceRange.first;
0881            itSurface != externalSurfaceRange.second; itSurface++) {
0882         navOpts.externalSurfaces.push_back(itSurface->second);
0883       }
0884     }
0885 
0886     // Request the compatible surfaces
0887     state.navSurfaces = currentLayer->compatibleSurfaces(
0888         state.options.geoContext, position, direction, navOpts);
0889     // Sort the surfaces by path length.
0890     // Special care is taken for the external surfaces which should always
0891     // come first, so they are preferred to be targeted and hit first.
0892     std::ranges::sort(state.navSurfaces, [&state](const NavigationTarget& a,
0893                                                   const NavigationTarget& b) {
0894       // Prefer to sort by path length. We assume surfaces are at the same
0895       // distance if the difference is smaller than the tolerance.
0896       if (std::abs(a.pathLength() - b.pathLength()) >
0897           state.options.surfaceTolerance) {
0898         return NavigationTarget::pathLengthOrder(a, b);
0899       }
0900       // If the path length is practically the same, sort by geometry.
0901       // First we check if one of the surfaces is external.
0902       bool aIsExternal = a.boundaryTolerance().isInfinite();
0903       bool bIsExternal = b.boundaryTolerance().isInfinite();
0904       if (aIsExternal == bIsExternal) {
0905         // If both are external or both are not external, sort by geometry
0906         // identifier
0907         return a.surface().geometryId() < b.surface().geometryId();
0908       }
0909       // If only one is external, it should come first
0910       return aIsExternal;
0911     });
0912     // For now we implicitly remove overlapping surfaces.
0913     // For track finding it might be useful to discover overlapping surfaces
0914     // and check for compatible measurements. This is under investigation
0915     // and might be implemented in the future.
0916     auto toBeRemoved = std::ranges::unique(
0917         state.navSurfaces, [&](const auto& a, const auto& b) {
0918           return std::abs(a.pathLength() - b.pathLength()) <
0919                  state.options.surfaceTolerance;
0920         });
0921     if (toBeRemoved.begin() != toBeRemoved.end()) {
0922       ACTS_VERBOSE(volInfo(state)
0923                    << "Removing "
0924                    << std::distance(toBeRemoved.begin(), toBeRemoved.end())
0925                    << " overlapping surfaces.");
0926     }
0927     state.navSurfaces.erase(toBeRemoved.begin(), toBeRemoved.end());
0928 
0929     // Print surface information
0930     if (logger().doPrint(Logging::VERBOSE)) {
0931       std::ostringstream os;
0932       os << state.navSurfaces.size();
0933       os << " surface candidates found at path(s): ";
0934       for (auto& sfc : state.navSurfaces) {
0935         os << sfc.pathLength() << "  ";
0936       }
0937       logger().log(Logging::VERBOSE, os.str());
0938     }
0939 
0940     if (state.navSurfaces.empty()) {
0941       ACTS_VERBOSE(volInfo(state) << "No surface candidates found.");
0942     }
0943   }
0944 
0945   /// @brief Resolve compatible layers
0946   ///
0947   /// This function resolves the compatible layers for the navigation.
0948   ///
0949   /// @param state The navigation state
0950   /// @param position The current position
0951   /// @param direction The current direction
0952   void resolveLayers(State& state, const Vector3& position,
0953                      const Vector3& direction) const {
0954     ACTS_VERBOSE(volInfo(state) << "Searching for compatible layers.");
0955 
0956     NavigationOptions<Layer> navOpts;
0957     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0958     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0959     navOpts.resolvePassive = m_cfg.resolvePassive;
0960     navOpts.startObject = state.currentLayer;
0961     navOpts.nearLimit = state.options.nearLimit;
0962     navOpts.farLimit = state.options.farLimit;
0963 
0964     // Request the compatible layers
0965     state.navLayers = state.currentVolume->compatibleLayers(
0966         state.options.geoContext, position, direction, navOpts);
0967     std::ranges::sort(state.navLayers, NavigationTarget::pathLengthOrder);
0968 
0969     // Print layer information
0970     if (logger().doPrint(Logging::VERBOSE)) {
0971       std::ostringstream os;
0972       os << state.navLayers.size();
0973       os << " layer candidates found at path(s): ";
0974       for (auto& lc : state.navLayers) {
0975         os << lc.pathLength() << "  ";
0976       }
0977       logger().log(Logging::VERBOSE, os.str());
0978     }
0979 
0980     if (state.navLayers.empty()) {
0981       ACTS_VERBOSE(volInfo(state) << "No layer candidates found.");
0982     }
0983   }
0984 
0985   /// @brief Resolve compatible boundaries
0986   ///
0987   /// This function resolves the compatible boundaries for the navigation.
0988   ///
0989   /// @param state The navigation state
0990   /// @param position The current position
0991   /// @param direction The current direction
0992   void resolveBoundaries(State& state, const Vector3& position,
0993                          const Vector3& direction) const {
0994     ACTS_VERBOSE(volInfo(state) << "Searching for compatible boundaries.");
0995 
0996     NavigationOptions<Surface> navOpts;
0997     navOpts.startObject = state.currentSurface;
0998     navOpts.nearLimit = state.options.nearLimit;
0999     navOpts.farLimit = state.options.farLimit;
1000 
1001     ACTS_VERBOSE(volInfo(state)
1002                  << "Try to find boundaries, we are at: " << toString(position)
1003                  << ", dir: " << toString(direction));
1004 
1005     // Request the compatible boundaries
1006     state.navBoundaries = state.currentVolume->compatibleBoundaries(
1007         state.options.geoContext, position, direction, navOpts, logger());
1008     std::ranges::sort(state.navBoundaries, NavigationTarget::pathLengthOrder);
1009 
1010     // Print boundary information
1011     if (logger().doPrint(Logging::VERBOSE)) {
1012       std::ostringstream os;
1013       os << state.navBoundaries.size();
1014       os << " boundary candidates found at path(s): ";
1015       for (const auto& bc : state.navBoundaries) {
1016         os << bc.pathLength() << "  ";
1017       }
1018       logger().log(Logging::VERBOSE, os.str());
1019     }
1020 
1021     if (state.navBoundaries.empty()) {
1022       ACTS_VERBOSE(volInfo(state) << "No boundary candidates found.");
1023     }
1024   }
1025 
1026   /// @brief Check if the navigator is inactive
1027   ///
1028   /// This function checks if the navigator is inactive.
1029   ///
1030   /// @param state The navigation state
1031   ///
1032   /// @return True if the navigator is inactive
1033   bool inactive(const State& state) const {
1034     // Turn the navigator into void when you are instructed to do nothing
1035     if (!m_cfg.resolveSensitive && !m_cfg.resolveMaterial &&
1036         !m_cfg.resolvePassive) {
1037       return true;
1038     }
1039 
1040     if (state.navigationBreak) {
1041       return true;
1042     }
1043 
1044     return false;
1045   }
1046 
1047  private:
1048   template <typename propagator_state_t>
1049   std::string volInfo(const propagator_state_t& state) const {
1050     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
1051                                            : "No Volume") +
1052            " | ";
1053   }
1054 
1055   const Logger& logger() const { return *m_logger; }
1056 
1057   Config m_cfg;
1058 
1059   // Cached so we don't have to query the TrackingGeometry constantly.
1060   TrackingGeometry::GeometryVersion m_geometryVersion{};
1061 
1062   std::shared_ptr<const Logger> m_logger;
1063 };
1064 
1065 }  // namespace Acts