File indexing completed on 2026-05-15 07:39:36
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/TrackingGeometry.hpp"
0023 #include "Acts/Geometry/TrackingVolume.hpp"
0024 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0025 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0026 #include "Acts/Navigation/INavigationPolicy.hpp"
0027 #include "Acts/Navigation/NavigationStream.hpp"
0028 #include "Acts/Surfaces/RectangleBounds.hpp"
0029 #include "Acts/Utilities/Logger.hpp"
0030 #include "Acts/Utilities/ProtoAxis.hpp"
0031 #include "Acts/Visualization/GeometryView3D.hpp"
0032 #include "Acts/Visualization/ObjVisualization3D.hpp"
0033 #include "ActsTests/CommonHelpers/DetectorElementStub.hpp"
0034
0035 #include <fstream>
0036 #include <random>
0037 #include <vector>
0038
0039 using namespace Acts;
0040 using namespace UnitLiterals;
0041
0042 using Experimental::Blueprint;
0043 using Experimental::LayerBlueprintNode;
0044 using Experimental::MaterialDesignatorBlueprintNode;
0045
0046 namespace ActsTests {
0047
0048 auto logger = getDefaultLogger("UnitTests", Logging::DEBUG);
0049
0050 auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0051
0052 inline std::vector<std::shared_ptr<Surface>> makeFanLayer(
0053 const Transform3& base,
0054 std::vector<std::unique_ptr<SurfacePlacementBase>>& elements,
0055 double r = 300_mm, std::size_t nSensors = 8, double thickness = 0) {
0056 auto recBounds = std::make_shared<RectangleBounds>(40_mm, 60_mm);
0057
0058 double deltaPhi = 2 * std::numbers::pi / nSensors;
0059 std::vector<std::shared_ptr<Surface>> surfaces;
0060 for (std::size_t i = 0; i < nSensors; i++) {
0061
0062
0063 Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} *
0064 Translation3(Vector3::UnitX() * r);
0065
0066 if (i % 2 == 0) {
0067 trf = trf * Translation3{Vector3::UnitZ() * 5_mm};
0068 }
0069
0070 auto& element = elements.emplace_back(
0071 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0072
0073 element->surface().assignSurfacePlacement(*element);
0074
0075 surfaces.push_back(element->surface().getSharedPtr());
0076 }
0077 return surfaces;
0078 }
0079
0080 inline std::vector<std::shared_ptr<Surface>> makeBarrelLayer(
0081 const Transform3& base,
0082 std::vector<std::unique_ptr<SurfacePlacementBase>>& elements,
0083 double r = 300_mm, std::size_t nStaves = 10, int nSensorsPerStave = 8,
0084 double thickness = 0, double hlPhi = 40_mm, double hlZ = 60_mm) {
0085 auto recBounds = std::make_shared<RectangleBounds>(hlPhi, hlZ);
0086
0087 double deltaPhi = 2 * std::numbers::pi / nStaves;
0088 std::vector<std::shared_ptr<Surface>> surfaces;
0089
0090 for (std::size_t istave = 0; istave < nStaves; istave++) {
0091 for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave;
0092 isensor++) {
0093 double z = isensor * (2 * hlZ + 5_mm);
0094
0095 Transform3 trf = base * Translation3(Vector3::UnitZ() * z) *
0096 AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} *
0097 Translation3(Vector3::UnitX() * r) *
0098 AngleAxis3{10_degree, Vector3::UnitZ()} *
0099 AngleAxis3{90_degree, Vector3::UnitY()} *
0100 AngleAxis3{90_degree, Vector3::UnitZ()};
0101 auto& element = elements.emplace_back(
0102 std::make_unique<DetectorElementStub>(trf, recBounds, thickness));
0103 element->surface().assignSurfacePlacement(*element);
0104 surfaces.push_back(element->surface().getSharedPtr());
0105 }
0106 }
0107
0108 return surfaces;
0109 }
0110
0111 }
0112
0113 using namespace ActsTests;
0114
0115 BOOST_AUTO_TEST_SUITE(GeometrySuite);
0116
0117 void pseudoNavigation(const TrackingGeometry& trackingGeometry,
0118 Vector3 position, const Vector3& direction,
0119 std::ostream& csv, std::size_t run,
0120 std::size_t substepsPerCm, const Logger& logger) {
0121 ACTS_VERBOSE("start navigation " << run);
0122 ACTS_VERBOSE("dir: " << direction.transpose());
0123 ACTS_VERBOSE(direction.norm());
0124
0125 std::mt19937 rng{static_cast<unsigned int>(run)};
0126 std::uniform_real_distribution<> dist{0.01, 0.99};
0127
0128 const auto* volume = trackingGeometry.lowestTrackingVolume(gctx, position);
0129 BOOST_REQUIRE_NE(volume, nullptr);
0130 ACTS_VERBOSE(volume->volumeName());
0131
0132 NavigationStream main;
0133 const TrackingVolume* currentVolume = volume;
0134
0135 csv << run << "," << position[0] << "," << position[1] << "," << position[2];
0136 csv << "," << volume->geometryId().volume();
0137 csv << "," << volume->geometryId().boundary();
0138 csv << "," << volume->geometryId().sensitive();
0139 csv << std::endl;
0140
0141 ACTS_VERBOSE("start pseudo navigation");
0142
0143 for (std::size_t i = 0; i < 100; i++) {
0144 main = NavigationStream{};
0145 AppendOnlyNavigationStream stream{main};
0146
0147 NavigationArguments navArgs{.position = position, .direction = direction};
0148 NavigationPolicyStateManager stateManager;
0149 currentVolume->navigationPolicy()->createState(gctx, navArgs, stateManager,
0150 logger);
0151 auto policyState = stateManager.currentState();
0152 currentVolume->initializeNavigationCandidates(gctx, navArgs, policyState,
0153 stream, logger);
0154
0155 ACTS_VERBOSE(main.candidates().size() << " candidates");
0156
0157 for (const auto& candidate : main.candidates()) {
0158 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0159 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0160 }
0161
0162 ACTS_VERBOSE("initializing candidates");
0163 main.initialize(gctx, {position, direction}, BoundaryTolerance::None());
0164
0165 ACTS_VERBOSE(main.candidates().size() << " candidates remaining");
0166
0167 for (const auto& candidate : main.candidates()) {
0168 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0169 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0170 }
0171
0172 if (main.currentCandidate().surface().isOnSurface(gctx, position,
0173 direction)) {
0174 ACTS_VERBOSE("Already on surface at initialization, skipping candidate");
0175
0176 auto id = main.currentCandidate().surface().geometryId();
0177 csv << run << "," << position[0] << "," << position[1] << ","
0178 << position[2];
0179 csv << "," << id.volume();
0180 csv << "," << id.boundary();
0181 csv << "," << id.sensitive();
0182 csv << std::endl;
0183 if (!main.switchToNextCandidate()) {
0184 ACTS_WARNING("candidates exhausted unexpectedly");
0185 break;
0186 }
0187 }
0188
0189 auto writeIntersection = [&](const Vector3& pos, const Surface& surface) {
0190 csv << run << "," << pos[0] << "," << pos[1] << "," << pos[2];
0191 csv << "," << surface.geometryId().volume();
0192 csv << "," << surface.geometryId().boundary();
0193 csv << "," << surface.geometryId().sensitive();
0194 csv << std::endl;
0195 };
0196
0197 bool terminated = false;
0198 while (main.remainingCandidates() > 0) {
0199 const auto& candidate = main.currentCandidate();
0200
0201 ACTS_VERBOSE(candidate.position().transpose());
0202
0203 ACTS_VERBOSE("moving to position: " << position.transpose() << " (r="
0204 << VectorHelpers::perp(position)
0205 << ")");
0206
0207 Vector3 delta = candidate.position() - position;
0208
0209 std::size_t substeps =
0210 std::max(1l, std::lround(delta.norm() / 10_cm * substepsPerCm));
0211
0212 for (std::size_t j = 0; j < substeps; j++) {
0213
0214 Vector3 subpos = position + dist(rng) * delta;
0215 csv << run << "," << subpos[0] << "," << subpos[1] << "," << subpos[2];
0216 csv << "," << currentVolume->geometryId().volume();
0217 csv << ",0,0";
0218 csv << std::endl;
0219 }
0220
0221 position = candidate.position();
0222 ACTS_VERBOSE(" -> "
0223 << position.transpose()
0224 << " (r=" << VectorHelpers::perp(position) << ")");
0225
0226 writeIntersection(position, candidate.surface());
0227
0228 if (candidate.isPortalTarget()) {
0229 ACTS_VERBOSE("On portal: " << candidate.surface().toStream(gctx));
0230 currentVolume =
0231 candidate.portal().resolveVolume(gctx, position, direction).value();
0232
0233 if (currentVolume == nullptr) {
0234 ACTS_VERBOSE("switched to nullptr -> we're done");
0235 terminated = true;
0236 }
0237 break;
0238
0239 } else {
0240 ACTS_VERBOSE("Not on portal");
0241 }
0242
0243 main.switchToNextCandidate();
0244 }
0245
0246 if (terminated) {
0247 ACTS_VERBOSE("Terminate pseudo navigation");
0248 break;
0249 }
0250
0251 ACTS_VERBOSE("switched to " << currentVolume->volumeName());
0252
0253 ACTS_VERBOSE("-----");
0254 }
0255 }
0256
0257 BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
0258
0259 Transform3 base{Transform3::Identity()};
0260
0261 std::vector<std::unique_ptr<SurfacePlacementBase>> detectorElements;
0262 auto makeFan = [&](const Transform3& layerBase, auto&&..., double r,
0263 std::size_t nSensors, double thickness) {
0264 return makeFanLayer(layerBase, detectorElements, r, nSensors, thickness);
0265 };
0266
0267 Blueprint::Config cfg;
0268 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0269 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0270 auto root = std::make_unique<Blueprint>(cfg);
0271
0272 root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
0273 mat) {
0274 using enum AxisDirection;
0275 using enum AxisBoundaryType;
0276 using enum CylinderVolumeBounds::Face;
0277
0278
0279 mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20}, {AxisZ, Bound, 20});
0280 mat.configureFace(NegativeDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
0281 mat.configureFace(PositiveDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
0282
0283 mat.addCylinderContainer("Detector", AxisDirection::AxisR, [&](auto& det) {
0284 det.addCylinderContainer("Pixel", AxisDirection::AxisZ, [&](auto& cyl) {
0285 cyl.setAttachmentStrategy(VolumeAttachmentStrategy::Gap)
0286 .setResizeStrategy(VolumeResizeStrategy::Gap);
0287
0288 cyl.addCylinderContainer(
0289 "PixelNegativeEndcap", AxisDirection::AxisZ, [&](auto& ec) {
0290 ec.setAttachmentStrategy(VolumeAttachmentStrategy::Gap);
0291
0292 auto makeLayer = [&](const Transform3& trf, auto& layer) {
0293 std::vector<std::shared_ptr<Surface>> surfaces;
0294 auto layerSurfaces = makeFan(trf, 300_mm, 10, 2_mm);
0295 std::copy(layerSurfaces.begin(), layerSurfaces.end(),
0296 std::back_inserter(surfaces));
0297 layerSurfaces = makeFan(trf, 500_mm, 16, 2_mm);
0298 std::copy(layerSurfaces.begin(), layerSurfaces.end(),
0299 std::back_inserter(surfaces));
0300
0301 layer.setSurfaces(surfaces)
0302 .setLayerType(LayerBlueprintNode::LayerType::Disc)
0303 .setEnvelope(ExtentEnvelope{{
0304 .z = {5_mm, 5_mm},
0305 .r = {10_mm, 20_mm},
0306 }})
0307 .setTransform(base);
0308 };
0309
0310 ec.addLayer("PixelNeg1", [&](auto& layer) {
0311 makeLayer(base * Translation3{Vector3{0, 0, -700_mm}}, layer);
0312 });
0313
0314 ec.addLayer("PixelNeg2", [&](auto& layer) {
0315 makeLayer(base * Translation3{Vector3{0, 0, -500_mm}}, layer);
0316 });
0317 });
0318
0319 cyl.addCylinderContainer(
0320 "PixelBarrel", AxisDirection::AxisR, [&](auto& brl) {
0321 brl.setAttachmentStrategy(VolumeAttachmentStrategy::Gap)
0322 .setResizeStrategy(VolumeResizeStrategy::Gap);
0323
0324 auto makeLayer = [&](const std::string& name, double r,
0325 std::size_t nStaves, int nSensorsPerStave) {
0326 brl.addLayer(name, [&](auto& layer) {
0327 std::vector<std::shared_ptr<Surface>> surfaces =
0328 makeBarrelLayer(base, detectorElements, r, nStaves,
0329 nSensorsPerStave, 2.5_mm, 10_mm, 20_mm);
0330
0331 layer.setSurfaces(surfaces)
0332 .setLayerType(LayerBlueprintNode::LayerType::Cylinder)
0333 .setEnvelope(ExtentEnvelope{{
0334 .z = {5_mm, 5_mm},
0335 .r = {1_mm, 1_mm},
0336 }})
0337 .setTransform(base);
0338 });
0339 };
0340
0341 makeLayer("PixelLayer0", 30_mm, 18, 5);
0342 makeLayer("PixelLayer1", 90_mm, 30, 6);
0343
0344 brl.addStaticVolume(base,
0345 std::make_shared<CylinderVolumeBounds>(
0346 100_mm, 110_mm, 250_mm),
0347 "PixelSupport");
0348
0349 makeLayer("PixelLayer2", 150_mm, 40, 7);
0350 makeLayer("PixelLayer3", 250_mm, 70, 8);
0351 });
0352
0353 auto& ec =
0354 cyl.addCylinderContainer("PixelPosWrapper", AxisDirection::AxisR);
0355 ec.setResizeStrategy(VolumeResizeStrategy::Gap);
0356 ec.addStaticVolume(std::make_unique<TrackingVolume>(
0357 base * Translation3{Vector3{0, 0, 600_mm}},
0358 std::make_shared<CylinderVolumeBounds>(150_mm, 390_mm, 200_mm),
0359 "PixelPositiveEndcap"));
0360 });
0361
0362 det.addStaticVolume(
0363 base, std::make_shared<CylinderVolumeBounds>(0_mm, 23_mm, 1000_mm),
0364 "BeamPipe");
0365 });
0366 });
0367
0368 std::ofstream dot{"api_test_container.dot"};
0369 root->graphviz(dot);
0370
0371 auto trackingGeometry = root->construct({}, gctx, *logger);
0372
0373 BOOST_REQUIRE(trackingGeometry);
0374 BOOST_CHECK(trackingGeometry->geometryVersion() ==
0375 TrackingGeometry::GeometryVersion::Gen3);
0376
0377 trackingGeometry->visitVolumes([&](const TrackingVolume* volume) {
0378 std::cout << volume->volumeName() << std::endl;
0379 std::cout << " -> id: " << volume->geometryId() << std::endl;
0380 std::cout << " -> " << volume->portals().size() << " portals" << std::endl;
0381 });
0382
0383 ObjVisualization3D vis;
0384
0385 trackingGeometry->visualize(vis, gctx, {}, {});
0386
0387 vis.write("api_test_container.obj");
0388
0389 Vector3 position = Vector3::Zero();
0390 std::ofstream csv{"api_test_container.csv"};
0391 csv << "x,y,z,volume,boundary,sensitive" << std::endl;
0392
0393 std::mt19937 rnd{42};
0394
0395 std::uniform_real_distribution<> dist{-1, 1};
0396
0397 double etaWidth = 3;
0398 double thetaMin = 2 * std::atan(std::exp(-etaWidth));
0399 double thetaMax = 2 * std::atan(std::exp(etaWidth));
0400 std::uniform_real_distribution<> thetaDist{thetaMin, thetaMax};
0401
0402 using namespace UnitLiterals;
0403
0404 for (std::size_t i = 0; i < 5000; i++) {
0405 double theta = thetaDist(rnd);
0406 double phi = 2 * std::numbers::pi * dist(rnd);
0407
0408 Vector3 direction;
0409 direction[0] = std::sin(theta) * std::cos(phi);
0410 direction[1] = std::sin(theta) * std::sin(phi);
0411 direction[2] = std::cos(theta);
0412
0413 pseudoNavigation(*trackingGeometry, position, direction, csv, i, 2,
0414 *logger->clone(std::nullopt, Logging::DEBUG));
0415 }
0416 }
0417
0418 BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
0419 Transform3 base{Transform3::Identity()};
0420
0421 Blueprint::Config cfg;
0422 cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
0423 cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
0424 auto root = std::make_unique<Blueprint>(cfg);
0425
0426 root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
0427 mat) {
0428 using enum AxisDirection;
0429 using enum AxisBoundaryType;
0430 using enum CuboidVolumeBounds::Face;
0431
0432
0433 mat.configureFace(NegativeXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
0434 mat.configureFace(PositiveXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
0435 mat.configureFace(NegativeYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0436 mat.configureFace(PositiveYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0437 mat.configureFace(NegativeZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0438 mat.configureFace(PositiveZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
0439
0440 mat.addStaticVolume(
0441 base, std::make_shared<CuboidVolumeBounds>(100_mm, 100_mm, 100_mm),
0442 "TestVolume");
0443 });
0444
0445 auto trackingGeometry = root->construct({}, gctx, *logger);
0446 BOOST_REQUIRE(trackingGeometry);
0447 BOOST_CHECK(trackingGeometry->geometryVersion() ==
0448 TrackingGeometry::GeometryVersion::Gen3);
0449 }
0450
0451 BOOST_AUTO_TEST_SUITE_END();