File indexing completed on 2026-08-14 08:19:17
0001
0002
0003
0004
0005
0006
0007
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
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048 class TryAllNavigator final {
0049 public:
0050
0051 struct Config final {
0052
0053 std::shared_ptr<const TrackingGeometry> trackingGeometry;
0054
0055
0056 bool resolveSensitive = true;
0057
0058 bool resolveMaterial = true;
0059
0060 bool resolvePassive = false;
0061
0062
0063 BoundaryTolerance boundaryToleranceSurfaceApproach =
0064 BoundaryTolerance::None();
0065 };
0066
0067
0068 struct Options final : public NavigatorPlainOptions {
0069
0070 explicit Options(const GeometryContext& gctx)
0071 : NavigatorPlainOptions(gctx) {}
0072
0073
0074 double surfaceTolerance = s_onSurfaceTolerance;
0075
0076
0077 double nearLimit = s_onSurfaceTolerance;
0078
0079
0080 double farLimit = std::numeric_limits<double>::max();
0081
0082
0083 void setPlainOptions(const NavigatorPlainOptions& options) {
0084 static_cast<NavigatorPlainOptions&>(*this) = options;
0085 }
0086 };
0087
0088
0089 struct State final {
0090
0091 explicit State(const Options& options_) : options(options_) {}
0092
0093
0094 Options options;
0095
0096
0097
0098
0099
0100 const Surface* startSurface = nullptr;
0101
0102
0103
0104
0105
0106 const Surface* targetSurface = nullptr;
0107
0108
0109
0110
0111 const Surface* currentSurface = nullptr;
0112
0113 const TrackingVolume* currentVolume = nullptr;
0114
0115
0116 std::vector<detail::NavigationObjectCandidate> navigationCandidates;
0117
0118
0119 bool navigationBreak = false;
0120
0121
0122 NavigatorStatistics statistics;
0123
0124
0125 std::vector<NavigationTarget> activeTargetsAhead;
0126
0127 std::vector<NavigationTarget> activeTargetsBehind;
0128
0129
0130 std::int32_t activeTargetBehindIndex = -1;
0131
0132
0133 std::optional<Vector3> lastPosition;
0134
0135
0136
0137
0138
0139 const std::vector<NavigationTarget>& currentTargets() const {
0140 if (hasTargetsBehind()) {
0141 return activeTargetsBehind;
0142 }
0143 return activeTargetsAhead;
0144 }
0145
0146
0147
0148 const NavigationTarget& activeTargetBehind() const {
0149 return activeTargetsBehind.at(activeTargetBehindIndex);
0150 }
0151
0152
0153
0154
0155 bool hasTargetsBehind() const {
0156 return !activeTargetsBehind.empty() &&
0157 activeTargetBehindIndex <
0158 static_cast<std::int32_t>(activeTargetsBehind.size());
0159 }
0160 };
0161
0162
0163
0164
0165
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
0172
0173
0174 State makeState(const Options& options) const {
0175 State state(options);
0176 return state;
0177 }
0178
0179
0180
0181
0182 const Surface* currentSurface(const State& state) const {
0183 return state.currentSurface;
0184 }
0185
0186
0187
0188
0189 const TrackingVolume* currentVolume(const State& state) const {
0190 return state.currentVolume;
0191 }
0192
0193
0194
0195
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
0204
0205
0206 const Surface* startSurface(const State& state) const {
0207 return state.startSurface;
0208 }
0209
0210
0211
0212
0213 const Surface* targetSurface(const State& state) const {
0214 return state.targetSurface;
0215 }
0216
0217
0218
0219
0220 bool endOfWorldReached(State& state) const {
0221 return state.currentVolume == nullptr;
0222 }
0223
0224
0225
0226
0227 bool navigationBreak(const State& state) const {
0228 return state.navigationBreak;
0229 }
0230
0231
0232
0233
0234
0235
0236
0237
0238
0239
0240
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
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
0291 reinitializeCandidates(state);
0292
0293 state.lastPosition.reset();
0294
0295 return Result<void>::success();
0296 }
0297
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307
0308
0309 NavigationTarget nextTarget(State& state, const Vector3& position,
0310 const Vector3& direction) const {
0311
0312 state.currentSurface = nullptr;
0313
0314
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
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
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
0423
0424
0425
0426
0427
0428
0429
0430
0431
0432
0433
0434
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
0445
0446
0447
0448
0449
0450
0451
0452 void handleSurfaceReached(State& state, const Vector3& position,
0453 const Vector3& direction,
0454 const Surface& ) const {
0455
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
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
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
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
0543 Config m_cfg;
0544
0545
0546 std::unique_ptr<const Logger> m_logger;
0547
0548
0549
0550 const Logger& logger() const { return *m_logger; }
0551
0552
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
0563
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
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
0595 if (!intersection.isValid() ||
0596 !detail::checkPathLength(intersection.pathLength(), nearLimit,
0597 farLimit)) {
0598 continue;
0599 }
0600
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
0611
0612
0613 std::string volInfo(const State& state) const {
0614 return (state.currentVolume != nullptr ? state.currentVolume->volumeName()
0615 : "No Volume") +
0616 " | ";
0617 }
0618 };
0619
0620 }