diff --git a/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelMuonMockupBuilder.hpp b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelMuonMockupBuilder.hpp index c3cabf3799f..37b34ee9f8f 100644 --- a/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelMuonMockupBuilder.hpp +++ b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelMuonMockupBuilder.hpp @@ -53,12 +53,78 @@ class GeoModelMuonMockupBuilder : public Acts::ITrackingGeometryBuilder { const Acts::GeometryContext& gctx) const override; private: + // Enum class for the station indices + enum class StationIdx : std::uint8_t { + BI, // Inner Barrel + BM, // Middle Barrel + BO, // Outer Barrel + EAI, // Endcap Inner A-side + EAM, // Endcap Middle A-side + EAO, // Endcap Outer A-side + ECI, // Endcap Inner C-side + ECM, // Endcap Middle C-side + ECO, // Endcap Outer C-side + nStations + }; + // Enum class for the first level of container indices + enum class FirstContainerIdx : std::uint8_t { + Central, // Central container for barrel & NSWs + BW_A, // Big Wheel A-side + BW_C, // Big Wheel C-side + nFirstContainers + }; + // Enum class for the second level of container indices when the first is + // Central + enum class SecondContainerIdx : std::uint8_t { + Barrel, // Barrel container + NSWs, // NSW container + nSecondContainers + }; + Config m_cfg; - std::shared_ptr buildBarrelNode( - const ConvertedVolList_t& boundingBoxes, const std::string& name, - Acts::VolumeBoundFactory& boundFactory, - const Acts::GeometryIdentifier& geoId) const; + using Box_t = ConvertedVolList_t::value_type; + using Node_t = Acts::Experimental::StaticBlueprintNode; + using NodePtr_t = std::shared_ptr; + + /// @brief Produce a station node from the provided converted volume boxes + NodePtr_t processStation(const std::span boundingBoxes, + const std::string& station, const bool isBarrel, + Acts::VolumeBoundFactory& boundFactory, + const Acts::GeometryIdentifier& geoId) const; + + /// @brief Build a child chamber volume from the provided converted volume box + std::unique_ptr buildChildChamber( + const Box_t& box, Acts::VolumeBoundFactory& boundFactory) const; + + /// @brief Helper struct to store cylinder bounds, used to compute the overall bounds + /// of a station tracking volume from its component volumes + struct cylBounds { + /// @brief Lowest radius + double rMin{std::numeric_limits::max()}; + /// @brief Highest radius + double rMax{std::numeric_limits::lowest()}; + /// @brief Lowest longitudinal coordinate + double zMin{std::numeric_limits::max()}; + /// @brief Highest longitudinal coordinate + double zMax{std::numeric_limits::lowest()}; + }; + /// @brief Helper function to update the cylinder bounds of each station across its component volumes (chambers) + /// @tparam VolBounds_t: The bounds type of the chamber volume + /// @param volume: The chamber volume to extract the bounds from + /// @param bounds: The cylBounds object to be updated + template + void updateBounds(const Acts::TrackingVolume& volume, + cylBounds& bounds) const; + + // Helper function returning the station idx from a box volume + StationIdx getStationIdx(const Box_t& box) const; + // Helper function returning the first-level container idx from a station idx + FirstContainerIdx getFirstContainerIdx(const StationIdx& stationIdx) const; + // Helper function converting the station idx to string + static std::string stationIdxToString(const StationIdx idx); + // Helper function converting the first-level container idx to string + static std::string firstContainerIdxToString(const FirstContainerIdx idx); /// Private access method to the logger const Acts::Logger& logger() const { return *m_logger; } diff --git a/Examples/Detectors/GeoModelDetector/src/GeoModelMuonMockupBuilder.cpp b/Examples/Detectors/GeoModelDetector/src/GeoModelMuonMockupBuilder.cpp index fbfadf9a80e..a9569bbc452 100644 --- a/Examples/Detectors/GeoModelDetector/src/GeoModelMuonMockupBuilder.cpp +++ b/Examples/Detectors/GeoModelDetector/src/GeoModelMuonMockupBuilder.cpp @@ -22,6 +22,7 @@ #include "Acts/Geometry/detail/TrackingGeometryPrintVisitor.hpp" #include "Acts/Surfaces/LineBounds.hpp" #include "Acts/Utilities/AxisDefinitions.hpp" +#include "Acts/Utilities/Helpers.hpp" #include "Acts/Utilities/MathHelpers.hpp" #include @@ -39,211 +40,458 @@ std::unique_ptr GeoModelMuonMockupBuilder::trackingGeometry( const Acts::GeometryContext& gctx) const { ConvertedVolList_t boundingBoxes = m_cfg.volumeBoxFPVs; + if (boundingBoxes.empty()) { + throw std::invalid_argument( + "GeoModelMuonMockupBuilder() -- No converted bounding boxes in the " + "configuration - provide volumes " + "(e.g from the GeModelDetectorObjectFactory) "); + } // Blue print construction for the tracking geometry Acts::Experimental::Blueprint::Config bpCfg; bpCfg.envelope[Acts::AxisDirection::AxisZ] = {20_mm, 20_mm}; bpCfg.envelope[Acts::AxisDirection::AxisR] = {5008.87000_mm, 12_mm}; Acts::Experimental::Blueprint root{bpCfg}; - auto& cyl = root.addCylinderContainer("MuonMockupBarrelContainer", - Acts::AxisDirection::AxisR); - cyl.setAttachmentStrategy(Acts::VolumeAttachmentStrategy::Gap); - cyl.setResizeStrategy(Acts::VolumeResizeStrategy::Gap); - if (boundingBoxes.empty()) { - throw std::invalid_argument( - "GeoModelMuonMockupBuilder() -- No converted bounding boxes in the " - "configuration - provide volumes " - "(e.g from the GeModelDetectorObjectFactory) "); - } + // Helper lambda to configure a container + auto configureContainer = [](CylinderContainerBlueprintNode& c) { + c.setAttachmentStrategy(Acts::VolumeAttachmentStrategy::Gap); + c.setResizeStrategy(Acts::VolumeResizeStrategy::Gap); + }; + + // Higher level container for the muon mockup detector, stacked in Z + auto& cyl = root.addCylinderContainer("MuonMockupContainer", + Acts::AxisDirection::AxisZ); + configureContainer(cyl); + + // First level: one container for the central cylindrical part of the MS + // (barrel plus NSWs) and two for the Big Wheels (A and C side). These + // containers will be stacked in Z. The Central container will have internal + // stacking of volumes in R, while the Endcaps containers will stack station + // volumes in Z. + std::array + FirstContainers{}; + + // Second level: one container for the barrel and one for the two NSWs. These + // containers are attached to the Central container and will be stacked in R. + // The Barrel container will have internal stacking of station volumes in R, + // while the NSWs container will stack them in Z. + std::array + SecondContainers{}; + + // Helper lambda to retrieve the first-level container + auto retrieveFirstContainer = [&cyl, &FirstContainers, &configureContainer]( + FirstContainerIdx containerIdx) { + auto& container = FirstContainers[Acts::toUnderlying(containerIdx)]; + if (container == nullptr) { + container = + &cyl.addCylinderContainer(firstContainerIdxToString(containerIdx), + (containerIdx == FirstContainerIdx::Central) + ? Acts::AxisDirection::AxisR + : Acts::AxisDirection::AxisZ); + configureContainer(*container); + } + return container; + }; + + // Helper lambda to retrieve the second-level container + auto retrieveSecondContainer = [&retrieveFirstContainer, &SecondContainers, + &configureContainer](bool isBarrel) { + auto& container = SecondContainers[Acts::toUnderlying( + isBarrel ? SecondContainerIdx::Barrel : SecondContainerIdx::NSWs)]; + if (container == nullptr) { + auto& centralContainer = + *retrieveFirstContainer(FirstContainerIdx::Central); + container = ¢ralContainer.addCylinderContainer( + isBarrel ? "BarrelContainer" : "NSWsContainer", + isBarrel ? Acts::AxisDirection::AxisR : Acts::AxisDirection::AxisZ); + configureContainer(*container); + } + return container; + }; - // Add the station nodes as static cylidner nodes - std::size_t layerId = 1; + // Sorting the boxes by station + std::ranges::sort(boundingBoxes, [this](const auto& a, const auto& b) { + return getStationIdx(a) < getStationIdx(b); + }); - for (const auto& str : m_cfg.stationNames) { - auto geoIdNode = Acts::GeometryIdentifier().withLayer(layerId); - ++layerId; - auto node = buildBarrelNode(boundingBoxes, str, *m_cfg.volumeBoundFactory, - geoIdNode); - cyl.addChild(std::move(node)); + auto it = boundingBoxes.begin(); + std::size_t layerId = 1; + while (it != boundingBoxes.end()) { + // Current station index + StationIdx currentIdx = getStationIdx(*it); + const bool isBarrel = + (currentIdx == StationIdx ::BI || currentIdx == StationIdx ::BM || + currentIdx == StationIdx ::BO); + + // Find the range of boxes for the current station + auto rangeEnd = std::find_if(it, boundingBoxes.end(), + [this, currentIdx](const auto& box) { + return getStationIdx(box) != currentIdx; + }); + + // Check we want to build this station + const std::string station = stationIdxToString(currentIdx); + if (!Acts::rangeContainsValue(m_cfg.stationNames, station)) { + ACTS_DEBUG("Skipping station " + << station << " as not in the configured station names."); + it = rangeEnd; + continue; + } + // Build GeometryIdentifier for this station + auto geoIdNode = Acts::GeometryIdentifier().withLayer(layerId++); + + ACTS_DEBUG("Building nodes for station " << station << " with geoId " + << geoIdNode); + auto stationNode = + processStation(std::span(&*it, std::distance(it, rangeEnd)), station, + isBarrel, *m_cfg.volumeBoundFactory, geoIdNode); + + // Attach station node to the proper container + const FirstContainerIdx firstContIdx = getFirstContainerIdx(currentIdx); + CylinderContainerBlueprintNode* targetContainer = + firstContIdx == FirstContainerIdx::Central + ? retrieveSecondContainer(isBarrel) + : retrieveFirstContainer(firstContIdx); + targetContainer->addChild(std::move(stationNode)); + + it = rangeEnd; } - auto trackingGeometry = root.construct({}, gctx, *m_logger); if (logger().doPrint(Acts::Logging::Level::DEBUG)) { Acts::detail::TrackingGeometryPrintVisitor trkGeoPrinter{gctx}; trackingGeometry->apply(trkGeoPrinter); ACTS_DEBUG(std::endl << trkGeoPrinter.stream().str()); } - return trackingGeometry; } -std::shared_ptr -GeoModelMuonMockupBuilder::buildBarrelNode( - const ConvertedVolList_t& boundingBoxes, const std::string& name, - Acts::VolumeBoundFactory& boundFactory, +GeoModelMuonMockupBuilder::NodePtr_t GeoModelMuonMockupBuilder::processStation( + const std::span boundingBoxes, const std::string& station, + const bool isBarrel, Acts::VolumeBoundFactory& boundFactory, const Acts::GeometryIdentifier& geoId) const { - using enum Acts::CuboidVolumeBounds::BoundValues; - + if (boundingBoxes.empty()) { + ACTS_DEBUG("No chambers could be found for station" << station); + return {}; + } /** Assume a station paradigm. MDT multilayers and complementary strip * detectors are residing under a common parent node representing a muon - * station envelope. Group the passed boxes under by their parent */ - std::map commonStations{}; + * chamber envelope. The passed boxes are grouped under their parent. + * We create a map mapping the logical volume of each parent to its + * StaticBlueprintNode, which contains all its children*/ + std::map> + chamberVolumes{}; + cylBounds bounds{}; + std::size_t volNum{1}; for (const auto& box : boundingBoxes) { - ACTS_VERBOSE("Test whether " << box.name << " contains '" << name - << "' as substring"); - if (box.name.find(name) == std::string::npos) { - continue; // skip boxes that do not match the station name - } auto parent = box.fullPhysVol->getParent().get(); - if (parent == nullptr) { - throw std::domain_error("buildBarrelNode() No parent found for " + name); + throw std::domain_error(std::format( + "processStation() No parent found for chamber {} in station {}", + box.name, station)); } - commonStations[parent].push_back(box); - } - // Create a vector to hold the chambers and inner volumes - std::vector> volChambers; - std::vector< - std::vector>> - innerVolumesNodes; - innerVolumesNodes.resize(commonStations.size()); - - if (commonStations.empty()) { - throw std::invalid_argument("No barrel stations could be found."); - } - volChambers.reserve(commonStations.size()); - std::size_t stationNum = 1; - double maxZ = std::numeric_limits::lowest(); - for (const auto& [parentPhysVol, childrenTrkVols] : commonStations) { - std::shared_ptr parentVolume = - ActsPlugins::GeoModel::convertVolume( - ActsPlugins::GeoModel::volumePosInSpace(parentPhysVol), - parentPhysVol->getLogVol()->getShape(), boundFactory); - - auto chamberVolume = std::make_unique( - *parentVolume, std::format("{:}_Chamber_{:d}", name, stationNum)); - chamberVolume->assignGeometryId(geoId.withVolume(stationNum)); - - ACTS_VERBOSE("Boundaries of the chamber volume: " - << chamberVolume->boundarySurfaces().size()); - - std::size_t childVol = 1; - auto chamberId = chamberVolume->geometryId(); - - for (auto& child : childrenTrkVols) { - std::unique_ptr trVol{nullptr}; - - // use dedicated builder for MDT multilayers - if (child.name.find("MDT") != std::string::npos) { - MultiWireVolumeBuilder::Config mwCfg; - auto vb = child.volume->volumeBoundsPtr(); - double halfY{0}; - double halfZ{0}; - using LineBounds = Acts::LineBounds::BoundValues; - - if (vb->type() == Acts::VolumeBounds::eTrapezoid) { - using BoundVal = Acts::TrapezoidVolumeBounds::BoundValues; - - auto tzb = std::dynamic_pointer_cast(vb); - mwCfg.bounds = boundFactory.insert(tzb); - halfY = tzb->get(BoundVal::eHalfLengthY); - halfZ = tzb->get(BoundVal::eHalfLengthZ); - - } else if (vb->type() == Acts::VolumeBounds::eCuboid) { - using BoundVal = Acts::CuboidVolumeBounds::BoundValues; - - auto cbb = std::dynamic_pointer_cast(vb); - mwCfg.bounds = boundFactory.insert(cbb); - - halfY = cbb->get(BoundVal::eHalfLengthY); - halfZ = cbb->get(BoundVal::eHalfLengthZ); - - } else { - throw std::runtime_error( - "GeoModelMuonMockupBuilder::buildBarrelNode() - Not a trapezoid " - "or cuboid volume bounds"); - } - mwCfg.name = child.name; - mwCfg.mlSurfaces = child.surfaces; - mwCfg.transform = child.volume->transform(); - auto& sb = child.surfaces.front()->bounds(); - auto lineBounds = dynamic_cast(&sb); - if (lineBounds == nullptr) { - throw std::runtime_error( - "This MDT does not have tubes, what does it have?"); + auto it = chamberVolumes.find(parent); + if (it == chamberVolumes.end()) { + // We create a new chamber node for this parent + std::shared_ptr parentVolume = + ActsPlugins::GeoModel::convertVolume( + ActsPlugins::GeoModel::volumePosInSpace(parent), + parent->getLogVol()->getShape(), boundFactory); + + auto chamberVolume = std::make_unique( + *parentVolume, std::format("{:}_Chamber_{:d}", station, volNum)); + chamberVolume->assignGeometryId(geoId.withVolume(volNum)); + + ACTS_VERBOSE("New parent: " << chamberVolume->volumeName() + << " from box: " << box.name + << ", Id: " << chamberVolume->geometryId() + << ", center: " << chamberVolume->center().x() + << ", " << chamberVolume->center().y() << ", " + << chamberVolume->center().z() << ", bounds: " + << chamberVolume->volumeBounds()); + + // update bounds + if (isBarrel) { + if (chamberVolume->volumeBounds().type() != + Acts::VolumeBounds::eCuboid) { + throw std::runtime_error(std::format( + "processStation() -- Barrel chamber {} has bound type {} instead " + "of Cuboid", + chamberVolume->volumeName(), + static_cast(chamberVolume->volumeBounds().type()))); } - double tubeR = lineBounds->get(LineBounds::eR); - mwCfg.binning = { - {{Acts::AxisDirection::AxisY, Acts::AxisBoundaryType::Bound, -halfY, - halfY, static_cast(std::lround(1. * halfY / tubeR))}, - 2}, - {{Acts::AxisDirection::AxisZ, Acts::AxisBoundaryType::Bound, -halfZ, - halfZ, static_cast(std::lround(1. * halfZ / tubeR))}, - 1}}; - - MultiWireVolumeBuilder mdtBuilder{mwCfg}; - trVol = mdtBuilder.buildVolume(); - + updateBounds(*chamberVolume, bounds); } else { - trVol = - std::make_unique(*child.volume, child.name); - trVol->assignGeometryId(chamberId.withExtra(childVol)); - - // add the sensitives (tubes) in the constructed tracking volume - for (const auto& surface : child.surfaces) { - trVol->addSurface(surface); + if (chamberVolume->volumeBounds().type() != + Acts::VolumeBounds::eTrapezoid) { + throw std::runtime_error(std::format( + "processStation() -- Endcap chamber {} has bound type {} instead " + "of Trapezoid", + chamberVolume->volumeName(), + static_cast(chamberVolume->volumeBounds().type()))); } + updateBounds(*chamberVolume, bounds); } - trVol->assignGeometryId(chamberId.withExtra(childVol)); - ++childVol; + it = chamberVolumes + .emplace(parent, std::make_pair(std::make_shared( + std::move(chamberVolume)), + volNum++)) + .first; + } + // We add the box to the parent node + NodePtr_t& chamberNode = it->second.first; + if (!chamberNode) { + throw std::logic_error(std::format( + "processStation() -- Found null chamber node for parent {}", + parent->getLogVol()->getName())); + } + auto trVol = buildChildChamber(box, boundFactory); + trVol->assignGeometryId(geoId.withVolume(it->second.second) + .withExtra(chamberNode->children().size() + 1)); - auto innerNode = - std::make_shared( - std::move(trVol)); + ACTS_VERBOSE("\t\t Added child: " << trVol->volumeName() << ", " + << trVol->geometryId() + << " to Parent: " << chamberNode->name()); + + // create static blueprint node for the inner volume and add it to the + // chamber node + chamberNode->addChild(std::make_shared(std::move(trVol))); + } + // Create a new station node with the attached cylinder volume + const double translationZ = + isBarrel ? 0.0 : 0.5 * (bounds.zMax + bounds.zMin); + const double cylHalfLenght = + isBarrel ? bounds.zMax : 0.5 * (bounds.zMax - bounds.zMin); + auto stationNode = + std::make_shared(std::make_unique( + Acts::Transform3{Acts::Translation3(0., 0., translationZ)}, + boundFactory.makeBounds( + bounds.rMin, bounds.rMax, cylHalfLenght), + std::format("{:}_StationVol", station))); + ACTS_DEBUG("Created station volume: " + << stationNode->name() << " with bounds r: " << bounds.rMin + << " - " << bounds.rMax << ", z: " << translationZ << " pm " + << cylHalfLenght); + + // create bluprint nodes for the chambers and add them as children to the + // cylinder station node + for (auto& [_, entry] : chamberVolumes) { + stationNode->addChild(std::move(entry.first)); + } - innerVolumesNodes[stationNum - 1].push_back(std::move(innerNode)); + return stationNode; +} + +std::unique_ptr +GeoModelMuonMockupBuilder::buildChildChamber( + const Box_t& box, Acts::VolumeBoundFactory& boundFactory) const { + std::unique_ptr trVol{nullptr}; + + // use dedicated builder for MDT multilayers + if (box.name.find("MDT") != std::string::npos) { + MultiWireVolumeBuilder::Config mwCfg; + auto vb = box.volume->volumeBoundsPtr(); + double halfY{0}, halfZ{0}; + using LineBounds = Acts::LineBounds::BoundValues; + + if (vb->type() == Acts::VolumeBounds::eTrapezoid) { + using BoundVal = Acts::TrapezoidVolumeBounds::BoundValues; + + auto tzb = std::dynamic_pointer_cast(vb); + mwCfg.bounds = boundFactory.insert(tzb); + halfY = tzb->get(BoundVal::eHalfLengthY); + halfZ = tzb->get(BoundVal::eHalfLengthZ); + + } else if (vb->type() == Acts::VolumeBounds::eCuboid) { + using BoundVal = Acts::CuboidVolumeBounds::BoundValues; + + auto cbb = std::dynamic_pointer_cast(vb); + mwCfg.bounds = boundFactory.insert(cbb); + halfY = cbb->get(BoundVal::eHalfLengthY); + halfZ = cbb->get(BoundVal::eHalfLengthZ); + + } else { + throw std::runtime_error( + "GeoModelMuonMockupBuilder::buildBarrelNode() - Not a trapezoid " + "or cuboid volume bounds"); + } + + mwCfg.name = box.name; + mwCfg.mlSurfaces = box.surfaces; + mwCfg.transform = box.volume->transform(); + auto& sb = box.surfaces.front()->bounds(); + auto lineBounds = dynamic_cast(&sb); + if (lineBounds == nullptr) { + throw std::runtime_error( + "This MDT does not have tubes, what does it have?"); + } + double tubeR = lineBounds->get(LineBounds::eR); + mwCfg.binning = { + {{Acts::AxisDirection::AxisY, Acts::AxisBoundaryType::Bound, -halfY, + halfY, static_cast(std::lround(1. * halfY / tubeR))}, + 2}, + {{Acts::AxisDirection::AxisZ, Acts::AxisBoundaryType::Bound, -halfZ, + halfZ, static_cast(std::lround(1. * halfZ / tubeR))}, + 1}}; + + MultiWireVolumeBuilder mdtBuilder{mwCfg}; + trVol = mdtBuilder.buildVolume(); + + } else { + trVol = std::make_unique(*box.volume, box.name); + + // add the sensitives in the constructed tracking volume + for (const auto& surface : box.surfaces) { + trVol->addSurface(surface); } - volChambers.push_back(std::move(chamberVolume)); - maxZ = std::max( - maxZ, std::abs(volChambers.back()->center().z()) + - volChambers.back()->volumeBounds().values()[eHalfLengthY]); - ++stationNum; } + return trVol; +} +template +void GeoModelMuonMockupBuilder::updateBounds(const Acts::TrackingVolume& volume, + cylBounds& bounds) const { + const Acts::Vector3& center{volume.center()}; + const double rCenter{Acts::fastHypot(center.x(), center.y())}; + const auto volBounds{volume.volumeBounds().values()}; + double rMin{0.0}; + double rMax{0.0}; + double zMin{0.0}; + double zMax{0.0}; + + if constexpr (VolBounds_t == Acts::VolumeBounds::eTrapezoid) { + using enum Acts::TrapezoidVolumeBounds::BoundValues; + rMin = rCenter - volBounds[eHalfLengthY]; + rMax = Acts::fastHypot(rCenter + volBounds[eHalfLengthY], + volBounds[eHalfLengthXposY]); + zMin = center.z() - volBounds[eHalfLengthZ]; + zMax = center.z() + volBounds[eHalfLengthZ]; + } else if constexpr (VolBounds_t == Acts::VolumeBounds::eCuboid) { + using enum Acts::CuboidVolumeBounds::BoundValues; + rMin = rCenter - volBounds[eHalfLengthZ]; + rMax = Acts::fastHypot(rCenter + volBounds[eHalfLengthZ], + volBounds[eHalfLengthX]); + zMax = Acts::abs(center.z()) + volBounds[eHalfLengthY]; + } else { + static_assert(VolBounds_t == Acts::VolumeBounds::eTrapezoid || + VolBounds_t == Acts::VolumeBounds::eCuboid, + "Unsupported volume bounds type in cylBounds::update"); + } + + ACTS_VERBOSE("Computed cylindrical bounds: " + << "r: " << rMin << ", " << rMax << " " + << "z: " << zMin << ", " << zMax); + + bounds.rMin = std::min(bounds.rMin, rMin); + bounds.rMax = std::max(bounds.rMax, rMax); + bounds.zMin = std::min(bounds.zMin, zMin); + bounds.zMax = std::max(bounds.zMax, zMax); +} - const Acts::Vector3& cent{volChambers.front()->center()}; - double rmincyl = Acts::fastHypot(cent.x(), cent.y()) - - volChambers.front()->volumeBounds().values()[eHalfLengthZ]; - double rmaxcyl = Acts::fastHypot( - rmincyl + 2 * volChambers.front()->volumeBounds().values()[eHalfLengthZ], - volChambers.front()->volumeBounds().values()[eHalfLengthX]); - double halfZ = maxZ; - - // Create the barrel node with the attached cylinder volume - auto barrelNode = std::make_shared( - std::make_unique( - Acts::Transform3::Identity(), - std::make_shared(rmincyl, rmaxcyl, halfZ), - std::format("{:}_Barrel", name))); - - // create the bluprint nodes for the chambers and add them as children to the - // cylinder barrel node - for (std::size_t chamberNum = 0; chamberNum < volChambers.size(); - ++chamberNum) { - auto chamberNode = - std::make_shared( - std::move(volChambers[chamberNum])); - - for (auto& innerVolNode : innerVolumesNodes[chamberNum]) { - chamberNode->addChild(std::move(innerVolNode)); +GeoModelMuonMockupBuilder::StationIdx GeoModelMuonMockupBuilder::getStationIdx( + const Box_t& box) const { + const auto& name = box.name; + + auto contains = [&name](std::string_view key) { + return name.find(key) != std::string::npos; + }; + auto isPositiveSide = [&name]() { + // Assume stationEta is the first number following an underscore in the + // chamber name + std::size_t pos = name.find('_'); + while (pos != std::string::npos && pos + 1 < name.size()) { + const char c = name[pos + 1]; + if (std::isdigit(c) != 0) { + // we found a digit right after underscore, so positive side + return true; + } else if (c == '-' && pos + 2 < name.size() && + std::isdigit(name[pos + 2]) != 0) { + // we found a negative digit right after underscore, so negative side + return false; + } + pos = name.find('_', pos + 1); } + throw std::runtime_error("No stationEta found in name: " + name); + }; + // Handle the inner stations + if (contains("SmallWheel")) { + return isPositiveSide() ? StationIdx::EAI : StationIdx::ECI; + } else if (contains("Inner")) { + return StationIdx::BI; + } + // Handle TGCs + if (contains("TGC")) { + return isPositiveSide() ? StationIdx::EAM : StationIdx::ECM; + } + // Handle MDTs and RPCs + const bool isBarrel = contains("BMDT") || contains("RPC"); + if (contains("Middle")) { + return isBarrel ? StationIdx::BM + : (isPositiveSide() ? StationIdx::EAM : StationIdx::ECM); + } else if (contains("Outer")) { + return isBarrel ? StationIdx::BO + : (isPositiveSide() ? StationIdx::EAO : StationIdx::ECO); + } else { + throw std::domain_error( + "getStationIdx() -- Could not deduce station idx from volume name: " + + name); + } +} - barrelNode->addChild(std::move(chamberNode)); +GeoModelMuonMockupBuilder::FirstContainerIdx +GeoModelMuonMockupBuilder::getFirstContainerIdx( + const StationIdx& stationIdx) const { + if (stationIdx == StationIdx::EAM || stationIdx == StationIdx::EAO) { + return FirstContainerIdx::BW_A; + } else if (stationIdx == StationIdx::ECM || stationIdx == StationIdx::ECO) { + return FirstContainerIdx::BW_C; + } else { + return FirstContainerIdx::Central; } +} - return barrelNode; +std::string GeoModelMuonMockupBuilder::stationIdxToString( + const GeoModelMuonMockupBuilder::StationIdx idx) { + switch (idx) { + case StationIdx::BI: + return "BI"; + case StationIdx::BM: + return "BM"; + case StationIdx::BO: + return "BO"; + case StationIdx::EAI: + return "EAI"; + case StationIdx::EAM: + return "EAM"; + case StationIdx::EAO: + return "EAO"; + case StationIdx::ECI: + return "ECI"; + case StationIdx::ECM: + return "ECM"; + case StationIdx::ECO: + return "ECO"; + default: + throw std::domain_error( + "stationIdxToString() -- Unexpected StationIdx value"); + } } +std::string GeoModelMuonMockupBuilder::firstContainerIdxToString( + const GeoModelMuonMockupBuilder::FirstContainerIdx idx) { + switch (idx) { + case FirstContainerIdx::Central: + return "CentralContainer"; + case FirstContainerIdx::BW_A: + return "BigWheelA_Container"; + case FirstContainerIdx::BW_C: + return "BigWheelC_Container"; + default: + throw std::domain_error( + "firstContainerIdxToString() -- Unexpected FirstContainerIdx value"); + } +} } // namespace ActsExamples diff --git a/Examples/Detectors/MuonSpectrometerMockupDetector/src/GeoMuonMockupExperiment.cpp b/Examples/Detectors/MuonSpectrometerMockupDetector/src/GeoMuonMockupExperiment.cpp index 0c4684138a9..d97068b34dd 100644 --- a/Examples/Detectors/MuonSpectrometerMockupDetector/src/GeoMuonMockupExperiment.cpp +++ b/Examples/Detectors/MuonSpectrometerMockupDetector/src/GeoMuonMockupExperiment.cpp @@ -136,7 +136,7 @@ ActsPlugins::GeoModelTree GeoMuonMockupExperiment::constructMS() { assembleBigWheel(muonEnvelope, Outer, -outWheelZ); const double innerWheelZ = 0.8 * barrelZ; const double innerWheelR = - 0.95 * m_cfg.barrelRadii[toUnderlying(MuonLayer::Inner)]; + 0.90 * m_cfg.barrelRadii[toUnderlying(MuonLayer::Inner)]; assembleSmallWheel(muonEnvelope, innerWheelR, innerWheelZ); assembleSmallWheel(muonEnvelope, innerWheelR, -innerWheelZ); } diff --git a/Examples/Scripts/Python/geomodel_G4.py b/Examples/Scripts/Python/geomodel_G4.py index e96862ff651..063501b2d41 100644 --- a/Examples/Scripts/Python/geomodel_G4.py +++ b/Examples/Scripts/Python/geomodel_G4.py @@ -131,11 +131,21 @@ def main(): mockUpCfg.dbName = "ActsGeoMS.db" mockUpCfg.nSectors = 12 mockUpCfg.nEtaStations = 8 - mockUpCfg.buildEndcaps = False + mockUpCfg.buildEndcaps = True mockUpBuilder = gm_ex.GeoMuonMockupExperiment( mockUpCfg, "GeoMockUpMS", logLevel ) - gmBuilderConfig.stationNames = ["Inner", "Middle", "Outer"] + gmBuilderConfig.stationNames = [ + "BI", + "BM", + "BO", + "EAI", + "EAM", + "EAO", + "ECI", + "ECM", + "ECO", + ] gmTree = mockUpBuilder.constructMS() else: @@ -149,7 +159,7 @@ def main(): "SmallWheelGasGap", ] gmFactoryConfig.convertSubVolumes = True - gmFactoryConfig.convertBox = ["MDT", "RPC"] + gmFactoryConfig.convertBox = ["MDT", "RPC", "SmallWheel", "TGC"] gmFactory = gm.GeoModelDetectorObjectFactory(gmFactoryConfig, logLevel) # The options