Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 09:41:06

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 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Navigation/NavigationStream.hpp"
0012 #include "Acts/Surfaces/CylinderSurface.hpp"
0013 #include "Acts/Surfaces/PlaneSurface.hpp"
0014 #include "Acts/Surfaces/RectangleBounds.hpp"
0015 #include "Acts/Utilities/Intersection.hpp"
0016 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0017 
0018 using namespace Acts;
0019 
0020 namespace ActsTests {
0021 
0022 ACTS_LOCAL_LOGGER(getDefaultLogger("NavigationStreamTests",
0023                                    Logging::Level::INFO));
0024 
0025 // This creates a set of plane surfaces along the z axis
0026 std::vector<std::shared_ptr<Surface>> createPlaneSurfaces() {
0027   auto rectangle = std::make_shared<RectangleBounds>(10., 10.);
0028   // Surface A:
0029   // This surface should not be reachable from (0.,0.,0.) position along z
0030   Transform3 aTransform = Transform3::Identity();
0031   aTransform.pretranslate(Vector3(0., 0., -20.));
0032   auto surfaceA = Surface::makeShared<PlaneSurface>(aTransform, rectangle);
0033   surfaceA->assignGeometryId(GeometryIdentifier{}.withSensitive(1));
0034   // Surface B:
0035   // This surface should not be reachable from (0.,0.,0.) position along z with
0036   // boundary check
0037   Transform3 bTransform = Transform3::Identity();
0038   bTransform.pretranslate(Vector3(50., 50., 100.));
0039   auto surfaceB = Surface::makeShared<PlaneSurface>(bTransform, rectangle);
0040   surfaceB->assignGeometryId(GeometryIdentifier{}.withSensitive(2));
0041   // Surface C:
0042   Transform3 cTransform = Transform3::Identity();
0043   cTransform.pretranslate(Vector3(0., 0., 200.));
0044   auto surfaceC = Surface::makeShared<PlaneSurface>(cTransform, rectangle);
0045   surfaceC->assignGeometryId(GeometryIdentifier{}.withSensitive(3));
0046   // Surface D:
0047   Transform3 dTransform = Transform3::Identity();
0048   dTransform.pretranslate(Vector3(0., 0., 400.));
0049   auto surfaceD = Surface::makeShared<PlaneSurface>(dTransform, rectangle);
0050   surfaceD->assignGeometryId(GeometryIdentifier{}.withSensitive(4));
0051 
0052   // Let's fill them shuffled
0053   return {surfaceC, surfaceA, surfaceD, surfaceB};
0054 }
0055 
0056 // This creates a set of cylinder surfaces
0057 std::vector<std::shared_ptr<Surface>> createCylinders() {
0058   // Surface A:
0059   // A concentric cylinder with a radius of 10 and a half length of 20
0060   Transform3 aTransform = Transform3::Identity();
0061   auto surfaceA = Surface::makeShared<CylinderSurface>(aTransform, 10., 20);
0062   surfaceA->assignGeometryId(GeometryIdentifier{}.withSensitive(6));
0063 
0064   // Surface B:
0065   // A  small cylinder sitting at 20, 20
0066   Transform3 bTransform = Transform3::Identity();
0067   bTransform.pretranslate(Vector3(20., 20., 0.));
0068   auto surfaceB = Surface::makeShared<CylinderSurface>(bTransform, 2., 10);
0069   surfaceB->assignGeometryId(GeometryIdentifier{}.withSensitive(7));
0070 
0071   // Surface C:
0072   // A concentric cylinder with a radius of 40 and a half length of 20
0073   Transform3 cTransform = Transform3::Identity();
0074   auto surfaceC = Surface::makeShared<CylinderSurface>(cTransform, 40., 20);
0075   surfaceC->assignGeometryId(GeometryIdentifier{}.withSensitive(8));
0076 
0077   // Surface D:
0078   // A concentric, but shifted cylinder with a radius of 50 and a half length of
0079   // 5
0080   Transform3 dTransform = Transform3::Identity();
0081   dTransform.pretranslate(Vector3(0., 0., 10.));
0082   auto surfaceD = Surface::makeShared<CylinderSurface>(dTransform, 50., 5.);
0083   surfaceD->assignGeometryId(GeometryIdentifier{}.withSensitive(9));
0084 
0085   // Return in a shuffled order
0086   return {surfaceC, surfaceB, surfaceA, surfaceD};
0087 }
0088 
0089 auto gContext = GeometryContext::dangerouslyDefaultConstruct();
0090 
0091 // The stream intersects each candidate with the tolerance it was added with
0092 NavigationStream makeStream(std::span<const std::shared_ptr<Surface>> surfaces,
0093                             const BoundaryTolerance& tolerance) {
0094   NavigationStream stream;
0095   for (const auto& surface : surfaces) {
0096     stream.addSurfaceCandidate(*surface, tolerance);
0097   }
0098   BOOST_CHECK_EQUAL(stream.remainingCandidates(), 4u);
0099   return stream;
0100 }
0101 
0102 BOOST_AUTO_TEST_SUITE(Navigation)
0103 
0104 BOOST_AUTO_TEST_CASE(NavigationStream_InitializePlanes) {
0105   // Create the punch of surfaces
0106   auto surfaces = createPlaneSurfaces();
0107   // (1) Run an initial update
0108   // - from a position where all are reachable and valid
0109   // - with infinite boundary tolerance
0110   NavigationStream nStream =
0111       makeStream(surfaces, BoundaryTolerance::Infinite());
0112   BOOST_CHECK(nStream.initialize(
0113       gContext, {Vector3(0., 0., -30.), Vector3(0., 0., 1.)}, logger()));
0114 
0115   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 4u);
0116   BOOST_CHECK(nStream.isValid());
0117   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0118                     surfaces.at(1u).get());
0119 
0120   // (2) Run an initial update
0121   // - from a position where all but one are reachable
0122   // - with infinite boundary tolerance
0123   nStream = makeStream(surfaces, BoundaryTolerance::Infinite());
0124   BOOST_CHECK(nStream.initialize(
0125       gContext, {Vector3(0., 0., 0.), Vector3(0., 0., 1.)}, logger()));
0126   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 3u);
0127   BOOST_CHECK(nStream.isValid());
0128   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0129                     surfaces.at(3u).get());
0130 
0131   // (3) Run an initial update
0132   // - from a position where all would be reachable, but
0133   // - with no boundary tolerance
0134   nStream = makeStream(surfaces, BoundaryTolerance::None());
0135   BOOST_CHECK(nStream.initialize(
0136       gContext, {Vector3(0., 0., -100.), Vector3(0., 0., 1.)}, logger()));
0137   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 3u);
0138 
0139   // (4) Run an initial update
0140   // - none of the surfaces should be reachable
0141   nStream = makeStream(surfaces, BoundaryTolerance::Infinite());
0142   BOOST_CHECK(!nStream.initialize(
0143       gContext, {Vector3(0., 0., 0.), Vector3(1., 0., 0.)}, logger()));
0144   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 0u);
0145   BOOST_CHECK_THROW(nStream.currentCandidate(), std::out_of_range);
0146 
0147   // (5) Test de-duplication
0148   nStream = makeStream(surfaces, BoundaryTolerance::Infinite());
0149   nStream.addSurfaceCandidate(*surfaces.at(0), BoundaryTolerance::None());
0150   // One surface is duplicated in the stream
0151   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 5u);
0152   // Initialize stream reaches all surfaces, but also de-duplicates
0153   BOOST_CHECK(nStream.initialize(
0154       gContext, {Vector3(0., 0., -100.), Vector3(0., 0., 1.)}, logger()));
0155   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 4u);
0156 
0157   // (6) Even when the caller asserts uniqueness (skipping the
0158   // pre-intersection de-duplication pass), a duplicate that slips through is
0159   // still removed by the post-sort unique pass
0160   nStream = makeStream(surfaces, BoundaryTolerance::Infinite());
0161   nStream.addSurfaceCandidate(*surfaces.at(0), BoundaryTolerance::None());
0162   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 5u);
0163   BOOST_CHECK(nStream.initialize(gContext,
0164                                  {Vector3(0., 0., -100.), Vector3(0., 0., 1.)},
0165                                  logger(), s_onSurfaceTolerance,
0166                                  /*candidatesAreUnique=*/true));
0167   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 4u);
0168 }
0169 
0170 BOOST_AUTO_TEST_CASE(NavigationStream_UpdatePlanes) {
0171   // Create the punch of surfaces
0172   auto surfaces = createPlaneSurfaces();
0173 
0174   // Run an initial update
0175   // - from a position where all are reachable and valid
0176   // - with infinite boundary tolerance
0177   NavigationStream::QueryPoint qPoint = {Vector3(0., 0., -30.),
0178                                          Vector3(0., 0., 1.)};
0179 
0180   NavigationStream nStream =
0181       makeStream(surfaces, BoundaryTolerance::Infinite());
0182   BOOST_CHECK(nStream.initialize(gContext, qPoint, logger()));
0183   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 4u);
0184   BOOST_CHECK(nStream.isValid());
0185 
0186   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0187                     surfaces.at(1u).get());
0188   CHECK_CLOSE_ABS(nStream.currentCandidate().pathLength(), 10.,
0189                   std::numeric_limits<double>::epsilon());
0190 
0191   // Let's push a bit closer to the surface
0192   qPoint.position = Vector3(0., 0., -22.);
0193   BOOST_CHECK(nStream.update(gContext, qPoint, logger()));
0194   BOOST_CHECK(nStream.isValid());
0195 
0196   // Surface unchanged, but the intersection should be closer
0197   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0198                     surfaces.at(1u).get());
0199   CHECK_CLOSE_ABS(nStream.currentCandidate().pathLength(), 2.,
0200                   std::numeric_limits<double>::epsilon());
0201 
0202   // Uuuups, an overstep
0203   qPoint.position = Vector3(0., 0., -19.5);
0204   BOOST_CHECK(nStream.update(gContext, qPoint, logger()));
0205   BOOST_CHECK(nStream.isValid());
0206 
0207   // Surface still unchanged, but pathLength is now negative
0208   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0209                     surfaces.at(1u).get());
0210   CHECK_CLOSE_ABS(nStream.currentCandidate().pathLength(), -0.5,
0211                   std::numeric_limits<double>::epsilon());
0212 
0213   // Finally hit it
0214   qPoint.position = Vector3(0., 0., -20.);
0215   BOOST_CHECK(nStream.update(gContext, qPoint, logger()));
0216   BOOST_CHECK(nStream.isValid());
0217   // Surface still unchanged, however, now withL
0218   // - pathlength smaller on surface tolerance, intersection status onSurface
0219   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0220                     surfaces.at(1u).get());
0221   CHECK_CLOSE_ABS(
0222       nStream.currentCandidate().pathLength(), s_onSurfaceTolerance,
0223       std::numeric_limits<double>::epsilon() + s_onSurfaceTolerance);
0224   BOOST_CHECK_EQUAL(nStream.currentCandidate().status(),
0225                     IntersectionStatus::onSurface);
0226   // Let's say the stepper confirms this
0227   BOOST_CHECK(nStream.switchToNextCandidate());
0228   // Surface is now surfaceB
0229   BOOST_CHECK_EQUAL(&nStream.currentCandidate().surface(),
0230                     surfaces.at(3u).get());
0231   // Distance should be the initial estimate from the intialializeStream() call
0232   CHECK_CLOSE_ABS(nStream.currentCandidate().pathLength(), 130.,
0233                   std::numeric_limits<double>::epsilon());
0234   nStream = makeStream(surfaces, BoundaryTolerance::None());
0235   BOOST_CHECK(nStream.initialize(gContext, qPoint, logger()));
0236   BOOST_CHECK(nStream.switchToNextCandidate());
0237 
0238   // Query update will re-evaluate this one: however, we will miss the surface
0239   // due to outside bounds - and will switch to the next candidate: which sits
0240   // at 200 and then will yield 220
0241   BOOST_CHECK(nStream.update(gContext, qPoint, logger()));
0242   BOOST_CHECK(nStream.isValid());
0243   CHECK_CLOSE_ABS(nStream.currentCandidate().pathLength(), 220.,
0244                   std::numeric_limits<double>::epsilon());
0245   // Oh noooo, an actor just kicked in and changed the direction
0246   qPoint.direction = Vector3(0., 1., 1.).normalized();
0247   // All is lost, no surface is reachable anymore
0248   BOOST_CHECK(!nStream.update(gContext, qPoint, logger()));
0249 }
0250 
0251 BOOST_AUTO_TEST_CASE(NavigationStream_InitializeCylinders) {
0252   // Create the cylinder setup
0253   auto surfaces = createCylinders();
0254 
0255   // (1) Run an initial update - from a position/direction where all are
0256   // reachable
0257   // - with infinite boundary tolerance
0258   NavigationStream nStream =
0259       makeStream(surfaces, BoundaryTolerance::Infinite());
0260   BOOST_CHECK(nStream.initialize(
0261       gContext, {Vector3(0., 0., 0.), Vector3(1., 1., 0.).normalized()},
0262       logger()));
0263 
0264   // Keep both crossings of the small cylinder at (20, 20, 0).
0265   BOOST_REQUIRE_EQUAL(nStream.remainingCandidates(), 5u);
0266   // First one is inner candidate
0267   BOOST_CHECK_EQUAL(&nStream.candidates()[0u].surface(), surfaces.at(2).get());
0268   BOOST_CHECK_EQUAL(&nStream.candidates()[1u].surface(), surfaces.at(1).get());
0269   BOOST_CHECK_EQUAL(&nStream.candidates()[2u].surface(), surfaces.at(1).get());
0270   BOOST_CHECK_NE(nStream.candidates()[1u].intersectionIndex(),
0271                  nStream.candidates()[2u].intersectionIndex());
0272   BOOST_CHECK_EQUAL(&nStream.candidates()[3u].surface(), surfaces.at(0).get());
0273 
0274   // (2) Run an initial update - from a position/direction where only
0275   // the concentric ones are reachable
0276   // - with infinite boundary tolerance
0277   nStream = makeStream(surfaces, BoundaryTolerance::Infinite());
0278   BOOST_CHECK(nStream.initialize(
0279       gContext, {Vector3(0., 0., 0.), Vector3(1., 0., 0.)}, logger()));
0280   // We should have 3 candidates
0281   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 3u);
0282 
0283   // (3) Run an initial update - from a position/direction where only the
0284   // concentric ones within bounds are reachable
0285   nStream = makeStream(surfaces, BoundaryTolerance::None());
0286   BOOST_CHECK(nStream.initialize(
0287       gContext, {Vector3(0., 0., 0.), Vector3(1., 0., 0.)}, logger()));
0288   // We should have 2 candidates
0289   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 2u);
0290 
0291   // (4) Run an initial update - from a position/direction where none are
0292   // reachable
0293   // - (even) with infinite boundary tolerance
0294   nStream = makeStream(surfaces, BoundaryTolerance::None());
0295   BOOST_CHECK(!nStream.initialize(
0296       gContext, {Vector3(0., 0., 0.), Vector3(0., 0., 1.)}, logger()));
0297   // We should have 0 candidates
0298   BOOST_CHECK_EQUAL(nStream.remainingCandidates(), 0u);
0299   BOOST_CHECK_THROW(nStream.currentCandidate(), std::out_of_range);
0300 }
0301 
0302 BOOST_AUTO_TEST_CASE(NavigationStream_InitializeCylinderFromBoundaryInwards) {
0303   // Reproduce https://github.com/acts-project/acts/issues/6267: entering an
0304   // empty rMin = 0 volume must leave its far-side cylinder exit reachable.
0305   auto cylinder =
0306       Surface::makeShared<CylinderSurface>(Transform3::Identity(), 20., 100.);
0307   NavigationStream stream;
0308   stream.addSurfaceCandidate(*cylinder, BoundaryTolerance::None());
0309   BOOST_REQUIRE(stream.initialize(
0310       gContext, {Vector3(20., 0., 0.), Vector3(-1., 0., 0.)}, logger()));
0311 
0312   // The navigator skips intersections at or behind its near limit. Permit
0313   // the stream to retain the entry intersection, but require the exit to
0314   // survive after that entry has been skipped.
0315   while (stream.isValid() &&
0316          stream.currentCandidate().pathLength() <= s_onSurfaceTolerance) {
0317     stream.switchToNextCandidate();
0318   }
0319   BOOST_REQUIRE_MESSAGE(
0320       stream.isValid(),
0321       "Far-side cylinder exit was lost at the entry boundary");
0322   BOOST_CHECK_EQUAL(&stream.currentCandidate().surface(), cylinder.get());
0323   CHECK_CLOSE_ABS(stream.currentCandidate().pathLength(), 40.,
0324                   s_onSurfaceTolerance);
0325 }
0326 
0327 BOOST_AUTO_TEST_SUITE_END()
0328 
0329 }  // namespace ActsTests