File indexing completed on 2026-09-17 08:23:56
0001
0002
0003
0004
0005
0006
0007
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/ContainerBlueprintNode.hpp"
0018 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0019 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0020 #include "Acts/Geometry/GeometryContext.hpp"
0021 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0022 #include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
0023 #include "Acts/Geometry/PadBlueprintNode.hpp"
0024 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0025 #include "Acts/Geometry/TrackingVolume.hpp"
0026 #include "Acts/Geometry/TrapezoidVolumeBounds.hpp"
0027 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0028 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0029 #include "Acts/Material/Material.hpp"
0030 #include "Acts/Material/MaterialSlab.hpp"
0031 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
0032 #include "Acts/Surfaces/RectangleBounds.hpp"
0033 #include "Acts/Utilities/AxisDefinitions.hpp"
0034 #include "Acts/Utilities/AxisSpec.hpp"
0035 #include "Acts/Utilities/Diagnostics.hpp"
0036 #include "Acts/Utilities/Logger.hpp"
0037 #include "Acts/Utilities/ProtoAxis.hpp"
0038 #include "ActsTests/CommonHelpers/DetectorElementStub.hpp"
0039
0040 #include <memory>
0041 #include <stdexcept>
0042 #include <vector>
0043
0044 using namespace Acts;
0045 using namespace Acts::UnitLiterals;
0046 using Acts::Blueprint;
0047 using Acts::BlueprintNode;
0048 using Acts::BlueprintOptions;
0049 using Acts::LayerBlueprintNode;
0050 using Acts::MaterialDesignatorBlueprintNode;
0051 using Acts::PadBlueprintNode;
0052 using Acts::StaticBlueprintNode;
0053
0054 namespace ActsTests {
0055
0056 auto logger = getDefaultLogger("UnitTests", Logging::VERBOSE);
0057
0058 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0059
0060 auto nameLookup(const TrackingGeometry& geo) {
0061 return [&](const std::string& name) -> const TrackingVolume& {
0062 const TrackingVolume* volume = nullptr;
0063
0064 geo.visitVolumes([&](const TrackingVolume* v) {
0065 if (v->volumeName() == name) {
0066 volume = v;
0067 }
0068 });
0069
0070 if (volume == nullptr) {
0071 throw std::runtime_error("Volume not found: " + name);
0072 }
0073 return *volume;
0074 };
0075 }
0076
0077 std::size_t countVolumes(const TrackingGeometry& geo) {
0078 std::size_t nVolumes = 0;
0079 geo.visitVolumes([&](const TrackingVolume* ) { nVolumes++; });
0080 return nVolumes;
0081 }
0082
0083 BOOST_AUTO_TEST_SUITE(GeometrySuite);
0084
0085 BOOST_AUTO_TEST_CASE(InvalidRoot) {
0086 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
0087
0088 Blueprint::Config cfg;
0089 Blueprint root{cfg};
0090 BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error);
0091
0092
0093 auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 100_mm);
0094 root.addChild(
0095 std::make_unique<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0096 Transform3::Identity(), cylBounds, "child1")));
0097 root.addChild(
0098 std::make_unique<StaticBlueprintNode>(std::make_unique<TrackingVolume>(
0099 Transform3::Identity(), cylBounds, "child2")));
0100
0101 BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error);
0102 }
0103
0104 class DummyNode : public BlueprintNode {
0105 public:
0106 explicit DummyNode(const std::string& name) : m_name(name) {}
0107
0108 const std::string& name() const override { return m_name; }
0109
0110 Volume& build(const BlueprintOptions& ,
0111 const GeometryContext& ,
0112 const Logger& ) override {
0113 throw std::logic_error("Not implemented");
0114 }
0115
0116 PortalShellBase& connect(const BlueprintOptions& ,
0117 const GeometryContext& ,
0118 const Logger& ) override {
0119 throw std::logic_error("Not implemented");
0120 }
0121
0122 void finalize(const BlueprintOptions& ,
0123 const GeometryContext& , TrackingVolume& ,
0124 const Logger& ) override {
0125 throw std::logic_error("Not implemented");
0126 }
0127
0128 private:
0129 std::string m_name;
0130 };
0131
0132 BOOST_AUTO_TEST_CASE(RootCannotBeChild) {
0133 auto node = std::make_shared<DummyNode>("node");
0134 Blueprint::Config cfg;
0135 auto root = std::make_shared<Blueprint>(cfg);
0136
0137 BOOST_CHECK_THROW(node->addChild(root), std::invalid_argument);
0138 }
0139
0140 BOOST_AUTO_TEST_CASE(AddChildInvalid) {
0141 auto node = std::make_shared<DummyNode>("node");
0142
0143
0144 BOOST_CHECK_THROW(node->addChild(node), std::invalid_argument);
0145
0146
0147 BOOST_CHECK_THROW(node->addChild(nullptr), std::invalid_argument);
0148
0149 auto nodeB = std::make_shared<DummyNode>("nodeB");
0150 auto nodeC = std::make_shared<DummyNode>("nodeC");
0151
0152 node->addChild(nodeB);
0153 nodeB->addChild(nodeC);
0154 BOOST_CHECK_THROW(nodeC->addChild(node), std::invalid_argument);
0155
0156
0157 BOOST_CHECK_THROW(node->addChild(nodeC), std::invalid_argument);
0158 }
0159
0160 BOOST_AUTO_TEST_CASE(Depth) {
0161 auto node1 = std::make_shared<DummyNode>("node1");
0162 auto node2 = std::make_shared<DummyNode>("node2");
0163 auto node3 = std::make_shared<DummyNode>("node3");
0164
0165 BOOST_CHECK_EQUAL(node1->depth(), 0);
0166 BOOST_CHECK_EQUAL(node2->depth(), 0);
0167 BOOST_CHECK_EQUAL(node3->depth(), 0);
0168
0169 node2->addChild(node3);
0170 BOOST_CHECK_EQUAL(node2->depth(), 0);
0171 BOOST_CHECK_EQUAL(node3->depth(), 1);
0172
0173 node1->addChild(node2);
0174 BOOST_CHECK_EQUAL(node1->depth(), 0);
0175 BOOST_CHECK_EQUAL(node2->depth(), 1);
0176 BOOST_CHECK_EQUAL(node3->depth(), 2);
0177 }
0178
0179 BOOST_AUTO_TEST_CASE(Static) {
0180 Blueprint::Config cfg;
0181 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0182 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0183 Blueprint root{cfg};
0184
0185 double hlZ = 30_mm;
0186 auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0187 auto cyl = std::make_unique<TrackingVolume>(Transform3::Identity(), cylBounds,
0188 "child");
0189
0190 root.addStaticVolume(std::move(cyl));
0191
0192 BOOST_CHECK_EQUAL(root.children().size(), 1);
0193
0194 auto tGeometry = root.construct({}, gctx, *logger);
0195
0196 BOOST_REQUIRE(tGeometry);
0197
0198 BOOST_CHECK(tGeometry->geometryVersion() ==
0199 TrackingGeometry::GeometryVersion::Gen3);
0200
0201 BOOST_CHECK_EQUAL(tGeometry->highestTrackingVolume()->volumes().size(), 1);
0202
0203 BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 2);
0204
0205 auto lookup = nameLookup(*tGeometry);
0206 BOOST_CHECK_EQUAL(lookup("child").volumeBounds().type(),
0207 VolumeBounds::BoundsType::eCylinder);
0208 auto actCyl =
0209 dynamic_cast<const CylinderVolumeBounds&>(lookup("child").volumeBounds());
0210
0211 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0212 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0213 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0214
0215 BOOST_CHECK_EQUAL(lookup("World").volumeBounds().type(),
0216 VolumeBounds::BoundsType::eCylinder);
0217 auto worldCyl =
0218 dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0219 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 9_mm);
0220 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 22_mm);
0221 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
0222 hlZ + 20_mm);
0223
0224 BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0225 }
0226
0227 BOOST_AUTO_TEST_CASE(CylinderContainer) {
0228 Blueprint::Config cfg;
0229 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0230 cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
0231 auto root = std::make_unique<Blueprint>(cfg);
0232
0233 auto& cyl = root->addCylinderContainer("Container", AxisDirection::AxisZ);
0234 cyl.setAttachmentStrategy(VolumeAttachmentStrategy::Gap);
0235
0236 double z0 = -200_mm;
0237 double hlZ = 30_mm;
0238 auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0239 for (std::size_t i = 0; i < 3; i++) {
0240 auto childCyl = std::make_unique<TrackingVolume>(
0241 Transform3::Identity() *
0242 Translation3{Vector3{0, 0, z0 + i * 2 * hlZ * 1.2}},
0243 cylBounds, "child" + std::to_string(i));
0244 cyl.addStaticVolume(std::move(childCyl));
0245 }
0246
0247 auto tGeometry = root->construct({}, gctx, *logger);
0248 BOOST_CHECK(tGeometry->geometryVersion() ==
0249 TrackingGeometry::GeometryVersion::Gen3);
0250
0251
0252 BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 6);
0253
0254 auto lookup = nameLookup(*tGeometry);
0255 BOOST_CHECK_EQUAL(lookup("World").volumeBounds().type(),
0256 VolumeBounds::BoundsType::eCylinder);
0257 auto worldCyl =
0258 dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0259 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 8_mm);
0260 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 40_mm);
0261 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 122_mm);
0262
0263 BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0264
0265 for (std::size_t i = 0; i < 3; i++) {
0266 const auto& vol{lookup("child" + std::to_string(i))};
0267 BOOST_CHECK_EQUAL(vol.volumeBounds().type(),
0268 VolumeBounds::BoundsType::eCylinder);
0269 auto actCyl = dynamic_cast<const CylinderVolumeBounds&>(vol.volumeBounds());
0270 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0271 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0272 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0273 }
0274
0275 for (std::size_t i = 0; i < 2; i++) {
0276 const auto& vol{lookup("Container::Gap" + std::to_string(i + 1))};
0277 BOOST_CHECK_EQUAL(vol.volumeBounds().type(),
0278 VolumeBounds::BoundsType::eCylinder);
0279 auto gapCyl = dynamic_cast<const CylinderVolumeBounds&>(vol.volumeBounds());
0280 BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
0281 BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
0282 BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eHalfLengthZ), 6_mm);
0283 }
0284 }
0285
0286 BOOST_AUTO_TEST_CASE(Confined) {
0287 Transform3 base{Transform3::Identity()};
0288
0289 Blueprint::Config cfg;
0290 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0291 cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
0292 auto root = std::make_unique<Blueprint>(cfg);
0293
0294 root->addStaticVolume(
0295 base, std::make_shared<CylinderVolumeBounds>(50_mm, 400_mm, 1000_mm),
0296 "PixelWrapper", [&](auto& wrap) {
0297 double rMin = 100_mm;
0298 double rMax = 350_mm;
0299 double hlZ = 100_mm;
0300
0301 wrap.addStaticVolume(
0302 base * Translation3{Vector3{0, 0, -600_mm}},
0303 std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0304 "PixelNeg1");
0305
0306 wrap.addStaticVolume(
0307 base * Translation3{Vector3{0, 0, -200_mm}},
0308 std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0309 "PixelNeg2");
0310
0311 wrap.addStaticVolume(
0312 base * Translation3{Vector3{0, 0, 200_mm}},
0313 std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0314 "PixelPos1");
0315
0316 wrap.addStaticVolume(
0317 base * Translation3{Vector3{0, 0, 600_mm}},
0318 std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ),
0319 "PixelPos2");
0320 });
0321
0322 auto trackingGeometry = root->construct({}, gctx, *logger);
0323
0324
0325 auto lookup = nameLookup(*trackingGeometry);
0326
0327 BOOST_CHECK_EQUAL(lookup("World").volumeBounds().type(),
0328 VolumeBounds::BoundsType::eCylinder);
0329 auto worldCyl =
0330 dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0331 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 48_mm);
0332 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 420_mm);
0333 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1020_mm);
0334
0335
0336 BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0337 BOOST_CHECK_EQUAL(lookup("World").volumes().size(), 1);
0338
0339 BOOST_CHECK_EQUAL(lookup("PixelWrapper").volumeBounds().type(),
0340 VolumeBounds::BoundsType::eCylinder);
0341
0342 auto wrapperCyl = dynamic_cast<const CylinderVolumeBounds&>(
0343 lookup("PixelWrapper").volumeBounds());
0344 BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMinR), 50_mm);
0345 BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMaxR), 400_mm);
0346 BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eHalfLengthZ),
0347 1000_mm);
0348 BOOST_CHECK_EQUAL(lookup("PixelWrapper").portals().size(), 4 + 4 * 4);
0349 BOOST_CHECK_EQUAL(lookup("PixelWrapper").volumes().size(), 4);
0350
0351 for (const auto& name :
0352 {"PixelNeg1", "PixelNeg2", "PixelPos1", "PixelPos2"}) {
0353 BOOST_CHECK_EQUAL(lookup(name).volumeBounds().type(),
0354 VolumeBounds::BoundsType::eCylinder);
0355 auto actCyl =
0356 dynamic_cast<const CylinderVolumeBounds&>(lookup(name).volumeBounds());
0357 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 100_mm);
0358 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 350_mm);
0359 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), 100_mm);
0360 BOOST_CHECK_EQUAL(lookup(name).portals().size(), 4);
0361 }
0362 }
0363
0364 BOOST_AUTO_TEST_CASE(ConfinedWithShared) {
0365 Transform3 base{Transform3::Identity()};
0366
0367 constexpr double rMin = 100_mm;
0368 constexpr double rMax = 350_mm;
0369 constexpr double hlZ = 100_mm;
0370
0371 auto sharedBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
0372 Blueprint::Config cfg;
0373 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0374 cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
0375 auto root = std::make_unique<Blueprint>(cfg);
0376
0377 root->addCylinderContainer(
0378 "PixelWrapper", AxisDirection::AxisZ, [&](auto& wrap) {
0379 wrap.addStaticVolume(base * Translation3{Vector3{0, 0, -750_mm}},
0380 sharedBounds, "PixelNeg1");
0381
0382 wrap.addStaticVolume(base * Translation3{Vector3{0, 0, -200_mm}},
0383 sharedBounds, "PixelNeg2");
0384
0385 wrap.addStaticVolume(base * Translation3{Vector3{0, 0, 200_mm}},
0386 sharedBounds, "PixelPos1");
0387
0388 wrap.addStaticVolume(base * Translation3{Vector3{0, 0, 975_mm}},
0389 sharedBounds, "PixelPos2");
0390 });
0391 auto trackingGeometry = root->construct({}, gctx, *logger);
0392
0393 auto lookup = nameLookup(*trackingGeometry);
0394 BOOST_CHECK_EQUAL(lookup("World").volumeBounds().type(),
0395 VolumeBounds::BoundsType::eCylinder);
0396 auto worldCyl =
0397 dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
0398 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 98_mm);
0399 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 370_mm);
0400 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 982.5_mm);
0401
0402 BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8);
0403 BOOST_CHECK_EQUAL(lookup("World").volumes().size(), 4);
0404
0405 constexpr std::array<double, 4> expHalfL{187.5_mm, 237.5_mm, 293.75_mm,
0406 243.75_mm};
0407 const std::array<std::string, 4> volNames{"PixelNeg1", "PixelNeg2",
0408 "PixelPos1", "PixelPos2"};
0409 for (std::size_t v = 0; v < 4; ++v) {
0410 const auto& testMe{lookup(volNames[v])};
0411 BOOST_CHECK_EQUAL(testMe.volumeBounds().type(),
0412 VolumeBounds::BoundsType::eCylinder);
0413 BOOST_CHECK_EQUAL(testMe.volumeBoundsPtr() != sharedBounds, true);
0414
0415 auto actCyl =
0416 dynamic_cast<const CylinderVolumeBounds&>(testMe.volumeBounds());
0417 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 100_mm);
0418 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 350_mm);
0419 BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ),
0420 expHalfL[v]);
0421 BOOST_CHECK_EQUAL(testMe.portals().size(), 4);
0422 if (v + 1 == 4) {
0423 break;
0424 }
0425 const auto& nextVol = lookup(volNames[(v + 1)]);
0426 const Vector3 outside =
0427 testMe.localToGlobalTransform(gctx).translation() +
0428 Vector3{150_mm, 0.,
0429 actCyl.get(CylinderVolumeBounds::eHalfLengthZ) - 0.5_mm};
0430 BOOST_CHECK_EQUAL(nextVol.inside(gctx, outside), false);
0431 const Vector3 inside =
0432 testMe.localToGlobalTransform(gctx).translation() +
0433 Vector3{150_mm, 0.,
0434 actCyl.get(CylinderVolumeBounds::eHalfLengthZ) + 0.5_mm};
0435 BOOST_CHECK_EQUAL(nextVol.inside(gctx, inside), true);
0436 }
0437 }
0438
0439 BOOST_AUTO_TEST_CASE(DiscLayer) {
0440 double yrot = 45_degree;
0441 Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
0442
0443 std::vector<std::shared_ptr<Surface>> surfaces;
0444 std::vector<std::unique_ptr<SurfacePlacementBase>> elements;
0445 double r = 300_mm;
0446 std::size_t nSensors = 8;
0447 double thickness = 2.5_mm;
0448 auto recBounds = std::make_shared<RectangleBounds>(40_mm, 60_mm);
0449
0450 double deltaPhi = 2 * std::numbers::pi / nSensors;
0451 for (std::size_t i = 0; i < nSensors; i++) {
0452
0453
0454 Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} *
0455 Translation3(Vector3::UnitX() * r);
0456
0457 if (i % 2 == 0) {
0458 trf = trf * Translation3{Vector3::UnitZ() * 5_mm};
0459 }
0460
0461 auto& element = elements.emplace_back(
0462 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0463
0464 element->surface().assignSurfacePlacement(*element);
0465
0466 surfaces.push_back(element->surface().getSharedPtr());
0467 }
0468
0469 std::function<void(LayerBlueprintNode&)> withSurfaces =
0470 [&surfaces, &base](LayerBlueprintNode& layer) {
0471 layer.setSurfaces(surfaces)
0472 .setLayerType(LayerBlueprintNode::LayerType::Disc)
0473 .setEnvelope(ExtentEnvelope{{
0474 .z = {0.1_mm, 0.1_mm},
0475 .r = {1_mm, 1_mm},
0476 }})
0477 .setTransform(base);
0478 };
0479
0480 std::function<void(LayerBlueprintNode&)> withProtoLayer =
0481 [&surfaces, &base](LayerBlueprintNode& layer) {
0482 MutableProtoLayer protoLayer{gctx, surfaces, base.inverse()};
0483 layer.setProtoLayer(protoLayer)
0484 .setLayerType(LayerBlueprintNode::LayerType::Disc)
0485 .setEnvelope(ExtentEnvelope{{
0486 .z = {0.1_mm, 0.1_mm},
0487 .r = {1_mm, 1_mm},
0488 }});
0489 };
0490
0491 for (const auto& func : {withSurfaces, withProtoLayer}) {
0492 Blueprint root{{.envelope = ExtentEnvelope{{
0493 .z = {2_mm, 2_mm},
0494 .r = {3_mm, 5_mm},
0495 }}}};
0496
0497 root.addLayer("Layer0", [&](auto& layer) { func(layer); });
0498
0499 auto trackingGeometry = root.construct({}, gctx, *logger);
0500
0501 std::size_t nSurfaces = 0;
0502
0503 trackingGeometry->visitSurfaces([&](const Surface* surface) {
0504 if (surface->isSensitive()) {
0505 nSurfaces++;
0506 }
0507 });
0508
0509 BOOST_CHECK_EQUAL(nSurfaces, surfaces.size());
0510 BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0511 auto lookup = nameLookup(*trackingGeometry);
0512
0513 BOOST_CHECK_EQUAL(lookup("Layer0").volumeBounds().type(),
0514 VolumeBounds::BoundsType::eCylinder);
0515 auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
0516 lookup("Layer0").volumeBounds());
0517 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 258.9999999_mm,
0518 1e-6);
0519 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR),
0520 346.25353003_mm, 1e-6);
0521 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 3.85_mm,
0522 1e-6);
0523 }
0524 }
0525
0526 BOOST_AUTO_TEST_CASE(CylinderLayer) {
0527 double yrot = 0_degree;
0528 Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
0529
0530 std::vector<std::shared_ptr<Surface>> surfaces;
0531 std::vector<std::unique_ptr<SurfacePlacementBase>> elements;
0532
0533 double r = 300_mm;
0534 std::size_t nStaves = 10;
0535 int nSensorsPerStave = 8;
0536 double thickness = 0;
0537 double hlPhi = 40_mm;
0538 double hlZ = 60_mm;
0539 auto recBounds = std::make_shared<RectangleBounds>(hlPhi, hlZ);
0540
0541 double deltaPhi = 2 * std::numbers::pi / nStaves;
0542
0543 for (std::size_t istave = 0; istave < nStaves; istave++) {
0544 for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave;
0545 isensor++) {
0546 double z = isensor * (2 * hlZ + 5_mm);
0547
0548 Transform3 trf = base * Translation3(Vector3::UnitZ() * z) *
0549 AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} *
0550 Translation3(Vector3::UnitX() * r) *
0551 AngleAxis3{10_degree, Vector3::UnitZ()} *
0552 AngleAxis3{90_degree, Vector3::UnitY()} *
0553 AngleAxis3{90_degree, Vector3::UnitZ()};
0554 auto& element = elements.emplace_back(
0555 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0556 element->surface().assignSurfacePlacement(*element);
0557 surfaces.push_back(element->surface().getSharedPtr());
0558 }
0559 }
0560
0561 std::function<void(LayerBlueprintNode&)> withSurfaces =
0562 [&surfaces, &base](LayerBlueprintNode& layer) {
0563 layer.setSurfaces(surfaces)
0564 .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
0565 .setEnvelope(ExtentEnvelope{{
0566 .z = {10_mm, 10_mm},
0567 .r = {20_mm, 10_mm},
0568 }})
0569 .setTransform(base);
0570 };
0571
0572 std::function<void(LayerBlueprintNode&)> withProtoLayer =
0573 [&surfaces, &base](LayerBlueprintNode& layer) {
0574 MutableProtoLayer protoLayer{gctx, surfaces, base.inverse()};
0575 layer.setProtoLayer(protoLayer)
0576 .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
0577 .setEnvelope(ExtentEnvelope{{
0578 .z = {10_mm, 10_mm},
0579 .r = {20_mm, 10_mm},
0580 }});
0581 };
0582
0583 for (const auto& func : {withSurfaces, withProtoLayer}) {
0584 Blueprint root{{.envelope = ExtentEnvelope{{
0585 .z = {2_mm, 2_mm},
0586 .r = {3_mm, 5_mm},
0587 }}}};
0588
0589 root.addLayer("Layer0", [&](auto& layer) { func(layer); });
0590
0591 auto trackingGeometry = root.construct({}, gctx, *logger);
0592
0593 std::size_t nSurfaces = 0;
0594
0595 trackingGeometry->visitSurfaces([&](const Surface* surface) {
0596 if (surface->isSensitive()) {
0597 nSurfaces++;
0598 }
0599 });
0600
0601 BOOST_CHECK_EQUAL(nSurfaces, surfaces.size());
0602 BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0603 auto lookup = nameLookup(*trackingGeometry);
0604
0605 BOOST_CHECK_EQUAL(lookup("Layer0").volumeBounds().type(),
0606 VolumeBounds::BoundsType::eCylinder);
0607 auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
0608 lookup("Layer0").volumeBounds());
0609 BOOST_CHECK_EQUAL(lookup("Layer0").portals().size(), 4);
0610 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 275.6897761_mm,
0611 1e-6);
0612 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 319.4633358_mm,
0613 1e-6);
0614 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1070_mm,
0615 1e-6);
0616 }
0617 }
0618
0619 BOOST_AUTO_TEST_CASE(MaterialTesting) {
0620 Blueprint::Config cfg;
0621 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0622 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0623 Blueprint root{cfg};
0624
0625 double hlZ = 30_mm;
0626 auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0627 auto cyl = std::make_unique<TrackingVolume>(Transform3::Identity(), cylBounds,
0628 "child");
0629
0630 using enum AxisDirection;
0631 using enum CylinderVolumeBounds::Face;
0632 using enum AxisBoundaryType;
0633
0634 root.addMaterial("Material", [&](auto& mat) {
0635 mat.configureFace(NegativeDisc, AxisSpec::DeferredEquidistant(5, AxisR),
0636 AxisSpec::DeferredEquidistant(10, AxisPhi));
0637 mat.configureFace(PositiveDisc, AxisSpec::DeferredEquidistant(15, AxisR),
0638 AxisSpec::DeferredEquidistant(20, AxisPhi));
0639 mat.configureFace(OuterCylinder,
0640 AxisSpec::DeferredEquidistant(25, AxisRPhi),
0641 AxisSpec::DeferredEquidistant(30, AxisZ));
0642
0643 mat.addStaticVolume(std::move(cyl));
0644 });
0645
0646 auto trackingGeometry = root.construct({}, gctx, *logger);
0647
0648 BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0649 auto lookup = nameLookup(*trackingGeometry);
0650 auto& child = lookup("child");
0651
0652
0653 const auto* negDisc = child.portals()
0654 .at(static_cast<std::size_t>(NegativeDisc))
0655 .surface()
0656 .surfaceMaterial();
0657 BOOST_REQUIRE_NE(negDisc, nullptr);
0658 const auto& negDiscMat =
0659 dynamic_cast<const ProtoGridSurfaceMaterial&>(*negDisc);
0660
0661 const auto* posDisc = child.portals()
0662 .at(static_cast<std::size_t>(PositiveDisc))
0663 .surface()
0664 .surfaceMaterial();
0665 BOOST_REQUIRE_NE(posDisc, nullptr);
0666 const auto& posDiscMat =
0667 dynamic_cast<const ProtoGridSurfaceMaterial&>(*posDisc);
0668
0669 BOOST_CHECK_EQUAL(negDiscMat.binning().axisSpec(0).nBins(), 5);
0670 BOOST_CHECK_EQUAL(negDiscMat.binning().axisSpec(1).nBins(), 10);
0671 BOOST_CHECK_EQUAL(posDiscMat.binning().axisSpec(0).nBins(), 15);
0672 BOOST_CHECK_EQUAL(posDiscMat.binning().axisSpec(1).nBins(), 20);
0673
0674
0675 const auto* outerCyl = child.portals()
0676 .at(static_cast<std::size_t>(OuterCylinder))
0677 .surface()
0678 .surfaceMaterial();
0679 BOOST_REQUIRE_NE(outerCyl, nullptr);
0680 const auto& outerCylMat =
0681 dynamic_cast<const ProtoGridSurfaceMaterial&>(*outerCyl);
0682 BOOST_CHECK_EQUAL(outerCylMat.binning().axisSpec(0).nBins(), 25);
0683 BOOST_CHECK_EQUAL(outerCylMat.binning().axisSpec(1).nBins(), 30);
0684
0685
0686 for (std::size_t i = 0; i < child.portals().size(); i++) {
0687 if (i != static_cast<std::size_t>(NegativeDisc) &&
0688 i != static_cast<std::size_t>(PositiveDisc) &&
0689 i != static_cast<std::size_t>(OuterCylinder)) {
0690 BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
0691 nullptr);
0692 }
0693 }
0694 }
0695
0696 BOOST_AUTO_TEST_CASE(MaterialInvalidAxisDirections) {
0697 Blueprint::Config cfg;
0698 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0699 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0700 Blueprint root{cfg};
0701
0702 using enum AxisDirection;
0703 using enum AxisBoundaryType;
0704
0705
0706 BOOST_CHECK_THROW(
0707 root.addMaterial("Material",
0708 [&](auto& mat) {
0709 mat.configureFace(
0710 CylinderVolumeBounds::Face::NegativeDisc,
0711 AxisSpec::DeferredEquidistant(5, AxisZ),
0712 AxisSpec::DeferredEquidistant(10, AxisPhi));
0713 }),
0714 std::invalid_argument);
0715
0716 BOOST_CHECK_THROW(
0717 root.addMaterial("Material",
0718 [&](auto& mat) {
0719 mat.configureFace(
0720 CylinderVolumeBounds::Face::OuterCylinder,
0721 AxisSpec::DeferredEquidistant(5, AxisR),
0722 AxisSpec::DeferredEquidistant(10, AxisR));
0723 }),
0724 std::invalid_argument);
0725
0726
0727 BOOST_CHECK_THROW(
0728 root.addMaterial("Material",
0729 [&](auto& mat) {
0730 mat.configureFace(
0731 CuboidVolumeBounds::Face::NegativeXFace,
0732 AxisSpec::DeferredEquidistant(5, AxisX),
0733 AxisSpec::DeferredEquidistant(10, AxisZ));
0734 }),
0735 std::invalid_argument);
0736
0737 BOOST_CHECK_THROW(
0738 root.addMaterial("Material",
0739 [&](auto& mat) {
0740 mat.configureFace(
0741 CuboidVolumeBounds::Face::PositiveYFace,
0742 AxisSpec::DeferredEquidistant(5, AxisY),
0743 AxisSpec::DeferredEquidistant(10, AxisX));
0744 }),
0745 std::invalid_argument);
0746
0747 BOOST_CHECK_THROW(
0748 root.addMaterial("Material",
0749 [&](auto& mat) {
0750 mat.configureFace(
0751 CuboidVolumeBounds::Face::NegativeZFace,
0752 AxisSpec::DeferredEquidistant(5, AxisZ),
0753 AxisSpec::DeferredEquidistant(10, AxisY));
0754 }),
0755 std::invalid_argument);
0756 }
0757
0758 BOOST_AUTO_TEST_CASE(MaterialAxisValidation) {
0759 Blueprint::Config cfg;
0760 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0761 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0762 Blueprint root{cfg};
0763
0764 using enum AxisDirection;
0765 using enum AxisBoundaryType;
0766 using enum CylinderVolumeBounds::Face;
0767
0768
0769 BOOST_CHECK_THROW(
0770 root.addMaterial("Material",
0771 [&](auto& mat) {
0772 mat.configureFace(
0773 NegativeDisc,
0774 AxisSpec::Equidistant(5, 0., 1., Bound, AxisR),
0775 AxisSpec::DeferredEquidistant(10, AxisPhi));
0776 }),
0777 std::invalid_argument);
0778
0779
0780 BOOST_CHECK_NO_THROW(root.addMaterial("Material", [&](auto& mat) {
0781 mat.configureFace(NegativeDisc, AxisSpec::DeferredEquidistant(5),
0782 AxisSpec::DeferredEquidistant(10));
0783 }));
0784
0785
0786 ACTS_PUSH_IGNORE_DEPRECATED()
0787 BOOST_CHECK_NO_THROW(root.addMaterial("Material", [&](auto& mat) {
0788 mat.configureFace(PositiveDisc, DirectedProtoAxis(AxisR, Bound, 5),
0789 DirectedProtoAxis(AxisPhi, Bound, 0., 1., 10));
0790 }));
0791 ACTS_POP_IGNORE_DEPRECATED()
0792 }
0793
0794 BOOST_AUTO_TEST_CASE(MaterialMixedVolumeTypes) {
0795 Blueprint::Config cfg;
0796 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0797 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0798 Blueprint root{cfg};
0799
0800 using enum AxisDirection;
0801 using enum AxisBoundaryType;
0802
0803
0804 BOOST_CHECK_THROW(
0805 root.addMaterial(
0806 "Material",
0807 [&](auto& mat) {
0808 mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
0809 AxisSpec::DeferredEquidistant(5, AxisR),
0810 AxisSpec::DeferredEquidistant(10, AxisPhi));
0811 mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
0812 AxisSpec::DeferredEquidistant(5, AxisX),
0813 AxisSpec::DeferredEquidistant(10, AxisY));
0814 }),
0815 std::runtime_error);
0816
0817
0818 BOOST_CHECK_THROW(
0819 root.addMaterial(
0820 "Material",
0821 [&](auto& mat) {
0822 mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
0823 AxisSpec::DeferredEquidistant(5, AxisX),
0824 AxisSpec::DeferredEquidistant(10, AxisY));
0825 mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
0826 AxisSpec::DeferredEquidistant(5, AxisR),
0827 AxisSpec::DeferredEquidistant(10, AxisPhi));
0828 }),
0829 std::runtime_error);
0830 }
0831
0832 BOOST_AUTO_TEST_CASE(MaterialCuboid) {
0833 Blueprint::Config cfg;
0834 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0835 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0836 Blueprint root{cfg};
0837
0838 using enum AxisDirection;
0839 using enum AxisBoundaryType;
0840 using enum CuboidVolumeBounds::Face;
0841
0842 double hlX = 30_mm;
0843 double hlY = 40_mm;
0844 double hlZ = 50_mm;
0845 auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(hlX, hlY, hlZ);
0846 auto cuboid = std::make_unique<TrackingVolume>(Transform3::Identity(),
0847 cuboidBounds, "child");
0848
0849 auto mat = std::make_shared<MaterialDesignatorBlueprintNode>("Material");
0850
0851
0852 mat->configureFace(NegativeXFace, AxisSpec::DeferredEquidistant(5, AxisX),
0853 AxisSpec::DeferredEquidistant(10, AxisY));
0854 mat->configureFace(PositiveXFace, AxisSpec::DeferredEquidistant(15, AxisX),
0855 AxisSpec::DeferredEquidistant(20, AxisY));
0856 mat->configureFace(NegativeYFace, AxisSpec::DeferredEquidistant(25, AxisX),
0857 AxisSpec::DeferredEquidistant(30, AxisY));
0858 mat->configureFace(PositiveYFace, AxisSpec::DeferredEquidistant(35, AxisX),
0859 AxisSpec::DeferredEquidistant(40, AxisY));
0860 mat->configureFace(NegativeZFace, AxisSpec::DeferredEquidistant(45, AxisX),
0861 AxisSpec::DeferredEquidistant(50, AxisY));
0862 mat->configureFace(PositiveZFace, AxisSpec::DeferredEquidistant(55, AxisX),
0863 AxisSpec::DeferredEquidistant(60, AxisY));
0864
0865 mat->addChild(std::make_shared<StaticBlueprintNode>(std::move(cuboid)));
0866
0867 root.addChild(mat);
0868
0869 auto trackingGeometry =
0870 root.construct({}, gctx, *logger->clone(std::nullopt, Logging::VERBOSE));
0871
0872 BOOST_REQUIRE(trackingGeometry);
0873
0874 auto lookup = nameLookup(*trackingGeometry);
0875 auto& child = lookup("child");
0876
0877
0878 for (std::size_t i = 0; i < child.portals().size(); i++) {
0879 const auto* material = child.portals().at(i).surface().surfaceMaterial();
0880 BOOST_REQUIRE_NE(material, nullptr);
0881
0882 const auto& gridMaterial =
0883 dynamic_cast<const ProtoGridSurfaceMaterial&>(*material);
0884
0885
0886 CuboidVolumeBounds::Face face = static_cast<CuboidVolumeBounds::Face>(i);
0887 switch (face) {
0888 case NegativeXFace:
0889 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 5);
0890 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 10);
0891 break;
0892 case PositiveXFace:
0893 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 15);
0894 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 20);
0895 break;
0896 case NegativeYFace:
0897 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 25);
0898 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 30);
0899 break;
0900 case PositiveYFace:
0901 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 35);
0902 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 40);
0903 break;
0904 case NegativeZFace:
0905 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 45);
0906 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 50);
0907 break;
0908 case PositiveZFace:
0909 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(0).nBins(), 55);
0910 BOOST_CHECK_EQUAL(gridMaterial.binning().axisSpec(1).nBins(), 60);
0911 break;
0912 }
0913 }
0914 }
0915
0916 BOOST_AUTO_TEST_CASE(HomogeneousMaterialCylinder) {
0917 Blueprint::Config cfg;
0918 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0919 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0920 Blueprint root{cfg};
0921
0922 double hlZ = 30_mm;
0923 auto cylBounds = std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, hlZ);
0924 auto cyl = std::make_unique<TrackingVolume>(Transform3::Identity(), cylBounds,
0925 "child");
0926
0927 using enum CylinderVolumeBounds::Face;
0928
0929
0930 auto testMaterial = Material::fromMolarDensity(
0931 9.370_cm, 46.52_cm, 28.0855, 14, (2.329 / 28.0855) * 1_mol / 1_cm3);
0932
0933 auto negDiscMat = std::make_shared<HomogeneousSurfaceMaterial>(
0934 MaterialSlab(testMaterial, 0.1_mm));
0935 auto posDiscMat = std::make_shared<HomogeneousSurfaceMaterial>(
0936 MaterialSlab(testMaterial, 0.2_mm));
0937 auto outerCylMat = std::make_shared<HomogeneousSurfaceMaterial>(
0938 MaterialSlab(testMaterial, 0.3_mm));
0939
0940 root.addMaterial("Material", [&](auto& mat) {
0941 mat.configureFace(NegativeDisc, negDiscMat);
0942 mat.configureFace(PositiveDisc, posDiscMat);
0943 mat.configureFace(OuterCylinder, outerCylMat);
0944
0945 mat.addStaticVolume(std::move(cyl));
0946 });
0947
0948 auto trackingGeometry = root.construct({}, gctx, *logger);
0949
0950 BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2);
0951 auto lookup = nameLookup(*trackingGeometry);
0952 auto& child = lookup("child");
0953
0954
0955 const auto* negDisc = child.portals()
0956 .at(static_cast<std::size_t>(NegativeDisc))
0957 .surface()
0958 .surfaceMaterial();
0959 BOOST_REQUIRE_NE(negDisc, nullptr);
0960 BOOST_CHECK_EQUAL(negDisc, negDiscMat.get());
0961
0962
0963 const auto* posDisc = child.portals()
0964 .at(static_cast<std::size_t>(PositiveDisc))
0965 .surface()
0966 .surfaceMaterial();
0967 BOOST_REQUIRE_NE(posDisc, nullptr);
0968 BOOST_CHECK_EQUAL(posDisc, posDiscMat.get());
0969
0970
0971 const auto* outerCyl = child.portals()
0972 .at(static_cast<std::size_t>(OuterCylinder))
0973 .surface()
0974 .surfaceMaterial();
0975 BOOST_REQUIRE_NE(outerCyl, nullptr);
0976 BOOST_CHECK_EQUAL(outerCyl, outerCylMat.get());
0977
0978
0979 for (std::size_t i = 0; i < child.portals().size(); i++) {
0980 if (i != static_cast<std::size_t>(NegativeDisc) &&
0981 i != static_cast<std::size_t>(PositiveDisc) &&
0982 i != static_cast<std::size_t>(OuterCylinder)) {
0983 BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
0984 nullptr);
0985 }
0986 }
0987 }
0988
0989 BOOST_AUTO_TEST_CASE(HomogeneousMaterialCuboid) {
0990 Blueprint::Config cfg;
0991 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0992 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
0993 Blueprint root{cfg};
0994
0995 using enum CuboidVolumeBounds::Face;
0996
0997 double hlX = 30_mm;
0998 double hlY = 40_mm;
0999 double hlZ = 50_mm;
1000 auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(hlX, hlY, hlZ);
1001 auto cuboid = std::make_unique<TrackingVolume>(Transform3::Identity(),
1002 cuboidBounds, "child");
1003
1004
1005 auto testMaterial = Material::fromMolarDensity(
1006 9.370_cm, 46.52_cm, 28.0855, 14, (2.329 / 28.0855) * 1_mol / 1_cm3);
1007
1008 auto negXMat = std::make_shared<HomogeneousSurfaceMaterial>(
1009 MaterialSlab(testMaterial, 0.1_mm));
1010 auto posXMat = std::make_shared<HomogeneousSurfaceMaterial>(
1011 MaterialSlab(testMaterial, 0.2_mm));
1012 auto negYMat = std::make_shared<HomogeneousSurfaceMaterial>(
1013 MaterialSlab(testMaterial, 0.3_mm));
1014 auto posYMat = std::make_shared<HomogeneousSurfaceMaterial>(
1015 MaterialSlab(testMaterial, 0.4_mm));
1016 auto negZMat = std::make_shared<HomogeneousSurfaceMaterial>(
1017 MaterialSlab(testMaterial, 0.5_mm));
1018 auto posZMat = std::make_shared<HomogeneousSurfaceMaterial>(
1019 MaterialSlab(testMaterial, 0.6_mm));
1020
1021 root.addMaterial("Material", [&](auto& mat) {
1022 mat.configureFace(NegativeXFace, negXMat);
1023 mat.configureFace(PositiveXFace, posXMat);
1024 mat.configureFace(NegativeYFace, negYMat);
1025 mat.configureFace(PositiveYFace, posYMat);
1026 mat.configureFace(NegativeZFace, negZMat);
1027 mat.configureFace(PositiveZFace, posZMat);
1028
1029 mat.addStaticVolume(std::move(cuboid));
1030 });
1031
1032 auto trackingGeometry = root.construct({}, gctx, *logger);
1033
1034 BOOST_REQUIRE(trackingGeometry);
1035
1036 auto lookup = nameLookup(*trackingGeometry);
1037 auto& child = lookup("child");
1038
1039
1040 for (std::size_t i = 0; i < child.portals().size(); i++) {
1041 const auto* material = child.portals().at(i).surface().surfaceMaterial();
1042 BOOST_REQUIRE_NE(material, nullptr);
1043
1044 const auto* homMaterial =
1045 dynamic_cast<const HomogeneousSurfaceMaterial*>(material);
1046 BOOST_REQUIRE_NE(homMaterial, nullptr);
1047
1048
1049 CuboidVolumeBounds::Face face = static_cast<CuboidVolumeBounds::Face>(i);
1050 switch (face) {
1051 case NegativeXFace:
1052 BOOST_CHECK_EQUAL(homMaterial, negXMat.get());
1053 break;
1054 case PositiveXFace:
1055 BOOST_CHECK_EQUAL(homMaterial, posXMat.get());
1056 break;
1057 case NegativeYFace:
1058 BOOST_CHECK_EQUAL(homMaterial, negYMat.get());
1059 break;
1060 case PositiveYFace:
1061 BOOST_CHECK_EQUAL(homMaterial, posYMat.get());
1062 break;
1063 case NegativeZFace:
1064 BOOST_CHECK_EQUAL(homMaterial, negZMat.get());
1065 break;
1066 case PositiveZFace:
1067 BOOST_CHECK_EQUAL(homMaterial, posZMat.get());
1068 break;
1069 }
1070 }
1071 }
1072
1073 BOOST_AUTO_TEST_CASE(HomogeneousMaterialMixedVolumeTypes) {
1074 Blueprint::Config cfg;
1075 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1076 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1077 Blueprint root{cfg};
1078
1079 auto testMaterial = Material::fromMolarDensity(
1080 9.370_cm, 46.52_cm, 28.0855, 14, (2.329 / 28.0855) * 1_mol / 1_cm3);
1081
1082 auto material = std::make_shared<HomogeneousSurfaceMaterial>(
1083 MaterialSlab(testMaterial, 0.1_mm));
1084
1085
1086 BOOST_CHECK_THROW(
1087 root.addMaterial("Material",
1088 [&](auto& mat) {
1089 mat.configureFace(
1090 CylinderVolumeBounds::Face::NegativeDisc,
1091 material);
1092 mat.configureFace(
1093 CuboidVolumeBounds::Face::NegativeXFace, material);
1094 }),
1095 std::runtime_error);
1096
1097
1098 BOOST_CHECK_THROW(
1099 root.addMaterial("Material",
1100 [&](auto& mat) {
1101 mat.configureFace(
1102 CuboidVolumeBounds::Face::NegativeXFace, material);
1103 mat.configureFace(
1104 CylinderVolumeBounds::Face::NegativeDisc,
1105 material);
1106 }),
1107 std::runtime_error);
1108 }
1109
1110 BOOST_AUTO_TEST_CASE(LayerCenterOfGravity) {
1111
1112 {
1113 double yrot = 45_degree;
1114 Transform3 base =
1115 Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
1116
1117 std::vector<std::shared_ptr<Surface>> surfaces;
1118 std::vector<std::unique_ptr<SurfacePlacementBase>> elements;
1119 double r = 300_mm;
1120 std::size_t nSensors = 8;
1121 double thickness = 2.5_mm;
1122 auto recBounds = std::make_shared<RectangleBounds>(40_mm, 60_mm);
1123
1124 double deltaPhi = 2 * std::numbers::pi / nSensors;
1125 for (std::size_t i = 0; i < nSensors; i++) {
1126 Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} *
1127 Translation3(Vector3::UnitX() * r);
1128
1129 if (i % 2 == 0) {
1130 trf = trf * Translation3{Vector3::UnitZ() * 5_mm};
1131 }
1132
1133 auto& element = elements.emplace_back(
1134 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
1135
1136 element->surface().assignSurfacePlacement(*element);
1137 surfaces.push_back(element->surface().getSharedPtr());
1138 }
1139
1140 Blueprint root{{.envelope = ExtentEnvelope{{
1141 .z = {2_mm, 2_mm},
1142 .r = {3_mm, 5_mm},
1143 }}}};
1144
1145 root.addLayer("Layer0", [&](auto& layer) {
1146 layer.setSurfaces(surfaces)
1147 .setLayerType(LayerBlueprintNode::LayerType::Disc)
1148 .setEnvelope(ExtentEnvelope{{
1149 .z = {0.1_mm, 0.1_mm},
1150 .r = {1_mm, 1_mm},
1151 }})
1152 .setTransform(base)
1153 .setUseCenterOfGravity(false, false, false);
1154 });
1155
1156 auto trackingGeometry = root.construct({}, gctx, *logger);
1157 auto lookup = nameLookup(*trackingGeometry);
1158
1159 BOOST_CHECK_EQUAL(lookup("Layer0").volumeBounds().type(),
1160 VolumeBounds::BoundsType::eCylinder);
1161
1162 auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
1163 lookup("Layer0").volumeBounds());
1164
1165
1166 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 258.9999999_mm,
1167 1e-6);
1168 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR),
1169 346.25353003_mm, 1e-6);
1170 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 3.85_mm,
1171 1e-6);
1172 }
1173
1174
1175 {
1176 double yrot = 0_degree;
1177 Transform3 base =
1178 Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()};
1179
1180 std::vector<std::shared_ptr<Surface>> surfaces;
1181 std::vector<std::unique_ptr<SurfacePlacementBase>> elements;
1182
1183 double r = 300_mm;
1184 std::size_t nStaves = 10;
1185 int nSensorsPerStave = 8;
1186 double thickness = 0;
1187 double hlPhi = 40_mm;
1188 double hlZ = 60_mm;
1189 auto recBounds = std::make_shared<RectangleBounds>(hlPhi, hlZ);
1190
1191 double deltaPhi = 2 * std::numbers::pi / nStaves;
1192
1193 for (std::size_t istave = 0; istave < nStaves; istave++) {
1194 for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave;
1195 isensor++) {
1196 double z = isensor * (2 * hlZ + 5_mm);
1197
1198 Transform3 trf = base * Translation3(Vector3::UnitZ() * z) *
1199 AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} *
1200 Translation3(Vector3::UnitX() * r) *
1201 AngleAxis3{10_degree, Vector3::UnitZ()} *
1202 AngleAxis3{90_degree, Vector3::UnitY()} *
1203 AngleAxis3{90_degree, Vector3::UnitZ()};
1204 auto& element = elements.emplace_back(
1205 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
1206 element->surface().assignSurfacePlacement(*element);
1207 surfaces.push_back(element->surface().getSharedPtr());
1208 }
1209 }
1210
1211 Blueprint root{{.envelope = ExtentEnvelope{{
1212 .z = {2_mm, 2_mm},
1213 .r = {3_mm, 5_mm},
1214 }}}};
1215
1216 root.addLayer("Layer0", [&](auto& layer) {
1217 layer.setSurfaces(surfaces)
1218 .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
1219 .setEnvelope(ExtentEnvelope{{
1220 .z = {10_mm, 10_mm},
1221 .r = {20_mm, 10_mm},
1222 }})
1223 .setTransform(base)
1224 .setUseCenterOfGravity(false, false, false);
1225 });
1226
1227 auto trackingGeometry = root.construct({}, gctx, *logger);
1228 auto lookup = nameLookup(*trackingGeometry);
1229 BOOST_CHECK_EQUAL(lookup("Layer0").volumeBounds().type(),
1230 VolumeBounds::BoundsType::eCylinder);
1231
1232 auto layerCyl = dynamic_cast<const CylinderVolumeBounds&>(
1233 lookup("Layer0").volumeBounds());
1234
1235
1236 BOOST_CHECK_EQUAL(lookup("Layer0").portals().size(), 4);
1237 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 275.6897761_mm,
1238 1e-6);
1239 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 319.4633358_mm,
1240 1e-6);
1241 BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1070_mm,
1242 1e-6);
1243 }
1244 }
1245
1246 BOOST_AUTO_TEST_CASE(GeometryIdnetifiersForPortals) {
1247 Blueprint::Config cfg;
1248 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1249 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1250 Blueprint root{cfg};
1251
1252 auto& cubcontainer =
1253 root.addCuboidContainer("CuboidContainer", AxisDirection::AxisX);
1254 auto parentBounds = std::make_shared<CuboidVolumeBounds>(1_m, 20_mm, 20_mm);
1255 auto parentVol = std::make_unique<TrackingVolume>(Transform3::Identity(),
1256 parentBounds, "parent");
1257 parentVol->assignGeometryId(GeometryIdentifier{}.withVolume(1));
1258 auto parentNode = std::make_shared<StaticBlueprintNode>(std::move(parentVol));
1259 std::size_t nChambers = 50;
1260
1261 double startX = -1000. + 3. + 0.5;
1262 Transform3 trf = Transform3(Translation3(startX, 0, 0));
1263 auto tbounds =
1264 std::make_shared<TrapezoidVolumeBounds>(3_mm, 3_mm, 10_mm, 15_mm);
1265
1266 for (std::size_t i = 0; i < nChambers; i++) {
1267
1268 trf.translation() += Vector3::UnitX() * i * 7_mm;
1269
1270 auto childVol = std::make_unique<TrackingVolume>(
1271 trf, tbounds, "child" + std::to_string(i));
1272 childVol->assignGeometryId(
1273 GeometryIdentifier{}.withVolume(1).withLayer(i + 1));
1274 auto childNode = std::make_shared<StaticBlueprintNode>(std::move(childVol));
1275 parentNode->addChild(std::move(childNode));
1276 }
1277
1278 cubcontainer.addChild(std::move(parentNode));
1279
1280 auto trackingGeometry = root.construct({}, gctx, *logger);
1281 }
1282
1283 BOOST_AUTO_TEST_CASE(PadBlueprintNodeCylinder) {
1284 Blueprint::Config cfg;
1285 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1286 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1287
1288 PadBlueprintNode pad("World", cfg.envelope);
1289
1290 auto child = std::make_unique<TrackingVolume>(
1291 Transform3::Identity(),
1292 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm), "child");
1293 const TrackingVolume* childVol = child.get();
1294 pad.addStaticVolume(std::move(child));
1295
1296 BlueprintOptions options;
1297
1298 auto& world =
1299 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1300
1301
1302 const auto& childCyl =
1303 dynamic_cast<const CylinderVolumeBounds&>(childVol->volumeBounds());
1304 BOOST_CHECK_EQUAL(childCyl.get(CylinderVolumeBounds::eMinR), 10_mm);
1305 BOOST_CHECK_EQUAL(childCyl.get(CylinderVolumeBounds::eMaxR), 20_mm);
1306 BOOST_CHECK_EQUAL(childCyl.get(CylinderVolumeBounds::eHalfLengthZ), 30_mm);
1307
1308 BOOST_CHECK_EQUAL(world.volumeName(), "World");
1309 BOOST_CHECK_EQUAL(world.volumeBounds().type(),
1310 VolumeBounds::BoundsType::eCylinder);
1311
1312 const auto& worldCyl =
1313 dynamic_cast<const CylinderVolumeBounds&>(world.volumeBounds());
1314 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 10_mm - 1_mm);
1315 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 20_mm + 2_mm);
1316 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1317 30_mm + 20_mm);
1318 }
1319
1320 BOOST_AUTO_TEST_CASE(PadBlueprintNodeLegacyPaddedOverload) {
1321 Blueprint::Config cfg;
1322 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1323 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1324
1325 Volume child(Transform3::Identity(),
1326 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm));
1327
1328
1329
1330 ACTS_PUSH_IGNORE_DEPRECATED()
1331 auto world =
1332 PadBlueprintNode::padded(gctx, child, cfg.envelope, "World", *logger);
1333 ACTS_POP_IGNORE_DEPRECATED()
1334
1335 BOOST_CHECK_EQUAL(world->volumeName(), "World");
1336 const auto& worldCyl =
1337 dynamic_cast<const CylinderVolumeBounds&>(world->volumeBounds());
1338 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 10_mm - 1_mm);
1339 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 20_mm + 2_mm);
1340 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1341 30_mm + 20_mm);
1342 BOOST_CHECK_SMALL(world->center(gctx).norm(), 1e-9);
1343 }
1344
1345 BOOST_AUTO_TEST_CASE(PadBlueprintNodeCuboid) {
1346 Blueprint::Config cfg;
1347 cfg.envelope[AxisDirection::AxisX] = {3_mm, 3_mm};
1348 cfg.envelope[AxisDirection::AxisY] = {5_mm, 5_mm};
1349 cfg.envelope[AxisDirection::AxisZ] = {7_mm, 7_mm};
1350
1351 PadBlueprintNode pad("World", cfg.envelope);
1352 auto child = std::make_unique<TrackingVolume>(
1353 Transform3::Identity(),
1354 std::make_shared<CuboidVolumeBounds>(10_mm, 20_mm, 30_mm), "child");
1355 const TrackingVolume* childVol = child.get();
1356 pad.addStaticVolume(std::move(child));
1357
1358 BlueprintOptions options;
1359
1360 auto& world =
1361 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1362
1363
1364 const auto& childBox =
1365 dynamic_cast<const CuboidVolumeBounds&>(childVol->volumeBounds());
1366 BOOST_CHECK_EQUAL(childBox.get(CuboidVolumeBounds::eHalfLengthX), 10_mm);
1367 BOOST_CHECK_EQUAL(childBox.get(CuboidVolumeBounds::eHalfLengthY), 20_mm);
1368 BOOST_CHECK_EQUAL(childBox.get(CuboidVolumeBounds::eHalfLengthZ), 30_mm);
1369
1370 BOOST_CHECK_EQUAL(world.volumeName(), "World");
1371 BOOST_CHECK_EQUAL(world.volumeBounds().type(),
1372 VolumeBounds::BoundsType::eCuboid);
1373
1374 const auto& worldBox =
1375 dynamic_cast<const CuboidVolumeBounds&>(world.volumeBounds());
1376 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthX),
1377 10_mm + 3_mm);
1378 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthY),
1379 20_mm + 5_mm);
1380 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthZ),
1381 30_mm + 7_mm);
1382
1383
1384 BOOST_CHECK_EQUAL(world.portals().size(), 0);
1385 }
1386
1387 BOOST_AUTO_TEST_CASE(PadBlueprintNodeRotatedCylinder) {
1388 Blueprint::Config cfg;
1389 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1390 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1391
1392 PadBlueprintNode pad("World", cfg.envelope);
1393
1394 Transform3 childTrf =
1395 Transform3::Identity() * AngleAxis3{45_degree, Vector3::UnitY()};
1396
1397 pad.addStaticVolume(std::make_unique<TrackingVolume>(
1398 childTrf, std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm),
1399 "child"));
1400
1401 BlueprintOptions options;
1402 auto& world =
1403 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1404
1405 BOOST_CHECK_EQUAL(world.volumeName(), "World");
1406 BOOST_CHECK_EQUAL(world.volumeBounds().type(),
1407 VolumeBounds::BoundsType::eCylinder);
1408
1409
1410 const auto& worldCyl =
1411 dynamic_cast<const CylinderVolumeBounds&>(world.volumeBounds());
1412 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 10_mm - 1_mm);
1413 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 20_mm + 2_mm);
1414 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1415 30_mm + 20_mm);
1416
1417
1418 BOOST_CHECK(world.localToGlobalTransform(gctx).isApprox(childTrf));
1419 }
1420
1421 BOOST_AUTO_TEST_CASE(PadBlueprintNodeNestedInContainer) {
1422 Blueprint::Config cfg;
1423 cfg.envelope[AxisDirection::AxisZ] = {5_mm, 5_mm};
1424 cfg.envelope[AxisDirection::AxisR] = {5_mm, 5_mm};
1425 Blueprint root{cfg};
1426
1427 auto& container =
1428 root.addCylinderContainer("Container", AxisDirection::AxisZ);
1429
1430 ExtentEnvelope padEnvelope = ExtentEnvelope::Zero();
1431 padEnvelope[AxisDirection::AxisZ] = {10_mm, 10_mm};
1432 padEnvelope[AxisDirection::AxisR] = {1_mm, 10_mm};
1433 auto pad = std::make_shared<PadBlueprintNode>("Pad", padEnvelope);
1434
1435 pad->addStaticVolume(std::make_unique<TrackingVolume>(
1436 Transform3::Identity(),
1437 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm), "child"));
1438
1439 container.addChild(pad);
1440
1441 auto trackingGeometry = root.construct({}, gctx, *logger);
1442 auto lookup = nameLookup(*trackingGeometry);
1443
1444
1445
1446 const auto& padCyl =
1447 dynamic_cast<const CylinderVolumeBounds&>(lookup("Pad").volumeBounds());
1448 BOOST_CHECK_EQUAL(padCyl.get(CylinderVolumeBounds::eMinR), 10_mm - 1_mm);
1449 BOOST_CHECK_EQUAL(padCyl.get(CylinderVolumeBounds::eMaxR), 20_mm + 10_mm);
1450 BOOST_CHECK_EQUAL(padCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1451 30_mm + 10_mm);
1452
1453
1454
1455
1456 const auto& worldCyl =
1457 dynamic_cast<const CylinderVolumeBounds&>(lookup("World").volumeBounds());
1458 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR),
1459 padCyl.get(CylinderVolumeBounds::eMinR) - 5_mm);
1460 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR),
1461 padCyl.get(CylinderVolumeBounds::eMaxR) + 5_mm);
1462 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1463 padCyl.get(CylinderVolumeBounds::eHalfLengthZ) + 5_mm);
1464 }
1465
1466 BOOST_AUTO_TEST_CASE(PadBlueprintNodeReferenceAxisBounds) {
1467 Blueprint::Config cfg;
1468 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1469 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1470
1471
1472 PadBlueprintNode pad("World", cfg.envelope);
1473 pad.setReferenceAxis(Transform3::Identity());
1474
1475 const double offsetX = 50_mm;
1476 auto child = std::make_unique<TrackingVolume>(
1477 Transform3::Identity() * Translation3{Vector3{offsetX, 0, 0}},
1478 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm), "child");
1479 const TrackingVolume* childVol = child.get();
1480 pad.addStaticVolume(std::move(child));
1481
1482 BlueprintOptions options;
1483 auto& world =
1484 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1485
1486
1487 BOOST_CHECK_CLOSE(childVol->center(gctx)[eX], offsetX, 1e-9);
1488
1489 BOOST_CHECK_EQUAL(world.volumeName(), "World");
1490 BOOST_CHECK_EQUAL(world.volumeBounds().type(),
1491 VolumeBounds::BoundsType::eCylinder);
1492
1493
1494 BOOST_CHECK_SMALL(world.center(gctx)[eX], 1e-9);
1495 BOOST_CHECK_SMALL(world.center(gctx)[eY], 1e-9);
1496
1497
1498
1499
1500
1501 const auto& worldCyl =
1502 dynamic_cast<const CylinderVolumeBounds&>(world.volumeBounds());
1503 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 29_mm);
1504 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 72_mm);
1505 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ),
1506 30_mm + 20_mm);
1507 }
1508
1509 BOOST_AUTO_TEST_CASE(PadBlueprintNodeReferenceAxisRejectsCuboid) {
1510
1511
1512 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
1513
1514 Blueprint::Config cfg;
1515 cfg.envelope[AxisDirection::AxisX] = {1_mm, 1_mm};
1516 cfg.envelope[AxisDirection::AxisY] = {1_mm, 1_mm};
1517 cfg.envelope[AxisDirection::AxisZ] = {1_mm, 1_mm};
1518
1519 PadBlueprintNode pad("World", cfg.envelope);
1520 pad.setReferenceAxis(Transform3::Identity());
1521 pad.addStaticVolume(std::make_unique<TrackingVolume>(
1522 Transform3::Identity(),
1523 std::make_shared<CuboidVolumeBounds>(10_mm, 20_mm, 30_mm), "child"));
1524
1525 BlueprintOptions options;
1526 BOOST_CHECK_THROW(pad.build(options, gctx, *logger), std::logic_error);
1527 }
1528
1529 BOOST_AUTO_TEST_CASE(PadBlueprintNodeReferenceAxisInCylinderHierarchy) {
1530 Blueprint::Config cfg;
1531 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
1532 cfg.envelope[AxisDirection::AxisR] = {2_mm, 20_mm};
1533 auto root = std::make_unique<Blueprint>(cfg);
1534
1535 std::vector<std::unique_ptr<SurfacePlacementBase>> elements;
1536 std::vector<std::shared_ptr<Surface>> b0SensitiveSurfaces;
1537
1538
1539
1540 auto makeB0Layer = [&](std::size_t layer) -> std::unique_ptr<TrackingVolume> {
1541 const double offsetX = -160_mm;
1542 const double z = 6250_mm + layer * 100_mm;
1543 auto layerVolume = std::make_unique<TrackingVolume>(
1544 Transform3::Identity() * Translation3{Vector3{offsetX, 0., z}},
1545 std::make_shared<CylinderVolumeBounds>(35_mm, 150_mm, 20_mm),
1546 "B0Layer" + std::to_string(layer));
1547
1548 const std::size_t nModules = 6;
1549 const double moduleR = 90_mm;
1550 const auto moduleBounds = std::make_shared<RectangleBounds>(8_mm, 15_mm);
1551 for (std::size_t i = 0; i < nModules; ++i) {
1552 const double phi = 2. * std::numbers::pi * static_cast<double>(i) /
1553 static_cast<double>(nModules);
1554 Transform3 moduleTransform = Transform3::Identity() *
1555 Translation3{Vector3{offsetX, 0., z}} *
1556 AngleAxis3{phi, Vector3::UnitZ()} *
1557 Translation3{Vector3::UnitX() * moduleR} *
1558 AngleAxis3{90_degree, Vector3::UnitY()} *
1559 AngleAxis3{90_degree, Vector3::UnitZ()};
1560
1561 auto& element =
1562 elements.emplace_back(std::make_unique<DetectorElementStub>(
1563 moduleTransform, moduleBounds, 0.));
1564 element->surface().assignSurfacePlacement(*element);
1565 auto surface = element->surface().getSharedPtr();
1566 layerVolume->addSurface(surface);
1567 b0SensitiveSurfaces.push_back(std::move(surface));
1568 }
1569
1570 return layerVolume;
1571 };
1572
1573 root->addCylinderContainer("Detector", AxisDirection::AxisZ, [&](auto& det) {
1574 det.addStaticVolume(
1575 Transform3::Identity(),
1576 std::make_shared<CylinderVolumeBounds>(20_mm, 400_mm, 1000_mm),
1577 "MainTracker");
1578
1579
1580
1581 ExtentEnvelope padEnvelope = ExtentEnvelope::Zero();
1582 padEnvelope[AxisDirection::AxisZ] = {2_mm, 2_mm};
1583 padEnvelope[AxisDirection::AxisR] = {2_mm, 2_mm};
1584 auto pad = std::make_shared<PadBlueprintNode>("B0Envelope", padEnvelope);
1585 pad->setReferenceAxis(Transform3::Identity());
1586 pad->addCylinderContainer("B0", AxisDirection::AxisZ, [&](auto& b0) {
1587 b0.addStaticVolume(makeB0Layer(0));
1588 b0.addStaticVolume(makeB0Layer(1));
1589 });
1590 det.addChild(pad);
1591 });
1592
1593 auto trackingGeometry = root->construct({}, gctx, *logger);
1594 BOOST_REQUIRE(trackingGeometry != nullptr);
1595 BOOST_CHECK(trackingGeometry->geometryVersion() ==
1596 TrackingGeometry::GeometryVersion::Gen3);
1597
1598
1599 const auto* envelope = trackingGeometry->findVolumeByName("B0Envelope");
1600 BOOST_REQUIRE(envelope != nullptr);
1601 BOOST_CHECK_SMALL(envelope->center(gctx)[eX], 1e-9);
1602 BOOST_CHECK_SMALL(envelope->center(gctx)[eY], 1e-9);
1603
1604
1605 const auto* b0Layer0 = trackingGeometry->findVolumeByName("B0Layer0");
1606 BOOST_REQUIRE(b0Layer0 != nullptr);
1607 BOOST_CHECK_CLOSE(b0Layer0->center(gctx)[eX], -160_mm, 1e-8);
1608
1609
1610
1611 std::size_t found = 0;
1612 for (const auto& surface : b0SensitiveSurfaces) {
1613 const auto id = surface->geometryId();
1614 BOOST_CHECK_NE(id.sensitive(), 0u);
1615 BOOST_REQUIRE(trackingGeometry->findSurface(id) != nullptr);
1616 found++;
1617 }
1618 BOOST_CHECK_EQUAL(found, b0SensitiveSurfaces.size());
1619 }
1620
1621 BOOST_AUTO_TEST_CASE(PadBlueprintNodeCenteredRejectsAsymmetricZ) {
1622
1623
1624
1625 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
1626
1627 Blueprint::Config cfg;
1628 cfg.envelope[AxisDirection::AxisZ] = {10_mm, 40_mm};
1629 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1630
1631 PadBlueprintNode pad("World", cfg.envelope);
1632 pad.addStaticVolume(std::make_unique<TrackingVolume>(
1633 Transform3::Identity(),
1634 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm), "child"));
1635
1636 BlueprintOptions options;
1637 BOOST_CHECK_THROW(pad.build(options, gctx, *logger), std::logic_error);
1638 }
1639
1640 BOOST_AUTO_TEST_CASE(PadBlueprintNodeFitBoundsCylinder) {
1641
1642
1643 Blueprint::Config cfg;
1644 cfg.envelope[AxisDirection::AxisZ] = {10_mm, 40_mm};
1645 cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
1646
1647 PadBlueprintNode pad("World", cfg.envelope);
1648 pad.setCentering(PadBlueprintNode::Centering::FitBounds);
1649 pad.addStaticVolume(std::make_unique<TrackingVolume>(
1650 Transform3::Identity(),
1651 std::make_shared<CylinderVolumeBounds>(10_mm, 20_mm, 30_mm), "child"));
1652
1653 BlueprintOptions options;
1654 auto& world =
1655 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1656
1657 const auto& worldCyl =
1658 dynamic_cast<const CylinderVolumeBounds&>(world.volumeBounds());
1659
1660 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 55_mm);
1661 BOOST_CHECK_CLOSE(world.center(gctx)[eZ], 15_mm, 1e-9);
1662
1663 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 9_mm);
1664 BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 22_mm);
1665 }
1666
1667 BOOST_AUTO_TEST_CASE(PadBlueprintNodeFitBoundsCuboid) {
1668
1669 Blueprint::Config cfg;
1670 cfg.envelope[AxisDirection::AxisX] = {2_mm, 6_mm};
1671 cfg.envelope[AxisDirection::AxisY] = {3_mm, 3_mm};
1672 cfg.envelope[AxisDirection::AxisZ] = {0_mm, 0_mm};
1673
1674 PadBlueprintNode pad("World", cfg.envelope);
1675 pad.setCentering(PadBlueprintNode::Centering::FitBounds);
1676 pad.addStaticVolume(std::make_unique<TrackingVolume>(
1677 Transform3::Identity(),
1678 std::make_shared<CuboidVolumeBounds>(10_mm, 20_mm, 30_mm), "child"));
1679
1680 BlueprintOptions options;
1681 auto& world =
1682 dynamic_cast<TrackingVolume&>(pad.build(options, gctx, *logger));
1683
1684 const auto& worldBox =
1685 dynamic_cast<const CuboidVolumeBounds&>(world.volumeBounds());
1686 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthX), 14_mm);
1687 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthY), 23_mm);
1688 BOOST_CHECK_EQUAL(worldBox.get(CuboidVolumeBounds::eHalfLengthZ), 30_mm);
1689 BOOST_CHECK_CLOSE(world.center(gctx)[eX], 2_mm, 1e-9);
1690 BOOST_CHECK_SMALL(world.center(gctx)[eY], 1e-9);
1691 BOOST_CHECK_SMALL(world.center(gctx)[eZ], 1e-9);
1692 }
1693
1694 BOOST_AUTO_TEST_SUITE_END();
1695
1696 }