Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-22 09:33:44

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/CuboidVolumeStack.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Tolerance.hpp"
0013 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0014 #include "Acts/Utilities/AxisDefinitions.hpp"
0015 #include "Acts/Utilities/Logger.hpp"
0016 
0017 #include <algorithm>
0018 #include <cstddef>
0019 #include <initializer_list>
0020 #include <iomanip>
0021 #include <memory>
0022 #include <numeric>
0023 #include <sstream>
0024 #include <stdexcept>
0025 #include <utility>
0026 
0027 namespace Acts {
0028 
0029 struct CuboidVolumeStack::VolumeTuple {
0030   Volume* volume{};
0031   const CuboidVolumeBounds* bounds{};
0032   std::shared_ptr<CuboidVolumeBounds> updatedBounds{};
0033   Transform3 localTransform = Transform3::Identity();
0034   Transform3 globalTransform = Transform3::Identity();
0035 
0036   bool transformDirty = false;
0037 
0038   VolumeTuple(Volume& volume_, const Transform3& groupTransform)
0039       : volume{&volume_},
0040         localTransform{groupTransform.inverse() * volume_.transform()},
0041         globalTransform{volume_.transform()} {
0042     bounds = dynamic_cast<const CuboidVolumeBounds*>(&volume_.volumeBounds());
0043     assert(bounds != nullptr);
0044     updatedBounds = std::make_shared<CuboidVolumeBounds>(*bounds);
0045   }
0046 
0047   double mid(AxisDirection direction) const {
0048     return localTransform.translation()[axisToIndex(direction)];
0049   }
0050   double halfLength(AxisDirection direction) const {
0051     return updatedBounds->get(CuboidVolumeBounds::fromAxisDirection(direction));
0052   }
0053   double min(AxisDirection direction) const {
0054     return mid(direction) - halfLength(direction);
0055   }
0056   double max(AxisDirection direction) const {
0057     return mid(direction) + halfLength(direction);
0058   }
0059 
0060   void set(
0061       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>
0062           keyValues) {
0063     updatedBounds->set(keyValues);
0064   }
0065 
0066   void setLocalTransform(const Transform3& transform,
0067                          const Transform3& groupTransform) {
0068     localTransform = transform;
0069     globalTransform = groupTransform * localTransform;
0070     transformDirty = true;
0071   }
0072 
0073   void commit(const Logger& logger) {
0074     // make a copy so we can't accidentally modify in-place
0075     auto copy = std::make_shared<CuboidVolumeBounds>(*updatedBounds);
0076 
0077     std::optional<Transform3> transform = std::nullopt;
0078     if (transformDirty) {
0079       transform = globalTransform;
0080     }
0081 
0082     volume->update(std::move(updatedBounds), transform, logger);
0083     bounds = copy.get();
0084     updatedBounds = std::move(copy);
0085     transformDirty = false;
0086   }
0087 };
0088 
0089 std::size_t CuboidVolumeStack::axisToIndex(AxisDirection direction) {
0090   switch (direction) {
0091     case AxisDirection::AxisX:
0092       return 0;
0093       break;
0094     case AxisDirection::AxisY:
0095       return 1;
0096       break;
0097     case AxisDirection::AxisZ:
0098       return 2;
0099       break;
0100     default:
0101       throw std::invalid_argument("Invalid axis direction");
0102   }
0103 }
0104 
0105 std::pair<AxisDirection, AxisDirection> CuboidVolumeStack::getOrthogonalAxes(
0106     AxisDirection direction) {
0107   switch (direction) {
0108     case AxisDirection::AxisX:
0109       return {AxisDirection::AxisY, AxisDirection::AxisZ};
0110       break;
0111     case AxisDirection::AxisY:
0112       return {AxisDirection::AxisZ, AxisDirection::AxisX};
0113       break;
0114     case AxisDirection::AxisZ:
0115       return {AxisDirection::AxisX, AxisDirection::AxisY};
0116       break;
0117     default:
0118       throw std::invalid_argument("Invalid axis direction");
0119   }
0120 }
0121 
0122 CuboidVolumeStack::CuboidVolumeStack(std::vector<Volume*>& volumes,
0123                                      AxisDirection direction,
0124                                      VolumeAttachmentStrategy strategy,
0125                                      VolumeResizeStrategy resizeStrategy,
0126                                      const Logger& logger)
0127     : Volume(initialVolume(volumes)),
0128       m_dir(direction),
0129       m_resizeStrategy(resizeStrategy),
0130       m_volumes(volumes) {
0131   std::tie(m_dirOrth1, m_dirOrth2) = getOrthogonalAxes(m_dir);
0132 
0133   initializeOuterVolume(strategy, logger);
0134 }
0135 
0136 CuboidVolumeStack::CuboidVolumeStack(std::vector<Volume*>& volumes,
0137                                      const Vector3& direction,
0138                                      VolumeAttachmentStrategy strategy,
0139                                      VolumeResizeStrategy resizeStrategy,
0140                                      const Logger& logger)
0141     : Volume(initialVolume(volumes)),
0142       m_resizeStrategy(resizeStrategy),
0143       m_volumes(volumes) {
0144   Vector3 localDirVector =
0145       m_volumes.front()->transform().rotation().inverse() * direction;
0146   if ((localDirVector - Vector3::UnitX()).norm() < 1e-4) {
0147     m_dir = AxisDirection::AxisX;
0148   } else if ((localDirVector - Vector3::UnitY()).norm() < 1e-4) {
0149     m_dir = AxisDirection::AxisY;
0150   } else if ((localDirVector - Vector3::UnitZ()).norm() < 1e-4) {
0151     m_dir = AxisDirection::AxisZ;
0152   } else {
0153     throw std::invalid_argument("CuboidVolumeStack: Invalid axis direction");
0154   }
0155 
0156   std::tie(m_dirOrth1, m_dirOrth2) = getOrthogonalAxes(m_dir);
0157 
0158   initializeOuterVolume(strategy, logger);
0159 }
0160 
0161 Volume& CuboidVolumeStack::initialVolume(const std::vector<Volume*>& volumes) {
0162   if (volumes.empty()) {
0163     throw std::invalid_argument(
0164         "CuboidVolumeStack requires at least one volume");
0165   }
0166   return *volumes.front();
0167 }
0168 
0169 void CuboidVolumeStack::initializeOuterVolume(VolumeAttachmentStrategy strategy,
0170                                               const Logger& logger) {
0171   ACTS_DEBUG("Creating CuboidVolumeStack from " << m_volumes.size()
0172                                                 << " volumes in direction "
0173                                                 << axisDirectionName(m_dir));
0174   if (m_volumes.empty()) {
0175     throw std::invalid_argument(
0176         "CuboidVolumeStack requires at least one volume");
0177   }
0178 
0179   if (m_dir != Acts::AxisDirection::AxisX &&
0180       m_dir != Acts::AxisDirection::AxisY &&
0181       m_dir != Acts::AxisDirection::AxisZ) {
0182     throw std::invalid_argument(axisDirectionName(m_dir) +
0183                                 " is not supported ");
0184   }
0185 
0186   // For alignment check, we have to pick one of the volumes as the base
0187   m_groupTransform = m_volumes.front()->transform();
0188   ACTS_VERBOSE("Initial group transform is:\n" << m_groupTransform.matrix());
0189 
0190   std::vector<VolumeTuple> volumeTuples;
0191   volumeTuples.reserve(m_volumes.size());
0192 
0193   for (const auto& volume : m_volumes) {
0194     const auto* cuboidBounds =
0195         dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0196     if (cuboidBounds == nullptr) {
0197       throw std::invalid_argument{
0198           "CuboidVolumeStack requires all volumes to "
0199           "have CuboidVolumeBounds"};
0200     }
0201 
0202     volumeTuples.emplace_back(*volume, m_groupTransform);
0203   }
0204 
0205   ACTS_DEBUG("*** Initial volume configuration:");
0206   printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0207 
0208   if (m_volumes.size() == 1) {
0209     ACTS_VERBOSE("Only one volume, returning");
0210     setTransform(m_volumes.front()->transform());
0211     const auto* bounds = dynamic_cast<const CuboidVolumeBounds*>(
0212         &m_volumes.front()->volumeBounds());
0213     assert(bounds != nullptr && "Volume bounds are not cuboid bounds");
0214     Volume::update(std::make_shared<CuboidVolumeBounds>(*bounds), std::nullopt,
0215                    logger);
0216     ACTS_VERBOSE("Transform is now: " << m_transform.matrix());
0217     return;
0218   }
0219 
0220   ACTS_VERBOSE("Checking volume alignment");
0221   checkVolumeAlignment(volumeTuples, logger);
0222 
0223   auto dirIdx = axisToIndex(m_dir);
0224   ACTS_VERBOSE("Sorting by volume " << axisDirectionName(m_dir) << " position");
0225   std::ranges::sort(volumeTuples, {}, [dirIdx](const auto& v) {
0226     return v.localTransform.translation()[dirIdx];
0227   });
0228   ACTS_VERBOSE("Checking for overlaps and attaching volumes in "
0229                << axisDirectionName(m_dir));
0230   std::vector<VolumeTuple> gapVolumes =
0231       checkOverlapAndAttach(volumeTuples, strategy, logger);
0232 
0233   ACTS_VERBOSE("Appending " << 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 " << axisDirectionName(m_dir)
0239                                                  << " attachment:");
0240   printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0241 
0242   ACTS_VERBOSE("Synchronizing bounds in " << axisDirectionName(m_dirOrth1)
0243                                           << "/"
0244                                           << axisDirectionName(m_dirOrth2));
0245   const auto [hl1, hl2] = synchronizeBounds(volumeTuples, logger);
0246 
0247   for (auto& vt : volumeTuples) {
0248     ACTS_VERBOSE("Updated bounds for volume at "
0249                  << axisDirectionName(m_dir) << ": "
0250                  << vt.localTransform.translation()[dirIdx]);
0251     ACTS_VERBOSE(*vt.updatedBounds);
0252 
0253     vt.commit(logger);
0254   }
0255 
0256   ACTS_VERBOSE("*** Volume configuration after "
0257                << axisDirectionName(m_dirOrth1) << "/"
0258                << axisDirectionName(m_dirOrth2) << " synchronization:");
0259   printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0260 
0261   std::ranges::sort(volumeTuples, {},
0262                     [*this](const auto& v) { return v.mid(m_dir); });
0263 
0264   m_volumes.clear();
0265   for (const auto& vt : volumeTuples) {
0266     m_volumes.push_back(vt.volume);
0267   }
0268 
0269   ACTS_DEBUG("*** Volume configuration after final " << axisDirectionName(m_dir)
0270                                                      << " sorting:");
0271   printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0272 
0273   double min = volumeTuples.front().min(m_dir);
0274   double max = volumeTuples.back().max(m_dir);
0275 
0276   double mid = std::midpoint(min, max);
0277   double hl = std::midpoint(max, -min);
0278 
0279   Translation3 translation(Vector3::Unit(dirIdx) * mid);
0280   m_transform = m_groupTransform * translation;
0281   auto bounds = std::make_shared<CuboidVolumeBounds>(
0282       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0283           {CuboidVolumeBounds::fromAxisDirection(m_dir), hl},
0284           {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1), hl1},
0285           {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2), hl2}});
0286   Volume::update(bounds, std::nullopt, logger);
0287   ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0288   ACTS_DEBUG("Outer transform / new group transform is:\n"
0289              << m_transform.matrix());
0290 
0291   // Update group transform to the new center
0292   // @TODO: We probably can reuse m_transform
0293   m_groupTransform = m_transform;
0294 }
0295 
0296 void CuboidVolumeStack::overlapPrint(const CuboidVolumeStack::VolumeTuple& a,
0297                                      const CuboidVolumeStack::VolumeTuple& b,
0298                                      const Logger& logger) {
0299   if (logger().doPrint(Acts::Logging::DEBUG)) {
0300     std::stringstream ss;
0301     ss << std::fixed;
0302     ss << std::setprecision(3);
0303     ss << std::setfill(' ');
0304 
0305     int w = 9;
0306 
0307     ACTS_VERBOSE("Checking overlap between");
0308     ss << " - " << " " << axisDirectionName(m_dir) << ": [ " << std::setw(w)
0309        << a.min(m_dir) << " <- " << std::setw(w) << a.mid(m_dir) << " -> "
0310        << std::setw(w) << a.max(m_dir) << " ]";
0311     ACTS_VERBOSE(ss.str());
0312 
0313     ss.str("");
0314     ss << " - " << " " << axisDirectionName(m_dir) << ": [ " << std::setw(w)
0315        << b.min(m_dir) << " <- " << std::setw(w) << b.mid(m_dir) << " -> "
0316        << std::setw(w) << b.max(m_dir) << " ]";
0317     ACTS_VERBOSE(ss.str());
0318   }
0319 }
0320 
0321 std::vector<CuboidVolumeStack::VolumeTuple>
0322 CuboidVolumeStack::checkOverlapAndAttach(std::vector<VolumeTuple>& volumes,
0323                                          VolumeAttachmentStrategy strategy,
0324                                          const Logger& logger) {
0325   // Preconditions: volumes are sorted along stacking direction
0326   auto dirIdx = axisToIndex(m_dir);
0327   auto dirBoundIdx = CuboidVolumeBounds::fromAxisDirection(m_dir);
0328 
0329   std::vector<VolumeTuple> gapVolumes;
0330   for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0331     std::size_t j = i + 1;
0332     auto& a = volumes.at(i);
0333     auto& b = volumes.at(j);
0334 
0335     overlapPrint(a, b, logger);
0336 
0337     // TODO: What's a good tolerance?
0338     constexpr auto tolerance = s_onSurfaceTolerance;
0339     if (a.max(m_dir) - tolerance > b.min(m_dir)) {
0340       ACTS_ERROR(" -> Overlap in " << axisDirectionName(m_dir));
0341       throw std::invalid_argument("Volumes overlap in " +
0342                                   axisDirectionName(m_dir));
0343     } else {
0344       ACTS_VERBOSE(" -> No overlap");
0345     }
0346 
0347     if (std::abs(a.max(m_dir) - b.min(m_dir)) < tolerance) {
0348       ACTS_VERBOSE("No gap between volumes, no attachment needed");
0349     } else {
0350       double gapWidth = b.min(m_dir) - a.max(m_dir);
0351       ACTS_VERBOSE("Gap width: " << gapWidth);
0352 
0353       ACTS_VERBOSE("Synchronizing bounds in "
0354                    << axisDirectionName(m_dir)
0355                    << " with strategy: " << strategy);
0356       switch (strategy) {
0357         case VolumeAttachmentStrategy::Midpoint: {
0358           ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0359 
0360           double aMidNew = (a.min(m_dir) + a.max(m_dir)) / 2.0 + gapWidth / 4.0;
0361           double aHlNew = a.halfLength(m_dir) + gapWidth / 4.0;
0362           ACTS_VERBOSE("  - New halflength for first volume: " << aHlNew);
0363           ACTS_VERBOSE("  - New bounds for first volume: ["
0364                        << (aMidNew - aHlNew) << " <- " << aMidNew << " -> "
0365                        << (aMidNew + aHlNew) << "]");
0366 
0367           assert(std::abs(a.min(m_dir) - (aMidNew - aHlNew)) < 1e-9 &&
0368                  "Volume shrunk");
0369           assert(aHlNew >= a.halfLength(m_dir) && "Volume shrunk");
0370 
0371           double bMidNew = (b.min(m_dir) + b.max(m_dir)) / 2.0 - gapWidth / 4.0;
0372           double bHlNew = b.halfLength(m_dir) + gapWidth / 4.0;
0373           ACTS_VERBOSE("  - New halflength for second volume: " << bHlNew);
0374           ACTS_VERBOSE("  - New bounds for second volume: ["
0375                        << (bMidNew - bHlNew) << " <- " << bMidNew << " -> "
0376                        << (bMidNew + bHlNew) << "]");
0377 
0378           assert(bHlNew >= b.halfLength(m_dir) && "Volume shrunk");
0379           assert(std::abs(b.max(m_dir) - (bMidNew + bHlNew)) < 1e-9 &&
0380                  "Volume shrunk");
0381 
0382           Translation3 translationA(Vector3::Unit(dirIdx) * aMidNew);
0383           a.setLocalTransform(Transform3{translationA}, m_groupTransform);
0384           a.updatedBounds->set(dirBoundIdx, aHlNew);
0385 
0386           Translation3 translationB(Vector3::Unit(dirIdx) * bMidNew);
0387           b.setLocalTransform(Transform3{translationB}, m_groupTransform);
0388           b.updatedBounds->set(dirBoundIdx, bHlNew);
0389 
0390           break;
0391         }
0392         case VolumeAttachmentStrategy::First: {
0393           ACTS_VERBOSE(" -> Strategy: Expand first volume");
0394           double aMidNew = (a.min(m_dir) + b.min(m_dir)) / 2.0;
0395           double aHlNew = (b.min(m_dir) - a.min(m_dir)) / 2.0;
0396           ACTS_VERBOSE("  - Gap width: " << gapWidth);
0397           ACTS_VERBOSE("  - New bounds for first volume: ["
0398                        << (aMidNew - aHlNew) << " <- " << aMidNew << " -> "
0399                        << (aMidNew + aHlNew) << "]");
0400 
0401           assert(std::abs(a.min(m_dir) - (aMidNew - aHlNew)) < 1e-9 &&
0402                  "Volume shrunk");
0403           assert(aHlNew >= a.halfLength(m_dir) && "Volume shrunk");
0404 
0405           Translation3 translationA(Vector3::Unit(dirIdx) * aMidNew);
0406           a.setLocalTransform(Transform3{translationA}, m_groupTransform);
0407           a.updatedBounds->set(dirBoundIdx, aHlNew);
0408 
0409           break;
0410         }
0411         case VolumeAttachmentStrategy::Second: {
0412           ACTS_VERBOSE(" -> Strategy: Expand second volume");
0413           double bMidNew = (a.max(m_dir) + b.max(m_dir)) / 2.0;
0414           double bHlNew = (b.max(m_dir) - a.max(m_dir)) / 2.0;
0415           ACTS_VERBOSE("  - New halflength for second volume: " << bHlNew);
0416           ACTS_VERBOSE("  - New bounds for second volume: ["
0417                        << (bMidNew - bHlNew) << " <- " << bMidNew << " -> "
0418                        << (bMidNew + bHlNew) << "]");
0419 
0420           assert(bHlNew >= b.halfLength(m_dir) && "Volume shrunk");
0421           assert(std::abs(b.max(m_dir) - (bMidNew + bHlNew)) < 1e-9 &&
0422                  "Volume shrunk");
0423 
0424           Translation3 translationB(Vector3::Unit(dirIdx) * bMidNew);
0425           b.setLocalTransform(Transform3{translationB}, m_groupTransform);
0426           b.updatedBounds->set(dirBoundIdx, bHlNew);
0427           break;
0428         }
0429         case VolumeAttachmentStrategy::Gap: {
0430           ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0431           double gapHl = (b.min(m_dir) - a.max(m_dir)) / 2.0;
0432           double gapMid = (b.min(m_dir) + a.max(m_dir)) / 2.0;
0433 
0434           ACTS_VERBOSE("  - Gap half length: " << gapHl << " at "
0435                                                << axisDirectionName(m_dir)
0436                                                << ": " << gapMid);
0437 
0438           Translation3 gapTranslation(Vector3::Unit(dirIdx) * gapMid);
0439 
0440           double min1 = std::min(a.min(m_dirOrth1), b.min(m_dirOrth1));
0441           double max1 = std::max(a.max(m_dirOrth1), b.max(m_dirOrth1));
0442 
0443           double min2 = std::min(a.min(m_dirOrth2), b.min(m_dirOrth2));
0444           double max2 = std::max(a.max(m_dirOrth2), b.max(m_dirOrth2));
0445 
0446           Transform3 gapLocalTransform{gapTranslation};
0447           Transform3 gapGlobalTransform = m_groupTransform * gapLocalTransform;
0448 
0449           auto gapBounds = std::make_shared<CuboidVolumeBounds>(
0450               std::initializer_list<
0451                   std::pair<CuboidVolumeBounds::BoundValues, double>>{
0452                   {CuboidVolumeBounds::fromAxisDirection(m_dir), gapHl},
0453                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1),
0454                    (max1 - min1) / 2},
0455                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2),
0456                    (max2 - min2) / 2}});
0457           auto gap = addGapVolume(gapGlobalTransform, gapBounds);
0458           gapVolumes.emplace_back(*gap, m_groupTransform);
0459 
0460           break;
0461         }
0462         default:
0463           ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0464           std::stringstream ss;
0465           ss << strategy;
0466           throw std::invalid_argument("Attachment strategy " + ss.str() +
0467                                       " not implemented");
0468       }
0469     }
0470   }
0471 
0472   return gapVolumes;
0473 }
0474 
0475 void CuboidVolumeStack::printVolumeSequence(
0476     const std::vector<VolumeTuple>& volumes, const Logger& logger,
0477     Acts::Logging::Level lvl) {
0478   if (!logger().doPrint(lvl)) {
0479     return;
0480   }
0481   for (const auto& vt : volumes) {
0482     std::stringstream ss;
0483     ss << std::fixed;
0484     ss << std::setprecision(3);
0485     ss << std::setfill(' ');
0486 
0487     int w = 9;
0488 
0489     for (const auto& axis :
0490          {AxisDirection::AxisX, AxisDirection::AxisY, AxisDirection::AxisZ}) {
0491       ss << axisDirectionName(axis) << ": [ " << std::setw(w) << vt.min(axis)
0492          << " <- " << std::setw(w) << vt.mid(axis) << " -> " << std::setw(w)
0493          << vt.max(axis) << " ]\n";
0494     }
0495     logger().log(lvl, ss.str());
0496   }
0497 }
0498 
0499 void CuboidVolumeStack::checkVolumeAlignment(
0500     const std::vector<VolumeTuple>& volumes, const Logger& logger) const {
0501   std::size_t n = 0;
0502   auto dirIdx = axisToIndex(m_dir);
0503   auto dirOrth1Idx = axisToIndex(m_dirOrth1);
0504   auto dirOrth2Idx = axisToIndex(m_dirOrth2);
0505 
0506   for (auto& vt : volumes) {
0507     ACTS_VERBOSE("Checking volume #"
0508                  << n << " at " << axisDirectionName(m_dir) << ": "
0509                  << vt.localTransform.translation()[dirIdx]);
0510     ACTS_VERBOSE("- Local transform is:\n" << vt.localTransform.matrix());
0511 
0512     // @TODO: Rotation precision?
0513     constexpr auto tolerance = s_onSurfaceTolerance;
0514 
0515     // In the group coordinate system:
0516 
0517     // a) the volumes cannot have any relative rotation
0518     if ((vt.localTransform.rotation().matrix() - RotationMatrix3::Identity())
0519             .norm() > tolerance) {
0520       ACTS_ERROR("Volumes are not aligned: rotation is different");
0521       throw std::invalid_argument(
0522           "Volumes are not aligned: rotation is different");
0523     }
0524 
0525     ACTS_VERBOSE(" -> Rotation is ok!");
0526 
0527     // b) the volumes cannot have translation in orthogonal directions
0528     if (std::abs(vt.localTransform.translation()[dirOrth1Idx]) > tolerance ||
0529         std::abs(vt.localTransform.translation()[dirOrth2Idx]) > tolerance) {
0530       ACTS_ERROR("Volumes are not aligned: translation in "
0531                  << axisDirectionName(m_dirOrth1) << " or "
0532                  << axisDirectionName(m_dirOrth2));
0533       throw std::invalid_argument("Volumes are not aligned: translation in " +
0534                                   axisDirectionName(m_dirOrth1) + " or " +
0535                                   axisDirectionName(m_dirOrth2));
0536     }
0537     ACTS_VERBOSE(" -> Translation in " << axisDirectionName(m_dirOrth1) << "/"
0538                                        << axisDirectionName(m_dirOrth2)
0539                                        << " is ok!");
0540 
0541     n++;
0542   }
0543 }
0544 
0545 std::pair<double, double> CuboidVolumeStack::synchronizeBounds(
0546     std::vector<VolumeTuple>& volumes, const Logger& logger) {
0547   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(m_dirOrth1);
0548   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(m_dirOrth2);
0549 
0550   const double maxHl1 =
0551       std::max_element(volumes.begin(), volumes.end(),
0552                        [boundDirOrth1](const auto& a, const auto& b) {
0553                          return a.bounds->get(boundDirOrth1) <
0554                                 b.bounds->get(boundDirOrth1);
0555                        })
0556           ->bounds->get(boundDirOrth1);
0557   const double maxHl2 =
0558       std::max_element(volumes.begin(), volumes.end(),
0559                        [boundDirOrth2](const auto& a, const auto& b) {
0560                          return a.bounds->get(boundDirOrth2) <
0561                                 b.bounds->get(boundDirOrth2);
0562                        })
0563           ->bounds->get(boundDirOrth2);
0564   ACTS_VERBOSE("Found: half length " << axisDirectionName(m_dirOrth1) << ":"
0565                                      << maxHl1 << ", half length "
0566                                      << axisDirectionName(m_dirOrth2) << ":"
0567                                      << maxHl2);
0568 
0569   for (auto& vt : volumes) {
0570     vt.set({
0571         {boundDirOrth1, maxHl1},
0572         {boundDirOrth2, maxHl2},
0573     });
0574   }
0575 
0576   return {maxHl1, maxHl2};
0577 }
0578 
0579 void CuboidVolumeStack::update(std::shared_ptr<VolumeBounds> volbounds,
0580                                std::optional<Transform3> transform,
0581                                const Logger& logger) {
0582   ACTS_DEBUG("Resizing CuboidVolumeStack with strategy: " << m_resizeStrategy);
0583   ACTS_DEBUG("Currently have " << m_volumes.size() << " children");
0584   ACTS_DEBUG(m_gaps.size() << " gaps");
0585   for (const auto& v : m_volumes) {
0586     ACTS_DEBUG(" - volume bounds: \n" << v->volumeBounds());
0587     ACTS_DEBUG("          transform: \n" << v->transform().matrix());
0588   }
0589 
0590   ACTS_DEBUG("New bounds are: \n" << *volbounds);
0591 
0592   auto bounds = std::dynamic_pointer_cast<CuboidVolumeBounds>(volbounds);
0593   if (bounds == nullptr) {
0594     throw std::invalid_argument(
0595         "CuboidVolumeStack requires CuboidVolumeBounds");
0596   }
0597 
0598   if (*bounds == volumeBounds()) {
0599     ACTS_VERBOSE("Bounds are the same, no resize needed");
0600     return;
0601   }
0602 
0603   ACTS_VERBOSE("Group transform is:\n" << m_groupTransform.matrix());
0604   ACTS_VERBOSE("Current transform is:\n" << m_transform.matrix());
0605   if (transform.has_value()) {
0606     ACTS_VERBOSE("Input transform:\n" << transform.value().matrix());
0607   }
0608 
0609   VolumeTuple oldVolume{*this, m_transform};
0610   VolumeTuple newVolume{*this, m_transform};
0611   newVolume.updatedBounds = std::make_shared<CuboidVolumeBounds>(*bounds);
0612   newVolume.globalTransform = transform.value_or(m_transform);
0613   newVolume.localTransform = m_transform.inverse() * newVolume.globalTransform;
0614 
0615   if (!transform.has_value()) {
0616     ACTS_VERBOSE("Local transform does not change");
0617   } else {
0618     ACTS_VERBOSE("Local transform changes from\n"
0619                  << m_groupTransform.matrix() << "\nto\n"
0620                  << newVolume.localTransform.matrix());
0621     ACTS_VERBOSE("Checking transform consistency");
0622 
0623     std::vector<VolumeTuple> volTemp{newVolume};
0624     checkVolumeAlignment(volTemp, logger);
0625   }
0626 
0627   constexpr auto tolerance = s_onSurfaceTolerance;
0628   auto same = [](double a, double b) { return std::abs(a - b) < tolerance; };
0629 
0630   for (const auto& dir : {m_dir, m_dirOrth1, m_dirOrth2}) {
0631     const double newMin = newVolume.min(dir);
0632     const double newMax = newVolume.max(dir);
0633     const double newMid = newVolume.mid(dir);
0634     const double newHl = newVolume.halfLength(dir);
0635 
0636     const double oldMin = oldVolume.min(dir);
0637     const double oldMax = oldVolume.max(dir);
0638     const double oldMid = oldVolume.mid(dir);
0639     const double oldHl = oldVolume.halfLength(dir);
0640 
0641     ACTS_VERBOSE("Previous bounds are: " << axisDirectionName(dir) << ": [ "
0642                                          << oldMin << " <- " << oldMid << " -> "
0643                                          << oldMax << " ] (" << oldHl << ")\n");
0644     ACTS_VERBOSE("New bounds are: " << axisDirectionName(dir) << ":      [ "
0645                                     << newMin << " <- " << newMid << " -> "
0646                                     << newMax << " ] (" << newHl << ")\n");
0647 
0648     if (!same(newMin, oldMin) && newMin > oldMin) {
0649       ACTS_ERROR("Shrinking the stack size in "
0650                  << axisDirectionName(dir) << " is not supported: " << newMin
0651                  << " -> " << oldMin);
0652       throw std::invalid_argument("Shrinking the stack in " +
0653                                   axisDirectionName(dir) + " is not supported");
0654     }
0655 
0656     if (!same(newMax, oldMax) && newMax < oldMax) {
0657       ACTS_ERROR("Shrinking the stack size in "
0658                  << axisDirectionName(dir) << " is not supported: " << newMax
0659                  << " -> " << oldMax);
0660       throw std::invalid_argument("Shrinking the stack in " +
0661                                   axisDirectionName(dir) + " is not supported");
0662     }
0663   }
0664   auto isGap = [this](const Volume* vol) {
0665     return std::ranges::any_of(
0666         m_gaps, [&](const auto& gap) { return vol == gap.get(); });
0667   };
0668   ACTS_VERBOSE("Stack direction is " << axisDirectionName(m_dir));
0669 
0670   std::vector<VolumeTuple> volumeTuples;
0671   volumeTuples.reserve(m_volumes.size());
0672   std::transform(m_volumes.begin(), m_volumes.end(),
0673                  std::back_inserter(volumeTuples), [this](const auto& volume) {
0674                    return VolumeTuple{*volume, m_groupTransform};
0675                  });
0676 
0677   ACTS_VERBOSE("*** Initial volume configuration:");
0678   printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0679   for (const auto& dir : {m_dirOrth1, m_dirOrth2}) {
0680     if (!same(newVolume.min(dir), oldVolume.min(dir)) ||
0681         !same(newVolume.max(dir), oldVolume.max(dir))) {
0682       ACTS_VERBOSE("Resize all volumes to new " << axisDirectionName(dir)
0683                                                 << " bounds");
0684       for (auto& volume : volumeTuples) {
0685         volume.set({{CuboidVolumeBounds::fromAxisDirection(dir),
0686                      newVolume.halfLength(dir)}});
0687       }
0688       ACTS_VERBOSE("*** Volume configuration after " << axisDirectionName(dir)
0689                                                      << " resizing:");
0690       printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0691     } else {
0692       ACTS_VERBOSE(axisDirectionName(dir)
0693                    << " bounds are the same, no " << axisDirectionName(dir)
0694                    << " resize needed");
0695     }
0696   }
0697 
0698   if (same(newVolume.halfLength(m_dir), oldVolume.halfLength(m_dir))) {
0699     ACTS_VERBOSE("Halflength " << axisDirectionName(m_dir) << "is the same, no "
0700                                << axisDirectionName(m_dir) << "resize needed");
0701   } else {
0702     auto dirIdx = axisToIndex(m_dir);
0703     auto boundDirIdx = CuboidVolumeBounds::fromAxisDirection(m_dir);
0704     if (m_resizeStrategy == VolumeResizeStrategy::Expand) {
0705       if (newVolume.min(m_dir) < oldVolume.min(m_dir)) {
0706         ACTS_VERBOSE("Expanding first volume to new "
0707                      << axisDirectionName(m_dir) << "bounds");
0708 
0709         auto& first = volumeTuples.front();
0710         double newMinFirst = newVolume.min(m_dir);
0711         double newMidFirst = (newMinFirst + first.max(m_dir)) / 2.0;
0712         double newHlFirst = (first.max(m_dir) - newMinFirst) / 2.0;
0713 
0714         ACTS_VERBOSE(" -> first " << axisDirectionName(m_dir) << ": [ "
0715                                   << newMinFirst << " <- " << newMidFirst
0716                                   << " -> " << first.max(m_dir)
0717                                   << " ] (hl: " << newHlFirst << ")");
0718 
0719         Translation3 translation(Vector3::Unit(dirIdx) * newMidFirst);
0720         first.set({{boundDirIdx, newHlFirst}});
0721         first.setLocalTransform(Transform3{translation}, m_groupTransform);
0722       }
0723 
0724       if (newVolume.max(m_dir) > oldVolume.max(m_dir)) {
0725         ACTS_VERBOSE("Expanding last volume to new " << axisDirectionName(m_dir)
0726                                                      << " bounds");
0727 
0728         auto& last = volumeTuples.back();
0729         double newMaxLast = newVolume.max(m_dir);
0730         double newMidLast = (last.min(m_dir) + newMaxLast) / 2.0;
0731         double newHlLast = (newMaxLast - last.min(m_dir)) / 2.0;
0732 
0733         ACTS_VERBOSE(" -> last " << axisDirectionName(m_dir) << ": [ "
0734                                  << last.min(m_dir) << " <- " << newMidLast
0735                                  << " -> " << newMaxLast
0736                                  << " ] (hl: " << newHlLast << ")");
0737 
0738         Translation3 translation(Vector3::Unit(dirIdx) * newMidLast);
0739         last.set({{boundDirIdx, newHlLast}});
0740         last.setLocalTransform(Transform3{translation}, m_groupTransform);
0741       }
0742     } else if (m_resizeStrategy == VolumeResizeStrategy::Gap) {
0743       ACTS_VERBOSE("Creating gap volumes to fill the new "
0744                    << axisDirectionName(m_dir) << " bounds");
0745 
0746       auto printGapDimensions = [&](const VolumeTuple& gap,
0747                                     const std::string& prefix = "") {
0748         for (const auto& dir : {m_dir, m_dirOrth1, m_dirOrth2}) {
0749           ACTS_VERBOSE(" -> gap" << prefix << ": " << axisDirectionName(dir)
0750                                  << ": [ " << gap.min(m_dir) << " <- "
0751                                  << gap.mid(dir) << " -> " << gap.max(dir)
0752                                  << " ]");
0753         }
0754       };
0755 
0756       if (!same(newVolume.min(m_dir), oldVolume.min(m_dir)) &&
0757           newVolume.min(m_dir) < oldVolume.min(m_dir)) {
0758         double gap1Min = newVolume.min(m_dir);
0759         double gap1Max = oldVolume.min(m_dir);
0760         double gap1Hl = (gap1Max - gap1Min) / 2.0;
0761         double gap1P = (gap1Max + gap1Min) / 2.0;
0762 
0763         // check if we need a new gap volume or reuse an existing one
0764         auto& candidate = volumeTuples.front();
0765         if (isGap(candidate.volume)) {
0766           ACTS_VERBOSE("~> Reusing existing gap volume at negative "
0767                        << axisDirectionName(m_dir));
0768 
0769           gap1Hl = candidate.bounds->get(
0770                        CuboidVolumeBounds::fromAxisDirection(m_dir)) +
0771                    gap1Hl;
0772           gap1Max = gap1Min + gap1Hl * 2;
0773           gap1P = (gap1Max + gap1Min) / 2.0;
0774 
0775           printGapDimensions(candidate, " before");
0776 
0777           auto gap1Bounds = std::make_shared<CuboidVolumeBounds>(
0778               std::initializer_list<
0779                   std::pair<CuboidVolumeBounds::BoundValues, double>>{
0780                   {CuboidVolumeBounds::fromAxisDirection(m_dir), gap1Hl},
0781                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1),
0782                    newVolume.halfLength(m_dirOrth1)},
0783                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2),
0784                    newVolume.halfLength(m_dirOrth2)}});
0785           Translation3 gap1Translation(Vector3::Unit(dirIdx) * gap1P);
0786           Transform3 gap1Transform = m_groupTransform * gap1Translation;
0787           candidate.volume->update(std::move(gap1Bounds), gap1Transform);
0788           candidate = VolumeTuple{*candidate.volume, m_groupTransform};
0789           ACTS_VERBOSE("After:");
0790           printGapDimensions(candidate, " after ");
0791 
0792         } else {
0793           ACTS_VERBOSE("~> Creating new gap volume at negative ");
0794           auto gap1Bounds = std::make_shared<CuboidVolumeBounds>(
0795               std::initializer_list<
0796                   std::pair<CuboidVolumeBounds::BoundValues, double>>{
0797                   {CuboidVolumeBounds::fromAxisDirection(m_dir), gap1Hl},
0798                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1),
0799                    newVolume.halfLength(m_dirOrth1)},
0800                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2),
0801                    newVolume.halfLength(m_dirOrth2)}});
0802           Translation3 gap1Translation(Vector3::Unit(dirIdx) * gap1P);
0803           Transform3 gap1Transform = m_groupTransform * gap1Translation;
0804           auto gap1 = addGapVolume(gap1Transform, std::move(gap1Bounds));
0805           volumeTuples.insert(volumeTuples.begin(),
0806                               VolumeTuple{*gap1, m_groupTransform});
0807           printGapDimensions(volumeTuples.front());
0808         }
0809       }
0810 
0811       if (!same(newVolume.max(m_dir), oldVolume.max(m_dir)) &&
0812           newVolume.max(m_dir) > oldVolume.max(m_dir)) {
0813         double gap2Min = oldVolume.max(m_dir);
0814         double gap2Max = newVolume.max(m_dir);
0815         double gap2Hl = (gap2Max - gap2Min) / 2.0;
0816         double gap2P = (gap2Max + gap2Min) / 2.0;
0817 
0818         // check if we need a new gap volume or reuse an existing one
0819         auto& candidate = volumeTuples.back();
0820         if (isGap(candidate.volume)) {
0821           ACTS_VERBOSE("~> Reusing existing gap volume at positive ");
0822 
0823           gap2Hl = candidate.bounds->get(
0824                        CuboidVolumeBounds::fromAxisDirection(m_dir)) +
0825                    gap2Hl;
0826           gap2Min = newVolume.max(m_dir) - gap2Hl * 2;
0827           gap2P = (gap2Max + gap2Min) / 2.0;
0828 
0829           auto gap2Bounds = std::make_shared<CuboidVolumeBounds>(
0830               std::initializer_list<
0831                   std::pair<CuboidVolumeBounds::BoundValues, double>>{
0832                   {CuboidVolumeBounds::fromAxisDirection(m_dir), gap2Hl},
0833                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1),
0834                    newVolume.halfLength(m_dirOrth1)},
0835                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2),
0836                    newVolume.halfLength(m_dirOrth2)}});
0837           Translation3 gap2Translation(Vector3::Unit(dirIdx) * gap2P);
0838           Transform3 gap2Transform = m_groupTransform * gap2Translation;
0839 
0840           candidate.volume->update(std::move(gap2Bounds), gap2Transform);
0841           candidate = VolumeTuple{*candidate.volume, m_groupTransform};
0842           printGapDimensions(candidate, " after ");
0843         } else {
0844           ACTS_VERBOSE("~> Creating new gap volume at positive ");
0845           auto gap2Bounds = std::make_shared<CuboidVolumeBounds>(
0846               std::initializer_list<
0847                   std::pair<CuboidVolumeBounds::BoundValues, double>>{
0848                   {CuboidVolumeBounds::fromAxisDirection(m_dir), gap2Hl},
0849                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth1),
0850                    newVolume.halfLength(m_dirOrth1)},
0851                   {CuboidVolumeBounds::fromAxisDirection(m_dirOrth2),
0852                    newVolume.halfLength(m_dirOrth2)}});
0853           Translation3 gap2Translation(Vector3::Unit(dirIdx) * gap2P);
0854           Transform3 gap2Transform = m_groupTransform * gap2Translation;
0855           auto gap2 = addGapVolume(gap2Transform, std::move(gap2Bounds));
0856           volumeTuples.emplace_back(*gap2, m_groupTransform);
0857           printGapDimensions(volumeTuples.back());
0858         }
0859       }
0860     }
0861 
0862     ACTS_VERBOSE("*** Volume configuration after " << axisDirectionName(m_dir)
0863                                                    << " resizing:");
0864     printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0865   }
0866 
0867   ACTS_VERBOSE("Commit and update outer vector of volumes");
0868   m_volumes.clear();
0869   for (auto& vt : volumeTuples) {
0870     vt.commit(logger);
0871     m_volumes.push_back(vt.volume);
0872   }
0873 
0874   m_transform = newVolume.globalTransform;
0875   // @TODO: We probably can reuse m_transform
0876   m_groupTransform = m_transform;
0877   Volume::update(std::move(bounds), std::nullopt, logger);
0878 }
0879 
0880 std::shared_ptr<Volume> CuboidVolumeStack::addGapVolume(
0881     const Transform3& transform, const std::shared_ptr<VolumeBounds>& bounds) {
0882   auto gapVolume = std::make_shared<Volume>(transform, bounds);
0883   m_gaps.push_back(gapVolume);
0884   return gapVolume;
0885 }
0886 
0887 const std::vector<std::shared_ptr<Volume>>& CuboidVolumeStack::gaps() const {
0888   return m_gaps;
0889 }
0890 
0891 }  // namespace Acts