Back to home page

sPhenix code displayed by LXR

 
 

    


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

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