Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-11 07:51:16

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