Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:07:52

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 #include "Acts/Propagator/Navigator.hpp"
0010 
0011 #include "Acts/Geometry/BoundarySurfaceT.hpp"
0012 #include "Acts/Geometry/Portal.hpp"
0013 #include "Acts/Propagator/NavigatorError.hpp"
0014 #include "Acts/Surfaces/Surface.hpp"
0015 #include "Acts/Utilities/Intersection.hpp"
0016 #include "Acts/Utilities/StringHelpers.hpp"
0017 
0018 #include <algorithm>
0019 #include <cassert>
0020 #include <sstream>
0021 
0022 namespace Acts {
0023 
0024 Navigator::Navigator(Config cfg, std::shared_ptr<const Logger> _logger)
0025     : m_cfg{std::move(cfg)}, m_logger{std::move(_logger)} {
0026   if (m_cfg.trackingGeometry == nullptr) {
0027     throw std::invalid_argument("Navigator: No tracking geometry provided.");
0028   }
0029   m_geometryVersion = m_cfg.trackingGeometry->geometryVersion();
0030 }
0031 
0032 Navigator::State Navigator::makeState(const Options& options) const {
0033   State state(options);
0034   return state;
0035 }
0036 
0037 const Surface* Navigator::currentSurface(const State& state) const {
0038   return state.currentSurface;
0039 }
0040 
0041 const TrackingVolume* Navigator::currentVolume(const State& state) const {
0042   return state.currentVolume;
0043 }
0044 
0045 const IVolumeMaterial* Navigator::currentVolumeMaterial(
0046     const State& state) const {
0047   if (state.currentVolume == nullptr) {
0048     return nullptr;
0049   }
0050   return state.currentVolume->volumeMaterial();
0051 }
0052 
0053 const Surface* Navigator::startSurface(const State& state) const {
0054   return state.startSurface;
0055 }
0056 
0057 const Surface* Navigator::targetSurface(const State& state) const {
0058   return state.targetSurface;
0059 }
0060 
0061 bool Navigator::endOfWorldReached(const State& state) const {
0062   return state.currentVolume == nullptr;
0063 }
0064 
0065 bool Navigator::navigationBreak(const State& state) const {
0066   return state.navigationBreak;
0067 }
0068 
0069 Result<void> Navigator::initialize(State& state, const Vector3& position,
0070                                    const Vector3& direction,
0071                                    Direction propagationDirection) const {
0072   static_cast<void>(propagationDirection);
0073 
0074   ACTS_VERBOSE(volInfo(state) << "Initialization.");
0075 
0076   auto printGeometryVersion = [](auto ver) {
0077     using enum TrackingGeometry::GeometryVersion;
0078     switch (ver) {
0079       case Gen1:
0080         return "Gen1";
0081       case Gen3:
0082         return "Gen3";
0083       default:
0084         throw std::runtime_error("Unknown geometry version.");
0085     }
0086   };
0087   ACTS_VERBOSE(volInfo(state) << "Geometry version is: "
0088                               << printGeometryVersion(m_geometryVersion));
0089 
0090   state.reset();
0091 
0092   if (m_geometryVersion == GeometryVersion::Gen3) {
0093     // Empirical pre-allocation of candidates for the next navigation
0094     // iteration.
0095     // @TODO: Make this user configurable through the configuration
0096     state.stream.candidates().reserve(50);
0097 
0098     state.freeCandidates.clear();
0099     state.freeCandidates.reserve(state.options.freeSurfaces.size());
0100     for (const Surface* candidate : state.options.freeSurfaces) {
0101       state.freeCandidates.emplace_back(candidate, false);
0102     }
0103   }
0104 
0105   state.startSurface = state.options.startSurface;
0106   state.targetSurface = state.options.targetSurface;
0107 
0108   // @TODO: Implement fast initialization with Gen3. This requires the volume
0109   // lookup to work properly
0110 
0111   // Fast Navigation initialization for start condition:
0112   // - short-cut through object association, saves navigation in the
0113   // - geometry and volume tree search for the lowest volume
0114   if (state.startSurface != nullptr &&
0115       state.startSurface->associatedLayer() != nullptr) {
0116     ACTS_VERBOSE(
0117         volInfo(state)
0118         << "Fast start initialization through association from Surface.");
0119 
0120     state.startLayer = state.startSurface->associatedLayer();
0121     state.startVolume = state.startLayer->trackingVolume();
0122   } else if (state.startVolume != nullptr) {
0123     ACTS_VERBOSE(
0124         volInfo(state)
0125         << "Fast start initialization through association from Volume.");
0126 
0127     state.startLayer =
0128         state.startVolume->associatedLayer(state.options.geoContext, position);
0129   } else {
0130     ACTS_VERBOSE(volInfo(state) << "Slow start initialization through search.");
0131     ACTS_VERBOSE(volInfo(state)
0132                  << "Starting from position " << toString(position)
0133                  << " and direction " << toString(direction));
0134 
0135     // current volume and layer search through global search
0136     state.startVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0137         state.options.geoContext, position);
0138 
0139     if (state.startVolume != nullptr) {
0140       state.startLayer = state.startVolume->associatedLayer(
0141           state.options.geoContext, position);
0142     } else {
0143       ACTS_DEBUG(volInfo(state)
0144                  << "No start volume resolved. Nothing left to do.");
0145       state.navigationBreak = true;
0146       return Result<void>::failure(NavigatorError::NoStartVolume);
0147     }
0148   }
0149 
0150   state.currentVolume = state.startVolume;
0151   state.currentLayer = state.startLayer;
0152   state.currentSurface = state.startSurface;
0153 
0154   if (state.currentVolume != nullptr) {
0155     ACTS_VERBOSE(volInfo(state) << "Start volume resolved "
0156                                 << state.currentVolume->geometryId());
0157 
0158     if (!state.currentVolume->inside(state.options.geoContext, position,
0159                                      state.options.surfaceTolerance)) {
0160       ACTS_DEBUG(volInfo(state)
0161                  << "We did not end up inside the expected volume. position = "
0162                  << position.transpose());
0163 
0164       return Result<void>::failure(NavigatorError::NotInsideExpectedVolume);
0165     }
0166   }
0167   if (state.currentLayer != nullptr) {
0168     ACTS_VERBOSE(volInfo(state) << "Start layer resolved "
0169                                 << state.currentLayer->geometryId());
0170   }
0171   if (state.currentSurface != nullptr) {
0172     ACTS_VERBOSE(volInfo(state) << "Start surface resolved "
0173                                 << state.currentSurface->geometryId());
0174 
0175     if (!state.currentSurface->isOnSurface(
0176             state.options.geoContext, position, direction,
0177             BoundaryTolerance::Infinite(), state.options.surfaceTolerance)) {
0178       ACTS_DEBUG(volInfo(state)
0179                  << "We did not end up on the expected surface. surface = "
0180                  << state.currentSurface->geometryId()
0181                  << " position = " << position.transpose()
0182                  << " direction = " << direction.transpose());
0183 
0184       return Result<void>::failure(NavigatorError::NotOnExpectedSurface);
0185     }
0186   }
0187 
0188   return Result<void>::success();
0189 }
0190 
0191 NavigationTarget Navigator::nextTarget(State& state, const Vector3& position,
0192                                        const Vector3& direction) const {
0193   // Reset the current surface
0194   state.currentSurface = nullptr;
0195 
0196   if (inactive(state)) {
0197     return NavigationTarget::None();
0198   }
0199 
0200   ACTS_VERBOSE(volInfo(state) << "Entering Navigator::nextTarget.");
0201 
0202   NavigationTarget nextTarget = tryGetNextTarget(state, position, direction);
0203   if (!nextTarget.isNone()) {
0204     return nextTarget;
0205   }
0206 
0207   state.reset();
0208   ++state.statistics.nRenavigations;
0209 
0210   // We might have punched through a boundary and entered another volume
0211   // so we have to reinitialize
0212   state.currentVolume = m_cfg.trackingGeometry->lowestTrackingVolume(
0213       state.options.geoContext, position);
0214 
0215   if (state.currentVolume == nullptr) {
0216     ACTS_VERBOSE(volInfo(state) << "No volume found, stop navigation.");
0217     state.navigationBreak = true;
0218     return NavigationTarget::None();
0219   }
0220 
0221   state.currentLayer =
0222       state.currentVolume->associatedLayer(state.options.geoContext, position);
0223 
0224   ACTS_VERBOSE(volInfo(state) << "Resolved volume and layer.");
0225 
0226   // Rerun the targeting
0227   nextTarget = tryGetNextTarget(state, position, direction);
0228   if (!nextTarget.isNone()) {
0229     return nextTarget;
0230   }
0231 
0232   ACTS_VERBOSE(
0233       volInfo(state)
0234       << "No targets found again, we got really lost! Stop navigation.");
0235   state.navigationBreak = true;
0236   return NavigationTarget::None();
0237 }
0238 
0239 bool Navigator::checkTargetValid(const State& state, const Vector3& position,
0240                                  const Vector3& direction) const {
0241   static_cast<void>(position);
0242   static_cast<void>(direction);
0243 
0244   return state.navigationStage != Stage::initial;
0245 }
0246 
0247 void Navigator::handleSurfaceReached(State& state, const Vector3& position,
0248                                      const Vector3& direction,
0249                                      const Surface& surface) const {
0250   if (inactive(state)) {
0251     return;
0252   }
0253 
0254   ACTS_VERBOSE(volInfo(state) << "Entering Navigator::handleSurfaceReached.");
0255 
0256   state.currentSurface = &surface;
0257 
0258   ACTS_VERBOSE(volInfo(state)
0259                << "Current surface: " << state.currentSurface->geometryId());
0260 
0261   // handling portals in gen3 configuration
0262   if (m_geometryVersion == GeometryVersion::Gen3) {
0263     if (state.navCandidate().isPortalTarget() &&
0264         &state.navCandidate().surface() == &surface) {
0265       ACTS_VERBOSE(volInfo(state) << "Handling portal status.");
0266 
0267       // Switch to the next volume using the portal
0268       const Portal* portal = &state.navCandidate().portal();
0269       auto res =
0270           portal->resolveVolume(state.options.geoContext, position, direction);
0271       if (!res.ok()) {
0272         ACTS_ERROR(volInfo(state) << "Failed to resolve volume through portal: "
0273                                   << res.error().message());
0274         return;
0275       }
0276 
0277       state.currentVolume = res.value();
0278 
0279       // partial reset
0280       state.resetAfterVolumeSwitch();
0281 
0282       if (state.currentVolume != nullptr) {
0283         ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0284 
0285         // this is set only for the check target validity since gen3 does not
0286         // care
0287         state.navigationStage = Stage::surfaceTarget;
0288       } else {
0289         ACTS_VERBOSE(volInfo(state)
0290                      << "No more volume to progress to, stopping navigation.");
0291         state.navigationBreak = true;
0292       }
0293     }
0294     // Mark reached free candidates
0295     else if (&state.navCandidate().surface() == &surface &&
0296              surface.geometryId() == GeometryIdentifier{}) {
0297       auto freeItr = std::ranges::find_if(
0298           state.freeCandidates,
0299           [&surface](const std::pair<const Surface*, bool>& cand) {
0300             return &surface == cand.first;
0301           });
0302       if (freeItr != state.freeCandidates.end()) {
0303         freeItr->second = true;
0304       }
0305     }
0306     return;
0307   }
0308 
0309   if (state.navigationStage == Stage::surfaceTarget &&
0310       &state.navSurface().surface() == &surface) {
0311     ACTS_VERBOSE(volInfo(state) << "Handling surface status.");
0312 
0313     return;
0314   }
0315 
0316   if (state.navigationStage == Stage::layerTarget &&
0317       &state.navLayer().surface() == &surface) {
0318     ACTS_VERBOSE(volInfo(state) << "Handling layer status.");
0319 
0320     // Switch to the next layer
0321     state.currentLayer = &state.navLayer().layer();
0322     state.navigationStage = Stage::surfaceTarget;
0323 
0324     // partial reset
0325     state.resetAfterLayerSwitch();
0326 
0327     return;
0328   }
0329 
0330   if (state.navigationStage == Stage::boundaryTarget &&
0331       &state.navBoundary().surface() == &surface) {
0332     ACTS_VERBOSE(volInfo(state) << "Handling boundary status.");
0333 
0334     // Switch to the next volume using the boundary
0335     const BoundarySurface* boundary = &state.navBoundary().boundarySurface();
0336     assert(boundary != nullptr && "Retrieved boundary surface is nullptr");
0337     state.currentVolume =
0338         boundary->attachedVolume(state.options.geoContext, position, direction);
0339 
0340     // partial reset
0341     state.resetAfterVolumeSwitch();
0342 
0343     if (state.currentVolume != nullptr) {
0344       ACTS_VERBOSE(volInfo(state) << "Volume updated.");
0345       state.navigationStage = Stage::layerTarget;
0346     } else {
0347       ACTS_VERBOSE(volInfo(state)
0348                    << "No more volume to progress to, stopping navigation.");
0349       state.navigationBreak = true;
0350     }
0351 
0352     return;
0353   }
0354 
0355   ACTS_ERROR(volInfo(state) << "Surface reached but unknown state.");
0356 }
0357 
0358 NavigationTarget Navigator::getNextTargetGen1(State& state,
0359                                               const Vector3& position,
0360                                               const Vector3& direction) const {
0361   // Try targeting the surfaces - then layers - then boundaries
0362   if (state.navigationStage == Stage::surfaceTarget) {
0363     if (!state.navSurfaceIndex.has_value()) {
0364       // First time, resolve the surfaces
0365       resolveSurfaces(state, position, direction);
0366       state.navSurfaceIndex = 0;
0367     } else {
0368       ++state.navSurfaceIndex.value();
0369     }
0370     if (state.navSurfaceIndex.value() < state.navSurfaces.size()) {
0371       ACTS_VERBOSE(volInfo(state) << "Target set to next surface.");
0372       return state.navSurface();
0373     } else {
0374       // This was the last surface, switch to layers
0375       ACTS_VERBOSE(volInfo(state) << "Target layers.");
0376       state.navigationStage = Stage::layerTarget;
0377     }
0378   }
0379 
0380   if (state.navigationStage == Stage::layerTarget) {
0381     if (!state.navLayerIndex.has_value()) {
0382       // First time, resolve the layers
0383       resolveLayers(state, position, direction);
0384       state.navLayerIndex = 0;
0385     } else {
0386       ++state.navLayerIndex.value();
0387     }
0388     if (state.navLayerIndex.value() < state.navLayers.size()) {
0389       ACTS_VERBOSE(volInfo(state) << "Target set to next layer.");
0390       return state.navLayer();
0391     } else {
0392       // This was the last layer, switch to boundaries
0393       ACTS_VERBOSE(volInfo(state) << "Target boundaries.");
0394       state.navigationStage = Stage::boundaryTarget;
0395     }
0396   }
0397 
0398   if (state.navigationStage == Stage::boundaryTarget) {
0399     if (!state.navBoundaryIndex.has_value()) {
0400       // First time, resolve the boundaries
0401       resolveBoundaries(state, position, direction);
0402       state.navBoundaryIndex = 0;
0403     } else {
0404       ++state.navBoundaryIndex.value();
0405     }
0406     if (state.navBoundaryIndex.value() < state.navBoundaries.size()) {
0407       ACTS_VERBOSE(volInfo(state) << "Target set to next boundary.");
0408       return state.navBoundary();
0409     } else {
0410       // This was the last boundary, we have to leave the volume somehow,
0411       // renavigate
0412       ACTS_VERBOSE(volInfo(state) << "Boundary targets exhausted. Renavigate.");
0413       return NavigationTarget::None();
0414     }
0415   }
0416 
0417   ACTS_VERBOSE(volInfo(state) << "Unknown state. No target found. Renavigate.");
0418   return NavigationTarget::None();
0419 }
0420 
0421 NavigationTarget Navigator::getNextTargetGen3(State& state,
0422                                               const Vector3& position,
0423                                               const Vector3& direction) const {
0424   if (!state.navCandidateIndex.has_value()) {
0425     // first time, resolve the candidates
0426     resolveCandidates(state, position, direction);
0427     state.navCandidateIndex = 0;
0428   } else {
0429     ++state.navCandidateIndex.value();
0430   }
0431   if (state.navCandidateIndex.value() < state.navCandidates.size()) {
0432     ACTS_VERBOSE(volInfo(state)
0433                  << "Target set to next candidate " << state.navCandidate());
0434     return state.navCandidate();
0435   } else {
0436     ACTS_VERBOSE(volInfo(state) << "Candidate targets exhausted. Renavigate.");
0437     return NavigationTarget::None();
0438   }
0439 }
0440 
0441 NavigationTarget Navigator::tryGetNextTarget(State& state,
0442                                              const Vector3& position,
0443                                              const Vector3& direction) const {
0444   // Try different approach to get navigation target for gen1 and gen3
0445   // configuration
0446 
0447   // This is common, in gen1 we start by surfaces and in gen3 we always look
0448   // for surfaces
0449   if (state.navigationStage == Stage::initial) {
0450     ACTS_VERBOSE(volInfo(state) << "Target surfaces.");
0451     state.navigationStage = Stage::surfaceTarget;
0452   }
0453 
0454   if (m_geometryVersion == GeometryVersion::Gen1) {
0455     return getNextTargetGen1(state, position, direction);
0456 
0457   } else {  // gen3 handling of the next target
0458 
0459     return getNextTargetGen3(state, position, direction);
0460   }
0461 }
0462 
0463 void Navigator::resolveCandidates(State& state, const Vector3& position,
0464                                   const Vector3& direction) const {
0465   if (state.currentVolume == nullptr) {
0466     ACTS_VERBOSE(volInfo(state) << "No volume to resolve candidates.");
0467     return;
0468   }
0469   ACTS_VERBOSE(volInfo(state) << "Searching for compatible candidates.");
0470 
0471   state.stream.reset();
0472   AppendOnlyNavigationStream appendOnly{state.stream};
0473   NavigationArguments args;
0474   args.position = position;
0475   args.direction = direction;
0476   state.currentVolume->initializeNavigationCandidates(
0477       state.options.geoContext, args, appendOnly, logger());
0478 
0479   ACTS_VERBOSE(volInfo(state) << "Found " << state.stream.candidates().size()
0480                               << " navigation candidates.");
0481   if (!state.options.externalSurfaces.empty()) {
0482     for (const GeometryIdentifier& geoId : state.options.externalSurfaces) {
0483       // Don't add any surface which is not in the same volume (volume bits)
0484       // or sub volume (extra bits)
0485       if (geoId.volume() != state.currentVolume->geometryId().volume() ||
0486           geoId.extra() != state.currentVolume->geometryId().extra()) {
0487         continue;
0488       }
0489       const Surface* surface = m_cfg.trackingGeometry->findSurface(geoId);
0490       assert(surface != nullptr);
0491       ACTS_VERBOSE(volInfo(state) << "Try to navigate to " << surface->type()
0492                                   << " surface " << geoId);
0493       appendOnly.addSurfaceCandidate(*surface, BoundaryTolerance::Infinite());
0494     };
0495   }
0496   bool pruneFreeCand{false};
0497   if (!state.freeCandidates.empty()) {
0498     for (const auto& [surface, wasReached] : state.freeCandidates) {
0499       /// Don't process already reached surfaces again
0500       if (wasReached) {
0501         continue;
0502       }
0503       if (!state.options.freeSurfaceSelector.connected() ||
0504           state.options.freeSurfaceSelector(state.options.geoContext,
0505                                             *state.currentVolume, position,
0506                                             direction, *surface)) {
0507         ACTS_VERBOSE(volInfo(state)
0508                      << "Append free " << surface->type() << " surface  \n"
0509                      << surface->toStream(state.options.geoContext));
0510         appendOnly.addSurfaceCandidate(*surface, BoundaryTolerance::Infinite());
0511         pruneFreeCand = !state.options.freeSurfaceSelector.connected();
0512       }
0513     };
0514   }
0515   state.stream.initialize(state.options.geoContext, {position, direction},
0516                           BoundaryTolerance::None(),
0517                           state.options.surfaceTolerance);
0518 
0519   ACTS_VERBOSE(volInfo(state) << "Now " << state.stream.candidates().size()
0520                               << " navigation candidates after initialization");
0521 
0522   state.navCandidates.clear();
0523 
0524   double farLimit = state.options.farLimit;
0525   // If the user has not provided the selection delegate, then
0526   // just apply a simple candidate pruning. Constrain the maximum
0527   // reach of the navigation to the last portal in the state
0528   if (pruneFreeCand) {
0529     farLimit = state.options.nearLimit;
0530     for (const auto& candidate : state.stream.candidates()) {
0531       if (candidate.isPortalTarget()) {
0532         farLimit = std::max(farLimit, candidate.intersection().pathLength() +
0533                                           state.options.surfaceTolerance);
0534       }
0535     }
0536   }
0537 
0538   for (auto& candidate : state.stream.candidates()) {
0539     if (!detail::checkPathLength(candidate.intersection().pathLength(),
0540                                  state.options.nearLimit, farLimit, logger())) {
0541       continue;
0542     }
0543 
0544     state.navCandidates.emplace_back(candidate);
0545   }
0546 
0547   // Sort the candidates with the path length
0548   std::ranges::sort(state.navCandidates, [](const auto& a, const auto& b) {
0549     return a.intersection().pathLength() < b.intersection().pathLength();
0550   });
0551 
0552   // Print the navigation candidates
0553 
0554   if (logger().doPrint(Logging::VERBOSE)) {
0555     std::ostringstream os;
0556     os << "Navigation candidates: " << state.navCandidates.size() << "\n";
0557     for (auto& candidate : state.navCandidates) {
0558       os << " -- " << candidate << "\n";
0559     }
0560 
0561     logger().log(Logging::VERBOSE, os.str());
0562   }
0563 }
0564 
0565 void Navigator::resolveSurfaces(State& state, const Vector3& position,
0566                                 const Vector3& direction) const {
0567   // Gen-1 surface resolution
0568   ACTS_VERBOSE(volInfo(state) << "Searching for compatible surfaces.");
0569 
0570   const Layer* currentLayer = state.currentLayer;
0571 
0572   if (currentLayer == nullptr) {
0573     ACTS_VERBOSE(volInfo(state) << "No layer to resolve surfaces.");
0574     return;
0575   }
0576 
0577   const Surface* layerSurface = &currentLayer->surfaceRepresentation();
0578 
0579   NavigationOptions<Surface> navOpts;
0580   navOpts.resolveSensitive = m_cfg.resolveSensitive;
0581   navOpts.resolveMaterial = m_cfg.resolveMaterial;
0582   navOpts.resolvePassive = m_cfg.resolvePassive;
0583   navOpts.startObject = state.currentSurface;
0584   navOpts.endObject = state.targetSurface;
0585   navOpts.nearLimit = state.options.nearLimit;
0586   navOpts.farLimit = state.options.farLimit;
0587 
0588   if (!state.options.externalSurfaces.empty()) {
0589     const auto layerId = layerSurface->geometryId().layer();
0590     for (const GeometryIdentifier& id : state.options.externalSurfaces) {
0591       if (id.layer() == layerId) {
0592         navOpts.externalSurfaces.push_back(id);
0593       }
0594     }
0595   }
0596 
0597   // Request the compatible surfaces
0598   state.navSurfaces = currentLayer->compatibleSurfaces(
0599       state.options.geoContext, position, direction, navOpts);
0600   // Sort the surfaces by path length.
0601   // Special care is taken for the external surfaces which should always
0602   // come first, so they are preferred to be targeted and hit first.
0603   std::ranges::sort(state.navSurfaces, [&state](const NavigationTarget& a,
0604                                                 const NavigationTarget& b) {
0605     // Prefer to sort by path length. We assume surfaces are at the same
0606     // distance if the difference is smaller than the tolerance.
0607     if (std::abs(a.pathLength() - b.pathLength()) >
0608         state.options.surfaceTolerance) {
0609       return NavigationTarget::pathLengthOrder(a, b);
0610     }
0611     // If the path length is practically the same, sort by geometry.
0612     // First we check if one of the surfaces is external.
0613     bool aIsExternal = a.boundaryTolerance().isInfinite();
0614     bool bIsExternal = b.boundaryTolerance().isInfinite();
0615     if (aIsExternal == bIsExternal) {
0616       // If both are external or both are not external, sort by geometry
0617       // identifier
0618       return a.surface().geometryId() < b.surface().geometryId();
0619     }
0620     // If only one is external, it should come first
0621     return aIsExternal;
0622   });
0623   // For now we implicitly remove overlapping surfaces.
0624   // For track finding it might be useful to discover overlapping surfaces
0625   // and check for compatible measurements. This is under investigation
0626   // and might be implemented in the future.
0627   auto toBeRemoved =
0628       std::ranges::unique(state.navSurfaces, [&](const auto& a, const auto& b) {
0629         return std::abs(a.pathLength() - b.pathLength()) <
0630                state.options.surfaceTolerance;
0631       });
0632   if (toBeRemoved.begin() != toBeRemoved.end()) {
0633     ACTS_VERBOSE(volInfo(state)
0634                  << "Removing "
0635                  << std::distance(toBeRemoved.begin(), toBeRemoved.end())
0636                  << " overlapping surfaces.");
0637   }
0638   state.navSurfaces.erase(toBeRemoved.begin(), toBeRemoved.end());
0639 
0640   // Print surface information
0641   if (logger().doPrint(Logging::VERBOSE)) {
0642     std::ostringstream os;
0643     os << state.navSurfaces.size();
0644     os << " surface candidates found at path(s): ";
0645     for (auto& sfc : state.navSurfaces) {
0646       os << sfc.pathLength() << "  ";
0647     }
0648     logger().log(Logging::VERBOSE, os.str());
0649   }
0650 
0651   if (state.navSurfaces.empty()) {
0652     ACTS_VERBOSE(volInfo(state) << "No surface candidates found.");
0653   }
0654 }
0655 
0656 void Navigator::resolveLayers(State& state, const Vector3& position,
0657                               const Vector3& direction) const {
0658   ACTS_VERBOSE(volInfo(state) << "Searching for compatible layers.");
0659 
0660   NavigationOptions<Layer> navOpts;
0661   navOpts.resolveSensitive = m_cfg.resolveSensitive;
0662   navOpts.resolveMaterial = m_cfg.resolveMaterial;
0663   navOpts.resolvePassive = m_cfg.resolvePassive;
0664   navOpts.startObject = state.currentLayer;
0665   navOpts.nearLimit = state.options.nearLimit;
0666   navOpts.farLimit = state.options.farLimit;
0667 
0668   // Request the compatible layers
0669   state.navLayers = state.currentVolume->compatibleLayers(
0670       state.options.geoContext, position, direction, navOpts);
0671   std::ranges::sort(state.navLayers, NavigationTarget::pathLengthOrder);
0672 
0673   // Print layer information
0674   if (logger().doPrint(Logging::VERBOSE)) {
0675     std::ostringstream os;
0676     os << state.navLayers.size();
0677     os << " layer candidates found at path(s): ";
0678     for (auto& lc : state.navLayers) {
0679       os << lc.pathLength() << "  ";
0680     }
0681     logger().log(Logging::VERBOSE, os.str());
0682   }
0683 
0684   if (state.navLayers.empty()) {
0685     ACTS_VERBOSE(volInfo(state) << "No layer candidates found.");
0686   }
0687 }
0688 
0689 void Navigator::resolveBoundaries(State& state, const Vector3& position,
0690                                   const Vector3& direction) const {
0691   ACTS_VERBOSE(volInfo(state) << "Searching for compatible boundaries.");
0692 
0693   NavigationOptions<Surface> navOpts;
0694   navOpts.startObject = state.currentSurface;
0695   navOpts.nearLimit = state.options.nearLimit;
0696   navOpts.farLimit = state.options.farLimit;
0697 
0698   ACTS_VERBOSE(volInfo(state)
0699                << "Try to find boundaries, we are at: " << toString(position)
0700                << ", dir: " << toString(direction));
0701 
0702   // Request the compatible boundaries
0703   state.navBoundaries = state.currentVolume->compatibleBoundaries(
0704       state.options.geoContext, position, direction, navOpts, logger());
0705   std::ranges::sort(state.navBoundaries, NavigationTarget::pathLengthOrder);
0706 
0707   // Print boundary information
0708   if (logger().doPrint(Logging::VERBOSE)) {
0709     std::ostringstream os;
0710     os << state.navBoundaries.size();
0711     os << " boundary candidates found at path(s): ";
0712     for (const auto& bc : state.navBoundaries) {
0713       os << bc.pathLength() << "  ";
0714     }
0715     logger().log(Logging::VERBOSE, os.str());
0716   }
0717 
0718   if (state.navBoundaries.empty()) {
0719     ACTS_VERBOSE(volInfo(state) << "No boundary candidates found.");
0720   }
0721 }
0722 
0723 bool Navigator::inactive(const State& state) const {
0724   // Turn the navigator into void when you are instructed to do nothing
0725   if (!m_cfg.resolveSensitive && !m_cfg.resolveMaterial &&
0726       !m_cfg.resolvePassive) {
0727     return true;
0728   }
0729 
0730   if (state.navigationBreak) {
0731     return true;
0732   }
0733 
0734   return false;
0735 }
0736 
0737 std::string Navigator::volInfo(const State& state) const {
0738   if (state.currentVolume == nullptr) {
0739     return "No Volume | ";
0740   }
0741   std::stringstream sstr{};
0742   sstr << state.currentVolume->volumeName() << " ("
0743        << state.currentVolume->geometryId() << ") | ";
0744   return sstr.str();
0745 }
0746 
0747 }  // namespace Acts