Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-14 08:19:17

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Definitions/Algebra.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/NavigatorError.hpp"
0017 #include "Acts/Propagator/NavigatorOptions.hpp"
0018 #include "Acts/Propagator/NavigatorStatistics.hpp"
0019 #include "Acts/Propagator/detail/NavigationHelpers.hpp"
0020 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0021 #include "Acts/Surfaces/Surface.hpp"
0022 #include "Acts/Utilities/Enumerate.hpp"
0023 #include "Acts/Utilities/Intersection.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025 #include "Acts/Utilities/StringHelpers.hpp"
0026 
0027 #include <algorithm>
0028 #include <cstdint>
0029 #include <limits>
0030 #include <memory>
0031 #include <vector>
0032 
0033 namespace Acts::Experimental {
0034 
0035 /// @brief Alternative @c Navigator which tries all possible intersections
0036 ///
0037 /// See @c Navigator for more general information about the Navigator concept.
0038 ///
0039 /// This Navigator tries all possible intersections with all surfaces in the
0040 /// current volume. It does not use any information about the geometry to
0041 /// optimise the search. It is therefore very slow, but can be used as a
0042 /// reference implementation.
0043 ///
0044 /// Additionally, this implementation tries to discover additional
0045 /// intersections after stepping forward and then checking for intersections
0046 /// based on the previous and current positions. This is slower, but more robust
0047 /// against bent tracks.
0048 class TryAllNavigator final {
0049  public:
0050   /// Configuration for this Navigator
0051   struct Config final {
0052     /// Tracking Geometry for this Navigator
0053     std::shared_ptr<const TrackingGeometry> trackingGeometry;
0054 
0055     /// stop at every sensitive surface (whether it has material or not)
0056     bool resolveSensitive = true;
0057     /// stop at every material surface (whether it is passive or not)
0058     bool resolveMaterial = true;
0059     /// stop at every surface regardless what it is
0060     bool resolvePassive = false;
0061 
0062     /// Which boundary checks to perform for surface approach
0063     BoundaryTolerance boundaryToleranceSurfaceApproach =
0064         BoundaryTolerance::None();
0065   };
0066 
0067   /// Options for this Navigator
0068   struct Options final : public NavigatorPlainOptions {
0069     /// @param gctx The geometry context for this navigator instance
0070     explicit Options(const GeometryContext& gctx)
0071         : NavigatorPlainOptions(gctx) {}
0072 
0073     /// The surface tolerance
0074     double surfaceTolerance = s_onSurfaceTolerance;
0075 
0076     /// The near limit to resolve surfaces
0077     double nearLimit = s_onSurfaceTolerance;
0078 
0079     /// The far limit to resolve surfaces
0080     double farLimit = std::numeric_limits<double>::max();
0081 
0082     /// @param options The plain options to copy
0083     void setPlainOptions(const NavigatorPlainOptions& options) {
0084       static_cast<NavigatorPlainOptions&>(*this) = options;
0085     }
0086   };
0087 
0088   /// Nested state struct
0089   struct State final {
0090     /// @param options_ Navigator options to initialise state with
0091     explicit State(const Options& options_) : options(options_) {}
0092 
0093     /// Navigation options containing configuration for this propagation
0094     Options options;
0095 
0096     // Starting geometry information of the navigation which should only be set
0097     // while initialization. NOTE: This information is mostly used by actors to
0098     // check if we are on the starting surface (e.g. MaterialInteraction).
0099     /// Surface where the propagation started
0100     const Surface* startSurface = nullptr;
0101 
0102     // Target geometry information of the navigation which should only be set
0103     // while initialization. NOTE: This information is mostly used by actors to
0104     // check if we are on the target surface (e.g. MaterialInteraction).
0105     /// Surface that is the target of the propagation
0106     const Surface* targetSurface = nullptr;
0107 
0108     // Current geometry information of the navigation which is set during
0109     // initialization and potentially updated after each step.
0110     /// Currently active surface during propagation
0111     const Surface* currentSurface = nullptr;
0112     /// Currently active tracking volume during propagation
0113     const TrackingVolume* currentVolume = nullptr;
0114 
0115     /// The vector of navigation candidates to work through
0116     std::vector<detail::NavigationObjectCandidate> navigationCandidates;
0117 
0118     /// If a break has been detected
0119     bool navigationBreak = false;
0120 
0121     /// Navigation statistics
0122     NavigatorStatistics statistics;
0123 
0124     /// The vector of active targets ahead of the current position
0125     std::vector<NavigationTarget> activeTargetsAhead;
0126     /// The vector of active targets behind the current position
0127     std::vector<NavigationTarget> activeTargetsBehind;
0128     /// Index to keep track of which target behind the current position is
0129     /// currently active
0130     std::int32_t activeTargetBehindIndex = -1;
0131 
0132     /// The position before the last step
0133     std::optional<Vector3> lastPosition;
0134 
0135     /// Provides easy access to the current active targets, prioritizing targets
0136     /// behind the current position.
0137     /// @return Reference to the vector of currently active intersection
0138     /// candidates, prioritizing targets behind the current position
0139     const std::vector<NavigationTarget>& currentTargets() const {
0140       if (hasTargetsBehind()) {
0141         return activeTargetsBehind;
0142       }
0143       return activeTargetsAhead;
0144     }
0145 
0146     /// Provides easy access to the active intersection target
0147     /// @return Reference to the currently active intersection candidate
0148     const NavigationTarget& activeTargetBehind() const {
0149       return activeTargetsBehind.at(activeTargetBehindIndex);
0150     }
0151 
0152     /// Checks if there are still active targets behind the current position
0153     /// that have not been tried yet
0154     /// @return True if there are still active targets behind the current position, false otherwise
0155     bool hasTargetsBehind() const {
0156       return !activeTargetsBehind.empty() &&
0157              activeTargetBehindIndex <
0158                  static_cast<std::int32_t>(activeTargetsBehind.size());
0159     }
0160   };
0161 
0162   /// Constructor with configuration object
0163   ///
0164   /// @param cfg The navigator configuration
0165   /// @param logger a logger instance
0166   explicit TryAllNavigator(Config cfg, std::unique_ptr<const Logger> logger =
0167                                            getDefaultLogger("TryAllNavigator",
0168                                                             Logging::INFO))
0169       : m_cfg(std::move(cfg)), m_logger(std::move(logger)) {}
0170 
0171   /// Creates a new navigator state
0172   /// @param options Navigator options for state initialization
0173   /// @return Initialized navigator state with current candidates storage
0174   State makeState(const Options& options) const {
0175     State state(options);
0176     return state;
0177   }
0178 
0179   /// Get the current surface from the navigation state
0180   /// @param state The navigation state
0181   /// @return Pointer to the current surface, or nullptr if none
0182   const Surface* currentSurface(const State& state) const {
0183     return state.currentSurface;
0184   }
0185 
0186   /// Get the current tracking volume from the navigation state
0187   /// @param state The navigation state
0188   /// @return Pointer to the current tracking volume, or nullptr if none
0189   const TrackingVolume* currentVolume(const State& state) const {
0190     return state.currentVolume;
0191   }
0192 
0193   /// Get the material of the current tracking volume
0194   /// @param state The navigation state
0195   /// @return Pointer to the volume material, or nullptr if no volume or no material
0196   const IVolumeMaterial* currentVolumeMaterial(const State& state) const {
0197     if (state.currentVolume == nullptr) {
0198       return nullptr;
0199     }
0200     return state.currentVolume->volumeMaterial();
0201   }
0202 
0203   /// Get the start surface from the navigation state
0204   /// @param state The navigation state
0205   /// @return Pointer to the start surface, or nullptr if none
0206   const Surface* startSurface(const State& state) const {
0207     return state.startSurface;
0208   }
0209 
0210   /// Get the target surface from the navigation state
0211   /// @param state The navigation state
0212   /// @return Pointer to the target surface, or nullptr if none
0213   const Surface* targetSurface(const State& state) const {
0214     return state.targetSurface;
0215   }
0216 
0217   /// Check if the end of the world has been reached
0218   /// @param state The navigation state
0219   /// @return True if no current volume is set (end of world reached)
0220   bool endOfWorldReached(State& state) const {
0221     return state.currentVolume == nullptr;
0222   }
0223 
0224   /// Check if navigation has been interrupted
0225   /// @param state The navigation state
0226   /// @return True if navigation break flag is set
0227   bool navigationBreak(const State& state) const {
0228     return state.navigationBreak;
0229   }
0230 
0231   /// @brief Initialize the navigator
0232   ///
0233   /// This method initializes the navigator for a new propagation. It sets the
0234   /// current volume and surface to the start volume and surface, respectively.
0235   ///
0236   /// @param state The navigation state
0237   /// @param position The starting position
0238   /// @param direction The starting direction
0239   /// @param propagationDirection The propagation direction
0240   /// @return Result indicating success or failure of initialization
0241   [[nodiscard]] Result<void> initialize(State& state, const Vector3& position,
0242                                         const Vector3& direction,
0243                                         Direction propagationDirection) const {
0244     static_cast<void>(propagationDirection);
0245 
0246     ACTS_VERBOSE("initialize");
0247 
0248     state.startSurface = state.options.startSurface;
0249     state.targetSurface = state.options.targetSurface;
0250 
0251     const TrackingVolume* startVolume = nullptr;
0252 
0253     if (state.startSurface != nullptr &&
0254         state.startSurface->associatedLayer() != nullptr) {
0255       ACTS_VERBOSE(
0256           "Fast start initialization through association from Surface.");
0257       const auto* startLayer = state.startSurface->associatedLayer();
0258       startVolume = startLayer->trackingVolume();
0259     } else {
0260       ACTS_VERBOSE("Slow start initialization through search.");
0261       ACTS_VERBOSE("Starting from position " << toString(position)
0262                                              << " and direction "
0263                                              << toString(direction));
0264       startVolume =
0265           m_cfg.trackingGeometry
0266               ->resolveLowestTrackingVolume(state.options.geoContext, position)
0267               .value();
0268     }
0269 
0270     // Initialize current volume, layer and surface
0271     {
0272       state.currentVolume = startVolume;
0273       if (state.currentVolume != nullptr) {
0274         ACTS_VERBOSE(volInfo(state) << "Start volume resolved.");
0275       } else {
0276         ACTS_DEBUG("Start volume not resolved.");
0277         state.navigationBreak = true;
0278         return NavigatorError::NoStartVolume;
0279       }
0280 
0281       state.currentSurface = state.startSurface;
0282       if (state.currentSurface != nullptr) {
0283         ACTS_VERBOSE(volInfo(state) << "Current surface set to start surface "
0284                                     << state.currentSurface->geometryId());
0285       } else {
0286         ACTS_VERBOSE(volInfo(state) << "No start surface set.");
0287       }
0288     }
0289 
0290     // Initialize navigation candidates for the start volume
0291     reinitializeCandidates(state);
0292 
0293     state.lastPosition.reset();
0294 
0295     return Result<void>::success();
0296   }
0297 
0298   /// @brief Get the next target surface
0299   ///
0300   /// This method gets the next target surface based on the current
0301   /// position and direction. It returns a none target if no target can be
0302   /// found.
0303   ///
0304   /// @param state The navigation state
0305   /// @param position The current position
0306   /// @param direction The current direction
0307   ///
0308   /// @return The next target surface
0309   NavigationTarget nextTarget(State& state, const Vector3& position,
0310                               const Vector3& direction) const {
0311     // Navigator preStep always resets the current surface
0312     state.currentSurface = nullptr;
0313 
0314     // Check if the navigator is inactive
0315     if (state.navigationBreak) {
0316       return NavigationTarget::None();
0317     }
0318 
0319     ACTS_VERBOSE(volInfo(state) << "nextTarget");
0320 
0321     if (state.lastPosition.has_value() && !state.hasTargetsBehind()) {
0322       ACTS_VERBOSE(volInfo(state) << "Evaluate blind step");
0323 
0324       const Vector3 stepStart = state.lastPosition.value();
0325       state.lastPosition.reset();
0326       const Vector3 stepEnd = position;
0327       const Vector3 step = stepEnd - stepStart;
0328       const double stepDistance = step.norm();
0329 
0330       ACTS_VERBOSE("- from: " << stepStart.transpose());
0331       ACTS_VERBOSE("- to: " << stepEnd.transpose());
0332       ACTS_VERBOSE("- distance: " << stepDistance);
0333 
0334       if (stepDistance < std::numeric_limits<double>::epsilon()) {
0335         ACTS_DEBUG(volInfo(state) << "Step distance is zero: " << stepDistance
0336                                   << ". Retry to resolve the next target.");
0337         return nextTarget(state, position, direction);
0338       }
0339 
0340       const Vector3 stepDirection = step.normalized();
0341 
0342       const double nearLimit = -stepDistance + state.options.surfaceTolerance;
0343       const double farLimit = 0;
0344 
0345       state.activeTargetsBehind =
0346           resolveTargets(state, stepEnd, stepDirection, nearLimit, farLimit);
0347       state.activeTargetBehindIndex = -1;
0348 
0349       ACTS_VERBOSE(volInfo(state)
0350                    << "Found " << state.activeTargetsBehind.size()
0351                    << " intersections behind");
0352 
0353       for (const auto& target : state.activeTargetsBehind) {
0354         ACTS_VERBOSE("Found target behind " << target.surface().geometryId());
0355       }
0356     }
0357 
0358     // Prioritize targets behind the current position
0359     ++state.activeTargetBehindIndex;
0360     if (state.hasTargetsBehind()) {
0361       ACTS_VERBOSE(volInfo(state) << "Handle active candidates behind");
0362 
0363       ACTS_VERBOSE(volInfo(state)
0364                    << (state.activeTargetsBehind.size() -
0365                        state.activeTargetBehindIndex)
0366                    << " out of " << state.activeTargetsBehind.size()
0367                    << " surfaces remain to try.");
0368 
0369       const NavigationTarget& nextTarget = state.activeTargetBehind();
0370 
0371       ACTS_VERBOSE(volInfo(state) << "Next target behind selected: "
0372                                   << nextTarget.surface().geometryId());
0373 
0374       return nextTarget;
0375     }
0376 
0377     // No more targets behind, now try to find targets ahead as usual
0378 
0379     state.lastPosition = position;
0380     state.activeTargetsBehind.clear();
0381     state.activeTargetBehindIndex = -1;
0382 
0383     ACTS_VERBOSE(volInfo(state)
0384                  << "No targets behind, try to find targets ahead");
0385 
0386     const double nearLimit = state.options.nearLimit;
0387     const double farLimit = state.options.farLimit;
0388 
0389     state.activeTargetsAhead =
0390         resolveTargets(state, position, direction, nearLimit, farLimit);
0391 
0392     NavigationTarget nextTarget = NavigationTarget::None();
0393 
0394     for (const auto& target : state.activeTargetsAhead) {
0395       const Intersection3D& intersection = target.intersection();
0396 
0397       if (intersection.status() == IntersectionStatus::onSurface) {
0398         ACTS_ERROR(volInfo(state)
0399                    << "We are on surface " << target.surface().geometryId()
0400                    << " before trying to reach it. This should not happen. "
0401                       "Good luck.");
0402         continue;
0403       }
0404 
0405       if (intersection.status() == IntersectionStatus::reachable) {
0406         nextTarget = target;
0407         break;
0408       }
0409     }
0410 
0411     if (nextTarget.isNone()) {
0412       ACTS_VERBOSE(volInfo(state)
0413                    << "No target ahead found. Step blindly forward.");
0414     } else {
0415       ACTS_VERBOSE(volInfo(state) << "Next target ahead selected: "
0416                                   << nextTarget.surface().geometryId());
0417     }
0418 
0419     return nextTarget;
0420   }
0421 
0422   /// @brief Check if the target is still valid
0423   ///
0424   /// This method checks if the target is valid based on the current position
0425   /// and direction. It returns true if the target is still valid.
0426   ///
0427   /// For the TryAllNavigator, the target is always invalid since we do not want
0428   /// to assume any specific surface sequence over multiple steps.
0429   ///
0430   /// @param state The navigation state
0431   /// @param position The current position
0432   /// @param direction The current direction
0433   ///
0434   /// @return True if the target is still valid
0435   bool checkTargetValid(const State& state, const Vector3& position,
0436                         const Vector3& direction) const {
0437     static_cast<void>(state);
0438     static_cast<void>(position);
0439     static_cast<void>(direction);
0440 
0441     return false;
0442   }
0443 
0444   /// @brief Handle the surface reached
0445   ///
0446   /// This method is called when a surface is reached. It sets the current
0447   /// surface in the navigation state and updates the navigation candidates.
0448   ///
0449   /// @param state The navigation state
0450   /// @param position The current position
0451   /// @param direction The current direction
0452   void handleSurfaceReached(State& state, const Vector3& position,
0453                             const Vector3& direction,
0454                             const Surface& /*surface*/) const {
0455     // Check if the navigator is inactive
0456     if (state.navigationBreak) {
0457       return;
0458     }
0459 
0460     ACTS_VERBOSE(volInfo(state) << "handleSurfaceReached");
0461 
0462     const std::vector<NavigationTarget>& currentTargets =
0463         state.currentTargets();
0464 
0465     if (currentTargets.empty()) {
0466       ACTS_VERBOSE(volInfo(state) << "No current target set.");
0467       return;
0468     }
0469 
0470     assert(state.currentSurface == nullptr && "Current surface must be reset.");
0471 
0472     // handle multiple surface intersections due to increased bounds
0473 
0474     std::vector<NavigationTarget> hitTargets;
0475 
0476     for (const auto& target : currentTargets) {
0477       const std::uint8_t index = target.intersectionIndex();
0478       const Surface& surface = target.surface();
0479       const BoundaryTolerance boundaryTolerance = BoundaryTolerance::None();
0480 
0481       const Intersection3D intersection =
0482           surface
0483               .intersect(state.options.geoContext, position, direction,
0484                          boundaryTolerance, state.options.surfaceTolerance)
0485               .at(index);
0486 
0487       if (intersection.status() == IntersectionStatus::onSurface) {
0488         hitTargets.emplace_back(target);
0489       }
0490     }
0491 
0492     ACTS_VERBOSE(volInfo(state)
0493                  << "Found " << hitTargets.size()
0494                  << " intersections on surface with bounds check.");
0495 
0496     // reset stored targets
0497     state.lastPosition.reset();
0498     state.activeTargetsAhead.clear();
0499     state.activeTargetsBehind.clear();
0500     state.activeTargetBehindIndex = -1;
0501 
0502     if (hitTargets.empty()) {
0503       ACTS_VERBOSE(volInfo(state) << "No hit targets found.");
0504       return;
0505     }
0506 
0507     if (hitTargets.size() > 1) {
0508       ACTS_VERBOSE(volInfo(state)
0509                    << "Only using first intersection within bounds.");
0510     }
0511 
0512     // we can only handle a single surface hit so we pick the first one
0513     const NavigationTarget& target = hitTargets.front();
0514     const Surface& surface = target.surface();
0515 
0516     ACTS_VERBOSE(volInfo(state) << "Surface " << surface.geometryId()
0517                                 << " successfully hit, storing it.");
0518     state.currentSurface = &surface;
0519 
0520     if (target.isSurfaceTarget()) {
0521       ACTS_VERBOSE(volInfo(state) << "This is a surface");
0522     } else if (target.isLayerTarget()) {
0523       ACTS_VERBOSE(volInfo(state) << "This is a layer");
0524     } else if (target.isPortalTarget()) {
0525       ACTS_VERBOSE(volInfo(state)
0526                    << "This is a boundary. Reinitialize navigation");
0527 
0528       const BoundarySurface& boundary = target.boundarySurface();
0529 
0530       state.currentVolume = boundary.attachedVolume(state.options.geoContext,
0531                                                     position, direction);
0532 
0533       ACTS_VERBOSE(volInfo(state) << "Switched volume");
0534 
0535       reinitializeCandidates(state);
0536     } else {
0537       ACTS_ERROR(volInfo(state) << "Unknown intersection type");
0538     }
0539   }
0540 
0541  private:
0542   /// Configuration object for this navigator
0543   Config m_cfg;
0544 
0545   /// Logger instance for this navigator
0546   std::unique_ptr<const Logger> m_logger;
0547 
0548   /// @brief Get the logger instance
0549   /// @return Reference to the logger instance
0550   const Logger& logger() const { return *m_logger; }
0551 
0552   /// Helper method to reset and reinitialize the navigation candidates.
0553   void reinitializeCandidates(State& state) const {
0554     state.navigationCandidates.clear();
0555     state.activeTargetsAhead.clear();
0556     state.activeTargetsBehind.clear();
0557     state.activeTargetBehindIndex = -1;
0558 
0559     initializeVolumeCandidates(state);
0560   }
0561 
0562   /// Helper method to initialise navigation candidates for the current volume.
0563   /// @param state Navigation state to initialise candidates for
0564   void initializeVolumeCandidates(State& state) const {
0565     const TrackingVolume* volume = state.currentVolume;
0566     ACTS_VERBOSE(volInfo(state) << "Initialize volume");
0567 
0568     if (volume == nullptr) {
0569       state.navigationBreak = true;
0570       ACTS_VERBOSE(volInfo(state) << "No volume set. Good luck.");
0571       return;
0572     }
0573 
0574     emplaceAllVolumeCandidates(
0575         state.navigationCandidates, *volume, m_cfg.resolveSensitive,
0576         m_cfg.resolveMaterial, m_cfg.resolvePassive,
0577         m_cfg.boundaryToleranceSurfaceApproach, logger());
0578   }
0579 
0580   std::vector<NavigationTarget> resolveTargets(State& state,
0581                                                const Vector3& position,
0582                                                const Vector3& direction,
0583                                                double nearLimit,
0584                                                double farLimit) const {
0585     std::vector<NavigationTarget> targets;
0586 
0587     // Find intersections with all candidates
0588     for (const auto& candidate : state.navigationCandidates) {
0589       auto intersections =
0590           candidate.intersect(state.options.geoContext, position, direction,
0591                               state.options.surfaceTolerance);
0592       for (auto [intersectionIndex, intersection] :
0593            Acts::enumerate(intersections)) {
0594         // exclude invalid intersections
0595         if (!intersection.isValid() ||
0596             !detail::checkPathLength(intersection.pathLength(), nearLimit,
0597                                      farLimit)) {
0598           continue;
0599         }
0600         // store candidate
0601         targets.emplace_back(candidate.target(intersection, intersectionIndex));
0602       }
0603     }
0604 
0605     std::ranges::sort(targets, NavigationTarget::pathLengthOrder);
0606 
0607     return targets;
0608   }
0609 
0610   /// @brief Get volume information string for logging
0611   /// @param state The navigation state
0612   /// @return String containing volume name or "No Volume" followed by separator
0613   std::string volInfo(const State& state) const {
0614     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0615                                            : "No Volume") +
0616            " | ";
0617   }
0618 };
0619 
0620 }  // namespace Acts::Experimental