Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-20 08:20:11

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/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 /// A sensitive Geant4 volume in global coordinates
0037 struct G4Sensitive {
0038   Acts::Vector3 center;
0039   Acts::Vector2 halfLengths;
0040 };
0041 
0042 /// Collect all silicon volumes of the tree, composing transforms the way
0043 /// `SensitiveSurfaceMapper` does
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     // Geant4 stores the rotation of the mother relative to the daughter
0055     // frame, hence the transpose
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 /// Check the whole tree for overlaps
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 }  // namespace
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   // The world is built once and cached
0114   BOOST_CHECK_EQUAL(construction->Construct(), world);
0115 
0116   BOOST_CHECK(!hasOverlaps(*world));
0117 }
0118 
0119 // The Geant4 sensors have to match the tracking geometry surfaces on all axes
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 // Unsorted positions and a stack that is not centered on the origin
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()