Back to home page

EIC code displayed by LXR

 
 

    


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

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/data/test_case.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/Blueprint.hpp"
0016 #include "Acts/Geometry/BlueprintNode.hpp"
0017 #include "Acts/Geometry/CylinderContainerBlueprintNode.hpp"
0018 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0019 #include "Acts/Geometry/CylinderVolumeStack.hpp"
0020 #include "Acts/Geometry/GeometryContext.hpp"
0021 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0022 #include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
0023 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0024 #include "Acts/Geometry/TrackingVolume.hpp"
0025 #include "Acts/Material/BinnedSurfaceMaterial.hpp"
0026 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
0027 #include "Acts/Surfaces/RectangleBounds.hpp"
0028 #include "Acts/Tests/CommonHelpers/DetectorElementStub.hpp"
0029 #include "Acts/Utilities/BinningType.hpp"
0030 #include "Acts/Utilities/Logger.hpp"
0031 
0032 #include <fstream>
0033 #include <stdexcept>
0034 #include <vector>
0035 
0036 using namespace Acts::UnitLiterals;
0037 
0038 namespace Acts::Test {
0039 
0040 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::INFO);
0041 
0042 GeometryContext gctx;
0043 
0044 namespace {
0045 
0046 auto nameLookup(const TrackingGeometry& geo) {
0047   return [&](const std::string& name) -> const TrackingVolume& {
0048     const TrackingVolume* volume = nullptr;
0049 
0050     geo.visitVolumes([&](const TrackingVolume* v) {
0051       if (v->volumeName() == name) {
0052         volume = v;
0053       }
0054     });
0055 
0056     if (volume == nullptr) {
0057       throw std::runtime_error("Volume not found: " + name);
0058     }
0059     return *volume;
0060   };
0061 }
0062 
0063 std::size_t countVolumes(const TrackingGeometry& geo) {
0064   std::size_t nVolumes = 0;
0065   geo.visitVolumes([&](const TrackingVolume* /*volume*/) { nVolumes++; });
0066   return nVolumes;
0067 }
0068 
0069 }  // namespace
0070 
0071 BOOST_AUTO_TEST_SUITE(Geometry);
0072 
0073 BOOST_AUTO_TEST_SUITE(BlueprintNodeTest);
0074 
0075 BOOST_AUTO_TEST_CASE(InvalidRoot) {
0076   Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
0077 
0078   Blueprint::Config cfg;
0079   Blueprint root{cfg};
0080   BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error);
0081 
0082   // More than one child is also invalid
0083   auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 100_mm);
0084   root.addChild(
0085       std::make_unique<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0086           Transform3::Identity(), cylBounds, "child1")));
0087   root.addChild(
0088       std::make_unique<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0089           Transform3::Identity(), cylBounds, "child2")));
0090 
0091   BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error);
0092 }
0093 
0094 class DummyNode : public BlueprintNode {
0095  public:
0096   explicit DummyNode(const std::string& name) : m_name(name) {}
0097 
0098   const std::string& name() const override { return m_name; }
0099 
0100   Volume& build(const BlueprintOptions& /*options*/,
0101                 const GeometryContext& /*gctx*/,
0102                 const Acts::Logger& /*logger*/) override {
0103     throw std::logic_error("Not implemented");
0104   }
0105 
0106   PortalShellBase& connect(const BlueprintOptions& /*options*/,
0107                            const GeometryContext& /*gctx*/,
0108                            const Logger& /*logger */) override {
0109     throw std::logic_error("Not implemented");
0110   }
0111 
0112   void finalize(const BlueprintOptions& /*options*/,
0113                 const GeometryContext& /*gctx*/, TrackingVolume& /*parent*/,
0114                 const Logger& /*logger*/) override {
0115     throw std::logic_error("Not implemented");
0116   }
0117 
0118  private:
0119   std::string m_name;
0120 };
0121 
0122 BOOST_AUTO_TEST_CASE(RootCannotBeChild) {
0123   auto node = std::make_shared<DummyNode>("node");
0124   Blueprint::Config cfg;
0125   auto root = std::make_shared<Blueprint>(cfg);
0126 
0127   BOOST_CHECK_THROW(node->addChild(root), std::invalid_argument);
0128 }
0129 
0130 BOOST_AUTO_TEST_CASE(AddChildInvalid) {
0131   auto node = std::make_shared<DummyNode>("node");
0132 
0133   // Add self
0134   BOOST_CHECK_THROW(node->addChild(node), std::invalid_argument);
0135 
0136   // Add nullptr
0137   BOOST_CHECK_THROW(node->addChild(nullptr), std::invalid_argument);
0138 
0139   auto nodeB = std::make_shared<DummyNode>("nodeB");
0140   auto nodeC = std::make_shared<DummyNode>("nodeC");
0141 
0142   node->addChild(nodeB);
0143   nodeB->addChild(nodeC);
0144   BOOST_CHECK_THROW(nodeC->addChild(node), std::invalid_argument);
0145 
0146   // already has parent, can't be added as a child anywhere else
0147   BOOST_CHECK_THROW(node->addChild(nodeC), std::invalid_argument);
0148 }
0149 
0150 BOOST_AUTO_TEST_CASE(Depth) {
0151   auto node1 = std::make_shared<DummyNode>("node1");
0152   auto node2 = std::make_shared<DummyNode>("node2");
0153   auto node3 = std::make_shared<DummyNode>("node3");
0154 
0155   BOOST_CHECK_EQUAL(node1->depth(), 0);
0156   BOOST_CHECK_EQUAL(node2->depth(), 0);
0157   BOOST_CHECK_EQUAL(node3->depth(), 0);
0158 
0159   node2->addChild(node3);
0160   BOOST_CHECK_EQUAL(node2->depth(), 0);
0161   BOOST_CHECK_EQUAL(node3->depth(), 1);
0162 
0163   node1->addChild(node2);
0164   BOOST_CHECK_EQUAL(node1->depth(), 0);
0165   BOOST_CHECK_EQUAL(node2->depth(), 1);
0166   BOOST_CHECK_EQUAL(node3->depth(), 2);
0167 }
0168 
0169 BOOST_AUTO_TEST_CASE(Static) {
0170   Blueprint::Config cfg;
0171   cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0172   cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0173   Blueprint root{cfg};
0174 
0175   double hlZ = 30_mm;
0176   auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0177   auto cyl = std::make_unique<TrackingVolume>(Transform3::Identity(), cylBounds,
0178                                               "child");
0179 
0180   root.addStaticVolume(std::move(cyl));
0181 
0182   BOOST_CHECK_EQUAL(root.children().size(), 1);
0183 
0184   auto tGeometry = root.construct({}, gctx, *logger);
0185 
0186   BOOST_REQUIRE(tGeometry);
0187 
0188   BOOST_CHECK_EQUAL(tGeometry->highestTrackingVolume()->volumes().size(), 1);
0189 
0190   BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 2);
0191 
0192   auto lookup = nameLookup(*tGeometry);
0193   auto actCyl =
0194       dynamic_cast<const CylinderVolumeBounds&>(lookup("child").volumeBounds());
0195   // Size as given
0196   BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0197   BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0198   BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0199 
0200   auto worldCyl =
0201       dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0202   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 9_mm);
0203   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 22_mm);
0204   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
0205                     hlZ + 20_mm);
0206 
0207   BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0208 }
0209 
0210 BOOST_AUTO_TEST_CASE(CylinderContainer) {
0211   Blueprint::Config cfg;
0212   cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0213   cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
0214   auto root = std::make_unique<Blueprint>(cfg);
0215 
0216   auto& cyl = root->addCylinderContainer("Container", AxisDirection::AxisZ);
0217   cyl.setAttachmentStrategy(CylinderVolumeStack::AttachmentStrategy::Gap);
0218 
0219   double z0 = -200_mm;
0220   double hlZ = 30_mm;
0221   auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0222   for (std::size_t i = 0; i < 3; i++) {
0223     auto childCyl = std::make_unique<TrackingVolume>(
0224         Transform3::Identity() *
0225             Translation3{Vector3{0, 0, z0 + i * 2 * hlZ * 1.2}},
0226         cylBounds, "child" + std::to_string(i));
0227     cyl.addStaticVolume(std::move(childCyl));
0228   }
0229 
0230   auto tGeometry = root->construct({}, gctx, *logger);
0231 
0232   // 4 real volumes + 2 gaps
0233   BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 6);
0234 
0235   auto lookup = nameLookup(*tGeometry);
0236   auto worldCyl =
0237       dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0238   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 8_mm);
0239   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 40_mm);
0240   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 122_mm);
0241 
0242   BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0243 
0244   for (std::size_t i = 0; i < 3; i++) {
0245     auto actCyl = dynamic_cast<const CylinderVolumeBounds&>(
0246         lookup("child" + std::to_string(i)).volumeBounds());
0247     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0248     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0249     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0250   }
0251 
0252   for (std::size_t i = 0; i < 2; i++) {
0253     auto gapCyl = dynamic_cast<const CylinderVolumeBounds&>(
0254         lookup("Container::Gap" + std::to_string(i + 1)).volumeBounds());
0255     BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0256     BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0257     BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eHalfLengthZ), 6_mm);
0258   }
0259 }
0260 
0261 BOOST_AUTO_TEST_CASE(Confined) {
0262   Transform3 base{Transform3::Identity()};
0263 
0264   Blueprint::Config cfg;
0265   cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0266   cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
0267   auto root = std::make_unique<Blueprint>(cfg);
0268 
0269   root->addStaticVolume(
0270       base, std::make_shared<CylinderVolumeBounds>(50_mm, 400_mm, 1000_mm),
0271       "PixelWrapper", [&](auto& wrap) {
0272         double rMin = 100_mm;
0273         double rMax = 350_mm;
0274         double hlZ = 100_mm;
0275 
0276         wrap.addStaticVolume(
0277             base * Translation3{Vector3{0, 0, -600_mm}},
0278             std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0279             "PixelNeg1");
0280 
0281         wrap.addStaticVolume(
0282             base * Translation3{Vector3{0, 0, -200_mm}},
0283             std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0284             "PixelNeg2");
0285 
0286         wrap.addStaticVolume(
0287             base * Translation3{Vector3{0, 0, 200_mm}},
0288             std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0289             "PixelPos1");
0290 
0291         wrap.addStaticVolume(
0292             base * Translation3{Vector3{0, 0, 600_mm}},
0293             std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0294             "PixelPos2");
0295       });
0296 
0297   auto trackingGeometry = root->construct({}, gctx, *logger);
0298 
0299   // overall dimensions are the wrapper volume + envelope
0300   auto lookup = nameLookup(*trackingGeometry);
0301   auto worldCyl =
0302       dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0303   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 48_mm);
0304   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 420_mm);
0305   BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1020_mm);
0306 
0307   // 4 outer portals and 4 inner
0308   BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0309   BOOST_CHECK_EQUAL(lookup("World").volumes().size(), 1);
0310 
0311   auto wrapperCyl = dynamic_cast<const CylinderVolumeBounds&>(
0312       lookup("PixelWrapper").volumeBounds());
0313   BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMinR), 50_mm);
0314   BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMaxR), 400_mm);
0315   BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eHalfLengthZ),
0316                     1000_mm);
0317   BOOST_CHECK_EQUAL(lookup("PixelWrapper").portals().size(), 4 + 4 * 4);
0318   BOOST_CHECK_EQUAL(lookup("PixelWrapper").volumes().size(), 4);
0319 
0320   for (const auto& name :
0321        {"PixelNeg1", "PixelNeg2", "PixelPos1", "PixelPos2"}) {
0322     auto actCyl =
0323         dynamic_cast<const CylinderVolumeBounds&>(lookup(name).volumeBounds());
0324     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 100_mm);
0325     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 350_mm);
0326     BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), 100_mm);
0327     BOOST_CHECK_EQUAL(lookup(name).portals().size(), 4);
0328   }
0329 }
0330 
0331 BOOST_AUTO_TEST_CASE(DiscLayer) {
0332   double yrot = 45_degree;
0333   Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
0334 
0335   std::vector<std::shared_ptr<Surface>> surfaces;
0336   std::vector<std::unique_ptr<DetectorElementBase>> elements;
0337   double r = 300_mm;
0338   std::size_t nSensors = 8;
0339   double thickness = 2.5_mm;
0340   auto recBounds = std::make_shared<RectangleBounds>(40_mm, 60_mm);
0341 
0342   double deltaPhi = 2 * std::numbers::pi / nSensors;
0343   for (std::size_t i = 0; i < nSensors; i++) {
0344     // Create a fan of sensors
0345 
0346     Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} *
0347                      Translation3(Vector3::UnitX() * r);
0348 
0349     if (i % 2 == 0) {
0350       trf = trf * Translation3{Vector3::UnitZ() * 5_mm};
0351     }
0352 
0353     auto& element = elements.emplace_back(
0354         std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0355 
0356     element->surface().assignDetectorElement(*element);
0357 
0358     surfaces.push_back(element->surface().getSharedPtr());
0359   }
0360 
0361   Blueprint root{{.envelope = ExtentEnvelope{{
0362                       .z = {2_mm, 2_mm},
0363                       .r = {3_mm, 5_mm},
0364                   }}}};
0365 
0366   root.addLayer("Layer0", [&](auto& layer) {
0367     layer.setSurfaces(surfaces)
0368         .setLayerType(LayerBlueprintNode::LayerType::Disc)
0369         .setEnvelope(ExtentEnvelope{{
0370             .z = {0.1_mm, 0.1_mm},
0371             .r = {1_mm, 1_mm},
0372         }})
0373         .setTransform(base);
0374   });
0375 
0376   auto trackingGeometry = root.construct({}, gctx, *logger);
0377 
0378   std::size_t nSurfaces = 0;
0379 
0380   trackingGeometry->visitSurfaces([&](const Surface* surface) {
0381     if (surface->associatedDetectorElement() != nullptr) {
0382       nSurfaces++;
0383     }
0384   });
0385 
0386   BOOST_CHECK_EQUAL(nSurfaces, surfaces.size());
0387   BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0388   auto lookup = nameLookup(*trackingGeometry);
0389   auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
0390       lookup("Layer0").volumeBounds());
0391   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 258.9999999_mm,
0392                     1e-6);
0393   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 346.25353003_mm,
0394                     1e-6);
0395   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 3.85_mm,
0396                     1e-6);
0397 }
0398 
0399 BOOST_AUTO_TEST_CASE(CylinderLayer) {
0400   double yrot = 0_degree;
0401   Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
0402 
0403   std::vector<std::shared_ptr<Surface>> surfaces;
0404   std::vector<std::unique_ptr<DetectorElementBase>> elements;
0405 
0406   double r = 300_mm;
0407   std::size_t nStaves = 10;
0408   int nSensorsPerStave = 8;
0409   double thickness = 0;
0410   double hlPhi = 40_mm;
0411   double hlZ = 60_mm;
0412   auto recBounds = std::make_shared<RectangleBounds>(hlPhi, hlZ);
0413 
0414   double deltaPhi = 2 * std::numbers::pi / nStaves;
0415 
0416   for (std::size_t istave = 0; istave < nStaves; istave++) {
0417     for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave;
0418          isensor++) {
0419       double z = isensor * (2 * hlZ + 5_mm);
0420 
0421       Transform3 trf = base * Translation3(Vector3::UnitZ() * z) *
0422                        AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} *
0423                        Translation3(Vector3::UnitX() * r) *
0424                        AngleAxis3{10_degree, Vector3::UnitZ()} *
0425                        AngleAxis3{90_degree, Vector3::UnitY()} *
0426                        AngleAxis3{90_degree, Vector3::UnitZ()};
0427       auto& element = elements.emplace_back(
0428           std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0429       element->surface().assignDetectorElement(*element);
0430       surfaces.push_back(element->surface().getSharedPtr());
0431     }
0432   }
0433 
0434   Blueprint root{{.envelope = ExtentEnvelope{{
0435                       .z = {2_mm, 2_mm},
0436                       .r = {3_mm, 5_mm},
0437                   }}}};
0438 
0439   root.addLayer("Layer0", [&](auto& layer) {
0440     layer.setSurfaces(surfaces)
0441         .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
0442         .setEnvelope(ExtentEnvelope{{
0443             .z = {10_mm, 10_mm},
0444             .r = {20_mm, 10_mm},
0445         }})
0446         .setTransform(base);
0447   });
0448 
0449   auto trackingGeometry = root.construct({}, gctx, *logger);
0450 
0451   std::size_t nSurfaces = 0;
0452 
0453   trackingGeometry->visitSurfaces([&](const Surface* surface) {
0454     if (surface->associatedDetectorElement() != nullptr) {
0455       nSurfaces++;
0456     }
0457   });
0458 
0459   BOOST_CHECK_EQUAL(nSurfaces, surfaces.size());
0460   BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0461   auto lookup = nameLookup(*trackingGeometry);
0462   auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
0463       lookup("Layer0").volumeBounds());
0464   BOOST_CHECK_EQUAL(lookup("Layer0").portals().size(), 4);
0465   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 275.6897761_mm,
0466                     1e-6);
0467   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 319.4633358_mm,
0468                     1e-6);
0469   BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1070_mm,
0470                     1e-6);
0471 }
0472 
0473 BOOST_AUTO_TEST_CASE(Material) {
0474   Blueprint::Config cfg;
0475   cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0476   cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0477   Blueprint root{cfg};
0478 
0479   double hlZ = 30_mm;
0480   auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0481   auto cyl = std::make_unique<TrackingVolume>(Transform3::Identity(), cylBounds,
0482                                               "child");
0483 
0484   using enum AxisDirection;
0485   using enum CylinderVolumeBounds::Face;
0486   using enum AxisBoundaryType;
0487 
0488   root.addMaterial("Material", [&](auto& mat) {
0489     // @TODO: This API is not great
0490     mat.setBinning(std::vector{
0491         std::tuple{NegativeDisc, Experimental::ProtoBinning{AxisR, Bound, 5},
0492                    Experimental::ProtoBinning{AxisPhi, Bound, 10}},
0493         std::tuple{PositiveDisc, Experimental::ProtoBinning{AxisR, Bound, 15},
0494                    Experimental::ProtoBinning{AxisPhi, Bound, 20}},
0495     });
0496 
0497     mat.addStaticVolume(std::move(cyl));
0498   });
0499 
0500   auto trackingGeometry = root.construct({}, gctx, *logger);
0501 
0502   BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0503   auto lookup = nameLookup(*trackingGeometry);
0504   auto& child = lookup("child");
0505 
0506   const auto* negDisc = child.portals().at(0).surface().surfaceMaterial();
0507   const auto* posDisc = child.portals().at(1).surface().surfaceMaterial();
0508   BOOST_CHECK_NE(negDisc, nullptr);
0509   BOOST_CHECK_NE(posDisc, nullptr);
0510 
0511   const auto& negDiscMat =
0512       dynamic_cast<const ProtoGridSurfaceMaterial&>(*negDisc);
0513   const auto& posDiscMat =
0514       dynamic_cast<const ProtoGridSurfaceMaterial&>(*posDisc);
0515 
0516   BOOST_CHECK_EQUAL(negDiscMat.binning().binning.at(0).bins(), 5);
0517   BOOST_CHECK_EQUAL(negDiscMat.binning().binning.at(1).bins(), 10);
0518   BOOST_CHECK_EQUAL(posDiscMat.binning().binning.at(0).bins(), 15);
0519   BOOST_CHECK_EQUAL(posDiscMat.binning().binning.at(1).bins(), 20);
0520 
0521   for (std::size_t i = 2; i < child.portals().size(); i++) {
0522     BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
0523                       nullptr);
0524   }
0525 }
0526 
0527 BOOST_AUTO_TEST_SUITE_END();
0528 
0529 BOOST_AUTO_TEST_SUITE_END();
0530 
0531 }  // namespace Acts::Test