Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-01 07:53:53

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::boundsFromAxisDirection(dir);
0089   auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0090   auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(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(Asymmetric,
0343                      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0344                                                   Acts::AxisDirection::AxisY,
0345                                                   Acts::AxisDirection::AxisZ),
0346                      dir) {
0347   double halfDir1 = 200_mm;
0348   double pDir1 = -1100_mm;
0349   double halfDir2 = 600_mm;
0350   double pDir2 = -200_mm;
0351   double halfDir3 = 400_mm;
0352   double pDir3 = 850_mm;
0353 
0354   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0355 
0356   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0357 
0358   auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0359   auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0360   auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0361 
0362   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0363       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0364           {boundDir, halfDir1},
0365           {boundDirOrth1, 100_mm},
0366           {boundDirOrth2, 400_mm}});
0367 
0368   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0369       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0370           {boundDir, halfDir2},
0371           {boundDirOrth1, 200_mm},
0372           {boundDirOrth2, 600_mm}});
0373 
0374   auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0375       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0376           {boundDir, halfDir3},
0377           {boundDirOrth1, 300_mm},
0378           {boundDirOrth2, 500_mm}});
0379 
0380   Translation3 translation1(Vector3::Unit(dirIdx) * pDir1);
0381   Transform3 transform1(translation1);
0382   auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0383 
0384   Translation3 translation2(Vector3::Unit(dirIdx) * pDir2);
0385   Transform3 transform2(translation2);
0386   auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0387 
0388   Translation3 translation3(Vector3::Unit(dirIdx) * pDir3);
0389   Transform3 transform3(translation3);
0390   auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0391 
0392   std::vector<Volume*> volumes = {vol2.get(), vol1.get(), vol3.get()};
0393 
0394   CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0395                           VolumeResizeStrategy::Gap, *logger);
0396   BOOST_CHECK_EQUAL(volumes.size(), 5);
0397 
0398   auto stackBounds =
0399       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0400   BOOST_REQUIRE(stackBounds != nullptr);
0401 
0402   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth1), 300_mm, 1e-6);
0403   BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth2), 600_mm, 1e-6);
0404   BOOST_CHECK_CLOSE(stackBounds->get(boundDir),
0405                     (std::abs(pDir1 - halfDir1) + pDir3 + halfDir3) / 2.0,
0406                     1e-6);
0407 
0408   double midDir = (pDir1 - halfDir1 + pDir3 + halfDir3) / 2.0;
0409   Translation3 expectedTranslation(Vector3::Unit(dirIdx) * midDir);
0410   Transform3 expectedTransform = Transform3::Identity() * expectedTranslation;
0411   CHECK_CLOSE_OR_SMALL(stack.transform().matrix(), expectedTransform.matrix(),
0412                        1e-10, 1e-12);
0413 }
0414 
0415 BOOST_DATA_TEST_CASE(UpdateStack,
0416                      (boost::unit_test::data::xrange(-135, 180, 45) *
0417                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0418                                                    Vector3{20_mm, 0_mm, 0_mm},
0419                                                    Vector3{0_mm, 20_mm, 0_mm},
0420                                                    Vector3{20_mm, 20_mm, 0_mm},
0421                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0422                       boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
0423                       boost::unit_test::data::make(resizeStrategies) *
0424                       boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0425                                                    Acts::AxisDirection::AxisY,
0426                                                    Acts::AxisDirection::AxisZ)),
0427                      angle, offset, zshift, strategy, dir) {
0428   double halfDir = 400_mm;
0429 
0430   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0431 
0432   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0433   auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0434 
0435   auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0436   auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0437   auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0438 
0439   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0440       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0441           {boundDir, halfDir},
0442           {boundDirOrth1, 100_mm},
0443           {boundDirOrth2, 600_mm}});
0444 
0445   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0446       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0447           {boundDir, halfDir},
0448           {boundDirOrth1, 100_mm},
0449           {boundDirOrth2, 600_mm}});
0450 
0451   auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0452       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0453           {boundDir, halfDir},
0454           {boundDirOrth1, 100_mm},
0455           {boundDirOrth2, 600_mm}});
0456 
0457   Vector3 shift = Vector3::Unit(dirIdx) * zshift;
0458   Transform3 base = AngleAxis3(angle * 1_degree, Vector3::Unit(dirOrth1Idx)) *
0459                     Translation3(offset + shift);
0460 
0461   Translation3 translation1(Vector3::Unit(dirIdx) * -2 * halfDir);
0462   Transform3 transform1 = base * translation1;
0463   auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0464 
0465   Transform3 transform2 = base;
0466   auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0467 
0468   Translation3 translation3(Vector3::Unit(dirIdx) * 2 * halfDir);
0469   Transform3 transform3 = base * translation3;
0470   auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0471 
0472   std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0473   std::vector<Volume*> originalVolumes = volumes;
0474 
0475   std::vector<Transform3> originalTransforms = {transform1, transform2,
0476                                                 transform3};
0477 
0478   CuboidVolumeStack stack(volumes, dir,
0479                           VolumeAttachmentStrategy::Gap,  // should not make a
0480                                                           // difference
0481                           strategy, *logger);
0482 
0483   const auto* originalBounds =
0484       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0485 
0486   auto assertOriginalBounds = [&]() {
0487     const auto* bounds =
0488         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0489     BOOST_REQUIRE(bounds != nullptr);
0490     BOOST_CHECK_EQUAL(bounds, originalBounds);
0491     BOOST_CHECK_CLOSE(bounds->get(boundDirOrth1), 100_mm, 1e-6);
0492     BOOST_CHECK_CLOSE(bounds->get(boundDirOrth2), 600_mm, 1e-6);
0493     BOOST_CHECK_CLOSE(bounds->get(boundDir), 3 * halfDir, 1e-6);
0494   };
0495 
0496   assertOriginalBounds();
0497 
0498   {
0499     // Assign a copy of the identical bounds gives identical bounds
0500     auto bounds = std::make_shared<CuboidVolumeBounds>(
0501         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0502     stack.update(bounds, std::nullopt, *logger);
0503     assertOriginalBounds();
0504   }
0505 
0506   {
0507     // Cannot decrease half length
0508     auto bounds = std::make_shared<CuboidVolumeBounds>(
0509         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0510     bounds->set(boundDirOrth1, 20_mm);
0511     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0512                       std::invalid_argument);
0513     assertOriginalBounds();
0514   }
0515 
0516   {
0517     // Cannot decrease half length
0518     auto bounds = std::make_shared<CuboidVolumeBounds>(
0519         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0520     bounds->set(boundDirOrth2, 200_mm);
0521     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0522                       std::invalid_argument);
0523     assertOriginalBounds();
0524   }
0525 
0526   {
0527     // Cannot decrease half length
0528     auto bounds = std::make_shared<CuboidVolumeBounds>(
0529         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0530     bounds->set(boundDir, 2 * halfDir);
0531     BOOST_CHECK_THROW(stack.update(bounds, std::nullopt, *logger),
0532                       std::invalid_argument);
0533     assertOriginalBounds();
0534   }
0535 
0536   {
0537     // Increase half length
0538     auto bounds = std::make_shared<CuboidVolumeBounds>(
0539         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0540     bounds->set(boundDirOrth1, 700_mm);
0541     stack.update(bounds, std::nullopt, *logger);
0542     const auto* updatedBounds =
0543         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0544     BOOST_REQUIRE(updatedBounds != nullptr);
0545     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0546     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 600_mm, 1e-6);
0547     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0548 
0549     // No gap volumes were added
0550     BOOST_CHECK_EQUAL(volumes.size(), 3);
0551 
0552     // All volumes increase half x to accommodate
0553     for (const auto& [volume, origTransform] :
0554          zip(volumes, originalTransforms)) {
0555       const auto* newBounds =
0556           dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0557       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0558       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 600_mm, 1e-6);
0559       BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0560 
0561       // Position stayed the same
0562       BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0563     }
0564   }
0565   {
0566     // Increase half length
0567     auto bounds = std::make_shared<CuboidVolumeBounds>(
0568         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0569     bounds->set(boundDirOrth2, 700_mm);
0570     stack.update(bounds, std::nullopt, *logger);
0571     const auto* updatedBounds =
0572         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0573     BOOST_REQUIRE(updatedBounds != nullptr);
0574     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0575     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0576     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0577 
0578     // No gap volumes were added
0579     BOOST_CHECK_EQUAL(volumes.size(), 3);
0580 
0581     // All volumes increase half y to accommodate
0582     for (const auto& [volume, origTransform] :
0583          zip(volumes, originalTransforms)) {
0584       const auto* newBounds =
0585           dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0586       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0587       BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0588       BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0589 
0590       // Position stayed the same
0591       BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0592     }
0593   }
0594 
0595   {
0596     // Increase half length
0597     auto bounds = std::make_shared<CuboidVolumeBounds>(
0598         dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0599     bounds->set(boundDir, 4 * halfDir);
0600     stack.update(bounds, std::nullopt, *logger);
0601     const auto* updatedBounds =
0602         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0603     BOOST_REQUIRE(updatedBounds != nullptr);
0604     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 4 * halfDir, 1e-6);
0605     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0606     BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0607 
0608     if (strategy == VolumeResizeStrategy::Expand) {
0609       // No gap volumes were added
0610       BOOST_CHECK_EQUAL(volumes.size(), 3);
0611 
0612       // Volume 1 got bigger and shifted left
0613       auto newBounds1 =
0614           dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0615       BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + halfDir / 2.0,
0616                         1e-6);
0617       auto expectedTranslation1 =
0618           Translation3(Vector3::Unit(dirIdx) * (-2 * halfDir - halfDir / 2.0));
0619       Transform3 expectedTransform1 = base * expectedTranslation1;
0620       CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0621                            expectedTransform1.matrix(), 1e-10, 1e-12);
0622 
0623       // Volume 2 stayed the same
0624       auto newBounds2 =
0625           dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0626       BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir, 1e-6);
0627       CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(), transform2.matrix(),
0628                            1e-10, 1e-12);
0629 
0630       // Volume 3 got bigger and shifted right
0631       auto newBounds3 =
0632           dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0633       BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + halfDir / 2.0,
0634                         1e-6);
0635       auto expectedTranslation3 =
0636           Translation3(Vector3::Unit(dirIdx) * (2 * halfDir + halfDir / 2.0));
0637       Transform3 expectedTransform3 = base * expectedTranslation3;
0638       CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0639                            expectedTransform3.matrix(), 1e-10, 1e-12);
0640     } else if (strategy == VolumeResizeStrategy::Gap) {
0641       // Gap volumes were added
0642       BOOST_CHECK_EQUAL(volumes.size(), 5);
0643 
0644       for (const auto& [volume, origTransform] :
0645            zip(originalVolumes, originalTransforms)) {
0646         const auto* newBounds =
0647             dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0648         BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0649         BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0650         BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0651         // Position stayed the same
0652         CHECK_CLOSE_OR_SMALL(volume->transform().matrix(),
0653                              origTransform.matrix(), 1e-10, 1e-12);
0654       }
0655 
0656       auto gap1 = volumes.front();
0657       auto gap2 = volumes.back();
0658 
0659       const auto* gapBounds1 =
0660           dynamic_cast<const CuboidVolumeBounds*>(&gap1->volumeBounds());
0661       const auto* gapBounds2 =
0662           dynamic_cast<const CuboidVolumeBounds*>(&gap2->volumeBounds());
0663 
0664       BOOST_CHECK_CLOSE(gapBounds1->get(boundDir), halfDir / 2.0, 1e-6);
0665       BOOST_CHECK_CLOSE(gapBounds2->get(boundDir), halfDir / 2.0, 1e-6);
0666       auto gap1Translation =
0667           Translation3(Vector3::Unit(dirIdx) * (-3 * halfDir - halfDir / 2.0));
0668       Transform3 gap1Transform = base * gap1Translation;
0669 
0670       auto gap2Translation =
0671           Translation3(Vector3::Unit(dirIdx) * (3 * halfDir + halfDir / 2.0));
0672       Transform3 gap2Transform = base * gap2Translation;
0673       CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0674                            1e-10, 1e-12);
0675       CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0676                            1e-10, 1e-12);
0677     }
0678   }
0679 }
0680 
0681 BOOST_DATA_TEST_CASE(
0682     UpdateStackOneSided,
0683     ((boost::unit_test::data::make(-1.0, 1.0) ^
0684       boost::unit_test::data::make(VolumeResizeStrategy::Gap,
0685                                    VolumeResizeStrategy::Expand)) *
0686      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0687                                   Acts::AxisDirection::AxisY,
0688                                   Acts::AxisDirection::AxisZ)),
0689     f, strategy, dir) {
0690   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0691 
0692   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0693   auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0694   auto dirOrth2Idx = CuboidVolumeStack::axisToIndex(dirOrth2);
0695 
0696   auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0697   auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0698   auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0699 
0700   auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0701       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0702           {boundDir, 400_mm},
0703           {boundDirOrth1, 100_mm},
0704           {boundDirOrth2, 300_mm}});
0705 
0706   auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0707       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0708           {boundDir, 400_mm},
0709           {boundDirOrth1, 100_mm},
0710           {boundDirOrth2, 300_mm}});
0711 
0712   auto trf = Transform3::Identity();
0713 
0714   auto translation1 = Translation3(Vector3::Unit(dirIdx) * -500_mm);
0715   auto trf1 = trf * translation1;
0716   auto vol1 = std::make_shared<Volume>(trf1, bounds1);
0717 
0718   auto translation2 = Translation3(Vector3::Unit(dirIdx) * 500_mm);
0719   auto trf2 = trf * translation2;
0720   auto vol2 = std::make_shared<Volume>(trf2, bounds2);
0721 
0722   std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0723 
0724   CuboidVolumeStack stack{volumes, dir, VolumeAttachmentStrategy::Gap, strategy,
0725                           *logger};
0726   const auto* originalBounds =
0727       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0728 
0729   // Increase half length by 50mm
0730   auto newBounds = std::make_shared<CuboidVolumeBounds>(
0731       dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0732   newBounds->set(boundDir, 950_mm);
0733   // Shift to +stacking direction by 50mm
0734   auto delta = Translation3(Vector3::Unit(dirIdx) * f * 50_mm);
0735   trf *= delta;
0736   // -> left edge should stay at -400mm, right edge should be at 500mm or the
0737   // other direction
0738   auto checkUnchanged = [&]() {
0739     const auto* bounds =
0740         dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0741     BOOST_REQUIRE(bounds != nullptr);
0742     BOOST_CHECK_EQUAL(*bounds, *originalBounds);
0743   };
0744 
0745   // Invalid: shift too far in merging direction
0746   BOOST_CHECK_THROW(
0747       auto errDelta = Translation3(Vector3::Unit(dirIdx) * f * 20_mm);
0748       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0749   checkUnchanged();
0750 
0751   // Invalid: shift in orthogonal direction
0752   BOOST_CHECK_THROW(
0753       auto errDelta = Translation3(Vector3::Unit(dirOrth1Idx) * 10_mm);
0754       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0755   checkUnchanged();
0756 
0757   // Invalid: shift in orthogonal direction
0758   BOOST_CHECK_THROW(
0759       auto errDelta = Translation3(Vector3::Unit(dirOrth2Idx) * 10_mm);
0760       stack.update(newBounds, trf * errDelta, *logger), std::invalid_argument);
0761   checkUnchanged();
0762 
0763   // Invalid: rotation
0764   BOOST_CHECK_THROW(
0765       stack.update(newBounds,
0766                    trf * AngleAxis3{10_degree, Vector3::Unit(dirOrth1Idx)},
0767                    *logger),
0768       std::invalid_argument);
0769   checkUnchanged();
0770 
0771   stack.update(newBounds, trf, *logger);
0772 
0773   CHECK_CLOSE_OR_SMALL(stack.transform().matrix(), trf.matrix(), 1e-10, 1e-12);
0774   const auto* bounds =
0775       dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0776   BOOST_REQUIRE(bounds != nullptr);
0777   BOOST_CHECK_CLOSE(bounds->get(boundDir), 950_mm, 1e-6);
0778 
0779   // All volumes including gaps should have same size in orthogonal plane
0780   for (const auto* vol : volumes) {
0781     const auto* volBounds =
0782         dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0783     BOOST_REQUIRE(volBounds != nullptr);
0784     BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth1), 100_mm, 1e-6);
0785     BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth2), 300_mm, 1e-6);
0786   }
0787 
0788   if (strategy == VolumeResizeStrategy::Expand) {
0789     // No gaps were added, there was one gap initially
0790     BOOST_CHECK_EQUAL(volumes.size(), 3);
0791     const Volume* vol = nullptr;
0792     if (f < 0.0) {
0793       // first volume should have gotten bigger
0794       vol = volumes.front();
0795     } else {
0796       // last volume should have gotten bigger
0797       vol = volumes.back();
0798     }
0799 
0800     const auto* volBounds =
0801         dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0802     BOOST_REQUIRE(volBounds != nullptr);
0803     BOOST_CHECK_CLOSE(volBounds->get(boundDir), 450_mm, 1e-6);
0804     BOOST_CHECK_EQUAL(vol->center()[dirIdx], f * 550_mm);
0805   } else if (strategy == VolumeResizeStrategy::Gap) {
0806     // One gap volume was added
0807     BOOST_CHECK_EQUAL(volumes.size(), 4);
0808 
0809     const Volume* gap = nullptr;
0810     if (f < 0.0) {
0811       gap = volumes.front();
0812     } else {
0813       gap = volumes.back();
0814     }
0815     const auto* gapBounds =
0816         dynamic_cast<const CuboidVolumeBounds*>(&gap->volumeBounds());
0817     BOOST_REQUIRE(gapBounds != nullptr);
0818 
0819     BOOST_CHECK_CLOSE(gapBounds->get(boundDir), 50_mm, 1e-6);
0820     BOOST_CHECK_EQUAL(gap->center()[dirIdx], f * 950_mm);
0821   }
0822 }
0823 
0824 //   original size
0825 // <--------------->
0826 // +---------------+
0827 // |               |
0828 // |               |
0829 // |   Volume 1    |
0830 // |               |
0831 // |               |
0832 // +---------------+
0833 //         first resize
0834 // <-------------------------->
0835 // +---------------+----------+
0836 // |               |          |
0837 // |               |          |
0838 // |   Volume 1    |   Gap    |
0839 // |               |          |      Gap is
0840 // |               |          |      reused!--+
0841 // +---------------+----------+               |
0842 //             second resize                  |
0843 // <----------------------------------->      |
0844 // +---------------+-------------------+      |
0845 // |               |                   |      |
0846 // |               |                   |      |
0847 // |   Volume 1    |        Gap        |<-----+
0848 // |               |                   |
0849 // |               |                   |
0850 // +---------------+-------------------+
0851 //
0852 BOOST_DATA_TEST_CASE(ResizeGapMultiple,
0853                      boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0854                                                   Acts::AxisDirection::AxisY,
0855                                                   Acts::AxisDirection::AxisZ),
0856                      dir) {
0857   auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0858 
0859   auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0860 
0861   auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0862   auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0863   auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0864 
0865   auto bounds = std::make_shared<CuboidVolumeBounds>(
0866       std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0867           {boundDir, 100}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0868   Transform3 trf = Transform3::Identity();
0869   Volume vol{trf, bounds};
0870 
0871   BOOST_TEST_CONTEXT("Positive") {
0872     std::vector<Volume*> volumes = {&vol};
0873     CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0874                             VolumeResizeStrategy::Gap, *logger);
0875 
0876     BOOST_CHECK_EQUAL(volumes.size(), 1);
0877     BOOST_CHECK(stack.gaps().empty());
0878 
0879     auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0880         std::initializer_list<
0881             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0882             {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0883     stack.update(newBounds1, trf * Translation3{Vector3::Unit(dirIdx) * 100},
0884                  *logger);
0885     BOOST_CHECK_EQUAL(volumes.size(), 2);
0886     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0887 
0888     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], 200.0);
0889     const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0890         &stack.gaps().front()->volumeBounds());
0891     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0892     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
0893 
0894     auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
0895         std::initializer_list<
0896             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0897             {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0898     stack.update(newBounds2, trf * Translation3{Vector3::Unit(dirIdx) * 200},
0899                  *logger);
0900 
0901     BOOST_CHECK_EQUAL(volumes.size(), 2);
0902     // No additional gap volume was added!
0903     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0904 
0905     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], 300.0);
0906     updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0907         &stack.gaps().front()->volumeBounds());
0908     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0909     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
0910   }
0911 
0912   BOOST_TEST_CONTEXT("Negative") {
0913     std::vector<Volume*> volumes = {&vol};
0914     CuboidVolumeStack stack(volumes, dir, VolumeAttachmentStrategy::Gap,
0915                             VolumeResizeStrategy::Gap, *logger);
0916 
0917     BOOST_CHECK_EQUAL(volumes.size(), 1);
0918     BOOST_CHECK(stack.gaps().empty());
0919 
0920     auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0921         std::initializer_list<
0922             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0923             {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0924     stack.update(newBounds1, trf * Translation3{Vector3::Unit(dirIdx) * -100},
0925                  *logger);
0926     BOOST_CHECK_EQUAL(volumes.size(), 2);
0927     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0928 
0929     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], -200.0);
0930     const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0931         &stack.gaps().front()->volumeBounds());
0932     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0933     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
0934 
0935     auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
0936         std::initializer_list<
0937             std::pair<CuboidVolumeBounds::BoundValues, double>>{
0938             {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0939     stack.update(newBounds2, trf * Translation3{Vector3::Unit(dirIdx) * -200},
0940                  *logger);
0941 
0942     BOOST_CHECK_EQUAL(volumes.size(), 2);
0943     // No additional gap volume was added!
0944     BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0945 
0946     BOOST_CHECK_EQUAL(stack.gaps().front()->center()[dirIdx], -300.0);
0947     updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0948         &stack.gaps().front()->volumeBounds());
0949     BOOST_REQUIRE_NE(updatedBounds, nullptr);
0950     BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
0951   }
0952 }
0953 
0954 BOOST_DATA_TEST_CASE(InvalidDirection, boost::unit_test::data::make(strategies),
0955                      strategy) {
0956   std::vector<Volume*> volumes;
0957   auto vol1 = std::make_shared<Volume>(
0958       Transform3::Identity(),
0959       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
0960   volumes.push_back(vol1.get());
0961 
0962   // Single volume invalid direction still gives an error
0963   BOOST_CHECK_THROW(CuboidVolumeStack(volumes, AxisDirection::AxisR, strategy),
0964                     std::invalid_argument);
0965 
0966   auto vol2 = std::make_shared<Volume>(
0967       Transform3::Identity(),
0968       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
0969   volumes.push_back(vol2.get());
0970 
0971   BOOST_CHECK_THROW(CuboidVolumeStack(volumes, AxisDirection::AxisR, strategy),
0972                     std::invalid_argument);
0973 }
0974 
0975 BOOST_DATA_TEST_CASE(InvalidInput,
0976                      (boost::unit_test::data::make(strategies) *
0977                       boost::unit_test::data::make(Acts::AxisDirection::AxisX,
0978                                                    Acts::AxisDirection::AxisY,
0979                                                    Acts::AxisDirection::AxisZ)),
0980                      strategy, direction) {
0981   BOOST_TEST_CONTEXT("Empty Volume") {
0982     std::vector<Volume*> volumes;
0983     BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy),
0984                       std::invalid_argument);
0985   }
0986 
0987   BOOST_TEST_CONTEXT("Volumes rotated relative to each other") {
0988     // At this time, all rotations are considered invalid, even around
0989     // orientation
0990     for (const Vector3 axis : {Vector3::UnitX(), Vector3::UnitY()}) {
0991       std::vector<Volume*> volumes;
0992       auto vol1 = std::make_shared<Volume>(
0993           Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
0994           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
0995       volumes.push_back(vol1.get());
0996 
0997       BOOST_TEST_MESSAGE("Axis: " << axis);
0998       auto vol2 = std::make_shared<Volume>(
0999           Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}} *
1000                      AngleAxis3(1_degree, axis)},
1001           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1002       volumes.push_back(vol2.get());
1003 
1004       BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy,
1005                                           VolumeResizeStrategy::Gap, *logger),
1006                         std::invalid_argument);
1007     }
1008   }
1009 
1010   BOOST_TEST_CONTEXT(
1011       "Volumes shifted in the orthogonal plane relative to each other") {
1012     for (const Vector3& shift :
1013          {Vector3{5_mm, 0, 0}, Vector3{0, -5_mm, 0}, Vector3{2_mm, -2_mm, 0}}) {
1014       std::vector<Volume*> volumes;
1015       auto vol1 = std::make_shared<Volume>(
1016           Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1017           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1018       volumes.push_back(vol1.get());
1019 
1020       auto vol2 = std::make_shared<Volume>(
1021           Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm} + shift}},
1022           std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1023       volumes.push_back(vol2.get());
1024 
1025       BOOST_CHECK_THROW(CuboidVolumeStack(volumes, direction, strategy,
1026                                           VolumeResizeStrategy::Gap, *logger),
1027                         std::invalid_argument);
1028     }
1029   }
1030 }
1031 
1032 BOOST_DATA_TEST_CASE(JoinCuboidVolumeSingle,
1033                      (boost::unit_test::data::make(Acts::AxisDirection::AxisX,
1034                                                    Acts::AxisDirection::AxisY,
1035                                                    Acts::AxisDirection::AxisZ) *
1036                       boost::unit_test::data::make(strategies)),
1037                      direction, strategy) {
1038   auto vol = std::make_shared<Volume>(
1039       Transform3::Identity() * Translation3{14_mm, 24_mm, 0_mm} *
1040           AngleAxis3(73_degree, Vector3::UnitX()),
1041       std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1042 
1043   std::vector<Volume*> volumes{vol.get()};
1044 
1045   CuboidVolumeStack stack(volumes, direction, strategy,
1046                           VolumeResizeStrategy::Gap, *logger);
1047 
1048   // Cuboid stack has the same transform as bounds as the single input
1049   // volume
1050   BOOST_CHECK_EQUAL(volumes.size(), 1);
1051   BOOST_CHECK_EQUAL(volumes.at(0), vol.get());
1052   BOOST_CHECK_EQUAL(vol->transform().matrix(), stack.transform().matrix());
1053   BOOST_CHECK_EQUAL(vol->volumeBounds(), stack.volumeBounds());
1054 }
1055 
1056 BOOST_AUTO_TEST_SUITE_END()
1057 BOOST_AUTO_TEST_SUITE_END()
1058 
1059 }  // namespace Acts::Test