Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:12:41

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/CompositePortalLink.hpp"
0016 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Geometry/GridPortalLink.hpp"
0019 #include "Acts/Geometry/Portal.hpp"
0020 #include "Acts/Geometry/TrackingVolume.hpp"
0021 #include "Acts/Geometry/TrivialPortalLink.hpp"
0022 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0023 #include "Acts/Surfaces/CylinderSurface.hpp"
0024 #include "Acts/Surfaces/DiscSurface.hpp"
0025 #include "Acts/Surfaces/RadialBounds.hpp"
0026 #include "Acts/Surfaces/Surface.hpp"
0027 #include "Acts/Surfaces/SurfaceMergingException.hpp"
0028 #include "Acts/Utilities/BinningType.hpp"
0029 #include "Acts/Utilities/ThrowAssert.hpp"
0030 
0031 #include <stdexcept>
0032 
0033 using namespace Acts::UnitLiterals;
0034 
0035 namespace Acts::Test {
0036 
0037 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0038 
0039 struct Fixture {
0040   Logging::Level m_level;
0041   Fixture() {
0042     m_level = Acts::Logging::getFailureThreshold();
0043     Acts::Logging::setFailureThreshold(Acts::Logging::FATAL);
0044   }
0045 
0046   ~Fixture() { Acts::Logging::setFailureThreshold(m_level); }
0047 };
0048 
0049 std::shared_ptr<TrackingVolume> makeDummyVolume() {
0050   return std::make_shared<TrackingVolume>(
0051       Transform3::Identity(),
0052       std::make_shared<CylinderVolumeBounds>(30_mm, 40_mm, 100_mm));
0053 }
0054 
0055 GeometryContext gctx;
0056 
0057 BOOST_FIXTURE_TEST_SUITE(Geometry, Fixture)
0058 
0059 BOOST_AUTO_TEST_SUITE(Portals)
0060 BOOST_AUTO_TEST_SUITE(Merging)
0061 
0062 BOOST_AUTO_TEST_CASE(Cylinder) {
0063   auto vol1 = makeDummyVolume();
0064   vol1->setVolumeName("vol1");
0065   auto vol2 = makeDummyVolume();
0066   vol2->setVolumeName("vol2");
0067 
0068   auto cyl1 = Surface::makeShared<CylinderSurface>(
0069       Transform3{Translation3{Vector3::UnitZ() * -100_mm}}, 50_mm, 100_mm);
0070 
0071   auto cyl2 = Surface::makeShared<CylinderSurface>(
0072       Transform3{Translation3{Vector3::UnitZ() * 100_mm}}, 50_mm, 100_mm);
0073 
0074   Portal portal1{Direction::AlongNormal(),
0075                  std::make_unique<TrivialPortalLink>(cyl1, *vol1)};
0076   BOOST_CHECK(portal1.isValid());
0077 
0078   BOOST_CHECK_EQUAL(
0079       portal1
0080           .resolveVolume(gctx, Vector3{50_mm, 0_mm, -100_mm}, Vector3::UnitX())
0081           .value(),
0082       vol1.get());
0083 
0084   BOOST_CHECK_EQUAL(
0085       portal1
0086           .resolveVolume(gctx, Vector3{50_mm, 0_mm, -100_mm}, -Vector3::UnitX())
0087           .value(),
0088       nullptr);
0089 
0090   Portal portal2{Direction::AlongNormal(), cyl2, *vol2};
0091   BOOST_CHECK(portal2.isValid());
0092 
0093   BOOST_CHECK_EQUAL(
0094       portal2
0095           .resolveVolume(gctx, Vector3{50_mm, 0_mm, 100_mm}, -Vector3::UnitX())
0096           .value(),
0097       nullptr);
0098 
0099   BOOST_CHECK_EQUAL(
0100       portal2
0101           .resolveVolume(gctx, Vector3{50_mm, 0_mm, 100_mm}, Vector3::UnitX())
0102           .value(),
0103       vol2.get());
0104 
0105   Portal portal3{gctx, std::make_unique<TrivialPortalLink>(cyl2, *vol2),
0106                  nullptr};
0107   BOOST_CHECK(portal3.isValid());
0108 
0109   BOOST_CHECK_NE(portal3.getLink(Direction::AlongNormal()), nullptr);
0110   BOOST_CHECK_EQUAL(portal3.getLink(Direction::OppositeNormal()), nullptr);
0111 
0112   Portal portal4{gctx, nullptr,
0113                  std::make_unique<TrivialPortalLink>(cyl2, *vol2)};
0114   BOOST_CHECK(portal4.isValid());
0115 
0116   BOOST_CHECK_EQUAL(portal4.getLink(Direction::AlongNormal()), nullptr);
0117   BOOST_CHECK_NE(portal4.getLink(Direction::OppositeNormal()), nullptr);
0118 
0119   // Not mergeable because 1 has portal along but 4 has portal oppsite
0120   //         ^
0121   //         |
0122   //  portal1|              portal2
0123   // +-------+-------+  +  +---------------+
0124   // |               |     |               |
0125   // +---------------+     +-------+-------+
0126   //                               |
0127   //                               |
0128   //                               v
0129   BOOST_CHECK_THROW(
0130       Portal::merge(gctx, portal1, portal4, AxisDirection::AxisZ, *logger),
0131       PortalMergingException);
0132 
0133   // This call leaves both valid because the exception is thrown before the
0134   // pointers are moved
0135   BOOST_CHECK(portal1.isValid());
0136   BOOST_CHECK(portal2.isValid());
0137 
0138   BOOST_CHECK_EQUAL(
0139       portal2.resolveVolume(gctx, Vector3{50_mm, 0_mm, 50_mm}, Vector3::UnitX())
0140           .value(),
0141       vol2.get());
0142 
0143   BOOST_CHECK_EQUAL(
0144       portal2
0145           .resolveVolume(gctx, Vector3{50_mm, 0_mm, 50_mm}, -Vector3::UnitX())
0146           .value(),
0147       nullptr);
0148 
0149   // Cannot merge in AxisRPhi
0150   BOOST_CHECK_THROW(
0151       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisRPhi, *logger),
0152       SurfaceMergingException);
0153 
0154   // The call above leaves both portals invalid because the exception is thrown
0155   // after the pointers are moved (during durface merging)
0156   BOOST_CHECK(!portal1.isValid());
0157   BOOST_CHECK(!portal2.isValid());
0158 
0159   //         ^                     ^
0160   //         |                     |
0161   //  portal1|              portal2|
0162   // +-------+-------+  +  +-------+-------+
0163   // |               |     |               |
0164   // +---------------+     +---------------+
0165 
0166   // Reset portals to valid to continue
0167   portal1 = Portal{gctx, {.alongNormal = {cyl1, *vol1}}};
0168   portal2 = Portal{gctx, {.alongNormal = {cyl2, *vol2}}};
0169 
0170   Portal merged12 =
0171       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisZ, *logger);
0172   BOOST_CHECK_NE(merged12.getLink(Direction::AlongNormal()), nullptr);
0173   BOOST_CHECK_EQUAL(merged12.getLink(Direction::OppositeNormal()), nullptr);
0174 
0175   auto composite12 = dynamic_cast<const CompositePortalLink*>(
0176       merged12.getLink(Direction::AlongNormal()));
0177   BOOST_REQUIRE_NE(composite12, nullptr);
0178 
0179   BOOST_CHECK_EQUAL(
0180       merged12
0181           .resolveVolume(gctx, Vector3{50_mm, 0_mm, -50_mm}, Vector3::UnitX())
0182           .value(),
0183       vol1.get());
0184 
0185   BOOST_CHECK_EQUAL(
0186       merged12
0187           .resolveVolume(gctx, Vector3{50_mm, 0_mm, 50_mm}, Vector3::UnitX())
0188           .value(),
0189       vol2.get());
0190 
0191   BOOST_CHECK_EQUAL(
0192       merged12
0193           .resolveVolume(gctx, Vector3{50_mm, 0_mm, -50_mm}, -Vector3::UnitX())
0194           .value(),
0195       nullptr);
0196 
0197   BOOST_CHECK_EQUAL(
0198       merged12
0199           .resolveVolume(gctx, Vector3{50_mm, 0_mm, 50_mm}, -Vector3::UnitX())
0200           .value(),
0201       nullptr);
0202 
0203   portal1 = Portal{gctx, {.alongNormal = {cyl1, *vol1}}};
0204 
0205   // Can't merge with self
0206   BOOST_CHECK_THROW(
0207       Portal::merge(gctx, portal1, portal1, AxisDirection::AxisZ, *logger),
0208       PortalMergingException);
0209 
0210   // Can't merge because the surfaces are the same
0211   portal1 = Portal{gctx, {.alongNormal = {cyl1, *vol1}}};
0212   portal2 = Portal{gctx, {.alongNormal = {cyl1, *vol2}}};
0213   BOOST_CHECK_THROW(
0214       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisZ, *logger),
0215       AssertionFailureException);
0216 
0217   // Can't merge because surface has material
0218   auto material =
0219       std::make_shared<HomogeneousSurfaceMaterial>(MaterialSlab{});  // vacuum
0220   cyl2->assignSurfaceMaterial(material);
0221   portal1 = Portal{gctx, {.alongNormal = {cyl1, *vol1}}};
0222   portal2 = Portal{gctx, {.alongNormal = {cyl2, *vol2}}};
0223   BOOST_CHECK_THROW(
0224       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisZ, *logger),
0225       PortalMergingException);
0226 }
0227 
0228 BOOST_AUTO_TEST_CASE(Disc) {
0229   auto vol1 = makeDummyVolume();
0230   vol1->setVolumeName("vol1");
0231   auto vol2 = makeDummyVolume();
0232   vol2->setVolumeName("vol2");
0233   auto vol3 = makeDummyVolume();
0234   vol3->setVolumeName("vol3");
0235   auto vol4 = makeDummyVolume();
0236   vol4->setVolumeName("vol4");
0237 
0238   auto disc1 = Surface::makeShared<DiscSurface>(
0239       Transform3::Identity(), std::make_shared<RadialBounds>(50_mm, 100_mm));
0240 
0241   auto disc2 = Surface::makeShared<DiscSurface>(
0242       Transform3::Identity(), std::make_shared<RadialBounds>(100_mm, 150_mm));
0243 
0244   Portal portal1{
0245       gctx, {.alongNormal = {disc1, *vol1}, .oppositeNormal = {disc1, *vol2}}};
0246 
0247   Portal portal2{
0248       gctx, {.alongNormal = {disc2, *vol3}, .oppositeNormal = {disc2, *vol4}}};
0249 
0250   BOOST_CHECK(portal1.isValid());
0251   BOOST_CHECK(portal2.isValid());
0252 
0253   BOOST_CHECK_EQUAL(
0254       portal1.resolveVolume(gctx, Vector3{55_mm, 0_mm, 0_mm}, Vector3::UnitZ())
0255           .value(),
0256       vol1.get());
0257   BOOST_CHECK_EQUAL(
0258       portal1.resolveVolume(gctx, Vector3{55_mm, 0_mm, 0_mm}, -Vector3::UnitZ())
0259           .value(),
0260       vol2.get());
0261 
0262   BOOST_CHECK_EQUAL(
0263       portal2.resolveVolume(gctx, Vector3{105_mm, 0_mm, 0_mm}, Vector3::UnitZ())
0264           .value(),
0265       vol3.get());
0266   BOOST_CHECK_EQUAL(
0267       portal2
0268           .resolveVolume(gctx, Vector3{105_mm, 0_mm, 0_mm}, -Vector3::UnitZ())
0269           .value(),
0270       vol4.get());
0271 
0272   BOOST_CHECK_THROW(
0273       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisZ, *logger),
0274       AssertionFailureException);
0275 
0276   BOOST_CHECK(portal1.isValid());
0277   BOOST_CHECK(portal2.isValid());
0278 
0279   BOOST_CHECK_THROW(
0280       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisPhi, *logger),
0281       SurfaceMergingException);
0282 
0283   // Portals not valid anymore because they were moved before the exception was
0284   // thrown
0285   BOOST_CHECK(!portal1.isValid());
0286   BOOST_CHECK(!portal2.isValid());
0287 
0288   // recreate them
0289   portal1 = Portal{
0290       gctx, {.alongNormal = {disc1, *vol1}, .oppositeNormal = {disc1, *vol2}}};
0291 
0292   portal2 = Portal{
0293       gctx, {.alongNormal = {disc2, *vol3}, .oppositeNormal = {disc2, *vol4}}};
0294 
0295   //         ^                     ^
0296   //         |                     |
0297   //  portal1|              portal2|
0298   // +-------+-------+     +-------+-------+
0299   // |               |  +  |               |
0300   // +-------+-------+     +-------+-------+
0301   //         |                     |
0302   //         |                     |
0303   //         v                     v
0304   Portal merged12 =
0305       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisR, *logger);
0306 
0307   BOOST_CHECK_EQUAL(
0308       merged12.resolveVolume(gctx, Vector3{55_mm, 0_mm, 0_mm}, Vector3::UnitZ())
0309           .value(),
0310       vol1.get());
0311   BOOST_CHECK_EQUAL(
0312       merged12
0313           .resolveVolume(gctx, Vector3{55_mm, 0_mm, 0_mm}, -Vector3::UnitZ())
0314           .value(),
0315       vol2.get());
0316 
0317   BOOST_CHECK_EQUAL(
0318       merged12
0319           .resolveVolume(gctx, Vector3{105_mm, 0_mm, 0_mm}, Vector3::UnitZ())
0320           .value(),
0321       vol3.get());
0322   BOOST_CHECK_EQUAL(
0323       merged12
0324           .resolveVolume(gctx, Vector3{105_mm, 0_mm, 0_mm}, -Vector3::UnitZ())
0325           .value(),
0326       vol4.get());
0327 
0328   // Can't merge because surface has material
0329   auto material =
0330       std::make_shared<HomogeneousSurfaceMaterial>(MaterialSlab{});  // vacuum
0331   disc2->assignSurfaceMaterial(material);
0332   portal1 = Portal{
0333       gctx, {.alongNormal = {disc1, *vol1}, .oppositeNormal = {disc1, *vol2}}};
0334   portal2 = Portal{
0335       gctx, {.alongNormal = {disc2, *vol3}, .oppositeNormal = {disc2, *vol4}}};
0336   BOOST_CHECK_THROW(
0337       Portal::merge(gctx, portal1, portal2, AxisDirection::AxisR, *logger),
0338       PortalMergingException);
0339 }
0340 
0341 BOOST_AUTO_TEST_SUITE_END()  // Merging
0342 
0343 BOOST_AUTO_TEST_SUITE(Fusing)
0344 
0345 BOOST_AUTO_TEST_CASE(Separated) {
0346   auto vol1 = makeDummyVolume();
0347   vol1->setVolumeName("vol1");
0348   auto vol2 = makeDummyVolume();
0349   vol2->setVolumeName("vol2");
0350 
0351   auto cyl1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0352                                                    50_mm, 100_mm);
0353 
0354   auto cyl2 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0355                                                    60_mm, 100_mm);
0356 
0357   Portal portal1{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0358 
0359   Portal portal2{gctx, {.alongNormal = {cyl2, *vol2}}};
0360 
0361   BOOST_CHECK(portal1.isValid());
0362   BOOST_CHECK(portal2.isValid());
0363 
0364   // Surfaces have a 10mm gap in r
0365   BOOST_CHECK_THROW(Portal::fuse(gctx, portal1, portal2, *logger),
0366                     PortalFusingException);
0367 
0368   BOOST_CHECK(portal1.isValid());
0369   BOOST_CHECK(portal2.isValid());
0370 
0371   // Same way can't set cyl2 as other link
0372   BOOST_CHECK_THROW(
0373       portal1.setLink(gctx, Direction::AlongNormal(), cyl2, *vol2),
0374       PortalFusingException);
0375   BOOST_CHECK_EQUAL(portal1.getLink(Direction::AlongNormal()), nullptr);
0376 
0377   Portal portal1b{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0378   BOOST_CHECK(portal1b.isValid());
0379 
0380   //    portal1     portal1b
0381   //      +---+        +---+
0382   //      |   |        |   |
0383   //      |   |        |   |
0384   // <----+   | + <----+   |
0385   //      |   |        |   |
0386   //      |   |        |   |
0387   //      +---+        +---+
0388   BOOST_CHECK_THROW(Portal::fuse(gctx, portal1, portal1b, *logger),
0389                     PortalFusingException);
0390   BOOST_CHECK(portal1.isValid());
0391   BOOST_CHECK(portal1b.isValid());
0392 
0393   auto disc1 = Surface::makeShared<DiscSurface>(
0394       Transform3::Identity(), std::make_shared<RadialBounds>(50_mm, 100_mm));
0395 
0396   auto disc2 = Surface::makeShared<DiscSurface>(
0397       Transform3{Translation3{Vector3{0, 0, 5_mm}}},
0398       std::make_shared<RadialBounds>(50_mm, 100_mm));
0399 
0400   // portal2       portal2b
0401   //   +---+          +---+
0402   //   |   |          |   |
0403   //   |   |          |   |
0404   //   |   +---->  +  |   +---->
0405   //   |   |          |   |
0406   //   |   |          |   |
0407   //   +---+          +---+
0408   Portal portal2b{gctx, {.alongNormal = {disc2, *vol2}}};
0409 
0410   BOOST_CHECK_THROW(Portal::fuse(gctx, portal2, portal2b, *logger),
0411                     PortalFusingException);
0412   BOOST_CHECK(portal1.isValid());
0413   BOOST_CHECK(portal2.isValid());
0414 
0415   //    portal2     portal2c
0416   //      +---+        +---+
0417   //      |   |        |   |
0418   //      |   |        |   |
0419   // <----+   | + <----+   +---->
0420   //      |   |        |   |
0421   //      |   |        |   |
0422   //      +---+        +---+
0423   Portal portal2c{
0424       gctx, {.alongNormal = {disc2, *vol1}, .oppositeNormal = {disc2, *vol2}}};
0425   BOOST_CHECK(portal2c.isValid());
0426 
0427   BOOST_CHECK_THROW(Portal::fuse(gctx, portal2, portal2c, *logger),
0428                     PortalFusingException);
0429   BOOST_CHECK(portal2.isValid());
0430   BOOST_CHECK(portal2c.isValid());
0431 }
0432 
0433 BOOST_AUTO_TEST_CASE(Success) {
0434   auto vol1 = makeDummyVolume();
0435   vol1->setVolumeName("vol1");
0436   auto vol2 = makeDummyVolume();
0437   vol2->setVolumeName("vol2");
0438 
0439   auto cyl1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0440                                                    50_mm, 100_mm);
0441 
0442   auto cyl2 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0443                                                    50_mm, 100_mm);
0444 
0445   BOOST_CHECK(*cyl1 == *cyl2);
0446 
0447   //    portal1   portal2
0448   //      +---+   +---+
0449   //      |   |   |   |
0450   //      |   |   |   |
0451   // <----+   | + |   +---->
0452   //      |   |   |   |
0453   //      |   |   |   |
0454   //      +---+   +---+
0455   Portal portal1{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0456   BOOST_CHECK_EQUAL(&portal1.getLink(Direction::OppositeNormal())->surface(),
0457                     cyl1.get());
0458 
0459   Portal portal2{gctx, {.alongNormal = {cyl2, *vol2}}};
0460 
0461   BOOST_CHECK(portal1.isValid());
0462   BOOST_CHECK(portal2.isValid());
0463 
0464   Portal portal3 = Portal::fuse(gctx, portal1, portal2, *logger);
0465   // Input portals get invalidated by the fuse
0466   BOOST_CHECK(!portal1.isValid());
0467   BOOST_CHECK(!portal2.isValid());
0468   BOOST_CHECK(portal3.isValid());
0469 
0470   BOOST_CHECK_EQUAL(portal3.surface().surfaceMaterial(), nullptr);
0471 
0472   // Portal surface is set to the one from "along", because it gets set first
0473   BOOST_CHECK_EQUAL(&portal3.surface(), cyl2.get());
0474   // "Opposite" gets the already-set surface set as well
0475   BOOST_CHECK_EQUAL(&portal3.getLink(Direction::OppositeNormal())->surface(),
0476                     cyl2.get());
0477 }
0478 
0479 BOOST_AUTO_TEST_CASE(Material) {
0480   auto vol1 = makeDummyVolume();
0481   auto vol2 = makeDummyVolume();
0482 
0483   auto cyl1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0484                                                    50_mm, 100_mm);
0485 
0486   auto cyl2 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0487                                                    50_mm, 100_mm);
0488 
0489   //    portal1   portal2
0490   //      +---+   +---+
0491   //      |   |   |   |
0492   //      |   |   |   |
0493   // <----+   | + |   +---->
0494   //      |   |   |   |
0495   //      |   |   |   |
0496   //      +---+   +---+
0497   Portal portal1{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0498   Portal portal2{gctx, {.alongNormal = {cyl2, *vol2}}};
0499 
0500   auto material =
0501       std::make_shared<HomogeneousSurfaceMaterial>(MaterialSlab{});  // vacuum
0502 
0503   cyl1->assignSurfaceMaterial(material);
0504 
0505   Portal portal12 = Portal::fuse(gctx, portal1, portal2, *logger);
0506 
0507   // cyl1 had material, so this surface needs to be retained
0508   BOOST_CHECK_EQUAL(&portal12.surface(), cyl1.get());
0509   BOOST_CHECK_EQUAL(portal12.surface().surfaceMaterial(), material.get());
0510 
0511   // Reset portals
0512   portal1 = Portal{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0513   portal2 = Portal{gctx, {.alongNormal = {cyl2, *vol2}}};
0514   cyl2->assignSurfaceMaterial(material);
0515 
0516   // Both have material, this should fail
0517   BOOST_CHECK_THROW(Portal::fuse(gctx, portal1, portal2, *logger),
0518                     PortalFusingException);
0519   // Portals should stay valid
0520   BOOST_CHECK(portal1.isValid());
0521   BOOST_CHECK(portal2.isValid());
0522 
0523   cyl1->assignSurfaceMaterial(nullptr);
0524 
0525   portal12 = Portal::fuse(gctx, portal1, portal2, *logger);
0526 
0527   // cyl2 had material, so this surface needs to be retained
0528   BOOST_CHECK_EQUAL(&portal12.surface(), cyl2.get());
0529   BOOST_CHECK_EQUAL(portal12.surface().surfaceMaterial(), material.get());
0530 }
0531 
0532 BOOST_AUTO_TEST_CASE(GridCreationOnFuse) {
0533   Transform3 base = Transform3::Identity();
0534 
0535   auto vol1 = std::make_shared<TrackingVolume>(
0536       base, std::make_shared<CylinderVolumeBounds>(30_mm, 40_mm, 100_mm));
0537 
0538   auto vol2 = std::make_shared<TrackingVolume>(
0539       base, std::make_shared<CylinderVolumeBounds>(30_mm, 40_mm, 100_mm));
0540 
0541   auto vol3 = std::make_shared<TrackingVolume>(
0542       base, std::make_shared<CylinderVolumeBounds>(40_mm, 50_mm, 100_mm));
0543 
0544   auto vol4 = std::make_shared<TrackingVolume>(
0545       base, std::make_shared<CylinderVolumeBounds>(40_mm, 50_mm, 100_mm));
0546 
0547   auto disc1 =
0548       Surface::makeShared<DiscSurface>(Transform3::Identity(), 30_mm, 60_mm);
0549 
0550   auto disc2 =
0551       Surface::makeShared<DiscSurface>(Transform3::Identity(), 60_mm, 90_mm);
0552 
0553   auto disc3 =
0554       Surface::makeShared<DiscSurface>(Transform3::Identity(), 90_mm, 120_mm);
0555 
0556   auto trivial1 = std::make_unique<TrivialPortalLink>(disc1, *vol1);
0557   BOOST_REQUIRE(trivial1);
0558   auto trivial2 = std::make_unique<TrivialPortalLink>(disc2, *vol2);
0559   BOOST_REQUIRE(trivial2);
0560   auto trivial3 = std::make_unique<TrivialPortalLink>(disc3, *vol3);
0561   BOOST_REQUIRE(trivial3);
0562 
0563   std::vector<std::unique_ptr<PortalLinkBase>> links;
0564   links.push_back(std::move(trivial1));
0565   links.push_back(std::move(trivial2));
0566   links.push_back(std::move(trivial3));
0567 
0568   auto composite = std::make_unique<CompositePortalLink>(std::move(links),
0569                                                          AxisDirection::AxisR);
0570 
0571   auto discOpposite =
0572       Surface::makeShared<DiscSurface>(Transform3::Identity(), 30_mm, 120_mm);
0573 
0574   auto trivialOpposite =
0575       std::make_unique<TrivialPortalLink>(discOpposite, *vol4);
0576 
0577   Portal aPortal{gctx, std::move(composite), nullptr};
0578   Portal bPortal{gctx, nullptr, std::move(trivialOpposite)};
0579 
0580   Portal fused = Portal::fuse(gctx, aPortal, bPortal, *logger);
0581 
0582   BOOST_CHECK_NE(dynamic_cast<const TrivialPortalLink*>(
0583                      fused.getLink(Direction::OppositeNormal())),
0584                  nullptr);
0585 
0586   const auto* grid = dynamic_cast<const GridPortalLink*>(
0587       fused.getLink(Direction::AlongNormal()));
0588   BOOST_REQUIRE_NE(grid, nullptr);
0589 
0590   BOOST_CHECK_EQUAL(grid->grid().axes().front()->getNBins(), 3);
0591 }
0592 
0593 BOOST_AUTO_TEST_SUITE_END()  // Fusing
0594 
0595 BOOST_AUTO_TEST_CASE(Construction) {
0596   auto vol1 = makeDummyVolume();
0597 
0598   // Displaced surfaces fail
0599   auto disc1 = Surface::makeShared<DiscSurface>(
0600       Transform3::Identity(), std::make_shared<RadialBounds>(50_mm, 100_mm));
0601 
0602   auto disc2 = Surface::makeShared<DiscSurface>(
0603       Transform3{Translation3{Vector3{0, 0, 5_mm}}},
0604       std::make_shared<RadialBounds>(50_mm, 100_mm));
0605 
0606   BOOST_CHECK_THROW(std::make_unique<Portal>(
0607                         gctx, std::make_unique<TrivialPortalLink>(disc1, *vol1),
0608                         std::make_unique<TrivialPortalLink>(disc2, *vol1)),
0609                     PortalFusingException);
0610 
0611   BOOST_CHECK_THROW((Portal{gctx, nullptr, nullptr}), std::invalid_argument);
0612   BOOST_CHECK_THROW(Portal(gctx, {}), std::invalid_argument);
0613 }
0614 
0615 BOOST_AUTO_TEST_CASE(InvalidConstruction) {
0616   BOOST_CHECK_THROW(Portal(Direction::AlongNormal(), nullptr),
0617                     std::invalid_argument);
0618 
0619   auto vol1 = makeDummyVolume();
0620 
0621   BOOST_CHECK_THROW(Portal(Direction::AlongNormal(), nullptr, *vol1),
0622                     std::invalid_argument);
0623 
0624   auto disc1 = Surface::makeShared<DiscSurface>(
0625       Transform3::Identity(), std::make_shared<RadialBounds>(50_mm, 100_mm));
0626   Portal portal(Direction::AlongNormal(), disc1, *vol1);
0627 
0628   BOOST_CHECK_THROW(portal.setLink(gctx, Direction::AlongNormal(), nullptr),
0629                     std::invalid_argument);
0630 }
0631 
0632 BOOST_AUTO_TEST_CASE(PortalFill) {
0633   auto vol1 = makeDummyVolume();
0634   auto vol2 = makeDummyVolume();
0635 
0636   auto cyl1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0637                                                    50_mm, 100_mm);
0638 
0639   Portal portal1{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0640   Portal portal2{gctx, {.alongNormal = {cyl1, *vol2}}};
0641 
0642   // Fuse these to make portal 1 and 2 empty
0643   Portal::fuse(gctx, portal1, portal2, *logger);
0644 
0645   BOOST_CHECK_THROW(portal1.fill(*vol2), std::logic_error);
0646 
0647   portal1 = Portal{gctx, {.oppositeNormal = {cyl1, *vol1}}};
0648   portal2 = Portal{gctx, {.alongNormal = {cyl1, *vol2}}};
0649 
0650   BOOST_CHECK_EQUAL(portal1.getLink(Direction::AlongNormal()), nullptr);
0651   BOOST_CHECK_NE(portal1.getLink(Direction::OppositeNormal()), nullptr);
0652 
0653   portal1.fill(*vol2);
0654   BOOST_CHECK_NE(portal1.getLink(Direction::AlongNormal()), nullptr);
0655   BOOST_CHECK_NE(portal1.getLink(Direction::OppositeNormal()), nullptr);
0656 
0657   BOOST_CHECK_THROW(portal1.fill(*vol2), std::logic_error);
0658 }
0659 
0660 BOOST_AUTO_TEST_SUITE_END()  // Portals
0661 
0662 BOOST_AUTO_TEST_SUITE_END()  // Geometry
0663 
0664 }  // namespace Acts::Test