File indexing completed on 2026-08-20 08:20:11
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/unit_test.hpp>
0011
0012 #include "Acts/Definitions/Algebra.hpp"
0013 #include "Acts/Geometry/GeometryContext.hpp"
0014 #include "Acts/Geometry/TrackingGeometry.hpp"
0015 #include "Acts/Surfaces/RectangleBounds.hpp"
0016 #include "Acts/Surfaces/Surface.hpp"
0017 #include "ActsExamples/Geant4/AlgebraConverters.hpp"
0018 #include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp"
0019 #include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp"
0020 #include "ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp"
0021
0022 #include <algorithm>
0023 #include <cstddef>
0024 #include <vector>
0025
0026 #include <G4Box.hh>
0027 #include <G4LogicalVolume.hh>
0028 #include <G4Material.hh>
0029 #include <G4RotationMatrix.hh>
0030 #include <G4VPhysicalVolume.hh>
0031
0032 using namespace ActsExamples;
0033
0034 namespace {
0035
0036
0037 struct G4Sensitive {
0038 Acts::Vector3 center;
0039 Acts::Vector2 halfLengths;
0040 };
0041
0042
0043
0044 void collectSensitives(const G4VPhysicalVolume& physicalVolume,
0045 const Acts::Transform3& motherTransform,
0046 std::vector<G4Sensitive>& sensitives) {
0047 const G4LogicalVolume& logicalVolume = *physicalVolume.GetLogicalVolume();
0048
0049 Acts::Transform3 localToGlobal =
0050 motherTransform * Acts::Translation3(Geant4::convertPosition(
0051 physicalVolume.GetTranslation()));
0052 if (const G4RotationMatrix* g4Rotation = physicalVolume.GetRotation();
0053 g4Rotation != nullptr) {
0054
0055
0056 Acts::RotationMatrix3 rotation;
0057 rotation << g4Rotation->xx(), g4Rotation->yx(), g4Rotation->zx(),
0058 g4Rotation->xy(), g4Rotation->yy(), g4Rotation->zy(), g4Rotation->xz(),
0059 g4Rotation->yz(), g4Rotation->zz();
0060 localToGlobal.rotate(rotation);
0061 }
0062
0063 if (logicalVolume.GetMaterial()->GetName() == "Silicon") {
0064 const auto& box = dynamic_cast<const G4Box&>(*logicalVolume.GetSolid());
0065 constexpr double convertLength = CLHEP::mm / Acts::UnitConstants::mm;
0066 sensitives.push_back({localToGlobal.translation(),
0067 {box.GetXHalfLength() * convertLength,
0068 box.GetYHalfLength() * convertLength}});
0069 }
0070
0071 for (std::size_t i = 0; i < logicalVolume.GetNoDaughters(); ++i) {
0072 collectSensitives(*logicalVolume.GetDaughter(i), localToGlobal, sensitives);
0073 }
0074 }
0075
0076
0077 bool hasOverlaps(G4VPhysicalVolume& physicalVolume) {
0078 constexpr G4int resolution = 1000;
0079 constexpr G4double tolerance = 0.;
0080 constexpr G4bool verbose = false;
0081 bool overlaps = physicalVolume.CheckOverlaps(resolution, tolerance, verbose);
0082
0083 G4LogicalVolume& logicalVolume = *physicalVolume.GetLogicalVolume();
0084 for (std::size_t i = 0; i < logicalVolume.GetNoDaughters(); ++i) {
0085 overlaps |= hasOverlaps(*logicalVolume.GetDaughter(i));
0086 }
0087 return overlaps;
0088 }
0089
0090 TelescopeDetector::Config makeConfig(int binValue) {
0091 TelescopeDetector::Config cfg;
0092 cfg.positions = {30, 60, 90, 120, 150, 180};
0093 cfg.stereos = {0, 0, 0, 0, 0, 0};
0094 cfg.offsets = {10, -20};
0095 cfg.bounds = {25, 100};
0096 cfg.binValue = binValue;
0097 return cfg;
0098 }
0099
0100 }
0101
0102 BOOST_AUTO_TEST_SUITE(TelescopeG4DetectorConstructionTests)
0103
0104 BOOST_AUTO_TEST_CASE(ConstructSmoke) {
0105 TelescopeDetector detector{TelescopeDetector::Config{}};
0106
0107 auto construction =
0108 detector.buildGeant4DetectorConstruction(Geant4ConstructionOptions{});
0109 BOOST_REQUIRE(construction != nullptr);
0110
0111 G4VPhysicalVolume* world = construction->Construct();
0112 BOOST_REQUIRE(world != nullptr);
0113
0114 BOOST_CHECK_EQUAL(construction->Construct(), world);
0115
0116 BOOST_CHECK(!hasOverlaps(*world));
0117 }
0118
0119
0120 BOOST_DATA_TEST_CASE(MatchesTrackingGeometry,
0121 boost::unit_test::data::make({0, 1, 2}), binValue) {
0122 const auto cfg = makeConfig(binValue);
0123 TelescopeDetector detector{cfg};
0124
0125 auto construction =
0126 detector.buildGeant4DetectorConstruction(Geant4ConstructionOptions{});
0127 G4VPhysicalVolume* world = construction->Construct();
0128 BOOST_REQUIRE(world != nullptr);
0129
0130 BOOST_CHECK(!hasOverlaps(*world));
0131
0132 std::vector<G4Sensitive> sensitives;
0133 collectSensitives(*world, Acts::Transform3::Identity(), sensitives);
0134 BOOST_REQUIRE_EQUAL(sensitives.size(), cfg.positions.size());
0135
0136 const auto& gctx = detector.nominalGeometryContext();
0137 std::vector<const Acts::Surface*> surfaces;
0138 detector.trackingGeometry()->visitSurfaces(
0139 [&](const Acts::Surface* surface) { surfaces.push_back(surface); }, true);
0140 BOOST_REQUIRE_EQUAL(surfaces.size(), cfg.positions.size());
0141
0142 for (const auto* surface : surfaces) {
0143 const Acts::Vector3 center = surface->center(gctx);
0144
0145 auto it = std::ranges::find_if(sensitives, [&](const G4Sensitive& g4) {
0146 return (g4.center - center).norm() < 1e-6;
0147 });
0148 BOOST_REQUIRE_MESSAGE(
0149 it != sensitives.end(),
0150 "no Geant4 volume at surface center " << center.transpose());
0151
0152 const auto& bounds =
0153 dynamic_cast<const Acts::RectangleBounds&>(surface->bounds());
0154 BOOST_CHECK_CLOSE(it->halfLengths[0], bounds.halfLengthX(), 1e-6);
0155 BOOST_CHECK_CLOSE(it->halfLengths[1], bounds.halfLengthY(), 1e-6);
0156 }
0157 }
0158
0159
0160 BOOST_AUTO_TEST_CASE(UnsortedAndShiftedPositions) {
0161 TelescopeDetector::Config cfg;
0162 cfg.positions = {-100, 200, 50};
0163 cfg.stereos = {0, 0, 0};
0164 cfg.offsets = {40, 40};
0165
0166 TelescopeDetector detector{cfg};
0167 auto construction =
0168 detector.buildGeant4DetectorConstruction(Geant4ConstructionOptions{});
0169 G4VPhysicalVolume* world = construction->Construct();
0170 BOOST_REQUIRE(world != nullptr);
0171
0172 BOOST_CHECK(!hasOverlaps(*world));
0173
0174 std::vector<G4Sensitive> sensitives;
0175 collectSensitives(*world, Acts::Transform3::Identity(), sensitives);
0176 BOOST_CHECK_EQUAL(sensitives.size(), cfg.positions.size());
0177 }
0178
0179 BOOST_AUTO_TEST_CASE(EmptyPositionsThrows) {
0180 TelescopeDetector::Config cfg;
0181 cfg.positions = {};
0182 cfg.stereos = {};
0183
0184 BOOST_CHECK_THROW(TelescopeDetector{cfg}, std::invalid_argument);
0185 }
0186
0187 BOOST_AUTO_TEST_SUITE_END()