Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-19 08:35:44

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