File indexing completed on 2026-07-16 08:08:08
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/GeoModelDetector/GeoModelMuonMockupBuilder.hpp"
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/Geometry/Blueprint.hpp"
0013 #include "Acts/Geometry/ContainerBlueprintNode.hpp"
0014 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0015 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0016 #include "Acts/Geometry/GeometryContext.hpp"
0017 #include "Acts/Geometry/MultiWireVolumeBuilder.hpp"
0018 #include "Acts/Geometry/TrackingVolume.hpp"
0019 #include "Acts/Geometry/TrapezoidVolumeBounds.hpp"
0020 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0021 #include "Acts/Geometry/VolumeBounds.hpp"
0022 #include "Acts/Geometry/detail/TrackingGeometryPrintVisitor.hpp"
0023 #include "Acts/Surfaces/LineBounds.hpp"
0024 #include "Acts/Utilities/AxisDefinitions.hpp"
0025 #include "Acts/Utilities/Helpers.hpp"
0026 #include "Acts/Utilities/MathHelpers.hpp"
0027 #include "Acts/Utilities/StringHelpers.hpp"
0028
0029 #include <format>
0030 #include <typeinfo>
0031 using namespace Acts::UnitLiterals;
0032 using namespace Acts::Experimental;
0033
0034 namespace ActsExamples {
0035
0036 GeoModelMuonMockupBuilder::GeoModelMuonMockupBuilder(
0037 const Config& cfg, std::unique_ptr<const Acts::Logger> logger)
0038 : m_cfg(cfg), m_logger(std::move(logger)) {}
0039
0040 std::unique_ptr<const Acts::TrackingGeometry>
0041 GeoModelMuonMockupBuilder::trackingGeometry(
0042 const Acts::GeometryContext& gctx) const {
0043 ConvertedVolList_t boundingBoxes = m_cfg.volumeBoxFPVs;
0044 if (boundingBoxes.empty()) {
0045 throw std::invalid_argument(
0046 "GeoModelMuonMockupBuilder() -- No converted bounding boxes in the "
0047 "configuration - provide volumes "
0048 "(e.g from the GeModelDetectorObjectFactory) ");
0049 }
0050
0051
0052 Acts::Experimental::Blueprint::Config bpCfg;
0053 bpCfg.envelope[Acts::AxisDirection::AxisZ] = {20_mm, 20_mm};
0054 bpCfg.envelope[Acts::AxisDirection::AxisR] = {5008.87000_mm, 12_mm};
0055 Acts::Experimental::Blueprint root{bpCfg};
0056
0057
0058 auto configureContainer = [](CylinderContainerBlueprintNode& c) {
0059 c.setAttachmentStrategy(Acts::VolumeAttachmentStrategy::Gap);
0060 c.setResizeStrategy(Acts::VolumeResizeStrategy::Gap);
0061 };
0062
0063
0064 auto& cyl = root.addCylinderContainer("MuonMockupContainer",
0065 Acts::AxisDirection::AxisZ);
0066 configureContainer(cyl);
0067
0068
0069
0070
0071
0072
0073 std::array<CylinderContainerBlueprintNode*,
0074 Acts::toUnderlying(FirstContainerIdx::nFirstContainers)>
0075 FirstContainers{};
0076
0077
0078
0079
0080
0081 std::array<CylinderContainerBlueprintNode*,
0082 Acts::toUnderlying(SecondContainerIdx::nSecondContainers)>
0083 secondContainers{};
0084
0085
0086 auto retrieveFirstContainer = [&cyl, &FirstContainers, &configureContainer](
0087 FirstContainerIdx containerIdx) {
0088 auto& container = FirstContainers[Acts::toUnderlying(containerIdx)];
0089 if (container == nullptr) {
0090 container =
0091 &cyl.addCylinderContainer(firstContainerIdxToString(containerIdx),
0092 (containerIdx == FirstContainerIdx::Central)
0093 ? Acts::AxisDirection::AxisR
0094 : Acts::AxisDirection::AxisZ);
0095 configureContainer(*container);
0096 }
0097 return container;
0098 };
0099
0100
0101 auto retrieveSecondContainer = [&retrieveFirstContainer, &secondContainers,
0102 &configureContainer](bool isBarrel) {
0103 auto& container = secondContainers[Acts::toUnderlying(
0104 isBarrel ? SecondContainerIdx::Barrel : SecondContainerIdx::NSWs)];
0105 if (container == nullptr) {
0106 auto& centralContainer =
0107 *retrieveFirstContainer(FirstContainerIdx::Central);
0108 container = ¢ralContainer.addCylinderContainer(
0109 isBarrel ? "BarrelContainer" : "NSWsContainer",
0110 isBarrel ? Acts::AxisDirection::AxisR : Acts::AxisDirection::AxisZ);
0111 configureContainer(*container);
0112 }
0113 return container;
0114 };
0115
0116
0117 std::ranges::sort(boundingBoxes, [this](const auto& a, const auto& b) {
0118 return getStationIdx(a) < getStationIdx(b);
0119 });
0120
0121 auto it = boundingBoxes.begin();
0122 std::size_t layerId = 1;
0123 while (it != boundingBoxes.end()) {
0124
0125 StationIdx currentIdx = getStationIdx(*it);
0126 const bool isBarrel =
0127 (currentIdx == StationIdx ::BI || currentIdx == StationIdx ::BM ||
0128 currentIdx == StationIdx ::BO);
0129
0130
0131 auto rangeEnd = std::find_if(it, boundingBoxes.end(),
0132 [this, currentIdx](const auto& box) {
0133 return getStationIdx(box) != currentIdx;
0134 });
0135
0136
0137 const std::string station = stationIdxToString(currentIdx);
0138 if (!Acts::rangeContainsValue(m_cfg.stationNames, station)) {
0139 ACTS_DEBUG("Skipping station "
0140 << station << " as not in the configured station names.");
0141 it = rangeEnd;
0142 continue;
0143 }
0144
0145 auto geoIdNode = Acts::GeometryIdentifier().withLayer(layerId++);
0146
0147 ACTS_DEBUG("Building nodes for station " << station << " with geoId "
0148 << geoIdNode);
0149 auto stationNode = processStation(
0150 gctx, std::span(std::to_address(it), std::distance(it, rangeEnd)),
0151 station, isBarrel, *m_cfg.volumeBoundFactory, geoIdNode);
0152
0153
0154 const FirstContainerIdx firstContIdx = getFirstContainerIdx(currentIdx);
0155 CylinderContainerBlueprintNode* targetContainer =
0156 firstContIdx == FirstContainerIdx::Central
0157 ? retrieveSecondContainer(isBarrel)
0158 : retrieveFirstContainer(firstContIdx);
0159 targetContainer->addChild(std::move(stationNode));
0160
0161 it = rangeEnd;
0162 }
0163 auto trackingGeometry = root.construct({}, gctx, *m_logger);
0164 if (logger().doPrint(Acts::Logging::Level::DEBUG)) {
0165 Acts::detail::TrackingGeometryPrintVisitor trkGeoPrinter{gctx};
0166 trackingGeometry->apply(trkGeoPrinter);
0167 ACTS_DEBUG(std::endl << trkGeoPrinter.stream().str());
0168 }
0169 return trackingGeometry;
0170 }
0171 GeoModelMuonMockupBuilder::NodePtr_t GeoModelMuonMockupBuilder::processStation(
0172 const Acts::GeometryContext& gctx, const std::span<Box_t> boundingBoxes,
0173 const std::string& station, const bool isBarrel,
0174 Acts::VolumeBoundFactory& boundFactory,
0175 const Acts::GeometryIdentifier& geoId) const {
0176 if (boundingBoxes.empty()) {
0177 ACTS_DEBUG("No chambers could be found for station" << station);
0178 return {};
0179 }
0180
0181
0182
0183
0184
0185 std::map<const GeoVPhysVol*, std::pair<NodePtr_t, std::size_t>>
0186 chamberVolumes{};
0187 cylBounds bounds{};
0188 std::size_t volNum{1};
0189 for (const auto& box : boundingBoxes) {
0190 const GeoVPhysVol* parent = box.fullPhysVol->getParent();
0191 if (parent == nullptr) {
0192 throw std::domain_error(std::format(
0193 "processStation() No parent found for chamber {} in station {}",
0194 box.name, station));
0195 }
0196
0197 auto it = chamberVolumes.find(parent);
0198 if (it == chamberVolumes.end()) {
0199
0200 std::shared_ptr<Acts::Volume> parentVolume =
0201 ActsPlugins::GeoModel::convertVolume(
0202 ActsPlugins::GeoModel::volumePosInSpace(parent),
0203 parent->getLogVol()->getShape(), boundFactory);
0204
0205 auto chamberVolume = std::make_unique<Acts::TrackingVolume>(
0206 *parentVolume, std::format("{:}_Chamber_{:d}", station, volNum));
0207 chamberVolume->assignGeometryId(geoId.withVolume(volNum));
0208
0209 ACTS_VERBOSE("New parent: "
0210 << chamberVolume->volumeName() << " from box: " << box.name
0211 << ", Id: " << chamberVolume->geometryId() << ", center: "
0212 << Acts::toString(chamberVolume->center(gctx))
0213 << ", bounds: " << chamberVolume->volumeBounds());
0214
0215
0216 if (isBarrel) {
0217 if (chamberVolume->volumeBounds().type() !=
0218 Acts::VolumeBounds::eCuboid) {
0219 throw std::runtime_error(std::format(
0220 "processStation() -- Barrel chamber {} has bound type {} instead "
0221 "of Cuboid",
0222 chamberVolume->volumeName(),
0223 static_cast<int>(chamberVolume->volumeBounds().type())));
0224 }
0225 updateBounds<Acts::VolumeBounds::eCuboid>(gctx, *chamberVolume, bounds);
0226 } else {
0227 if (chamberVolume->volumeBounds().type() !=
0228 Acts::VolumeBounds::eTrapezoid) {
0229 throw std::runtime_error(std::format(
0230 "processStation() -- Endcap chamber {} has bound type {} instead "
0231 "of Trapezoid",
0232 chamberVolume->volumeName(),
0233 static_cast<int>(chamberVolume->volumeBounds().type())));
0234 }
0235 updateBounds<Acts::VolumeBounds::eTrapezoid>(gctx, *chamberVolume,
0236 bounds);
0237 }
0238
0239 it =
0240 chamberVolumes
0241 .try_emplace(parent, std::make_pair(std::make_shared<Node_t>(
0242 std::move(chamberVolume)),
0243 volNum++))
0244 .first;
0245 }
0246
0247 NodePtr_t& chamberNode = it->second.first;
0248 if (!chamberNode) {
0249 throw std::logic_error(std::format(
0250 "processStation() -- Found null chamber node for parent {}",
0251 parent->getLogVol()->getName()));
0252 }
0253 auto trVol = buildChildChamber(gctx, box, boundFactory);
0254 trVol->assignGeometryId(geoId.withVolume(it->second.second)
0255 .withExtra(chamberNode->children().size() + 1));
0256
0257 ACTS_VERBOSE("\t\t Added child: " << trVol->volumeName() << ", "
0258 << trVol->geometryId()
0259 << " to Parent: " << chamberNode->name());
0260
0261
0262
0263 chamberNode->addChild(std::make_shared<Node_t>(std::move(trVol)));
0264 }
0265
0266 const double translationZ =
0267 isBarrel ? 0.0 : 0.5 * (bounds.zMax + bounds.zMin);
0268 const double cylHalfLenght =
0269 isBarrel ? bounds.zMax : 0.5 * (bounds.zMax - bounds.zMin);
0270 auto stationNode =
0271 std::make_shared<Node_t>(std::make_unique<Acts::TrackingVolume>(
0272 Acts::Transform3{Acts::Translation3(0., 0., translationZ)},
0273 boundFactory.makeBounds<Acts::CylinderVolumeBounds>(
0274 bounds.rMin, bounds.rMax, cylHalfLenght),
0275 std::format("{:}_StationVol", station)));
0276 ACTS_DEBUG("Created station volume: "
0277 << stationNode->name() << " with bounds r: " << bounds.rMin
0278 << " - " << bounds.rMax << ", z: " << translationZ << " pm "
0279 << cylHalfLenght);
0280
0281
0282
0283 for (auto& [_, entry] : chamberVolumes) {
0284 stationNode->addChild(std::move(entry.first));
0285 }
0286
0287 return stationNode;
0288 }
0289
0290 std::unique_ptr<Acts::TrackingVolume>
0291 GeoModelMuonMockupBuilder::buildChildChamber(
0292 const Acts::GeometryContext& gctx, const Box_t& box,
0293 Acts::VolumeBoundFactory& boundFactory) const {
0294 std::unique_ptr<Acts::TrackingVolume> trVol{nullptr};
0295
0296
0297 if (box.name.find("MDT") != std::string::npos) {
0298 MultiWireVolumeBuilder::Config mwCfg;
0299 auto vb = box.volume->volumeBoundsPtr();
0300 double halfY{0}, halfZ{0};
0301 using LineBounds = Acts::LineBounds::BoundValues;
0302
0303 if (vb->type() == Acts::VolumeBounds::eTrapezoid) {
0304 using BoundVal = Acts::TrapezoidVolumeBounds::BoundValues;
0305
0306 auto tzb = std::dynamic_pointer_cast<Acts::TrapezoidVolumeBounds>(vb);
0307 mwCfg.bounds = boundFactory.insert(tzb);
0308 halfY = tzb->get(BoundVal::eHalfLengthY);
0309 halfZ = tzb->get(BoundVal::eHalfLengthZ);
0310
0311 } else if (vb->type() == Acts::VolumeBounds::eCuboid) {
0312 using BoundVal = Acts::CuboidVolumeBounds::BoundValues;
0313
0314 auto cbb = std::dynamic_pointer_cast<Acts::CuboidVolumeBounds>(vb);
0315 mwCfg.bounds = boundFactory.insert(cbb);
0316 halfY = cbb->get(BoundVal::eHalfLengthY);
0317 halfZ = cbb->get(BoundVal::eHalfLengthZ);
0318
0319 } else {
0320 throw std::runtime_error(
0321 "GeoModelMuonMockupBuilder::buildBarrelNode() - Not a trapezoid "
0322 "or cuboid volume bounds");
0323 }
0324
0325 mwCfg.name = box.name;
0326 mwCfg.mlSurfaces = box.surfaces;
0327 mwCfg.transform = box.volume->localToGlobalTransform(gctx);
0328 auto& sb = box.surfaces.front()->bounds();
0329 auto lineBounds = dynamic_cast<const Acts::LineBounds*>(&sb);
0330 if (lineBounds == nullptr) {
0331 throw std::runtime_error(
0332 "This MDT does not have tubes, what does it have?");
0333 }
0334 double tubeR = lineBounds->get(LineBounds::eR);
0335 mwCfg.binning = {
0336 {{Acts::AxisDirection::AxisY, Acts::AxisBoundaryType::Bound, -halfY,
0337 halfY, static_cast<std::size_t>(std::lround(1. * halfY / tubeR))},
0338 2},
0339 {{Acts::AxisDirection::AxisZ, Acts::AxisBoundaryType::Bound, -halfZ,
0340 halfZ, static_cast<std::size_t>(std::lround(1. * halfZ / tubeR))},
0341 1}};
0342
0343 MultiWireVolumeBuilder mdtBuilder{mwCfg};
0344 trVol = mdtBuilder.buildVolume();
0345
0346 } else {
0347 trVol = std::make_unique<Acts::TrackingVolume>(*box.volume, box.name);
0348
0349
0350 for (const auto& surface : box.surfaces) {
0351 trVol->addSurface(surface);
0352 }
0353 }
0354 return trVol;
0355 }
0356 template <Acts::VolumeBounds::BoundsType VolBounds_t>
0357 void GeoModelMuonMockupBuilder::updateBounds(const Acts::GeometryContext& gctx,
0358 const Acts::TrackingVolume& volume,
0359 cylBounds& bounds) const {
0360 const Acts::Vector3& center{volume.center(gctx)};
0361 const double rCenter{Acts::fastHypot(center.x(), center.y())};
0362 const auto volBounds{volume.volumeBounds().values()};
0363 double rMin{0.0};
0364 double rMax{0.0};
0365 double zMin{0.0};
0366 double zMax{0.0};
0367
0368 if constexpr (VolBounds_t == Acts::VolumeBounds::eTrapezoid) {
0369 using enum Acts::TrapezoidVolumeBounds::BoundValues;
0370 rMin = rCenter - volBounds[eHalfLengthY];
0371 rMax = Acts::fastHypot(rCenter + volBounds[eHalfLengthY],
0372 volBounds[eHalfLengthXposY]);
0373 zMin = center.z() - volBounds[eHalfLengthZ];
0374 zMax = center.z() + volBounds[eHalfLengthZ];
0375 } else if constexpr (VolBounds_t == Acts::VolumeBounds::eCuboid) {
0376 using enum Acts::CuboidVolumeBounds::BoundValues;
0377 rMin = rCenter - volBounds[eHalfLengthZ];
0378 rMax = Acts::fastHypot(rCenter + volBounds[eHalfLengthZ],
0379 volBounds[eHalfLengthX]);
0380 zMax = Acts::abs(center.z()) + volBounds[eHalfLengthY];
0381 } else {
0382 static_assert(VolBounds_t == Acts::VolumeBounds::eTrapezoid ||
0383 VolBounds_t == Acts::VolumeBounds::eCuboid,
0384 "Unsupported volume bounds type in cylBounds::update");
0385 }
0386
0387 ACTS_VERBOSE("Computed cylindrical bounds: "
0388 << "r: " << rMin << ", " << rMax << " "
0389 << "z: " << zMin << ", " << zMax);
0390
0391 bounds.rMin = std::min(bounds.rMin, rMin);
0392 bounds.rMax = std::max(bounds.rMax, rMax);
0393 bounds.zMin = std::min(bounds.zMin, zMin);
0394 bounds.zMax = std::max(bounds.zMax, zMax);
0395 }
0396
0397 GeoModelMuonMockupBuilder::StationIdx GeoModelMuonMockupBuilder::getStationIdx(
0398 const Box_t& box) const {
0399 const auto& name = box.name;
0400
0401 auto contains = [&name](std::string_view key) {
0402 return name.find(key) != std::string::npos;
0403 };
0404 auto isPositiveSide = [&name]() {
0405
0406
0407 std::size_t pos = name.find('_');
0408 while (pos != std::string::npos && pos + 1 < name.size()) {
0409 const char c = name[pos + 1];
0410 if (std::isdigit(c) != 0) {
0411
0412 return true;
0413 } else if (c == '-' && pos + 2 < name.size() &&
0414 std::isdigit(name[pos + 2]) != 0) {
0415
0416 return false;
0417 }
0418 pos = name.find('_', pos + 1);
0419 }
0420 throw std::runtime_error("No stationEta found in name: " + name);
0421 };
0422
0423 if (contains("SmallWheel")) {
0424 return isPositiveSide() ? StationIdx::EAI : StationIdx::ECI;
0425 } else if (contains("Inner")) {
0426 return StationIdx::BI;
0427 }
0428
0429 if (contains("TGC")) {
0430 return isPositiveSide() ? StationIdx::EAM : StationIdx::ECM;
0431 }
0432
0433 const bool isBarrel = contains("BMDT") || contains("RPC");
0434 if (contains("Middle")) {
0435 return isBarrel ? StationIdx::BM
0436 : (isPositiveSide() ? StationIdx::EAM : StationIdx::ECM);
0437 } else if (contains("Outer")) {
0438 return isBarrel ? StationIdx::BO
0439 : (isPositiveSide() ? StationIdx::EAO : StationIdx::ECO);
0440 } else {
0441 throw std::domain_error(
0442 "getStationIdx() -- Could not deduce station idx from volume name: " +
0443 name);
0444 }
0445 }
0446
0447 GeoModelMuonMockupBuilder::FirstContainerIdx
0448 GeoModelMuonMockupBuilder::getFirstContainerIdx(
0449 const StationIdx& stationIdx) const {
0450 if (stationIdx == StationIdx::EAM || stationIdx == StationIdx::EAO) {
0451 return FirstContainerIdx::BW_A;
0452 } else if (stationIdx == StationIdx::ECM || stationIdx == StationIdx::ECO) {
0453 return FirstContainerIdx::BW_C;
0454 } else {
0455 return FirstContainerIdx::Central;
0456 }
0457 }
0458
0459 std::string GeoModelMuonMockupBuilder::stationIdxToString(
0460 const GeoModelMuonMockupBuilder::StationIdx idx) {
0461 switch (idx) {
0462 case StationIdx::BI:
0463 return "BI";
0464 case StationIdx::BM:
0465 return "BM";
0466 case StationIdx::BO:
0467 return "BO";
0468 case StationIdx::EAI:
0469 return "EAI";
0470 case StationIdx::EAM:
0471 return "EAM";
0472 case StationIdx::EAO:
0473 return "EAO";
0474 case StationIdx::ECI:
0475 return "ECI";
0476 case StationIdx::ECM:
0477 return "ECM";
0478 case StationIdx::ECO:
0479 return "ECO";
0480 default:
0481 throw std::domain_error(
0482 "stationIdxToString() -- Unexpected StationIdx value");
0483 }
0484 }
0485
0486 std::string GeoModelMuonMockupBuilder::firstContainerIdxToString(
0487 const GeoModelMuonMockupBuilder::FirstContainerIdx idx) {
0488 switch (idx) {
0489 case FirstContainerIdx::Central:
0490 return "CentralContainer";
0491 case FirstContainerIdx::BW_A:
0492 return "BigWheelA_Container";
0493 case FirstContainerIdx::BW_C:
0494 return "BigWheelC_Container";
0495 default:
0496 throw std::domain_error(
0497 "firstContainerIdxToString() -- Unexpected FirstContainerIdx value");
0498 }
0499 }
0500 }