Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-23 08:18:54

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/CylinderVolumeStack.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Common.hpp"
0013 #include "Acts/Definitions/Tolerance.hpp"
0014 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0015 #include "Acts/Utilities/Logger.hpp"
0016 #include "Acts/Utilities/StringHelpers.hpp"
0017 
0018 #include <algorithm>
0019 #include <memory>
0020 #include <numbers>
0021 #include <sstream>
0022 
0023 namespace Acts {
0024 
0025 struct CylinderVolumeStack::VolumeTuple {
0026   Volume* volume{};
0027   const CylinderVolumeBounds* bounds{};
0028   std::shared_ptr<CylinderVolumeBounds> updatedBounds{};
0029   Transform3 localTransform = Transform3::Identity();
0030   Transform3 globalTransform = Transform3::Identity();
0031 
0032   bool transformDirty = false;
0033 
0034   explicit VolumeTuple(const GeometryContext& gctx, Volume& volume_,
0035                        const Transform3& groupTransform)
0036       : volume{&volume_},
0037         localTransform{groupTransform.inverse() *
0038                        volume_.localToGlobalTransform(gctx)},
0039         globalTransform{volume_.localToGlobalTransform(gctx)} {
0040     bounds = dynamic_cast<const CylinderVolumeBounds*>(&volume_.volumeBounds());
0041     assert(bounds != nullptr);
0042     updatedBounds = std::make_shared<CylinderVolumeBounds>(*bounds);
0043   }
0044 
0045   double midZ() const { return localTransform.translation()[eZ]; }
0046   double halfLengthZ() const {
0047     return updatedBounds->get(CylinderVolumeBounds::eHalfLengthZ);
0048   }
0049   double minZ() const { return midZ() - halfLengthZ(); }
0050   double maxZ() const { return midZ() + halfLengthZ(); }
0051 
0052   double minR() const {
0053     return updatedBounds->get(CylinderVolumeBounds::eMinR);
0054   }
0055   double maxR() const {
0056     return updatedBounds->get(CylinderVolumeBounds::eMaxR);
0057   }
0058   double midR() const { return (minR() + maxR()) / 2.0; }
0059 
0060   void set(std::initializer_list<
0061            std::pair<CylinderVolumeBounds::BoundValues, double>>
0062                keyValues) {
0063     updatedBounds->set(keyValues);
0064   }
0065 
0066   void setLocalTransform(const Transform3& transform,
0067                          const Transform3& groupTransform) {
0068     localTransform = transform;
0069     globalTransform = groupTransform * localTransform;
0070     transformDirty = true;
0071   }
0072 
0073   void commit(const GeometryContext& gctx, const Logger& logger) {
0074     // make a copy so we can't accidentally modify in-place
0075     auto copy = std::make_shared<CylinderVolumeBounds>(*updatedBounds);
0076 
0077     std::optional<Transform3> transform = std::nullopt;
0078     if (transformDirty) {
0079       transform = globalTransform;
0080     }
0081 
0082     volume->update(gctx, std::move(updatedBounds), transform, logger);
0083     bounds = copy.get();
0084     updatedBounds = std::move(copy);
0085     transformDirty = false;
0086   }
0087 };
0088 
0089 CylinderVolumeStack::CylinderVolumeStack(const GeometryContext& gctx,
0090                                          std::vector<Volume*>& volumes,
0091                                          AxisDirection direction,
0092                                          VolumeAttachmentStrategy strategy,
0093                                          VolumeResizeStrategy resizeStrategy,
0094                                          const Logger& logger)
0095     : CylinderVolumeStack{
0096           gctx,  volumes, direction, strategy, {resizeStrategy, resizeStrategy},
0097           logger} {}
0098 
0099 CylinderVolumeStack::CylinderVolumeStack(
0100     const GeometryContext& gctx, std::vector<Volume*>& volumes,
0101     AxisDirection direction, VolumeAttachmentStrategy strategy,
0102     std::pair<VolumeResizeStrategy, VolumeResizeStrategy> resizeStrategies,
0103     const Logger& logger)
0104     : VolumeStack(volumes, direction,
0105                   {resizeStrategies.first, resizeStrategies.second}) {
0106   initializeOuterVolume(gctx, direction, strategy, logger);
0107 }
0108 
0109 void CylinderVolumeStack::initializeOuterVolume(
0110     const GeometryContext& gctx, AxisDirection direction,
0111     VolumeAttachmentStrategy strategy, const Logger& logger) {
0112   ACTS_DEBUG("Creating CylinderVolumeStack from "
0113              << m_volumes.size() << " volumes in direction "
0114              << axisDirectionName(direction));
0115   if (m_volumes.empty()) {
0116     throw std::invalid_argument(
0117         "CylinderVolumeStack requires at least one volume");
0118   }
0119 
0120   if (direction != Acts::AxisDirection::AxisZ &&
0121       direction != Acts::AxisDirection::AxisR) {
0122     throw std::invalid_argument(axisDirectionName(direction) +
0123                                 " is not supported ");
0124   }
0125 
0126   // For alignment check, we have to pick one of the volumes as the base
0127   m_groupTransform = m_volumes.front()->localToGlobalTransform(gctx);
0128   ACTS_VERBOSE("Initial group transform is:\n" << m_groupTransform.matrix());
0129 
0130   std::vector<VolumeTuple> volumeTuples;
0131   volumeTuples.reserve(m_volumes.size());
0132 
0133   for (const auto& volume : m_volumes) {
0134     const auto* cylinderBounds =
0135         dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0136     if (cylinderBounds == nullptr) {
0137       throw std::invalid_argument{
0138           "CylinderVolumeStack requires all volumes to "
0139           "have CylinderVolumeBounds"};
0140     }
0141 
0142     checkNoPhiSector(*cylinderBounds, logger);
0143 
0144     volumeTuples.emplace_back(gctx, *volume, m_groupTransform);
0145   }
0146 
0147   ACTS_DEBUG("*** Initial volume configuration:");
0148   printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0149 
0150   if (m_volumes.size() == 1) {
0151     ACTS_VERBOSE("Only one volume, returning");
0152     setTransform(m_volumes.front()->localToGlobalTransform(gctx));
0153     const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0154         &m_volumes.front()->volumeBounds());
0155     assert(cylBounds != nullptr && "Volume bounds are not cylinder bounds");
0156     Volume::update(gctx, std::make_shared<CylinderVolumeBounds>(*cylBounds),
0157                    std::nullopt, logger);
0158     ACTS_VERBOSE(
0159         "Transform is now: " << toString(localToGlobalTransform(gctx)));
0160     return;
0161   }
0162 
0163   ACTS_VERBOSE("Checking volume alignment");
0164   checkVolumeAlignment(volumeTuples, logger);
0165 
0166   if (direction == Acts::AxisDirection::AxisZ) {
0167     ACTS_VERBOSE("Sorting by volume z position");
0168     std::ranges::sort(volumeTuples, {}, [](const auto& v) {
0169       return v.localTransform.translation()[eZ];
0170     });
0171 
0172     ACTS_VERBOSE("Checking for overlaps and attaching volumes in z");
0173     std::vector<VolumeTuple> gapVolumes =
0174         checkOverlapAndAttachInZ(gctx, volumeTuples, strategy, logger);
0175 
0176     ACTS_VERBOSE("Appending "
0177                  << gapVolumes.size()
0178                  << " gap volumes to the end of the volume vector");
0179     std::ranges::copy(gapVolumes, std::back_inserter(volumeTuples));
0180 
0181     ACTS_VERBOSE("*** Volume configuration after z attachment:");
0182     printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0183 
0184     ACTS_VERBOSE("Synchronizing bounds in r");
0185     const auto [minR, maxR] = synchronizeRBounds(volumeTuples, logger);
0186 
0187     for (auto& vt : volumeTuples) {
0188       ACTS_VERBOSE("Updated bounds for volume at z: "
0189                    << vt.localTransform.translation()[eZ]);
0190       ACTS_VERBOSE(*vt.updatedBounds);
0191 
0192       vt.commit(gctx, logger);
0193     }
0194 
0195     ACTS_VERBOSE("*** Volume configuration after r synchronization:");
0196     printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0197 
0198     std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midZ(); });
0199 
0200     m_volumes.clear();
0201     for (const auto& vt : volumeTuples) {
0202       m_volumes.push_back(vt.volume);
0203     }
0204 
0205     ACTS_DEBUG("*** Volume configuration after final z sorting:");
0206     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0207 
0208     double minZ = volumeTuples.front().minZ();
0209     double maxZ = volumeTuples.back().maxZ();
0210 
0211     double midZ = (minZ + maxZ) / 2.0;
0212     double hlZ = (maxZ - minZ) / 2.0;
0213 
0214     Volume::update(gctx,
0215                    std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0216                    m_groupTransform * Translation3{0, 0, midZ}, logger);
0217     ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0218     ACTS_DEBUG("Outer transform / new group transform is:\n"
0219                << toString(localToGlobalTransform(gctx)));
0220 
0221     // Update group transform to the new center
0222     // @TODO: We probably can reuse m_transform
0223     m_groupTransform = localToGlobalTransform(gctx);
0224 
0225   } else if (direction == Acts::AxisDirection::AxisR) {
0226     ACTS_VERBOSE("Sorting by volume r middle point");
0227     std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midR(); });
0228 
0229     ACTS_VERBOSE("Checking for overlaps and attaching volumes in r");
0230     std::vector<VolumeTuple> gapVolumes =
0231         checkOverlapAndAttachInR(gctx, volumeTuples, strategy, logger);
0232 
0233     ACTS_VERBOSE("Appending "
0234                  << gapVolumes.size()
0235                  << " gap volumes to the end of the volume vector");
0236     std::ranges::copy(gapVolumes, std::back_inserter(volumeTuples));
0237 
0238     ACTS_VERBOSE("*** Volume configuration after r attachment:");
0239     printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0240 
0241     ACTS_VERBOSE("Synchronizing bounds in z");
0242     const auto [minZ, maxZ] = synchronizeZBounds(volumeTuples, logger);
0243 
0244     for (auto& vt : volumeTuples) {
0245       ACTS_VERBOSE("Updated bounds for volume at r: " << vt.midR());
0246       ACTS_VERBOSE(*vt.updatedBounds);
0247       vt.commit(gctx, logger);
0248     }
0249 
0250     ACTS_VERBOSE("*** Volume configuration after z synchronization:");
0251     printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0252 
0253     std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midR(); });
0254 
0255     m_volumes.clear();
0256     for (const auto& vt : volumeTuples) {
0257       m_volumes.push_back(vt.volume);
0258     }
0259 
0260     ACTS_DEBUG("*** Volume configuration after final r sorting:");
0261     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0262 
0263     double minR = volumeTuples.front().minR();
0264     double maxR = volumeTuples.back().maxR();
0265 
0266     double midZ = (minZ + maxZ) / 2.0;
0267     double hlZ = (maxZ - minZ) / 2.0;
0268 
0269     Volume::update(gctx,
0270                    std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0271                    m_groupTransform * Translation3{0, 0, midZ}, logger);
0272 
0273     ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0274     ACTS_DEBUG("Outer transform is:\n"
0275                << toString(localToGlobalTransform(gctx)));
0276 
0277     // Update group transform to the new center
0278     // @TODO: We probably can reuse m_transform
0279     m_groupTransform = localToGlobalTransform(gctx);
0280 
0281   } else {
0282     ACTS_ERROR("Binning in " << axisDirectionName(direction)
0283                              << " is not supported");
0284     throw std::invalid_argument(axisDirectionName(direction) +
0285                                 " is not supported ");
0286   }
0287 }
0288 
0289 void CylinderVolumeStack::overlapPrint(
0290     AxisDirection direction, const CylinderVolumeStack::VolumeTuple& a,
0291     const CylinderVolumeStack::VolumeTuple& b, const Logger& logger) {
0292   if (logger().doPrint(Acts::Logging::DEBUG)) {
0293     std::stringstream ss;
0294     ss << std::fixed;
0295     ss << std::setprecision(3);
0296     ss << std::setfill(' ');
0297 
0298     int w = 9;
0299 
0300     ACTS_VERBOSE("Checking overlap between");
0301     if (direction == AxisDirection::AxisZ) {
0302       ss << " - " << " z: [ " << std::setw(w) << a.minZ() << " <- "
0303          << std::setw(w) << a.midZ() << " -> " << std::setw(w) << a.maxZ()
0304          << " ]";
0305       ACTS_VERBOSE(ss.str());
0306 
0307       ss.str("");
0308       ss << " - " << " z: [ " << std::setw(w) << b.minZ() << " <- "
0309          << std::setw(w) << b.midZ() << " -> " << std::setw(w) << b.maxZ()
0310          << " ]";
0311       ACTS_VERBOSE(ss.str());
0312     } else {
0313       ss << " - " << " r: [ " << std::setw(w) << a.minR() << " <-> "
0314          << std::setw(w) << a.maxR() << " ]";
0315       ACTS_VERBOSE(ss.str());
0316 
0317       ss.str("");
0318       ss << " - " << " r: [ " << std::setw(w) << b.minR() << " <-> "
0319          << std::setw(w) << b.maxR() << " ]";
0320       ACTS_VERBOSE(ss.str());
0321     }
0322   }
0323 }
0324 
0325 std::vector<CylinderVolumeStack::VolumeTuple>
0326 CylinderVolumeStack::checkOverlapAndAttachInZ(const GeometryContext& gctx,
0327                                               std::vector<VolumeTuple>& volumes,
0328                                               VolumeAttachmentStrategy strategy,
0329                                               const Logger& logger) {
0330   // Preconditions: volumes are sorted by z
0331   std::vector<VolumeTuple> gapVolumes;
0332   for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0333     std::size_t j = i + 1;
0334     auto& a = volumes.at(i);
0335     auto& b = volumes.at(j);
0336 
0337     overlapPrint(AxisDirection::AxisZ, a, b, logger);
0338 
0339     if (a.maxZ() > b.minZ()) {
0340       ACTS_ERROR(" -> Overlap in z");
0341       throw std::invalid_argument("Volumes overlap in z");
0342     } else {
0343       ACTS_VERBOSE(" -> No overlap");
0344     }
0345 
0346     constexpr auto tolerance = s_onSurfaceTolerance;
0347     if (std::abs(a.maxZ() - b.minZ()) < tolerance) {
0348       ACTS_VERBOSE("No gap between volumes, no attachment needed");
0349     } else {
0350       double gapWidth = b.minZ() - a.maxZ();
0351       ACTS_VERBOSE("Gap width: " << gapWidth);
0352 
0353       ACTS_VERBOSE("Synchronizing bounds in z with strategy: " << strategy);
0354       switch (strategy) {
0355         case VolumeAttachmentStrategy::Midpoint: {
0356           ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0357 
0358           double aZMidNew = (a.minZ() + a.maxZ()) / 2.0 + gapWidth / 4.0;
0359           double aHlZNew = a.halfLengthZ() + gapWidth / 4.0;
0360           ACTS_VERBOSE("  - New halflength for first volume: " << aHlZNew);
0361           ACTS_VERBOSE("  - New bounds for first volume: ["
0362                        << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0363                        << (aZMidNew + aHlZNew) << "]");
0364 
0365           assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0366                  "Volume shrunk");
0367           assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0368 
0369           double bZMidNew = (b.minZ() + b.maxZ()) / 2.0 - gapWidth / 4.0;
0370           double bHlZNew = b.halfLengthZ() + gapWidth / 4.0;
0371           ACTS_VERBOSE("  - New halflength for second volume: " << bHlZNew);
0372           ACTS_VERBOSE("  - New bounds for second volume: ["
0373                        << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0374                        << (bZMidNew + bHlZNew) << "]");
0375 
0376           assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0377           assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0378                  "Volume shrunk");
0379 
0380           a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0381                               m_groupTransform);
0382           a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0383 
0384           b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0385                               m_groupTransform);
0386           b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0387 
0388           break;
0389         }
0390         case VolumeAttachmentStrategy::First: {
0391           ACTS_VERBOSE(" -> Strategy: Expand first volume");
0392           double aZMidNew = (a.minZ() + b.minZ()) / 2.0;
0393           double aHlZNew = (b.minZ() - a.minZ()) / 2.0;
0394           ACTS_VERBOSE("  - Gap width: " << gapWidth);
0395           ACTS_VERBOSE("  - New bounds for first volume: ["
0396                        << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0397                        << (aZMidNew + aHlZNew) << "]");
0398 
0399           assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0400                  "Volume shrunk");
0401           assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0402 
0403           a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0404                               m_groupTransform);
0405           a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0406 
0407           break;
0408         }
0409         case VolumeAttachmentStrategy::Second: {
0410           ACTS_VERBOSE(" -> Strategy: Expand second volume");
0411           double bZMidNew = (a.maxZ() + b.maxZ()) / 2.0;
0412           double bHlZNew = (b.maxZ() - a.maxZ()) / 2.0;
0413           ACTS_VERBOSE("  - New halflength for second volume: " << bHlZNew);
0414           ACTS_VERBOSE("  - New bounds for second volume: ["
0415                        << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0416                        << (bZMidNew + bHlZNew) << "]");
0417 
0418           assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0419           assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0420                  "Volume shrunk");
0421 
0422           b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0423                               m_groupTransform);
0424           b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0425           break;
0426         }
0427         case VolumeAttachmentStrategy::Gap: {
0428           ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0429           double gapHlZ = (b.minZ() - a.maxZ()) / 2.0;
0430           double gapMidZ = (b.minZ() + a.maxZ()) / 2.0;
0431 
0432           ACTS_VERBOSE("  - Gap half length: " << gapHlZ
0433                                                << " at z: " << gapMidZ);
0434 
0435           double minR = std::min(a.minR(), b.minR());
0436           double maxR = std::max(a.maxR(), b.maxR());
0437 
0438           Transform3 gapLocalTransform{Translation3{0, 0, gapMidZ}};
0439           Transform3 gapGlobalTransform = m_groupTransform * gapLocalTransform;
0440           auto gapBounds =
0441               std::make_shared<CylinderVolumeBounds>(minR, maxR, gapHlZ);
0442 
0443           auto gap = addGapVolume(gapGlobalTransform, gapBounds);
0444           gapVolumes.emplace_back(gctx, *gap, m_groupTransform);
0445 
0446           break;
0447         }
0448         default:
0449           ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0450           std::stringstream ss;
0451           ss << strategy;
0452           throw std::invalid_argument("Attachment strategy " + ss.str() +
0453                                       " not implemented");
0454       }
0455     }
0456   }
0457 
0458   return gapVolumes;
0459 }
0460 
0461 std::vector<CylinderVolumeStack::VolumeTuple>
0462 CylinderVolumeStack::checkOverlapAndAttachInR(const GeometryContext& gctx,
0463                                               std::vector<VolumeTuple>& volumes,
0464                                               VolumeAttachmentStrategy strategy,
0465                                               const Logger& logger) {
0466   std::vector<VolumeTuple> gapVolumes;
0467   for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0468     std::size_t j = i + 1;
0469     auto& a = volumes.at(i);
0470     auto& b = volumes.at(j);
0471 
0472     overlapPrint(AxisDirection::AxisR, a, b, logger);
0473 
0474     if (a.maxR() > b.minR()) {
0475       ACTS_ERROR(" -> Overlap in r");
0476       throw std::invalid_argument("Volumes overlap in r");
0477     } else {
0478       ACTS_VERBOSE(" -> No overlap");
0479     }
0480 
0481     constexpr auto tolerance = s_onSurfaceTolerance;
0482     if (std::abs(a.maxR() - b.minR()) < tolerance) {
0483       ACTS_VERBOSE("No gap between volumes, no attachment needed");
0484     } else {
0485       double gapWidth = b.minR() - a.maxR();
0486       ACTS_VERBOSE("Gap width: " << gapWidth);
0487 
0488       ACTS_VERBOSE("Synchronizing bounds in r with strategy: " << strategy);
0489       switch (strategy) {
0490         case VolumeAttachmentStrategy::Midpoint: {
0491           ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0492 
0493           a.set({{CylinderVolumeBounds::eMaxR, a.maxR() + gapWidth / 2.0}});
0494           b.set({{CylinderVolumeBounds::eMinR, b.minR() - gapWidth / 2.0}});
0495 
0496           break;
0497         }
0498         case VolumeAttachmentStrategy::First: {
0499           ACTS_VERBOSE(" -> Strategy: Expand first volume");
0500 
0501           a.set({{CylinderVolumeBounds::eMaxR, b.minR()}});
0502 
0503           break;
0504         }
0505         case VolumeAttachmentStrategy::Second: {
0506           ACTS_VERBOSE(" -> Strategy: Expand second volume");
0507 
0508           b.set({{CylinderVolumeBounds::eMinR, a.maxR()}});
0509 
0510           break;
0511         }
0512         case VolumeAttachmentStrategy::Gap: {
0513           ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0514 
0515           auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0516               a.maxR(), b.minR(), a.halfLengthZ());
0517           auto gap = addGapVolume(m_groupTransform, gapBounds);
0518 
0519           gapVolumes.emplace_back(gctx, *gap, m_groupTransform);
0520           break;
0521         }
0522         default:
0523           ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0524           std::stringstream ss;
0525           ss << strategy;
0526           throw std::invalid_argument("Attachment strategy " + ss.str() +
0527                                       " not implemented");
0528       }
0529     }
0530   }
0531 
0532   return gapVolumes;
0533 }
0534 
0535 void CylinderVolumeStack::printVolumeSequence(
0536     const std::vector<VolumeTuple>& volumes, const Logger& logger,
0537     Acts::Logging::Level lvl) {
0538   if (!logger().doPrint(lvl)) {
0539     return;
0540   }
0541   for (const auto& vt : volumes) {
0542     std::stringstream ss;
0543     ss << std::fixed;
0544     ss << std::setprecision(3);
0545     ss << std::setfill(' ');
0546 
0547     int w = 9;
0548     ss << "z: [ " << std::setw(w) << vt.minZ() << " <- " << std::setw(w)
0549        << vt.midZ() << " -> " << std::setw(w) << vt.maxZ() << " ], r: [ "
0550        << std::setw(w) << vt.minR() << " <-> " << std::setw(w) << vt.maxR()
0551        << " ]";
0552 
0553     logger().log(lvl, ss.str());
0554   }
0555 }
0556 
0557 void CylinderVolumeStack::checkVolumeAlignment(
0558     const std::vector<VolumeTuple>& volumes, const Logger& logger) {
0559   std::size_t n = 0;
0560   for (auto& vt : volumes) {
0561     ACTS_VERBOSE("Checking volume #"
0562                  << n << " at z: " << vt.localTransform.translation()[eZ]);
0563     ACTS_VERBOSE("- Local transform is:\n" << vt.localTransform.matrix());
0564 
0565     // @TODO: What's a good tolerance here?
0566     constexpr auto tolerance = s_onSurfaceTolerance;
0567 
0568     // In the group coordinate system:
0569 
0570     // a) the volumes cannot rotate around x or y
0571     if (std::abs(vt.localTransform.rotation().col(eX)[eZ]) >= tolerance ||
0572         std::abs(vt.localTransform.rotation().col(eY)[eZ]) >= tolerance) {
0573       ACTS_ERROR("Volumes are not aligned: rotation is different");
0574       throw std::invalid_argument(
0575           "Volumes are not aligned: rotation is different");
0576     }
0577 
0578     ACTS_VERBOSE(" -> Rotation is ok!");
0579 
0580     // b) the volumes cannot have translation in x or y
0581     Vector2 translation = vt.localTransform.translation().head<2>();
0582     if (std::abs(translation[0]) > tolerance ||  //
0583         std::abs(translation[1]) > tolerance) {
0584       ACTS_ERROR("Volumes are not aligned: translation in x or y");
0585       throw std::invalid_argument(
0586           "Volumes are not aligned: translation in x or y");
0587     }
0588     ACTS_VERBOSE(" -> Translation in x/y is ok!");
0589 
0590     n++;
0591   }
0592 }
0593 
0594 std::pair<double, double> CylinderVolumeStack::synchronizeRBounds(
0595     std::vector<VolumeTuple>& volumes, const Logger& logger) {
0596   const double minR =
0597       std::ranges::min_element(volumes, [](const auto& a, const auto& b) {
0598         return a.bounds->get(CylinderVolumeBounds::eMinR) <
0599                b.bounds->get(CylinderVolumeBounds::eMinR);
0600       })->bounds->get(CylinderVolumeBounds::eMinR);
0601 
0602   const double maxR =
0603       std::ranges::max_element(volumes, [](const auto& a, const auto& b) {
0604         return a.bounds->get(CylinderVolumeBounds::eMaxR) <
0605                b.bounds->get(CylinderVolumeBounds::eMaxR);
0606       })->bounds->get(CylinderVolumeBounds::eMaxR);
0607   ACTS_VERBOSE("Found: minR: " << minR << " maxR: " << maxR);
0608 
0609   for (auto& vt : volumes) {
0610     vt.set({
0611         {CylinderVolumeBounds::eMinR, minR},
0612         {CylinderVolumeBounds::eMaxR, maxR},
0613     });
0614   }
0615 
0616   return {minR, maxR};
0617 }
0618 
0619 std::pair<double, double> CylinderVolumeStack::synchronizeZBounds(
0620     std::vector<VolumeTuple>& volumes, const Logger& logger) {
0621   const double minZ =
0622       std::ranges::min_element(volumes, [](const auto& a, const auto& b) {
0623         return a.minZ() < b.minZ();
0624       })->minZ();
0625 
0626   const double maxZ =
0627       std::ranges::max_element(volumes, [](const auto& a, const auto& b) {
0628         return a.maxZ() < b.maxZ();
0629       })->maxZ();
0630   const double midZ = (minZ + maxZ) / 2.0;
0631   const double hlZ = (maxZ - minZ) / 2.0;
0632   ACTS_DEBUG("Found overall z bounds: [ " << minZ << " <- " << midZ << " -> "
0633                                           << maxZ << " ]");
0634   const Transform3 transform{Translation3{0, 0, midZ}};
0635 
0636   for (auto& vt : volumes) {
0637     vt.set({{CylinderVolumeBounds::eHalfLengthZ, hlZ}});
0638     vt.setLocalTransform(transform, m_groupTransform);
0639   }
0640 
0641   return {minZ, maxZ};
0642 }
0643 
0644 void CylinderVolumeStack::update(const GeometryContext& gctx,
0645                                  std::shared_ptr<VolumeBounds> volbounds,
0646                                  std::optional<Transform3> transform,
0647                                  const Logger& logger) {
0648   ACTS_DEBUG(
0649       "Resizing CylinderVolumeStack with strategy: " << m_resizeStrategies);
0650   ACTS_DEBUG("Currently have " << m_volumes.size() << " children");
0651   ACTS_DEBUG(m_gaps.size() << " gaps");
0652   for (const auto& v : m_volumes) {
0653     ACTS_DEBUG(" - volume bounds: \n" << v->volumeBounds());
0654     ACTS_DEBUG("          transform: \n"
0655                << v->localToGlobalTransform(gctx).matrix());
0656   }
0657 
0658   ACTS_DEBUG("New bounds are: \n" << *volbounds);
0659 
0660   auto cylBounds = std::dynamic_pointer_cast<CylinderVolumeBounds>(volbounds);
0661   if (cylBounds == nullptr) {
0662     throw std::invalid_argument(
0663         "CylinderVolumeStack requires CylinderVolumeBounds");
0664   }
0665 
0666   if (cylBounds == nullptr) {
0667     throw std::invalid_argument("New bounds are nullptr");
0668   }
0669 
0670   if (*cylBounds == volumeBounds()) {
0671     ACTS_VERBOSE("Bounds are the same, no resize needed");
0672     return;
0673   }
0674 
0675   ACTS_VERBOSE("Group transform is:\n" << toString(m_groupTransform));
0676   ACTS_VERBOSE("Current transform is:\n"
0677                << toString(localToGlobalTransform(gctx)));
0678   if (transform.has_value()) {
0679     ACTS_VERBOSE("Input transform:\n" << toString(transform.value()));
0680   }
0681 
0682   VolumeTuple oldVolume{gctx, *this, localToGlobalTransform(gctx)};
0683   VolumeTuple newVolume{gctx, *this, localToGlobalTransform(gctx)};
0684   newVolume.updatedBounds = std::make_shared<CylinderVolumeBounds>(*cylBounds);
0685   newVolume.globalTransform = transform.value_or(localToGlobalTransform(gctx));
0686   newVolume.localTransform =
0687       globalToLocalTransform(gctx) * newVolume.globalTransform;
0688 
0689   if (!transform.has_value()) {
0690     ACTS_VERBOSE("Local transform does not change");
0691   } else {
0692     ACTS_VERBOSE("Local transform changes from\n"
0693                  << m_groupTransform.matrix() << "\nto\n"
0694                  << newVolume.localTransform.matrix());
0695     ACTS_VERBOSE("Checking transform consistency");
0696 
0697     std::vector<VolumeTuple> volTemp{newVolume};
0698     checkVolumeAlignment(volTemp, logger);
0699   }
0700 
0701   checkNoPhiSector(*cylBounds, logger);
0702 
0703   const double newMinR = newVolume.minR();
0704   const double newMaxR = newVolume.maxR();
0705   const double newMinZ = newVolume.minZ();
0706   const double newMaxZ = newVolume.maxZ();
0707   const double newMidZ = newVolume.midZ();
0708   const double newHlZ = newVolume.halfLengthZ();
0709 
0710   const double oldMinR = oldVolume.minR();
0711   const double oldMaxR = oldVolume.maxR();
0712   const double oldMinZ = oldVolume.minZ();
0713   const double oldMaxZ = oldVolume.maxZ();
0714   const double oldMidZ = oldVolume.midZ();
0715   const double oldHlZ = oldVolume.halfLengthZ();
0716 
0717   ACTS_VERBOSE("Previous bounds are: z: [ "
0718                << oldMinZ << " <- " << oldMidZ << " -> " << oldMaxZ << " ] ("
0719                << oldHlZ << "), r: [ " << oldMinR << " <-> " << oldMaxR
0720                << " ]");
0721   ACTS_VERBOSE("New bounds are: z:      [ "
0722                << newMinZ << " <- " << newMidZ << " -> " << newMaxZ << " ] ("
0723                << newHlZ << "), r: [ " << newMinR << " <-> " << newMaxR
0724                << " ]");
0725 
0726   constexpr auto tolerance = s_onSurfaceTolerance;
0727   auto same = [](double a, double b) { return std::abs(a - b) < tolerance; };
0728 
0729   if (!same(newMinZ, oldMinZ) && newMinZ > oldMinZ) {
0730     ACTS_ERROR("Shrinking the stack size in z is not supported: "
0731                << newMinZ << " -> " << oldMinZ);
0732     throw std::invalid_argument("Shrinking the stack in z is not supported");
0733   }
0734 
0735   if (!same(newMaxZ, oldMaxZ) && newMaxZ < oldMaxZ) {
0736     ACTS_ERROR("Shrinking the stack size in z is not supported: "
0737                << newMaxZ << " -> " << oldMaxZ);
0738     throw std::invalid_argument("Shrinking the stack in z is not supported");
0739   }
0740 
0741   if (!same(newMinR, oldMinR) && newMinR > oldMinR) {
0742     ACTS_ERROR("Shrinking the stack size in r is not supported: "
0743                << newMinR << " -> " << oldMinR);
0744     throw std::invalid_argument("Shrinking the stack in r is not supported");
0745   }
0746 
0747   if (!same(newMaxR, oldMaxR) && newMaxR < oldMaxR) {
0748     ACTS_ERROR("Shrinking the stack size in r is not supported: "
0749                << newMaxR << " -> " << oldMaxR);
0750     throw std::invalid_argument("Shrinking the stack is r in not supported");
0751   }
0752 
0753   auto isGap = [this](const Volume* vol) {
0754     return std::ranges::any_of(
0755         m_gaps, [&](const auto& gap) { return vol == gap.get(); });
0756   };
0757 
0758   const auto& [firstStrategy, secondStrategy] = m_resizeStrategies;
0759 
0760   if (m_direction == AxisDirection::AxisZ) {
0761     ACTS_VERBOSE("Stack direction is z");
0762 
0763     std::vector<VolumeTuple> volumeTuples;
0764     volumeTuples.reserve(m_volumes.size());
0765     std::ranges::transform(m_volumes, std::back_inserter(volumeTuples),
0766                            [this, &gctx](const auto& volume) {
0767                              return VolumeTuple{gctx, *volume,
0768                                                 m_groupTransform};
0769                            });
0770 
0771     ACTS_VERBOSE("*** Initial volume configuration:");
0772     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0773 
0774     if (!same(newMinR, oldMinR) || !same(newMaxR, oldMaxR)) {
0775       ACTS_VERBOSE("Resize all volumes to new r bounds");
0776       for (auto& volume : volumeTuples) {
0777         volume.set({
0778             {CylinderVolumeBounds::eMinR, newMinR},
0779             {CylinderVolumeBounds::eMaxR, newMaxR},
0780         });
0781       }
0782       ACTS_VERBOSE("*** Volume configuration after r resizing:");
0783       printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0784     } else {
0785       ACTS_VERBOSE("R bounds are the same, no r resize needed");
0786     }
0787 
0788     auto printGapDimensions = [&](const VolumeTuple& gap,
0789                                   const std::string& prefix = "") {
0790       ACTS_VERBOSE(" -> gap" << prefix << ": [ " << gap.minZ() << " <- "
0791                              << gap.midZ() << " -> " << gap.maxZ()
0792                              << " ], r: [ " << gap.minR() << " <-> "
0793                              << gap.maxR() << " ]");
0794     };
0795 
0796     if (same(newHlZ, oldHlZ)) {
0797       ACTS_VERBOSE("Halflength z is the same, no z resize needed");
0798     } else {
0799       if (!same(newMinZ, oldMinZ) && newMinZ < oldMinZ) {
0800         if (firstStrategy == VolumeResizeStrategy::Expand) {
0801           ACTS_VERBOSE("Expanding first volume to new z bounds");
0802 
0803           auto& first = volumeTuples.front();
0804           double newMinZFirst = newVolume.minZ();
0805           double newMidZFirst = (newMinZFirst + first.maxZ()) / 2.0;
0806           double newHlZFirst = (first.maxZ() - newMinZFirst) / 2.0;
0807 
0808           ACTS_VERBOSE(" -> first z: [ "
0809                        << newMinZFirst << " <- " << newMidZFirst << " -> "
0810                        << first.maxZ() << " ] (hl: " << newHlZFirst << ")");
0811 
0812           first.set({{CylinderVolumeBounds::eHalfLengthZ, newHlZFirst}});
0813           first.setLocalTransform(Transform3{Translation3{0, 0, newMidZFirst}},
0814                                   m_groupTransform);
0815         } else if (firstStrategy == VolumeResizeStrategy::Gap) {
0816           ACTS_VERBOSE("Creating gap volumes to fill the new z bounds at minZ");
0817 
0818           double gap1MinZ = newVolume.minZ();
0819           double gap1MaxZ = oldVolume.minZ();
0820           double gap1HlZ = (gap1MaxZ - gap1MinZ) / 2.0;
0821           double gap1PZ = (gap1MaxZ + gap1MinZ) / 2.0;
0822 
0823           // // check if we need a new gap volume or reuse an existing one
0824           auto& candidate = volumeTuples.front();
0825           if (isGap(candidate.volume)) {
0826             ACTS_VERBOSE("~> Reusing existing gap volume at negative z");
0827 
0828             gap1HlZ =
0829                 candidate.bounds->get(CylinderVolumeBounds::eHalfLengthZ) +
0830                 gap1HlZ;
0831             gap1MaxZ = gap1MinZ + gap1HlZ * 2;
0832             gap1PZ = (gap1MaxZ + gap1MinZ) / 2.0;
0833 
0834             printGapDimensions(candidate, " before");
0835             auto gap1Bounds = std::make_shared<CylinderVolumeBounds>(
0836                 newMinR, newMaxR, gap1HlZ);
0837             auto gap1Transform = m_groupTransform * Translation3{0, 0, gap1PZ};
0838             candidate.volume->update(gctx, std::move(gap1Bounds),
0839                                      gap1Transform);
0840             candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0841             ACTS_VERBOSE("After:");
0842             printGapDimensions(candidate, " after ");
0843 
0844           } else {
0845             ACTS_VERBOSE("~> Creating new gap volume at negative z");
0846             auto gap1Bounds = std::make_shared<CylinderVolumeBounds>(
0847                 newMinR, newMaxR, gap1HlZ);
0848             auto gap1Transform = m_groupTransform * Translation3{0, 0, gap1PZ};
0849             auto gap1 = addGapVolume(gap1Transform, std::move(gap1Bounds));
0850             volumeTuples.insert(volumeTuples.begin(),
0851                                 VolumeTuple{gctx, *gap1, m_groupTransform});
0852             printGapDimensions(volumeTuples.front());
0853           }
0854         }
0855       }
0856 
0857       if (!same(newMaxZ, oldMaxZ) && newMaxZ > oldMaxZ) {
0858         if (secondStrategy == VolumeResizeStrategy::Expand) {
0859           ACTS_VERBOSE("Expanding last volume to new z bounds");
0860 
0861           auto& last = volumeTuples.back();
0862           double newMaxZLast = newVolume.maxZ();
0863           double newMidZLast = (last.minZ() + newMaxZLast) / 2.0;
0864           double newHlZLast = (newMaxZLast - last.minZ()) / 2.0;
0865 
0866           ACTS_VERBOSE(" -> last z: [ " << last.minZ() << " <- " << newMidZLast
0867                                         << " -> " << newMaxZLast
0868                                         << " ] (hl: " << newHlZLast << ")");
0869 
0870           last.set({{CylinderVolumeBounds::eHalfLengthZ, newHlZLast}});
0871           last.setLocalTransform(Transform3{Translation3{0, 0, newMidZLast}},
0872                                  m_groupTransform);
0873         } else if (secondStrategy == VolumeResizeStrategy::Gap) {
0874           ACTS_VERBOSE("Creating gap volumes to fill the new z bounds at maxZ");
0875 
0876           double gap2MinZ = oldVolume.maxZ();
0877           double gap2MaxZ = newVolume.maxZ();
0878           double gap2HlZ = (gap2MaxZ - gap2MinZ) / 2.0;
0879           double gap2PZ = (gap2MaxZ + gap2MinZ) / 2.0;
0880 
0881           // check if we need a new gap volume or reuse an existing one
0882           auto& candidate = volumeTuples.back();
0883           if (isGap(candidate.volume)) {
0884             ACTS_VERBOSE("~> Reusing existing gap volume at positive z");
0885 
0886             gap2HlZ =
0887                 candidate.bounds->get(CylinderVolumeBounds::eHalfLengthZ) +
0888                 gap2HlZ;
0889             gap2MinZ = newVolume.maxZ() - gap2HlZ * 2;
0890             gap2PZ = (gap2MaxZ + gap2MinZ) / 2.0;
0891 
0892             printGapDimensions(candidate, " before");
0893             auto gap2Bounds = std::make_shared<CylinderVolumeBounds>(
0894                 newMinR, newMaxR, gap2HlZ);
0895             auto gap2Transform = m_groupTransform * Translation3{0, 0, gap2PZ};
0896 
0897             candidate.volume->update(gctx, std::move(gap2Bounds),
0898                                      gap2Transform);
0899             candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0900             printGapDimensions(candidate, " after ");
0901           } else {
0902             ACTS_VERBOSE("~> Creating new gap volume at positive z");
0903             auto gap2Bounds = std::make_shared<CylinderVolumeBounds>(
0904                 newMinR, newMaxR, gap2HlZ);
0905             auto gap2Transform = m_groupTransform * Translation3{0, 0, gap2PZ};
0906             auto gap2 = addGapVolume(gap2Transform, std::move(gap2Bounds));
0907             volumeTuples.emplace_back(gctx, *gap2, m_groupTransform);
0908             printGapDimensions(volumeTuples.back());
0909           }
0910         }
0911       }
0912 
0913       ACTS_VERBOSE("*** Volume configuration after z resizing:");
0914       printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0915     }
0916 
0917     ACTS_VERBOSE("Commit and update outer vector of volumes");
0918     m_volumes.clear();
0919     for (auto& vt : volumeTuples) {
0920       vt.commit(gctx, logger);
0921       m_volumes.push_back(vt.volume);
0922     }
0923 
0924   } else if (m_direction == AxisDirection::AxisR) {
0925     ACTS_VERBOSE("Stack direction is r");
0926 
0927     std::vector<VolumeTuple> volumeTuples;
0928     volumeTuples.reserve(m_volumes.size());
0929     std::ranges::transform(m_volumes, std::back_inserter(volumeTuples),
0930                            [this, &gctx](const auto& volume) {
0931                              return VolumeTuple{gctx, *volume,
0932                                                 m_groupTransform};
0933                            });
0934 
0935     ACTS_VERBOSE("*** Initial volume configuration:");
0936     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0937 
0938     ACTS_VERBOSE("Resize all volumes to new z bounds and update transforms");
0939     for (auto& volume : volumeTuples) {
0940       volume.set({
0941           {CylinderVolumeBounds::eHalfLengthZ, newHlZ},
0942       });
0943       volume.setLocalTransform(newVolume.localTransform, m_groupTransform);
0944     }
0945 
0946     ACTS_VERBOSE("*** Volume configuration after z resizing:");
0947     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0948 
0949     if (same(oldMinR, newMinR) && same(oldMaxR, newMaxR)) {
0950       ACTS_VERBOSE("Radii are the same, no r resize needed");
0951     } else {
0952       auto printGapDimensions = [&](const VolumeTuple& gap,
0953                                     const std::string& prefix = "") {
0954         ACTS_VERBOSE(" -> gap" << prefix << ": [ " << gap.minZ() << " <- "
0955                                << gap.midZ() << " -> " << gap.maxZ()
0956                                << " ], r: [ " << gap.minR() << " <-> "
0957                                << gap.maxR() << " ]");
0958       };
0959 
0960       if (!same(oldMinR, newMinR) && oldMinR > newMinR) {
0961         if (firstStrategy == VolumeResizeStrategy::Expand) {
0962           // expand innermost volume
0963           auto& first = volumeTuples.front();
0964           first.set({
0965               {CylinderVolumeBounds::eMinR, newMinR},
0966           });
0967           ACTS_VERBOSE(" -> z: [ " << first.minZ() << " <- " << first.midZ()
0968                                    << " -> " << first.maxZ() << " ], r: [ "
0969                                    << first.minR() << " <-> " << first.maxR()
0970                                    << " ]");
0971         } else if (firstStrategy == VolumeResizeStrategy::Gap) {
0972           auto& candidate = volumeTuples.front();
0973           if (isGap(candidate.volume)) {
0974             ACTS_VERBOSE("~> Reusing existing gap volume at inner r");
0975             auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
0976                 candidate.volume->volumeBounds());
0977             printGapDimensions(candidate, " before");
0978             candidateCylBounds.set(CylinderVolumeBounds::eMinR, newMinR);
0979             candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0980             printGapDimensions(candidate, " after ");
0981           } else {
0982             ACTS_VERBOSE("~> Creating new gap volume at inner r");
0983             auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0984                 newMinR, oldMinR, newHlZ);
0985             auto gapTransform = newVolume.globalTransform;
0986             auto gapVolume = addGapVolume(gapTransform, gapBounds);
0987             volumeTuples.insert(
0988                 volumeTuples.begin(),
0989                 VolumeTuple{gctx, *gapVolume, m_groupTransform});
0990             auto gap = volumeTuples.front();
0991             printGapDimensions(gap);
0992           }
0993         }
0994       }
0995 
0996       if (!same(oldMaxR, newMaxR) && oldMaxR < newMaxR) {
0997         if (secondStrategy == VolumeResizeStrategy::Expand) {
0998           // expand outermost volume
0999           auto& last = volumeTuples.back();
1000           last.set({
1001               {CylinderVolumeBounds::eMaxR, newMaxR},
1002           });
1003           ACTS_VERBOSE(" -> z: [ " << last.minZ() << " <- " << last.midZ()
1004                                    << " -> " << last.maxZ() << " ], r: [ "
1005                                    << last.minR() << " <-> " << last.maxR()
1006                                    << " ]");
1007         } else if (secondStrategy == VolumeResizeStrategy::Gap) {
1008           auto& candidate = volumeTuples.back();
1009           if (isGap(candidate.volume)) {
1010             ACTS_VERBOSE("~> Reusing existing gap volume at outer r");
1011             auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
1012                 candidate.volume->volumeBounds());
1013             printGapDimensions(candidate, " before");
1014             candidateCylBounds.set(CylinderVolumeBounds::eMaxR, newMaxR);
1015             candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
1016             printGapDimensions(candidate, " after ");
1017           } else {
1018             ACTS_VERBOSE("~> Creating new gap volume at outer r");
1019             auto gapBounds = std::make_shared<CylinderVolumeBounds>(
1020                 oldMaxR, newMaxR, newHlZ);
1021             auto gapTransform = newVolume.globalTransform;
1022             auto gapVolume = addGapVolume(gapTransform, gapBounds);
1023             volumeTuples.emplace_back(gctx, *gapVolume,
1024                                       newVolume.globalTransform);
1025             auto gap = volumeTuples.back();
1026             printGapDimensions(gap);
1027           }
1028         }
1029       }
1030 
1031       ACTS_VERBOSE("*** Volume configuration after r resizing:");
1032       printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
1033     }
1034 
1035     ACTS_VERBOSE("Commit and update outer vector of volumes");
1036     m_volumes.clear();
1037     for (auto& vt : volumeTuples) {
1038       vt.commit(gctx, logger);
1039       m_volumes.push_back(vt.volume);
1040     }
1041   }
1042 
1043   Volume::update(gctx, std::move(cylBounds), newVolume.globalTransform, logger);
1044   // @TODO: We probably can reuse m_transform
1045   m_groupTransform = localToGlobalTransform(gctx);
1046 }
1047 
1048 void CylinderVolumeStack::checkNoPhiSector(const CylinderVolumeBounds& bounds,
1049                                            const Logger& logger) {
1050   if (bounds.get(CylinderVolumeBounds::eHalfPhiSector) != std::numbers::pi) {
1051     ACTS_ERROR(
1052         "CylinderVolumeStack requires all volumes to have a full "
1053         "phi sector");
1054     throw std::invalid_argument(
1055         "CylinderVolumeStack requires all volumes to have a full phi sector");
1056   }
1057 
1058   if (bounds.get(CylinderVolumeBounds::eAveragePhi) != 0.0) {
1059     ACTS_ERROR(
1060         "CylinderVolumeStack requires all volumes to have an average "
1061         "phi of 0");
1062     throw std::invalid_argument(
1063         "CylinderVolumeStack requires all volumes to have an average phi of "
1064         "0");
1065   }
1066 }
1067 
1068 }  // namespace Acts