Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:22

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