Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-06-21 07:47:50

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