Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-23 09:15:51

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 <boost/test/data/test_case.hpp>
0010 #include <boost/test/detail/log_level.hpp>
0011 #include <boost/test/tools/context.hpp>
0012 #include <boost/test/tools/old/interface.hpp>
0013 #include <boost/test/unit_test.hpp>
0014 #include <boost/test/unit_test_log.hpp>
0015 #include <boost/test/unit_test_parameters.hpp>
0016 #include <boost/test/unit_test_suite.hpp>
0017 
0018 #include "Acts/Definitions/Algebra.hpp"
0019 #include "Acts/Definitions/Units.hpp"
0020 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0021 #include "Acts/Geometry/CuboidVolumeStack.hpp"
0022 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0023 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0024 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0025 #include "Acts/Utilities/AxisDefinitions.hpp"
0026 #include "Acts/Utilities/Logger.hpp"
0027 #include "Acts/Utilities/ThrowAssert.hpp"
0028 #include "Acts/Utilities/Zip.hpp"
0029 
0030 #include <cassert>
0031 #include <initializer_list>
0032 #include <stdexcept>
0033 #include <utility>
0034 
0035 using namespace Acts::UnitLiterals;
0036 
0037 namespace Acts::Test {
0038 
0039 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0040 
0041 struct Fixture {
0042   Logging::Level m_level;
0043   Fixture() {
0044     m_level = Acts::Logging::getFailureThreshold();
0045     Acts::Logging::setFailureThreshold(Acts::Logging::FATAL);
0046   }
0047 
0048   ~Fixture() { Acts::Logging::setFailureThreshold(m_level); }
0049 };
0050 
0051 BOOST_FIXTURE_TEST_SUITE(Geometry, Fixture)
0052 
0053 static const std::vector<VolumeAttachmentStrategy> strategies = {
0054     VolumeAttachmentStrategy::Gap,
0055     VolumeAttachmentStrategy::First,
0056     VolumeAttachmentStrategy::Second,
0057     VolumeAttachmentStrategy::Midpoint,
0058 };
0059 
0060 static const std::vector<VolumeResizeStrategy> resizeStrategies = {
0061     VolumeResizeStrategy::Expand,
0062     VolumeResizeStrategy::Gap,
0063 };
0064 
0065 BOOST_AUTO_TEST_SUITE(CuboidVolumeStackTest)
0066 
0067 BOOST_DATA_TEST_CASE(BaselineLocal,
0068                      (boost::unit_test::data::xrange(-135, 180, 45) *
0069                       boost::unit_test::data::xrange(0, 2, 1) *
0070                       boost::unit_test::data::make(0.8, 1.0, 1.2) *
0071                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0072                                                    Vector3{20_mm, 0_mm, 0_mm},
0073                                                    Vector3{0_mm, 20_mm, 0_mm},
0074                                                    Vector3{20_mm, 20_mm, 0_mm},
0075                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0076                       boost::unit_test::data::make(strategies) *
0077                       boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0078                                                    Acts::AxisDirection::AxisY,
0079                                                    Acts::AxisDirection::AxisZ)),
0080                      angle, rotate, shift, offset, strategy, dir) {
0081   double halfDir = 400_mm;
0082 
0083   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0084 
0085   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0086   auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0087 
0088   auto boundDir = CuboidVolumeBounds::fromAxisDirection(dir);
0089   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(dirOrth1);
0090   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(dirOrth2);
0091 
0092   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0093       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0094           {boundDir, halfDir},
0095           {boundDirOrth1, 100_mm},
0096           {boundDirOrth2, 400_mm}});
0097 
0098   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0099       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0100           {boundDir, halfDir},
0101           {boundDirOrth1, 200_mm},
0102           {boundDirOrth2, 600_mm}});
0103 
0104   auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0105       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0106           {boundDir, halfDir},
0107           {boundDirOrth1, 300_mm},
0108           {boundDirOrth2, 500_mm}});
0109 
0110   Transform3 base = AngleAxis3(angle * 1_degree, Vector3::Unit(dirOrth1Idx)) *
0111                     Translation3(offset);
0112 
0113   Translation3 translation1(Vector3::Unit(dirIdx) * (-2 * halfDir * shift));
0114   Transform3 transform1 = base * translation1;
0115   auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0116 
0117   Transform3 transform2 = base;
0118   auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0119 
0120   Translation3 translation3(Vector3::Unit(dirIdx) * (2 * halfDir * shift));
0121   Transform3 transform3 = base * translation3;
0122   auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0123 
0124   std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0125   // Rotate to simulate unsorted volumes: all results should be the same!
0126   std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
0127 
0128   auto origVolumes = volumes;
0129 
0130   std::vector<CuboidVolumeBounds> originalBounds;
0131   std::transform(volumes.begin(), volumes.end(),
0132                  std::back_inserter(originalBounds), [](const auto& vol) {
0133                    const auto* res =
0134                        dynamic_cast<CuboidVolumeBounds*>(&vol->volumeBounds());
0135                    throw_assert(res != nullptr, "");
0136                    return *res;
0137                  });
0138 
0139   if (shift < 1.0) {
0140     BOOST_CHECK_THROW(CuboidVolumeStack(volumes, dir, strategy,
0141                                         VolumeResizeStrategy::Gap, *logger),
0142                       std::invalid_argument);
0143     return;
0144   }
0145   CuboidVolumeStack stack(volumes, dir, strategy, VolumeResizeStrategy::Gap,
0146                           *logger);
0147 
0148   auto stackBounds =
0149       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0150   BOOST_REQUIRE(stackBounds != nullptr);
0151 
0152   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth1), 300_mm, 1e-6);
0153   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth2), 600_mm, 1e-6);
0154   BOOST_CHECK_CLOSE(stackBounds->get(boundDir), halfDir + 2 * halfDir * shift,
0155                     1e-6);
0156   CHECK_CLOSE_OR_SMALL(stack.transform().matrix(), base.matrix(), 1e-10, 1e-12);
0157 
0158   // All volumes (including gaps) are cuboids and have the same orthogonal
0159   // bounds
0160   for (const auto& volume : volumes) {
0161     const auto* cuboidBounds =
0162         dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0163     BOOST_REQUIRE(cuboidBounds != nullptr);
0164     BOOST_CHECK_CLOSE(cuboidBounds->get(boundDirOrth1), 300_mm, 1e-6);
0165     BOOST_CHECK_CLOSE(cuboidBounds->get(boundDirOrth2), 600_mm, 1e-6);
0166   }
0167 
0168   // Volumes are sorted in (local) stacking direction
0169   for (std::size_t i = 0; i < volumes.size() - 1; ++i) {
0170     const auto& a = volumes.at(i);
0171     const auto& b = volumes.at(i + 1);
0172 
0173     BOOST_CHECK_LT((base.inverse() * a->center())[dirIdx],
0174                    (base.inverse() * b->center())[dirIdx]);
0175   }
0176 
0177   if (shift <= 1.0) {
0178     // No gap volumes were added
0179     BOOST_CHECK_EQUAL(volumes.size(), 3);
0180 
0181     // No expansion, original volumes did not move
0182     BOOST_CHECK_EQUAL(vol1->transform().matrix(), transform1.matrix());
0183     BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0184     BOOST_CHECK_EQUAL(vol3->transform().matrix(), transform3.matrix());
0185 
0186     for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0187       const auto* newBounds =
0188           dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0189       BOOST_CHECK_CLOSE(newBounds->get(boundDir), bounds.get(boundDir), 1e-6);
0190     }
0191   } else {
0192     if (strategy == VolumeAttachmentStrategy::Gap) {
0193       // Gap volumes were added
0194       BOOST_CHECK_EQUAL(volumes.size(), 5);
0195       auto gap1 = volumes.at(1);
0196       auto gap2 = volumes.at(3);
0197 
0198       BOOST_TEST_MESSAGE("Gap 1: " << gap1->transform().matrix());
0199       BOOST_TEST_MESSAGE("Gap 2: " << gap2->transform().matrix());
0200 
0201       const auto* gapBounds1 =
0202           dynamic_cast<const CuboidVolumeBounds*>(&gap1->volumeBounds());
0203       const auto* gapBounds2 =
0204           dynamic_cast<const CuboidVolumeBounds*>(&gap2->volumeBounds());
0205 
0206       double gapHlDir = (shift - 1.0) * halfDir;
0207 
0208       BOOST_CHECK(std::abs(gapBounds1->get(boundDir) - gapHlDir) < 1e-12);
0209       BOOST_CHECK(std::abs(gapBounds2->get(boundDir) - gapHlDir) < 1e-12);
0210 
0211       double gap1Dir = (-2 * halfDir * shift) + halfDir + gapHlDir;
0212       double gap2Dir = (2 * halfDir * shift) - halfDir - gapHlDir;
0213 
0214       Translation3 gap1Translation(Vector3::Unit(dirIdx) * gap1Dir);
0215       Translation3 gap2Translation(Vector3::Unit(dirIdx) * gap2Dir);
0216 
0217       Transform3 gap1Transform = base * gap1Translation;
0218       Transform3 gap2Transform = base * gap2Translation;
0219 
0220       CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0221                            1e-10, 1e-12);
0222       CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0223                            1e-10, 1e-12);
0224 
0225       // Original volumes did not changes bounds
0226       for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0227         const auto* newBounds =
0228             dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0229         BOOST_CHECK_CLOSE(newBounds->get(boundDir), bounds.get(boundDir), 1e-6);
0230       }
0231 
0232       // No expansion, original volumes did not move
0233       BOOST_CHECK_EQUAL(vol1->transform().matrix(), transform1.matrix());
0234       BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0235       BOOST_CHECK_EQUAL(vol3->transform().matrix(), transform3.matrix());
0236     } else if (strategy == VolumeAttachmentStrategy::First) {
0237       // No gap volumes were added
0238       BOOST_CHECK_EQUAL(volumes.size(), 3);
0239 
0240       double wGap = (shift - 1.0) * halfDir * 2;
0241 
0242       // Volume 1 got bigger and shifted right
0243       auto newBounds1 =
0244           dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0245       BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0246       double pDir1 = -2 * halfDir * shift + wGap / 2.0;
0247       Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0248       Transform3 expectedTransform1 = base * expectedTranslation1;
0249       CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0250                            expectedTransform1.matrix(), 1e-10, 1e-12);
0251 
0252       // Volume 2 got bigger and shifted left
0253       auto newBounds2 =
0254           dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0255       BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0256       double pDir2 = wGap / 2.0;
0257       Translation3 expectedTranslation2(Vector3::Unit(dirIdx) * pDir2);
0258       Transform3 expectedTransform2 = base * expectedTranslation2;
0259       CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(),
0260                            expectedTransform2.matrix(), 1e-10, 1e-12);
0261 
0262       // Volume 3 stayed the same
0263       auto newBounds3 =
0264           dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0265       BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir, 1e-6);
0266       double pDir3 = 2 * halfDir * shift;
0267       Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0268       Transform3 expectedTransform3 = base * expectedTranslation3;
0269       CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0270                            expectedTransform3.matrix(), 1e-10, 1e-12);
0271     } else if (strategy == VolumeAttachmentStrategy::Second) {
0272       // No gap volumes were added
0273       BOOST_CHECK_EQUAL(volumes.size(), 3);
0274 
0275       double wGap = (shift - 1.0) * halfDir * 2;
0276 
0277       // Volume 1 stayed the same
0278       auto newBounds1 =
0279           dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0280       BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir, 1e-6);
0281       double pDir1 = -2 * halfDir * shift;
0282       Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0283       Transform3 expectedTransform1 = base * expectedTranslation1;
0284       CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0285                            expectedTransform1.matrix(), 1e-10, 1e-12);
0286 
0287       // Volume 2 got bigger and shifted left
0288       auto newBounds2 =
0289           dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0290       BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0291       double pDir2 = -wGap / 2.0;
0292       Translation3 expectedTranslation2(Vector3::Unit(dirIdx) * pDir2);
0293       Transform3 expectedTransform2 = base * expectedTranslation2;
0294       CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(),
0295                            expectedTransform2.matrix(), 1e-10, 1e-12);
0296 
0297       // Volume 3 got bigger and shifted left
0298       auto newBounds3 =
0299           dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0300       BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0301       double pDir3 = 2 * halfDir * shift - wGap / 2.0;
0302       Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0303       Transform3 expectedTransform3 = base * expectedTranslation3;
0304       CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0305                            expectedTransform3.matrix(), 1e-10, 1e-12);
0306     } else if (strategy == VolumeAttachmentStrategy::Midpoint) {
0307       // No gap volumes were added
0308       BOOST_CHECK_EQUAL(volumes.size(), 3);
0309 
0310       double wGap = (shift - 1.0) * halfDir * 2;
0311 
0312       // Volume 1 got bigger and shifted right
0313       auto newBounds1 =
0314           dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0315       BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + wGap / 4.0, 1e-6);
0316       double pDir1 = -2 * halfDir * shift + wGap / 4.0;
0317       Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0318       Transform3 expectedTransform1 = base * expectedTranslation1;
0319       CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0320                            expectedTransform1.matrix(), 1e-10, 1e-12);
0321 
0322       // Volume 2 got bigger but didn't move
0323       auto newBounds2 =
0324           dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0325       BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0326       CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(), base.matrix(), 1e-10,
0327                            1e-12);
0328 
0329       // Volume 3 got bigger and shifted left
0330       auto newBounds3 =
0331           dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0332       BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + wGap / 4.0, 1e-6);
0333       double pDir3 = 2 * halfDir * shift - wGap / 4.0;
0334       Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0335       Transform3 expectedTransform3 = base * expectedTranslation3;
0336       CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0337                            expectedTransform3.matrix(), 1e-10, 1e-12);
0338     }
0339   }
0340 }
0341 
0342 BOOST_DATA_TEST_CASE(BaselineGlobal,
0343                      (boost::unit_test::data::xrange(-135, 180, 45) *
0344                       boost::unit_test::data::xrange(0, 2, 1) *
0345                       boost::unit_test::data::make(1.0, 1.2) *
0346                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0347                                                    Vector3{20_mm, 0_mm, 0_mm},
0348                                                    Vector3{0_mm, 20_mm, 0_mm},
0349                                                    Vector3{20_mm, 20_mm, 0_mm},
0350                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0351                       boost::unit_test::data::make(strategies)),
0352                      angle, rotate, shift, offset, strategy) {
0353   double halfDir = 400_mm;
0354 
0355   auto bounds1 = std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, halfDir);
0356   auto bounds2 = std::make_shared<CuboidVolumeBounds>(200_mm, 600_mm, halfDir);
0357   auto bounds3 = std::make_shared<CuboidVolumeBounds>(300_mm, 500_mm, halfDir);
0358 
0359   AngleAxis3 baseRotation = AngleAxis3(angle, Vector3::UnitX());
0360   Transform3 base = baseRotation * Translation3(offset);
0361 
0362   Vector3 orientation = baseRotation * Vector3::UnitZ();
0363 
0364   Translation3 translation1(Vector3::UnitZ() * (-2 * halfDir * shift));
0365   Transform3 transform1 = base * translation1;
0366   auto vol1Orientation = std::make_shared<Volume>(transform1, bounds1);
0367   auto vol1AxisDirection = std::make_shared<Volume>(transform1, bounds1);
0368 
0369   Transform3 transform2 = base;
0370   auto vol2Orientation = std::make_shared<Volume>(transform2, bounds2);
0371   auto vol2AxisDirection = std::make_shared<Volume>(transform2, bounds2);
0372 
0373   Translation3 translation3(Vector3::UnitZ() * (2 * halfDir * shift));
0374   Transform3 transform3 = base * translation3;
0375   auto vol3Orientation = std::make_shared<Volume>(transform3, bounds3);
0376   auto vol3AxisDirection = std::make_shared<Volume>(transform3, bounds3);
0377 
0378   std::vector<Volume*> volumesOrientation = {
0379       vol1Orientation.get(), vol2Orientation.get(), vol3Orientation.get()};
0380   std::vector<Volume*> volumesAxisDirection = {vol1AxisDirection.get(),
0381                                                vol2AxisDirection.get(),
0382                                                vol3AxisDirection.get()};
0383 
0384   // Rotate to simulate unsorted volumes: all results should be the same!
0385   std::rotate(volumesOrientation.begin(), volumesOrientation.begin() + rotate,
0386               volumesOrientation.end());
0387   std::rotate(volumesAxisDirection.begin(),
0388               volumesAxisDirection.begin() + rotate,
0389               volumesAxisDirection.end());
0390 
0391   CuboidVolumeStack stackOrientation(volumesOrientation, orientation, strategy,
0392                                      VolumeResizeStrategy::Gap, *logger);
0393   CuboidVolumeStack stackAxisDirection(volumesAxisDirection,
0394                                        AxisDirection::AxisZ, strategy,
0395                                        VolumeResizeStrategy::Gap, *logger);
0396 
0397   auto stackBoundsOrientation =
0398       dynamic_cast<const CuboidVolumeBounds*>(&stackOrientation.volumeBounds());
0399   auto stackBoundsAxisDirection = dynamic_cast<const CuboidVolumeBounds*>(
0400       &stackAxisDirection.volumeBounds());
0401 
0402   // Global vector direction and local axis direction should
0403   // produce the same results
0404   BOOST_CHECK_NE(stackBoundsOrientation, nullptr);
0405   BOOST_CHECK_NE(stackBoundsAxisDirection, nullptr);
0406   BOOST_CHECK_EQUAL(*stackBoundsOrientation, *stackBoundsAxisDirection);
0407   CHECK_CLOSE_OR_SMALL(stackOrientation.transform().matrix(),
0408                        stackAxisDirection.transform().matrix(), 1e-10, 1e-12);
0409 }
0410 
0411 BOOST_DATA_TEST_CASE(Asymmetric,
0412                      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0413                                                   Acts::AxisDirection::AxisY,
0414                                                   Acts::AxisDirection::AxisZ),
0415                      dir) {
0416   double halfDir1 = 200_mm;
0417   double pDir1 = -1100_mm;
0418   double halfDir2 = 600_mm;
0419   double pDir2 = -200_mm;
0420   double halfDir3 = 400_mm;
0421   double pDir3 = 850_mm;
0422 
0423   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0424 
0425   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0426 
0427   auto boundDir = CuboidVolumeBounds::fromAxisDirection(dir);
0428   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(dirOrth1);
0429   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(dirOrth2);
0430 
0431   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0432       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0433           {boundDir, halfDir1},
0434           {boundDirOrth1, 100_mm},
0435           {boundDirOrth2, 400_mm}});
0436 
0437   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0438       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0439           {boundDir, halfDir2},
0440           {boundDirOrth1, 200_mm},
0441           {boundDirOrth2, 600_mm}});
0442 
0443   auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0444       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0445           {boundDir, halfDir3},
0446           {boundDirOrth1, 300_mm},
0447           {boundDirOrth2, 500_mm}});
0448 
0449   Translation3 translation1(Vector3::Unit(dirIdx) * pDir1);
0450   Transform3 transform1(translation1);
0451   auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0452 
0453   Translation3 translation2(Vector3::Unit(dirIdx) * pDir2);
0454   Transform3 transform2(translation2);
0455   auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0456 
0457   Translation3 translation3(Vector3::Unit(dirIdx) * pDir3);
0458   Transform3 transform3(translation3);
0459   auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0460 
0461   std::vector<Volume*> volumes = {vol2.get(), vol1.get(), vol3.get()};
0462 
0463   CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0464                           VolumeResizeStrategy::Gap, *logger);
0465   BOOST_CHECK_EQUAL(volumes.size(), 5);
0466 
0467   auto stackBounds =
0468       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0469   BOOST_REQUIRE(stackBounds != nullptr);
0470 
0471   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth1), 300_mm, 1e-6);
0472   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth2), 600_mm, 1e-6);
0473   BOOST_CHECK_CLOSE(stackBounds->get(boundDir),
0474                     (std::abs(pDir1 - halfDir1) + pDir3 + halfDir3) / 2.0,
0475                     1e-6);
0476 
0477   double midDir = (pDir1 - halfDir1 + pDir3 + halfDir3) / 2.0;
0478   Translation3 expectedTranslation(Vector3::Unit(dirIdx) * midDir);
0479   Transform3 expectedTransform = Transform3::Identity() * expectedTranslation;
0480   CHECK_CLOSE_OR_SMALL(stack.transform().matrix(), expectedTransform.matrix(),
0481                        1e-10, 1e-12);
0482 }
0483 
0484 BOOST_DATA_TEST_CASE(UpdateStack,
0485                      (boost::unit_test::data::xrange(-135, 180, 45) *
0486                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0487                                                    Vector3{20_mm, 0_mm, 0_mm},
0488                                                    Vector3{0_mm, 20_mm, 0_mm},
0489                                                    Vector3{20_mm, 20_mm, 0_mm},
0490                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0491                       boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
0492                       boost::unit_test::data::make(resizeStrategies) *
0493                       boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0494                                                    Acts::AxisDirection::AxisY,
0495                                                    Acts::AxisDirection::AxisZ)),
0496                      angle, offset, zshift, strategy, dir) {
0497   double halfDir = 400_mm;
0498 
0499   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0500 
0501   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0502   auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0503 
0504   auto boundDir = CuboidVolumeBounds::fromAxisDirection(dir);
0505   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(dirOrth1);
0506   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(dirOrth2);
0507 
0508   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0509       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0510           {boundDir, halfDir},
0511           {boundDirOrth1, 100_mm},
0512           {boundDirOrth2, 600_mm}});
0513 
0514   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0515       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0516           {boundDir, halfDir},
0517           {boundDirOrth1, 100_mm},
0518           {boundDirOrth2, 600_mm}});
0519 
0520   auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0521       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0522           {boundDir, halfDir},
0523           {boundDirOrth1, 100_mm},
0524           {boundDirOrth2, 600_mm}});
0525 
0526   Vector3 shift = Vector3::Unit(dirIdx) * zshift;
0527   Transform3 base = AngleAxis3(angle * 1_degree, Vector3::Unit(dirOrth1Idx)) *
0528                     Translation3(offset + shift);
0529 
0530   Translation3 translation1(Vector3::Unit(dirIdx) * -2 * halfDir);
0531   Transform3 transform1 = base * translation1;
0532   auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0533 
0534   Transform3 transform2 = base;
0535   auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0536 
0537   Translation3 translation3(Vector3::Unit(dirIdx) * 2 * halfDir);
0538   Transform3 transform3 = base * translation3;
0539   auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0540 
0541   std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0542   std::vector<Volume*> originalVolumes = volumes;
0543 
0544   std::vector<Transform3> originalTransforms = {transform1, transform2,
0545                                                 transform3};
0546 
0547   CuboidVolumeStack stack(volumes, dir,
0548                           VolumeAttachmentStrategy::Gap,  // should not make a
0549                                                           // difference
0550                           strategy, *logger);
0551 
0552   const auto* originalBounds =
0553       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0554 
0555   auto assertOriginalBounds = [&]() {
0556     const auto* bounds =
0557         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0558     BOOST_REQUIRE(bounds != nullptr);
0559     BOOST_CHECK_EQUAL(bounds, originalBounds);
0560     BOOST_CHECK_CLOSE(bounds->get(boundDirOrth1), 100_mm, 1e-6);
0561     BOOST_CHECK_CLOSE(bounds->get(boundDirOrth2), 600_mm, 1e-6);
0562     BOOST_CHECK_CLOSE(bounds->get(boundDir), 3 * halfDir, 1e-6);
0563   };
0564 
0565   assertOriginalBounds();
0566 
0567   {
0568     // Assign a copy of the identical bounds gives identical bounds
0569     auto bounds = std::make_shared<CuboidVolumeBounds>(
0570         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0571     stack.update(bounds, std::nullopt, *logger);
0572     assertOriginalBounds();
0573   }
0574 
0575   {
0576     // Cannot decrease half length
0577     auto bounds = std::make_shared<CuboidVolumeBounds>(
0578         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0579     bounds->set(boundDirOrth1, 20_mm);
0580     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0581                       std::invalid_argument);
0582     assertOriginalBounds();
0583   }
0584 
0585   {
0586     // Cannot decrease half length
0587     auto bounds = std::make_shared<CuboidVolumeBounds>(
0588         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0589     bounds->set(boundDirOrth2, 200_mm);
0590     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0591                       std::invalid_argument);
0592     assertOriginalBounds();
0593   }
0594 
0595   {
0596     // Cannot decrease half length
0597     auto bounds = std::make_shared<CuboidVolumeBounds>(
0598         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0599     bounds->set(boundDir, 2 * halfDir);
0600     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0601                       std::invalid_argument);
0602     assertOriginalBounds();
0603   }
0604 
0605   {
0606     // Increase half length
0607     auto bounds = std::make_shared<CuboidVolumeBounds>(
0608         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0609     bounds->set(boundDirOrth1, 700_mm);
0610     stack.update(bounds, std::nullopt, *logger);
0611     const auto* updatedBounds =
0612         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0613     BOOST_REQUIRE(updatedBounds != nullptr);
0614     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0615     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 600_mm, 1e-6);
0616     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0617 
0618     // No gap volumes were added
0619     BOOST_CHECK_EQUAL(volumes.size(), 3);
0620 
0621     // All volumes increase half x to accommodate
0622     for (const auto& [volume, origTransform] :
0623          zip(volumes, originalTransforms)) {
0624       const auto* newBounds =
0625           dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0626       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0627       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 600_mm, 1e-6);
0628       BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0629 
0630       // Position stayed the same
0631       BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0632     }
0633   }
0634   {
0635     // Increase half length
0636     auto bounds = std::make_shared<CuboidVolumeBounds>(
0637         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0638     bounds->set(boundDirOrth2, 700_mm);
0639     stack.update(bounds, std::nullopt, *logger);
0640     const auto* updatedBounds =
0641         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0642     BOOST_REQUIRE(updatedBounds != nullptr);
0643     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0644     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0645     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0646 
0647     // No gap volumes were added
0648     BOOST_CHECK_EQUAL(volumes.size(), 3);
0649 
0650     // All volumes increase half y to accommodate
0651     for (const auto& [volume, origTransform] :
0652          zip(volumes, originalTransforms)) {
0653       const auto* newBounds =
0654           dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0655       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0656       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0657       BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0658 
0659       // Position stayed the same
0660       BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0661     }
0662   }
0663 
0664   {
0665     // Increase half length
0666     auto bounds = std::make_shared<CuboidVolumeBounds>(
0667         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0668     bounds->set(boundDir, 4 * halfDir);
0669     stack.update(bounds, std::nullopt, *logger);
0670     const auto* updatedBounds =
0671         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0672     BOOST_REQUIRE(updatedBounds != nullptr);
0673     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 4 * halfDir, 1e-6);
0674     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0675     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0676 
0677     if (strategy == VolumeResizeStrategy::Expand) {
0678       // No gap volumes were added
0679       BOOST_CHECK_EQUAL(volumes.size(), 3);
0680 
0681       // Volume 1 got bigger and shifted left
0682       auto newBounds1 =
0683           dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0684       BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + halfDir / 2.0,
0685                         1e-6);
0686       auto expectedTranslation1 =
0687           Translation3(Vector3::Unit(dirIdx) * (-2 * halfDir - halfDir / 2.0));
0688       Transform3 expectedTransform1 = base * expectedTranslation1;
0689       CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0690                            expectedTransform1.matrix(), 1e-10, 1e-12);
0691 
0692       // Volume 2 stayed the same
0693       auto newBounds2 =
0694           dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0695       BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir, 1e-6);
0696       CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(), transform2.matrix(),
0697                            1e-10, 1e-12);
0698 
0699       // Volume 3 got bigger and shifted right
0700       auto newBounds3 =
0701           dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0702       BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + halfDir / 2.0,
0703                         1e-6);
0704       auto expectedTranslation3 =
0705           Translation3(Vector3::Unit(dirIdx) * (2 * halfDir + halfDir / 2.0));
0706       Transform3 expectedTransform3 = base * expectedTranslation3;
0707       CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0708                            expectedTransform3.matrix(), 1e-10, 1e-12);
0709     } else if (strategy == VolumeResizeStrategy::Gap) {
0710       // Gap volumes were added
0711       BOOST_CHECK_EQUAL(volumes.size(), 5);
0712 
0713       for (const auto& [volume, origTransform] :
0714            zip(originalVolumes, originalTransforms)) {
0715         const auto* newBounds =
0716             dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0717         BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0718         BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0719         BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0720         // Position stayed the same
0721         CHECK_CLOSE_OR_SMALL(volume->transform().matrix(),
0722                              origTransform.matrix(), 1e-10, 1e-12);
0723       }
0724 
0725       auto gap1 = volumes.front();
0726       auto gap2 = volumes.back();
0727 
0728       const auto* gapBounds1 =
0729           dynamic_cast<const CuboidVolumeBounds*>(&gap1->volumeBounds());
0730       const auto* gapBounds2 =
0731           dynamic_cast<const CuboidVolumeBounds*>(&gap2->volumeBounds());
0732 
0733       BOOST_CHECK_CLOSE(gapBounds1->get(boundDir), halfDir / 2.0, 1e-6);
0734       BOOST_CHECK_CLOSE(gapBounds2->get(boundDir), halfDir / 2.0, 1e-6);
0735       auto gap1Translation =
0736           Translation3(Vector3::Unit(dirIdx) * (-3 * halfDir - halfDir / 2.0));
0737       Transform3 gap1Transform = base * gap1Translation;
0738 
0739       auto gap2Translation =
0740           Translation3(Vector3::Unit(dirIdx) * (3 * halfDir + halfDir / 2.0));
0741       Transform3 gap2Transform = base * gap2Translation;
0742       CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0743                            1e-10, 1e-12);
0744       CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0745                            1e-10, 1e-12);
0746     }
0747   }
0748 }
0749 
0750 BOOST_DATA_TEST_CASE(
0751     UpdateStackOneSided,
0752     ((boost::unit_test::data::make(-1.0, 1.0) ^
0753       boost::unit_test::data::make(VolumeResizeStrategy::Gap,
0754                                    VolumeResizeStrategy::Expand)) *
0755      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0756                                   Acts::AxisDirection::AxisY,
0757                                   Acts::AxisDirection::AxisZ)),
0758     f, strategy, dir) {
0759   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0760 
0761   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0762   auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0763   auto dirOrth2Idx = CuboidVolumeStack::axisToIndex(dirOrth2);
0764 
0765   auto boundDir = CuboidVolumeBounds::fromAxisDirection(dir);
0766   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(dirOrth1);
0767   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(dirOrth2);
0768 
0769   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0770       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0771           {boundDir, 400_mm},
0772           {boundDirOrth1, 100_mm},
0773           {boundDirOrth2, 300_mm}});
0774 
0775   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0776       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0777           {boundDir, 400_mm},
0778           {boundDirOrth1, 100_mm},
0779           {boundDirOrth2, 300_mm}});
0780 
0781   auto trf = Transform3::Identity();
0782 
0783   auto translation1 = Translation3(Vector3::Unit(dirIdx) * -500_mm);
0784   auto trf1 = trf * translation1;
0785   auto vol1 = std::make_shared<Volume>(trf1, bounds1);
0786 
0787   auto translation2 = Translation3(Vector3::Unit(dirIdx) * 500_mm);
0788   auto trf2 = trf * translation2;
0789   auto vol2 = std::make_shared<Volume>(trf2, bounds2);
0790 
0791   std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0792 
0793   CuboidVolumeStack stack{volumes, dir, VolumeAttachmentStrategy::Gap, strategy,
0794                           *logger};
0795   const auto* originalBounds =
0796       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0797 
0798   // Increase half length by 50mm
0799   auto newBounds = std::make_shared<CuboidVolumeBounds>(
0800       dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0801   newBounds->set(boundDir, 950_mm);
0802   // Shift to +stacking direction by 50mm
0803   auto delta = Translation3(Vector3::Unit(dirIdx) * f * 50_mm);
0804   trf *= delta;
0805   // -> left edge should stay at -400mm, right edge should be at 500mm or the
0806   // other direction
0807   auto checkUnchanged = [&]() {
0808     const auto* bounds =
0809         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0810     BOOST_REQUIRE(bounds != nullptr);
0811     BOOST_CHECK_EQUAL(*bounds, *originalBounds);
0812   };
0813 
0814   // Invalid: shift too far in merging direction
0815   BOOST_CHECK_THROW(
0816       auto errDelta = Translation3(Vector3::Unit(dirIdx) * f * 20_mm);
0817       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0818   checkUnchanged();
0819 
0820   // Invalid: shift in orthogonal direction
0821   BOOST_CHECK_THROW(
0822       auto errDelta = Translation3(Vector3::Unit(dirOrth1Idx) * 10_mm);
0823       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0824   checkUnchanged();
0825 
0826   // Invalid: shift in orthogonal direction
0827   BOOST_CHECK_THROW(
0828       auto errDelta = Translation3(Vector3::Unit(dirOrth2Idx) * 10_mm);
0829       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0830   checkUnchanged();
0831 
0832   // Invalid: rotation
0833   BOOST_CHECK_THROW(
0834       stack.update(newBounds,
0835                    trf * AngleAxis3{10_degree, Vector3::Unit(dirOrth1Idx)},
0836                    *logger),
0837       std::invalid_argument);
0838   checkUnchanged();
0839 
0840   stack.update(newBounds, trf, *logger);
0841 
0842   CHECK_CLOSE_OR_SMALL(stack.transform().matrix(), trf.matrix(), 1e-10, 1e-12);
0843   const auto* bounds =
0844       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0845   BOOST_REQUIRE(bounds != nullptr);
0846   BOOST_CHECK_CLOSE(bounds->get(boundDir), 950_mm, 1e-6);
0847 
0848   // All volumes including gaps should have same size in orthogonal plane
0849   for (const auto* vol : volumes) {
0850     const auto* volBounds =
0851         dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0852     BOOST_REQUIRE(volBounds != nullptr);
0853     BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth1), 100_mm, 1e-6);
0854     BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth2), 300_mm, 1e-6);
0855   }
0856 
0857   if (strategy == VolumeResizeStrategy::Expand) {
0858     // No gaps were added, there was one gap initially
0859     BOOST_CHECK_EQUAL(volumes.size(), 3);
0860     const Volume* vol = nullptr;
0861     if (f < 0.0) {
0862       // first volume should have gotten bigger
0863       vol = volumes.front();
0864     } else {
0865       // last volume should have gotten bigger
0866       vol = volumes.back();
0867     }
0868 
0869     const auto* volBounds =
0870         dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0871     BOOST_REQUIRE(volBounds != nullptr);
0872     BOOST_CHECK_CLOSE(volBounds->get(boundDir), 450_mm, 1e-6);
0873     BOOST_CHECK_EQUAL(vol->center()[dirIdx], f * 550_mm);
0874   } else if (strategy == VolumeResizeStrategy::Gap) {
0875     // One gap volume was added
0876     BOOST_CHECK_EQUAL(volumes.size(), 4);
0877 
0878     const Volume* gap = nullptr;
0879     if (f < 0.0) {
0880       gap = volumes.front();
0881     } else {
0882       gap = volumes.back();
0883     }
0884     const auto* gapBounds =
0885         dynamic_cast<const CuboidVolumeBounds*>(&gap->volumeBounds());
0886     BOOST_REQUIRE(gapBounds != nullptr);
0887 
0888     BOOST_CHECK_CLOSE(gapBounds->get(boundDir), 50_mm, 1e-6);
0889     BOOST_CHECK_EQUAL(gap->center()[dirIdx], f * 950_mm);
0890   }
0891 }
0892 
0893 //   original size
0894 // <--------------->
0895 // +---------------+
0896 // |               |
0897 // |               |
0898 // |   Volume 1    |
0899 // |               |
0900 // |               |
0901 // +---------------+
0902 //         first resize
0903 // <-------------------------->
0904 // +---------------+----------+
0905 // |               |          |
0906 // |               |          |
0907 // |   Volume 1    |   Gap    |
0908 // |               |          |      Gap is
0909 // |               |          |      reused!--+
0910 // +---------------+----------+               |
0911 //             second resize                  |
0912 // <----------------------------------->      |
0913 // +---------------+-------------------+      |
0914 // |               |                   |      |
0915 // |               |                   |      |
0916 // |   Volume 1    |        Gap        |<-----+
0917 // |               |                   |
0918 // |               |                   |
0919 // +---------------+-------------------+
0920 //
0921 BOOST_DATA_TEST_CASE(ResizeGapMultiple,
0922                      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0923                                                   Acts::AxisDirection::AxisY,
0924                                                   Acts::AxisDirection::AxisZ),
0925                      dir) {
0926   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0927 
0928   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0929 
0930   auto boundDir = CuboidVolumeBounds::fromAxisDirection(dir);
0931   auto boundDirOrth1 = CuboidVolumeBounds::fromAxisDirection(dirOrth1);
0932   auto boundDirOrth2 = CuboidVolumeBounds::fromAxisDirection(dirOrth2);
0933 
0934   auto bounds = std::make_shared<CuboidVolumeBounds>(
0935       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0936           {boundDir, 100}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0937   Transform3 trf = Transform3::Identity();
0938   Volume vol{trf, bounds};
0939 
0940   BOOST_TEST_CONTEXT("Positive") {
0941     std::vector<Volume*> volumes = {&vol};
0942     CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0943                             VolumeResizeStrategy::Gap, *logger);
0944 
0945     BOOST_CHECK_EQUAL(volumes.size(), 1);
0946     BOOST_CHECK(stack.gaps().empty());
0947 
0948     auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0949         std::initializer_list<
0950             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0951             {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0952     stack.update(newBounds1, trf * Translation3{Vector3::Unit(dirIdx) * 100},
0953                  *logger);
0954     BOOST_CHECK_EQUAL(volumes.size(), 2);
0955     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0956 
0957     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], 200.0);
0958     const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0959         &stack.gaps().front()->volumeBounds());
0960     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0961     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
0962 
0963     auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
0964         std::initializer_list<
0965             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0966             {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0967     stack.update(newBounds2, trf * Translation3{Vector3::Unit(dirIdx) * 200},
0968                  *logger);
0969 
0970     BOOST_CHECK_EQUAL(volumes.size(), 2);
0971     // No additional gap volume was added!
0972     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0973 
0974     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], 300.0);
0975     updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0976         &stack.gaps().front()->volumeBounds());
0977     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0978     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
0979   }
0980 
0981   BOOST_TEST_CONTEXT("Negative") {
0982     std::vector<Volume*> volumes = {&vol};
0983     CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0984                             VolumeResizeStrategy::Gap, *logger);
0985 
0986     BOOST_CHECK_EQUAL(volumes.size(), 1);
0987     BOOST_CHECK(stack.gaps().empty());
0988 
0989     auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0990         std::initializer_list<
0991             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0992             {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0993     stack.update(newBounds1, trf * Translation3{Vector3::Unit(dirIdx) * -100},
0994                  *logger);
0995     BOOST_CHECK_EQUAL(volumes.size(), 2);
0996     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0997 
0998     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], -200.0);
0999     const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
1000         &stack.gaps().front()->volumeBounds());
1001     BOOST_REQUIRE_NE(updatedBounds, nullptr);
1002     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
1003 
1004     auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
1005         std::initializer_list<
1006             std::pair<CuboidVolumeBounds::BoundValues, double>>{
1007             {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
1008     stack.update(newBounds2, trf * Translation3{Vector3::Unit(dirIdx) * -200},
1009                  *logger);
1010 
1011     BOOST_CHECK_EQUAL(volumes.size(), 2);
1012     // No additional gap volume was added!
1013     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1014 
1015     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], -300.0);
1016     updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
1017         &stack.gaps().front()->volumeBounds());
1018     BOOST_REQUIRE_NE(updatedBounds, nullptr);
1019     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
1020   }
1021 }
1022 
1023 BOOST_DATA_TEST_CASE(InvalidDirection, boost::unit_test::data::make(strategies),
1024                      strategy) {
1025   std::vector<Volume*> volumes;
1026   auto vol1 = std::make_shared<Volume>(
1027       Transform3::Identity(),
1028       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1029   volumes.push_back(vol1.get());
1030 
1031   // Single volume invalid direction still gives an error
1032   BOOST_CHECK_THROW(CuboidVolumeStack(volumes, AxisDirection::AxisR, strategy),
1033                     std::invalid_argument);
1034 
1035   auto vol2 = std::make_shared<Volume>(
1036       Transform3::Identity(),
1037       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1038   volumes.push_back(vol2.get());
1039 
1040   BOOST_CHECK_THROW(CuboidVolumeStack(volumes, AxisDirection::AxisR, strategy),
1041                     std::invalid_argument);
1042 }
1043 
1044 BOOST_DATA_TEST_CASE(InvalidInput,
1045                      (boost::unit_test::data::make(strategies) *
1046                       boost::unit_test::data::make(Acts::AxisDirection::AxisX,
1047                                                    Acts::AxisDirection::AxisY,
1048                                                    Acts::AxisDirection::AxisZ)),
1049                      strategy, direction) {
1050   BOOST_TEST_CONTEXT("Empty Volume") {
1051     std::vector<Volume*> volumes;
1052     BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy),
1053                       std::invalid_argument);
1054   }
1055 
1056   BOOST_TEST_CONTEXT("Volumes rotated relative to each other") {
1057     // At this time, all rotations are considered invalid, even around
1058     // orientation
1059     for (const Vector3 axis : {Vector3::UnitX(), Vector3::UnitY()}) {
1060       std::vector<Volume*> volumes;
1061       auto vol1 = std::make_shared<Volume>(
1062           Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1063           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1064       volumes.push_back(vol1.get());
1065 
1066       BOOST_TEST_MESSAGE("Axis: " << axis);
1067       auto vol2 = std::make_shared<Volume>(
1068           Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}} *
1069                      AngleAxis3(1_degree, axis)},
1070           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1071       volumes.push_back(vol2.get());
1072 
1073       BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy,
1074                                           VolumeResizeStrategy::Gap, *logger),
1075                         std::invalid_argument);
1076     }
1077   }
1078 
1079   BOOST_TEST_CONTEXT(
1080       "Volumes shifted in the orthogonal plane relative to each other") {
1081     for (const Vector3& shift :
1082          {Vector3{5_mm, 0, 0}, Vector3{0, -5_mm, 0}, Vector3{2_mm, -2_mm, 0}}) {
1083       std::vector<Volume*> volumes;
1084       auto vol1 = std::make_shared<Volume>(
1085           Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1086           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1087       volumes.push_back(vol1.get());
1088 
1089       auto vol2 = std::make_shared<Volume>(
1090           Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm} + shift}},
1091           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1092       volumes.push_back(vol2.get());
1093 
1094       BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy,
1095                                           VolumeResizeStrategy::Gap, *logger),
1096                         std::invalid_argument);
1097     }
1098   }
1099 }
1100 
1101 BOOST_DATA_TEST_CASE(JoinCuboidVolumeSingle,
1102                      (boost::unit_test::data::make(Acts::AxisDirection::AxisX,
1103                                                    Acts::AxisDirection::AxisY,
1104                                                    Acts::AxisDirection::AxisZ) *
1105                       boost::unit_test::data::make(strategies)),
1106                      direction, strategy) {
1107   auto vol = std::make_shared<Volume>(
1108       Transform3::Identity() * Translation3{14_mm, 24_mm, 0_mm} *
1109           AngleAxis3(73_degree, Vector3::UnitX()),
1110       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1111 
1112   std::vector<Volume*> volumes{vol.get()};
1113 
1114   CuboidVolumeStack stack(volumes, direction, strategy,
1115                           VolumeResizeStrategy::Gap, *logger);
1116 
1117   // Cuboid stack has the same transform as bounds as the single input
1118   // volume
1119   BOOST_CHECK_EQUAL(volumes.size(), 1);
1120   BOOST_CHECK_EQUAL(volumes.at(0), vol.get());
1121   BOOST_CHECK_EQUAL(vol->transform().matrix(), stack.transform().matrix());
1122   BOOST_CHECK_EQUAL(vol->volumeBounds(), stack.volumeBounds());
1123 }
1124 
1125 BOOST_AUTO_TEST_SUITE_END()
1126 BOOST_AUTO_TEST_SUITE_END()
1127 
1128 }  // namespace Acts::Test