Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-12 09:39:47

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