Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-15 08:13:44

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().surface(),
0438                                   state.navSurface().index(),
0439                                   state.navSurface().boundaryTolerance());
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.surface(),
0462                                   state.navLayer().first.index(),
0463                                   state.navLayer().first.boundaryTolerance());
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(
0482               state.navBoundary().intersection.surface(),
0483               state.navBoundary().intersection.index(),
0484               state.navBoundary().intersection.boundaryTolerance());
0485         } else {
0486           // This was the last boundary, we have to leave the volume somehow,
0487           // renavigate
0488           ACTS_VERBOSE(volInfo(state)
0489                        << "Boundary targets exhausted. Renavigate.");
0490         }
0491       }
0492 
0493       ACTS_VERBOSE(volInfo(state)
0494                    << "Unknown state. No target found. Renavigate.");
0495       return NavigationTarget::None();
0496     };
0497 
0498     NavigationTarget nextTarget = tryGetNextTarget();
0499     if (!nextTarget.isNone()) {
0500       return nextTarget;
0501     }
0502 
0503     state.reset();
0504     ++state.statistics.nRenavigations;
0505 
0506     // We might have punched through a boundary and entered another volume
0507     // so we have to reinitialize
0508     state.currentVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0509         state.options.geoContext, position);
0510 
0511     if (state.currentVolume == nullptr) {
0512       ACTS_VERBOSE(volInfo(state) << "No volume found, stop navigation.");
0513       state.navigationBreak = true;
0514       return NavigationTarget::None();
0515     }
0516 
0517     state.currentLayer = state.currentVolume->associatedLayer(
0518         state.options.geoContext, position);
0519 
0520     ACTS_VERBOSE(volInfo(state) << "Resolved volume and layer.");
0521 
0522     // Rerun the targeting
0523     nextTarget = tryGetNextTarget();
0524     if (!nextTarget.isNone()) {
0525       return nextTarget;
0526     }
0527 
0528     ACTS_VERBOSE(
0529         volInfo(state)
0530         << "No targets found again, we got really lost! Stop navigation.");
0531     state.navigationBreak = true;
0532     return NavigationTarget::None();
0533   }
0534 
0535   /// @brief Check if the current target is still valid
0536   ///
0537   /// This function checks if the target is valid.
0538   ///
0539   /// @param state The navigation state
0540   /// @param position The current position
0541   /// @param direction The current direction
0542   ///
0543   /// @return True if the target is valid
0544   bool checkTargetValid(const State& state, const Vector3& position,
0545                         const Vector3& direction) const {
0546     (void)position;
0547     (void)direction;
0548 
0549     return state.navigationStage != Stage::initial;
0550   }
0551 
0552   /// @brief Handle the surface reached
0553   ///
0554   /// This function handles the surface reached.
0555   ///
0556   /// @param state The navigation state
0557   /// @param position The current position
0558   /// @param direction The current direction
0559   /// @param surface The surface reached
0560   void handleSurfaceReached(State& state, const Vector3& position,
0561                             const Vector3& direction,
0562                             const Surface& surface) const {
0563     if (inactive(state)) {
0564       return;
0565     }
0566 
0567     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::handleSurfaceReached.");
0568 
0569     state.currentSurface = &surface;
0570 
0571     ACTS_VERBOSE(volInfo(state)
0572                  << "Current surface: " << state.currentSurface->geometryId());
0573 
0574     if (state.navigationStage == Stage::surfaceTarget &&
0575         &state.navSurface().surface() == &surface) {
0576       ACTS_VERBOSE(volInfo(state) << "Handling surface status.");
0577 
0578       return;
0579     }
0580 
0581     if (state.navigationStage == Stage::layerTarget &&
0582         &state.navLayer().first.surface() == &surface) {
0583       ACTS_VERBOSE(volInfo(state) << "Handling layer status.");
0584 
0585       // Switch to the next layer
0586       state.currentLayer = state.navLayer().second;
0587       state.navigationStage = Stage::surfaceTarget;
0588 
0589       // partial reset
0590       state.resetAfterLayerSwitch();
0591 
0592       return;
0593     }
0594 
0595     if (state.navigationStage == Stage::boundaryTarget &&
0596         &state.navBoundary().intersection.surface() == &surface) {
0597       ACTS_VERBOSE(volInfo(state) << "Handling boundary status.");
0598 
0599       if (m_geometryVersion == GeometryVersion::Gen1) {
0600         // Switch to the next volume using the boundary
0601         const BoundarySurface* boundary = state.navBoundary().boundarySurface;
0602         assert(boundary != nullptr && "Retrieved boundary surface is nullptr");
0603         state.currentVolume = boundary->attachedVolume(state.options.geoContext,
0604                                                        position, direction);
0605       } else {
0606         const Portal* portal = state.navBoundary().portal;
0607         assert(portal != nullptr && "Retrieved portal is nullptr");
0608         auto res = portal->resolveVolume(state.options.geoContext, position,
0609                                          direction);
0610         if (!res.ok()) {
0611           ACTS_ERROR(volInfo(state)
0612                      << "Failed to resolve volume through portal: "
0613                      << res.error().message());
0614           return;
0615         }
0616 
0617         state.currentVolume = res.value();
0618       }
0619 
0620       // partial reset
0621       state.resetAfterVolumeSwitch();
0622 
0623       if (state.currentVolume != nullptr) {
0624         ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0625         if (m_geometryVersion == GeometryVersion::Gen1) {
0626           state.navigationStage = Stage::layerTarget;
0627         } else {
0628           state.navigationStage = Stage::surfaceTarget;
0629         }
0630       } else {
0631         ACTS_VERBOSE(volInfo(state)
0632                      << "No more volume to progress to, stopping navigation.");
0633         state.navigationBreak = true;
0634       }
0635 
0636       return;
0637     }
0638 
0639     ACTS_ERROR(volInfo(state) << "Surface reached but unknown state.");
0640   }
0641 
0642  private:
0643   /// @brief Resolve compatible surfaces
0644   ///
0645   /// This function resolves the compatible surfaces for the navigation.
0646   ///
0647   /// @param state The navigation state
0648   /// @param position The current position
0649   /// @param direction The current direction
0650   void resolveSurfaces(State& state, const Vector3& position,
0651                        const Vector3& direction) const {
0652     ACTS_VERBOSE(volInfo(state) << "Searching for compatible surfaces.");
0653 
0654     if (m_geometryVersion == GeometryVersion::Gen1) {
0655       const Layer* currentLayer = state.currentLayer;
0656 
0657       if (currentLayer == nullptr) {
0658         ACTS_VERBOSE(volInfo(state) << "No layer to resolve surfaces.");
0659         return;
0660       }
0661 
0662       const Surface* layerSurface = &currentLayer->surfaceRepresentation();
0663 
0664       NavigationOptions<Surface> navOpts;
0665       navOpts.resolveSensitive = m_cfg.resolveSensitive;
0666       navOpts.resolveMaterial = m_cfg.resolveMaterial;
0667       navOpts.resolvePassive = m_cfg.resolvePassive;
0668       navOpts.startObject = state.currentSurface;
0669       navOpts.endObject = state.targetSurface;
0670       navOpts.nearLimit = state.options.nearLimit;
0671       navOpts.farLimit = state.options.farLimit;
0672 
0673       if (!state.options.externalSurfaces.empty()) {
0674         auto layerId = layerSurface->geometryId().layer();
0675         auto externalSurfaceRange =
0676             state.options.externalSurfaces.equal_range(layerId);
0677         navOpts.externalSurfaces.reserve(
0678             state.options.externalSurfaces.count(layerId));
0679         for (auto itSurface = externalSurfaceRange.first;
0680              itSurface != externalSurfaceRange.second; itSurface++) {
0681           navOpts.externalSurfaces.push_back(itSurface->second);
0682         }
0683       }
0684 
0685       // Request the compatible surfaces
0686       state.navSurfaces = currentLayer->compatibleSurfaces(
0687           state.options.geoContext, position, direction, navOpts);
0688       // Sort the surfaces by path length.
0689       // Special care is taken for the external surfaces which should always
0690       // come first, so they are preferred to be targeted and hit first.
0691       std::ranges::sort(
0692           state.navSurfaces,
0693           [&state](const SurfaceIntersection& a, const SurfaceIntersection& b) {
0694             // Prefer to sort by path length. We assume surfaces are at the same
0695             // distance if the difference is smaller than the tolerance.
0696             if (std::abs(a.pathLength() - b.pathLength()) >
0697                 state.options.surfaceTolerance) {
0698               return SurfaceIntersection::pathLengthOrder(a, b);
0699             }
0700             // If the path length is practically the same, sort by geometry.
0701             // First we check if one of the surfaces is external.
0702             bool aIsExternal = a.boundaryTolerance().isInfinite();
0703             bool bIsExternal = b.boundaryTolerance().isInfinite();
0704             if (aIsExternal == bIsExternal) {
0705               // If both are external or both are not external, sort by geometry
0706               // identifier
0707               return a.surface().geometryId() < b.surface().geometryId();
0708             }
0709             // If only one is external, it should come first
0710             return aIsExternal;
0711           });
0712       // For now we implicitly remove overlapping surfaces.
0713       // For track finding it might be useful to discover overlapping surfaces
0714       // and check for compatible measurements. This is under investigation
0715       // and might be implemented in the future.
0716       auto toBeRemoved = std::ranges::unique(
0717           state.navSurfaces, [&](const auto& a, const auto& b) {
0718             return std::abs(a.pathLength() - b.pathLength()) <
0719                    state.options.surfaceTolerance;
0720           });
0721       if (toBeRemoved.begin() != toBeRemoved.end()) {
0722         ACTS_VERBOSE(volInfo(state)
0723                      << "Removing "
0724                      << std::distance(toBeRemoved.begin(), toBeRemoved.end())
0725                      << " overlapping surfaces.");
0726       }
0727       state.navSurfaces.erase(toBeRemoved.begin(), toBeRemoved.end());
0728     } else {
0729       // @TODO: What to do with external surfaces?
0730       // Gen 3 !
0731       state.stream.reset();
0732       AppendOnlyNavigationStream appendOnly{state.stream};
0733       NavigationArguments args;
0734       args.position = position;
0735       args.direction = direction;
0736       args.wantsPortals = false;
0737       args.wantsSurfaces = true;
0738       state.currentVolume->initializeNavigationCandidates(args, appendOnly,
0739                                                           logger());
0740 
0741       // Filter out portals before intersection
0742 
0743       ACTS_VERBOSE(volInfo(state)
0744                    << "Found " << state.stream.candidates().size()
0745                    << " navigation candidates.");
0746 
0747       state.stream.initialize(state.options.geoContext, {position, direction},
0748                               BoundaryTolerance::None(),
0749                               state.options.surfaceTolerance);
0750       ACTS_VERBOSE(volInfo(state)
0751                    << "Now " << state.stream.candidates().size()
0752                    << " navigation candidates after initialization");
0753 
0754       state.navSurfaces.clear();
0755 
0756       auto it = std::ranges::find_if(
0757           state.stream.candidates(), [&](const auto& candidate) {
0758             return detail::checkPathLength(candidate.intersection.pathLength(),
0759                                            state.options.nearLimit,
0760                                            state.options.farLimit, logger());
0761           });
0762 
0763       for (; it != state.stream.candidates().end(); ++it) {
0764         state.navSurfaces.emplace_back(it->intersection);
0765       }
0766     }
0767 
0768     // Print surface information
0769     if (logger().doPrint(Logging::VERBOSE)) {
0770       std::ostringstream os;
0771       os << state.navSurfaces.size();
0772       os << " surface candidates found at path(s): ";
0773       for (auto& sfc : state.navSurfaces) {
0774         os << sfc.pathLength() << "  ";
0775       }
0776       logger().log(Logging::VERBOSE, os.str());
0777     }
0778 
0779     if (state.navSurfaces.empty()) {
0780       ACTS_VERBOSE(volInfo(state) << "No surface candidates found.");
0781     }
0782   }
0783 
0784   /// @brief Resolve compatible layers
0785   ///
0786   /// This function resolves the compatible layers for the navigation.
0787   ///
0788   /// @param state The navigation state
0789   /// @param position The current position
0790   /// @param direction The current direction
0791   void resolveLayers(State& state, const Vector3& position,
0792                      const Vector3& direction) const {
0793     ACTS_VERBOSE(volInfo(state) << "Searching for compatible layers.");
0794 
0795     NavigationOptions<Layer> navOpts;
0796     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0797     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0798     navOpts.resolvePassive = m_cfg.resolvePassive;
0799     navOpts.startObject = state.currentLayer;
0800     navOpts.nearLimit = state.options.nearLimit;
0801     navOpts.farLimit = state.options.farLimit;
0802 
0803     // Request the compatible layers
0804     state.navLayers = state.currentVolume->compatibleLayers(
0805         state.options.geoContext, position, direction, navOpts);
0806     std::ranges::sort(state.navLayers, [](const auto& a, const auto& b) {
0807       return SurfaceIntersection::pathLengthOrder(a.first, b.first);
0808     });
0809 
0810     // Print layer information
0811     if (logger().doPrint(Logging::VERBOSE)) {
0812       std::ostringstream os;
0813       os << state.navLayers.size();
0814       os << " layer candidates found at path(s): ";
0815       for (auto& lc : state.navLayers) {
0816         os << lc.first.pathLength() << "  ";
0817       }
0818       logger().log(Logging::VERBOSE, os.str());
0819     }
0820 
0821     if (state.navLayers.empty()) {
0822       ACTS_VERBOSE(volInfo(state) << "No layer candidates found.");
0823     }
0824   }
0825 
0826   /// @brief Resolve compatible boundaries
0827   ///
0828   /// This function resolves the compatible boundaries for the navigation.
0829   ///
0830   /// @param state The navigation state
0831   /// @param position The current position
0832   /// @param direction The current direction
0833   void resolveBoundaries(State& state, const Vector3& position,
0834                          const Vector3& direction) const {
0835     ACTS_VERBOSE(volInfo(state) << "Searching for compatible boundaries.");
0836 
0837     NavigationOptions<Surface> navOpts;
0838     navOpts.startObject = state.currentSurface;
0839     navOpts.nearLimit = state.options.nearLimit;
0840     navOpts.farLimit = state.options.farLimit;
0841 
0842     ACTS_VERBOSE(volInfo(state)
0843                  << "Try to find boundaries, we are at: " << toString(position)
0844                  << ", dir: " << toString(direction));
0845 
0846     if (m_geometryVersion == GeometryVersion::Gen1) {
0847       // Request the compatible boundaries
0848       state.navBoundaries = state.currentVolume->compatibleBoundaries(
0849           state.options.geoContext, position, direction, navOpts, logger());
0850       std::ranges::sort(state.navBoundaries, [](const auto& a, const auto& b) {
0851         return SurfaceIntersection::pathLengthOrder(a.intersection,
0852                                                     b.intersection);
0853       });
0854     } else {
0855       // Gen 3 !
0856       state.stream.reset();
0857       AppendOnlyNavigationStream appendOnly{state.stream};
0858       NavigationArguments args;
0859       args.position = position;
0860       args.direction = direction;
0861       args.wantsPortals = true;
0862       args.wantsSurfaces = false;
0863       state.currentVolume->initializeNavigationCandidates(args, appendOnly,
0864                                                           logger());
0865 
0866       ACTS_VERBOSE(volInfo(state)
0867                    << "Found " << state.stream.candidates().size()
0868                    << " navigation candidates.");
0869 
0870       state.stream.initialize(state.options.geoContext, {position, direction},
0871                               BoundaryTolerance::None(),
0872                               state.options.surfaceTolerance);
0873 
0874       state.navBoundaries.clear();
0875       for (auto& candidate : state.stream.candidates()) {
0876         if (!detail::checkPathLength(candidate.intersection.pathLength(),
0877                                      state.options.nearLimit,
0878                                      state.options.farLimit, logger())) {
0879           continue;
0880         }
0881 
0882         state.navBoundaries.emplace_back(candidate.intersection, nullptr,
0883                                          candidate.portal);
0884       }
0885     }
0886 
0887     // Print boundary information
0888     if (logger().doPrint(Logging::VERBOSE)) {
0889       std::ostringstream os;
0890       os << state.navBoundaries.size();
0891       os << " boundary candidates found at path(s): ";
0892       for (auto& bc : state.navBoundaries) {
0893         os << bc.intersection.pathLength() << "  ";
0894       }
0895       logger().log(Logging::VERBOSE, os.str());
0896     }
0897 
0898     if (state.navBoundaries.empty()) {
0899       ACTS_VERBOSE(volInfo(state) << "No boundary candidates found.");
0900     }
0901   }
0902 
0903   /// @brief Check if the navigator is inactive
0904   ///
0905   /// This function checks if the navigator is inactive.
0906   ///
0907   /// @param state The navigation state
0908   ///
0909   /// @return True if the navigator is inactive
0910   bool inactive(const State& state) const {
0911     // Turn the navigator into void when you are instructed to do nothing
0912     if (!m_cfg.resolveSensitive && !m_cfg.resolveMaterial &&
0913         !m_cfg.resolvePassive) {
0914       return true;
0915     }
0916 
0917     if (state.navigationBreak) {
0918       return true;
0919     }
0920 
0921     return false;
0922   }
0923 
0924  private:
0925   template <typename propagator_state_t>
0926   std::string volInfo(const propagator_state_t& state) const {
0927     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0928                                            : "No Volume") +
0929            " | ";
0930   }
0931 
0932   const Logger& logger() const { return *m_logger; }
0933 
0934   Config m_cfg;
0935 
0936   // Cached so we don't have to query the TrackingGeometry constantly.
0937   TrackingGeometry::GeometryVersion m_geometryVersion;
0938 
0939   std::shared_ptr<const Logger> m_logger;
0940 };
0941 
0942 }  // namespace Acts