File indexing completed on 2025-01-18 09:12:38
0001
0002
0003
0004
0005
0006
0007
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/CylinderVolumeBounds.hpp"
0021 #include "Acts/Geometry/CylinderVolumeStack.hpp"
0022 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0023 #include "Acts/Utilities/BinningType.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025 #include "Acts/Utilities/Zip.hpp"
0026
0027 #include <numbers>
0028
0029 using namespace Acts::UnitLiterals;
0030
0031 namespace Acts::Test {
0032
0033 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0034
0035 struct Fixture {
0036 Logging::Level m_level;
0037 Fixture() {
0038 m_level = Acts::Logging::getFailureThreshold();
0039 Acts::Logging::setFailureThreshold(Acts::Logging::FATAL);
0040 }
0041
0042 ~Fixture() { Acts::Logging::setFailureThreshold(m_level); }
0043 };
0044
0045 BOOST_FIXTURE_TEST_SUITE(Geometry, Fixture)
0046
0047 static const std::vector<CylinderVolumeStack::AttachmentStrategy> strategies = {
0048 CylinderVolumeStack::AttachmentStrategy::Gap,
0049 CylinderVolumeStack::AttachmentStrategy::First,
0050 CylinderVolumeStack::AttachmentStrategy::Second,
0051 CylinderVolumeStack::AttachmentStrategy::Midpoint,
0052 };
0053
0054 static const std::vector<CylinderVolumeStack::ResizeStrategy> resizeStrategies =
0055 {
0056 CylinderVolumeStack::ResizeStrategy::Expand,
0057 CylinderVolumeStack::ResizeStrategy::Gap,
0058 };
0059
0060 BOOST_AUTO_TEST_SUITE(CylinderVolumeStackTest)
0061 BOOST_AUTO_TEST_SUITE(ZDirection)
0062
0063 BOOST_DATA_TEST_CASE(Baseline,
0064 (boost::unit_test::data::xrange(-135, 180, 45) *
0065 boost::unit_test::data::xrange(0, 2, 1) *
0066 boost::unit_test::data::make(0.8, 1.0, 1.2) *
0067 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0068 Vector3{20_mm, 0_mm, 0_mm},
0069 Vector3{0_mm, 20_mm, 0_mm},
0070 Vector3{20_mm, 20_mm, 0_mm},
0071 Vector3{0_mm, 0_mm, 20_mm}) *
0072 boost::unit_test::data::make(strategies)),
0073 angle, rotate, shift, offset, strategy) {
0074 double hlZ = 400_mm;
0075
0076
0077 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, hlZ);
0078 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 600_mm, hlZ);
0079 auto bounds3 = std::make_shared<CylinderVolumeBounds>(300_mm, 500_mm, hlZ);
0080
0081 Transform3 base =
0082 AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0083
0084 Transform3 transform1 = base;
0085 transform1.translate(Vector3{0_mm, 0_mm, -2 * hlZ * shift});
0086 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0087
0088 Transform3 transform2 = base;
0089 transform2.translate(Vector3{0_mm, 0_mm, 0_mm});
0090 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0091
0092 Transform3 transform3 = base;
0093 transform3.translate(Vector3{0_mm, 0_mm, 2 * hlZ * shift});
0094 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0095
0096 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0097
0098 std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
0099
0100 auto origVolumes = volumes;
0101
0102 std::vector<CylinderVolumeBounds> originalBounds;
0103 std::transform(
0104 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
0105 [](const auto& vol) {
0106 return *dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0107 });
0108
0109 if (shift < 1.0) {
0110 BOOST_CHECK_THROW(
0111 CylinderVolumeStack(volumes, AxisDirection::AxisZ, strategy,
0112 CylinderVolumeStack::ResizeStrategy::Gap, *logger),
0113 std::invalid_argument);
0114 return;
0115 }
0116 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisZ, strategy,
0117 CylinderVolumeStack::ResizeStrategy::Gap,
0118 *logger);
0119
0120 auto stackBounds =
0121 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0122 BOOST_REQUIRE(stackBounds != nullptr);
0123
0124 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0125 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0126 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0127 hlZ + 2 * hlZ * shift);
0128 CHECK_CLOSE_OR_SMALL(cylStack.transform().matrix(), base.matrix(), 1e-10,
0129 1e-14);
0130
0131
0132 for (const auto& volume : volumes) {
0133 const auto* cylinderBounds =
0134 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0135 BOOST_REQUIRE(cylinderBounds != nullptr);
0136 BOOST_CHECK_EQUAL(cylinderBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0137 BOOST_CHECK_EQUAL(cylinderBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0138 }
0139
0140
0141 for (std::size_t i = 0; i < volumes.size() - 1; ++i) {
0142 const auto& a = volumes.at(i);
0143 const auto& b = volumes.at(i + 1);
0144
0145 BOOST_CHECK_LT((base.inverse() * a->center())[eZ],
0146 (base.inverse() * b->center())[eZ]);
0147 }
0148
0149 if (shift <= 1.0) {
0150
0151 BOOST_CHECK_EQUAL(volumes.size(), 3);
0152
0153
0154 BOOST_CHECK_EQUAL(vol1->transform().matrix(), transform1.matrix());
0155 BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0156 BOOST_CHECK_EQUAL(vol3->transform().matrix(), transform3.matrix());
0157
0158 for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0159 const auto* newBounds =
0160 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0161 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0162 bounds.get(CylinderVolumeBounds::eHalfLengthZ));
0163 }
0164 } else {
0165 if (strategy == CylinderVolumeStack::AttachmentStrategy::Gap) {
0166
0167 BOOST_CHECK_EQUAL(volumes.size(), 5);
0168 auto gap1 = volumes.at(1);
0169 auto gap2 = volumes.at(3);
0170
0171 BOOST_TEST_MESSAGE("Gap 1: " << gap1->transform().matrix());
0172 BOOST_TEST_MESSAGE("Gap 2: " << gap2->transform().matrix());
0173
0174 const auto* gapBounds1 =
0175 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
0176 const auto* gapBounds2 =
0177 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
0178
0179 double gapHlZ = (shift - 1.0) * hlZ;
0180
0181 BOOST_CHECK(std::abs(gapBounds1->get(CylinderVolumeBounds::eHalfLengthZ) -
0182 gapHlZ) < 1e-10);
0183 BOOST_CHECK(std::abs(gapBounds2->get(CylinderVolumeBounds::eHalfLengthZ) -
0184 gapHlZ) < 1e-10);
0185
0186 double gap1Z = (-2 * hlZ * shift) + hlZ + gapHlZ;
0187 double gap2Z = (2 * hlZ * shift) - hlZ - gapHlZ;
0188
0189 Transform3 gap1Transform = base * Translation3{0_mm, 0_mm, gap1Z};
0190 Transform3 gap2Transform = base * Translation3{0_mm, 0_mm, gap2Z};
0191
0192 CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0193 1e-10, 1e-14);
0194 CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0195 1e-10, 1e-14);
0196
0197
0198 for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0199 const auto* newBounds =
0200 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0201 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0202 bounds.get(CylinderVolumeBounds::eHalfLengthZ));
0203 }
0204
0205
0206 BOOST_CHECK_EQUAL(vol1->transform().matrix(), transform1.matrix());
0207 BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0208 BOOST_CHECK_EQUAL(vol3->transform().matrix(), transform3.matrix());
0209
0210 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::First) {
0211
0212 BOOST_CHECK_EQUAL(volumes.size(), 3);
0213
0214 double wGap = (shift - 1.0) * hlZ * 2;
0215
0216
0217 auto newBounds1 =
0218 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0219 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0220 hlZ + wGap / 2.0);
0221 double pZ1 = -2 * hlZ * shift + wGap / 2.0;
0222 Transform3 expectedTransform1 = base * Translation3{0_mm, 0_mm, pZ1};
0223 CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0224 expectedTransform1.matrix(), 1e-10, 1e-14);
0225
0226
0227 auto newBounds2 =
0228 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0229 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0230 hlZ + wGap / 2.0);
0231 double pZ2 = wGap / 2.0;
0232 Transform3 expectedTransform2 = base * Translation3{0_mm, 0_mm, pZ2};
0233 CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(),
0234 expectedTransform2.matrix(), 1e-10, 1e-14);
0235
0236
0237 auto newBounds3 =
0238 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
0239 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eHalfLengthZ),
0240 hlZ);
0241 double pZ3 = 2 * hlZ * shift;
0242 Transform3 expectedTransform3 = base * Translation3{0_mm, 0_mm, pZ3};
0243 CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0244 expectedTransform3.matrix(), 1e-10, 1e-14);
0245 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Second) {
0246
0247 BOOST_CHECK_EQUAL(volumes.size(), 3);
0248
0249 double wGap = (shift - 1.0) * hlZ * 2;
0250
0251
0252 auto newBounds1 =
0253 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0254 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0255 hlZ);
0256 double pZ1 = -2 * hlZ * shift;
0257 Transform3 expectedTransform1 = base * Translation3{0_mm, 0_mm, pZ1};
0258 CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0259 expectedTransform1.matrix(), 1e-10, 1e-14);
0260
0261
0262 auto newBounds2 =
0263 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0264 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0265 hlZ + wGap / 2.0);
0266 double pZ2 = -wGap / 2.0;
0267 Transform3 expectedTransform2 = base * Translation3{0_mm, 0_mm, pZ2};
0268 CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(),
0269 expectedTransform2.matrix(), 1e-10, 1e-14);
0270
0271
0272 auto newBounds3 =
0273 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
0274 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eHalfLengthZ),
0275 hlZ + wGap / 2.0);
0276 double pZ3 = 2 * hlZ * shift - wGap / 2.0;
0277 Transform3 expectedTransform3 = base * Translation3{0_mm, 0_mm, pZ3};
0278 CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0279 expectedTransform3.matrix(), 1e-10, 1e-14);
0280 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Midpoint) {
0281
0282 BOOST_CHECK_EQUAL(volumes.size(), 3);
0283
0284 double wGap = (shift - 1.0) * hlZ * 2;
0285
0286
0287 auto newBounds1 =
0288 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0289 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0290 hlZ + wGap / 4.0);
0291 double pZ1 = -2 * hlZ * shift + wGap / 4.0;
0292 Transform3 expectedTransform1 = base * Translation3{0_mm, 0_mm, pZ1};
0293 CHECK_CLOSE_OR_SMALL(vol1->transform().matrix(),
0294 expectedTransform1.matrix(), 1e-10, 1e-14);
0295
0296
0297 auto newBounds2 =
0298 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0299 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0300 hlZ + wGap / 2.0);
0301 CHECK_CLOSE_OR_SMALL(vol2->transform().matrix(), base.matrix(), 1e-10,
0302 1e-14);
0303
0304
0305 auto newBounds3 =
0306 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
0307 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eHalfLengthZ),
0308 hlZ + wGap / 4.0);
0309 double pZ3 = 2 * hlZ * shift - wGap / 4.0;
0310 Transform3 expectedTransform3 = base * Translation3{0_mm, 0_mm, pZ3};
0311 CHECK_CLOSE_OR_SMALL(vol3->transform().matrix(),
0312 expectedTransform3.matrix(), 1e-10, 1e-14);
0313 }
0314 }
0315 }
0316
0317 BOOST_AUTO_TEST_CASE(Asymmetric) {
0318 double hlZ1 = 200_mm;
0319 double pZ1 = -1100_mm;
0320 double hlZ2 = 600_mm;
0321 double pZ2 = -200_mm;
0322 double hlZ3 = 400_mm;
0323 double pZ3 = 850_mm;
0324
0325
0326 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, hlZ1);
0327 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 600_mm, hlZ2);
0328 auto bounds3 = std::make_shared<CylinderVolumeBounds>(300_mm, 500_mm, hlZ3);
0329
0330 Transform3 transform1 = Transform3::Identity();
0331 transform1.translate(Vector3{0_mm, 0_mm, pZ1});
0332 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0333
0334 Transform3 transform2 = Transform3::Identity();
0335 transform2.translate(Vector3{0_mm, 0_mm, pZ2});
0336 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0337
0338 Transform3 transform3 = Transform3::Identity();
0339 transform3.translate(Vector3{0_mm, 0_mm, pZ3});
0340 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0341
0342 std::vector<Volume*> volumes = {vol2.get(), vol1.get(), vol3.get()};
0343
0344 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisZ,
0345 CylinderVolumeStack::AttachmentStrategy::Gap,
0346 CylinderVolumeStack::ResizeStrategy::Gap,
0347 *logger);
0348 BOOST_CHECK_EQUAL(volumes.size(), 5);
0349
0350 auto stackBounds =
0351 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0352 BOOST_REQUIRE(stackBounds != nullptr);
0353
0354 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0355 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0356 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0357 (std::abs(pZ1 - hlZ1) + pZ3 + hlZ3) / 2.0);
0358
0359 double midZ = (pZ1 - hlZ1 + pZ3 + hlZ3) / 2.0;
0360 Transform3 expectedTransform{Translation3{0_mm, 0_mm, midZ}};
0361 CHECK_CLOSE_OR_SMALL(cylStack.transform().matrix(),
0362 expectedTransform.matrix(), 1e-10, 1e-14);
0363 }
0364
0365 BOOST_DATA_TEST_CASE(RotationInZ, boost::unit_test::data::make(strategies),
0366 strategy) {
0367 double hlZ = 400_mm;
0368 double gap = 100_mm;
0369 double shift = 300_mm;
0370
0371 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, hlZ);
0372 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 300_mm, hlZ);
0373
0374 auto vol1 = std::make_shared<Volume>(
0375 Transform3::Identity() *
0376 Translation3{0_mm, 0_mm, -hlZ - gap / 2.0 + shift},
0377 bounds1);
0378
0379 auto vol2 = std::make_shared<Volume>(
0380 Transform3::Identity() *
0381 Translation3{0_mm, 0_mm, hlZ + gap / 2.0 + shift} *
0382 AngleAxis3{30_degree, Vector3::UnitZ()},
0383 bounds2);
0384
0385 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0386
0387 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisZ, strategy,
0388 CylinderVolumeStack::ResizeStrategy::Gap,
0389 *logger);
0390
0391 auto stackBounds =
0392 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0393 BOOST_REQUIRE(stackBounds != nullptr);
0394 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0395 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR), 400_mm);
0396 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0397 2 * hlZ + gap / 2.0);
0398
0399 auto newBounds1 =
0400 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0401 auto newBounds2 =
0402 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0403
0404 for (const auto& bounds : {newBounds1, newBounds2}) {
0405 BOOST_CHECK_EQUAL(bounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0406 BOOST_CHECK_EQUAL(bounds->get(CylinderVolumeBounds::eMaxR), 400_mm);
0407 }
0408
0409 if (strategy == CylinderVolumeStack::AttachmentStrategy::Gap) {
0410
0411 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 2.0 + shift);
0412 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 2.0 + shift);
0413 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0414 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0415 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::First) {
0416
0417 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ + shift);
0418 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0419 hlZ + gap / 2.0);
0420
0421 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 2.0 + shift);
0422 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0423 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Second) {
0424
0425 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 2.0 + shift);
0426 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0427
0428 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + shift);
0429 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0430 hlZ + gap / 2.0);
0431 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Midpoint) {
0432
0433 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 4.0 + shift);
0434 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0435 hlZ + gap / 4.0);
0436
0437
0438 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 4.0 + shift);
0439 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0440 hlZ + gap / 4.0);
0441 }
0442 }
0443
0444 BOOST_DATA_TEST_CASE(UpdateStack,
0445 (boost::unit_test::data::xrange(-135, 180, 45) *
0446 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0447 Vector3{20_mm, 0_mm, 0_mm},
0448 Vector3{0_mm, 20_mm, 0_mm},
0449 Vector3{20_mm, 20_mm, 0_mm},
0450 Vector3{0_mm, 0_mm, 20_mm}) *
0451 boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
0452 boost::unit_test::data::make(resizeStrategies)),
0453 angle, offset, zshift, strategy) {
0454 double hlZ = 400_mm;
0455
0456
0457 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0458 auto bounds2 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0459 auto bounds3 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0460
0461 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::UnitX()) *
0462 Translation3(offset + Vector3{0_mm, 0_mm, zshift});
0463
0464 Transform3 transform1 = base;
0465 transform1.translate(Vector3{0_mm, 0_mm, -2 * hlZ});
0466 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0467
0468 Transform3 transform2 = base;
0469 transform2.translate(Vector3{0_mm, 0_mm, 0_mm});
0470 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0471
0472 Transform3 transform3 = base;
0473 transform3.translate(Vector3{0_mm, 0_mm, 2 * hlZ});
0474 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0475
0476 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0477 std::vector<Volume*> originalVolumes = volumes;
0478
0479 std::vector<Transform3> originalTransforms = {transform1, transform2,
0480 transform3};
0481
0482 CylinderVolumeStack cylStack(
0483 volumes, AxisDirection::AxisZ,
0484 CylinderVolumeStack::AttachmentStrategy::Gap,
0485
0486 strategy, *logger);
0487
0488 const auto* originalBounds =
0489 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0490
0491 auto assertOriginalBounds = [&]() {
0492 const auto* cylBounds =
0493 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0494 BOOST_REQUIRE(cylBounds != nullptr);
0495 BOOST_CHECK_EQUAL(cylBounds, originalBounds);
0496 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0497 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0498 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0499 3 * hlZ);
0500 };
0501
0502 assertOriginalBounds();
0503
0504 {
0505
0506 auto bounds = std::make_shared<CylinderVolumeBounds>(
0507 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0508 cylStack.update(bounds, std::nullopt, *logger);
0509 assertOriginalBounds();
0510 }
0511
0512 {
0513
0514 auto bounds = std::make_shared<CylinderVolumeBounds>(
0515 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0516 bounds->set(CylinderVolumeBounds::eMinR, 200_mm);
0517 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0518 std::invalid_argument);
0519 assertOriginalBounds();
0520 }
0521
0522 {
0523
0524 auto bounds = std::make_shared<CylinderVolumeBounds>(
0525 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0526 bounds->set(CylinderVolumeBounds::eMaxR, 500_mm);
0527 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0528 std::invalid_argument);
0529 assertOriginalBounds();
0530 }
0531
0532 {
0533
0534 auto bounds = std::make_shared<CylinderVolumeBounds>(
0535 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0536 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 2 * hlZ);
0537 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0538 std::invalid_argument);
0539 assertOriginalBounds();
0540 }
0541
0542 {
0543
0544 auto bounds = std::make_shared<CylinderVolumeBounds>(
0545 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0546 bounds->set(CylinderVolumeBounds::eMinR, 50_mm);
0547 cylStack.update(bounds, std::nullopt, *logger);
0548 const auto* cylBounds =
0549 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0550 BOOST_REQUIRE(cylBounds != nullptr);
0551 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0552
0553 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0554 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0555 3 * hlZ);
0556
0557
0558 BOOST_CHECK_EQUAL(volumes.size(), 3);
0559
0560
0561 for (const auto& [volume, origTransform] :
0562 zip(volumes, originalTransforms)) {
0563 const auto* newBounds =
0564 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0565 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0566 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0567 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0568 hlZ);
0569
0570
0571 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0572 }
0573 }
0574
0575 {
0576
0577 auto bounds = std::make_shared<CylinderVolumeBounds>(
0578 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0579 bounds->set(CylinderVolumeBounds::eMaxR, 700_mm);
0580 cylStack.update(bounds, std::nullopt, *logger);
0581 const auto* cylBounds =
0582 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0583 BOOST_REQUIRE(cylBounds != nullptr);
0584 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0585
0586 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0587 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0588 3 * hlZ);
0589
0590
0591 BOOST_CHECK_EQUAL(volumes.size(), 3);
0592
0593
0594 for (const auto& [volume, origTransform] :
0595 zip(volumes, originalTransforms)) {
0596 const auto* newBounds =
0597 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0598 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0599 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0600 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0601 hlZ);
0602
0603
0604 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0605 }
0606 }
0607
0608 {
0609
0610 auto bounds = std::make_shared<CylinderVolumeBounds>(
0611 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0612 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 4 * hlZ);
0613 cylStack.update(bounds, std::nullopt, *logger);
0614 const auto* cylBounds =
0615 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0616 BOOST_REQUIRE(cylBounds != nullptr);
0617 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0618 4 * hlZ);
0619
0620
0621 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0622 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0623
0624 if (strategy == CylinderVolumeStack::ResizeStrategy::Expand) {
0625
0626 BOOST_CHECK_EQUAL(volumes.size(), 3);
0627
0628
0629 auto newBounds1 =
0630 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0631 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0632 hlZ + hlZ / 2.0);
0633 Transform3 expectedTransform1 =
0634 base * Translation3{0_mm, 0_mm, -2 * hlZ - hlZ / 2.0};
0635 BOOST_CHECK_EQUAL(vol1->transform().matrix(),
0636 expectedTransform1.matrix());
0637
0638
0639 auto newBounds2 =
0640 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0641 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0642 hlZ);
0643 BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0644
0645
0646 auto newBounds3 =
0647 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
0648 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eHalfLengthZ),
0649 hlZ + hlZ / 2.0);
0650 Transform3 expectedTransform3 =
0651 base * Translation3{0_mm, 0_mm, 2 * hlZ + hlZ / 2.0};
0652 BOOST_CHECK_EQUAL(vol3->transform().matrix(),
0653 expectedTransform3.matrix());
0654 } else if (strategy == CylinderVolumeStack::ResizeStrategy::Gap) {
0655
0656 BOOST_CHECK_EQUAL(volumes.size(), 5);
0657
0658 for (const auto& [volume, origTransform] :
0659 zip(originalVolumes, originalTransforms)) {
0660 const auto* newBounds =
0661 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0662 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0663 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0664 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0665 hlZ);
0666
0667 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0668 }
0669
0670 auto gap1 = volumes.front();
0671 auto gap2 = volumes.back();
0672
0673 const auto* gapBounds1 =
0674 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
0675 const auto* gapBounds2 =
0676 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
0677
0678 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0679 hlZ / 2.0);
0680 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0681 hlZ / 2.0);
0682
0683 Transform3 gap1Transform =
0684 base * Translation3{0_mm, 0_mm, -3 * hlZ - hlZ / 2.0};
0685 Transform3 gap2Transform =
0686 base * Translation3{0_mm, 0_mm, 3 * hlZ + hlZ / 2.0};
0687
0688 CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0689 1e-10, 1e-14);
0690 CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0691 1e-10, 1e-14);
0692 }
0693 }
0694 }
0695
0696 BOOST_DATA_TEST_CASE(
0697 UpdateStackOneSided,
0698 (boost::unit_test::data::make(-1.0, 1.0) ^
0699 boost::unit_test::data::make(CylinderVolumeStack::ResizeStrategy::Gap,
0700 CylinderVolumeStack::ResizeStrategy::Expand)),
0701 f, strategy) {
0702 auto trf = Transform3::Identity();
0703
0704 auto trf1 = trf * Translation3{Vector3{0_mm, 0_mm, -500_mm}};
0705 auto vol1 = std::make_shared<Volume>(
0706 trf1, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
0707
0708 auto trf2 = trf * Translation3{Vector3{0_mm, 0_mm, 500_mm}};
0709 auto vol2 = std::make_shared<Volume>(
0710 trf2, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
0711
0712 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0713
0714 CylinderVolumeStack cylStack{volumes, AxisDirection::AxisZ,
0715 CylinderVolumeStack::AttachmentStrategy::Gap,
0716 strategy, *logger};
0717 const auto* originalBounds =
0718 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0719
0720
0721 auto newBounds = std::make_shared<CylinderVolumeBounds>(
0722 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0723 newBounds->set(CylinderVolumeBounds::eHalfLengthZ, 950_mm);
0724
0725 trf *= Translation3{Vector3{0_mm, 0_mm, f * 50_mm}};
0726
0727
0728
0729 auto checkUnchanged = [&]() {
0730 const auto* cylBounds =
0731 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0732 BOOST_REQUIRE(cylBounds != nullptr);
0733 BOOST_CHECK_EQUAL(*cylBounds, *originalBounds);
0734 };
0735
0736
0737 BOOST_CHECK_THROW(
0738 cylStack.update(newBounds, trf * Translation3{Vector3{0, 0, f * 20_mm}},
0739 *logger),
0740 std::invalid_argument);
0741 checkUnchanged();
0742
0743
0744 BOOST_CHECK_THROW(
0745 cylStack.update(newBounds, trf * Translation3{Vector3{10_mm, 0, 0}},
0746 *logger),
0747 std::invalid_argument);
0748 checkUnchanged();
0749
0750
0751 BOOST_CHECK_THROW(
0752 cylStack.update(newBounds, trf * Translation3{Vector3{0, 10_mm, 0}},
0753 *logger),
0754 std::invalid_argument);
0755 checkUnchanged();
0756
0757
0758 BOOST_CHECK_THROW(
0759 cylStack.update(newBounds, trf * AngleAxis3{10_degree, Vector3::UnitY()},
0760 *logger),
0761 std::invalid_argument);
0762 checkUnchanged();
0763
0764 cylStack.update(newBounds, trf, *logger);
0765
0766 BOOST_CHECK_EQUAL(cylStack.transform().matrix(), trf.matrix());
0767 const auto* cylBounds =
0768 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0769 BOOST_REQUIRE(cylBounds != nullptr);
0770 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), 950_mm);
0771
0772
0773 for (const auto* vol : volumes) {
0774 const auto* volBounds =
0775 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0776 BOOST_REQUIRE(volBounds != nullptr);
0777 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0778 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMaxR), 300_mm);
0779 }
0780
0781 if (strategy == CylinderVolumeStack::ResizeStrategy::Expand) {
0782
0783 BOOST_CHECK_EQUAL(volumes.size(), 3);
0784 const Volume* vol = nullptr;
0785 if (f < 0.0) {
0786
0787 vol = volumes.front();
0788 } else {
0789
0790 vol = volumes.back();
0791 }
0792
0793 const auto* volBounds =
0794 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0795 BOOST_REQUIRE(volBounds != nullptr);
0796 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0797 450_mm);
0798 BOOST_CHECK_EQUAL(vol->center()[eZ], f * 550_mm);
0799 } else if (strategy == CylinderVolumeStack::ResizeStrategy::Gap) {
0800
0801 BOOST_CHECK_EQUAL(volumes.size(), 4);
0802
0803 const Volume* gap = nullptr;
0804 if (f < 0.0) {
0805 gap = volumes.front();
0806 } else {
0807 gap = volumes.back();
0808 }
0809 const auto* gapBounds =
0810 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
0811 BOOST_REQUIRE(gapBounds != nullptr);
0812
0813 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0814 50_mm);
0815 BOOST_CHECK_EQUAL(gap->center()[eZ], f * 950_mm);
0816 }
0817 }
0818
0819 BOOST_AUTO_TEST_CASE(ResizeReproduction1) {
0820 Transform3 trf1 =
0821 Transform3::Identity() * Translation3{Vector3::UnitZ() * -2000};
0822 auto bounds1 = std::make_shared<CylinderVolumeBounds>(70, 100, 100.0);
0823 Volume vol1{trf1, bounds1};
0824
0825 std::vector<Volume*> volumes = {&vol1};
0826 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0827 CylinderVolumeStack::AttachmentStrategy::Gap,
0828 CylinderVolumeStack::ResizeStrategy::Gap, *logger);
0829
0830 Transform3 trf2 =
0831 Transform3::Identity() * Translation3{Vector3::UnitZ() * -1500};
0832 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 600), trf2,
0833 *logger);
0834
0835 std::cout << stack.volumeBounds() << std::endl;
0836 std::cout << stack.transform().matrix() << std::endl;
0837
0838 Transform3 trf3 =
0839 Transform3::Identity() * Translation3{Vector3::UnitZ() * -1600};
0840 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 700), trf3,
0841 *logger);
0842 }
0843
0844 BOOST_AUTO_TEST_CASE(ResizeReproduction2) {
0845
0846 Transform3 trf1 =
0847 Transform3::Identity() * Translation3{Vector3::UnitZ() * 263};
0848 auto bounds1 = std::make_shared<CylinderVolumeBounds>(30, 100, 4.075);
0849 Volume vol1{trf1, bounds1};
0850
0851 std::vector<Volume*> volumes = {&vol1};
0852 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0853 CylinderVolumeStack::AttachmentStrategy::Gap,
0854 CylinderVolumeStack::ResizeStrategy::Gap, *logger);
0855
0856 Transform3 trf2 =
0857 Transform3::Identity() * Translation3{Vector3::UnitZ() * 260.843};
0858 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 6.232), trf2,
0859 *logger);
0860
0861 std::cout << stack.volumeBounds() << std::endl;
0862 std::cout << stack.transform().matrix() << std::endl;
0863
0864 Transform3 trf3 =
0865 Transform3::Identity() * Translation3{Vector3::UnitZ() * 1627.31};
0866 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 1372.699),
0867 trf3, *logger);
0868 }
0869
0870
0871
0872
0873
0874
0875
0876
0877
0878
0879
0880
0881
0882
0883
0884
0885
0886
0887
0888
0889
0890
0891
0892
0893
0894
0895
0896
0897
0898 BOOST_AUTO_TEST_CASE(ResizeGapMultiple) {
0899 Transform3 trf = Transform3::Identity();
0900 auto bounds = std::make_shared<CylinderVolumeBounds>(70, 100, 100.0);
0901 Volume vol{trf, bounds};
0902
0903 BOOST_TEST_CONTEXT("Positive") {
0904 std::vector<Volume*> volumes = {&vol};
0905 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0906 CylinderVolumeStack::AttachmentStrategy::Gap,
0907 CylinderVolumeStack::ResizeStrategy::Gap,
0908 *logger);
0909
0910 BOOST_CHECK_EQUAL(volumes.size(), 1);
0911 BOOST_CHECK(stack.gaps().empty());
0912
0913 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 200),
0914 trf * Translation3{Vector3::UnitZ() * 100}, *logger);
0915 BOOST_CHECK_EQUAL(volumes.size(), 2);
0916 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0917
0918 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], 200.0);
0919 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0920 &stack.gaps().front()->volumeBounds());
0921 BOOST_REQUIRE_NE(cylBounds, nullptr);
0922 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0923 100.0);
0924
0925 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 300),
0926 trf * Translation3{Vector3::UnitZ() * 200}, *logger);
0927
0928 BOOST_CHECK_EQUAL(volumes.size(), 2);
0929
0930 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0931
0932 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], 300.0);
0933 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0934 &stack.gaps().front()->volumeBounds());
0935 BOOST_REQUIRE_NE(cylBounds, nullptr);
0936 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0937 200.0);
0938 }
0939
0940 BOOST_TEST_CONTEXT("Negative") {
0941 std::vector<Volume*> volumes = {&vol};
0942 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0943 CylinderVolumeStack::AttachmentStrategy::Gap,
0944 CylinderVolumeStack::ResizeStrategy::Gap,
0945 *logger);
0946
0947 BOOST_CHECK_EQUAL(volumes.size(), 1);
0948 BOOST_CHECK(stack.gaps().empty());
0949
0950 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 200),
0951 trf * Translation3{Vector3::UnitZ() * -100}, *logger);
0952 BOOST_CHECK_EQUAL(volumes.size(), 2);
0953 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0954
0955 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], -200.0);
0956 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0957 &stack.gaps().front()->volumeBounds());
0958 BOOST_REQUIRE_NE(cylBounds, nullptr);
0959 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0960 100.0);
0961
0962 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 300),
0963 trf * Translation3{Vector3::UnitZ() * -200}, *logger);
0964
0965 BOOST_CHECK_EQUAL(volumes.size(), 2);
0966
0967 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0968
0969 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], -300.0);
0970 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0971 &stack.gaps().front()->volumeBounds());
0972 BOOST_REQUIRE_NE(cylBounds, nullptr);
0973 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0974 200.0);
0975 }
0976 }
0977
0978 BOOST_AUTO_TEST_SUITE_END()
0979
0980 BOOST_AUTO_TEST_SUITE(RDirection)
0981
0982 BOOST_DATA_TEST_CASE(Baseline,
0983 (boost::unit_test::data::xrange(-135, 180, 45) *
0984 boost::unit_test::data::xrange(0, 2, 1) *
0985 boost::unit_test::data::make(-0.1, 0.0, 0.1) *
0986 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0987 Vector3{20_mm, 0_mm, 0_mm},
0988 Vector3{0_mm, 20_mm, 0_mm},
0989 Vector3{20_mm, 20_mm, 0_mm},
0990 Vector3{0_mm, 0_mm, 20_mm}) *
0991 boost::unit_test::data::make(strategies)),
0992 angle, rotate, f, offset, strategy) {
0993 double hlZ = 400_mm;
0994
0995 double fInner = 1.0 + f;
0996 double fOuter = 1.0 - f;
0997
0998
0999 auto bounds1 = std::make_shared<CylinderVolumeBounds>(fInner * 100_mm,
1000 fOuter * 300_mm, hlZ);
1001 auto bounds2 = std::make_shared<CylinderVolumeBounds>(fInner * 300_mm,
1002 fOuter * 600_mm, hlZ);
1003 auto bounds3 = std::make_shared<CylinderVolumeBounds>(fInner * 600_mm,
1004 fOuter * 900_mm, hlZ);
1005
1006 Transform3 base =
1007 AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
1008
1009
1010
1011 Transform3 transform1 = base;
1012 transform1.translate(Vector3{0_mm, 0_mm, 20_mm});
1013 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
1014
1015 Transform3 transform2 = base;
1016 transform2.translate(Vector3{0_mm, 0_mm, -30_mm});
1017 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
1018
1019 Transform3 transform3 = base;
1020 transform3.translate(Vector3{0_mm, 0_mm, 40_mm});
1021 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
1022
1023 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1024
1025 std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
1026
1027 std::vector<Volume*> origVolumes = volumes;
1028
1029 std::vector<CylinderVolumeBounds> originalBounds;
1030 std::transform(
1031 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
1032 [](const auto& vol) {
1033 return dynamic_cast<const CylinderVolumeBounds&>(vol->volumeBounds());
1034 });
1035
1036 if (f < 0.0) {
1037 BOOST_CHECK_THROW(
1038 CylinderVolumeStack(volumes, AxisDirection::AxisR, strategy,
1039 CylinderVolumeStack::ResizeStrategy::Gap, *logger),
1040 std::invalid_argument);
1041 return;
1042 }
1043
1044 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisR, strategy,
1045 CylinderVolumeStack::ResizeStrategy::Gap,
1046 *logger);
1047
1048 auto stackBounds =
1049 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1050 BOOST_REQUIRE(stackBounds != nullptr);
1051
1052 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR),
1053 fInner * 100_mm);
1054 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR),
1055 fOuter * 900_mm);
1056 double expectedHalfLengthZ = (40_mm + 30_mm + 2 * hlZ) / 2.0;
1057 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1058 expectedHalfLengthZ);
1059
1060
1061
1062
1063 Transform3 commonTransform = base * Translation3{0_mm, 0_mm, 5_mm};
1064
1065 CHECK_CLOSE_OR_SMALL(cylStack.transform().matrix(), commonTransform.matrix(),
1066 1e-10, 1e-14);
1067
1068 for (const auto& volume : volumes) {
1069 const auto* cylinderBounds =
1070 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1071 BOOST_REQUIRE(cylinderBounds != nullptr);
1072 BOOST_CHECK_EQUAL(cylinderBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1073 expectedHalfLengthZ);
1074 }
1075
1076 BOOST_CHECK_EQUAL(
1077 dynamic_cast<const CylinderVolumeBounds&>(vol1->volumeBounds())
1078 .get(CylinderVolumeBounds::eMinR),
1079 fInner * 100_mm);
1080
1081 BOOST_CHECK_EQUAL(
1082 dynamic_cast<const CylinderVolumeBounds&>(vol3->volumeBounds())
1083 .get(CylinderVolumeBounds::eMaxR),
1084 fOuter * 900_mm);
1085
1086
1087 for (std::size_t i = 0; i < volumes.size() - 1; ++i) {
1088 const auto& a = volumes.at(i);
1089 const auto& b = volumes.at(i + 1);
1090
1091 const auto* aBounds =
1092 dynamic_cast<const CylinderVolumeBounds*>(&a->volumeBounds());
1093 const auto* bBounds =
1094 dynamic_cast<const CylinderVolumeBounds*>(&b->volumeBounds());
1095
1096 double aMidR = (aBounds->get(CylinderVolumeBounds::eMinR) +
1097 aBounds->get(CylinderVolumeBounds::eMaxR)) /
1098 2.0;
1099
1100 double bMidR = (bBounds->get(CylinderVolumeBounds::eMinR) +
1101 bBounds->get(CylinderVolumeBounds::eMaxR)) /
1102 2.0;
1103
1104 BOOST_CHECK_LT(aMidR, bMidR);
1105 }
1106
1107 if (f == 0.0) {
1108
1109 BOOST_CHECK_EQUAL(volumes.size(), 3);
1110
1111
1112 for (const auto& [volume, origCylBounds] :
1113 zip(origVolumes, originalBounds)) {
1114 const auto* newBounds =
1115 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1116 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1117 origCylBounds.get(CylinderVolumeBounds::eMinR));
1118 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1119 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1120 }
1121 } else {
1122 const auto* newBounds1 =
1123 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1124 const auto* newBounds2 =
1125 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1126 const auto* newBounds3 =
1127 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1128 if (strategy == CylinderVolumeStack::AttachmentStrategy::Gap) {
1129
1130 BOOST_CHECK_EQUAL(volumes.size(), 5);
1131
1132
1133 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1134 fInner * 100_mm);
1135 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1136 fOuter * 300_mm);
1137 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1138 fInner * 300_mm);
1139 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1140 fOuter * 600_mm);
1141 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1142 fInner * 600_mm);
1143 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1144 fOuter * 900_mm);
1145
1146 auto gap1 = volumes.at(1);
1147 auto gap2 = volumes.at(3);
1148
1149 const auto* gapBounds1 =
1150 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
1151 const auto* gapBounds2 =
1152 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
1153
1154 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMinR),
1155 fOuter * 300_mm);
1156 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMaxR),
1157 fInner * 300_mm);
1158 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMinR),
1159 fOuter * 600_mm);
1160 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMaxR),
1161 fInner * 600_mm);
1162
1163 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::First) {
1164
1165 BOOST_CHECK_EQUAL(volumes.size(), 3);
1166
1167
1168 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1169 fInner * 100_mm);
1170 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1171 fInner * 300_mm);
1172
1173
1174 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1175 fInner * 300_mm);
1176 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1177 fInner * 600_mm);
1178
1179
1180 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1181 fInner * 600_mm);
1182 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1183 fOuter * 900_mm);
1184
1185 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Second) {
1186
1187 BOOST_CHECK_EQUAL(volumes.size(), 3);
1188
1189
1190 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1191 fInner * 100_mm);
1192 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1193 fOuter * 300_mm);
1194
1195
1196 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1197 fOuter * 300_mm);
1198 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1199 fOuter * 600_mm);
1200
1201
1202 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1203 fOuter * 600_mm);
1204 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1205 fOuter * 900_mm);
1206 } else if (strategy == CylinderVolumeStack::AttachmentStrategy::Midpoint) {
1207
1208 BOOST_CHECK_EQUAL(volumes.size(), 3);
1209
1210
1211 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1212 fInner * 100_mm);
1213 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1214 (fOuter * 300_mm + fInner * 300_mm) / 2.0);
1215
1216
1217 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1218 (fOuter * 300_mm + fInner * 300_mm) / 2.0);
1219 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1220 (fOuter * 600_mm + fInner * 600_mm) / 2.0);
1221
1222
1223 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1224 (fOuter * 600_mm + fInner * 600_mm) / 2.0);
1225 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1226 fOuter * 900_mm);
1227 }
1228 }
1229 }
1230
1231 BOOST_DATA_TEST_CASE(UpdateStack,
1232 (boost::unit_test::data::xrange(-135, 180, 45) *
1233 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
1234 Vector3{20_mm, 0_mm, 0_mm},
1235 Vector3{0_mm, 20_mm, 0_mm},
1236 Vector3{20_mm, 20_mm, 0_mm},
1237 Vector3{0_mm, 0_mm, 20_mm}) *
1238 boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
1239 boost::unit_test::data::make(resizeStrategies)),
1240 angle, offset, zshift, strategy) {
1241 double hlZ = 400_mm;
1242
1243
1244 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, hlZ);
1245 auto bounds2 = std::make_shared<CylinderVolumeBounds>(300_mm, 600_mm, hlZ);
1246 auto bounds3 = std::make_shared<CylinderVolumeBounds>(600_mm, 900_mm, hlZ);
1247
1248 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::UnitX()) *
1249 Translation3(offset + Vector3{0, 0, zshift});
1250
1251
1252 auto vol1 = std::make_shared<Volume>(base, bounds1);
1253 auto vol2 = std::make_shared<Volume>(base, bounds2);
1254 auto vol3 = std::make_shared<Volume>(base, bounds3);
1255
1256 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1257 std::vector<Volume*> originalVolumes = volumes;
1258
1259 std::vector<CylinderVolumeBounds> originalBounds;
1260
1261 std::transform(
1262 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
1263 [](const auto& vol) {
1264 return *dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
1265 });
1266
1267 const CylinderVolumeBounds* originalOuterBounds = nullptr;
1268
1269 std::unique_ptr<CylinderVolumeStack> cylStack;
1270
1271 auto resetCylStack = [&]() {
1272 volumes = originalVolumes;
1273
1274 for (const auto& [volume, origBounds] : zip(volumes, originalBounds)) {
1275 volume->assignVolumeBounds(
1276 std::make_shared<CylinderVolumeBounds>(origBounds));
1277 }
1278
1279 cylStack = std::make_unique<CylinderVolumeStack>(
1280 volumes, AxisDirection::AxisR,
1281 CylinderVolumeStack::AttachmentStrategy::Gap,
1282
1283 strategy, *logger);
1284
1285 originalOuterBounds =
1286 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1287 };
1288
1289 resetCylStack();
1290
1291 auto assertInitialVolumesUnchanged = [&]() {
1292 for (const auto& [volume, origCylBounds] :
1293 zip(originalVolumes, originalBounds)) {
1294 const auto* newBounds =
1295 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1296 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1297 origCylBounds.get(CylinderVolumeBounds::eMinR));
1298 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1299 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1300 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1301 origCylBounds.get(CylinderVolumeBounds::eHalfLengthZ));
1302 BOOST_CHECK_EQUAL(volume->transform().matrix(), base.matrix());
1303 }
1304 };
1305
1306 auto assertOriginalBounds = [&]() {
1307 const auto* cylBounds =
1308 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1309 BOOST_REQUIRE(cylBounds != nullptr);
1310 BOOST_CHECK_EQUAL(cylBounds, originalOuterBounds);
1311 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1312 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1313 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1314 };
1315
1316 assertOriginalBounds();
1317
1318 {
1319
1320 auto bounds = std::make_shared<CylinderVolumeBounds>(
1321 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1322 cylStack->update(bounds, std::nullopt, *logger);
1323 assertOriginalBounds();
1324 }
1325
1326 {
1327
1328 auto bounds = std::make_shared<CylinderVolumeBounds>(
1329 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1330 bounds->set(CylinderVolumeBounds::eMinR, 200_mm);
1331 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1332 std::invalid_argument);
1333 assertOriginalBounds();
1334 }
1335
1336 {
1337
1338 auto bounds = std::make_shared<CylinderVolumeBounds>(
1339 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1340 bounds->set(CylinderVolumeBounds::eMaxR, 500_mm);
1341 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1342 std::invalid_argument);
1343 assertOriginalBounds();
1344 }
1345
1346 {
1347
1348 auto bounds = std::make_shared<CylinderVolumeBounds>(
1349 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1350 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 0.5 * hlZ);
1351 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1352 std::invalid_argument);
1353 assertOriginalBounds();
1354 }
1355
1356 {
1357
1358 auto bounds = std::make_shared<CylinderVolumeBounds>(
1359 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1360 bounds->set(CylinderVolumeBounds::eMinR, 50_mm);
1361 cylStack->update(bounds, std::nullopt, *logger);
1362 const auto* cylBounds =
1363 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1364 BOOST_REQUIRE(cylBounds != nullptr);
1365 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
1366
1367 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1368 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1369
1370 if (strategy == CylinderVolumeStack::ResizeStrategy::Expand) {
1371
1372 BOOST_CHECK_EQUAL(volumes.size(), 3);
1373
1374
1375 const auto* newBounds1 =
1376 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1377 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR), 50_mm);
1378
1379 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1380
1381
1382 const auto* newBounds2 =
1383 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1384 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1385 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1386
1387 const auto* newBounds3 =
1388 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1389 BOOST_CHECK_EQUAL(*newBounds3, originalBounds[2]);
1390 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1391
1392 } else if (strategy == CylinderVolumeStack::ResizeStrategy::Gap) {
1393
1394 BOOST_CHECK_EQUAL(volumes.size(), 4);
1395
1396 auto gap = volumes.front();
1397 auto gapBounds =
1398 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
1399 BOOST_REQUIRE(gapBounds != nullptr);
1400 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
1401 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), 100_mm);
1402 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1403 hlZ);
1404 BOOST_CHECK_EQUAL(gap->transform().matrix(), base.matrix());
1405
1406
1407 assertInitialVolumesUnchanged();
1408 }
1409 }
1410
1411 resetCylStack();
1412
1413 {
1414
1415 auto bounds = std::make_shared<CylinderVolumeBounds>(
1416 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1417 bounds->set(CylinderVolumeBounds::eMaxR, 1000_mm);
1418 cylStack->update(bounds, std::nullopt, *logger);
1419 const auto* cylBounds =
1420 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1421 BOOST_REQUIRE(cylBounds != nullptr);
1422 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1423
1424 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1425 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1426
1427 if (strategy == CylinderVolumeStack::ResizeStrategy::Expand) {
1428
1429 BOOST_CHECK_EQUAL(volumes.size(), 3);
1430
1431
1432 const auto* newBounds3 =
1433 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1434 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1435
1436 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1437
1438
1439 const auto* newBounds1 =
1440 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1441 BOOST_CHECK_EQUAL(*newBounds1, originalBounds[0]);
1442 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1443
1444 const auto* newBounds2 =
1445 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1446 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1447 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1448
1449 } else if (strategy == CylinderVolumeStack::ResizeStrategy::Gap) {
1450
1451 BOOST_CHECK_EQUAL(volumes.size(), 4);
1452
1453 auto gap = volumes.back();
1454 auto gapBounds =
1455 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
1456 BOOST_REQUIRE(gapBounds != nullptr);
1457 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), 900_mm);
1458 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1459 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1460 hlZ);
1461 BOOST_CHECK_EQUAL(gap->transform().matrix(), base.matrix());
1462
1463
1464 assertInitialVolumesUnchanged();
1465 }
1466 }
1467
1468 resetCylStack();
1469
1470 {
1471
1472 auto bounds = std::make_shared<CylinderVolumeBounds>(
1473 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1474 bounds->set({
1475 {CylinderVolumeBounds::eMinR, 0_mm},
1476 {CylinderVolumeBounds::eMaxR, 1100_mm},
1477 });
1478
1479 cylStack->update(bounds, std::nullopt, *logger);
1480 const auto* cylBounds =
1481 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1482 BOOST_REQUIRE(cylBounds != nullptr);
1483 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1484
1485 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 0_mm);
1486 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1487
1488 if (strategy == CylinderVolumeStack::ResizeStrategy::Expand) {
1489
1490 BOOST_CHECK_EQUAL(volumes.size(), 3);
1491
1492
1493 const auto* newBounds1 =
1494 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1495 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR), 0_mm);
1496
1497 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1498
1499
1500 const auto* newBounds2 =
1501 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1502 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1503 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1504
1505
1506 const auto* newBounds3 =
1507 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1508 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1509
1510 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1511
1512 } else if (strategy == CylinderVolumeStack::ResizeStrategy::Gap) {
1513
1514 BOOST_CHECK_EQUAL(volumes.size(), 5);
1515
1516 auto gap1 = volumes.front();
1517 auto gapBounds1 =
1518 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
1519 BOOST_REQUIRE(gapBounds1 != nullptr);
1520 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMinR), 0_mm);
1521 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMaxR), 100_mm);
1522 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
1523 hlZ);
1524 BOOST_CHECK_EQUAL(gap1->transform().matrix(), base.matrix());
1525
1526 auto gap2 = volumes.back();
1527 auto gapBounds2 =
1528 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
1529 BOOST_REQUIRE(gapBounds2 != nullptr);
1530 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMinR), 900_mm);
1531 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1532 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
1533 hlZ);
1534
1535
1536 assertInitialVolumesUnchanged();
1537 }
1538 }
1539
1540 resetCylStack();
1541
1542 {
1543
1544 auto bounds = std::make_shared<CylinderVolumeBounds>(
1545 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1546 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 2 * hlZ);
1547 cylStack->update(bounds, std::nullopt, *logger);
1548 const auto* cylBounds =
1549 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1550 BOOST_REQUIRE(cylBounds != nullptr);
1551 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1552 2 * hlZ);
1553
1554
1555 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1556 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1557
1558
1559 BOOST_CHECK_EQUAL(volumes.size(), 3);
1560
1561 for (const auto& [volume, origCylBounds] :
1562 zip(originalVolumes, originalBounds)) {
1563 const auto* newBounds =
1564 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1565
1566 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1567 origCylBounds.get(CylinderVolumeBounds::eMinR));
1568 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1569 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1570
1571
1572 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1573 2 * hlZ);
1574
1575
1576 BOOST_CHECK_EQUAL(volume->transform().matrix(), base.matrix());
1577 }
1578 }
1579 }
1580
1581 BOOST_DATA_TEST_CASE(
1582 UpdateStackOneSided,
1583 (boost::unit_test::data::make(-1.0, 1.0) ^
1584 boost::unit_test::data::make(CylinderVolumeStack::ResizeStrategy::Gap,
1585 CylinderVolumeStack::ResizeStrategy::Expand)),
1586 f, strategy) {
1587
1588
1589 auto trf = Transform3::Identity();
1590
1591 auto vol1 = std::make_shared<Volume>(
1592 trf, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
1593
1594 auto vol2 = std::make_shared<Volume>(
1595 trf, std::make_shared<CylinderVolumeBounds>(400_mm, 600_mm, 400_mm));
1596
1597 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
1598
1599 CylinderVolumeStack cylStack{volumes, AxisDirection::AxisR,
1600 CylinderVolumeStack::AttachmentStrategy::Gap,
1601 strategy, *logger};
1602 const auto* originalBounds =
1603 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1604
1605
1606 auto newBounds = std::make_shared<CylinderVolumeBounds>(
1607 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
1608 newBounds->set(CylinderVolumeBounds::eHalfLengthZ, 450_mm);
1609
1610 trf *= Translation3{Vector3{0_mm, 0_mm, f * 50_mm}};
1611
1612
1613 auto checkUnchanged = [&]() {
1614 const auto* cylBounds =
1615 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1616 BOOST_REQUIRE(cylBounds != nullptr);
1617 BOOST_CHECK_EQUAL(*cylBounds, *originalBounds);
1618 };
1619
1620
1621 BOOST_CHECK_THROW(
1622 cylStack.update(newBounds, trf * Translation3{Vector3{0, 0, f * 20_mm}},
1623 *logger),
1624 std::invalid_argument);
1625 checkUnchanged();
1626
1627
1628 BOOST_CHECK_THROW(
1629 cylStack.update(newBounds, trf * Translation3{Vector3{10_mm, 0, 0}},
1630 *logger),
1631 std::invalid_argument);
1632 checkUnchanged();
1633
1634
1635 BOOST_CHECK_THROW(
1636 cylStack.update(newBounds, trf * Translation3{Vector3{0, 10_mm, 0}},
1637 *logger),
1638 std::invalid_argument);
1639 checkUnchanged();
1640
1641
1642 BOOST_CHECK_THROW(
1643 cylStack.update(newBounds, trf * AngleAxis3{10_degree, Vector3::UnitY()},
1644 *logger),
1645 std::invalid_argument);
1646 checkUnchanged();
1647
1648 cylStack.update(newBounds, trf, *logger);
1649
1650 BOOST_CHECK_EQUAL(cylStack.transform().matrix(), trf.matrix());
1651 const auto* cylBounds =
1652 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1653 BOOST_REQUIRE(cylBounds != nullptr);
1654 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1655 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
1656
1657
1658 for (const auto* vol : volumes) {
1659 const auto* volBounds =
1660 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
1661 BOOST_REQUIRE(volBounds != nullptr);
1662 BOOST_CHECK_EQUAL(vol->transform().matrix(), trf.matrix());
1663 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1664 450_mm);
1665 }
1666 }
1667
1668 BOOST_AUTO_TEST_CASE(ResizeGapMultiple) {
1669 Transform3 trf = Transform3::Identity();
1670 auto bounds = std::make_shared<CylinderVolumeBounds>(100, 200, 100);
1671 Volume vol{trf, bounds};
1672
1673 BOOST_TEST_CONTEXT("Outer") {
1674 std::vector<Volume*> volumes = {&vol};
1675 CylinderVolumeStack stack(volumes, AxisDirection::AxisR,
1676 CylinderVolumeStack::AttachmentStrategy::Gap,
1677 CylinderVolumeStack::ResizeStrategy::Gap,
1678 *logger);
1679
1680 BOOST_CHECK_EQUAL(volumes.size(), 1);
1681 BOOST_CHECK(stack.gaps().empty());
1682
1683 stack.update(std::make_shared<CylinderVolumeBounds>(100, 250, 100), trf,
1684 *logger);
1685 BOOST_CHECK_EQUAL(volumes.size(), 2);
1686 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1687
1688 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1689 &stack.gaps().front()->volumeBounds());
1690 BOOST_REQUIRE_NE(cylBounds, nullptr);
1691 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 200);
1692 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 250);
1693
1694 stack.update(std::make_shared<CylinderVolumeBounds>(100, 300, 100), trf,
1695 *logger);
1696
1697 BOOST_CHECK_EQUAL(volumes.size(), 2);
1698
1699 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1700
1701 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1702 &stack.gaps().front()->volumeBounds());
1703 BOOST_REQUIRE_NE(cylBounds, nullptr);
1704 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 200);
1705 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 300);
1706 }
1707
1708 BOOST_TEST_CONTEXT("Inner") {
1709 std::vector<Volume*> volumes = {&vol};
1710 CylinderVolumeStack stack(volumes, AxisDirection::AxisR,
1711 CylinderVolumeStack::AttachmentStrategy::Gap,
1712 CylinderVolumeStack::ResizeStrategy::Gap,
1713 *logger);
1714
1715 BOOST_CHECK_EQUAL(volumes.size(), 1);
1716 BOOST_CHECK(stack.gaps().empty());
1717
1718 stack.update(std::make_shared<CylinderVolumeBounds>(50, 200, 100), trf,
1719 *logger);
1720 BOOST_CHECK_EQUAL(volumes.size(), 2);
1721 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1722
1723 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1724 &stack.gaps().front()->volumeBounds());
1725 BOOST_REQUIRE_NE(cylBounds, nullptr);
1726 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50);
1727 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 100);
1728
1729 stack.update(std::make_shared<CylinderVolumeBounds>(0, 200, 100), trf,
1730 *logger);
1731
1732 BOOST_CHECK_EQUAL(volumes.size(), 2);
1733
1734 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1735
1736 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1737 &stack.gaps().front()->volumeBounds());
1738 BOOST_REQUIRE_NE(cylBounds, nullptr);
1739 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 0);
1740 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 100);
1741 }
1742 }
1743
1744 BOOST_AUTO_TEST_SUITE_END()
1745
1746 BOOST_AUTO_TEST_SUITE(Common)
1747
1748 BOOST_DATA_TEST_CASE(JoinCylinderVolumesInvalidDirection,
1749 boost::unit_test::data::make(strategies), strategy) {
1750 std::vector<Volume*> volumes;
1751 auto vol1 = std::make_shared<Volume>(
1752 Transform3::Identity(),
1753 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1754 volumes.push_back(vol1.get());
1755
1756
1757 BOOST_CHECK_THROW(
1758 CylinderVolumeStack(volumes, AxisDirection::AxisY, strategy),
1759 std::invalid_argument);
1760
1761 auto vol2 = std::make_shared<Volume>(
1762 Transform3::Identity(),
1763 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1764 volumes.push_back(vol2.get());
1765
1766 BOOST_CHECK_THROW(
1767 CylinderVolumeStack(volumes, AxisDirection::AxisY, strategy),
1768 std::invalid_argument);
1769 }
1770
1771 BOOST_DATA_TEST_CASE(JoinCylinderVolumesInvalidInput,
1772 (boost::unit_test::data::make(strategies) *
1773 boost::unit_test::data::make(Acts::AxisDirection::AxisZ,
1774 Acts::AxisDirection::AxisR)),
1775 strategy, direction) {
1776 BOOST_TEST_CONTEXT("Empty Volume") {
1777 std::vector<Volume*> volumes;
1778 BOOST_CHECK_THROW(CylinderVolumeStack(volumes, direction, strategy),
1779 std::invalid_argument);
1780 }
1781
1782 BOOST_TEST_CONTEXT("Volumes rotated relative to each other") {
1783
1784 for (const Vector3 axis : {Vector3::UnitX(), Vector3::UnitY()}) {
1785 std::vector<Volume*> volumes;
1786 auto vol1 = std::make_shared<Volume>(
1787 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1788 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1789 volumes.push_back(vol1.get());
1790
1791 BOOST_TEST_MESSAGE("Axis: " << axis);
1792 auto vol2 = std::make_shared<Volume>(
1793 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}} *
1794 AngleAxis3(1_degree, axis)},
1795 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1796 volumes.push_back(vol2.get());
1797
1798 BOOST_CHECK_THROW(CylinderVolumeStack(
1799 volumes, direction, strategy,
1800 CylinderVolumeStack::ResizeStrategy::Gap, *logger),
1801 std::invalid_argument);
1802 }
1803 }
1804
1805 BOOST_TEST_CONTEXT("Volumes shifted in the xy plane relative to each other") {
1806 for (const Vector3& shift :
1807 {Vector3{5_mm, 0, 0}, Vector3{0, -5_mm, 0}, Vector3{2_mm, -2_mm, 0}}) {
1808 std::vector<Volume*> volumes;
1809 auto vol1 = std::make_shared<Volume>(
1810 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1811 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1812 volumes.push_back(vol1.get());
1813
1814 auto vol2 = std::make_shared<Volume>(
1815 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm} + shift}},
1816 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1817 volumes.push_back(vol2.get());
1818
1819 BOOST_CHECK_THROW(CylinderVolumeStack(
1820 volumes, direction, strategy,
1821 CylinderVolumeStack::ResizeStrategy::Gap, *logger),
1822 std::invalid_argument);
1823 }
1824 }
1825
1826 BOOST_TEST_CONTEXT("Volume has phi values or bevel values") {
1827 std::vector<std::shared_ptr<CylinderVolumeBounds>> invalidVolumeBounds = {
1828 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1829 0.2 * std::numbers::pi),
1830
1831 std::make_shared<CylinderVolumeBounds>(
1832 100_mm, 400_mm, 400_mm, std::numbers::pi, 0.3 * std::numbers::pi),
1833
1834 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1835 std::numbers::pi, 0.,
1836 0.3 * std::numbers::pi),
1837 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1838 std::numbers::pi, 0., 0.,
1839 0.3 * std::numbers::pi),
1840 };
1841
1842 for (const auto& invalid : invalidVolumeBounds) {
1843 std::stringstream ss;
1844 ss << "Invalid bounds: " << *invalid;
1845 BOOST_TEST_CONTEXT(ss.str()) {
1846 std::vector<Volume*> volumes;
1847 auto vol1 = std::make_shared<Volume>(
1848 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1849 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1850 volumes.push_back(vol1.get());
1851
1852 {
1853
1854 CylinderVolumeStack cylStack(volumes, direction, strategy,
1855 CylinderVolumeStack::ResizeStrategy::Gap,
1856 *logger);
1857 BOOST_CHECK_THROW(cylStack.update(invalid, std::nullopt, *logger),
1858 std::invalid_argument);
1859 }
1860
1861 {
1862 std::shared_ptr<Volume> vol;
1863 if (direction == AxisDirection::AxisZ) {
1864 vol = std::make_shared<Volume>(
1865 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}}}, invalid);
1866 } else {
1867 invalid->set({
1868 {CylinderVolumeBounds::eMinR, 400_mm},
1869 {CylinderVolumeBounds::eMaxR, 600_mm},
1870 });
1871 vol = std::make_shared<Volume>(
1872 Transform3{Translation3{Vector3{0_mm, 0_mm, 0_mm}}}, invalid);
1873 }
1874 volumes.push_back(vol.get());
1875 BOOST_CHECK_THROW(
1876 CylinderVolumeStack(volumes, direction, strategy,
1877 CylinderVolumeStack::ResizeStrategy::Gap,
1878 *logger),
1879 std::invalid_argument);
1880 }
1881 }
1882 }
1883 }
1884 }
1885
1886 BOOST_DATA_TEST_CASE(JoinCylinderVolumeSingle,
1887 (boost::unit_test::data::make(Acts::AxisDirection::AxisZ,
1888 Acts::AxisDirection::AxisR) *
1889 boost::unit_test::data::make(strategies)),
1890 direction, strategy) {
1891 auto vol = std::make_shared<Volume>(
1892 Transform3::Identity() * Translation3{14_mm, 24_mm, 0_mm} *
1893 AngleAxis3(73_degree, Vector3::UnitX()),
1894 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1895
1896 std::vector<Volume*> volumes{vol.get()};
1897
1898 CylinderVolumeStack cylStack(volumes, direction, strategy,
1899 CylinderVolumeStack::ResizeStrategy::Gap,
1900 *logger);
1901
1902
1903
1904 BOOST_CHECK_EQUAL(volumes.size(), 1);
1905 BOOST_CHECK_EQUAL(volumes.at(0), vol.get());
1906 BOOST_CHECK_EQUAL(vol->transform().matrix(), cylStack.transform().matrix());
1907 BOOST_CHECK_EQUAL(vol->volumeBounds(), cylStack.volumeBounds());
1908 }
1909
1910 BOOST_AUTO_TEST_SUITE_END()
1911 BOOST_AUTO_TEST_SUITE_END()
1912 BOOST_AUTO_TEST_SUITE_END()
1913
1914 }