File indexing completed on 2026-07-14 07:51:53
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/Algebra.hpp"
0015 #include "Acts/Definitions/Units.hpp"
0016 #include "Acts/Geometry/Blueprint.hpp"
0017 #include "Acts/Geometry/ContainerBlueprintNode.hpp"
0018 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0019 #include "Acts/Geometry/GeometryContext.hpp"
0020 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0021 #include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
0022 #include "Acts/Geometry/Portal.hpp"
0023 #include "Acts/Geometry/PortalDesignatorBlueprintNode.hpp"
0024 #include "Acts/Geometry/TrackingGeometry.hpp"
0025 #include "Acts/Geometry/TrackingVolume.hpp"
0026 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0027 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0028 #include "Acts/Material/MergedMaterialMarker.hpp"
0029 #include "Acts/Navigation/INavigationPolicy.hpp"
0030 #include "Acts/Navigation/NavigationStream.hpp"
0031 #include "Acts/Surfaces/RectangleBounds.hpp"
0032 #include "Acts/Surfaces/Surface.hpp"
0033 #include "Acts/Utilities/Logger.hpp"
0034 #include "Acts/Utilities/ProtoAxis.hpp"
0035 #include "Acts/Visualization/GeometryView3D.hpp"
0036 #include "Acts/Visualization/ObjVisualization3D.hpp"
0037 #include "ActsTests/CommonHelpers/DetectorElementStub.hpp"
0038
0039 #include <fstream>
0040 #include <random>
0041 #include <vector>
0042
0043 using namespace Acts;
0044 using namespace UnitLiterals;
0045
0046 using Experimental::Blueprint;
0047 using Experimental::LayerBlueprintNode;
0048 using Experimental::MaterialDesignatorBlueprintNode;
0049
0050 namespace ActsTests {
0051
0052 auto logger = getDefaultLogger("UnitTests", Logging::DEBUG);
0053
0054 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0055
0056 inline std::vector<std::shared_ptr<Surface>> makeFanLayer(
0057 const Transform3& base,
0058 std::vector<std::unique_ptr<SurfacePlacementBase>>& elements,
0059 double r = 300_mm, std::size_t nSensors = 8, double thickness = 0) {
0060 auto recBounds = std::make_shared<RectangleBounds>(40_mm, 60_mm);
0061
0062 double deltaPhi = 2 * std::numbers::pi / nSensors;
0063 std::vector<std::shared_ptr<Surface>> surfaces;
0064 for (std::size_t i = 0; i < nSensors; i++) {
0065
0066
0067 Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} *
0068 Translation3(Vector3::UnitX() * r);
0069
0070 if (i % 2 == 0) {
0071 trf = trf * Translation3{Vector3::UnitZ() * 5_mm};
0072 }
0073
0074 auto& element = elements.emplace_back(
0075 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0076
0077 element->surface().assignSurfacePlacement(*element);
0078
0079 surfaces.push_back(element->surface().getSharedPtr());
0080 }
0081 return surfaces;
0082 }
0083
0084 inline std::vector<std::shared_ptr<Surface>> makeBarrelLayer(
0085 const Transform3& base,
0086 std::vector<std::unique_ptr<SurfacePlacementBase>>& elements,
0087 double r = 300_mm, std::size_t nStaves = 10, int nSensorsPerStave = 8,
0088 double thickness = 0, double hlPhi = 40_mm, double hlZ = 60_mm) {
0089 auto recBounds = std::make_shared<RectangleBounds>(hlPhi, hlZ);
0090
0091 double deltaPhi = 2 * std::numbers::pi / nStaves;
0092 std::vector<std::shared_ptr<Surface>> surfaces;
0093
0094 for (std::size_t istave = 0; istave < nStaves; istave++) {
0095 for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave;
0096 isensor++) {
0097 double z = isensor * (2 * hlZ + 5_mm);
0098
0099 Transform3 trf = base * Translation3(Vector3::UnitZ() * z) *
0100 AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} *
0101 Translation3(Vector3::UnitX() * r) *
0102 AngleAxis3{10_degree, Vector3::UnitZ()} *
0103 AngleAxis3{90_degree, Vector3::UnitY()} *
0104 AngleAxis3{90_degree, Vector3::UnitZ()};
0105 auto& element = elements.emplace_back(
0106 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0107 element->surface().assignSurfacePlacement(*element);
0108 surfaces.push_back(element->surface().getSharedPtr());
0109 }
0110 }
0111
0112 return surfaces;
0113 }
0114
0115 }
0116
0117 using namespace ActsTests;
0118
0119 BOOST_AUTO_TEST_SUITE(GeometrySuite);
0120
0121 void pseudoNavigation(const TrackingGeometry& trackingGeometry,
0122 Vector3 position, const Vector3& direction,
0123 std::ostream& csv, std::size_t run,
0124 std::size_t substepsPerCm, const Logger& logger) {
0125 ACTS_VERBOSE("start navigation " << run);
0126 ACTS_VERBOSE("dir: " << direction.transpose());
0127 ACTS_VERBOSE(direction.norm());
0128
0129 std::mt19937 rng{static_cast<unsigned int>(run)};
0130 std::uniform_real_distribution<> dist{0.01, 0.99};
0131
0132 const auto* volume = trackingGeometry.lowestTrackingVolume(gctx, position);
0133 BOOST_REQUIRE_NE(volume, nullptr);
0134 ACTS_VERBOSE(volume->volumeName());
0135
0136 NavigationStream main;
0137 const TrackingVolume* currentVolume = volume;
0138
0139 csv << run << "," << position[0] << "," << position[1] << "," << position[2];
0140 csv << "," << volume->geometryId().volume();
0141 csv << "," << volume->geometryId().boundary();
0142 csv << "," << volume->geometryId().sensitive();
0143 csv << std::endl;
0144
0145 ACTS_VERBOSE("start pseudo navigation");
0146
0147 for (std::size_t i = 0; i < 100; i++) {
0148 main = NavigationStream{};
0149 AppendOnlyNavigationStream stream{main};
0150
0151 NavigationArguments navArgs{.position = position, .direction = direction};
0152 NavigationPolicyStateManager stateManager;
0153 currentVolume->navigationPolicy()->createState(gctx, navArgs, stateManager,
0154 logger);
0155 auto policyState = stateManager.currentState();
0156 currentVolume->initializeNavigationCandidates(gctx, navArgs, policyState,
0157 stream, logger);
0158
0159 ACTS_VERBOSE(main.candidates().size() << " candidates");
0160
0161 for (const auto& candidate : main.candidates()) {
0162 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0163 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0164 }
0165
0166 ACTS_VERBOSE("initializing candidates");
0167 main.initialize(gctx, {position, direction}, BoundaryTolerance::None());
0168
0169 ACTS_VERBOSE(main.candidates().size() << " candidates remaining");
0170
0171 for (const auto& candidate : main.candidates()) {
0172 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0173 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0174 }
0175
0176 if (main.currentCandidate().surface().isOnSurface(gctx, position,
0177 direction)) {
0178 ACTS_VERBOSE("Already on surface at initialization, skipping candidate");
0179
0180 auto id = main.currentCandidate().surface().geometryId();
0181 csv << run << "," << position[0] << "," << position[1] << ","
0182 << position[2];
0183 csv << "," << id.volume();
0184 csv << "," << id.boundary();
0185 csv << "," << id.sensitive();
0186 csv << std::endl;
0187 if (!main.switchToNextCandidate()) {
0188 ACTS_WARNING("candidates exhausted unexpectedly");
0189 break;
0190 }
0191 }
0192
0193 auto writeIntersection = [&](const Vector3& pos, const Surface& surface) {
0194 csv << run << "," << pos[0] << "," << pos[1] << "," << pos[2];
0195 csv << "," << surface.geometryId().volume();
0196 csv << "," << surface.geometryId().boundary();
0197 csv << "," << surface.geometryId().sensitive();
0198 csv << std::endl;
0199 };
0200
0201 bool terminated = false;
0202 while (main.remainingCandidates() > 0) {
0203 const auto& candidate = main.currentCandidate();
0204
0205 ACTS_VERBOSE(candidate.position().transpose());
0206
0207 ACTS_VERBOSE("moving to position: " << position.transpose() << " (r="
0208 << VectorHelpers::perp(position)
0209 << ")");
0210
0211 Vector3 delta = candidate.position() - position;
0212
0213 std::size_t substeps =
0214 std::max(1l, std::lround(delta.norm() / 10_cm * substepsPerCm));
0215
0216 for (std::size_t j = 0; j < substeps; j++) {
0217
0218 Vector3 subpos = position + dist(rng) * delta;
0219 csv << run << "," << subpos[0] << "," << subpos[1] << "," << subpos[2];
0220 csv << "," << currentVolume->geometryId().volume();
0221 csv << ",0,0";
0222 csv << std::endl;
0223 }
0224
0225 position = candidate.position();
0226 ACTS_VERBOSE(" -> "
0227 << position.transpose()
0228 << " (r=" << VectorHelpers::perp(position) << ")");
0229
0230 writeIntersection(position, candidate.surface());
0231
0232 if (candidate.isPortalTarget()) {
0233 ACTS_VERBOSE("On portal: " << candidate.surface().toStream(gctx));
0234 currentVolume =
0235 candidate.portal().resolveVolume(gctx, position, direction).value();
0236
0237 if (currentVolume == nullptr) {
0238 ACTS_VERBOSE("switched to nullptr -> we're done");
0239 terminated = true;
0240 }
0241 break;
0242
0243 } else {
0244 ACTS_VERBOSE("Not on portal");
0245 }
0246
0247 main.switchToNextCandidate();
0248 }
0249
0250 if (terminated) {
0251 ACTS_VERBOSE("Terminate pseudo navigation");
0252 break;
0253 }
0254
0255 ACTS_VERBOSE("switched to " << currentVolume->volumeName());
0256
0257 ACTS_VERBOSE("-----");
0258 }
0259 }
0260
0261 BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
0262
0263 Transform3 base{Transform3::Identity()};
0264
0265 std::vector<std::unique_ptr<SurfacePlacementBase>> detectorElements;
0266 auto makeFan = [&](const Transform3& layerBase, auto&&..., double r,
0267 std::size_t nSensors, double thickness) {
0268 return makeFanLayer(layerBase, detectorElements, r, nSensors, thickness);
0269 };
0270
0271 Blueprint::Config cfg;
0272 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0273 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0274 auto root = std::make_unique<Blueprint>(cfg);
0275
0276 root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
0277 mat) {
0278 using enum AxisDirection;
0279 using enum AxisBoundaryType;
0280 using enum CylinderVolumeBounds::Face;
0281
0282
0283 mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20}, {AxisZ, Bound, 20});
0284 mat.configureFace(NegativeDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
0285 mat.configureFace(PositiveDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
0286
0287 mat.addCylinderContainer("Detector", AxisDirection::AxisR, [&](auto& det) {
0288 det.addCylinderContainer("Pixel", AxisDirection::AxisZ, [&](auto& cyl) {
0289 cyl.setAttachmentStrategy(VolumeAttachmentStrategy::Gap)
0290 .setResizeStrategy(VolumeResizeStrategy::Gap);
0291
0292 cyl.addCylinderContainer(
0293 "PixelNegativeEndcap", AxisDirection::AxisZ, [&](auto& ec) {
0294 ec.setAttachmentStrategy(VolumeAttachmentStrategy::Gap);
0295
0296 auto makeLayer = [&](const Transform3& trf, auto& layer) {
0297 std::vector<std::shared_ptr<Surface>> surfaces;
0298 auto layerSurfaces = makeFan(trf, 300_mm, 10, 2_mm);
0299 std::copy(layerSurfaces.begin(), layerSurfaces.end(),
0300 std::back_inserter(surfaces));
0301 layerSurfaces = makeFan(trf, 500_mm, 16, 2_mm);
0302 std::copy(layerSurfaces.begin(), layerSurfaces.end(),
0303 std::back_inserter(surfaces));
0304
0305 layer.setSurfaces(surfaces)
0306 .setLayerType(LayerBlueprintNode::LayerType::Disc)
0307 .setEnvelope(ExtentEnvelope{{
0308 .z = {5_mm, 5_mm},
0309 .r = {10_mm, 20_mm},
0310 }})
0311 .setTransform(base);
0312 };
0313
0314 ec.addLayer("PixelNeg1", [&](auto& layer) {
0315 makeLayer(base * Translation3{Vector3{0, 0, -700_mm}}, layer);
0316 });
0317
0318 ec.addLayer("PixelNeg2", [&](auto& layer) {
0319 makeLayer(base * Translation3{Vector3{0, 0, -500_mm}}, layer);
0320 });
0321 });
0322
0323 cyl.addCylinderContainer(
0324 "PixelBarrel", AxisDirection::AxisR, [&](auto& brl) {
0325 brl.setAttachmentStrategy(VolumeAttachmentStrategy::Gap)
0326 .setResizeStrategy(VolumeResizeStrategy::Gap);
0327
0328 auto makeLayer = [&](const std::string& name, double r,
0329 std::size_t nStaves, int nSensorsPerStave) {
0330 brl.addLayer(name, [&](auto& layer) {
0331 std::vector<std::shared_ptr<Surface>> surfaces =
0332 makeBarrelLayer(base, detectorElements, r, nStaves,
0333 nSensorsPerStave, 2.5_mm, 10_mm, 20_mm);
0334
0335 layer.setSurfaces(surfaces)
0336 .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
0337 .setEnvelope(ExtentEnvelope{{
0338 .z = {5_mm, 5_mm},
0339 .r = {1_mm, 1_mm},
0340 }})
0341 .setTransform(base);
0342 });
0343 };
0344
0345 makeLayer("PixelLayer0", 30_mm, 18, 5);
0346 makeLayer("PixelLayer1", 90_mm, 30, 6);
0347
0348 brl.addStaticVolume(base,
0349 std::make_shared<CylinderVolumeBounds>(
0350 100_mm, 110_mm, 250_mm),
0351 "PixelSupport");
0352
0353 makeLayer("PixelLayer2", 150_mm, 40, 7);
0354 makeLayer("PixelLayer3", 250_mm, 70, 8);
0355 });
0356
0357 auto& ec =
0358 cyl.addCylinderContainer("PixelPosWrapper", AxisDirection::AxisR);
0359 ec.setResizeStrategy(VolumeResizeStrategy::Gap);
0360 ec.addStaticVolume(std::make_unique<TrackingVolume>(
0361 base * Translation3{Vector3{0, 0, 600_mm}},
0362 std::make_shared<CylinderVolumeBounds>(150_mm, 390_mm, 200_mm),
0363 "PixelPositiveEndcap"));
0364 });
0365
0366 det.addStaticVolume(
0367 base, std::make_shared<CylinderVolumeBounds>(0_mm, 23_mm, 1000_mm),
0368 "BeamPipe");
0369 });
0370 });
0371
0372 std::ofstream dot{"api_test_container.dot"};
0373 root->graphviz(dot);
0374
0375 auto trackingGeometry = root->construct({}, gctx, *logger);
0376
0377 BOOST_REQUIRE(trackingGeometry);
0378 BOOST_CHECK(trackingGeometry->geometryVersion() ==
0379 TrackingGeometry::GeometryVersion::Gen3);
0380
0381 trackingGeometry->visitVolumes([&](const TrackingVolume* volume) {
0382 std::cout << volume->volumeName() << std::endl;
0383 std::cout << " -> id: " << volume->geometryId() << std::endl;
0384 std::cout << " -> " << volume->portals().size() << " portals" << std::endl;
0385 });
0386
0387 ObjVisualization3D vis;
0388
0389 trackingGeometry->visualize(vis, gctx, {}, {});
0390
0391 vis.write("api_test_container.obj");
0392
0393 Vector3 position = Vector3::Zero();
0394 std::ofstream csv{"api_test_container.csv"};
0395 csv << "x,y,z,volume,boundary,sensitive" << std::endl;
0396
0397 std::mt19937 rnd{42};
0398
0399 std::uniform_real_distribution<> dist{-1, 1};
0400
0401 double etaWidth = 3;
0402 double thetaMin = 2 * std::atan(std::exp(-etaWidth));
0403 double thetaMax = 2 * std::atan(std::exp(etaWidth));
0404 std::uniform_real_distribution<> thetaDist{thetaMin, thetaMax};
0405
0406 using namespace UnitLiterals;
0407
0408 for (std::size_t i = 0; i < 5000; i++) {
0409 double theta = thetaDist(rnd);
0410 double phi = 2 * std::numbers::pi * dist(rnd);
0411
0412 Vector3 direction;
0413 direction[0] = std::sin(theta) * std::cos(phi);
0414 direction[1] = std::sin(theta) * std::sin(phi);
0415 direction[2] = std::cos(theta);
0416
0417 pseudoNavigation(*trackingGeometry, position, direction, csv, i, 2,
0418 *logger->clone(std::nullopt, Logging::DEBUG));
0419 }
0420 }
0421
0422 BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
0423 Transform3 base{Transform3::Identity()};
0424
0425 Blueprint::Config cfg;
0426 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0427 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0428 auto root = std::make_unique<Blueprint>(cfg);
0429
0430 root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
0431 mat) {
0432 using enum AxisDirection;
0433 using enum AxisBoundaryType;
0434 using enum CuboidVolumeBounds::Face;
0435
0436
0437 mat.configureFace(NegativeXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
0438 mat.configureFace(PositiveXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
0439 mat.configureFace(NegativeYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0440 mat.configureFace(PositiveYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0441 mat.configureFace(NegativeZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0442 mat.configureFace(PositiveZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0443
0444 mat.addStaticVolume(
0445 base, std::make_shared<CuboidVolumeBounds>(100_mm, 100_mm, 100_mm),
0446 "TestVolume");
0447 });
0448
0449 auto trackingGeometry = root->construct({}, gctx, *logger);
0450 BOOST_REQUIRE(trackingGeometry);
0451 BOOST_CHECK(trackingGeometry->geometryVersion() ==
0452 TrackingGeometry::GeometryVersion::Gen3);
0453 }
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463
0464 BOOST_AUTO_TEST_CASE(MaterialOnMergedPortalThrows) {
0465 Transform3 base{Transform3::Identity()};
0466
0467 Blueprint::Config cfg;
0468 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0469 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0470 auto root = std::make_unique<Blueprint>(cfg);
0471
0472 root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
0473 using enum AxisDirection;
0474 using enum AxisBoundaryType;
0475 using enum CylinderVolumeBounds::Face;
0476
0477
0478
0479 stack.addMaterial("Material", [&](auto& mat) {
0480 mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20},
0481 {AxisZ, Bound, 20});
0482 mat.addStaticVolume(
0483 base * Translation3{Vector3{0, 0, -200_mm}},
0484 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0485 "VolumeA");
0486 });
0487
0488
0489 stack.addStaticVolume(
0490 base * Translation3{Vector3{0, 0, 200_mm}},
0491 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0492 "VolumeB");
0493 });
0494
0495
0496
0497 bool thrown = false;
0498 {
0499 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
0500 try {
0501 root->construct({}, gctx, *logger);
0502 } catch (const PortalMergingException& e) {
0503 thrown = true;
0504 std::string msg = e.what();
0505 BOOST_CHECK(msg.find("OuterCylinder") != std::string::npos);
0506 BOOST_CHECK(msg.find("VolumeA") != std::string::npos);
0507 BOOST_CHECK(msg.find("material") != std::string::npos);
0508 }
0509 }
0510 BOOST_CHECK(thrown);
0511 }
0512
0513 BOOST_AUTO_TEST_CASE(MaterialOnMergedPortalKeepGoing) {
0514
0515
0516
0517 Transform3 base{Transform3::Identity()};
0518
0519 Blueprint::Config cfg;
0520 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0521 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0522 auto root = std::make_unique<Blueprint>(cfg);
0523
0524 root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
0525 using enum AxisDirection;
0526 using enum AxisBoundaryType;
0527 using enum CylinderVolumeBounds::Face;
0528
0529 stack.addMaterial("Material", [&](auto& mat) {
0530 mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20},
0531 {AxisZ, Bound, 20});
0532 mat.addStaticVolume(
0533 base * Translation3{Vector3{0, 0, -200_mm}},
0534 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0535 "VolumeA");
0536 });
0537
0538 stack.addStaticVolume(
0539 base * Translation3{Vector3{0, 0, 200_mm}},
0540 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0541 "VolumeB");
0542 });
0543
0544 Experimental::BlueprintOptions options;
0545 options.keepGoingOnMaterialMergeFailure = true;
0546
0547 std::unique_ptr<const TrackingGeometry> trackingGeometry;
0548 {
0549 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
0550 BOOST_REQUIRE_NO_THROW(trackingGeometry =
0551 root->construct(options, gctx, *logger));
0552 }
0553 BOOST_REQUIRE(trackingGeometry != nullptr);
0554
0555 std::size_t markerCount = 0;
0556 trackingGeometry->visitSurfaces(
0557 [&](const Surface* surface) {
0558 if (surface != nullptr && surface->surfaceMaterial() != nullptr &&
0559 dynamic_cast<const MergedMaterialMarker*>(
0560 surface->surfaceMaterial()) != nullptr) {
0561 ++markerCount;
0562 }
0563 },
0564 false);
0565 BOOST_CHECK_GE(markerCount, 1u);
0566 }
0567
0568 BOOST_AUTO_TEST_CASE(MaterialOnMergedPortalKeepGoingSingleChildFalseWarning) {
0569
0570
0571
0572
0573 Transform3 base{Transform3::Identity()};
0574
0575 Blueprint::Config cfg;
0576 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0577 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0578 auto root = std::make_unique<Blueprint>(cfg);
0579
0580 root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
0581 using enum AxisDirection;
0582 using enum AxisBoundaryType;
0583 using enum CylinderVolumeBounds::Face;
0584
0585 stack.addMaterial("Material", [&](auto& mat) {
0586 mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20},
0587 {AxisZ, Bound, 20});
0588 mat.addStaticVolume(
0589 base, std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0590 "VolumeA");
0591 });
0592 });
0593
0594
0595
0596
0597 Experimental::BlueprintOptions options;
0598
0599 std::unique_ptr<const TrackingGeometry> trackingGeometry;
0600 BOOST_REQUIRE_NO_THROW(trackingGeometry =
0601 root->construct(options, gctx, *logger));
0602 BOOST_REQUIRE(trackingGeometry != nullptr);
0603
0604 std::size_t markerCount = 0;
0605 trackingGeometry->visitSurfaces(
0606 [&](const Surface* surface) {
0607 if (surface != nullptr && surface->surfaceMaterial() != nullptr &&
0608 dynamic_cast<const MergedMaterialMarker*>(
0609 surface->surfaceMaterial()) != nullptr) {
0610 ++markerCount;
0611 }
0612 },
0613 false);
0614 BOOST_CHECK_EQUAL(markerCount, 0u);
0615 }
0616
0617
0618
0619 BOOST_AUTO_TEST_CASE(PortalTagLookup) {
0620 using Experimental::PortalDesignatorBlueprintNode;
0621 Transform3 base{Transform3::Identity()};
0622
0623 Blueprint::Config cfg;
0624 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0625 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0626 auto root = std::make_unique<Blueprint>(cfg);
0627
0628 root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
0629 using enum CylinderVolumeBounds::Face;
0630
0631
0632 stack.addPortalDesignator("Tags", [&](auto& tags) {
0633 tags.tagFace(PositiveDisc, "tracker_calo_boundary");
0634 tags.addStaticVolume(
0635 base * Translation3{Vector3{0, 0, -200_mm}},
0636 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0637 "VolumeA");
0638 });
0639
0640 stack.addStaticVolume(
0641 base * Translation3{Vector3{0, 0, 200_mm}},
0642 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0643 "VolumeB");
0644 });
0645
0646 std::unique_ptr<const TrackingGeometry> trackingGeometry =
0647 root->construct({}, gctx, *logger);
0648 BOOST_REQUIRE(trackingGeometry != nullptr);
0649
0650 const Portal* portal = trackingGeometry->findPortal("tracker_calo_boundary");
0651 BOOST_REQUIRE(portal != nullptr);
0652
0653 BOOST_CHECK(trackingGeometry->findPortal("does_not_exist") == nullptr);
0654
0655
0656 auto containsPortal = [&](const TrackingVolume& volume) {
0657 for (const auto& p : volume.portals()) {
0658 if (&p == portal) {
0659 return true;
0660 }
0661 }
0662 return false;
0663 };
0664
0665 const TrackingVolume* volumeA = nullptr;
0666 const TrackingVolume* volumeB = nullptr;
0667 trackingGeometry->apply([&](const TrackingVolume& volume) {
0668 if (volume.volumeName() == "VolumeA") {
0669 volumeA = &volume;
0670 } else if (volume.volumeName() == "VolumeB") {
0671 volumeB = &volume;
0672 }
0673 });
0674
0675 BOOST_REQUIRE(volumeA != nullptr);
0676 BOOST_REQUIRE(volumeB != nullptr);
0677 BOOST_CHECK(containsPortal(*volumeA));
0678 BOOST_CHECK(containsPortal(*volumeB));
0679 }
0680
0681
0682
0683 BOOST_AUTO_TEST_CASE(PortalTagDuplicateThrows) {
0684 using Experimental::PortalDesignatorBlueprintNode;
0685 Transform3 base{Transform3::Identity()};
0686
0687 Blueprint::Config cfg;
0688 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0689 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0690 auto root = std::make_unique<Blueprint>(cfg);
0691
0692 root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
0693 using enum CylinderVolumeBounds::Face;
0694
0695
0696 stack.addPortalDesignator("TagsA", [&](auto& tags) {
0697 tags.tagFace(NegativeDisc, "dup");
0698 tags.addStaticVolume(
0699 base * Translation3{Vector3{0, 0, -200_mm}},
0700 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0701 "VolumeA");
0702 });
0703
0704
0705
0706 stack.addPortalDesignator("TagsB", [&](auto& tags) {
0707 tags.tagFace(PositiveDisc, "dup");
0708 tags.addStaticVolume(
0709 base * Translation3{Vector3{0, 0, 200_mm}},
0710 std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
0711 "VolumeB");
0712 });
0713 });
0714
0715 Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL};
0716 BOOST_CHECK_THROW(root->construct({}, gctx, *logger), std::invalid_argument);
0717 }
0718
0719
0720
0721 BOOST_AUTO_TEST_CASE(PortalTagLookupCuboid) {
0722 using Experimental::PortalDesignatorBlueprintNode;
0723 Transform3 base{Transform3::Identity()};
0724
0725 Blueprint::Config cfg;
0726 cfg.envelope[AxisDirection::AxisX] = {20_mm, 20_mm};
0727 cfg.envelope[AxisDirection::AxisY] = {20_mm, 20_mm};
0728 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0729 auto root = std::make_unique<Blueprint>(cfg);
0730
0731 root->addCuboidContainer("Stack", AxisDirection::AxisX, [&](auto& stack) {
0732 using enum CuboidVolumeBounds::Face;
0733
0734 stack.addPortalDesignator("Tags", [&](auto& tags) {
0735 tags.tagFace(PositiveXFace, "cuboid_boundary");
0736 tags.addStaticVolume(
0737 base * Translation3{Vector3{-200_mm, 0, 0}},
0738 std::make_shared<CuboidVolumeBounds>(100_mm, 100_mm, 100_mm),
0739 "VolumeA");
0740 });
0741
0742 stack.addStaticVolume(
0743 base * Translation3{Vector3{200_mm, 0, 0}},
0744 std::make_shared<CuboidVolumeBounds>(100_mm, 100_mm, 100_mm),
0745 "VolumeB");
0746 });
0747
0748 std::unique_ptr<const TrackingGeometry> trackingGeometry =
0749 root->construct({}, gctx, *logger);
0750 BOOST_REQUIRE(trackingGeometry != nullptr);
0751
0752 const Portal* portal = trackingGeometry->findPortal("cuboid_boundary");
0753 BOOST_REQUIRE(portal != nullptr);
0754
0755 auto containsPortal = [&](const TrackingVolume& volume) {
0756 for (const auto& p : volume.portals()) {
0757 if (&p == portal) {
0758 return true;
0759 }
0760 }
0761 return false;
0762 };
0763
0764 const TrackingVolume* volumeA = nullptr;
0765 const TrackingVolume* volumeB = nullptr;
0766 trackingGeometry->apply([&](const TrackingVolume& volume) {
0767 if (volume.volumeName() == "VolumeA") {
0768 volumeA = &volume;
0769 } else if (volume.volumeName() == "VolumeB") {
0770 volumeB = &volume;
0771 }
0772 });
0773
0774 BOOST_REQUIRE(volumeA != nullptr);
0775 BOOST_REQUIRE(volumeB != nullptr);
0776 BOOST_CHECK(containsPortal(*volumeA));
0777 BOOST_CHECK(containsPortal(*volumeB));
0778 }
0779
0780 BOOST_AUTO_TEST_SUITE_END();