File indexing completed on 2025-09-18 08:12:36
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Geant4/SensitiveSurfaceMapper.hpp"
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/Surfaces/AnnulusBounds.hpp"
0013 #include "Acts/Surfaces/SurfaceArray.hpp"
0014 #include "Acts/Utilities/Helpers.hpp"
0015 #include "Acts/Visualization/GeometryView3D.hpp"
0016 #include "Acts/Visualization/ObjVisualization3D.hpp"
0017 #include "ActsExamples/Geant4/AlgebraConverters.hpp"
0018
0019 #include <algorithm>
0020 #include <ostream>
0021 #include <type_traits>
0022 #include <utility>
0023
0024 #include <G4LogicalVolume.hh>
0025 #include <G4Material.hh>
0026 #include <G4Polyhedron.hh>
0027 #include <G4VPhysicalVolume.hh>
0028 #include <G4VSolid.hh>
0029 #include <boost/container/flat_set.hpp>
0030 #include <boost/geometry.hpp>
0031
0032
0033
0034 namespace boost::geometry::traits {
0035
0036 template <typename T, int D>
0037 struct tag<Eigen::Matrix<T, D, 1>> {
0038 using type = point_tag;
0039 };
0040 template <typename T, int D>
0041 struct dimension<Eigen::Matrix<T, D, 1>> : std::integral_constant<int, D> {};
0042 template <typename T, int D>
0043 struct coordinate_type<Eigen::Matrix<T, D, 1>> {
0044 using type = T;
0045 };
0046 template <typename T, int D>
0047 struct coordinate_system<Eigen::Matrix<T, D, 1>> {
0048 using type = boost::geometry::cs::cartesian;
0049 };
0050
0051 template <typename T, int D, std::size_t Index>
0052 struct access<Eigen::Matrix<T, D, 1>, Index> {
0053 static_assert(Index < D, "Out of range");
0054 using Point = Eigen::Matrix<T, D, 1>;
0055 using CoordinateType = typename coordinate_type<Point>::type;
0056 static inline CoordinateType get(Point const& p) { return p[Index]; }
0057 static inline void set(Point& p, CoordinateType const& value) {
0058 p[Index] = value;
0059 }
0060 };
0061
0062 }
0063
0064 namespace {
0065
0066 void writeG4Polyhedron(
0067 Acts::IVisualization3D& visualizer, const G4Polyhedron& polyhedron,
0068 const Acts::Transform3& trafo = Acts::Transform3::Identity(),
0069 Acts::Color color = {0, 0, 0}) {
0070 constexpr double convertLength = CLHEP::mm / Acts::UnitConstants::mm;
0071
0072 for (int i = 1; i <= polyhedron.GetNoFacets(); ++i) {
0073
0074
0075 constexpr std::size_t maxPoints = 1000;
0076 G4Point3D points[maxPoints];
0077 int nPoints = 0;
0078 polyhedron.GetFacet(i, nPoints, points);
0079 assert(static_cast<std::size_t>(nPoints) < maxPoints);
0080
0081 std::vector<Acts::Vector3> faces;
0082 for (int j = 0; j < nPoints; ++j) {
0083 faces.emplace_back(points[j][0] * convertLength,
0084 points[j][1] * convertLength,
0085 points[j][2] * convertLength);
0086 faces.back() = trafo * faces.back();
0087 }
0088
0089 visualizer.face(faces, color);
0090 }
0091 }
0092 }
0093
0094 namespace ActsExamples::Geant4 {
0095
0096 SensitiveCandidates::SensitiveCandidates(
0097 const std::shared_ptr<const Acts::TrackingGeometry>& trackingGeometry,
0098 std::unique_ptr<const Acts::Logger> _logger)
0099 : m_trackingGeo{trackingGeometry}, m_logger{std::move(_logger)} {}
0100 std::vector<const Acts::Surface*> SensitiveCandidates::queryPosition(
0101 const Acts::GeometryContext& gctx, const Acts::Vector3& position) const {
0102 std::vector<const Acts::Surface*> surfaces{};
0103 ACTS_VERBOSE("Try to fetch the surfaces close to " << position.transpose());
0104
0105 switch (m_trackingGeo->geometryVersion()) {
0106 using enum Acts::TrackingGeometry::GeometryVersion;
0107 case Gen1: {
0108
0109 const auto layer = m_trackingGeo->associatedLayer(gctx, position);
0110 if (layer == nullptr) {
0111 return surfaces;
0112 }
0113
0114 const auto surfaceArray = layer->surfaceArray();
0115 if (surfaceArray == nullptr) {
0116 return surfaces;
0117 }
0118
0119 for (const auto& surface : surfaceArray->surfaces()) {
0120 if (surface->associatedDetectorElement() != nullptr) {
0121 surfaces.push_back(surface);
0122 }
0123 }
0124 break;
0125 }
0126 case Gen3: {
0127 const auto* refVolume =
0128 m_trackingGeo->lowestTrackingVolume(gctx, position);
0129 if (refVolume != nullptr) {
0130 constexpr bool restrictToSensitives = true;
0131 refVolume->visitSurfaces(
0132 [&](const Acts::Surface* surface) { surfaces.push_back(surface); },
0133 restrictToSensitives);
0134 }
0135 break;
0136 }
0137 }
0138 return surfaces;
0139 }
0140 std::vector<const Acts::Surface*> SensitiveCandidates::queryAll() const {
0141 std::vector<const Acts::Surface*> surfaces;
0142
0143 constexpr bool restrictToSensitives = true;
0144 m_trackingGeo->visitSurfaces(
0145 [&](auto surface) { surfaces.push_back(surface); }, restrictToSensitives);
0146
0147 return surfaces;
0148 }
0149
0150 SensitiveSurfaceMapper::SensitiveSurfaceMapper(
0151 const Config& cfg, std::unique_ptr<const Acts::Logger> logger)
0152 : m_cfg(cfg), m_logger(std::move(logger)) {}
0153
0154 void SensitiveSurfaceMapper::remapSensitiveNames(
0155 State& state, const Acts::GeometryContext& gctx,
0156 G4VPhysicalVolume* g4PhysicalVolume,
0157 const Acts::Transform3& motherTransform) const {
0158
0159
0160 auto g4LogicalVolume = g4PhysicalVolume->GetLogicalVolume();
0161 auto g4SensitiveDetector = g4LogicalVolume->GetSensitiveDetector();
0162
0163
0164 Acts::Transform3 localG4ToGlobal{Acts::Transform3::Identity()};
0165 {
0166 auto g4Translation = g4PhysicalVolume->GetTranslation();
0167 auto g4Rotation = g4PhysicalVolume->GetRotation();
0168 Acts::Vector3 g4RelPosition = convertPosition(g4Translation);
0169 Acts::Translation3 translation(g4RelPosition);
0170 if (g4Rotation == nullptr) {
0171 localG4ToGlobal = motherTransform * translation;
0172 } else {
0173 Acts::RotationMatrix3 rotation;
0174 rotation << g4Rotation->xx(), g4Rotation->yx(), g4Rotation->zx(),
0175 g4Rotation->xy(), g4Rotation->yy(), g4Rotation->zy(),
0176 g4Rotation->xz(), g4Rotation->yz(), g4Rotation->zz();
0177 localG4ToGlobal = motherTransform * (translation * rotation);
0178 }
0179 }
0180
0181 const Acts::Vector3 g4AbsPosition = localG4ToGlobal.translation();
0182
0183 if (G4int nDaughters = g4LogicalVolume->GetNoDaughters(); nDaughters > 0) {
0184
0185 for (G4int id = 0; id < nDaughters; ++id) {
0186 remapSensitiveNames(state, gctx, g4LogicalVolume->GetDaughter(id),
0187 localG4ToGlobal);
0188 }
0189 }
0190
0191 const std::string& volumeName{g4LogicalVolume->GetName()};
0192 const std::string& volumeMaterialName{
0193 g4LogicalVolume->GetMaterial()->GetName()};
0194
0195 const bool isSensitive = g4SensitiveDetector != nullptr;
0196 const bool isMappedMaterial =
0197 Acts::rangeContainsValue(m_cfg.materialMappings, volumeMaterialName);
0198 const bool isMappedVolume =
0199 Acts::rangeContainsValue(m_cfg.volumeMappings, volumeName);
0200
0201 if (!(isSensitive || isMappedMaterial || isMappedVolume)) {
0202 ACTS_VERBOSE("Did not try mapping '"
0203 << g4PhysicalVolume->GetName() << "' at "
0204 << g4AbsPosition.transpose()
0205 << " because g4SensitiveDetector (=" << g4SensitiveDetector
0206 << ") is null and volume name (=" << volumeName
0207 << ") and material name (=" << volumeMaterialName
0208 << ") were not found");
0209 return;
0210 }
0211 ACTS_VERBOSE("Attempt to map " << g4PhysicalVolume->GetName() << "' at "
0212 << g4AbsPosition.transpose()
0213 << " to the tracking geometry");
0214
0215
0216 const Acts::Surface* mappedSurface = nullptr;
0217
0218 std::vector<const Acts::Surface*> candidateSurfaces;
0219 const auto g4Polyhedron = g4LogicalVolume->GetSolid()->GetPolyhedron();
0220 for (int i = 1; i < g4Polyhedron->GetNoVertices(); ++i) {
0221 auto vtx = convertPosition(g4Polyhedron->GetVertex(i));
0222 auto vtxGlobal = localG4ToGlobal * vtx;
0223
0224 candidateSurfaces = m_cfg.candidateSurfaces->queryPosition(gctx, vtxGlobal);
0225
0226 if (!candidateSurfaces.empty()) {
0227 break;
0228 }
0229 }
0230
0231
0232 if (candidateSurfaces.empty()) {
0233 ACTS_DEBUG("No candidate surfaces for volume '" << volumeName << "' at "
0234 << g4AbsPosition.transpose()
0235 << ", query all surfaces");
0236 candidateSurfaces = m_cfg.candidateSurfaces->queryAll();
0237 }
0238
0239 ACTS_VERBOSE("Found " << candidateSurfaces.size()
0240 << " candidate surfaces for " << volumeName);
0241
0242 Acts::detail::TransformComparator trfSorter{};
0243 for (const auto& candidateSurface : candidateSurfaces) {
0244 if (trfSorter.compare<3>(candidateSurface->center(gctx), g4AbsPosition) ==
0245 0) {
0246 ACTS_DEBUG("Successful match with center: "
0247 << candidateSurface->center(gctx).transpose()
0248 << ", G4-position: " << g4AbsPosition.transpose());
0249 mappedSurface = candidateSurface;
0250 break;
0251 } else if (candidateSurface->bounds().type() ==
0252 Acts::SurfaceBounds::eAnnulus) {
0253 const auto& bounds =
0254 *static_cast<const Acts::AnnulusBounds*>(&candidateSurface->bounds());
0255
0256 const auto vertices = bounds.vertices(0);
0257
0258 constexpr bool clockwise = false;
0259 constexpr bool closed = false;
0260 using Polygon =
0261 boost::geometry::model::polygon<Acts::Vector2, clockwise, closed>;
0262
0263 Polygon poly;
0264 boost::geometry::assign_points(poly, vertices);
0265
0266 Acts::Vector2 boundsCentroidSurfaceFrame = Acts::Vector2::Zero();
0267 boost::geometry::centroid(poly, boundsCentroidSurfaceFrame);
0268
0269 Acts::Vector3 boundsCentroidGlobal{boundsCentroidSurfaceFrame[0],
0270 boundsCentroidSurfaceFrame[1], 0.0};
0271 boundsCentroidGlobal =
0272 candidateSurface->transform(gctx) * boundsCentroidGlobal;
0273
0274 const auto boundsCentroidG4Frame =
0275 localG4ToGlobal.inverse() * boundsCentroidGlobal;
0276
0277 if (g4LogicalVolume->GetSolid()->Inside(
0278 convertPosition(boundsCentroidG4Frame)) != EInside::kOutside) {
0279 ACTS_VERBOSE("Successful match with centroid matching");
0280 mappedSurface = candidateSurface;
0281 break;
0282 }
0283 }
0284 }
0285
0286 if (mappedSurface == nullptr) {
0287 ACTS_DEBUG("No mapping found for '"
0288 << volumeName << "' with material '" << volumeMaterialName
0289 << "' at position " << g4AbsPosition.transpose());
0290 state.missingVolumes.emplace_back(g4PhysicalVolume, localG4ToGlobal);
0291 return;
0292 }
0293
0294
0295 ACTS_DEBUG("Matched " << volumeName << " to " << mappedSurface->geometryId()
0296 << " at position " << g4AbsPosition.transpose());
0297
0298 if (volumeName.find(mappingPrefix) == std::string::npos) {
0299
0300 std::string mappedName = std::string(mappingPrefix) + volumeName;
0301 g4PhysicalVolume->SetName(mappedName);
0302 }
0303 if (state.g4VolumeToSurfaces.find(g4PhysicalVolume) ==
0304 state.g4VolumeToSurfaces.end()) {
0305 state.g4VolumeToSurfaces.insert(
0306 std::make_pair(g4PhysicalVolume, SurfacePosMap_t{trfSorter}));
0307 }
0308
0309 if (!state.g4VolumeToSurfaces[g4PhysicalVolume]
0310 .insert(std::make_pair(g4AbsPosition, mappedSurface))
0311 .second) {
0312 ACTS_WARNING("Duplicate surface found for " << volumeName << " @ "
0313 << g4AbsPosition.transpose());
0314 }
0315 }
0316
0317 bool SensitiveSurfaceMapper::checkMapping(
0318 const State& state, const Acts::GeometryContext& gctx,
0319 bool writeMissingG4VolsAsObj, bool writeMissingSurfacesAsObj) const {
0320 auto allSurfaces = m_cfg.candidateSurfaces->queryAll();
0321 std::ranges::sort(allSurfaces);
0322
0323 std::vector<const Acts::Surface*> found;
0324 for (const auto& [_, surfaceMap] : state.g4VolumeToSurfaces) {
0325 for (const auto& [__, surfacePtr] : surfaceMap) {
0326 found.push_back(surfacePtr);
0327 }
0328 }
0329 std::ranges::sort(found);
0330 auto newEnd = std::unique(found.begin(), found.end());
0331 found.erase(newEnd, found.end());
0332
0333 std::vector<const Acts::Surface*> missing;
0334 std::set_difference(allSurfaces.begin(), allSurfaces.end(), found.begin(),
0335 found.end(), std::back_inserter(missing));
0336
0337 ACTS_INFO("Number of overall sensitive surfaces: " << allSurfaces.size());
0338 ACTS_INFO("Number of mapped volume->surface mappings: " << found.size());
0339 ACTS_INFO(
0340 "Number of sensitive surfaces that are not mapped: " << missing.size());
0341 ACTS_INFO("Number of G4 volumes without a matching Surface: "
0342 << state.missingVolumes.size());
0343
0344 if (writeMissingG4VolsAsObj) {
0345 Acts::ObjVisualization3D visualizer;
0346 for (const auto& [g4vol, trafo] : state.missingVolumes) {
0347 auto polyhedron = g4vol->GetLogicalVolume()->GetSolid()->GetPolyhedron();
0348 writeG4Polyhedron(visualizer, *polyhedron, trafo);
0349 }
0350
0351 std::ofstream os("missing_g4_volumes.obj");
0352 visualizer.write(os);
0353 }
0354
0355 if (writeMissingSurfacesAsObj) {
0356 Acts::ObjVisualization3D visualizer;
0357 Acts::ViewConfig vcfg;
0358 vcfg.quarterSegments = 720;
0359 for (auto srf : missing) {
0360 Acts::GeometryView3D::drawSurface(visualizer, *srf, gctx,
0361 Acts::Transform3::Identity(), vcfg);
0362 }
0363
0364 std::ofstream os("missing_acts_surfaces.obj");
0365 visualizer.write(os);
0366 }
0367
0368 return missing.empty();
0369 }
0370
0371 }