Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:08:08

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 "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   // Blue print construction for the tracking geometry
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   // Helper lambda to configure a container
0058   auto configureContainer = [](CylinderContainerBlueprintNode& c) {
0059     c.setAttachmentStrategy(Acts::VolumeAttachmentStrategy::Gap);
0060     c.setResizeStrategy(Acts::VolumeResizeStrategy::Gap);
0061   };
0062 
0063   // Higher level container for the muon mockup detector, stacked in Z
0064   auto& cyl = root.addCylinderContainer("MuonMockupContainer",
0065                                         Acts::AxisDirection::AxisZ);
0066   configureContainer(cyl);
0067 
0068   // First level: one container for the central cylindrical part of the MS
0069   // (barrel plus NSWs) and two for the Big Wheels (A and C side). These
0070   // containers will be stacked in Z. The Central container will have internal
0071   // stacking of volumes in R, while the Endcaps containers will stack station
0072   // volumes in Z.
0073   std::array<CylinderContainerBlueprintNode*,
0074              Acts::toUnderlying(FirstContainerIdx::nFirstContainers)>
0075       FirstContainers{};
0076 
0077   // Second level: one container for the barrel and one for the two NSWs. These
0078   // containers are attached to the Central container and will be stacked in R.
0079   // The Barrel container will have internal stacking of station volumes in R,
0080   // while the NSWs container will stack them in Z.
0081   std::array<CylinderContainerBlueprintNode*,
0082              Acts::toUnderlying(SecondContainerIdx::nSecondContainers)>
0083       secondContainers{};
0084 
0085   // Helper lambda to retrieve the first-level container
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   // Helper lambda to retrieve the second-level container
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 = &centralContainer.addCylinderContainer(
0109           isBarrel ? "BarrelContainer" : "NSWsContainer",
0110           isBarrel ? Acts::AxisDirection::AxisR : Acts::AxisDirection::AxisZ);
0111       configureContainer(*container);
0112     }
0113     return container;
0114   };
0115 
0116   // Sorting the boxes by station
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     // Current station index
0125     StationIdx currentIdx = getStationIdx(*it);
0126     const bool isBarrel =
0127         (currentIdx == StationIdx ::BI || currentIdx == StationIdx ::BM ||
0128          currentIdx == StationIdx ::BO);
0129 
0130     // Find the range of boxes for the current station
0131     auto rangeEnd = std::find_if(it, boundingBoxes.end(),
0132                                  [this, currentIdx](const auto& box) {
0133                                    return getStationIdx(box) != currentIdx;
0134                                  });
0135 
0136     // Check we want to build this station
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     // Build GeometryIdentifier for this station
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     // Attach station node to the proper container
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   /** Assume a station paradigm. MDT multilayers and complementary strip
0181    * detectors are residing under a common parent node representing a muon
0182    * chamber envelope. The passed boxes are grouped under their parent.
0183    * We create a map mapping the logical volume of each parent to its
0184    * StaticBlueprintNode, which contains all its children*/
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       // We create a new chamber node for this parent
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       // update bounds
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     // We add the box to the parent node
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     // create static blueprint node for the inner volume and add it to the
0262     // chamber node
0263     chamberNode->addChild(std::make_shared<Node_t>(std::move(trVol)));
0264   }
0265   // Create a new station node with the attached cylinder volume
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   // create bluprint nodes for the chambers and add them as children to the
0282   // cylinder station node
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   // use dedicated builder for MDT multilayers
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     // add the sensitives in the constructed tracking volume
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     // Assume stationEta is the first number following an underscore in the
0406     // chamber name
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         // we found a digit right after underscore, so positive side
0412         return true;
0413       } else if (c == '-' && pos + 2 < name.size() &&
0414                  std::isdigit(name[pos + 2]) != 0) {
0415         // we found a negative digit right after underscore, so negative side
0416         return false;
0417       }
0418       pos = name.find('_', pos + 1);
0419     }
0420     throw std::runtime_error("No stationEta found in name: " + name);
0421   };
0422   // Handle the inner stations
0423   if (contains("SmallWheel")) {
0424     return isPositiveSide() ? StationIdx::EAI : StationIdx::ECI;
0425   } else if (contains("Inner")) {
0426     return StationIdx::BI;
0427   }
0428   // Handle TGCs
0429   if (contains("TGC")) {
0430     return isPositiveSide() ? StationIdx::EAM : StationIdx::ECM;
0431   }
0432   // Handle MDTs and RPCs
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 }  // namespace ActsExamples