Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:08:42

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/tools/context.hpp>
0010 #include <boost/test/tools/old/interface.hpp>
0011 #include <boost/test/unit_test.hpp>
0012 #include <boost/test/unit_test_suite.hpp>
0013 
0014 #include "Acts/Definitions/Units.hpp"
0015 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0016 #include "Acts/Geometry/CutoutCylinderVolumeBounds.hpp"
0017 #include "Acts/Geometry/CylinderPortalShell.hpp"
0018 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0019 #include "Acts/Geometry/GridPortalLink.hpp"
0020 #include "Acts/Geometry/Portal.hpp"
0021 #include "Acts/Geometry/TrackingVolume.hpp"
0022 #include "Acts/Geometry/TrivialPortalLink.hpp"
0023 #include "Acts/Surfaces/SurfaceMergingException.hpp"
0024 
0025 #include <stdexcept>
0026 
0027 using namespace Acts;
0028 using namespace Acts::UnitLiterals;
0029 
0030 namespace ActsTests {
0031 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0032 
0033 std::size_t getVolumeIndex() {
0034   static std::size_t i = 1;
0035   return i++;
0036 }
0037 
0038 auto makeVolume(auto&&... pars) {
0039   TrackingVolume vol(Transform3::Identity(),
0040                      std::make_shared<CylinderVolumeBounds>(
0041                          std::forward<decltype(pars)>(pars)...));
0042   vol.setVolumeName("cyl" + std::to_string(getVolumeIndex()));
0043   return vol;
0044 };
0045 
0046 auto logger = getDefaultLogger("UnitTests", Logging::VERBOSE);
0047 
0048 BOOST_AUTO_TEST_SUITE(GeometrySuite)
0049 
0050 BOOST_AUTO_TEST_CASE(ConstructionFromVolume) {
0051   // - Cylinder
0052   // |           | no phi | phi |
0053   // | --------- | ------ | --- |
0054   // | rMin > 0  | 1      | 3   |
0055   // | rMin == 0 | 2      | 4   |
0056 
0057   auto cyl1 = makeVolume(30_mm, 40_mm, 100_mm);
0058   auto cyl2 = makeVolume(0_mm, 40_mm, 100_mm);
0059   auto cyl3 = makeVolume(30_mm, 40_mm, 100_mm, 45_degree);
0060   auto cyl4 = makeVolume(0_mm, 40_mm, 100_mm, 45_degree);
0061 
0062   TrackingVolume boxVolume(
0063       Transform3::Identity(),
0064       std::make_shared<CuboidVolumeBounds>(10_mm, 10_mm, 10_mm));
0065 
0066   BOOST_CHECK_THROW(SingleCylinderPortalShell{boxVolume},
0067                     std::invalid_argument);
0068 
0069   SingleCylinderPortalShell shell1{cyl1};
0070   BOOST_CHECK_EQUAL(shell1.size(), 4);
0071 
0072   using enum CylinderVolumeBounds::Face;
0073 
0074   const auto* pDisc = shell1.portal(PositiveDisc).get();
0075   BOOST_REQUIRE_NE(pDisc, nullptr);
0076   BOOST_CHECK_EQUAL(
0077       pDisc
0078           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, -Vector3::UnitZ())
0079           .value(),
0080       &cyl1);
0081   BOOST_CHECK_EQUAL(
0082       pDisc->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, Vector3::UnitZ())
0083           .value(),
0084       nullptr);
0085 
0086   const auto* nDisc = shell1.portal(NegativeDisc).get();
0087   BOOST_REQUIRE_NE(nDisc, nullptr);
0088   BOOST_CHECK_EQUAL(nDisc
0089                         ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm},
0090                                         -Vector3::UnitZ())
0091                         .value(),
0092                     nullptr);
0093   BOOST_CHECK_EQUAL(
0094       nDisc
0095           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm}, Vector3::UnitZ())
0096           .value(),
0097       &cyl1);
0098 
0099   const auto* oCyl = shell1.portal(OuterCylinder).get();
0100   BOOST_REQUIRE_NE(oCyl, nullptr);
0101   BOOST_CHECK_EQUAL(
0102       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, Vector3::UnitX())
0103           .value(),
0104       nullptr);
0105   BOOST_CHECK_EQUAL(
0106       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0107           .value(),
0108       &cyl1);
0109 
0110   const auto* iCyl = shell1.portal(InnerCylinder).get();
0111   BOOST_REQUIRE_NE(iCyl, nullptr);
0112   BOOST_CHECK_EQUAL(
0113       iCyl->resolveVolume(gctx, Vector3{30_mm, 0_mm, 10_mm}, Vector3::UnitX())
0114           .value(),
0115       &cyl1);
0116   BOOST_CHECK_EQUAL(
0117       iCyl->resolveVolume(gctx, Vector3{30_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0118           .value(),
0119       nullptr);
0120 
0121   SingleCylinderPortalShell shell2{cyl2};
0122   BOOST_CHECK_EQUAL(shell2.size(), 3);
0123 
0124   pDisc = shell2.portal(PositiveDisc).get();
0125   BOOST_REQUIRE_NE(pDisc, nullptr);
0126   BOOST_CHECK_EQUAL(
0127       pDisc
0128           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, -Vector3::UnitZ())
0129           .value(),
0130       &cyl2);
0131   BOOST_CHECK_EQUAL(
0132       pDisc->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, Vector3::UnitZ())
0133           .value(),
0134       nullptr);
0135 
0136   nDisc = shell2.portal(NegativeDisc).get();
0137   BOOST_REQUIRE_NE(nDisc, nullptr);
0138   BOOST_CHECK_EQUAL(nDisc
0139                         ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm},
0140                                         -Vector3::UnitZ())
0141                         .value(),
0142                     nullptr);
0143   BOOST_CHECK_EQUAL(
0144       nDisc
0145           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm}, Vector3::UnitZ())
0146           .value(),
0147       &cyl2);
0148 
0149   oCyl = shell2.portal(OuterCylinder).get();
0150   BOOST_REQUIRE_NE(oCyl, nullptr);
0151   BOOST_CHECK_EQUAL(
0152       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, Vector3::UnitX())
0153           .value(),
0154       nullptr);
0155   BOOST_CHECK_EQUAL(
0156       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0157           .value(),
0158       &cyl2);
0159 
0160   iCyl = shell2.portal(InnerCylinder).get();
0161   BOOST_CHECK_EQUAL(iCyl, nullptr);
0162 
0163   SingleCylinderPortalShell shell3{cyl3};
0164   BOOST_CHECK_EQUAL(shell3.size(), 6);
0165 
0166   pDisc = shell3.portal(PositiveDisc).get();
0167   BOOST_REQUIRE_NE(pDisc, nullptr);
0168   BOOST_CHECK_EQUAL(
0169       pDisc
0170           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, -Vector3::UnitZ())
0171           .value(),
0172       &cyl3);
0173   BOOST_CHECK_EQUAL(
0174       pDisc->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, Vector3::UnitZ())
0175           .value(),
0176       nullptr);
0177 
0178   nDisc = shell3.portal(NegativeDisc).get();
0179   BOOST_REQUIRE_NE(nDisc, nullptr);
0180   BOOST_CHECK_EQUAL(nDisc
0181                         ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm},
0182                                         -Vector3::UnitZ())
0183                         .value(),
0184                     nullptr);
0185   BOOST_CHECK_EQUAL(
0186       nDisc
0187           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm}, Vector3::UnitZ())
0188           .value(),
0189       &cyl3);
0190 
0191   oCyl = shell3.portal(OuterCylinder).get();
0192   BOOST_REQUIRE_NE(oCyl, nullptr);
0193   BOOST_CHECK_EQUAL(
0194       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, Vector3::UnitX())
0195           .value(),
0196       nullptr);
0197   BOOST_CHECK_EQUAL(
0198       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0199           .value(),
0200       &cyl3);
0201 
0202   iCyl = shell3.portal(InnerCylinder).get();
0203   BOOST_REQUIRE_NE(iCyl, nullptr);
0204   BOOST_CHECK_EQUAL(
0205       iCyl->resolveVolume(gctx, Vector3{30_mm, 0_mm, 10_mm}, Vector3::UnitX())
0206           .value(),
0207       &cyl3);
0208   BOOST_CHECK_EQUAL(
0209       iCyl->resolveVolume(gctx, Vector3{30_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0210           .value(),
0211       nullptr);
0212 
0213   auto anglePoint = [](double angle, double r, double z) {
0214     return Vector3{r * std::cos(angle), r * std::sin(angle), z};
0215   };
0216 
0217   const auto* nPhi = shell3.portal(NegativePhiPlane).get();
0218   BOOST_REQUIRE_NE(nPhi, nullptr);
0219   Vector3 point = anglePoint(-45_degree, 35_mm, 10_mm);
0220   Vector3 dir = Vector3::UnitZ().cross(point).normalized();
0221   Vector3 idir = (-Vector3::UnitZ()).cross(point).normalized();
0222   BOOST_CHECK_EQUAL(nPhi->resolveVolume(gctx, point, dir).value(), nullptr);
0223   BOOST_CHECK_EQUAL(nPhi->resolveVolume(gctx, point, idir).value(), &cyl3);
0224 
0225   const auto* pPhi = shell3.portal(PositivePhiPlane).get();
0226   BOOST_REQUIRE_NE(pPhi, nullptr);
0227   point = anglePoint(45_degree, 35_mm, 10_mm);
0228   dir = Vector3::UnitZ().cross(point).normalized();
0229   idir = (-Vector3::UnitZ()).cross(point).normalized();
0230   BOOST_CHECK_EQUAL(pPhi->resolveVolume(gctx, point, dir).value(), nullptr);
0231   BOOST_CHECK_EQUAL(pPhi->resolveVolume(gctx, point, idir).value(), &cyl3);
0232 
0233   SingleCylinderPortalShell shell4{cyl4};
0234   BOOST_CHECK_EQUAL(shell4.size(), 5);
0235 
0236   pDisc = shell4.portal(PositiveDisc).get();
0237   BOOST_REQUIRE_NE(pDisc, nullptr);
0238   BOOST_CHECK_EQUAL(
0239       pDisc
0240           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, -Vector3::UnitZ())
0241           .value(),
0242       &cyl4);
0243   BOOST_CHECK_EQUAL(
0244       pDisc->resolveVolume(gctx, Vector3{35_mm, 0_mm, 100_mm}, Vector3::UnitZ())
0245           .value(),
0246       nullptr);
0247 
0248   nDisc = shell4.portal(NegativeDisc).get();
0249   BOOST_REQUIRE_NE(nDisc, nullptr);
0250   BOOST_CHECK_EQUAL(nDisc
0251                         ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm},
0252                                         -Vector3::UnitZ())
0253                         .value(),
0254                     nullptr);
0255   BOOST_CHECK_EQUAL(
0256       nDisc
0257           ->resolveVolume(gctx, Vector3{35_mm, 0_mm, -100_mm}, Vector3::UnitZ())
0258           .value(),
0259       &cyl4);
0260 
0261   oCyl = shell4.portal(OuterCylinder).get();
0262   BOOST_REQUIRE_NE(oCyl, nullptr);
0263   BOOST_CHECK_EQUAL(
0264       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, Vector3::UnitX())
0265           .value(),
0266       nullptr);
0267   BOOST_CHECK_EQUAL(
0268       oCyl->resolveVolume(gctx, Vector3{40_mm, 0_mm, 10_mm}, -Vector3::UnitX())
0269           .value(),
0270       &cyl4);
0271 
0272   iCyl = shell4.portal(InnerCylinder).get();
0273   BOOST_CHECK_EQUAL(iCyl, nullptr);
0274 
0275   nPhi = shell4.portal(NegativePhiPlane).get();
0276   BOOST_REQUIRE_NE(nPhi, nullptr);
0277   point = anglePoint(-45_degree, 35_mm, 10_mm);
0278   dir = Vector3::UnitZ().cross(point).normalized();
0279   idir = (-Vector3::UnitZ()).cross(point).normalized();
0280   BOOST_CHECK_EQUAL(nPhi->resolveVolume(gctx, point, dir).value(), nullptr);
0281   BOOST_CHECK_EQUAL(nPhi->resolveVolume(gctx, point, idir).value(), &cyl4);
0282 
0283   pPhi = shell4.portal(PositivePhiPlane).get();
0284   BOOST_REQUIRE_NE(pPhi, nullptr);
0285   point = anglePoint(45_degree, 35_mm, 10_mm);
0286   dir = Vector3::UnitZ().cross(point).normalized();
0287   idir = (-Vector3::UnitZ()).cross(point).normalized();
0288   BOOST_CHECK_EQUAL(pPhi->resolveVolume(gctx, point, dir).value(), nullptr);
0289   BOOST_CHECK_EQUAL(pPhi->resolveVolume(gctx, point, idir).value(), &cyl4);
0290 }
0291 
0292 //              outer cylinder
0293 //          +----------+----------+
0294 //          |          |          |
0295 // negative |          v          | positive
0296 //     disc +-->               <--+ disc
0297 //          |          ^          |
0298 //          |          |          |
0299 //          +----------+----------+
0300 //              inner cylinder
0301 
0302 BOOST_AUTO_TEST_CASE(PortalAssignment) {
0303   using enum CylinderVolumeBounds::Face;
0304   TrackingVolume vol(
0305       Transform3::Identity(),
0306       std::make_shared<CylinderVolumeBounds>(30_mm, 100_mm, 100_mm));
0307 
0308   SingleCylinderPortalShell shell{vol};
0309 
0310   const auto iCyl = shell.portal(InnerCylinder);
0311   const auto pDisc = shell.portal(PositiveDisc);
0312   auto oCyl = shell.portal(OuterCylinder);
0313   auto nDisc = shell.portal(NegativeDisc);
0314 
0315   // Setting new outer cylinder
0316   BOOST_REQUIRE_NE(oCyl, nullptr);
0317   auto* oCylLink = dynamic_cast<const TrivialPortalLink*>(
0318       oCyl->getLink(Direction::OppositeNormal()));
0319   BOOST_REQUIRE_NE(oCylLink, nullptr);
0320 
0321   auto grid = oCylLink->makeGrid(AxisDirection::AxisZ);
0322 
0323   auto portal2 =
0324       std::make_shared<Portal>(Direction::OppositeNormal(), std::move(grid));
0325   shell.setPortal(portal2, OuterCylinder);
0326   BOOST_CHECK_EQUAL(shell.portal(OuterCylinder), portal2);
0327 
0328   // Other portals should stay the same
0329   BOOST_CHECK_EQUAL(shell.portal(InnerCylinder), iCyl);
0330   BOOST_CHECK_EQUAL(shell.portal(PositiveDisc), pDisc);
0331   BOOST_CHECK_EQUAL(shell.portal(NegativeDisc), nDisc);
0332 
0333   // Setting new negative disc
0334   BOOST_REQUIRE_NE(nDisc, nullptr);
0335   auto* nDiscLink = dynamic_cast<const TrivialPortalLink*>(
0336       nDisc->getLink(Direction::AlongNormal()));
0337   BOOST_REQUIRE_NE(nDiscLink, nullptr);
0338 
0339   grid = nDiscLink->makeGrid(AxisDirection::AxisR);
0340 
0341   auto portal3 =
0342       std::make_shared<Portal>(Direction::AlongNormal(), std::move(grid));
0343   shell.setPortal(portal3, NegativeDisc);
0344   BOOST_CHECK_EQUAL(shell.portal(NegativeDisc), portal3);
0345 
0346   // Other portals should stay the same
0347   BOOST_CHECK_EQUAL(shell.portal(OuterCylinder), portal2);
0348   BOOST_CHECK_EQUAL(shell.portal(InnerCylinder), iCyl);
0349   BOOST_CHECK_EQUAL(shell.portal(PositiveDisc), pDisc);
0350 }
0351 
0352 BOOST_AUTO_TEST_SUITE(CylinderStack)
0353 BOOST_AUTO_TEST_CASE(ZDirection) {
0354   using enum CylinderVolumeBounds::Face;
0355   BOOST_TEST_CONTEXT("rMin>0") {
0356     TrackingVolume vol1(
0357         Transform3{Translation3{Vector3::UnitZ() * -100_mm}},
0358         std::make_shared<CylinderVolumeBounds>(30_mm, 100_mm, 100_mm));
0359 
0360     TrackingVolume vol2(
0361         Transform3{Translation3{Vector3::UnitZ() * 100_mm}},
0362         std::make_shared<CylinderVolumeBounds>(30_mm, 100_mm, 100_mm));
0363 
0364     SingleCylinderPortalShell shell1{vol1};
0365     SingleCylinderPortalShell shell2{vol2};
0366 
0367     BOOST_CHECK_NE(shell1.portal(PositiveDisc), shell2.portal(NegativeDisc));
0368 
0369     CylinderStackPortalShell stack{
0370         gctx, {&shell1, &shell2}, AxisDirection::AxisZ};
0371     BOOST_CHECK_EQUAL(stack.size(), 4);
0372 
0373     const auto iCyl = stack.portal(InnerCylinder);
0374     BOOST_CHECK_EQUAL(shell1.portal(InnerCylinder), iCyl);
0375     BOOST_CHECK_EQUAL(shell2.portal(InnerCylinder), iCyl);
0376 
0377     const auto oCyl = stack.portal(OuterCylinder);
0378     BOOST_CHECK_EQUAL(shell1.portal(OuterCylinder), oCyl);
0379     BOOST_CHECK_EQUAL(shell2.portal(OuterCylinder), oCyl);
0380 
0381     BOOST_CHECK_EQUAL(stack.portal(PositiveDisc), shell2.portal(PositiveDisc));
0382     BOOST_CHECK_EQUAL(stack.portal(NegativeDisc), shell1.portal(NegativeDisc));
0383     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0384     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0385 
0386     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0387     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0388 
0389     // Disc portals have been fused
0390     BOOST_CHECK_EQUAL(shell1.portal(PositiveDisc), shell2.portal(NegativeDisc));
0391     shell1 = SingleCylinderPortalShell{vol1};
0392     shell2 = SingleCylinderPortalShell{vol2};
0393 
0394     BOOST_CHECK_THROW(CylinderStackPortalShell(gctx, {&shell1, &shell2},
0395                                                AxisDirection::AxisR),
0396                       SurfaceMergingException);
0397   }
0398 
0399   BOOST_TEST_CONTEXT("rMin==0") {
0400     TrackingVolume vol1(
0401         Transform3{Translation3{Vector3::UnitZ() * -100_mm}},
0402         std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm));
0403 
0404     TrackingVolume vol2(
0405         Transform3{Translation3{Vector3::UnitZ() * 100_mm}},
0406         std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm));
0407 
0408     SingleCylinderPortalShell shell1{vol1};
0409     SingleCylinderPortalShell shell2{vol2};
0410 
0411     BOOST_CHECK_EQUAL(shell1.portal(InnerCylinder), nullptr);
0412     BOOST_CHECK_EQUAL(shell2.portal(InnerCylinder), nullptr);
0413 
0414     BOOST_CHECK_NE(shell1.portal(PositiveDisc), shell2.portal(NegativeDisc));
0415 
0416     CylinderStackPortalShell stack{
0417         gctx, {&shell1, &shell2}, AxisDirection::AxisZ};
0418     BOOST_CHECK_EQUAL(stack.size(), 3);
0419 
0420     // Disc portals have been fused
0421     BOOST_CHECK_EQUAL(shell1.portal(PositiveDisc), shell2.portal(NegativeDisc));
0422 
0423     const auto iCyl = stack.portal(InnerCylinder);
0424     BOOST_CHECK_EQUAL(iCyl, nullptr);
0425 
0426     const auto oCyl = stack.portal(OuterCylinder);
0427     BOOST_CHECK_EQUAL(shell1.portal(OuterCylinder), oCyl);
0428     BOOST_CHECK_EQUAL(shell2.portal(OuterCylinder), oCyl);
0429 
0430     BOOST_CHECK_EQUAL(stack.portal(PositiveDisc), shell2.portal(PositiveDisc));
0431     BOOST_CHECK_EQUAL(stack.portal(NegativeDisc), shell1.portal(NegativeDisc));
0432 
0433     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0434     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0435 
0436     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0437     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0438 
0439     shell1 = SingleCylinderPortalShell{vol1};
0440     shell2 = SingleCylinderPortalShell{vol2};
0441 
0442     BOOST_CHECK_THROW(CylinderStackPortalShell(gctx, {&shell1, &shell2},
0443                                                AxisDirection::AxisR),
0444                       SurfaceMergingException);
0445   }
0446 }
0447 
0448 BOOST_AUTO_TEST_CASE(RDirection) {
0449   using enum CylinderVolumeBounds::Face;
0450   BOOST_TEST_CONTEXT("rMin>0") {
0451     TrackingVolume vol1(
0452         Transform3::Identity(),
0453         std::make_shared<CylinderVolumeBounds>(30_mm, 100_mm, 100_mm));
0454 
0455     TrackingVolume vol2(
0456         Transform3::Identity(),
0457         std::make_shared<CylinderVolumeBounds>(100_mm, 150_mm, 100_mm));
0458 
0459     SingleCylinderPortalShell shell1{vol1};
0460     SingleCylinderPortalShell shell2{vol2};
0461 
0462     BOOST_CHECK_NE(shell1.portal(OuterCylinder), shell2.portal(InnerCylinder));
0463 
0464     CylinderStackPortalShell stack{
0465         gctx, {&shell1, &shell2}, AxisDirection::AxisR};
0466     BOOST_CHECK_EQUAL(stack.size(), 4);
0467 
0468     // Internal cylinder portals have been fused
0469     BOOST_CHECK_EQUAL(shell1.portal(OuterCylinder),
0470                       shell2.portal(InnerCylinder));
0471 
0472     const auto nDisc = stack.portal(NegativeDisc);
0473     const auto pDisc = stack.portal(PositiveDisc);
0474 
0475     BOOST_CHECK_EQUAL(shell1.portal(NegativeDisc), nDisc);
0476     BOOST_CHECK_EQUAL(shell2.portal(NegativeDisc), nDisc);
0477     BOOST_CHECK_EQUAL(shell1.portal(PositiveDisc), pDisc);
0478     BOOST_CHECK_EQUAL(shell2.portal(PositiveDisc), pDisc);
0479     BOOST_CHECK_EQUAL(stack.portal(InnerCylinder),
0480                       shell1.portal(InnerCylinder));
0481     BOOST_CHECK_EQUAL(stack.portal(OuterCylinder),
0482                       shell2.portal(OuterCylinder));
0483     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0484     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0485 
0486     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0487     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0488 
0489     shell1 = SingleCylinderPortalShell{vol1};
0490     shell2 = SingleCylinderPortalShell{vol2};
0491 
0492     BOOST_CHECK_THROW(CylinderStackPortalShell(gctx, {&shell1, &shell2},
0493                                                AxisDirection::AxisZ),
0494                       SurfaceMergingException);
0495   }
0496 
0497   BOOST_TEST_CONTEXT("rMin==0") {
0498     TrackingVolume vol1(
0499         Transform3::Identity(),
0500         std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm));
0501 
0502     TrackingVolume vol2(
0503         Transform3::Identity(),
0504         std::make_shared<CylinderVolumeBounds>(100_mm, 150_mm, 100_mm));
0505 
0506     SingleCylinderPortalShell shell1{vol1};
0507     SingleCylinderPortalShell shell2{vol2};
0508 
0509     BOOST_CHECK_EQUAL(shell1.portal(InnerCylinder), nullptr);
0510     BOOST_CHECK_NE(shell1.portal(OuterCylinder), shell2.portal(InnerCylinder));
0511 
0512     CylinderStackPortalShell stack{
0513         gctx, {&shell1, &shell2}, AxisDirection::AxisR};
0514     BOOST_CHECK_EQUAL(stack.size(), 4);
0515 
0516     // Internal cylinder portals have been fused
0517     BOOST_CHECK_EQUAL(shell1.portal(OuterCylinder),
0518                       shell2.portal(InnerCylinder));
0519 
0520     const auto nDisc = stack.portal(NegativeDisc);
0521     const auto pDisc = stack.portal(PositiveDisc);
0522     BOOST_CHECK_EQUAL(shell1.portal(NegativeDisc), nDisc);
0523     BOOST_CHECK_EQUAL(shell2.portal(NegativeDisc), nDisc);
0524     BOOST_CHECK_EQUAL(shell1.portal(PositiveDisc), pDisc);
0525     BOOST_CHECK_EQUAL(shell2.portal(PositiveDisc), pDisc);
0526     BOOST_CHECK_EQUAL(stack.portal(InnerCylinder), nullptr);
0527     BOOST_CHECK_EQUAL(stack.portal(OuterCylinder),
0528                       shell2.portal(OuterCylinder));
0529 
0530     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0531     BOOST_CHECK_EQUAL(stack.portal(NegativePhiPlane), nullptr);
0532 
0533     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0534     BOOST_CHECK_EQUAL(stack.portal(PositivePhiPlane), nullptr);
0535 
0536     shell1 = SingleCylinderPortalShell{vol1};
0537     shell2 = SingleCylinderPortalShell{vol2};
0538 
0539     BOOST_CHECK_THROW(CylinderStackPortalShell(gctx, {&shell1, &shell2},
0540                                                AxisDirection::AxisZ),
0541                       std::invalid_argument);
0542   }
0543 }
0544 
0545 BOOST_AUTO_TEST_CASE(NestedStacks) {
0546   //   ^
0547   // r |    +---------------------------------+---------+
0548   //   |    |                                 |         |
0549   //   |    |                                 |         |
0550   //   |    |              vol2               |         |
0551   //   |    |                                 |         |
0552   //   |    |                                 |         |
0553   //   |    +---------------------------------+         |
0554   //   |    |                                 |         |
0555   //   |    |                                 |         |
0556   //   |    |               gap               |  vol3   |
0557   //   |    |                                 |         |
0558   //   |    |                                 |         |
0559   //   |    +---------------------------------+         |
0560   //   |    |                                 |         |
0561   //   |    |                                 |         |
0562   //   |    |              vol1               |         |
0563   //   |    |                                 |         |
0564   //   |    |                                 |         |
0565   //   |    +---------------------------------+---------+
0566   //   |
0567   //   +-------------------------------------------------->
0568   //                                                      z
0569 
0570   Transform3 base = Transform3::Identity();
0571 
0572   TrackingVolume vol1(
0573       base, std::make_shared<CylinderVolumeBounds>(23_mm, 48_mm, 200_mm),
0574       "PixelLayer0");
0575 
0576   TrackingVolume gap(
0577       base, std::make_shared<CylinderVolumeBounds>(48_mm, 250_mm, 200_mm),
0578       "Gap");
0579 
0580   TrackingVolume vol2(
0581       base, std::make_shared<CylinderVolumeBounds>(250_mm, 400_mm, 200_mm),
0582       "PixelLayer3");
0583 
0584   TrackingVolume vol3(
0585       base * Translation3{Vector3::UnitZ() * 300_mm},
0586       std::make_shared<CylinderVolumeBounds>(23_mm, 400_mm, 100_mm),
0587       "PixelEcPos");
0588 
0589   SingleCylinderPortalShell shell1{vol1};
0590   BOOST_CHECK(shell1.isValid());
0591   SingleCylinderPortalShell gapShell{gap};
0592   BOOST_CHECK(gapShell.isValid());
0593   SingleCylinderPortalShell shell2{vol2};
0594   BOOST_CHECK(shell2.isValid());
0595 
0596   CylinderStackPortalShell stack{
0597       gctx, {&shell1, &gapShell, &shell2}, AxisDirection::AxisR};
0598 
0599   BOOST_CHECK(stack.isValid());
0600 
0601   SingleCylinderPortalShell shell3{vol3};
0602   BOOST_CHECK(shell3.isValid());
0603 
0604   CylinderStackPortalShell stack2{
0605       gctx, {&stack, &shell3}, AxisDirection::AxisZ, *logger};
0606   BOOST_CHECK(stack2.isValid());
0607 
0608   using enum CylinderVolumeBounds::Face;
0609 
0610   auto lookup = [](auto& shell, CylinderPortalShell::Face face,
0611                    Vector3 position,
0612                    Vector3 direction) -> const TrackingVolume* {
0613     const auto portal = shell.portal(face);
0614     BOOST_REQUIRE_NE(portal, nullptr);
0615     return portal->resolveVolume(gctx, position, direction).value();
0616   };
0617 
0618   // Interconnection in the r direction
0619 
0620   BOOST_CHECK_EQUAL(lookup(shell1, InnerCylinder, 23_mm * Vector3::UnitX(),
0621                            -Vector3::UnitX()),
0622                     nullptr);
0623   BOOST_CHECK_EQUAL(
0624       lookup(shell1, InnerCylinder, 23_mm * Vector3::UnitX(), Vector3::UnitX()),
0625       &vol1);
0626 
0627   BOOST_CHECK_EQUAL(
0628       lookup(shell1, OuterCylinder, 48_mm * Vector3::UnitX(), Vector3::UnitX()),
0629       &gap);
0630 
0631   BOOST_CHECK_EQUAL(lookup(gapShell, InnerCylinder, 48_mm * Vector3::UnitX(),
0632                            -Vector3::UnitX()),
0633                     &vol1);
0634 
0635   BOOST_CHECK_EQUAL(lookup(gapShell, OuterCylinder, 250_mm * Vector3::UnitX(),
0636                            Vector3::UnitX()),
0637                     &vol2);
0638 
0639   BOOST_CHECK_EQUAL(lookup(shell2, InnerCylinder, 250_mm * Vector3::UnitX(),
0640                            -Vector3::UnitX()),
0641                     &gap);
0642 
0643   BOOST_CHECK_EQUAL(lookup(shell2, OuterCylinder, 400_mm * Vector3::UnitX(),
0644                            Vector3::UnitX()),
0645                     nullptr);
0646 
0647   BOOST_CHECK_EQUAL(lookup(shell2, OuterCylinder, 400_mm * Vector3::UnitX(),
0648                            -Vector3::UnitX()),
0649                     &vol2);
0650 
0651   BOOST_CHECK_EQUAL(lookup(shell2, OuterCylinder, 400_mm * Vector3::UnitX(),
0652                            -Vector3::UnitX()),
0653                     &vol2);
0654 
0655   // Left connection
0656 
0657   BOOST_CHECK_EQUAL(lookup(shell1, NegativeDisc, Vector3(30_mm, 0, -200_mm),
0658                            -Vector3::UnitZ()),
0659                     nullptr);
0660 
0661   BOOST_CHECK_EQUAL(lookup(shell1, NegativeDisc, Vector3(30_mm, 0, -200_mm),
0662                            Vector3::UnitZ()),
0663                     &vol1);
0664 
0665   BOOST_CHECK_EQUAL(lookup(gapShell, NegativeDisc, Vector3(60_mm, 0, -200_mm),
0666                            -Vector3::UnitZ()),
0667                     nullptr);
0668 
0669   BOOST_CHECK_EQUAL(lookup(gapShell, NegativeDisc, Vector3(60_mm, 0, -200_mm),
0670                            Vector3::UnitZ()),
0671                     &gap);
0672 
0673   BOOST_CHECK_EQUAL(lookup(shell2, NegativeDisc, Vector3(300_mm, 0, -200_mm),
0674                            -Vector3::UnitZ()),
0675                     nullptr);
0676 
0677   BOOST_CHECK_EQUAL(lookup(shell2, NegativeDisc, Vector3(300_mm, 0, -200_mm),
0678                            Vector3::UnitZ()),
0679                     &vol2);
0680 
0681   // Right connection
0682 
0683   BOOST_CHECK_EQUAL(lookup(shell1, PositiveDisc, Vector3(30_mm, 0, 200_mm),
0684                            -Vector3::UnitZ()),
0685                     &vol1);
0686 
0687   BOOST_CHECK_EQUAL(
0688       lookup(shell1, PositiveDisc, Vector3(30_mm, 0, 200_mm), Vector3::UnitZ()),
0689       &vol3);
0690 
0691   BOOST_CHECK_EQUAL(lookup(gapShell, PositiveDisc, Vector3(60_mm, 0, 200_mm),
0692                            -Vector3::UnitZ()),
0693                     &gap);
0694 
0695   BOOST_CHECK_EQUAL(lookup(gapShell, PositiveDisc, Vector3(60_mm, 0, 200_mm),
0696                            Vector3::UnitZ()),
0697                     &vol3);
0698 
0699   BOOST_CHECK_EQUAL(lookup(shell2, PositiveDisc, Vector3(300_mm, 0, 200_mm),
0700                            -Vector3::UnitZ()),
0701                     &vol2);
0702 
0703   BOOST_CHECK_EQUAL(lookup(shell2, PositiveDisc, Vector3(300_mm, 0, 200_mm),
0704                            Vector3::UnitZ()),
0705                     &vol3);
0706 
0707   // Right outer connection
0708 
0709   BOOST_CHECK_EQUAL(lookup(shell3, PositiveDisc, Vector3(300_mm, 0, 400_mm),
0710                            -Vector3::UnitZ()),
0711                     &vol3);
0712 
0713   BOOST_CHECK_EQUAL(lookup(shell3, PositiveDisc, Vector3(300_mm, 0, 400_mm),
0714                            Vector3::UnitZ()),
0715                     nullptr);
0716 }
0717 
0718 BOOST_AUTO_TEST_CASE(Fill) {
0719   auto cyl1 = makeVolume(30_mm, 40_mm, 100_mm);
0720   auto cyl2 = makeVolume(0_mm, 50_mm, 110_mm);
0721 
0722   SingleCylinderPortalShell shell{cyl1};
0723 
0724   using enum CylinderVolumeBounds::Face;
0725   BOOST_CHECK_EQUAL(
0726       shell.portal(OuterCylinder)->getLink(Direction::AlongNormal()), nullptr);
0727   BOOST_CHECK_EQUAL(
0728       shell.portal(InnerCylinder)->getLink(Direction::OppositeNormal()),
0729       nullptr);
0730   BOOST_CHECK_EQUAL(
0731       shell.portal(PositiveDisc)->getLink(Direction::AlongNormal()), nullptr);
0732   BOOST_CHECK_EQUAL(
0733       shell.portal(NegativeDisc)->getLink(Direction::OppositeNormal()),
0734       nullptr);
0735 
0736   shell.fill(cyl2);
0737 
0738   BOOST_CHECK_NE(shell.portal(OuterCylinder)->getLink(Direction::AlongNormal()),
0739                  nullptr);
0740   BOOST_CHECK_NE(
0741       shell.portal(InnerCylinder)->getLink(Direction::OppositeNormal()),
0742       nullptr);
0743   BOOST_CHECK_NE(shell.portal(PositiveDisc)->getLink(Direction::AlongNormal()),
0744                  nullptr);
0745   BOOST_CHECK_NE(
0746       shell.portal(NegativeDisc)->getLink(Direction::OppositeNormal()),
0747       nullptr);
0748 }
0749 
0750 BOOST_AUTO_TEST_CASE(RegisterInto) {
0751   using enum CylinderVolumeBounds::Face;
0752   TrackingVolume vol1(
0753       Transform3::Identity(),
0754       std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm));
0755 
0756   SingleCylinderPortalShell shell{vol1};
0757 
0758   BOOST_CHECK_EQUAL(vol1.portals().size(), 0);
0759 
0760   shell.applyToVolume();
0761   BOOST_CHECK_EQUAL(vol1.portals().size(), 3);
0762 }
0763 
0764 BOOST_AUTO_TEST_SUITE_END()  // CylinderStack
0765 BOOST_AUTO_TEST_SUITE_END()
0766 
0767 }  // namespace ActsTests