File indexing completed on 2026-03-28 07:46:35
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Direction.hpp"
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Geometry/Blueprint.hpp"
0015 #include "Acts/Geometry/BlueprintOptions.hpp"
0016 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Geometry/NavigationPolicyFactory.hpp"
0019 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0020 #include "Acts/Geometry/TrackingGeometry.hpp"
0021 #include "Acts/Geometry/TrackingVolume.hpp"
0022 #include "Acts/MagneticField/ConstantBField.hpp"
0023 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0024 #include "Acts/Navigation/INavigationPolicy.hpp"
0025 #include "Acts/Navigation/NavigationStream.hpp"
0026 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0027 #include "Acts/Propagator/ActorList.hpp"
0028 #include "Acts/Propagator/EigenStepper.hpp"
0029 #include "Acts/Propagator/NavigationTarget.hpp"
0030 #include "Acts/Propagator/Navigator.hpp"
0031 #include "Acts/Propagator/Propagator.hpp"
0032 #include "Acts/Surfaces/Surface.hpp"
0033 #include "Acts/Utilities/Logger.hpp"
0034 #include "Acts/Utilities/Result.hpp"
0035 #include "Acts/Visualization/ObjVisualization3D.hpp"
0036 #include "ActsTests/CommonHelpers/TemporaryDirectory.hpp"
0037
0038 #include <atomic>
0039 #include <cmath>
0040 #include <cstdlib>
0041 #include <format>
0042 #include <memory>
0043 #include <numbers>
0044 #include <string>
0045 #include <vector>
0046
0047 using namespace Acts;
0048 using namespace Acts::UnitLiterals;
0049 using Experimental::Blueprint;
0050 using Experimental::BlueprintOptions;
0051 using Experimental::StaticBlueprintNode;
0052
0053 namespace ActsTests {
0054
0055 GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0056 MagneticFieldContext mfContext = MagneticFieldContext();
0057
0058 void step(Vector3& pos, const Vector3& dir, const Surface& surface) {
0059 Intersection3D intersection =
0060 surface.intersect(gctx, pos, dir).closestForward();
0061 pos += intersection.pathLength() * dir;
0062 }
0063
0064 void step(Vector3& pos, const Vector3& dir, const NavigationTarget& target) {
0065 step(pos, dir, target.surface());
0066 }
0067
0068
0069
0070
0071
0072
0073
0074
0075 class AlternatingInvalidPolicy final : public INavigationPolicy {
0076 public:
0077 struct State {
0078 int counter = 0;
0079 };
0080
0081 AlternatingInvalidPolicy(const GeometryContext& ,
0082 const TrackingVolume& ,
0083 const Logger& ) {}
0084
0085 void initializeCandidates(const GeometryContext& ,
0086 const NavigationArguments& ,
0087 NavigationPolicyState& ,
0088 AppendOnlyNavigationStream& ,
0089 const Logger& ) const {
0090
0091 }
0092
0093 void connect(NavigationDelegate& delegate) const override {
0094 connectDefault<AlternatingInvalidPolicy>(delegate);
0095 }
0096
0097 bool isValid(const GeometryContext& ,
0098 const NavigationArguments& ,
0099 NavigationPolicyState& state,
0100 const Logger& logger) const override {
0101 auto& s = state.as<State>();
0102 s.counter++;
0103 bool valid = (s.counter % 2 == 0);
0104 ACTS_VERBOSE("AlternatingInvalidPolicy isValid: counter="
0105 << s.counter << " valid=" << valid);
0106 return valid;
0107 }
0108
0109 void createState(const GeometryContext& ,
0110 const NavigationArguments& ,
0111 NavigationPolicyStateManager& stateManager,
0112 const Logger& logger) const override {
0113 ACTS_VERBOSE("AlternatingInvalidPolicy createState");
0114 stateManager.pushState<State>();
0115 }
0116
0117 void popState(NavigationPolicyStateManager& stateManager,
0118 const Logger& logger) const override {
0119 ACTS_VERBOSE("AlternatingInvalidPolicy popState");
0120 stateManager.popState();
0121 }
0122 };
0123
0124 static_assert(NavigationPolicyConcept<AlternatingInvalidPolicy>);
0125
0126
0127
0128
0129
0130
0131
0132
0133
0134 class ConeValidityPolicy final : public INavigationPolicy {
0135 public:
0136 struct State {
0137 Vector3 initialDirection = Vector3::Zero();
0138 };
0139
0140 ConeValidityPolicy(const GeometryContext& ,
0141 const TrackingVolume& , const Logger& ,
0142 double coneAngle,
0143 std::shared_ptr<std::atomic<int>> reresolutionCounter)
0144 : m_cosConeAngle(std::cos(coneAngle)),
0145 m_reresolutionCounter(std::move(reresolutionCounter)) {}
0146
0147 void initializeCandidates(const GeometryContext& ,
0148 const NavigationArguments& args,
0149 NavigationPolicyState& state,
0150 AppendOnlyNavigationStream& ,
0151 const Logger& logger) const {
0152
0153 auto& s = state.as<State>();
0154 s.initialDirection = args.direction.normalized();
0155 ACTS_VERBOSE("ConeValidityPolicy initializeCandidates: re-centered to "
0156 << s.initialDirection.transpose());
0157 }
0158
0159 void connect(NavigationDelegate& delegate) const override {
0160 connectDefault<ConeValidityPolicy>(delegate);
0161 }
0162
0163 bool isValid(const GeometryContext& , const NavigationArguments& args,
0164 NavigationPolicyState& state,
0165 const Logger& logger) const override {
0166 auto& s = state.as<State>();
0167 double dot = s.initialDirection.dot(args.direction.normalized());
0168 bool valid = (dot >= m_cosConeAngle);
0169 if (!valid) {
0170 ++(*m_reresolutionCounter);
0171 ACTS_VERBOSE("ConeValidityPolicy isValid: INVALID dot="
0172 << dot << " threshold=" << m_cosConeAngle);
0173 } else {
0174 ACTS_VERBOSE("ConeValidityPolicy isValid: VALID dot="
0175 << dot << " threshold=" << m_cosConeAngle);
0176 }
0177 return valid;
0178 }
0179
0180 void createState(const GeometryContext& ,
0181 const NavigationArguments& args,
0182 NavigationPolicyStateManager& stateManager,
0183 const Logger& logger) const override {
0184 ACTS_VERBOSE("ConeValidityPolicy createState: direction="
0185 << args.direction.transpose());
0186 auto& s = stateManager.pushState<State>();
0187 s.initialDirection = args.direction.normalized();
0188 }
0189
0190 void popState(NavigationPolicyStateManager& stateManager,
0191 const Logger& logger) const override {
0192 ACTS_VERBOSE("ConeValidityPolicy popState");
0193 stateManager.popState();
0194 }
0195
0196 private:
0197 double m_cosConeAngle;
0198 std::shared_ptr<std::atomic<int>> m_reresolutionCounter;
0199 };
0200
0201 static_assert(NavigationPolicyConcept<ConeValidityPolicy>);
0202
0203
0204
0205
0206
0207 struct StepCollector {
0208 struct this_result {
0209 std::vector<Vector3> position;
0210 std::vector<const Surface*> surfaces;
0211 };
0212
0213 using result_type = this_result;
0214
0215 template <typename propagator_state_t, typename stepper_t,
0216 typename navigator_t>
0217 Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0218 const navigator_t& , result_type& result,
0219 const Logger& ) const {
0220 result.position.push_back(stepper.position(state.stepping));
0221 if (state.navigation.currentSurface != nullptr) {
0222 result.surfaces.push_back(state.navigation.currentSurface);
0223 }
0224 return Result<void>::success();
0225 }
0226
0227 template <typename propagator_state_t, typename stepper_t,
0228 typename navigator_t>
0229 bool checkAbort(propagator_state_t& , const stepper_t& ,
0230 const navigator_t& , result_type& ,
0231 const Logger& ) const {
0232 return false;
0233 }
0234 };
0235
0236
0237
0238
0239
0240 std::unique_ptr<TrackingGeometry> buildBoxGeometry(const BlueprintOptions& opts,
0241 const Logger& logger) {
0242 Blueprint::Config cfg;
0243 cfg.envelope = ExtentEnvelope{{
0244 .x = {20_mm, 20_mm},
0245 .y = {20_mm, 20_mm},
0246 .z = {20_mm, 20_mm},
0247 }};
0248
0249 Blueprint root{cfg};
0250
0251
0252 auto outerBounds = std::make_shared<CuboidVolumeBounds>(1_m, 1_m, 1_m);
0253 auto outerVol = std::make_unique<TrackingVolume>(Transform3::Identity(),
0254 outerBounds, "Outer");
0255
0256 auto outerNode = std::make_shared<StaticBlueprintNode>(std::move(outerVol));
0257
0258
0259 auto box1Bounds =
0260 std::make_shared<CuboidVolumeBounds>(300_mm, 300_mm, 300_mm);
0261 auto box1Vol = std::make_unique<TrackingVolume>(
0262 Transform3(Translation3(-500_mm, 0, 0)), box1Bounds, "BoxLeft");
0263
0264 auto box1Node = std::make_shared<StaticBlueprintNode>(std::move(box1Vol));
0265
0266
0267 auto box2Bounds =
0268 std::make_shared<CuboidVolumeBounds>(300_mm, 300_mm, 300_mm);
0269 auto box2Vol = std::make_unique<TrackingVolume>(
0270 Transform3(Translation3(500_mm, 0, 0)), box2Bounds, "BoxRight");
0271
0272 auto box2Node = std::make_shared<StaticBlueprintNode>(std::move(box2Vol));
0273
0274
0275 auto box3Bounds =
0276 std::make_shared<CuboidVolumeBounds>(200_mm, 200_mm, 200_mm);
0277 auto box3Vol = std::make_unique<TrackingVolume>(
0278 Transform3(Translation3(500_mm, 600_mm, 0)), box3Bounds, "BoxTop");
0279
0280 auto box3Node = std::make_shared<StaticBlueprintNode>(std::move(box3Vol));
0281
0282 outerNode->addChild(box1Node);
0283 outerNode->addChild(box2Node);
0284 outerNode->addChild(box3Node);
0285
0286 root.addChild(outerNode);
0287
0288 return root.construct(opts, gctx, logger);
0289 }
0290
0291
0292
0293
0294
0295 BOOST_AUTO_TEST_SUITE(NavigationPolicyStateSuite)
0296
0297
0298
0299 BOOST_AUTO_TEST_CASE(AlternatingInvalidPolicy_StraightLine) {
0300 TemporaryDirectory tmp{};
0301 auto logger = getDefaultLogger("AlternatingTest", Logging::VERBOSE);
0302
0303
0304 BlueprintOptions opts;
0305 opts.defaultNavigationPolicyFactory = NavigationPolicyFactory{}
0306 .add<TryAllNavigationPolicy>()
0307 .add<AlternatingInvalidPolicy>()
0308 .asUniquePtr();
0309
0310 auto trackingGeometry = buildBoxGeometry(opts, *logger);
0311 BOOST_REQUIRE(trackingGeometry);
0312 BOOST_CHECK(trackingGeometry->geometryVersion() ==
0313 TrackingGeometry::GeometryVersion::Gen3);
0314
0315
0316 ObjVisualization3D vis;
0317 trackingGeometry->visualize(vis, gctx);
0318 vis.write(tmp.path() / "alternating_invalid_policy_geometry.obj");
0319
0320
0321 auto sharedGeometry =
0322 std::shared_ptr<const TrackingGeometry>(std::move(trackingGeometry));
0323
0324 Navigator::Config navCfg;
0325 navCfg.trackingGeometry = sharedGeometry;
0326 navCfg.resolveSensitive = true;
0327 navCfg.resolveMaterial = true;
0328 navCfg.resolvePassive = false;
0329 Navigator navigator(navCfg, logger->clone("Navigator"));
0330
0331
0332 Navigator::Options options(gctx);
0333 Navigator::State state = navigator.makeState(options);
0334
0335 Vector3 position{-900_mm, 0, 0};
0336 Vector3 direction = Vector3::UnitX();
0337
0338 Result<void> result =
0339 navigator.initialize(state, position, direction, Direction::Forward());
0340 BOOST_REQUIRE(result.ok());
0341 BOOST_REQUIRE(state.currentVolume != nullptr);
0342
0343
0344 std::vector<std::string> visitedVolumes;
0345 visitedVolumes.push_back(state.currentVolume->volumeName());
0346
0347 int maxSteps = 200;
0348 for (int i = 0; i < maxSteps; ++i) {
0349 NavigationTarget target = navigator.nextTarget(state, position, direction);
0350
0351 if (target.isNone()) {
0352 break;
0353 }
0354
0355 step(position, direction, target);
0356 navigator.handleSurfaceReached(state, position, direction,
0357 target.surface());
0358
0359 if (state.currentVolume != nullptr &&
0360 (visitedVolumes.empty() ||
0361 visitedVolumes.back() != state.currentVolume->volumeName())) {
0362 visitedVolumes.push_back(state.currentVolume->volumeName());
0363 }
0364
0365 if (state.navigationBreak) {
0366 break;
0367 }
0368 }
0369
0370
0371 BOOST_CHECK(state.navigationBreak || state.currentVolume == nullptr);
0372
0373
0374
0375 BOOST_CHECK_GE(visitedVolumes.size(), 3u);
0376
0377
0378 for (const auto& name : visitedVolumes) {
0379 BOOST_TEST_MESSAGE("Visited: " << name);
0380 }
0381 }
0382
0383
0384
0385 BOOST_AUTO_TEST_CASE(ConeValidityPolicy_MagneticField) {
0386 TemporaryDirectory tmp{};
0387 auto logger = getDefaultLogger("ConeTest", Logging::VERBOSE);
0388
0389 const double coneAngle = 10.0 * std::numbers::pi / 180.0;
0390
0391 using EigenStepperType = EigenStepper<>;
0392 using EigenPropagatorType = Propagator<EigenStepperType, Navigator>;
0393
0394
0395 auto runSubCase = [&](double bFieldZ, const std::string& label) -> int {
0396 auto counter = std::make_shared<std::atomic<int>>(0);
0397
0398 BlueprintOptions opts;
0399 opts.defaultNavigationPolicyFactory =
0400 NavigationPolicyFactory{}
0401 .add<TryAllNavigationPolicy>()
0402 .add<ConeValidityPolicy>(coneAngle, counter)
0403 .asUniquePtr();
0404
0405 auto trackingGeometry = buildBoxGeometry(opts, *logger);
0406 BOOST_REQUIRE(trackingGeometry);
0407
0408 ObjVisualization3D vis;
0409 trackingGeometry->visualize(vis, gctx);
0410 vis.write(tmp.path() / "cone_validity_geometry.obj");
0411
0412 auto sharedGeometry =
0413 std::shared_ptr<const TrackingGeometry>(std::move(trackingGeometry));
0414
0415 auto bField = std::make_shared<ConstantBField>(Vector3{0, 0, bFieldZ});
0416 EigenStepperType stepper(bField);
0417
0418 Navigator::Config navCfg;
0419 navCfg.trackingGeometry = sharedGeometry;
0420 navCfg.resolveSensitive = true;
0421 navCfg.resolveMaterial = true;
0422 navCfg.resolvePassive = false;
0423 Navigator navigator(navCfg, logger->clone("Navigator"));
0424
0425 EigenPropagatorType propagator(std::move(stepper), std::move(navigator),
0426 logger->clone("Propagator"));
0427
0428
0429 Vector3 startPos{-900_mm, 0, 0};
0430 Vector3 startDir = Vector3::UnitX();
0431
0432 BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0433 Vector4(startPos.x(), startPos.y(), startPos.z(), 0), startDir,
0434 1.0 / 1_GeV, std::nullopt, ParticleHypothesis::pion());
0435
0436 using MyActorList = ActorList<StepCollector, EndOfWorldReached>;
0437 EigenPropagatorType::Options<MyActorList> propOpts(gctx, mfContext);
0438 propOpts.pathLimit = 5_m;
0439 propOpts.stepping.maxStepSize = 100_mm;
0440
0441 auto result = propagator.propagate(start, propOpts);
0442 BOOST_REQUIRE(result.ok());
0443
0444 const auto& info =
0445 result.value().template get<StepCollector::this_result>();
0446
0447 const auto& positions = info.position;
0448 if (!positions.empty()) {
0449 ObjVisualization3D trajVis;
0450 for (std::size_t i = 0; i + 1 < positions.size(); ++i) {
0451 trajVis.line(positions[i], positions[i + 1]);
0452 }
0453 trajVis.write(tmp.path() / std::format("trajectory_{}.obj", label));
0454 }
0455
0456 ObjVisualization3D srfVis;
0457 if (!info.surfaces.empty()) {
0458 for (const auto* srf : info.surfaces) {
0459 std::cout << srf->toString(gctx) << std::endl;
0460 if (srf->type() != Surface::Plane ||
0461 srf->bounds().type() != SurfaceBounds::eRectangle) {
0462 continue;
0463 }
0464 srf->visualize(srfVis, gctx);
0465 }
0466 srfVis.write(tmp.path() / std::format("surfaces_{}.obj", label));
0467 }
0468
0469 return counter->load();
0470 };
0471
0472
0473 int strongReresolutions = runSubCase(-1.6_T, "strong");
0474 BOOST_TEST_MESSAGE("Strong field re-resolutions: " << strongReresolutions);
0475 BOOST_CHECK_GT(strongReresolutions, 0);
0476
0477
0478 int weakReresolutions = runSubCase(0.001_T, "weak");
0479 BOOST_TEST_MESSAGE("Weak field re-resolutions: " << weakReresolutions);
0480
0481
0482 BOOST_CHECK_GT(strongReresolutions, weakReresolutions);
0483 }
0484
0485 BOOST_AUTO_TEST_SUITE_END()
0486
0487 }