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