Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-19 07:47:24

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 = m_cfg.trackingGeometry->lowestTrackingVolume(
0265           state.options.geoContext, position);
0266     }
0267 
0268     // Initialize current volume, layer and surface
0269     {
0270       state.currentVolume = startVolume;
0271       if (state.currentVolume != nullptr) {
0272         ACTS_VERBOSE(volInfo(state) << "Start volume resolved.");
0273       } else {
0274         ACTS_DEBUG("Start volume not resolved.");
0275         state.navigationBreak = true;
0276         return NavigatorError::NoStartVolume;
0277       }
0278 
0279       state.currentSurface = state.startSurface;
0280       if (state.currentSurface != nullptr) {
0281         ACTS_VERBOSE(volInfo(state) << "Current surface set to start surface "
0282                                     << state.currentSurface->geometryId());
0283       } else {
0284         ACTS_VERBOSE(volInfo(state) << "No start surface set.");
0285       }
0286     }
0287 
0288     // Initialize navigation candidates for the start volume
0289     reinitializeCandidates(state);
0290 
0291     state.lastPosition.reset();
0292 
0293     return Result<void>::success();
0294   }
0295 
0296   /// @brief Get the next target surface
0297   ///
0298   /// This method gets the next target surface based on the current
0299   /// position and direction. It returns a none target if no target can be
0300   /// found.
0301   ///
0302   /// @param state The navigation state
0303   /// @param position The current position
0304   /// @param direction The current direction
0305   ///
0306   /// @return The next target surface
0307   NavigationTarget nextTarget(State& state, const Vector3& position,
0308                               const Vector3& direction) const {
0309     // Navigator preStep always resets the current surface
0310     state.currentSurface = nullptr;
0311 
0312     // Check if the navigator is inactive
0313     if (state.navigationBreak) {
0314       return NavigationTarget::None();
0315     }
0316 
0317     ACTS_VERBOSE(volInfo(state) << "nextTarget");
0318 
0319     if (state.lastPosition.has_value() && !state.hasTargetsBehind()) {
0320       ACTS_VERBOSE(volInfo(state) << "Evaluate blind step");
0321 
0322       const Vector3 stepStart = state.lastPosition.value();
0323       state.lastPosition.reset();
0324       const Vector3 stepEnd = position;
0325       const Vector3 step = stepEnd - stepStart;
0326       const double stepDistance = step.norm();
0327 
0328       ACTS_VERBOSE("- from: " << stepStart.transpose());
0329       ACTS_VERBOSE("- to: " << stepEnd.transpose());
0330       ACTS_VERBOSE("- distance: " << stepDistance);
0331 
0332       if (stepDistance < std::numeric_limits<double>::epsilon()) {
0333         ACTS_DEBUG(volInfo(state) << "Step distance is zero: " << stepDistance
0334                                   << ". Retry to resolve the next target.");
0335         return nextTarget(state, position, direction);
0336       }
0337 
0338       const Vector3 stepDirection = step.normalized();
0339 
0340       const double nearLimit = -stepDistance + state.options.surfaceTolerance;
0341       const double farLimit = 0;
0342 
0343       state.activeTargetsBehind =
0344           resolveTargets(state, stepEnd, stepDirection, nearLimit, farLimit);
0345       state.activeTargetBehindIndex = -1;
0346 
0347       ACTS_VERBOSE(volInfo(state)
0348                    << "Found " << state.activeTargetsBehind.size()
0349                    << " intersections behind");
0350 
0351       for (const auto& target : state.activeTargetsBehind) {
0352         ACTS_VERBOSE("Found target behind " << target.surface().geometryId());
0353       }
0354     }
0355 
0356     // Prioritize targets behind the current position
0357     ++state.activeTargetBehindIndex;
0358     if (state.hasTargetsBehind()) {
0359       ACTS_VERBOSE(volInfo(state) << "Handle active candidates behind");
0360 
0361       ACTS_VERBOSE(volInfo(state)
0362                    << (state.activeTargetsBehind.size() -
0363                        state.activeTargetBehindIndex)
0364                    << " out of " << state.activeTargetsBehind.size()
0365                    << " surfaces remain to try.");
0366 
0367       const NavigationTarget& nextTarget = state.activeTargetBehind();
0368 
0369       ACTS_VERBOSE(volInfo(state) << "Next target behind selected: "
0370                                   << nextTarget.surface().geometryId());
0371 
0372       return nextTarget;
0373     }
0374 
0375     // No more targets behind, now try to find targets ahead as usual
0376 
0377     state.lastPosition = position;
0378     state.activeTargetsBehind.clear();
0379     state.activeTargetBehindIndex = -1;
0380 
0381     ACTS_VERBOSE(volInfo(state)
0382                  << "No targets behind, try to find targets ahead");
0383 
0384     const double nearLimit = state.options.nearLimit;
0385     const double farLimit = state.options.farLimit;
0386 
0387     state.activeTargetsAhead =
0388         resolveTargets(state, position, direction, nearLimit, farLimit);
0389 
0390     NavigationTarget nextTarget = NavigationTarget::None();
0391 
0392     for (const auto& target : state.activeTargetsAhead) {
0393       const Intersection3D& intersection = target.intersection();
0394 
0395       if (intersection.status() == IntersectionStatus::onSurface) {
0396         ACTS_ERROR(volInfo(state)
0397                    << "We are on surface " << target.surface().geometryId()
0398                    << " before trying to reach it. This should not happen. "
0399                       "Good luck.");
0400         continue;
0401       }
0402 
0403       if (intersection.status() == IntersectionStatus::reachable) {
0404         nextTarget = target;
0405         break;
0406       }
0407     }
0408 
0409     if (nextTarget.isNone()) {
0410       ACTS_VERBOSE(volInfo(state)
0411                    << "No target ahead found. Step blindly forward.");
0412     } else {
0413       ACTS_VERBOSE(volInfo(state) << "Next target ahead selected: "
0414                                   << nextTarget.surface().geometryId());
0415     }
0416 
0417     return nextTarget;
0418   }
0419 
0420   /// @brief Check if the target is still valid
0421   ///
0422   /// This method checks if the target is valid based on the current position
0423   /// and direction. It returns true if the target is still valid.
0424   ///
0425   /// For the TryAllNavigator, the target is always invalid since we do not want
0426   /// to assume any specific surface sequence over multiple steps.
0427   ///
0428   /// @param state The navigation state
0429   /// @param position The current position
0430   /// @param direction The current direction
0431   ///
0432   /// @return True if the target is still valid
0433   bool checkTargetValid(const State& state, const Vector3& position,
0434                         const Vector3& direction) const {
0435     static_cast<void>(state);
0436     static_cast<void>(position);
0437     static_cast<void>(direction);
0438 
0439     return false;
0440   }
0441 
0442   /// @brief Handle the surface reached
0443   ///
0444   /// This method is called when a surface is reached. It sets the current
0445   /// surface in the navigation state and updates the navigation candidates.
0446   ///
0447   /// @param state The navigation state
0448   /// @param position The current position
0449   /// @param direction The current direction
0450   void handleSurfaceReached(State& state, const Vector3& position,
0451                             const Vector3& direction,
0452                             const Surface& /*surface*/) const {
0453     // Check if the navigator is inactive
0454     if (state.navigationBreak) {
0455       return;
0456     }
0457 
0458     ACTS_VERBOSE(volInfo(state) << "handleSurfaceReached");
0459 
0460     const std::vector<NavigationTarget>& currentTargets =
0461         state.currentTargets();
0462 
0463     if (currentTargets.empty()) {
0464       ACTS_VERBOSE(volInfo(state) << "No current target set.");
0465       return;
0466     }
0467 
0468     assert(state.currentSurface == nullptr && "Current surface must be reset.");
0469 
0470     // handle multiple surface intersections due to increased bounds
0471 
0472     std::vector<NavigationTarget> hitTargets;
0473 
0474     for (const auto& target : currentTargets) {
0475       const std::uint8_t index = target.intersectionIndex();
0476       const Surface& surface = target.surface();
0477       const BoundaryTolerance boundaryTolerance = BoundaryTolerance::None();
0478 
0479       const Intersection3D intersection =
0480           surface
0481               .intersect(state.options.geoContext, position, direction,
0482                          boundaryTolerance, state.options.surfaceTolerance)
0483               .at(index);
0484 
0485       if (intersection.status() == IntersectionStatus::onSurface) {
0486         hitTargets.emplace_back(target);
0487       }
0488     }
0489 
0490     ACTS_VERBOSE(volInfo(state)
0491                  << "Found " << hitTargets.size()
0492                  << " intersections on surface with bounds check.");
0493 
0494     // reset stored targets
0495     state.lastPosition.reset();
0496     state.activeTargetsAhead.clear();
0497     state.activeTargetsBehind.clear();
0498     state.activeTargetBehindIndex = -1;
0499 
0500     if (hitTargets.empty()) {
0501       ACTS_VERBOSE(volInfo(state) << "No hit targets found.");
0502       return;
0503     }
0504 
0505     if (hitTargets.size() > 1) {
0506       ACTS_VERBOSE(volInfo(state)
0507                    << "Only using first intersection within bounds.");
0508     }
0509 
0510     // we can only handle a single surface hit so we pick the first one
0511     const NavigationTarget& target = hitTargets.front();
0512     const Surface& surface = target.surface();
0513 
0514     ACTS_VERBOSE(volInfo(state) << "Surface " << surface.geometryId()
0515                                 << " successfully hit, storing it.");
0516     state.currentSurface = &surface;
0517 
0518     if (target.isSurfaceTarget()) {
0519       ACTS_VERBOSE(volInfo(state) << "This is a surface");
0520     } else if (target.isLayerTarget()) {
0521       ACTS_VERBOSE(volInfo(state) << "This is a layer");
0522     } else if (target.isPortalTarget()) {
0523       ACTS_VERBOSE(volInfo(state)
0524                    << "This is a boundary. Reinitialize navigation");
0525 
0526       const BoundarySurface& boundary = target.boundarySurface();
0527 
0528       state.currentVolume = boundary.attachedVolume(state.options.geoContext,
0529                                                     position, direction);
0530 
0531       ACTS_VERBOSE(volInfo(state) << "Switched volume");
0532 
0533       reinitializeCandidates(state);
0534     } else {
0535       ACTS_ERROR(volInfo(state) << "Unknown intersection type");
0536     }
0537   }
0538 
0539  private:
0540   /// Configuration object for this navigator
0541   Config m_cfg;
0542 
0543   /// Logger instance for this navigator
0544   std::unique_ptr<const Logger> m_logger;
0545 
0546   /// @brief Get the logger instance
0547   /// @return Reference to the logger instance
0548   const Logger& logger() const { return *m_logger; }
0549 
0550   /// Helper method to reset and reinitialize the navigation candidates.
0551   void reinitializeCandidates(State& state) const {
0552     state.navigationCandidates.clear();
0553     state.activeTargetsAhead.clear();
0554     state.activeTargetsBehind.clear();
0555     state.activeTargetBehindIndex = -1;
0556 
0557     initializeVolumeCandidates(state);
0558   }
0559 
0560   /// Helper method to initialise navigation candidates for the current volume.
0561   /// @param state Navigation state to initialise candidates for
0562   void initializeVolumeCandidates(State& state) const {
0563     const TrackingVolume* volume = state.currentVolume;
0564     ACTS_VERBOSE(volInfo(state) << "Initialize volume");
0565 
0566     if (volume == nullptr) {
0567       state.navigationBreak = true;
0568       ACTS_VERBOSE(volInfo(state) << "No volume set. Good luck.");
0569       return;
0570     }
0571 
0572     emplaceAllVolumeCandidates(
0573         state.navigationCandidates, *volume, m_cfg.resolveSensitive,
0574         m_cfg.resolveMaterial, m_cfg.resolvePassive,
0575         m_cfg.boundaryToleranceSurfaceApproach, logger());
0576   }
0577 
0578   std::vector<NavigationTarget> resolveTargets(State& state,
0579                                                const Vector3& position,
0580                                                const Vector3& direction,
0581                                                double nearLimit,
0582                                                double farLimit) const {
0583     std::vector<NavigationTarget> targets;
0584 
0585     // Find intersections with all candidates
0586     for (const auto& candidate : state.navigationCandidates) {
0587       auto intersections =
0588           candidate.intersect(state.options.geoContext, position, direction,
0589                               state.options.surfaceTolerance);
0590       for (auto [intersectionIndex, intersection] :
0591            Acts::enumerate(intersections)) {
0592         // exclude invalid intersections
0593         if (!intersection.isValid() ||
0594             !detail::checkPathLength(intersection.pathLength(), nearLimit,
0595                                      farLimit)) {
0596           continue;
0597         }
0598         // store candidate
0599         targets.emplace_back(candidate.target(intersection, intersectionIndex));
0600       }
0601     }
0602 
0603     std::ranges::sort(targets, NavigationTarget::pathLengthOrder);
0604 
0605     return targets;
0606   }
0607 
0608   /// @brief Get volume information string for logging
0609   /// @param state The navigation state
0610   /// @return String containing volume name or "No Volume" followed by separator
0611   std::string volInfo(const State& state) const {
0612     return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0613                                            : "No Volume") +
0614            " | ";
0615   }
0616 };
0617 
0618 }  // namespace Acts::Experimental