File indexing completed on 2025-07-02 07:52:11
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/Geometry/VolumeAttachmentStrategy.hpp"
0023 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0024 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0025 #include "Acts/Utilities/BinningType.hpp"
0026 #include "Acts/Utilities/Logger.hpp"
0027 #include "Acts/Utilities/Zip.hpp"
0028
0029 #include <numbers>
0030
0031 using namespace Acts::UnitLiterals;
0032
0033 namespace Acts::Test {
0034
0035 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0036
0037 struct Fixture {
0038 Logging::Level m_level;
0039 Fixture() {
0040 m_level = Acts::Logging::getFailureThreshold();
0041 Acts::Logging::setFailureThreshold(Acts::Logging::FATAL);
0042 }
0043
0044 ~Fixture() { Acts::Logging::setFailureThreshold(m_level); }
0045 };
0046
0047 BOOST_FIXTURE_TEST_SUITE(Geometry, Fixture)
0048
0049 static const std::vector<VolumeAttachmentStrategy> strategies = {
0050 VolumeAttachmentStrategy::Gap,
0051 VolumeAttachmentStrategy::First,
0052 VolumeAttachmentStrategy::Second,
0053 VolumeAttachmentStrategy::Midpoint,
0054 };
0055
0056 static const std::vector<VolumeResizeStrategy> resizeStrategies = {
0057 VolumeResizeStrategy::Expand,
0058 VolumeResizeStrategy::Gap,
0059 };
0060
0061 BOOST_AUTO_TEST_SUITE(CylinderVolumeStackTest)
0062 BOOST_AUTO_TEST_SUITE(ZDirection)
0063
0064 BOOST_DATA_TEST_CASE(Baseline,
0065 (boost::unit_test::data::xrange(-135, 180, 45) *
0066 boost::unit_test::data::xrange(0, 2, 1) *
0067 boost::unit_test::data::make(0.8, 1.0, 1.2) *
0068 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0069 Vector3{20_mm, 0_mm, 0_mm},
0070 Vector3{0_mm, 20_mm, 0_mm},
0071 Vector3{20_mm, 20_mm, 0_mm},
0072 Vector3{0_mm, 0_mm, 20_mm}) *
0073 boost::unit_test::data::make(strategies)),
0074 angle, rotate, shift, offset, strategy) {
0075 double hlZ = 400_mm;
0076
0077
0078 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, hlZ);
0079 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 600_mm, hlZ);
0080 auto bounds3 = std::make_shared<CylinderVolumeBounds>(300_mm, 500_mm, hlZ);
0081
0082 Transform3 base =
0083 AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0084
0085 Transform3 transform1 = base;
0086 transform1.translate(Vector3{0_mm, 0_mm, -2 * hlZ * shift});
0087 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0088
0089 Transform3 transform2 = base;
0090 transform2.translate(Vector3{0_mm, 0_mm, 0_mm});
0091 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0092
0093 Transform3 transform3 = base;
0094 transform3.translate(Vector3{0_mm, 0_mm, 2 * hlZ * shift});
0095 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0096
0097 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0098
0099 std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
0100
0101 auto origVolumes = volumes;
0102
0103 std::vector<CylinderVolumeBounds> originalBounds;
0104 std::transform(
0105 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
0106 [](const auto& vol) {
0107 return *dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0108 });
0109
0110 if (shift < 1.0) {
0111 BOOST_CHECK_THROW(
0112 CylinderVolumeStack(volumes, AxisDirection::AxisZ, strategy,
0113 VolumeResizeStrategy::Gap, *logger),
0114 std::invalid_argument);
0115 return;
0116 }
0117 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisZ, strategy,
0118 VolumeResizeStrategy::Gap, *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 == VolumeAttachmentStrategy::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 == VolumeAttachmentStrategy::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 == VolumeAttachmentStrategy::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 == VolumeAttachmentStrategy::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 VolumeAttachmentStrategy::Gap,
0346 VolumeResizeStrategy::Gap, *logger);
0347 BOOST_CHECK_EQUAL(volumes.size(), 5);
0348
0349 auto stackBounds =
0350 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0351 BOOST_REQUIRE(stackBounds != nullptr);
0352
0353 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0354 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0355 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0356 (std::abs(pZ1 - hlZ1) + pZ3 + hlZ3) / 2.0);
0357
0358 double midZ = (pZ1 - hlZ1 + pZ3 + hlZ3) / 2.0;
0359 Transform3 expectedTransform{Translation3{0_mm, 0_mm, midZ}};
0360 CHECK_CLOSE_OR_SMALL(cylStack.transform().matrix(),
0361 expectedTransform.matrix(), 1e-10, 1e-14);
0362 }
0363
0364 BOOST_DATA_TEST_CASE(RotationInZ, boost::unit_test::data::make(strategies),
0365 strategy) {
0366 double hlZ = 400_mm;
0367 double gap = 100_mm;
0368 double shift = 300_mm;
0369
0370 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, hlZ);
0371 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 300_mm, hlZ);
0372
0373 auto vol1 = std::make_shared<Volume>(
0374 Transform3::Identity() *
0375 Translation3{0_mm, 0_mm, -hlZ - gap / 2.0 + shift},
0376 bounds1);
0377
0378 auto vol2 = std::make_shared<Volume>(
0379 Transform3::Identity() *
0380 Translation3{0_mm, 0_mm, hlZ + gap / 2.0 + shift} *
0381 AngleAxis3{30_degree, Vector3::UnitZ()},
0382 bounds2);
0383
0384 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0385
0386 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisZ, strategy,
0387 VolumeResizeStrategy::Gap, *logger);
0388
0389 auto stackBounds =
0390 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0391 BOOST_REQUIRE(stackBounds != nullptr);
0392 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0393 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR), 400_mm);
0394 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0395 2 * hlZ + gap / 2.0);
0396
0397 auto newBounds1 =
0398 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0399 auto newBounds2 =
0400 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0401
0402 for (const auto& bounds : {newBounds1, newBounds2}) {
0403 BOOST_CHECK_EQUAL(bounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0404 BOOST_CHECK_EQUAL(bounds->get(CylinderVolumeBounds::eMaxR), 400_mm);
0405 }
0406
0407 if (strategy == VolumeAttachmentStrategy::Gap) {
0408
0409 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 2.0 + shift);
0410 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 2.0 + shift);
0411 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0412 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0413 } else if (strategy == VolumeAttachmentStrategy::First) {
0414
0415 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ + shift);
0416 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0417 hlZ + gap / 2.0);
0418
0419 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 2.0 + shift);
0420 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0421 } else if (strategy == VolumeAttachmentStrategy::Second) {
0422
0423 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 2.0 + shift);
0424 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
0425
0426 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + shift);
0427 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0428 hlZ + gap / 2.0);
0429 } else if (strategy == VolumeAttachmentStrategy::Midpoint) {
0430
0431 BOOST_CHECK_EQUAL(vol1->center()[eZ], -hlZ - gap / 4.0 + shift);
0432 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0433 hlZ + gap / 4.0);
0434
0435
0436 BOOST_CHECK_EQUAL(vol2->center()[eZ], hlZ + gap / 4.0 + shift);
0437 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0438 hlZ + gap / 4.0);
0439 }
0440 }
0441
0442 BOOST_DATA_TEST_CASE(UpdateStack,
0443 (boost::unit_test::data::xrange(-135, 180, 45) *
0444 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0445 Vector3{20_mm, 0_mm, 0_mm},
0446 Vector3{0_mm, 20_mm, 0_mm},
0447 Vector3{20_mm, 20_mm, 0_mm},
0448 Vector3{0_mm, 0_mm, 20_mm}) *
0449 boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
0450 boost::unit_test::data::make(resizeStrategies)),
0451 angle, offset, zshift, strategy) {
0452 double hlZ = 400_mm;
0453
0454
0455 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0456 auto bounds2 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0457 auto bounds3 = std::make_shared<CylinderVolumeBounds>(100_mm, 600_mm, hlZ);
0458
0459 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::UnitX()) *
0460 Translation3(offset + Vector3{0_mm, 0_mm, zshift});
0461
0462 Transform3 transform1 = base;
0463 transform1.translate(Vector3{0_mm, 0_mm, -2 * hlZ});
0464 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0465
0466 Transform3 transform2 = base;
0467 transform2.translate(Vector3{0_mm, 0_mm, 0_mm});
0468 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0469
0470 Transform3 transform3 = base;
0471 transform3.translate(Vector3{0_mm, 0_mm, 2 * hlZ});
0472 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0473
0474 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0475 std::vector<Volume*> originalVolumes = volumes;
0476
0477 std::vector<Transform3> originalTransforms = {transform1, transform2,
0478 transform3};
0479
0480 CylinderVolumeStack cylStack(
0481 volumes, AxisDirection::AxisZ,
0482 VolumeAttachmentStrategy::Gap,
0483
0484 strategy, *logger);
0485
0486 const auto* originalBounds =
0487 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0488
0489 auto assertOriginalBounds = [&]() {
0490 const auto* cylBounds =
0491 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0492 BOOST_REQUIRE(cylBounds != nullptr);
0493 BOOST_CHECK_EQUAL(cylBounds, originalBounds);
0494 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0495 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0496 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0497 3 * hlZ);
0498 };
0499
0500 assertOriginalBounds();
0501
0502 {
0503
0504 auto bounds = std::make_shared<CylinderVolumeBounds>(
0505 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0506 cylStack.update(bounds, std::nullopt, *logger);
0507 assertOriginalBounds();
0508 }
0509
0510 {
0511
0512 auto bounds = std::make_shared<CylinderVolumeBounds>(
0513 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0514 bounds->set(CylinderVolumeBounds::eMinR, 200_mm);
0515 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0516 std::invalid_argument);
0517 assertOriginalBounds();
0518 }
0519
0520 {
0521
0522 auto bounds = std::make_shared<CylinderVolumeBounds>(
0523 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0524 bounds->set(CylinderVolumeBounds::eMaxR, 500_mm);
0525 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0526 std::invalid_argument);
0527 assertOriginalBounds();
0528 }
0529
0530 {
0531
0532 auto bounds = std::make_shared<CylinderVolumeBounds>(
0533 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0534 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 2 * hlZ);
0535 BOOST_CHECK_THROW(cylStack.update(bounds, std::nullopt, *logger),
0536 std::invalid_argument);
0537 assertOriginalBounds();
0538 }
0539
0540 {
0541
0542 auto bounds = std::make_shared<CylinderVolumeBounds>(
0543 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0544 bounds->set(CylinderVolumeBounds::eMinR, 50_mm);
0545 cylStack.update(bounds, std::nullopt, *logger);
0546 const auto* cylBounds =
0547 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0548 BOOST_REQUIRE(cylBounds != nullptr);
0549 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0550
0551 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0552 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0553 3 * hlZ);
0554
0555
0556 BOOST_CHECK_EQUAL(volumes.size(), 3);
0557
0558
0559 for (const auto& [volume, origTransform] :
0560 zip(volumes, originalTransforms)) {
0561 const auto* newBounds =
0562 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0563 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0564 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
0565 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0566 hlZ);
0567
0568
0569 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0570 }
0571 }
0572
0573 {
0574
0575 auto bounds = std::make_shared<CylinderVolumeBounds>(
0576 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0577 bounds->set(CylinderVolumeBounds::eMaxR, 700_mm);
0578 cylStack.update(bounds, std::nullopt, *logger);
0579 const auto* cylBounds =
0580 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0581 BOOST_REQUIRE(cylBounds != nullptr);
0582 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0583
0584 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0585 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0586 3 * hlZ);
0587
0588
0589 BOOST_CHECK_EQUAL(volumes.size(), 3);
0590
0591
0592 for (const auto& [volume, origTransform] :
0593 zip(volumes, originalTransforms)) {
0594 const auto* newBounds =
0595 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0596 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0597 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0598 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0599 hlZ);
0600
0601
0602 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0603 }
0604 }
0605
0606 {
0607
0608 auto bounds = std::make_shared<CylinderVolumeBounds>(
0609 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0610 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 4 * hlZ);
0611 cylStack.update(bounds, std::nullopt, *logger);
0612 const auto* cylBounds =
0613 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0614 BOOST_REQUIRE(cylBounds != nullptr);
0615 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0616 4 * hlZ);
0617
0618
0619 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0620 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0621
0622 if (strategy == VolumeResizeStrategy::Expand) {
0623
0624 BOOST_CHECK_EQUAL(volumes.size(), 3);
0625
0626
0627 auto newBounds1 =
0628 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
0629 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0630 hlZ + hlZ / 2.0);
0631 Transform3 expectedTransform1 =
0632 base * Translation3{0_mm, 0_mm, -2 * hlZ - hlZ / 2.0};
0633 BOOST_CHECK_EQUAL(vol1->transform().matrix(),
0634 expectedTransform1.matrix());
0635
0636
0637 auto newBounds2 =
0638 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
0639 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0640 hlZ);
0641 BOOST_CHECK_EQUAL(vol2->transform().matrix(), transform2.matrix());
0642
0643
0644 auto newBounds3 =
0645 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
0646 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eHalfLengthZ),
0647 hlZ + hlZ / 2.0);
0648 Transform3 expectedTransform3 =
0649 base * Translation3{0_mm, 0_mm, 2 * hlZ + hlZ / 2.0};
0650 BOOST_CHECK_EQUAL(vol3->transform().matrix(),
0651 expectedTransform3.matrix());
0652 } else if (strategy == VolumeResizeStrategy::Gap) {
0653
0654 BOOST_CHECK_EQUAL(volumes.size(), 5);
0655
0656 for (const auto& [volume, origTransform] :
0657 zip(originalVolumes, originalTransforms)) {
0658 const auto* newBounds =
0659 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0660 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
0661 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR), 700_mm);
0662 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0663 hlZ);
0664
0665 BOOST_CHECK_EQUAL(volume->transform().matrix(), origTransform.matrix());
0666 }
0667
0668 auto gap1 = volumes.front();
0669 auto gap2 = volumes.back();
0670
0671 const auto* gapBounds1 =
0672 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
0673 const auto* gapBounds2 =
0674 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
0675
0676 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
0677 hlZ / 2.0);
0678 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
0679 hlZ / 2.0);
0680
0681 Transform3 gap1Transform =
0682 base * Translation3{0_mm, 0_mm, -3 * hlZ - hlZ / 2.0};
0683 Transform3 gap2Transform =
0684 base * Translation3{0_mm, 0_mm, 3 * hlZ + hlZ / 2.0};
0685
0686 CHECK_CLOSE_OR_SMALL(gap1->transform().matrix(), gap1Transform.matrix(),
0687 1e-10, 1e-14);
0688 CHECK_CLOSE_OR_SMALL(gap2->transform().matrix(), gap2Transform.matrix(),
0689 1e-10, 1e-14);
0690 }
0691 }
0692 }
0693
0694 BOOST_DATA_TEST_CASE(
0695 UpdateStackOneSided,
0696 (boost::unit_test::data::make(-1.0, 1.0) ^
0697 boost::unit_test::data::make(VolumeResizeStrategy::Gap,
0698 VolumeResizeStrategy::Expand)),
0699 f, strategy) {
0700 auto trf = Transform3::Identity();
0701
0702 auto trf1 = trf * Translation3{Vector3{0_mm, 0_mm, -500_mm}};
0703 auto vol1 = std::make_shared<Volume>(
0704 trf1, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
0705
0706 auto trf2 = trf * Translation3{Vector3{0_mm, 0_mm, 500_mm}};
0707 auto vol2 = std::make_shared<Volume>(
0708 trf2, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
0709
0710 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0711
0712 CylinderVolumeStack cylStack{volumes, AxisDirection::AxisZ,
0713 VolumeAttachmentStrategy::Gap, strategy,
0714 *logger};
0715 const auto* originalBounds =
0716 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0717
0718
0719 auto newBounds = std::make_shared<CylinderVolumeBounds>(
0720 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
0721 newBounds->set(CylinderVolumeBounds::eHalfLengthZ, 950_mm);
0722
0723 trf *= Translation3{Vector3{0_mm, 0_mm, f * 50_mm}};
0724
0725
0726
0727 auto checkUnchanged = [&]() {
0728 const auto* cylBounds =
0729 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0730 BOOST_REQUIRE(cylBounds != nullptr);
0731 BOOST_CHECK_EQUAL(*cylBounds, *originalBounds);
0732 };
0733
0734
0735 BOOST_CHECK_THROW(
0736 cylStack.update(newBounds, trf * Translation3{Vector3{0, 0, f * 20_mm}},
0737 *logger),
0738 std::invalid_argument);
0739 checkUnchanged();
0740
0741
0742 BOOST_CHECK_THROW(
0743 cylStack.update(newBounds, trf * Translation3{Vector3{10_mm, 0, 0}},
0744 *logger),
0745 std::invalid_argument);
0746 checkUnchanged();
0747
0748
0749 BOOST_CHECK_THROW(
0750 cylStack.update(newBounds, trf * Translation3{Vector3{0, 10_mm, 0}},
0751 *logger),
0752 std::invalid_argument);
0753 checkUnchanged();
0754
0755
0756 BOOST_CHECK_THROW(
0757 cylStack.update(newBounds, trf * AngleAxis3{10_degree, Vector3::UnitY()},
0758 *logger),
0759 std::invalid_argument);
0760 checkUnchanged();
0761
0762 cylStack.update(newBounds, trf, *logger);
0763
0764 BOOST_CHECK_EQUAL(cylStack.transform().matrix(), trf.matrix());
0765 const auto* cylBounds =
0766 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
0767 BOOST_REQUIRE(cylBounds != nullptr);
0768 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), 950_mm);
0769
0770
0771 for (const auto* vol : volumes) {
0772 const auto* volBounds =
0773 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0774 BOOST_REQUIRE(volBounds != nullptr);
0775 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
0776 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMaxR), 300_mm);
0777 }
0778
0779 if (strategy == VolumeResizeStrategy::Expand) {
0780
0781 BOOST_CHECK_EQUAL(volumes.size(), 3);
0782 const Volume* vol = nullptr;
0783 if (f < 0.0) {
0784
0785 vol = volumes.front();
0786 } else {
0787
0788 vol = volumes.back();
0789 }
0790
0791 const auto* volBounds =
0792 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
0793 BOOST_REQUIRE(volBounds != nullptr);
0794 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0795 450_mm);
0796 BOOST_CHECK_EQUAL(vol->center()[eZ], f * 550_mm);
0797 } else if (strategy == VolumeResizeStrategy::Gap) {
0798
0799 BOOST_CHECK_EQUAL(volumes.size(), 4);
0800
0801 const Volume* gap = nullptr;
0802 if (f < 0.0) {
0803 gap = volumes.front();
0804 } else {
0805 gap = volumes.back();
0806 }
0807 const auto* gapBounds =
0808 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
0809 BOOST_REQUIRE(gapBounds != nullptr);
0810
0811 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0812 50_mm);
0813 BOOST_CHECK_EQUAL(gap->center()[eZ], f * 950_mm);
0814 }
0815 }
0816
0817 BOOST_AUTO_TEST_CASE(ResizeReproduction1) {
0818 Transform3 trf1 =
0819 Transform3::Identity() * Translation3{Vector3::UnitZ() * -2000};
0820 auto bounds1 = std::make_shared<CylinderVolumeBounds>(70, 100, 100.0);
0821 Volume vol1{trf1, bounds1};
0822
0823 std::vector<Volume*> volumes = {&vol1};
0824 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0825 VolumeAttachmentStrategy::Gap,
0826 VolumeResizeStrategy::Gap, *logger);
0827
0828 Transform3 trf2 =
0829 Transform3::Identity() * Translation3{Vector3::UnitZ() * -1500};
0830 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 600), trf2,
0831 *logger);
0832
0833 std::cout << stack.volumeBounds() << std::endl;
0834 std::cout << stack.transform().matrix() << std::endl;
0835
0836 Transform3 trf3 =
0837 Transform3::Identity() * Translation3{Vector3::UnitZ() * -1600};
0838 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 700), trf3,
0839 *logger);
0840 }
0841
0842 BOOST_AUTO_TEST_CASE(ResizeReproduction2) {
0843
0844 Transform3 trf1 =
0845 Transform3::Identity() * Translation3{Vector3::UnitZ() * 263};
0846 auto bounds1 = std::make_shared<CylinderVolumeBounds>(30, 100, 4.075);
0847 Volume vol1{trf1, bounds1};
0848
0849 std::vector<Volume*> volumes = {&vol1};
0850 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0851 VolumeAttachmentStrategy::Gap,
0852 VolumeResizeStrategy::Gap, *logger);
0853
0854 Transform3 trf2 =
0855 Transform3::Identity() * Translation3{Vector3::UnitZ() * 260.843};
0856 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 6.232), trf2,
0857 *logger);
0858
0859 std::cout << stack.volumeBounds() << std::endl;
0860 std::cout << stack.transform().matrix() << std::endl;
0861
0862 Transform3 trf3 =
0863 Transform3::Identity() * Translation3{Vector3::UnitZ() * 1627.31};
0864 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 1372.699),
0865 trf3, *logger);
0866 }
0867
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 BOOST_AUTO_TEST_CASE(ResizeGapMultiple) {
0897 Transform3 trf = Transform3::Identity();
0898 auto bounds = std::make_shared<CylinderVolumeBounds>(70, 100, 100.0);
0899 Volume vol{trf, bounds};
0900
0901 BOOST_TEST_CONTEXT("Positive") {
0902 std::vector<Volume*> volumes = {&vol};
0903 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0904 VolumeAttachmentStrategy::Gap,
0905 VolumeResizeStrategy::Gap, *logger);
0906
0907 BOOST_CHECK_EQUAL(volumes.size(), 1);
0908 BOOST_CHECK(stack.gaps().empty());
0909
0910 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 200),
0911 trf * Translation3{Vector3::UnitZ() * 100}, *logger);
0912 BOOST_CHECK_EQUAL(volumes.size(), 2);
0913 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0914
0915 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], 200.0);
0916 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0917 &stack.gaps().front()->volumeBounds());
0918 BOOST_REQUIRE_NE(cylBounds, nullptr);
0919 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0920 100.0);
0921
0922 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 300),
0923 trf * Translation3{Vector3::UnitZ() * 200}, *logger);
0924
0925 BOOST_CHECK_EQUAL(volumes.size(), 2);
0926
0927 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0928
0929 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], 300.0);
0930 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0931 &stack.gaps().front()->volumeBounds());
0932 BOOST_REQUIRE_NE(cylBounds, nullptr);
0933 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0934 200.0);
0935 }
0936
0937 BOOST_TEST_CONTEXT("Negative") {
0938 std::vector<Volume*> volumes = {&vol};
0939 CylinderVolumeStack stack(volumes, AxisDirection::AxisZ,
0940 VolumeAttachmentStrategy::Gap,
0941 VolumeResizeStrategy::Gap, *logger);
0942
0943 BOOST_CHECK_EQUAL(volumes.size(), 1);
0944 BOOST_CHECK(stack.gaps().empty());
0945
0946 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 200),
0947 trf * Translation3{Vector3::UnitZ() * -100}, *logger);
0948 BOOST_CHECK_EQUAL(volumes.size(), 2);
0949 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0950
0951 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], -200.0);
0952 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0953 &stack.gaps().front()->volumeBounds());
0954 BOOST_REQUIRE_NE(cylBounds, nullptr);
0955 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0956 100.0);
0957
0958 stack.update(std::make_shared<CylinderVolumeBounds>(30.0, 100, 300),
0959 trf * Translation3{Vector3::UnitZ() * -200}, *logger);
0960
0961 BOOST_CHECK_EQUAL(volumes.size(), 2);
0962
0963 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0964
0965 BOOST_CHECK_EQUAL(stack.gaps().front()->center()[eZ], -300.0);
0966 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0967 &stack.gaps().front()->volumeBounds());
0968 BOOST_REQUIRE_NE(cylBounds, nullptr);
0969 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
0970 200.0);
0971 }
0972 }
0973
0974 BOOST_AUTO_TEST_SUITE_END()
0975
0976 BOOST_AUTO_TEST_SUITE(RDirection)
0977
0978 BOOST_DATA_TEST_CASE(Baseline,
0979 (boost::unit_test::data::xrange(-135, 180, 45) *
0980 boost::unit_test::data::xrange(0, 2, 1) *
0981 boost::unit_test::data::make(-0.1, 0.0, 0.1) *
0982 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0983 Vector3{20_mm, 0_mm, 0_mm},
0984 Vector3{0_mm, 20_mm, 0_mm},
0985 Vector3{20_mm, 20_mm, 0_mm},
0986 Vector3{0_mm, 0_mm, 20_mm}) *
0987 boost::unit_test::data::make(strategies)),
0988 angle, rotate, f, offset, strategy) {
0989 double hlZ = 400_mm;
0990
0991 double fInner = 1.0 + f;
0992 double fOuter = 1.0 - f;
0993
0994
0995 auto bounds1 = std::make_shared<CylinderVolumeBounds>(fInner * 100_mm,
0996 fOuter * 300_mm, hlZ);
0997 auto bounds2 = std::make_shared<CylinderVolumeBounds>(fInner * 300_mm,
0998 fOuter * 600_mm, hlZ);
0999 auto bounds3 = std::make_shared<CylinderVolumeBounds>(fInner * 600_mm,
1000 fOuter * 900_mm, hlZ);
1001
1002 Transform3 base =
1003 AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
1004
1005
1006
1007 Transform3 transform1 = base;
1008 transform1.translate(Vector3{0_mm, 0_mm, 20_mm});
1009 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
1010
1011 Transform3 transform2 = base;
1012 transform2.translate(Vector3{0_mm, 0_mm, -30_mm});
1013 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
1014
1015 Transform3 transform3 = base;
1016 transform3.translate(Vector3{0_mm, 0_mm, 40_mm});
1017 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
1018
1019 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1020
1021 std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
1022
1023 std::vector<Volume*> origVolumes = volumes;
1024
1025 std::vector<CylinderVolumeBounds> originalBounds;
1026 std::transform(
1027 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
1028 [](const auto& vol) {
1029 return dynamic_cast<const CylinderVolumeBounds&>(vol->volumeBounds());
1030 });
1031
1032 if (f < 0.0) {
1033 BOOST_CHECK_THROW(
1034 CylinderVolumeStack(volumes, AxisDirection::AxisR, strategy,
1035 VolumeResizeStrategy::Gap, *logger),
1036 std::invalid_argument);
1037 return;
1038 }
1039
1040 CylinderVolumeStack cylStack(volumes, AxisDirection::AxisR, strategy,
1041 VolumeResizeStrategy::Gap, *logger);
1042
1043 auto stackBounds =
1044 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1045 BOOST_REQUIRE(stackBounds != nullptr);
1046
1047 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMinR),
1048 fInner * 100_mm);
1049 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eMaxR),
1050 fOuter * 900_mm);
1051 double expectedHalfLengthZ = (40_mm + 30_mm + 2 * hlZ) / 2.0;
1052 BOOST_CHECK_EQUAL(stackBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1053 expectedHalfLengthZ);
1054
1055
1056
1057
1058 Transform3 commonTransform = base * Translation3{0_mm, 0_mm, 5_mm};
1059
1060 CHECK_CLOSE_OR_SMALL(cylStack.transform().matrix(), commonTransform.matrix(),
1061 1e-10, 1e-14);
1062
1063 for (const auto& volume : volumes) {
1064 const auto* cylinderBounds =
1065 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1066 BOOST_REQUIRE(cylinderBounds != nullptr);
1067 BOOST_CHECK_EQUAL(cylinderBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1068 expectedHalfLengthZ);
1069 }
1070
1071 BOOST_CHECK_EQUAL(
1072 dynamic_cast<const CylinderVolumeBounds&>(vol1->volumeBounds())
1073 .get(CylinderVolumeBounds::eMinR),
1074 fInner * 100_mm);
1075
1076 BOOST_CHECK_EQUAL(
1077 dynamic_cast<const CylinderVolumeBounds&>(vol3->volumeBounds())
1078 .get(CylinderVolumeBounds::eMaxR),
1079 fOuter * 900_mm);
1080
1081
1082 for (std::size_t i = 0; i < volumes.size() - 1; ++i) {
1083 const auto& a = volumes.at(i);
1084 const auto& b = volumes.at(i + 1);
1085
1086 const auto* aBounds =
1087 dynamic_cast<const CylinderVolumeBounds*>(&a->volumeBounds());
1088 const auto* bBounds =
1089 dynamic_cast<const CylinderVolumeBounds*>(&b->volumeBounds());
1090
1091 double aMidR = (aBounds->get(CylinderVolumeBounds::eMinR) +
1092 aBounds->get(CylinderVolumeBounds::eMaxR)) /
1093 2.0;
1094
1095 double bMidR = (bBounds->get(CylinderVolumeBounds::eMinR) +
1096 bBounds->get(CylinderVolumeBounds::eMaxR)) /
1097 2.0;
1098
1099 BOOST_CHECK_LT(aMidR, bMidR);
1100 }
1101
1102 if (f == 0.0) {
1103
1104 BOOST_CHECK_EQUAL(volumes.size(), 3);
1105
1106
1107 for (const auto& [volume, origCylBounds] :
1108 zip(origVolumes, originalBounds)) {
1109 const auto* newBounds =
1110 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1111 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1112 origCylBounds.get(CylinderVolumeBounds::eMinR));
1113 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1114 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1115 }
1116 } else {
1117 const auto* newBounds1 =
1118 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1119 const auto* newBounds2 =
1120 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1121 const auto* newBounds3 =
1122 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1123 if (strategy == VolumeAttachmentStrategy::Gap) {
1124
1125 BOOST_CHECK_EQUAL(volumes.size(), 5);
1126
1127
1128 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1129 fInner * 100_mm);
1130 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1131 fOuter * 300_mm);
1132 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1133 fInner * 300_mm);
1134 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1135 fOuter * 600_mm);
1136 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1137 fInner * 600_mm);
1138 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1139 fOuter * 900_mm);
1140
1141 auto gap1 = volumes.at(1);
1142 auto gap2 = volumes.at(3);
1143
1144 const auto* gapBounds1 =
1145 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
1146 const auto* gapBounds2 =
1147 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
1148
1149 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMinR),
1150 fOuter * 300_mm);
1151 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMaxR),
1152 fInner * 300_mm);
1153 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMinR),
1154 fOuter * 600_mm);
1155 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMaxR),
1156 fInner * 600_mm);
1157
1158 } else if (strategy == VolumeAttachmentStrategy::First) {
1159
1160 BOOST_CHECK_EQUAL(volumes.size(), 3);
1161
1162
1163 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1164 fInner * 100_mm);
1165 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1166 fInner * 300_mm);
1167
1168
1169 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1170 fInner * 300_mm);
1171 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1172 fInner * 600_mm);
1173
1174
1175 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1176 fInner * 600_mm);
1177 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1178 fOuter * 900_mm);
1179
1180 } else if (strategy == VolumeAttachmentStrategy::Second) {
1181
1182 BOOST_CHECK_EQUAL(volumes.size(), 3);
1183
1184
1185 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1186 fInner * 100_mm);
1187 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1188 fOuter * 300_mm);
1189
1190
1191 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1192 fOuter * 300_mm);
1193 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1194 fOuter * 600_mm);
1195
1196
1197 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1198 fOuter * 600_mm);
1199 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1200 fOuter * 900_mm);
1201 } else if (strategy == VolumeAttachmentStrategy::Midpoint) {
1202
1203 BOOST_CHECK_EQUAL(volumes.size(), 3);
1204
1205
1206 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR),
1207 fInner * 100_mm);
1208 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMaxR),
1209 (fOuter * 300_mm + fInner * 300_mm) / 2.0);
1210
1211
1212 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMinR),
1213 (fOuter * 300_mm + fInner * 300_mm) / 2.0);
1214 BOOST_CHECK_EQUAL(newBounds2->get(CylinderVolumeBounds::eMaxR),
1215 (fOuter * 600_mm + fInner * 600_mm) / 2.0);
1216
1217
1218 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMinR),
1219 (fOuter * 600_mm + fInner * 600_mm) / 2.0);
1220 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR),
1221 fOuter * 900_mm);
1222 }
1223 }
1224 }
1225
1226 BOOST_DATA_TEST_CASE(UpdateStack,
1227 (boost::unit_test::data::xrange(-135, 180, 45) *
1228 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
1229 Vector3{20_mm, 0_mm, 0_mm},
1230 Vector3{0_mm, 20_mm, 0_mm},
1231 Vector3{20_mm, 20_mm, 0_mm},
1232 Vector3{0_mm, 0_mm, 20_mm}) *
1233 boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
1234 boost::unit_test::data::make(resizeStrategies)),
1235 angle, offset, zshift, strategy) {
1236 double hlZ = 400_mm;
1237
1238
1239 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, hlZ);
1240 auto bounds2 = std::make_shared<CylinderVolumeBounds>(300_mm, 600_mm, hlZ);
1241 auto bounds3 = std::make_shared<CylinderVolumeBounds>(600_mm, 900_mm, hlZ);
1242
1243 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::UnitX()) *
1244 Translation3(offset + Vector3{0, 0, zshift});
1245
1246
1247 auto vol1 = std::make_shared<Volume>(base, bounds1);
1248 auto vol2 = std::make_shared<Volume>(base, bounds2);
1249 auto vol3 = std::make_shared<Volume>(base, bounds3);
1250
1251 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1252 std::vector<Volume*> originalVolumes = volumes;
1253
1254 std::vector<CylinderVolumeBounds> originalBounds;
1255
1256 std::transform(
1257 volumes.begin(), volumes.end(), std::back_inserter(originalBounds),
1258 [](const auto& vol) {
1259 return *dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
1260 });
1261
1262 const CylinderVolumeBounds* originalOuterBounds = nullptr;
1263
1264 std::unique_ptr<CylinderVolumeStack> cylStack;
1265
1266 auto resetCylStack = [&]() {
1267 volumes = originalVolumes;
1268
1269 for (const auto& [volume, origBounds] : zip(volumes, originalBounds)) {
1270 volume->assignVolumeBounds(
1271 std::make_shared<CylinderVolumeBounds>(origBounds));
1272 }
1273
1274 cylStack = std::make_unique<CylinderVolumeStack>(
1275 volumes, AxisDirection::AxisR,
1276 VolumeAttachmentStrategy::Gap,
1277
1278 strategy, *logger);
1279
1280 originalOuterBounds =
1281 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1282 };
1283
1284 resetCylStack();
1285
1286 auto assertInitialVolumesUnchanged = [&]() {
1287 for (const auto& [volume, origCylBounds] :
1288 zip(originalVolumes, originalBounds)) {
1289 const auto* newBounds =
1290 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1291 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1292 origCylBounds.get(CylinderVolumeBounds::eMinR));
1293 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1294 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1295 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1296 origCylBounds.get(CylinderVolumeBounds::eHalfLengthZ));
1297 BOOST_CHECK_EQUAL(volume->transform().matrix(), base.matrix());
1298 }
1299 };
1300
1301 auto assertOriginalBounds = [&]() {
1302 const auto* cylBounds =
1303 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1304 BOOST_REQUIRE(cylBounds != nullptr);
1305 BOOST_CHECK_EQUAL(cylBounds, originalOuterBounds);
1306 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1307 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1308 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1309 };
1310
1311 assertOriginalBounds();
1312
1313 {
1314
1315 auto bounds = std::make_shared<CylinderVolumeBounds>(
1316 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1317 cylStack->update(bounds, std::nullopt, *logger);
1318 assertOriginalBounds();
1319 }
1320
1321 {
1322
1323 auto bounds = std::make_shared<CylinderVolumeBounds>(
1324 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1325 bounds->set(CylinderVolumeBounds::eMinR, 200_mm);
1326 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1327 std::invalid_argument);
1328 assertOriginalBounds();
1329 }
1330
1331 {
1332
1333 auto bounds = std::make_shared<CylinderVolumeBounds>(
1334 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1335 bounds->set(CylinderVolumeBounds::eMaxR, 500_mm);
1336 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1337 std::invalid_argument);
1338 assertOriginalBounds();
1339 }
1340
1341 {
1342
1343 auto bounds = std::make_shared<CylinderVolumeBounds>(
1344 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1345 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 0.5 * hlZ);
1346 BOOST_CHECK_THROW(cylStack->update(bounds, std::nullopt, *logger),
1347 std::invalid_argument);
1348 assertOriginalBounds();
1349 }
1350
1351 {
1352
1353 auto bounds = std::make_shared<CylinderVolumeBounds>(
1354 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1355 bounds->set(CylinderVolumeBounds::eMinR, 50_mm);
1356 cylStack->update(bounds, std::nullopt, *logger);
1357 const auto* cylBounds =
1358 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1359 BOOST_REQUIRE(cylBounds != nullptr);
1360 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
1361
1362 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1363 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1364
1365 if (strategy == VolumeResizeStrategy::Expand) {
1366
1367 BOOST_CHECK_EQUAL(volumes.size(), 3);
1368
1369
1370 const auto* newBounds1 =
1371 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1372 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR), 50_mm);
1373
1374 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1375
1376
1377 const auto* newBounds2 =
1378 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1379 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1380 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1381
1382 const auto* newBounds3 =
1383 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1384 BOOST_CHECK_EQUAL(*newBounds3, originalBounds[2]);
1385 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1386
1387 } else if (strategy == VolumeResizeStrategy::Gap) {
1388
1389 BOOST_CHECK_EQUAL(volumes.size(), 4);
1390
1391 auto gap = volumes.front();
1392 auto gapBounds =
1393 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
1394 BOOST_REQUIRE(gapBounds != nullptr);
1395 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
1396 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), 100_mm);
1397 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1398 hlZ);
1399 BOOST_CHECK_EQUAL(gap->transform().matrix(), base.matrix());
1400
1401
1402 assertInitialVolumesUnchanged();
1403 }
1404 }
1405
1406 resetCylStack();
1407
1408 {
1409
1410 auto bounds = std::make_shared<CylinderVolumeBounds>(
1411 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1412 bounds->set(CylinderVolumeBounds::eMaxR, 1000_mm);
1413 cylStack->update(bounds, std::nullopt, *logger);
1414 const auto* cylBounds =
1415 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1416 BOOST_REQUIRE(cylBounds != nullptr);
1417 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1418
1419 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1420 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1421
1422 if (strategy == VolumeResizeStrategy::Expand) {
1423
1424 BOOST_CHECK_EQUAL(volumes.size(), 3);
1425
1426
1427 const auto* newBounds3 =
1428 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1429 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1430
1431 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1432
1433
1434 const auto* newBounds1 =
1435 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1436 BOOST_CHECK_EQUAL(*newBounds1, originalBounds[0]);
1437 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1438
1439 const auto* newBounds2 =
1440 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1441 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1442 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1443
1444 } else if (strategy == VolumeResizeStrategy::Gap) {
1445
1446 BOOST_CHECK_EQUAL(volumes.size(), 4);
1447
1448 auto gap = volumes.back();
1449 auto gapBounds =
1450 dynamic_cast<const CylinderVolumeBounds*>(&gap->volumeBounds());
1451 BOOST_REQUIRE(gapBounds != nullptr);
1452 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), 900_mm);
1453 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), 1000_mm);
1454 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1455 hlZ);
1456 BOOST_CHECK_EQUAL(gap->transform().matrix(), base.matrix());
1457
1458
1459 assertInitialVolumesUnchanged();
1460 }
1461 }
1462
1463 resetCylStack();
1464
1465 {
1466
1467 auto bounds = std::make_shared<CylinderVolumeBounds>(
1468 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1469 bounds->set({
1470 {CylinderVolumeBounds::eMinR, 0_mm},
1471 {CylinderVolumeBounds::eMaxR, 1100_mm},
1472 });
1473
1474 cylStack->update(bounds, std::nullopt, *logger);
1475 const auto* cylBounds =
1476 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1477 BOOST_REQUIRE(cylBounds != nullptr);
1478 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1479
1480 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 0_mm);
1481 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1482
1483 if (strategy == VolumeResizeStrategy::Expand) {
1484
1485 BOOST_CHECK_EQUAL(volumes.size(), 3);
1486
1487
1488 const auto* newBounds1 =
1489 dynamic_cast<const CylinderVolumeBounds*>(&vol1->volumeBounds());
1490 BOOST_CHECK_EQUAL(newBounds1->get(CylinderVolumeBounds::eMinR), 0_mm);
1491
1492 BOOST_CHECK_EQUAL(vol1->transform().matrix(), base.matrix());
1493
1494
1495 const auto* newBounds2 =
1496 dynamic_cast<const CylinderVolumeBounds*>(&vol2->volumeBounds());
1497 BOOST_CHECK_EQUAL(*newBounds2, originalBounds[1]);
1498 BOOST_CHECK_EQUAL(vol2->transform().matrix(), base.matrix());
1499
1500
1501 const auto* newBounds3 =
1502 dynamic_cast<const CylinderVolumeBounds*>(&vol3->volumeBounds());
1503 BOOST_CHECK_EQUAL(newBounds3->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1504
1505 BOOST_CHECK_EQUAL(vol3->transform().matrix(), base.matrix());
1506
1507 } else if (strategy == VolumeResizeStrategy::Gap) {
1508
1509 BOOST_CHECK_EQUAL(volumes.size(), 5);
1510
1511 auto gap1 = volumes.front();
1512 auto gapBounds1 =
1513 dynamic_cast<const CylinderVolumeBounds*>(&gap1->volumeBounds());
1514 BOOST_REQUIRE(gapBounds1 != nullptr);
1515 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMinR), 0_mm);
1516 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eMaxR), 100_mm);
1517 BOOST_CHECK_EQUAL(gapBounds1->get(CylinderVolumeBounds::eHalfLengthZ),
1518 hlZ);
1519 BOOST_CHECK_EQUAL(gap1->transform().matrix(), base.matrix());
1520
1521 auto gap2 = volumes.back();
1522 auto gapBounds2 =
1523 dynamic_cast<const CylinderVolumeBounds*>(&gap2->volumeBounds());
1524 BOOST_REQUIRE(gapBounds2 != nullptr);
1525 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMinR), 900_mm);
1526 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eMaxR), 1100_mm);
1527 BOOST_CHECK_EQUAL(gapBounds2->get(CylinderVolumeBounds::eHalfLengthZ),
1528 hlZ);
1529
1530
1531 assertInitialVolumesUnchanged();
1532 }
1533 }
1534
1535 resetCylStack();
1536
1537 {
1538
1539 auto bounds = std::make_shared<CylinderVolumeBounds>(
1540 dynamic_cast<const CylinderVolumeBounds&>(cylStack->volumeBounds()));
1541 bounds->set(CylinderVolumeBounds::eHalfLengthZ, 2 * hlZ);
1542 cylStack->update(bounds, std::nullopt, *logger);
1543 const auto* cylBounds =
1544 dynamic_cast<const CylinderVolumeBounds*>(&cylStack->volumeBounds());
1545 BOOST_REQUIRE(cylBounds != nullptr);
1546 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1547 2 * hlZ);
1548
1549
1550 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1551 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 900_mm);
1552
1553
1554 BOOST_CHECK_EQUAL(volumes.size(), 3);
1555
1556 for (const auto& [volume, origCylBounds] :
1557 zip(originalVolumes, originalBounds)) {
1558 const auto* newBounds =
1559 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
1560
1561 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMinR),
1562 origCylBounds.get(CylinderVolumeBounds::eMinR));
1563 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eMaxR),
1564 origCylBounds.get(CylinderVolumeBounds::eMaxR));
1565
1566
1567 BOOST_CHECK_EQUAL(newBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1568 2 * hlZ);
1569
1570
1571 BOOST_CHECK_EQUAL(volume->transform().matrix(), base.matrix());
1572 }
1573 }
1574 }
1575
1576 BOOST_DATA_TEST_CASE(
1577 UpdateStackOneSided,
1578 (boost::unit_test::data::make(-1.0, 1.0) ^
1579 boost::unit_test::data::make(VolumeResizeStrategy::Gap,
1580 VolumeResizeStrategy::Expand)),
1581 f, strategy) {
1582
1583
1584 auto trf = Transform3::Identity();
1585
1586 auto vol1 = std::make_shared<Volume>(
1587 trf, std::make_shared<CylinderVolumeBounds>(100_mm, 300_mm, 400_mm));
1588
1589 auto vol2 = std::make_shared<Volume>(
1590 trf, std::make_shared<CylinderVolumeBounds>(400_mm, 600_mm, 400_mm));
1591
1592 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
1593
1594 CylinderVolumeStack cylStack{volumes, AxisDirection::AxisR,
1595 VolumeAttachmentStrategy::Gap, strategy,
1596 *logger};
1597 const auto* originalBounds =
1598 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1599
1600
1601 auto newBounds = std::make_shared<CylinderVolumeBounds>(
1602 dynamic_cast<const CylinderVolumeBounds&>(cylStack.volumeBounds()));
1603 newBounds->set(CylinderVolumeBounds::eHalfLengthZ, 450_mm);
1604
1605 trf *= Translation3{Vector3{0_mm, 0_mm, f * 50_mm}};
1606
1607
1608 auto checkUnchanged = [&]() {
1609 const auto* cylBounds =
1610 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1611 BOOST_REQUIRE(cylBounds != nullptr);
1612 BOOST_CHECK_EQUAL(*cylBounds, *originalBounds);
1613 };
1614
1615
1616 BOOST_CHECK_THROW(
1617 cylStack.update(newBounds, trf * Translation3{Vector3{0, 0, f * 20_mm}},
1618 *logger),
1619 std::invalid_argument);
1620 checkUnchanged();
1621
1622
1623 BOOST_CHECK_THROW(
1624 cylStack.update(newBounds, trf * Translation3{Vector3{10_mm, 0, 0}},
1625 *logger),
1626 std::invalid_argument);
1627 checkUnchanged();
1628
1629
1630 BOOST_CHECK_THROW(
1631 cylStack.update(newBounds, trf * Translation3{Vector3{0, 10_mm, 0}},
1632 *logger),
1633 std::invalid_argument);
1634 checkUnchanged();
1635
1636
1637 BOOST_CHECK_THROW(
1638 cylStack.update(newBounds, trf * AngleAxis3{10_degree, Vector3::UnitY()},
1639 *logger),
1640 std::invalid_argument);
1641 checkUnchanged();
1642
1643 cylStack.update(newBounds, trf, *logger);
1644
1645 BOOST_CHECK_EQUAL(cylStack.transform().matrix(), trf.matrix());
1646 const auto* cylBounds =
1647 dynamic_cast<const CylinderVolumeBounds*>(&cylStack.volumeBounds());
1648 BOOST_REQUIRE(cylBounds != nullptr);
1649 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 100_mm);
1650 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 600_mm);
1651
1652
1653 for (const auto* vol : volumes) {
1654 const auto* volBounds =
1655 dynamic_cast<const CylinderVolumeBounds*>(&vol->volumeBounds());
1656 BOOST_REQUIRE(volBounds != nullptr);
1657 BOOST_CHECK_EQUAL(vol->transform().matrix(), trf.matrix());
1658 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1659 450_mm);
1660 }
1661 }
1662
1663 BOOST_AUTO_TEST_CASE(ResizeGapMultiple) {
1664 Transform3 trf = Transform3::Identity();
1665 auto bounds = std::make_shared<CylinderVolumeBounds>(100, 200, 100);
1666 Volume vol{trf, bounds};
1667
1668 BOOST_TEST_CONTEXT("Outer") {
1669 std::vector<Volume*> volumes = {&vol};
1670 CylinderVolumeStack stack(volumes, AxisDirection::AxisR,
1671 VolumeAttachmentStrategy::Gap,
1672 VolumeResizeStrategy::Gap, *logger);
1673
1674 BOOST_CHECK_EQUAL(volumes.size(), 1);
1675 BOOST_CHECK(stack.gaps().empty());
1676
1677 stack.update(std::make_shared<CylinderVolumeBounds>(100, 250, 100), trf,
1678 *logger);
1679 BOOST_CHECK_EQUAL(volumes.size(), 2);
1680 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1681
1682 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1683 &stack.gaps().front()->volumeBounds());
1684 BOOST_REQUIRE_NE(cylBounds, nullptr);
1685 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 200);
1686 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 250);
1687
1688 stack.update(std::make_shared<CylinderVolumeBounds>(100, 300, 100), trf,
1689 *logger);
1690
1691 BOOST_CHECK_EQUAL(volumes.size(), 2);
1692
1693 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1694
1695 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1696 &stack.gaps().front()->volumeBounds());
1697 BOOST_REQUIRE_NE(cylBounds, nullptr);
1698 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 200);
1699 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 300);
1700 }
1701
1702 BOOST_TEST_CONTEXT("Inner") {
1703 std::vector<Volume*> volumes = {&vol};
1704 CylinderVolumeStack stack(volumes, AxisDirection::AxisR,
1705 VolumeAttachmentStrategy::Gap,
1706 VolumeResizeStrategy::Gap, *logger);
1707
1708 BOOST_CHECK_EQUAL(volumes.size(), 1);
1709 BOOST_CHECK(stack.gaps().empty());
1710
1711 stack.update(std::make_shared<CylinderVolumeBounds>(50, 200, 100), trf,
1712 *logger);
1713 BOOST_CHECK_EQUAL(volumes.size(), 2);
1714 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1715
1716 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1717 &stack.gaps().front()->volumeBounds());
1718 BOOST_REQUIRE_NE(cylBounds, nullptr);
1719 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 50);
1720 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 100);
1721
1722 stack.update(std::make_shared<CylinderVolumeBounds>(0, 200, 100), trf,
1723 *logger);
1724
1725 BOOST_CHECK_EQUAL(volumes.size(), 2);
1726
1727 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
1728
1729 cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
1730 &stack.gaps().front()->volumeBounds());
1731 BOOST_REQUIRE_NE(cylBounds, nullptr);
1732 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMinR), 0);
1733 BOOST_CHECK_EQUAL(cylBounds->get(CylinderVolumeBounds::eMaxR), 100);
1734 }
1735 }
1736
1737 BOOST_AUTO_TEST_SUITE_END()
1738
1739 BOOST_AUTO_TEST_SUITE(Common)
1740
1741 BOOST_DATA_TEST_CASE(JoinCylinderVolumesInvalidDirection,
1742 boost::unit_test::data::make(strategies), strategy) {
1743 std::vector<Volume*> volumes;
1744 auto vol1 = std::make_shared<Volume>(
1745 Transform3::Identity(),
1746 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1747 volumes.push_back(vol1.get());
1748
1749
1750 BOOST_CHECK_THROW(
1751 CylinderVolumeStack(volumes, AxisDirection::AxisY, strategy),
1752 std::invalid_argument);
1753
1754 auto vol2 = std::make_shared<Volume>(
1755 Transform3::Identity(),
1756 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1757 volumes.push_back(vol2.get());
1758
1759 BOOST_CHECK_THROW(
1760 CylinderVolumeStack(volumes, AxisDirection::AxisY, strategy),
1761 std::invalid_argument);
1762 }
1763
1764 BOOST_DATA_TEST_CASE(JoinCylinderVolumesInvalidInput,
1765 (boost::unit_test::data::make(strategies) *
1766 boost::unit_test::data::make(Acts::AxisDirection::AxisZ,
1767 Acts::AxisDirection::AxisR)),
1768 strategy, direction) {
1769 BOOST_TEST_CONTEXT("Empty Volume") {
1770 std::vector<Volume*> volumes;
1771 BOOST_CHECK_THROW(CylinderVolumeStack(volumes, direction, strategy),
1772 std::invalid_argument);
1773 }
1774
1775 BOOST_TEST_CONTEXT("Volumes rotated relative to each other") {
1776
1777 for (const Vector3 axis : {Vector3::UnitX(), Vector3::UnitY()}) {
1778 std::vector<Volume*> volumes;
1779 auto vol1 = std::make_shared<Volume>(
1780 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1781 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1782 volumes.push_back(vol1.get());
1783
1784 BOOST_TEST_MESSAGE("Axis: " << axis);
1785 auto vol2 = std::make_shared<Volume>(
1786 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}} *
1787 AngleAxis3(1_degree, axis)},
1788 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1789 volumes.push_back(vol2.get());
1790
1791 BOOST_CHECK_THROW(CylinderVolumeStack(volumes, direction, strategy,
1792 VolumeResizeStrategy::Gap, *logger),
1793 std::invalid_argument);
1794 }
1795 }
1796
1797 BOOST_TEST_CONTEXT("Volumes shifted in the xy plane relative to each other") {
1798 for (const Vector3& shift :
1799 {Vector3{5_mm, 0, 0}, Vector3{0, -5_mm, 0}, Vector3{2_mm, -2_mm, 0}}) {
1800 std::vector<Volume*> volumes;
1801 auto vol1 = std::make_shared<Volume>(
1802 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1803 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1804 volumes.push_back(vol1.get());
1805
1806 auto vol2 = std::make_shared<Volume>(
1807 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm} + shift}},
1808 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1809 volumes.push_back(vol2.get());
1810
1811 BOOST_CHECK_THROW(CylinderVolumeStack(volumes, direction, strategy,
1812 VolumeResizeStrategy::Gap, *logger),
1813 std::invalid_argument);
1814 }
1815 }
1816
1817 BOOST_TEST_CONTEXT("Volume has phi values or bevel values") {
1818 std::vector<std::shared_ptr<CylinderVolumeBounds>> invalidVolumeBounds = {
1819 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1820 0.2 * std::numbers::pi),
1821
1822 std::make_shared<CylinderVolumeBounds>(
1823 100_mm, 400_mm, 400_mm, std::numbers::pi, 0.3 * std::numbers::pi),
1824
1825 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1826 std::numbers::pi, 0.,
1827 0.3 * std::numbers::pi),
1828 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm,
1829 std::numbers::pi, 0., 0.,
1830 0.3 * std::numbers::pi),
1831 };
1832
1833 for (const auto& invalid : invalidVolumeBounds) {
1834 std::stringstream ss;
1835 ss << "Invalid bounds: " << *invalid;
1836 BOOST_TEST_CONTEXT(ss.str()) {
1837 std::vector<Volume*> volumes;
1838 auto vol1 = std::make_shared<Volume>(
1839 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1840 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1841 volumes.push_back(vol1.get());
1842
1843 {
1844
1845 CylinderVolumeStack cylStack(volumes, direction, strategy,
1846 VolumeResizeStrategy::Gap, *logger);
1847 BOOST_CHECK_THROW(cylStack.update(invalid, std::nullopt, *logger),
1848 std::invalid_argument);
1849 }
1850
1851 {
1852 std::shared_ptr<Volume> vol;
1853 if (direction == AxisDirection::AxisZ) {
1854 vol = std::make_shared<Volume>(
1855 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}}}, invalid);
1856 } else {
1857 invalid->set({
1858 {CylinderVolumeBounds::eMinR, 400_mm},
1859 {CylinderVolumeBounds::eMaxR, 600_mm},
1860 });
1861 vol = std::make_shared<Volume>(
1862 Transform3{Translation3{Vector3{0_mm, 0_mm, 0_mm}}}, invalid);
1863 }
1864 volumes.push_back(vol.get());
1865 BOOST_CHECK_THROW(
1866 CylinderVolumeStack(volumes, direction, strategy,
1867 VolumeResizeStrategy::Gap, *logger),
1868 std::invalid_argument);
1869 }
1870 }
1871 }
1872 }
1873 }
1874
1875 BOOST_DATA_TEST_CASE(JoinCylinderVolumeSingle,
1876 (boost::unit_test::data::make(Acts::AxisDirection::AxisZ,
1877 Acts::AxisDirection::AxisR) *
1878 boost::unit_test::data::make(strategies)),
1879 direction, strategy) {
1880 auto vol = std::make_shared<Volume>(
1881 Transform3::Identity() * Translation3{14_mm, 24_mm, 0_mm} *
1882 AngleAxis3(73_degree, Vector3::UnitX()),
1883 std::make_shared<CylinderVolumeBounds>(100_mm, 400_mm, 400_mm));
1884
1885 std::vector<Volume*> volumes{vol.get()};
1886
1887 CylinderVolumeStack cylStack(volumes, direction, strategy,
1888 VolumeResizeStrategy::Gap, *logger);
1889
1890
1891
1892 BOOST_CHECK_EQUAL(volumes.size(), 1);
1893 BOOST_CHECK_EQUAL(volumes.at(0), vol.get());
1894 BOOST_CHECK_EQUAL(vol->transform().matrix(), cylStack.transform().matrix());
1895 BOOST_CHECK_EQUAL(vol->volumeBounds(), cylStack.volumeBounds());
1896 }
1897
1898 BOOST_AUTO_TEST_SUITE_END()
1899 BOOST_AUTO_TEST_SUITE_END()
1900 BOOST_AUTO_TEST_CASE(AsymmetricResizeZ) {
1901 double hlZ = 400_mm;
1902 double rMin = 100_mm;
1903 double rMax = 200_mm;
1904
1905
1906 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1907 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1908 auto bounds3 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1909
1910 Transform3 transform1 = Transform3::Identity();
1911 transform1.translate(Vector3{0_mm, 0_mm, -2 * hlZ});
1912 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
1913
1914 Transform3 transform2 = Transform3::Identity();
1915 transform2.translate(Vector3{0_mm, 0_mm, 0_mm});
1916 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
1917
1918 Transform3 transform3 = Transform3::Identity();
1919 transform3.translate(Vector3{0_mm, 0_mm, 2 * hlZ});
1920 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
1921
1922 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1923
1924 CylinderVolumeStack cylStack(
1925 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
1926 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
1927
1928
1929
1930 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 4 * hlZ);
1931 Transform3 newTransform =
1932 Transform3::Identity() * Translation3{0_mm, 0_mm, 0_mm};
1933
1934 cylStack.update(newBounds, newTransform, *logger);
1935
1936
1937 BOOST_CHECK_EQUAL(volumes.size(), 4);
1938
1939
1940 auto gapVol = volumes.front();
1941 auto gapBounds =
1942 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
1943 BOOST_REQUIRE(gapBounds != nullptr);
1944 BOOST_CHECK_EQUAL(
1945 gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1946 hlZ / 2);
1947 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), rMin);
1948 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), rMax);
1949 BOOST_CHECK_CLOSE(gapVol->center()[eZ], -3.5 * hlZ,
1950 1e-10);
1951
1952
1953 auto* lastVol = volumes.back();
1954 BOOST_CHECK_EQUAL(lastVol, vol3.get());
1955 auto lastBounds =
1956 dynamic_cast<const CylinderVolumeBounds*>(&lastVol->volumeBounds());
1957 BOOST_REQUIRE(lastBounds != nullptr);
1958 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eHalfLengthZ),
1959 1.5 * hlZ);
1960 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMinR), rMin);
1961 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMaxR), rMax);
1962 BOOST_CHECK_CLOSE(lastVol->center()[eZ], 2.5 * hlZ,
1963 1e-10);
1964
1965
1966 for (std::size_t i = 1; i < volumes.size() - 1; i++) {
1967 auto volBounds =
1968 dynamic_cast<const CylinderVolumeBounds*>(&volumes[i]->volumeBounds());
1969 BOOST_REQUIRE(volBounds != nullptr);
1970 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
1971 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMinR), rMin);
1972 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMaxR), rMax);
1973 }
1974 BOOST_CHECK_CLOSE(volumes[1]->center()[eZ], -2 * hlZ, 1e-10);
1975 BOOST_CHECK_CLOSE(volumes[2]->center()[eZ], 0, 1e-10);
1976 }
1977
1978 BOOST_AUTO_TEST_CASE(AsymmetricResizeZFlipped) {
1979 double hlZ = 400_mm;
1980 double rMin = 100_mm;
1981 double rMax = 200_mm;
1982
1983
1984 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1985 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1986 auto bounds3 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
1987
1988 Transform3 transform1 = Transform3::Identity() * Translation3(0, 0, -2 * hlZ);
1989 Transform3 transform2 = Transform3::Identity();
1990 Transform3 transform3 = Transform3::Identity() * Translation3(0, 0, 2 * hlZ);
1991
1992 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
1993 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
1994 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
1995
1996 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
1997
1998 CylinderVolumeStack cylStack(
1999 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
2000 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2001
2002
2003 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 4 * hlZ);
2004 cylStack.update(newBounds, std::nullopt, *logger);
2005
2006 BOOST_CHECK_EQUAL(volumes.size(), 4);
2007
2008
2009 auto gapVol = volumes.back();
2010 auto gapBounds =
2011 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
2012 BOOST_REQUIRE(gapBounds != nullptr);
2013 BOOST_CHECK_EQUAL(
2014 gapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2015 hlZ / 2);
2016 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), rMin);
2017 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2018 BOOST_CHECK_CLOSE(gapVol->center()[eZ], 3.5 * hlZ,
2019 1e-10);
2020
2021
2022 auto* firstVol = volumes.front();
2023 BOOST_CHECK_EQUAL(firstVol, vol1.get());
2024 auto firstBounds =
2025 dynamic_cast<const CylinderVolumeBounds*>(&firstVol->volumeBounds());
2026 BOOST_REQUIRE(firstBounds != nullptr);
2027 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2028 1.5 * hlZ);
2029 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMinR), rMin);
2030 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2031 BOOST_CHECK_CLOSE(firstVol->center()[eZ], -2.5 * hlZ,
2032 1e-10);
2033
2034
2035 for (std::size_t i = 1; i < volumes.size() - 1; i++) {
2036 auto volBounds =
2037 dynamic_cast<const CylinderVolumeBounds*>(&volumes[i]->volumeBounds());
2038 BOOST_REQUIRE(volBounds != nullptr);
2039 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2040 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMinR), rMin);
2041 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2042 }
2043 BOOST_CHECK_CLOSE(volumes[1]->center()[eZ], 0, 1e-10);
2044 BOOST_CHECK_CLOSE(volumes[2]->center()[eZ], 2 * hlZ, 1e-10);
2045 }
2046
2047 BOOST_AUTO_TEST_CASE(AsymmetricResizeR) {
2048 double hlZ = 400_mm;
2049
2050
2051 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 200_mm, hlZ);
2052 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 300_mm, hlZ);
2053 auto bounds3 = std::make_shared<CylinderVolumeBounds>(300_mm, 400_mm, hlZ);
2054
2055 Transform3 transform = Transform3::Identity();
2056 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2057 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2058 auto vol3 = std::make_shared<Volume>(transform, bounds3);
2059
2060 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
2061
2062 CylinderVolumeStack cylStack(
2063 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Midpoint,
2064 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
2065
2066
2067 auto newBounds = std::make_shared<CylinderVolumeBounds>(50_mm, 500_mm, hlZ);
2068 cylStack.update(newBounds, std::nullopt, *logger);
2069
2070 BOOST_CHECK_EQUAL(volumes.size(), 4);
2071
2072
2073 auto innerGap = volumes.front();
2074 auto innerGapBounds =
2075 dynamic_cast<const CylinderVolumeBounds*>(&innerGap->volumeBounds());
2076 BOOST_REQUIRE(innerGapBounds != nullptr);
2077 BOOST_CHECK_EQUAL(innerGapBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
2078 BOOST_CHECK_EQUAL(innerGapBounds->get(CylinderVolumeBounds::eMaxR), 100_mm);
2079 BOOST_CHECK_EQUAL(innerGapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2080 hlZ);
2081
2082
2083 auto* outerVol = volumes.back();
2084 BOOST_CHECK_EQUAL(outerVol, vol3.get());
2085
2086 auto outerBounds =
2087 dynamic_cast<const CylinderVolumeBounds*>(&outerVol->volumeBounds());
2088 BOOST_REQUIRE(outerBounds != nullptr);
2089 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eMinR), 300_mm);
2090 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eMaxR), 500_mm);
2091 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2092
2093
2094 for (std::size_t i = 1; i < volumes.size() - 1; i++) {
2095 auto volBounds =
2096 dynamic_cast<const CylinderVolumeBounds*>(&volumes[i]->volumeBounds());
2097 BOOST_REQUIRE(volBounds != nullptr);
2098 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2099 }
2100 }
2101
2102 BOOST_AUTO_TEST_CASE(AsymmetricResizeRFlipped) {
2103 double hlZ = 400_mm;
2104
2105
2106 auto bounds1 = std::make_shared<CylinderVolumeBounds>(100_mm, 200_mm, hlZ);
2107 auto bounds2 = std::make_shared<CylinderVolumeBounds>(200_mm, 300_mm, hlZ);
2108 auto bounds3 = std::make_shared<CylinderVolumeBounds>(300_mm, 400_mm, hlZ);
2109
2110 Transform3 transform = Transform3::Identity();
2111 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2112 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2113 auto vol3 = std::make_shared<Volume>(transform, bounds3);
2114
2115 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
2116
2117 CylinderVolumeStack cylStack(
2118 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Gap,
2119 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2120
2121
2122 auto newBounds = std::make_shared<CylinderVolumeBounds>(50_mm, 500_mm, hlZ);
2123 cylStack.update(newBounds, std::nullopt, *logger);
2124
2125 BOOST_CHECK_EQUAL(volumes.size(), 4);
2126
2127
2128 auto outerGap = volumes.back();
2129 auto outerGapBounds =
2130 dynamic_cast<const CylinderVolumeBounds*>(&outerGap->volumeBounds());
2131 BOOST_REQUIRE(outerGapBounds != nullptr);
2132 BOOST_CHECK_EQUAL(outerGapBounds->get(CylinderVolumeBounds::eMinR), 400_mm);
2133 BOOST_CHECK_EQUAL(outerGapBounds->get(CylinderVolumeBounds::eMaxR), 500_mm);
2134 BOOST_CHECK_EQUAL(outerGapBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2135 hlZ);
2136
2137
2138 auto* innerVol = volumes.front();
2139 BOOST_CHECK_EQUAL(innerVol, vol1.get());
2140
2141 auto innerBounds =
2142 dynamic_cast<const CylinderVolumeBounds*>(&innerVol->volumeBounds());
2143 BOOST_REQUIRE(innerBounds != nullptr);
2144 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
2145 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMaxR), 200_mm);
2146 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2147
2148
2149 for (std::size_t i = 1; i < volumes.size() - 1; i++) {
2150 auto volBounds =
2151 dynamic_cast<const CylinderVolumeBounds*>(&volumes[i]->volumeBounds());
2152 BOOST_REQUIRE(volBounds != nullptr);
2153 BOOST_CHECK_EQUAL(volBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2154 }
2155 }
2156
2157 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeZ) {
2158 double hlZ = 400_mm;
2159 double rMin = 100_mm;
2160 double rMax = 200_mm;
2161
2162
2163 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2164 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2165
2166 Transform3 transform1 = Transform3::Identity();
2167 transform1.translate(Vector3{0_mm, 0_mm, -hlZ});
2168 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
2169
2170 Transform3 transform2 = Transform3::Identity();
2171 transform2.translate(Vector3{0_mm, 0_mm, hlZ});
2172 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
2173
2174 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2175
2176
2177 CylinderVolumeStack cylStack(
2178 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
2179 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
2180
2181
2182 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 3 * hlZ);
2183 Transform3 newTransform =
2184 Transform3::Identity() * Translation3{0_mm, 0_mm, hlZ};
2185 cylStack.update(newBounds, newTransform, *logger);
2186
2187 auto* firstVol = volumes.front();
2188 BOOST_CHECK_EQUAL(firstVol, vol1.get());
2189 auto firstBounds =
2190 dynamic_cast<const CylinderVolumeBounds*>(&firstVol->volumeBounds());
2191 BOOST_REQUIRE(firstBounds != nullptr);
2192 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2193 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMinR), rMin);
2194 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2195 BOOST_CHECK_CLOSE(firstVol->center()[eZ], -hlZ, 1e-10);
2196
2197
2198 auto* lastVol = volumes.back();
2199 BOOST_CHECK_EQUAL(lastVol, vol2.get());
2200 auto lastBounds =
2201 dynamic_cast<const CylinderVolumeBounds*>(&lastVol->volumeBounds());
2202 BOOST_REQUIRE(lastBounds != nullptr);
2203 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2204 2 * hlZ);
2205 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMinR), rMin);
2206 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2207 BOOST_CHECK_CLOSE(lastVol->center()[eZ], 2 * hlZ, 1e-10);
2208
2209
2210 BOOST_CHECK_EQUAL(volumes.size(), 2);
2211 }
2212
2213 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeZFlipped) {
2214 double hlZ = 400_mm;
2215 double rMin = 100_mm;
2216 double rMax = 200_mm;
2217
2218
2219 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2220 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2221
2222 Transform3 transform1 = Transform3::Identity();
2223 transform1.translate(Vector3{0_mm, 0_mm, -hlZ});
2224 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
2225
2226 Transform3 transform2 = Transform3::Identity();
2227 transform2.translate(Vector3{0_mm, 0_mm, hlZ});
2228 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
2229
2230 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2231
2232 CylinderVolumeStack cylStack(
2233 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
2234 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2235
2236
2237 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 3 * hlZ);
2238 Transform3 newTransform =
2239 Transform3::Identity() * Translation3{0_mm, 0_mm, hlZ};
2240 cylStack.update(newBounds, newTransform, *logger);
2241
2242 auto* firstVol = volumes.front();
2243 BOOST_CHECK_EQUAL(firstVol, vol1.get());
2244 auto firstBounds =
2245 dynamic_cast<const CylinderVolumeBounds*>(&firstVol->volumeBounds());
2246 BOOST_REQUIRE(firstBounds != nullptr);
2247 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2248 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMinR), rMin);
2249 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2250 BOOST_CHECK_CLOSE(firstVol->center()[eZ], -hlZ, 1e-10);
2251
2252
2253 auto* midVol = volumes[1];
2254 BOOST_CHECK_EQUAL(midVol, vol2.get());
2255 auto midBounds =
2256 dynamic_cast<const CylinderVolumeBounds*>(&midVol->volumeBounds());
2257 BOOST_REQUIRE(midBounds != nullptr);
2258 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2259 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eMinR), rMin);
2260 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2261 BOOST_CHECK_CLOSE(midVol->center()[eZ], hlZ, 1e-10);
2262
2263
2264 BOOST_CHECK_EQUAL(volumes.size(), 3);
2265
2266
2267 auto* gapVol = volumes.back();
2268 auto gapBounds =
2269 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
2270 BOOST_REQUIRE(gapBounds != nullptr);
2271 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2272 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), rMin);
2273 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2274 BOOST_CHECK_CLOSE(gapVol->center()[eZ], 3 * hlZ, 1e-10);
2275 }
2276
2277 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeR) {
2278 double hlZ = 400_mm;
2279 double rMin1 = 100_mm;
2280 double rMax1 = 200_mm;
2281 double rMin2 = 200_mm;
2282 double rMax2 = 300_mm;
2283
2284
2285 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin1, rMax1, hlZ);
2286 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin2, rMax2, hlZ);
2287
2288 Transform3 transform = Transform3::Identity();
2289 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2290 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2291
2292 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2293
2294
2295 CylinderVolumeStack cylStack(
2296 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Gap,
2297 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
2298
2299
2300 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin1, 500_mm, hlZ);
2301 cylStack.update(newBounds, std::nullopt, *logger);
2302
2303
2304 auto* innerVol = volumes.front();
2305 BOOST_CHECK_EQUAL(innerVol, vol1.get());
2306 auto innerBounds =
2307 dynamic_cast<const CylinderVolumeBounds*>(&innerVol->volumeBounds());
2308 BOOST_REQUIRE(innerBounds != nullptr);
2309 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMinR), rMin1);
2310 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMaxR), rMax1);
2311 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2312
2313
2314 auto* outerVol = volumes.back();
2315 BOOST_CHECK_EQUAL(outerVol, vol2.get());
2316 auto outerBounds =
2317 dynamic_cast<const CylinderVolumeBounds*>(&outerVol->volumeBounds());
2318 BOOST_REQUIRE(outerBounds != nullptr);
2319 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eMinR), rMin2);
2320 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eMaxR), 500_mm);
2321 BOOST_CHECK_EQUAL(outerBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2322
2323
2324 BOOST_CHECK_EQUAL(volumes.size(), 2);
2325 }
2326
2327 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeRFlipped) {
2328 double hlZ = 400_mm;
2329 double rMin1 = 100_mm;
2330 double rMax1 = 200_mm;
2331 double rMin2 = 200_mm;
2332 double rMax2 = 300_mm;
2333
2334
2335 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin1, rMax1, hlZ);
2336 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin2, rMax2, hlZ);
2337
2338 Transform3 transform = Transform3::Identity();
2339 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2340 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2341
2342 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2343
2344 CylinderVolumeStack cylStack(
2345 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Gap,
2346 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2347
2348
2349 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin1, 500_mm, hlZ);
2350 cylStack.update(newBounds, std::nullopt, *logger);
2351
2352 auto* innerVol = volumes.front();
2353 BOOST_CHECK_EQUAL(innerVol, vol1.get());
2354 auto innerBounds =
2355 dynamic_cast<const CylinderVolumeBounds*>(&innerVol->volumeBounds());
2356 BOOST_REQUIRE(innerBounds != nullptr);
2357 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMinR), rMin1);
2358 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eMaxR), rMax1);
2359 BOOST_CHECK_EQUAL(innerBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2360 BOOST_CHECK_CLOSE(innerVol->center()[eZ], 0, 1e-10);
2361
2362
2363 auto* midVol = volumes[1];
2364 BOOST_CHECK_EQUAL(midVol, vol2.get());
2365 auto midBounds =
2366 dynamic_cast<const CylinderVolumeBounds*>(&midVol->volumeBounds());
2367 BOOST_REQUIRE(midBounds != nullptr);
2368 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eMinR), rMin2);
2369 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eMaxR), rMax2);
2370 BOOST_CHECK_EQUAL(midBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2371 BOOST_CHECK_CLOSE(midVol->center()[eZ], 0, 1e-10);
2372
2373
2374 BOOST_CHECK_EQUAL(volumes.size(), 3);
2375
2376
2377 auto* gapVol = volumes.back();
2378 auto gapBounds =
2379 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
2380 BOOST_REQUIRE(gapBounds != nullptr);
2381 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2382 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), rMax2);
2383 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), 500_mm);
2384 BOOST_CHECK_CLOSE(gapVol->center()[eZ], 0, 1e-10);
2385 }
2386
2387 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeZNegative) {
2388 double hlZ = 400_mm;
2389 double rMin = 100_mm;
2390 double rMax = 200_mm;
2391
2392
2393 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2394 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2395
2396 Transform3 transform1 = Transform3::Identity();
2397 transform1.translate(Vector3{0_mm, 0_mm, -hlZ});
2398 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
2399
2400 Transform3 transform2 = Transform3::Identity();
2401 transform2.translate(Vector3{0_mm, 0_mm, hlZ});
2402 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
2403
2404 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2405
2406 CylinderVolumeStack cylStack(
2407 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
2408 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2409
2410
2411 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 3 * hlZ);
2412 Transform3 newTransform =
2413 Transform3::Identity() * Translation3{0_mm, 0_mm, -hlZ};
2414 cylStack.update(newBounds, newTransform, *logger);
2415
2416 auto* firstVol = volumes.front();
2417 BOOST_CHECK_EQUAL(firstVol, vol1.get());
2418 auto firstBounds =
2419 dynamic_cast<const CylinderVolumeBounds*>(&firstVol->volumeBounds());
2420 BOOST_REQUIRE(firstBounds != nullptr);
2421 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eHalfLengthZ),
2422 2 * hlZ);
2423 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMinR), rMin);
2424 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2425 BOOST_CHECK_CLOSE(firstVol->center()[eZ], -2 * hlZ, 1e-10);
2426
2427
2428 auto* lastVol = volumes.back();
2429 BOOST_CHECK_EQUAL(lastVol, vol2.get());
2430 auto lastBounds =
2431 dynamic_cast<const CylinderVolumeBounds*>(&lastVol->volumeBounds());
2432 BOOST_REQUIRE(lastBounds != nullptr);
2433 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2434 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMinR), rMin);
2435 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2436 BOOST_CHECK_CLOSE(lastVol->center()[eZ], hlZ, 1e-10);
2437
2438
2439 BOOST_CHECK_EQUAL(volumes.size(), 2);
2440 }
2441
2442 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeZNegativeFlipped) {
2443 double hlZ = 400_mm;
2444 double rMin = 100_mm;
2445 double rMax = 200_mm;
2446
2447
2448 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2449 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin, rMax, hlZ);
2450
2451 Transform3 transform1 = Transform3::Identity();
2452 transform1.translate(Vector3{0_mm, 0_mm, -hlZ});
2453 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
2454
2455 Transform3 transform2 = Transform3::Identity();
2456 transform2.translate(Vector3{0_mm, 0_mm, hlZ});
2457 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
2458
2459 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2460
2461 CylinderVolumeStack cylStack(
2462 volumes, AxisDirection::AxisZ, VolumeAttachmentStrategy::Gap,
2463 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
2464
2465
2466 auto newBounds = std::make_shared<CylinderVolumeBounds>(rMin, rMax, 3 * hlZ);
2467 Transform3 newTransform =
2468 Transform3::Identity() * Translation3{0_mm, 0_mm, -hlZ};
2469 cylStack.update(newBounds, newTransform, *logger);
2470
2471
2472 BOOST_CHECK_EQUAL(volumes.size(), 3);
2473
2474
2475 auto* gapVol = volumes[0];
2476 auto gapBounds =
2477 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
2478 BOOST_REQUIRE(gapBounds != nullptr);
2479 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2480 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), rMin);
2481 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), rMax);
2482 BOOST_CHECK_CLOSE(gapVol->center()[eZ], -3 * hlZ, 1e-10);
2483
2484
2485 auto* originalFirstVol = volumes[1];
2486 BOOST_CHECK_EQUAL(originalFirstVol, vol1.get());
2487 auto originalFirstBounds = dynamic_cast<const CylinderVolumeBounds*>(
2488 &originalFirstVol->volumeBounds());
2489 BOOST_REQUIRE(originalFirstBounds != nullptr);
2490 BOOST_CHECK_EQUAL(
2491 originalFirstBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2492 BOOST_CHECK_EQUAL(originalFirstBounds->get(CylinderVolumeBounds::eMinR),
2493 rMin);
2494 BOOST_CHECK_EQUAL(originalFirstBounds->get(CylinderVolumeBounds::eMaxR),
2495 rMax);
2496 BOOST_CHECK_CLOSE(originalFirstVol->center()[eZ], -hlZ, 1e-10);
2497 }
2498
2499 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeRNegative) {
2500 double hlZ = 400_mm;
2501 double rMin1 = 100_mm;
2502 double rMax1 = 200_mm;
2503 double rMin2 = 200_mm;
2504 double rMax2 = 300_mm;
2505
2506
2507 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin1, rMax1, hlZ);
2508 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin2, rMax2, hlZ);
2509
2510 Transform3 transform = Transform3::Identity();
2511 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2512 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2513
2514 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2515
2516 CylinderVolumeStack cylStack(
2517 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Gap,
2518 {VolumeResizeStrategy::Expand, VolumeResizeStrategy::Gap}, *logger);
2519
2520
2521 auto newBounds = std::make_shared<CylinderVolumeBounds>(50_mm, rMax2, hlZ);
2522 cylStack.update(newBounds, std::nullopt, *logger);
2523
2524 auto* firstVol = volumes.front();
2525 BOOST_CHECK_EQUAL(firstVol, vol1.get());
2526 auto firstBounds =
2527 dynamic_cast<const CylinderVolumeBounds*>(&firstVol->volumeBounds());
2528 BOOST_REQUIRE(firstBounds != nullptr);
2529 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
2530 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eMaxR), rMax1);
2531 BOOST_CHECK_EQUAL(firstBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2532 BOOST_CHECK_CLOSE(firstVol->center()[eZ], 0, 1e-10);
2533
2534
2535 auto* lastVol = volumes.back();
2536 BOOST_CHECK_EQUAL(lastVol, vol2.get());
2537 auto lastBounds =
2538 dynamic_cast<const CylinderVolumeBounds*>(&lastVol->volumeBounds());
2539 BOOST_REQUIRE(lastBounds != nullptr);
2540 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMinR), rMin2);
2541 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eMaxR), rMax2);
2542 BOOST_CHECK_EQUAL(lastBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2543 BOOST_CHECK_CLOSE(lastVol->center()[eZ], 0, 1e-10);
2544
2545
2546 BOOST_CHECK_EQUAL(volumes.size(), 2);
2547 }
2548
2549 BOOST_AUTO_TEST_CASE(AsymmetricSingleSideResizeRNegativeFlipped) {
2550 double hlZ = 400_mm;
2551 double rMin1 = 100_mm;
2552 double rMax1 = 200_mm;
2553 double rMin2 = 200_mm;
2554 double rMax2 = 300_mm;
2555
2556
2557 auto bounds1 = std::make_shared<CylinderVolumeBounds>(rMin1, rMax1, hlZ);
2558 auto bounds2 = std::make_shared<CylinderVolumeBounds>(rMin2, rMax2, hlZ);
2559
2560 Transform3 transform = Transform3::Identity();
2561 auto vol1 = std::make_shared<Volume>(transform, bounds1);
2562 auto vol2 = std::make_shared<Volume>(transform, bounds2);
2563
2564 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
2565
2566 CylinderVolumeStack cylStack(
2567 volumes, AxisDirection::AxisR, VolumeAttachmentStrategy::Gap,
2568 {VolumeResizeStrategy::Gap, VolumeResizeStrategy::Expand}, *logger);
2569
2570
2571 auto newBounds = std::make_shared<CylinderVolumeBounds>(50_mm, rMax2, hlZ);
2572 cylStack.update(newBounds, std::nullopt, *logger);
2573
2574 BOOST_CHECK_EQUAL(volumes.size(), 3);
2575
2576
2577 auto* gapVol = volumes[0];
2578 auto gapBounds =
2579 dynamic_cast<const CylinderVolumeBounds*>(&gapVol->volumeBounds());
2580 BOOST_REQUIRE(gapBounds != nullptr);
2581 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2582 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMinR), 50_mm);
2583 BOOST_CHECK_EQUAL(gapBounds->get(CylinderVolumeBounds::eMaxR), rMin1);
2584 BOOST_CHECK_CLOSE(gapVol->center()[eZ], 0, 1e-10);
2585
2586
2587 auto* originalFirstVol = volumes[1];
2588 BOOST_CHECK_EQUAL(originalFirstVol, vol1.get());
2589 auto originalFirstBounds = dynamic_cast<const CylinderVolumeBounds*>(
2590 &originalFirstVol->volumeBounds());
2591 BOOST_REQUIRE(originalFirstBounds != nullptr);
2592 BOOST_CHECK_EQUAL(
2593 originalFirstBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2594 BOOST_CHECK_EQUAL(originalFirstBounds->get(CylinderVolumeBounds::eMinR),
2595 rMin1);
2596 BOOST_CHECK_EQUAL(originalFirstBounds->get(CylinderVolumeBounds::eMaxR),
2597 rMax1);
2598 BOOST_CHECK_CLOSE(originalFirstVol->center()[eZ], 0, 1e-10);
2599
2600
2601 auto* originalSecondVol = volumes[2];
2602 BOOST_CHECK_EQUAL(originalSecondVol, vol2.get());
2603 auto originalSecondBounds = dynamic_cast<const CylinderVolumeBounds*>(
2604 &originalSecondVol->volumeBounds());
2605 BOOST_REQUIRE(originalSecondBounds != nullptr);
2606 BOOST_CHECK_EQUAL(originalSecondBounds->get(CylinderVolumeBounds::eMinR),
2607 rMin2);
2608 BOOST_CHECK_EQUAL(originalSecondBounds->get(CylinderVolumeBounds::eMaxR),
2609 rMax2);
2610 BOOST_CHECK_EQUAL(
2611 originalSecondBounds->get(CylinderVolumeBounds::eHalfLengthZ), hlZ);
2612 BOOST_CHECK_CLOSE(originalSecondVol->center()[eZ], 0, 1e-10);
2613 }
2614
2615 BOOST_AUTO_TEST_SUITE_END()
2616
2617 }