Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-18 08:11:21

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