Back to home page

EIC code displayed by LXR

 
 

    


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

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/Geometry/GeometryIdentifier.hpp"
0012 #include "Acts/Geometry/Layer.hpp"
0013 #include "Acts/Geometry/TrackingGeometry.hpp"
0014 #include "Acts/Geometry/TrackingVolume.hpp"
0015 #include "Acts/Propagator/NavigationTarget.hpp"
0016 #include "Acts/Propagator/NavigatorOptions.hpp"
0017 #include "Acts/Propagator/NavigatorStatistics.hpp"
0018 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0019 #include "Acts/Surfaces/Surface.hpp"
0020 #include "Acts/Utilities/Intersection.hpp"
0021 #include "Acts/Utilities/Logger.hpp"
0022 #include "Acts/Utilities/StringHelpers.hpp"
0023 
0024 #include <algorithm>
0025 #include <optional>
0026 #include <sstream>
0027 #include <string>
0028 
0029 #include <boost/container/small_vector.hpp>
0030 
0031 namespace Acts {
0032 
0033 /// @brief The navigation options for the tracking geometry
0034 ///
0035 /// @tparam object_t Type of the object for navigation to check against
0036 template <typename object_t>
0037 struct NavigationOptions {
0038   /// The boundary check directive
0039   BoundaryTolerance boundaryTolerance = BoundaryTolerance::None();
0040 
0041   // How to resolve the geometry
0042   /// Always look for sensitive
0043   bool resolveSensitive = true;
0044   /// Always look for material
0045   bool resolveMaterial = true;
0046   /// always look for passive
0047   bool resolvePassive = false;
0048 
0049   /// Hint for start object
0050   const object_t* startObject = nullptr;
0051   /// Hint for end object
0052   const object_t* endObject = nullptr;
0053 
0054   /// External surface identifier for which the boundary check is ignored
0055   std::vector<GeometryIdentifier> externalSurfaces = {};
0056 
0057   /// The minimum distance for a surface to be considered
0058   double nearLimit = 0;
0059   /// The maximum distance for a surface to be considered
0060   double farLimit = std::numeric_limits<double>::max();
0061 };
0062 
0063 /// @brief Steers the propagation through the geometry by providing the next
0064 /// surface to be targeted.
0065 ///
0066 /// The Navigator is part of the propagation and responsible for steering
0067 /// the surface sequence to encounter all the relevant surfaces which are
0068 /// intersected by the trajectory.
0069 ///
0070 /// The current navigation stage is cached in the state struct and updated
0071 /// when necessary. If any surface in the extrapolation flow is hit, it is
0072 /// set to the navigation state, such that other actors can deal with it.
0073 ///
0074 /// The current target surface is referenced by an index which points into
0075 /// the navigation candidates. The navigation candidates are ordered by the
0076 /// path length to the surface. If a surface is hit, the
0077 /// `state.currentSurface` pointer is set. This actors to observe
0078 /// that we are on a surface.
0079 ///
0080 class Navigator {
0081  public:
0082   using NavigationSurfaces =
0083       boost::container::small_vector<SurfaceIntersection, 10>;
0084 
0085   using NavigationLayers =
0086       boost::container::small_vector<LayerIntersection, 10>;
0087 
0088   using NavigationBoundaries =
0089       boost::container::small_vector<BoundaryIntersection, 4>;
0090 
0091   using ExternalSurfaces = std::multimap<std::uint64_t, GeometryIdentifier>;
0092 
0093   /// The navigation stage
0094   enum struct Stage : int {
0095     initial = 0,
0096     surfaceTarget = 1,
0097     layerTarget = 2,
0098     boundaryTarget = 3,
0099   };
0100 
0101   /// The navigator configuration
0102   struct Config {
0103     /// Tracking Geometry for this Navigator
0104     std::shared_ptr<const TrackingGeometry> trackingGeometry{nullptr};
0105 
0106     /// stop at every sensitive surface (whether it has material or not)
0107     bool resolveSensitive = true;
0108     /// stop at every material surface (whether it is passive or not)
0109     bool resolveMaterial = true;
0110     /// stop at every surface regardless what it is
0111     bool resolvePassive = false;
0112   };
0113 
0114   /// The navigator options
0115   struct Options : public NavigatorPlainOptions {
0116     explicit Options(const GeometryContext& gctx)
0117         : NavigatorPlainOptions(gctx) {}
0118 
0119     /// The surface tolerance
0120     double surfaceTolerance = s_onSurfaceTolerance;
0121 
0122     /// The near limit to resolve surfaces
0123     double nearLimit = s_onSurfaceTolerance;
0124 
0125     /// The far limit to resolve surfaces
0126     double farLimit = std::numeric_limits<double>::max();
0127 
0128     /// Externally provided surfaces - these are tried to be hit
0129     ExternalSurfaces externalSurfaces = {};
0130 
0131     void insertExternalSurface(GeometryIdentifier geoid) {
0132       externalSurfaces.insert(
0133           std::pair<std::uint64_t, GeometryIdentifier>(geoid.layer(), geoid));
0134     }
0135 
0136     void setPlainOptions(const NavigatorPlainOptions& options) {
0137       static_cast<NavigatorPlainOptions&>(*this) = options;
0138     }
0139   };
0140 
0141   /// @brief Nested State struct
0142   ///
0143   /// It acts as an internal state which is created for every propagation and
0144   /// meant to keep thread-local navigation information.
0145   struct State {
0146     explicit State(const Options& options_) : options(options_) {}
0147 
0148     Options options;
0149 
0150     // Navigation on surface level
0151     /// the vector of navigation surfaces to work through
0152     NavigationSurfaces navSurfaces = {};
0153     /// the current surface index of the navigation state
0154     std::optional<std::size_t> navSurfaceIndex;
0155 
0156     // Navigation on layer level
0157     /// the vector of navigation layers to work through
0158     NavigationLayers navLayers = {};
0159     /// the current layer index of the navigation state
0160     std::optional<std::size_t> navLayerIndex;
0161 
0162     // Navigation on volume level
0163     /// the vector of boundary surfaces to work through
0164     NavigationBoundaries navBoundaries = {};
0165     /// the current boundary index of the navigation state
0166     std::optional<std::size_t> navBoundaryIndex;
0167 
0168     SurfaceIntersection& navSurface() {
0169       return navSurfaces.at(navSurfaceIndex.value());
0170     }
0171     LayerIntersection& navLayer() {
0172       return navLayers.at(navLayerIndex.value());
0173     }
0174     BoundaryIntersection& navBoundary() {
0175       return navBoundaries.at(navBoundaryIndex.value());
0176     }
0177 
0178     const TrackingVolume* startVolume = nullptr;
0179     const Layer* startLayer = nullptr;
0180     const Surface* startSurface = nullptr;
0181     const TrackingVolume* currentVolume = nullptr;
0182     const Layer* currentLayer = nullptr;
0183     const Surface* currentSurface = nullptr;
0184     const Surface* targetSurface = nullptr;
0185 
0186     bool navigationBreak = false;
0187     Stage navigationStage = Stage::initial;
0188 
0189     NavigatorStatistics statistics;
0190 
0191     void resetAfterLayerSwitch() {
0192       navSurfaces.clear();
0193       navSurfaceIndex.reset();
0194     }
0195 
0196     void resetAfterVolumeSwitch() {
0197       resetAfterLayerSwitch();
0198 
0199       navLayers.clear();
0200       navLayerIndex.reset();
0201       navBoundaries.clear();
0202       navBoundaryIndex.reset();
0203 
0204       currentLayer = nullptr;
0205     }
0206 
0207     void reset() {
0208       resetAfterVolumeSwitch();
0209 
0210       currentVolume = nullptr;
0211       currentSurface = nullptr;
0212 
0213       navigationBreak = false;
0214       navigationStage = Stage::initial;
0215     }
0216   };
0217 
0218   /// Constructor with configuration object
0219   ///
0220   /// @param cfg The navigator configuration
0221   /// @param _logger a logger instance
0222   explicit Navigator(Config cfg,
0223                      std::shared_ptr<const Logger> _logger =
0224                          getDefaultLogger("Navigator", Logging::Level::INFO))
0225       : m_cfg{std::move(cfg)}, m_logger{std::move(_logger)} {}
0226 
0227   State makeState(const Options& options) const {
0228     State state(options);
0229     state.startSurface = options.startSurface;
0230     state.targetSurface = options.targetSurface;
0231     return state;
0232   }
0233 
0234   const Surface* currentSurface(const State& state) const {
0235     return state.currentSurface;
0236   }
0237 
0238   const TrackingVolume* currentVolume(const State& state) const {
0239     return state.currentVolume;
0240   }
0241 
0242   const IVolumeMaterial* currentVolumeMaterial(const State& state) const {
0243     if (state.currentVolume == nullptr) {
0244       return nullptr;
0245     }
0246     return state.currentVolume->volumeMaterial();
0247   }
0248 
0249   const Surface* startSurface(const State& state) const {
0250     return state.startSurface;
0251   }
0252 
0253   const Surface* targetSurface(const State& state) const {
0254     return state.targetSurface;
0255   }
0256 
0257   bool endOfWorldReached(const State& state) const {
0258     return state.currentVolume == nullptr;
0259   }
0260 
0261   bool navigationBreak(const State& state) const {
0262     return state.navigationBreak;
0263   }
0264 
0265   /// @brief Initialize the navigator
0266   ///
0267   /// This function initializes the navigator for a new propagation.
0268   ///
0269   /// @param state The navigation state
0270   /// @param position The start position
0271   /// @param direction The start direction
0272   /// @param propagationDirection The propagation direction
0273   void initialize(State& state, const Vector3& position,
0274                   const Vector3& direction,
0275                   Direction propagationDirection) const {
0276     (void)propagationDirection;
0277 
0278     ACTS_VERBOSE(volInfo(state) << "Initialization.");
0279 
0280     state.reset();
0281 
0282     // Fast Navigation initialization for start condition:
0283     // - short-cut through object association, saves navigation in the
0284     // - geometry and volume tree search for the lowest volume
0285     if (state.startSurface != nullptr &&
0286         state.startSurface->associatedLayer() != nullptr) {
0287       ACTS_VERBOSE(
0288           volInfo(state)
0289           << "Fast start initialization through association from Surface.");
0290 
0291       state.startLayer = state.startSurface->associatedLayer();
0292       state.startVolume = state.startLayer->trackingVolume();
0293     } else if (state.startVolume != nullptr) {
0294       ACTS_VERBOSE(
0295           volInfo(state)
0296           << "Fast start initialization through association from Volume.");
0297 
0298       state.startLayer = state.startVolume->associatedLayer(
0299           state.options.geoContext, position);
0300     } else {
0301       ACTS_VERBOSE(volInfo(state)
0302                    << "Slow start initialization through search.");
0303       ACTS_VERBOSE(volInfo(state)
0304                    << "Starting from position " << toString(position)
0305                    << " and direction " << toString(direction));
0306 
0307       // current volume and layer search through global search
0308       state.startVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0309           state.options.geoContext, position);
0310 
0311       if (state.startVolume != nullptr) {
0312         state.startLayer = state.startVolume->associatedLayer(
0313             state.options.geoContext, position);
0314       } else {
0315         ACTS_ERROR(volInfo(state)
0316                    << "No start volume resolved. Nothing left to do.");
0317         state.navigationBreak = true;
0318       }
0319     }
0320 
0321     state.currentVolume = state.startVolume;
0322     state.currentLayer = state.startLayer;
0323     state.currentSurface = state.startSurface;
0324 
0325     if (state.currentVolume != nullptr) {
0326       ACTS_VERBOSE(volInfo(state) << "Start volume resolved "
0327                                   << state.currentVolume->geometryId());
0328       assert(state.currentVolume->inside(position,
0329                                          state.options.surfaceTolerance) &&
0330              "We did not end up inside the volume.");
0331     }
0332     if (state.currentLayer != nullptr) {
0333       ACTS_VERBOSE(volInfo(state) << "Start layer resolved "
0334                                   << state.currentLayer->geometryId());
0335     }
0336     if (state.currentSurface != nullptr) {
0337       ACTS_VERBOSE(volInfo(state) << "Start surface resolved "
0338                                   << state.currentSurface->geometryId());
0339       assert(state.currentSurface->isOnSurface(
0340                  state.options.geoContext, position, direction,
0341                  BoundaryTolerance::Infinite(),
0342                  state.options.surfaceTolerance) &&
0343              "Stepper not on surface");
0344     }
0345   }
0346 
0347   /// @brief Get the next target surface
0348   ///
0349   /// This function gets the next target surface for the propagation.
0350   ///
0351   /// @param state The navigation state
0352   /// @param position The current position
0353   /// @param direction The current direction
0354   ///
0355   /// @return The next target surface
0356   NavigationTarget nextTarget(State& state, const Vector3& position,
0357                               const Vector3& direction) const {
0358     if (inactive(state)) {
0359       return NavigationTarget::None();
0360     }
0361 
0362     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::nextTarget.");
0363 
0364     // Reset the current surface
0365     state.currentSurface = nullptr;
0366 
0367     auto tryGetNextTarget = [&]() -> NavigationTarget {
0368       // Try targeting the surfaces - then layers - then boundaries
0369 
0370       if (state.navigationStage == Stage::initial) {
0371         ACTS_VERBOSE(volInfo(state) << "Target surfaces.");
0372         state.navigationStage = Stage::surfaceTarget;
0373       }
0374 
0375       if (state.navigationStage == Stage::surfaceTarget) {
0376         if (!state.navSurfaceIndex.has_value()) {
0377           // First time, resolve the surfaces
0378           resolveSurfaces(state, position, direction);
0379           state.navSurfaceIndex = 0;
0380         } else {
0381           ++state.navSurfaceIndex.value();
0382         }
0383         if (state.navSurfaceIndex.value() < state.navSurfaces.size()) {
0384           ACTS_VERBOSE(volInfo(state) << "Target set to next surface.");
0385           return NavigationTarget(*state.navSurface().object(),
0386                                   state.navSurface().index(),
0387                                   BoundaryTolerance::None());
0388         } else {
0389           // This was the last surface, switch to layers
0390           ACTS_VERBOSE(volInfo(state) << "Target layers.");
0391           state.navigationStage = Stage::layerTarget;
0392         }
0393       }
0394 
0395       if (state.navigationStage == Stage::layerTarget) {
0396         if (!state.navLayerIndex.has_value()) {
0397           // First time, resolve the layers
0398           resolveLayers(state, position, direction);
0399           state.navLayerIndex = 0;
0400         } else {
0401           ++state.navLayerIndex.value();
0402         }
0403         if (state.navLayerIndex.value() < state.navLayers.size()) {
0404           ACTS_VERBOSE(volInfo(state) << "Target set to next layer.");
0405           return NavigationTarget(*state.navLayer().first.object(),
0406                                   state.navLayer().first.index(),
0407                                   BoundaryTolerance::None());
0408         } else {
0409           // This was the last layer, switch to boundaries
0410           ACTS_VERBOSE(volInfo(state) << "Target boundaries.");
0411           state.navigationStage = Stage::boundaryTarget;
0412         }
0413       }
0414 
0415       if (state.navigationStage == Stage::boundaryTarget) {
0416         if (!state.navBoundaryIndex.has_value()) {
0417           // First time, resolve the boundaries
0418           resolveBoundaries(state, position, direction);
0419           state.navBoundaryIndex = 0;
0420         } else {
0421           ++state.navBoundaryIndex.value();
0422         }
0423         if (state.navBoundaryIndex.value() < state.navBoundaries.size()) {
0424           ACTS_VERBOSE(volInfo(state) << "Target set to next boundary.");
0425           return NavigationTarget(*state.navBoundary().first.object(),
0426                                   state.navBoundary().first.index(),
0427                                   BoundaryTolerance::None());
0428         } else {
0429           // This was the last boundary, we have to leave the volume somehow,
0430           // renavigate
0431           ACTS_VERBOSE(volInfo(state)
0432                        << "Boundary targets exhausted. Renavigate.");
0433         }
0434       }
0435 
0436       ACTS_VERBOSE(volInfo(state)
0437                    << "Unknown state. No target found. Renavigate.");
0438       return NavigationTarget::None();
0439     };
0440 
0441     NavigationTarget nextTarget = tryGetNextTarget();
0442     if (!nextTarget.isNone()) {
0443       return nextTarget;
0444     }
0445 
0446     state.reset();
0447     ++state.statistics.nRenavigations;
0448 
0449     // We might have punched through a boundary and entered another volume
0450     // so we have to reinitialize
0451     state.currentVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0452         state.options.geoContext, position);
0453 
0454     if (state.currentVolume == nullptr) {
0455       ACTS_VERBOSE(volInfo(state) << "No volume found, stop navigation.");
0456       state.navigationBreak = true;
0457       return NavigationTarget::None();
0458     }
0459 
0460     state.currentLayer = state.currentVolume->associatedLayer(
0461         state.options.geoContext, position);
0462 
0463     ACTS_VERBOSE(volInfo(state) << "Resolved volume and layer.");
0464 
0465     // Rerun the targeting
0466     nextTarget = tryGetNextTarget();
0467     if (!nextTarget.isNone()) {
0468       return nextTarget;
0469     }
0470 
0471     ACTS_VERBOSE(
0472         volInfo(state)
0473         << "No targets found again, we got really lost! Stop navigation.");
0474     state.navigationBreak = true;
0475     return NavigationTarget::None();
0476   }
0477 
0478   /// @brief Check if the current target is still valid
0479   ///
0480   /// This function checks if the target is valid.
0481   ///
0482   /// @param state The navigation state
0483   /// @param position The current position
0484   /// @param direction The current direction
0485   ///
0486   /// @return True if the target is valid
0487   bool checkTargetValid(const State& state, const Vector3& position,
0488                         const Vector3& direction) const {
0489     (void)position;
0490     (void)direction;
0491 
0492     return state.navigationStage != Stage::initial;
0493   }
0494 
0495   /// @brief Handle the surface reached
0496   ///
0497   /// This function handles the surface reached.
0498   ///
0499   /// @param state The navigation state
0500   /// @param position The current position
0501   /// @param direction The current direction
0502   /// @param surface The surface reached
0503   void handleSurfaceReached(State& state, const Vector3& position,
0504                             const Vector3& direction,
0505                             const Surface& surface) const {
0506     if (inactive(state)) {
0507       return;
0508     }
0509 
0510     ACTS_VERBOSE(volInfo(state) << "Entering Navigator::handleSurfaceReached.");
0511 
0512     state.currentSurface = &surface;
0513 
0514     if (state.navigationStage == Stage::surfaceTarget &&
0515         state.navSurface().object() == &surface) {
0516       ACTS_VERBOSE(volInfo(state) << "Handling surface status.");
0517 
0518       return;
0519     }
0520 
0521     if (state.navigationStage == Stage::layerTarget &&
0522         state.navLayer().first.object() == &surface) {
0523       ACTS_VERBOSE(volInfo(state) << "Handling layer status.");
0524 
0525       // Switch to the next layer
0526       state.currentLayer = state.navLayer().second;
0527       state.navigationStage = Stage::surfaceTarget;
0528 
0529       // partial reset
0530       state.resetAfterLayerSwitch();
0531 
0532       return;
0533     }
0534 
0535     if (state.navigationStage == Stage::boundaryTarget &&
0536         state.navBoundary().first.object() == &surface) {
0537       ACTS_VERBOSE(volInfo(state) << "Handling boundary status.");
0538 
0539       // Switch to the next volume using the boundary
0540       const BoundarySurface* boundary = state.navBoundary().second;
0541       state.currentVolume = boundary->attachedVolume(state.options.geoContext,
0542                                                      position, direction);
0543 
0544       // partial reset
0545       state.resetAfterVolumeSwitch();
0546 
0547       if (state.currentVolume != nullptr) {
0548         ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0549         state.navigationStage = Stage::layerTarget;
0550       } else {
0551         ACTS_VERBOSE(volInfo(state)
0552                      << "No more volume to progress to, stopping navigation.");
0553         state.navigationBreak = true;
0554       }
0555 
0556       return;
0557     }
0558 
0559     ACTS_ERROR(volInfo(state) << "Surface reached but unknown state.");
0560   }
0561 
0562  private:
0563   /// @brief Resolve compatible surfaces
0564   ///
0565   /// This function resolves the compatible surfaces for the navigation.
0566   ///
0567   /// @param state The navigation state
0568   /// @param position The current position
0569   /// @param direction The current direction
0570   void resolveSurfaces(State& state, const Vector3& position,
0571                        const Vector3& direction) const {
0572     ACTS_VERBOSE(volInfo(state) << "Searching for compatible surfaces.");
0573 
0574     const Layer* currentLayer = state.currentLayer;
0575 
0576     if (currentLayer == nullptr) {
0577       ACTS_VERBOSE(volInfo(state) << "No layer to resolve surfaces.");
0578       return;
0579     }
0580 
0581     const Surface* layerSurface = &currentLayer->surfaceRepresentation();
0582 
0583     NavigationOptions<Surface> navOpts;
0584     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0585     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0586     navOpts.resolvePassive = m_cfg.resolvePassive;
0587     navOpts.startObject = state.currentSurface;
0588     navOpts.endObject = state.targetSurface;
0589     navOpts.nearLimit = state.options.nearLimit;
0590     navOpts.farLimit = state.options.farLimit;
0591 
0592     if (!state.options.externalSurfaces.empty()) {
0593       auto layerId = layerSurface->geometryId().layer();
0594       auto externalSurfaceRange =
0595           state.options.externalSurfaces.equal_range(layerId);
0596       navOpts.externalSurfaces.reserve(
0597           state.options.externalSurfaces.count(layerId));
0598       for (auto itSurface = externalSurfaceRange.first;
0599            itSurface != externalSurfaceRange.second; itSurface++) {
0600         navOpts.externalSurfaces.push_back(itSurface->second);
0601       }
0602     }
0603 
0604     // Request the compatible surfaces
0605     state.navSurfaces = currentLayer->compatibleSurfaces(
0606         state.options.geoContext, position, direction, navOpts);
0607     std::ranges::sort(state.navSurfaces, SurfaceIntersection::pathLengthOrder);
0608 
0609     // Print surface information
0610     if (logger().doPrint(Logging::VERBOSE)) {
0611       std::ostringstream os;
0612       os << state.navSurfaces.size();
0613       os << " surface candidates found at path(s): ";
0614       for (auto& sfc : state.navSurfaces) {
0615         os << sfc.pathLength() << "  ";
0616       }
0617       logger().log(Logging::VERBOSE, os.str());
0618     }
0619 
0620     if (state.navSurfaces.empty()) {
0621       ACTS_VERBOSE(volInfo(state) << "No surface candidates found.");
0622     }
0623   }
0624 
0625   /// @brief Resolve compatible layers
0626   ///
0627   /// This function resolves the compatible layers for the navigation.
0628   ///
0629   /// @param state The navigation state
0630   /// @param position The current position
0631   /// @param direction The current direction
0632   void resolveLayers(State& state, const Vector3& position,
0633                      const Vector3& direction) const {
0634     ACTS_VERBOSE(volInfo(state) << "Searching for compatible layers.");
0635 
0636     NavigationOptions<Layer> navOpts;
0637     navOpts.resolveSensitive = m_cfg.resolveSensitive;
0638     navOpts.resolveMaterial = m_cfg.resolveMaterial;
0639     navOpts.resolvePassive = m_cfg.resolvePassive;
0640     navOpts.startObject = state.currentLayer;
0641     navOpts.nearLimit = state.options.nearLimit;
0642     navOpts.farLimit = state.options.farLimit;
0643 
0644     // Request the compatible layers
0645     state.navLayers = state.currentVolume->compatibleLayers(
0646         state.options.geoContext, position, direction, navOpts);
0647     std::ranges::sort(state.navLayers, [](const auto& a, const auto& b) {
0648       return SurfaceIntersection::pathLengthOrder(a.first, b.first);
0649     });
0650 
0651     // Print layer information
0652     if (logger().doPrint(Logging::VERBOSE)) {
0653       std::ostringstream os;
0654       os << state.navLayers.size();
0655       os << " layer candidates found at path(s): ";
0656       for (auto& lc : state.navLayers) {
0657         os << lc.first.pathLength() << "  ";
0658       }
0659       logger().log(Logging::VERBOSE, os.str());
0660     }
0661 
0662     if (state.navLayers.empty()) {
0663       ACTS_VERBOSE(volInfo(state) << "No layer candidates found.");
0664     }
0665   }
0666 
0667   /// @brief Resolve compatible boundaries
0668   ///
0669   /// This function resolves the compatible boundaries for the navigation.
0670   ///
0671   /// @param state The navigation state
0672   /// @param position The current position
0673   /// @param direction The current direction
0674   void resolveBoundaries(State& state, const Vector3& position,
0675                          const Vector3& direction) const {
0676     ACTS_VERBOSE(volInfo(state) << "Searching for compatible boundaries.");
0677 
0678     NavigationOptions<Surface> navOpts;
0679     navOpts.startObject = state.currentSurface;
0680     navOpts.nearLimit = state.options.nearLimit;
0681     navOpts.farLimit = state.options.farLimit;
0682 
0683     ACTS_VERBOSE(volInfo(state)
0684                  << "Try to find boundaries, we are at: " << toString(position)
0685                  << ", dir: " << toString(direction));
0686 
0687     // Request the compatible boundaries
0688     state.navBoundaries = state.currentVolume->compatibleBoundaries(
0689         state.options.geoContext, position, direction, navOpts, logger());
0690     std::ranges::sort(state.navBoundaries, [](const auto& a, const auto& b) {
0691       return SurfaceIntersection::pathLengthOrder(a.first, b.first);
0692     });
0693 
0694     // Print boundary information
0695     if (logger().doPrint(Logging::VERBOSE)) {
0696       std::ostringstream os;
0697       os << state.navBoundaries.size();
0698       os << " boundary candidates found at path(s): ";
0699       for (auto& bc : state.navBoundaries) {
0700         os << bc.first.pathLength() << "  ";
0701       }
0702       logger().log(Logging::VERBOSE, os.str());
0703     }
0704 
0705     if (state.navBoundaries.empty()) {
0706       ACTS_VERBOSE(volInfo(state) << "No boundary candidates found.");
0707     }
0708   }
0709 
0710   /// @brief Check if the navigator is inactive
0711   ///
0712   /// This function checks if the navigator is inactive.
0713   ///
0714   /// @param state The navigation state
0715   ///
0716   /// @return True if the navigator is inactive
0717   bool inactive(const State& state) const {
0718     // Void behavior in case no tracking geometry is present
0719     if (m_cfg.trackingGeometry == nullptr) {
0720       return true;
0721     }
0722 
0723     // Turn the navigator into void when you are instructed to do nothing
0724     if (!m_cfg.resolveSensitive && !m_cfg.resolveMaterial &&
0725         !m_cfg.resolvePassive) {
0726       return true;
0727     }
0728 
0729     if (state.navigationBreak) {
0730       return true;
0731     }
0732 
0733     return false;
0734   }
0735 
0736  private:
0737   template <typename propagator_state_t>
0738   std::string volInfo(const propagator_state_t& state) const {
0739     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0740                                            : "No Volume") +
0741            " | ";
0742   }
0743 
0744   const Logger& logger() const { return *m_logger; }
0745 
0746   Config m_cfg;
0747 
0748   std::shared_ptr<const Logger> m_logger;
0749 };
0750 
0751 }  // namespace Acts