Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-05 08:11:08

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   using NavigationSurfaces =
0087       boost::container::small_vector<SurfaceIntersection, 10>;
0088 
0089   using NavigationLayers =
0090       boost::container::small_vector<LayerIntersection, 10>;
0091 
0092   using NavigationBoundaries =
0093       boost::container::small_vector<BoundaryIntersection, 4>;
0094 
0095   using ExternalSurfaces = std::multimap<std::uint64_t, GeometryIdentifier>;
0096 
0097   using GeometryVersion = TrackingGeometry::GeometryVersion;
0098 
0099   /// The navigation stage
0100   enum struct Stage : int {
0101     initial = 0,
0102     surfaceTarget = 1,
0103     layerTarget = 2,
0104     boundaryTarget = 3,
0105   };
0106 
0107   /// The navigator configuration
0108   struct Config {
0109     /// Tracking Geometry for this Navigator
0110     std::shared_ptr<const TrackingGeometry> trackingGeometry{nullptr};
0111 
0112     /// stop at every sensitive surface (whether it has material or not)
0113     bool resolveSensitive = true;
0114     /// stop at every material surface (whether it is passive or not)
0115     bool resolveMaterial = true;
0116     /// stop at every surface regardless what it is
0117     bool resolvePassive = false;
0118   };
0119 
0120   /// The navigator options
0121   struct Options : public NavigatorPlainOptions {
0122     explicit Options(const GeometryContext& gctx)
0123         : NavigatorPlainOptions(gctx) {}
0124 
0125     /// The surface tolerance
0126     double surfaceTolerance = s_onSurfaceTolerance;
0127 
0128     /// The near limit to resolve surfaces
0129     double nearLimit = s_onSurfaceTolerance;
0130 
0131     /// The far limit to resolve surfaces
0132     double farLimit = std::numeric_limits<double>::max();
0133 
0134     /// Externally provided surfaces - these are tried to be hit
0135     ExternalSurfaces externalSurfaces = {};
0136 
0137     void insertExternalSurface(GeometryIdentifier geoid) {
0138       externalSurfaces.insert(
0139           std::pair<std::uint64_t, GeometryIdentifier>(geoid.layer(), geoid));
0140     }
0141 
0142     void setPlainOptions(const NavigatorPlainOptions& options) {
0143       static_cast<NavigatorPlainOptions&>(*this) = options;
0144     }
0145   };
0146 
0147   /// @brief Nested State struct
0148   ///
0149   /// It acts as an internal state which is created for every propagation and
0150   /// meant to keep thread-local navigation information.
0151   struct State {
0152     explicit State(const Options& options_) : options(options_) {}
0153 
0154     Options options;
0155 
0156     // Navigation on surface level
0157     /// the vector of navigation surfaces to work through
0158     NavigationSurfaces navSurfaces = {};
0159     /// the current surface index of the navigation state
0160     std::optional<std::size_t> navSurfaceIndex;
0161 
0162     // Navigation on layer level
0163     /// the vector of navigation layers to work through
0164     NavigationLayers navLayers = {};
0165     /// the current layer index of the navigation state
0166     std::optional<std::size_t> navLayerIndex;
0167 
0168     // Navigation on volume level
0169     /// the vector of boundary surfaces to work through
0170     NavigationBoundaries navBoundaries = {};
0171     /// the current boundary index of the navigation state
0172     std::optional<std::size_t> navBoundaryIndex;
0173 
0174     SurfaceIntersection& navSurface() {
0175       return navSurfaces.at(navSurfaceIndex.value());
0176     }
0177     LayerIntersection& navLayer() {
0178       return navLayers.at(navLayerIndex.value());
0179     }
0180     BoundaryIntersection& navBoundary() {
0181       return navBoundaries.at(navBoundaryIndex.value());
0182     }
0183 
0184     const TrackingVolume* startVolume = nullptr;
0185     const Layer* startLayer = nullptr;
0186     const Surface* startSurface = nullptr;
0187     const TrackingVolume* currentVolume = nullptr;
0188     const Layer* currentLayer = nullptr;
0189     const Surface* currentSurface = nullptr;
0190     const Surface* targetSurface = nullptr;
0191 
0192     bool navigationBreak = false;
0193     Stage navigationStage = Stage::initial;
0194 
0195     NavigatorStatistics statistics;
0196 
0197     NavigationStream stream;
0198 
0199     void resetAfterLayerSwitch() {
0200       navSurfaces.clear();
0201       navSurfaceIndex.reset();
0202     }
0203 
0204     void resetAfterVolumeSwitch() {
0205       resetAfterLayerSwitch();
0206 
0207       navLayers.clear();
0208       navLayerIndex.reset();
0209       navBoundaries.clear();
0210       navBoundaryIndex.reset();
0211 
0212       currentLayer = nullptr;
0213     }
0214 
0215     void reset() {
0216       resetAfterVolumeSwitch();
0217 
0218       currentVolume = nullptr;
0219       currentSurface = nullptr;
0220 
0221       navigationBreak = false;
0222       navigationStage = Stage::initial;
0223     }
0224   };
0225 
0226   /// Constructor with configuration object
0227   ///
0228   /// @param cfg The navigator configuration
0229   /// @param _logger a logger instance
0230   explicit Navigator(Config cfg,
0231                      std::shared_ptr<const Logger> _logger =
0232                          getDefaultLogger("Navigator", Logging::Level::INFO))
0233       : m_cfg{std::move(cfg)}, m_logger{std::move(_logger)} {
0234     if (m_cfg.trackingGeometry == nullptr) {
0235       throw std::invalid_argument("Navigator: No tracking geometry provided.");
0236     }
0237     m_geometryVersion = m_cfg.trackingGeometry->geometryVersion();
0238   }
0239 
0240   State makeState(const Options& options) const {
0241     State state(options);
0242     return state;
0243   }
0244 
0245   const Surface* currentSurface(const State& state) const {
0246     return state.currentSurface;
0247   }
0248 
0249   const TrackingVolume* currentVolume(const State& state) const {
0250     return state.currentVolume;
0251   }
0252 
0253   const IVolumeMaterial* currentVolumeMaterial(const State& state) const {
0254     if (state.currentVolume == nullptr) {
0255       return nullptr;
0256     }
0257     return state.currentVolume->volumeMaterial();
0258   }
0259 
0260   const Surface* startSurface(const State& state) const {
0261     return state.startSurface;
0262   }
0263 
0264   const Surface* targetSurface(const State& state) const {
0265     return state.targetSurface;
0266   }
0267 
0268   bool endOfWorldReached(const State& state) const {
0269     return state.currentVolume == nullptr;
0270   }
0271 
0272   bool navigationBreak(const State& state) const {
0273     return state.navigationBreak;
0274   }
0275 
0276   /// @brief Initialize the navigator state
0277   ///
0278   /// This function initializes the navigator state for a new propagation.
0279   ///
0280   /// @param state The navigation state
0281   /// @param position The start position
0282   /// @param direction The start direction
0283   /// @param propagationDirection The propagation direction
0284   ///
0285   /// @return Indication if the initialization was successful
0286   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0287                                         const Vector3& direction,
0288                                         Direction propagationDirection) const {
0289     (void)propagationDirection;
0290 
0291     ACTS_VERBOSE(volInfo(state) << "Initialization.");
0292 
0293     auto printGeometryVersion = [](auto ver) {
0294       using enum TrackingGeometry::GeometryVersion;
0295       switch (ver) {
0296         case Gen1:
0297           return "Gen1";
0298         case Gen3:
0299           return "Gen3";
0300         default:
0301           throw std::runtime_error("Unknown geometry version.");
0302       }
0303     };
0304     ACTS_VERBOSE(volInfo(state) << "Geometry version is: "
0305                                 << printGeometryVersion(m_geometryVersion));
0306 
0307     state.reset();
0308 
0309     // Empirical pre-allocation of candidates for the next navigation iteration.
0310     // @TODO: Make this user configurable through the configuration
0311     state.stream.candidates().reserve(50);
0312 
0313     state.startSurface = state.options.startSurface;
0314     state.targetSurface = state.options.targetSurface;
0315 
0316     // @TODO: Implement fast initialization with Gen3. This requires the volume lookup to work properly
0317 
0318     // Fast Navigation initialization for start condition:
0319     // - short-cut through object association, saves navigation in the
0320     // - geometry and volume tree search for the lowest volume
0321     if (state.startSurface != nullptr &&
0322         state.startSurface->associatedLayer() != nullptr) {
0323       ACTS_VERBOSE(
0324           volInfo(state)
0325           << "Fast start initialization through association from Surface.");
0326 
0327       state.startLayer = state.startSurface->associatedLayer();
0328       state.startVolume = state.startLayer->trackingVolume();
0329     } else if (state.startVolume != nullptr) {
0330       ACTS_VERBOSE(
0331           volInfo(state)
0332           << "Fast start initialization through association from Volume.");
0333 
0334       state.startLayer = state.startVolume->associatedLayer(
0335           state.options.geoContext, position);
0336     } else {
0337       ACTS_VERBOSE(volInfo(state)
0338                    << "Slow start initialization through search.");
0339       ACTS_VERBOSE(volInfo(state)
0340                    << "Starting from position " << toString(position)
0341                    << " and direction " << toString(direction));
0342 
0343       // current volume and layer search through global search
0344       state.startVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0345           state.options.geoContext, position);
0346 
0347       if (state.startVolume != nullptr) {
0348         state.startLayer = state.startVolume->associatedLayer(
0349             state.options.geoContext, position);
0350       } else {
0351         ACTS_ERROR(volInfo(state)
0352                    << "No start volume resolved. Nothing left to do.");
0353         state.navigationBreak = true;
0354       }
0355     }
0356 
0357     state.currentVolume = state.startVolume;
0358     state.currentLayer = state.startLayer;
0359     state.currentSurface = state.startSurface;
0360 
0361     if (state.currentVolume != nullptr) {
0362       ACTS_VERBOSE(volInfo(state) << "Start volume resolved "
0363                                   << state.currentVolume->geometryId());
0364 
0365       if (!state.currentVolume->inside(position,
0366                                        state.options.surfaceTolerance)) {
0367         ACTS_DEBUG(
0368             volInfo(state)
0369             << "We did not end up inside the expected volume. position = "
0370             << position.transpose());
0371 
0372         return Result<void>::failure(NavigatorError::NotInsideExpectedVolume);
0373       }
0374     }
0375     if (state.currentLayer != nullptr) {
0376       ACTS_VERBOSE(volInfo(state) << "Start layer resolved "
0377                                   << state.currentLayer->geometryId());
0378     }
0379     if (state.currentSurface != nullptr) {
0380       ACTS_VERBOSE(volInfo(state) << "Start surface resolved "
0381                                   << state.currentSurface->geometryId());
0382 
0383       if (!state.currentSurface->isOnSurface(
0384               state.options.geoContext, position, direction,
0385               BoundaryTolerance::Infinite(), state.options.surfaceTolerance)) {
0386         ACTS_DEBUG(volInfo(state)
0387                    << "We did not end up on the expected surface. surface = "
0388                    << state.currentSurface->geometryId()
0389                    << " position = " << position.transpose()
0390                    << " direction = " << direction.transpose());
0391 
0392         return Result<void>::failure(NavigatorError::NotOnExpectedSurface);
0393       }
0394     }
0395 
0396     return Result<void>::success();
0397   }
0398 
0399   /// @brief Get the next target surface
0400   ///
0401   /// This function gets the next target surface for the propagation.
0402   ///
0403   /// @param state The navigation state
0404   /// @param position The current position
0405   /// @param direction The current direction
0406   ///
0407   /// @return The next target surface
0408   NavigationTarget nextTarget(State& state, const Vector3& position,
0409                               const Vector3& direction) const {
0410     // Reset the current surface
0411     state.currentSurface = nullptr;
0412 
0413     if (inactive(state)) {
0414       return NavigationTarget::None();
0415     }
0416 
0417     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::nextTarget.");
0418 
0419     auto tryGetNextTarget = [&]() -> NavigationTarget {
0420       // Try targeting the surfaces - then layers - then boundaries
0421 
0422       if (state.navigationStage == Stage::initial) {
0423         ACTS_VERBOSE(volInfo(state) << "Target surfaces.");
0424         state.navigationStage = Stage::surfaceTarget;
0425       }
0426 
0427       if (state.navigationStage == Stage::surfaceTarget) {
0428         if (!state.navSurfaceIndex.has_value()) {
0429           // First time, resolve the surfaces
0430           resolveSurfaces(state, position, direction);
0431           state.navSurfaceIndex = 0;
0432         } else {
0433           ++state.navSurfaceIndex.value();
0434         }
0435         if (state.navSurfaceIndex.value() < state.navSurfaces.size()) {
0436           ACTS_VERBOSE(volInfo(state) << "Target set to next surface.");
0437           return NavigationTarget(*state.navSurface().object(),
0438                                   state.navSurface().index(),
0439                                   BoundaryTolerance::None());
0440         } else {
0441           // This was the last surface, switch to layers
0442           ACTS_VERBOSE(volInfo(state) << "Target layers.");
0443           if (m_geometryVersion == GeometryVersion::Gen1) {
0444             state.navigationStage = Stage::layerTarget;
0445           } else {
0446             state.navigationStage = Stage::boundaryTarget;
0447           }
0448         }
0449       }
0450 
0451       if (state.navigationStage == Stage::layerTarget) {
0452         if (!state.navLayerIndex.has_value()) {
0453           // First time, resolve the layers
0454           resolveLayers(state, position, direction);
0455           state.navLayerIndex = 0;
0456         } else {
0457           ++state.navLayerIndex.value();
0458         }
0459         if (state.navLayerIndex.value() < state.navLayers.size()) {
0460           ACTS_VERBOSE(volInfo(state) << "Target set to next layer.");
0461           return NavigationTarget(*state.navLayer().first.object(),
0462                                   state.navLayer().first.index(),
0463                                   BoundaryTolerance::None());
0464         } else {
0465           // This was the last layer, switch to boundaries
0466           ACTS_VERBOSE(volInfo(state) << "Target boundaries.");
0467           state.navigationStage = Stage::boundaryTarget;
0468         }
0469       }
0470 
0471       if (state.navigationStage == Stage::boundaryTarget) {
0472         if (!state.navBoundaryIndex.has_value()) {
0473           // First time, resolve the boundaries
0474           resolveBoundaries(state, position, direction);
0475           state.navBoundaryIndex = 0;
0476         } else {
0477           ++state.navBoundaryIndex.value();
0478         }
0479         if (state.navBoundaryIndex.value() < state.navBoundaries.size()) {
0480           ACTS_VERBOSE(volInfo(state) << "Target set to next boundary.");
0481           return NavigationTarget(*state.navBoundary().intersection.object(),
0482                                   state.navBoundary().intersection.index(),
0483                                   BoundaryTolerance::None());
0484         } else {
0485           // This was the last boundary, we have to leave the volume somehow,
0486           // renavigate
0487           ACTS_VERBOSE(volInfo(state)
0488                        << "Boundary targets exhausted. Renavigate.");
0489         }
0490       }
0491 
0492       ACTS_VERBOSE(volInfo(state)
0493                    << "Unknown state. No target found. Renavigate.");
0494       return NavigationTarget::None();
0495     };
0496 
0497     NavigationTarget nextTarget = tryGetNextTarget();
0498     if (!nextTarget.isNone()) {
0499       return nextTarget;
0500     }
0501 
0502     state.reset();
0503     ++state.statistics.nRenavigations;
0504 
0505     // We might have punched through a boundary and entered another volume
0506     // so we have to reinitialize
0507     state.currentVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0508         state.options.geoContext, position);
0509 
0510     if (state.currentVolume == nullptr) {
0511       ACTS_VERBOSE(volInfo(state) << "No volume found, stop navigation.");
0512       state.navigationBreak = true;
0513       return NavigationTarget::None();
0514     }
0515 
0516     state.currentLayer = state.currentVolume->associatedLayer(
0517         state.options.geoContext, position);
0518 
0519     ACTS_VERBOSE(volInfo(state) << "Resolved volume and layer.");
0520 
0521     // Rerun the targeting
0522     nextTarget = tryGetNextTarget();
0523     if (!nextTarget.isNone()) {
0524       return nextTarget;
0525     }
0526 
0527     ACTS_VERBOSE(
0528         volInfo(state)
0529         << "No targets found again, we got really lost! Stop navigation.");
0530     state.navigationBreak = true;
0531     return NavigationTarget::None();
0532   }
0533 
0534   /// @brief Check if the current target is still valid
0535   ///
0536   /// This function checks if the target is valid.
0537   ///
0538   /// @param state The navigation state
0539   /// @param position The current position
0540   /// @param direction The current direction
0541   ///
0542   /// @return True if the target is valid
0543   bool checkTargetValid(const State& state, const Vector3& position,
0544                         const Vector3& direction) const {
0545     (void)position;
0546     (void)direction;
0547 
0548     return state.navigationStage != Stage::initial;
0549   }
0550 
0551   /// @brief Handle the surface reached
0552   ///
0553   /// This function handles the surface reached.
0554   ///
0555   /// @param state The navigation state
0556   /// @param position The current position
0557   /// @param direction The current direction
0558   /// @param surface The surface reached
0559   void handleSurfaceReached(State& state, const Vector3& position,
0560                             const Vector3& direction,
0561                             const Surface& surface) const {
0562     if (inactive(state)) {
0563       return;
0564     }
0565 
0566     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::handleSurfaceReached.");
0567 
0568     state.currentSurface = &surface;
0569 
0570     ACTS_VERBOSE(volInfo(state)
0571                  << "Current surface: " << state.currentSurface->geometryId());
0572 
0573     if (state.navigationStage == Stage::surfaceTarget &&
0574         state.navSurface().object() == &surface) {
0575       ACTS_VERBOSE(volInfo(state) << "Handling surface status.");
0576 
0577       return;
0578     }
0579 
0580     if (state.navigationStage == Stage::layerTarget &&
0581         state.navLayer().first.object() == &surface) {
0582       ACTS_VERBOSE(volInfo(state) << "Handling layer status.");
0583 
0584       // Switch to the next layer
0585       state.currentLayer = state.navLayer().second;
0586       state.navigationStage = Stage::surfaceTarget;
0587 
0588       // partial reset
0589       state.resetAfterLayerSwitch();
0590 
0591       return;
0592     }
0593 
0594     if (state.navigationStage == Stage::boundaryTarget &&
0595         state.navBoundary().intersection.object() == &surface) {
0596       ACTS_VERBOSE(volInfo(state) << "Handling boundary status.");
0597 
0598       if (m_geometryVersion == GeometryVersion::Gen1) {
0599         // Switch to the next volume using the boundary
0600         const BoundarySurface* boundary = state.navBoundary().boundarySurface;
0601         assert(boundary != nullptr && "Retrieved boundary surface is nullptr");
0602         state.currentVolume = boundary->attachedVolume(state.options.geoContext,
0603                                                        position, direction);
0604       } else {
0605         const Portal* portal = state.navBoundary().portal;
0606         assert(portal != nullptr && "Retrieved portal is nullptr");
0607         auto res = portal->resolveVolume(state.options.geoContext, position,
0608                                          direction);
0609         if (!res.ok()) {
0610           ACTS_ERROR(volInfo(state)
0611                      << "Failed to resolve volume through portal: "
0612                      << res.error().message());
0613           return;
0614         }
0615 
0616         state.currentVolume = res.value();
0617       }
0618 
0619       // partial reset
0620       state.resetAfterVolumeSwitch();
0621 
0622       if (state.currentVolume != nullptr) {
0623         ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0624         if (m_geometryVersion == GeometryVersion::Gen1) {
0625           state.navigationStage = Stage::layerTarget;
0626         } else {
0627           state.navigationStage = Stage::surfaceTarget;
0628         }
0629       } else {
0630         ACTS_VERBOSE(volInfo(state)
0631                      << "No more volume to progress to, stopping navigation.");
0632         state.navigationBreak = true;
0633       }
0634 
0635       return;
0636     }
0637 
0638     ACTS_ERROR(volInfo(state) << "Surface reached but unknown state.");
0639   }
0640 
0641  private:
0642   /// @brief Resolve compatible surfaces
0643   ///
0644   /// This function resolves the compatible surfaces for the navigation.
0645   ///
0646   /// @param state The navigation state
0647   /// @param position The current position
0648   /// @param direction The current direction
0649   void resolveSurfaces(State& state, const Vector3& position,
0650                        const Vector3& direction) const {
0651     ACTS_VERBOSE(volInfo(state) << "Searching for compatible surfaces.");
0652 
0653     if (m_geometryVersion == GeometryVersion::Gen1) {
0654       const Layer* currentLayer = state.currentLayer;
0655 
0656       if (currentLayer == nullptr) {
0657         ACTS_VERBOSE(volInfo(state) << "No layer to resolve surfaces.");
0658         return;
0659       }
0660 
0661       const Surface* layerSurface = &currentLayer->surfaceRepresentation();
0662 
0663       NavigationOptions<Surface> navOpts;
0664       navOpts.resolveSensitive = m_cfg.resolveSensitive;
0665       navOpts.resolveMaterial = m_cfg.resolveMaterial;
0666       navOpts.resolvePassive = m_cfg.resolvePassive;
0667       navOpts.startObject = state.currentSurface;
0668       navOpts.endObject = state.targetSurface;
0669       navOpts.nearLimit = state.options.nearLimit;
0670       navOpts.farLimit = state.options.farLimit;
0671 
0672       if (!state.options.externalSurfaces.empty()) {
0673         auto layerId = layerSurface->geometryId().layer();
0674         auto externalSurfaceRange =
0675             state.options.externalSurfaces.equal_range(layerId);
0676         navOpts.externalSurfaces.reserve(
0677             state.options.externalSurfaces.count(layerId));
0678         for (auto itSurface = externalSurfaceRange.first;
0679              itSurface != externalSurfaceRange.second; itSurface++) {
0680           navOpts.externalSurfaces.push_back(itSurface->second);
0681         }
0682       }
0683 
0684       // Request the compatible surfaces
0685       state.navSurfaces = currentLayer->compatibleSurfaces(
0686           state.options.geoContext, position, direction, navOpts);
0687       std::ranges::sort(state.navSurfaces,
0688                         SurfaceIntersection::pathLengthOrder);
0689     } else {
0690       // @TODO: What to do with external surfaces?
0691       // Gen 3 !
0692       state.stream.reset();
0693       AppendOnlyNavigationStream appendOnly{state.stream};
0694       NavigationArguments args;
0695       args.position = position;
0696       args.direction = direction;
0697       args.wantsPortals = false;
0698       args.wantsSurfaces = true;
0699       state.currentVolume->initializeNavigationCandidates(args, appendOnly,
0700                                                           logger());
0701 
0702       // Filter out portals before intersection
0703 
0704       ACTS_VERBOSE(volInfo(state)
0705                    << "Found " << state.stream.candidates().size()
0706                    << " navigation candidates.");
0707 
0708       state.stream.initialize(state.options.geoContext, {position, direction},
0709                               BoundaryTolerance::None(),
0710                               state.options.surfaceTolerance);
0711       ACTS_VERBOSE(volInfo(state)
0712                    << "Now " << state.stream.candidates().size()
0713                    << " navigation candidates after initialization");
0714 
0715       state.navSurfaces.clear();
0716 
0717       auto it = std::ranges::find_if(
0718           state.stream.candidates(), [&](const auto& candidate) {
0719             return detail::checkPathLength(candidate.intersection.pathLength(),
0720                                            state.options.nearLimit,
0721                                            state.options.farLimit, logger());
0722           });
0723 
0724       for (; it != state.stream.candidates().end(); ++it) {
0725         state.navSurfaces.emplace_back(it->intersection);
0726       }
0727     }
0728 
0729     // Print surface information
0730     if (logger().doPrint(Logging::VERBOSE)) {
0731       std::ostringstream os;
0732       os << state.navSurfaces.size();
0733       os << " surface candidates found at path(s): ";
0734       for (auto& sfc : state.navSurfaces) {
0735         os << sfc.pathLength() << "  ";
0736       }
0737       logger().log(Logging::VERBOSE, os.str());
0738     }
0739 
0740     if (state.navSurfaces.empty()) {
0741       ACTS_VERBOSE(volInfo(state) << "No surface candidates found.");
0742     }
0743   }
0744 
0745   /// @brief Resolve compatible layers
0746   ///
0747   /// This function resolves the compatible layers for the navigation.
0748   ///
0749   /// @param state The navigation state
0750   /// @param position The current position
0751   /// @param direction The current direction
0752   void resolveLayers(State& state, const Vector3& position,
0753                      const Vector3& direction) const {
0754     ACTS_VERBOSE(volInfo(state) << "Searching for compatible layers.");
0755 
0756     NavigationOptions<Layer> navOpts;
0757     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0758     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0759     navOpts.resolvePassive = m_cfg.resolvePassive;
0760     navOpts.startObject = state.currentLayer;
0761     navOpts.nearLimit = state.options.nearLimit;
0762     navOpts.farLimit = state.options.farLimit;
0763 
0764     // Request the compatible layers
0765     state.navLayers = state.currentVolume->compatibleLayers(
0766         state.options.geoContext, position, direction, navOpts);
0767     std::ranges::sort(state.navLayers, [](const auto& a, const auto& b) {
0768       return SurfaceIntersection::pathLengthOrder(a.first, b.first);
0769     });
0770 
0771     // Print layer information
0772     if (logger().doPrint(Logging::VERBOSE)) {
0773       std::ostringstream os;
0774       os << state.navLayers.size();
0775       os << " layer candidates found at path(s): ";
0776       for (auto& lc : state.navLayers) {
0777         os << lc.first.pathLength() << "  ";
0778       }
0779       logger().log(Logging::VERBOSE, os.str());
0780     }
0781 
0782     if (state.navLayers.empty()) {
0783       ACTS_VERBOSE(volInfo(state) << "No layer candidates found.");
0784     }
0785   }
0786 
0787   /// @brief Resolve compatible boundaries
0788   ///
0789   /// This function resolves the compatible boundaries for the navigation.
0790   ///
0791   /// @param state The navigation state
0792   /// @param position The current position
0793   /// @param direction The current direction
0794   void resolveBoundaries(State& state, const Vector3& position,
0795                          const Vector3& direction) const {
0796     ACTS_VERBOSE(volInfo(state) << "Searching for compatible boundaries.");
0797 
0798     NavigationOptions<Surface> navOpts;
0799     navOpts.startObject = state.currentSurface;
0800     navOpts.nearLimit = state.options.nearLimit;
0801     navOpts.farLimit = state.options.farLimit;
0802 
0803     ACTS_VERBOSE(volInfo(state)
0804                  << "Try to find boundaries, we are at: " << toString(position)
0805                  << ", dir: " << toString(direction));
0806 
0807     if (m_geometryVersion == GeometryVersion::Gen1) {
0808       // Request the compatible boundaries
0809       state.navBoundaries = state.currentVolume->compatibleBoundaries(
0810           state.options.geoContext, position, direction, navOpts, logger());
0811       std::ranges::sort(state.navBoundaries, [](const auto& a, const auto& b) {
0812         return SurfaceIntersection::pathLengthOrder(a.intersection,
0813                                                     b.intersection);
0814       });
0815     } else {
0816       // Gen 3 !
0817       state.stream.reset();
0818       AppendOnlyNavigationStream appendOnly{state.stream};
0819       NavigationArguments args;
0820       args.position = position;
0821       args.direction = direction;
0822       args.wantsPortals = true;
0823       args.wantsSurfaces = false;
0824       state.currentVolume->initializeNavigationCandidates(args, appendOnly,
0825                                                           logger());
0826 
0827       ACTS_VERBOSE(volInfo(state)
0828                    << "Found " << state.stream.candidates().size()
0829                    << " navigation candidates.");
0830 
0831       state.stream.initialize(state.options.geoContext, {position, direction},
0832                               BoundaryTolerance::None(),
0833                               state.options.surfaceTolerance);
0834 
0835       state.navBoundaries.clear();
0836       for (auto& candidate : state.stream.candidates()) {
0837         if (!detail::checkPathLength(candidate.intersection.pathLength(),
0838                                      state.options.nearLimit,
0839                                      state.options.farLimit, logger())) {
0840           continue;
0841         }
0842 
0843         state.navBoundaries.emplace_back(candidate.intersection, nullptr,
0844                                          candidate.portal);
0845       }
0846     }
0847 
0848     // Print boundary information
0849     if (logger().doPrint(Logging::VERBOSE)) {
0850       std::ostringstream os;
0851       os << state.navBoundaries.size();
0852       os << " boundary candidates found at path(s): ";
0853       for (auto& bc : state.navBoundaries) {
0854         os << bc.intersection.pathLength() << "  ";
0855       }
0856       logger().log(Logging::VERBOSE, os.str());
0857     }
0858 
0859     if (state.navBoundaries.empty()) {
0860       ACTS_VERBOSE(volInfo(state) << "No boundary candidates found.");
0861     }
0862   }
0863 
0864   /// @brief Check if the navigator is inactive
0865   ///
0866   /// This function checks if the navigator is inactive.
0867   ///
0868   /// @param state The navigation state
0869   ///
0870   /// @return True if the navigator is inactive
0871   bool inactive(const State& state) const {
0872     // Turn the navigator into void when you are instructed to do nothing
0873     if (!m_cfg.resolveSensitive && !m_cfg.resolveMaterial &&
0874         !m_cfg.resolvePassive) {
0875       return true;
0876     }
0877 
0878     if (state.navigationBreak) {
0879       return true;
0880     }
0881 
0882     return false;
0883   }
0884 
0885  private:
0886   template <typename propagator_state_t>
0887   std::string volInfo(const propagator_state_t& state) const {
0888     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0889                                            : "No Volume") +
0890            " | ";
0891   }
0892 
0893   const Logger& logger() const { return *m_logger; }
0894 
0895   Config m_cfg;
0896 
0897   // Cached so we don't have to query the TrackingGeometry constantly.
0898   TrackingGeometry::GeometryVersion m_geometryVersion;
0899 
0900   std::shared_ptr<const Logger> m_logger;
0901 };
0902 
0903 }  // namespace Acts