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