Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-17 07:34:43

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 "Acts/Geometry/Blueprint.hpp"
0010 
0011 #include "Acts/Geometry/CuboidPortalShell.hpp"
0012 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0013 #include "Acts/Geometry/CylinderPortalShell.hpp"
0014 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0015 #include "Acts/Geometry/Extent.hpp"
0016 #include "Acts/Geometry/GeometryIdentifier.hpp"
0017 #include "Acts/Geometry/PortalShell.hpp"
0018 #include "Acts/Geometry/VolumeBounds.hpp"
0019 #include "Acts/Geometry/detail/AlignablePortalVisitor.hpp"
0020 #include "Acts/Geometry/detail/BoundDeduplicator.hpp"
0021 #include "Acts/Navigation/INavigationPolicy.hpp"
0022 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0023 #include "Acts/Utilities/GraphViz.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025 
0026 #include <algorithm>
0027 
0028 namespace {
0029 const std::string s_rootName = "Root";
0030 }
0031 
0032 namespace Acts::Experimental {
0033 
0034 ///@class BlueprintVisitor
0035 /// A class for visiting blueprint hierarchy and apply the geometry identifiers
0036 class BlueprintVisitor : public TrackingGeometryMutableVisitor {
0037  public:
0038   explicit BlueprintVisitor(
0039       const Logger &logger,
0040       std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0041           &volumesById)
0042       : TrackingGeometryMutableVisitor(true),
0043         m_volumesById(volumesById),
0044         m_logger(logger) {}
0045 
0046   void visitVolume(TrackingVolume &volume) override {
0047     GeometryIdentifier::Value iportal = 0;
0048     GeometryIdentifier::Value isensitive = 0;
0049 
0050     auto id = volume.geometryId();
0051 
0052     if (id == GeometryIdentifier{}) {
0053       auto it = std::ranges::find(m_volumesById, nullptr);
0054       if (it == m_volumesById.end()) {
0055         ACTS_ERROR("No free volume IDs left, all " << m_volumesById.size()
0056                                                    << " are used");
0057         // @TODO: Maybe link to documentation about this
0058         throw std::logic_error("No free volume IDs left");
0059       }
0060 
0061       id = GeometryIdentifier().withVolume(
0062           std::distance(m_volumesById.begin(), it) + 1);
0063 
0064       ACTS_VERBOSE("Assigning volume ID " << id << " for "
0065                                           << volume.volumeName());
0066       volume.assignGeometryId(id);
0067       *it = &volume;
0068     }
0069 
0070     for (auto &portal : volume.portals()) {
0071       if (portal.surface().geometryId() != GeometryIdentifier{}) {
0072         continue;
0073       }
0074       iportal += 1;
0075       auto portalId = id.withBoundary(iportal);
0076       ACTS_VERBOSE("Assigning portal ID: " << portalId);
0077       portal.surface().assignGeometryId(portalId);
0078     }
0079     for (auto &surface : volume.surfaces()) {
0080       if (surface.geometryId() != GeometryIdentifier{}) {
0081         continue;
0082       }
0083       isensitive += 1;
0084       auto surfaceId = id.withSensitive(isensitive);
0085       ACTS_VERBOSE("Assigning surface ID: " << surfaceId);
0086       surface.assignGeometryId(surfaceId);
0087     }
0088   }
0089 
0090  private:
0091   std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0092       &m_volumesById;
0093   const Logger &m_logger;
0094   const Acts::Logger &logger() const { return m_logger; }
0095 };
0096 
0097 Blueprint::Blueprint(const Config &config) : m_cfg(config) {}
0098 
0099 const std::string &Blueprint::name() const {
0100   return s_rootName;
0101 }
0102 
0103 Volume &Blueprint::build(const BlueprintOptions & /*options*/,
0104                          const GeometryContext & /*gctx*/,
0105                          const Logger & /*logger*/) {
0106   throw std::logic_error("Root node cannot be built");
0107 }
0108 
0109 PortalShellBase &Blueprint::connect(const BlueprintOptions & /*options*/,
0110                                     const GeometryContext & /*gctx*/,
0111                                     const Logger & /*logger*/) {
0112   throw std::logic_error("Root node cannot be connected");
0113 }
0114 
0115 void Blueprint::finalize(const BlueprintOptions & /*options*/,
0116                          const GeometryContext & /*gctx*/,
0117                          TrackingVolume & /*parent*/,
0118                          const Logger & /*logger*/) {
0119   throw std::logic_error("Root node cannot be finalized");
0120 }
0121 
0122 void Blueprint::addToGraphviz(std::ostream &os) const {
0123   GraphViz::Node node{
0124       .id = name(), .label = "World", .shape = GraphViz::Shape::House};
0125 
0126   os << node;
0127   BlueprintNode::addToGraphviz(os);
0128 }
0129 
0130 std::unique_ptr<TrackingGeometry> Blueprint::construct(
0131     const BlueprintOptions &options, const GeometryContext &gctx,
0132     const Logger &logger) {
0133   using enum AxisDirection;
0134 
0135   ACTS_INFO(prefix() << "Building tracking geometry from blueprint tree");
0136 
0137   options.validate();
0138 
0139   if (m_cfg.envelope == ExtentEnvelope::Zero()) {
0140     ACTS_WARNING(prefix() << "Root node is configured with zero envelope. This "
0141                              "might lead to navigation issues");
0142   }
0143 
0144   if (children().size() != 1) {
0145     ACTS_ERROR(prefix() << "Root node must have exactly one child");
0146     throw std::logic_error("Root node must have exactly one child");
0147   }
0148 
0149   auto &child = children().at(0);
0150 
0151   ACTS_DEBUG(prefix() << "Executing building on tree");
0152   Volume &topVolume = child.build(options, gctx, logger);
0153   const auto &bounds = topVolume.volumeBounds();
0154 
0155   std::stringstream ss;
0156   bounds.toStream(ss);
0157   ACTS_DEBUG(prefix() << "have top volume: " << ss.str() << "\n"
0158                       << topVolume.localToGlobalTransform(gctx).matrix());
0159 
0160   std::unique_ptr<TrackingVolume> world;
0161   static const std::string worldName = "World";
0162 
0163   if (const auto *cyl = dynamic_cast<const CylinderVolumeBounds *>(&bounds);
0164       cyl != nullptr) {
0165     ACTS_VERBOSE(prefix() << "Expanding cylinder bounds");
0166     using enum CylinderVolumeBounds::BoundValues;
0167 
0168     // Make a copy that we'll modify
0169     auto newBounds = std::make_shared<CylinderVolumeBounds>(*cyl);
0170 
0171     const auto &zEnv = m_cfg.envelope[AxisZ];
0172     if (zEnv[0] != zEnv[1]) {
0173       ACTS_ERROR(
0174           prefix() << "Root node cylinder envelope for z must be symmetric");
0175       throw std::logic_error(
0176           "Root node cylinder envelope for z must be "
0177           "symmetric");
0178     }
0179 
0180     const auto &rEnv = m_cfg.envelope[AxisR];
0181 
0182     newBounds->set({
0183         {eHalfLengthZ, newBounds->get(eHalfLengthZ) + zEnv[0]},
0184         {eMinR, std::max(0.0, newBounds->get(eMinR) - rEnv[0])},
0185         {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
0186     });
0187 
0188     ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
0189                         << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
0190 
0191     world =
0192         std::make_unique<TrackingVolume>(topVolume.localToGlobalTransform(gctx),
0193                                          std::move(newBounds), worldName);
0194 
0195     // Need one-sided portal shell that connects outwards to nullptr
0196     SingleCylinderPortalShell worldShell{gctx, *world};
0197     worldShell.applyToVolume();
0198 
0199   } else if (const auto *box =
0200                  dynamic_cast<const CuboidVolumeBounds *>(&bounds);
0201              box != nullptr) {
0202     ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
0203     // Make a copy that we'll modify
0204     auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
0205 
0206     // Get the current half lengths
0207     double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
0208     double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
0209     double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
0210 
0211     // Apply envelope to each dimension
0212     const auto &xEnv = m_cfg.envelope[AxisX];
0213     const auto &yEnv = m_cfg.envelope[AxisY];
0214     const auto &zEnv = m_cfg.envelope[AxisZ];
0215 
0216     // Check if envelopes are symmetric for all dimensions
0217     if (xEnv[0] != xEnv[1]) {
0218       ACTS_ERROR(
0219           prefix() << "Root node cuboid envelope for X must be symmetric");
0220       throw std::logic_error(
0221           "Root node cuboid envelope for X must be symmetric");
0222     }
0223 
0224     if (yEnv[0] != yEnv[1]) {
0225       ACTS_ERROR(
0226           prefix() << "Root node cuboid envelope for Y must be symmetric");
0227       throw std::logic_error(
0228           "Root node cuboid envelope for Y must be symmetric");
0229     }
0230 
0231     if (zEnv[0] != zEnv[1]) {
0232       ACTS_ERROR(
0233           prefix() << "Root node cuboid envelope for Z must be symmetric");
0234       throw std::logic_error(
0235           "Root node cuboid envelope for Z must be symmetric");
0236     }
0237 
0238     newBounds->set({
0239         {CuboidVolumeBounds::eHalfLengthX, halfX + xEnv[0]},
0240         {CuboidVolumeBounds::eHalfLengthY, halfY + yEnv[0]},
0241         {CuboidVolumeBounds::eHalfLengthZ, halfZ + zEnv[0]},
0242     });
0243 
0244     ACTS_DEBUG(prefix() << "Applied envelope to cuboid: X=" << xEnv[0]
0245                         << ", Y=" << yEnv[0] << ", Z=" << zEnv[0]);
0246 
0247     world =
0248         std::make_unique<TrackingVolume>(topVolume.localToGlobalTransform(gctx),
0249                                          std::move(newBounds), worldName);
0250 
0251     // Need one-sided portal shell that connects outwards to nullptr
0252     SingleCuboidPortalShell worldShell{gctx, *world};
0253     worldShell.applyToVolume();
0254 
0255   } else {
0256     throw std::logic_error{"Unsupported volume bounds type"};
0257   }
0258 
0259   ACTS_DEBUG(prefix() << "New root volume bounds are: "
0260                       << world->volumeBounds());
0261 
0262   world->setNavigationPolicy(std::make_unique<Acts::TryAllNavigationPolicy>(
0263       gctx, *world, logger, Acts::TryAllNavigationPolicy::Config{}));
0264 
0265   auto &shell = child.connect(options, gctx, logger);
0266 
0267   // Composite of trivial will not be converted to grid like this
0268   // Performance impact should be negligible since it's a rare case, but might
0269   // want to change
0270   shell.fill(*world);
0271 
0272   if (m_cfg.boundDeduplication) {
0273     ACTS_DEBUG("Deduplicate equivalent bounds");
0274     detail::BoundDeduplicator deduplicator{};
0275     world->apply(deduplicator);
0276   }
0277   child.finalize(options, gctx, *world, logger);
0278 
0279   std::set<std::string, std::less<>> volumeNames;
0280   std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0281       volumesById{};
0282   volumesById.fill(nullptr);
0283 
0284   // @TODO: Take this from GeometryIdentifier instead of hard-coding
0285 
0286   world->apply([&, this](TrackingVolume &volume) {
0287     if (volumeNames.contains(volume.volumeName())) {
0288       ACTS_ERROR(prefix() << "Duplicate volume name: " << volume.volumeName());
0289       throw std::logic_error("Duplicate volume name");
0290     }
0291     volumeNames.insert(volume.volumeName());
0292 
0293     if (volume.geometryId() != GeometryIdentifier{}) {
0294       // We can have multiple volumes with the same volume ID component, but
0295       // they should differ in other components like "layer"
0296       if (volumesById.at(volume.geometryId().volume() - 1) == nullptr) {
0297         volumesById.at(volume.geometryId().volume() - 1) = &volume;
0298       }
0299     }
0300 
0301     // Clear boundary surfaces!
0302     volume.clearBoundarySurfaces();
0303   });
0304 
0305   std::size_t unusedVolumeIds = std::ranges::count(volumesById, nullptr);
0306   ACTS_DEBUG(prefix() << "Number of unused volume IDs: " << unusedVolumeIds);
0307 
0308   ACTS_DEBUG(prefix() << "Assigning volume IDs for remaining volumes");
0309 
0310   BlueprintVisitor visitor{logger, volumesById};
0311   world->apply(visitor);
0312 
0313   Acts::detail::AlignablePortalVisitor alignPortals{gctx, logger};
0314   world->apply(alignPortals);
0315 
0316   return std::make_unique<TrackingGeometry>(
0317       std::move(world), nullptr, GeometryIdentifierHook{}, logger, false);
0318 }
0319 
0320 }  // namespace Acts::Experimental