Back to home page

sPhenix code displayed by LXR

 
 

    


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

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