Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-17 07:46:43

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