File indexing completed on 2025-01-18 09:11:22
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/CylinderVolumeStack.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Common.hpp"
0013 #include "Acts/Definitions/Tolerance.hpp"
0014 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0015 #include "Acts/Utilities/BinningType.hpp"
0016 #include "Acts/Utilities/Logger.hpp"
0017
0018 #include <algorithm>
0019 #include <memory>
0020 #include <numbers>
0021 #include <sstream>
0022
0023 namespace Acts {
0024
0025 struct CylinderVolumeStack::VolumeTuple {
0026 Volume* volume{};
0027 const CylinderVolumeBounds* bounds{};
0028 std::shared_ptr<CylinderVolumeBounds> updatedBounds{};
0029 Transform3 localTransform = Transform3::Identity();
0030 Transform3 globalTransform = Transform3::Identity();
0031
0032 bool transformDirty = false;
0033
0034 VolumeTuple(Volume& volume_, const Transform3& groupTransform)
0035 : volume{&volume_},
0036 localTransform{groupTransform.inverse() * volume_.transform()},
0037 globalTransform{volume_.transform()} {
0038 bounds = dynamic_cast<const CylinderVolumeBounds*>(&volume_.volumeBounds());
0039 assert(bounds != nullptr);
0040 updatedBounds = std::make_shared<CylinderVolumeBounds>(*bounds);
0041 }
0042
0043 double midZ() const { return localTransform.translation()[eZ]; }
0044 double halfLengthZ() const {
0045 return updatedBounds->get(CylinderVolumeBounds::eHalfLengthZ);
0046 }
0047 double minZ() const { return midZ() - halfLengthZ(); }
0048 double maxZ() const { return midZ() + halfLengthZ(); }
0049
0050 double minR() const {
0051 return updatedBounds->get(CylinderVolumeBounds::eMinR);
0052 }
0053 double maxR() const {
0054 return updatedBounds->get(CylinderVolumeBounds::eMaxR);
0055 }
0056 double midR() const { return (minR() + maxR()) / 2.0; }
0057
0058 void set(std::initializer_list<
0059 std::pair<CylinderVolumeBounds::BoundValues, double>>
0060 keyValues) {
0061 updatedBounds->set(keyValues);
0062 }
0063
0064 void setLocalTransform(const Transform3& transform,
0065 const Transform3& groupTransform) {
0066 localTransform = transform;
0067 globalTransform = groupTransform * localTransform;
0068 transformDirty = true;
0069 }
0070
0071 void commit(const Logger& logger) {
0072
0073 auto copy = std::make_shared<CylinderVolumeBounds>(*updatedBounds);
0074
0075 std::optional<Transform3> transform = std::nullopt;
0076 if (transformDirty) {
0077 transform = globalTransform;
0078 }
0079
0080 volume->update(std::move(updatedBounds), transform, logger);
0081 bounds = copy.get();
0082 updatedBounds = std::move(copy);
0083 transformDirty = false;
0084 }
0085 };
0086
0087 CylinderVolumeStack::CylinderVolumeStack(std::vector<Volume*>& volumes,
0088 AxisDirection direction,
0089 AttachmentStrategy strategy,
0090 ResizeStrategy resizeStrategy,
0091 const Logger& logger)
0092 : Volume(initialVolume(volumes)),
0093 m_direction(direction),
0094 m_resizeStrategy(resizeStrategy),
0095 m_volumes(volumes) {
0096 initializeOuterVolume(direction, strategy, logger);
0097 }
0098
0099 Volume& CylinderVolumeStack::initialVolume(
0100 const std::vector<Volume*>& volumes) {
0101 if (volumes.empty()) {
0102 throw std::invalid_argument(
0103 "CylinderVolumeStack requires at least one volume");
0104 }
0105 return *volumes.front();
0106 }
0107
0108 void CylinderVolumeStack::initializeOuterVolume(AxisDirection direction,
0109 AttachmentStrategy strategy,
0110 const Logger& logger) {
0111 ACTS_DEBUG("Creating CylinderVolumeStack from "
0112 << m_volumes.size() << " volumes in direction "
0113 << axisDirectionName(direction));
0114 if (m_volumes.empty()) {
0115 throw std::invalid_argument(
0116 "CylinderVolumeStack requires at least one volume");
0117 }
0118
0119 if (direction != Acts::AxisDirection::AxisZ &&
0120 direction != Acts::AxisDirection::AxisR) {
0121 throw std::invalid_argument(axisDirectionName(direction) +
0122 " is not supported ");
0123 }
0124
0125
0126 m_groupTransform = m_volumes.front()->transform();
0127 ACTS_VERBOSE("Initial group transform is:\n" << m_groupTransform.matrix());
0128
0129 std::vector<VolumeTuple> volumeTuples;
0130 volumeTuples.reserve(m_volumes.size());
0131
0132 for (const auto& volume : m_volumes) {
0133 const auto* cylinderBounds =
0134 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0135 if (cylinderBounds == nullptr) {
0136 throw std::invalid_argument{
0137 "CylinderVolumeStack requires all volumes to "
0138 "have CylinderVolumeBounds"};
0139 }
0140
0141 checkNoPhiOrBevel(*cylinderBounds, logger);
0142
0143 volumeTuples.emplace_back(*volume, m_groupTransform);
0144 }
0145
0146 ACTS_DEBUG("*** Initial volume configuration:");
0147 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0148
0149 if (m_volumes.size() == 1) {
0150 ACTS_VERBOSE("Only one volume, returning");
0151 setTransform(m_volumes.front()->transform());
0152 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0153 &m_volumes.front()->volumeBounds());
0154 assert(cylBounds != nullptr && "Volume bounds are not cylinder bounds");
0155 Volume::update(std::make_shared<CylinderVolumeBounds>(*cylBounds),
0156 std::nullopt, logger);
0157 ACTS_VERBOSE("Transform is now: " << m_transform.matrix());
0158 return;
0159 }
0160
0161 ACTS_VERBOSE("Checking volume alignment");
0162 checkVolumeAlignment(volumeTuples, logger);
0163
0164 if (direction == Acts::AxisDirection::AxisZ) {
0165 ACTS_VERBOSE("Sorting by volume z position");
0166 std::ranges::sort(volumeTuples, {}, [](const auto& v) {
0167 return v.localTransform.translation()[eZ];
0168 });
0169
0170 ACTS_VERBOSE("Checking for overlaps and attaching volumes in z");
0171 std::vector<VolumeTuple> gapVolumes =
0172 checkOverlapAndAttachInZ(volumeTuples, strategy, logger);
0173
0174 ACTS_VERBOSE("Appending "
0175 << gapVolumes.size()
0176 << " gap volumes to the end of the volume vector");
0177 std::copy(gapVolumes.begin(), gapVolumes.end(),
0178 std::back_inserter(volumeTuples));
0179
0180 ACTS_VERBOSE("*** Volume configuration after z attachment:");
0181 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0182
0183 ACTS_VERBOSE("Synchronizing bounds in r");
0184 const auto [minR, maxR] = synchronizeRBounds(volumeTuples, logger);
0185
0186 for (auto& vt : volumeTuples) {
0187 ACTS_VERBOSE("Updated bounds for volume at z: "
0188 << vt.localTransform.translation()[eZ]);
0189 ACTS_VERBOSE(*vt.updatedBounds);
0190
0191 vt.commit(logger);
0192 }
0193
0194 ACTS_VERBOSE("*** Volume configuration after r synchronization:");
0195 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0196
0197 std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midZ(); });
0198
0199 m_volumes.clear();
0200 for (const auto& vt : volumeTuples) {
0201 m_volumes.push_back(vt.volume);
0202 }
0203
0204 ACTS_DEBUG("*** Volume configuration after final z sorting:");
0205 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0206
0207 double minZ = volumeTuples.front().minZ();
0208 double maxZ = volumeTuples.back().maxZ();
0209
0210 double midZ = (minZ + maxZ) / 2.0;
0211 double hlZ = (maxZ - minZ) / 2.0;
0212
0213 m_transform = m_groupTransform * Translation3{0, 0, midZ};
0214
0215 Volume::update(std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0216 std::nullopt, logger);
0217 ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0218 ACTS_DEBUG("Outer transform / new group transform is:\n"
0219 << m_transform.matrix());
0220
0221
0222
0223 m_groupTransform = m_transform;
0224
0225 } else if (direction == Acts::AxisDirection::AxisR) {
0226 ACTS_VERBOSE("Sorting by volume r middle point");
0227 std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midR(); });
0228
0229 ACTS_VERBOSE("Checking for overlaps and attaching volumes in r");
0230 std::vector<VolumeTuple> gapVolumes =
0231 checkOverlapAndAttachInR(volumeTuples, strategy, logger);
0232
0233 ACTS_VERBOSE("Appending "
0234 << gapVolumes.size()
0235 << " gap volumes to the end of the volume vector");
0236 std::copy(gapVolumes.begin(), gapVolumes.end(),
0237 std::back_inserter(volumeTuples));
0238
0239 ACTS_VERBOSE("*** Volume configuration after r attachment:");
0240 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0241
0242 ACTS_VERBOSE("Synchronizing bounds in z");
0243 const auto [minZ, maxZ] = synchronizeZBounds(volumeTuples, logger);
0244
0245 for (auto& vt : volumeTuples) {
0246 ACTS_VERBOSE("Updated bounds for volume at r: " << vt.midR());
0247 ACTS_VERBOSE(*vt.updatedBounds);
0248 vt.commit(logger);
0249 }
0250
0251 ACTS_VERBOSE("*** Volume configuration after z synchronization:");
0252 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0253
0254 std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midR(); });
0255
0256 m_volumes.clear();
0257 for (const auto& vt : volumeTuples) {
0258 m_volumes.push_back(vt.volume);
0259 }
0260
0261 ACTS_DEBUG("*** Volume configuration after final r sorting:");
0262 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0263
0264 double minR = volumeTuples.front().minR();
0265 double maxR = volumeTuples.back().maxR();
0266
0267 double midZ = (minZ + maxZ) / 2.0;
0268 double hlZ = (maxZ - minZ) / 2.0;
0269
0270 m_transform = m_groupTransform * Translation3{0, 0, midZ};
0271
0272 Volume::update(std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0273 std::nullopt, logger);
0274
0275 ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0276 ACTS_DEBUG("Outer transform is:\n" << m_transform.matrix());
0277
0278
0279
0280 m_groupTransform = m_transform;
0281
0282 } else {
0283 ACTS_ERROR("Binning in " << axisDirectionName(direction)
0284 << " is not supported");
0285 throw std::invalid_argument(axisDirectionName(direction) +
0286 " is not supported ");
0287 }
0288 }
0289
0290 void CylinderVolumeStack::overlapPrint(
0291 AxisDirection direction, const CylinderVolumeStack::VolumeTuple& a,
0292 const CylinderVolumeStack::VolumeTuple& b, const Logger& logger) {
0293 if (logger().doPrint(Acts::Logging::DEBUG)) {
0294 std::stringstream ss;
0295 ss << std::fixed;
0296 ss << std::setprecision(3);
0297 ss << std::setfill(' ');
0298
0299 int w = 9;
0300
0301 ACTS_VERBOSE("Checking overlap between");
0302 if (direction == AxisDirection::AxisZ) {
0303 ss << " - "
0304 << " z: [ " << std::setw(w) << a.minZ() << " <- " << std::setw(w)
0305 << a.midZ() << " -> " << std::setw(w) << a.maxZ() << " ]";
0306 ACTS_VERBOSE(ss.str());
0307
0308 ss.str("");
0309 ss << " - "
0310 << " z: [ " << std::setw(w) << b.minZ() << " <- " << std::setw(w)
0311 << b.midZ() << " -> " << std::setw(w) << b.maxZ() << " ]";
0312 ACTS_VERBOSE(ss.str());
0313 } else {
0314 ss << " - "
0315 << " r: [ " << std::setw(w) << a.minR() << " <-> " << std::setw(w)
0316 << a.maxR() << " ]";
0317 ACTS_VERBOSE(ss.str());
0318
0319 ss.str("");
0320 ss << " - "
0321 << " r: [ " << std::setw(w) << b.minR() << " <-> " << std::setw(w)
0322 << b.maxR() << " ]";
0323 ACTS_VERBOSE(ss.str());
0324 }
0325 }
0326 }
0327
0328 std::vector<CylinderVolumeStack::VolumeTuple>
0329 CylinderVolumeStack::checkOverlapAndAttachInZ(
0330 std::vector<VolumeTuple>& volumes,
0331 CylinderVolumeStack::AttachmentStrategy strategy, const Logger& logger) {
0332
0333 std::vector<VolumeTuple> gapVolumes;
0334 for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0335 std::size_t j = i + 1;
0336 auto& a = volumes.at(i);
0337 auto& b = volumes.at(j);
0338
0339 overlapPrint(AxisDirection::AxisZ, a, b, logger);
0340
0341 if (a.maxZ() > b.minZ()) {
0342 ACTS_ERROR(" -> Overlap in z");
0343 throw std::invalid_argument("Volumes overlap in z");
0344 } else {
0345 ACTS_VERBOSE(" -> No overlap");
0346 }
0347
0348 constexpr auto tolerance = s_onSurfaceTolerance;
0349 if (std::abs(a.maxZ() - b.minZ()) < tolerance) {
0350 ACTS_VERBOSE("No gap between volumes, no attachment needed");
0351 } else {
0352 double gapWidth = b.minZ() - a.maxZ();
0353 ACTS_VERBOSE("Gap width: " << gapWidth);
0354
0355 ACTS_VERBOSE("Synchronizing bounds in z with strategy: " << strategy);
0356 switch (strategy) {
0357 case AttachmentStrategy::Midpoint: {
0358 ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0359
0360 double aZMidNew = (a.minZ() + a.maxZ()) / 2.0 + gapWidth / 4.0;
0361 double aHlZNew = a.halfLengthZ() + gapWidth / 4.0;
0362 ACTS_VERBOSE(" - New halflength for first volume: " << aHlZNew);
0363 ACTS_VERBOSE(" - New bounds for first volume: ["
0364 << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0365 << (aZMidNew + aHlZNew) << "]");
0366
0367 assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0368 "Volume shrunk");
0369 assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0370
0371 double bZMidNew = (b.minZ() + b.maxZ()) / 2.0 - gapWidth / 4.0;
0372 double bHlZNew = b.halfLengthZ() + gapWidth / 4.0;
0373 ACTS_VERBOSE(" - New halflength for second volume: " << bHlZNew);
0374 ACTS_VERBOSE(" - New bounds for second volume: ["
0375 << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0376 << (bZMidNew + bHlZNew) << "]");
0377
0378 assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0379 assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0380 "Volume shrunk");
0381
0382 a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0383 m_groupTransform);
0384 a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0385
0386 b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0387 m_groupTransform);
0388 b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0389
0390 break;
0391 }
0392 case AttachmentStrategy::First: {
0393 ACTS_VERBOSE(" -> Strategy: Expand first volume");
0394 double aZMidNew = (a.minZ() + b.minZ()) / 2.0;
0395 double aHlZNew = (b.minZ() - a.minZ()) / 2.0;
0396 ACTS_VERBOSE(" - Gap width: " << gapWidth);
0397 ACTS_VERBOSE(" - New bounds for first volume: ["
0398 << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0399 << (aZMidNew + aHlZNew) << "]");
0400
0401 assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0402 "Volume shrunk");
0403 assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0404
0405 a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0406 m_groupTransform);
0407 a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0408
0409 break;
0410 }
0411 case AttachmentStrategy::Second: {
0412 ACTS_VERBOSE(" -> Strategy: Expand second volume");
0413 double bZMidNew = (a.maxZ() + b.maxZ()) / 2.0;
0414 double bHlZNew = (b.maxZ() - a.maxZ()) / 2.0;
0415 ACTS_VERBOSE(" - New halflength for second volume: " << bHlZNew);
0416 ACTS_VERBOSE(" - New bounds for second volume: ["
0417 << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0418 << (bZMidNew + bHlZNew) << "]");
0419
0420 assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0421 assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0422 "Volume shrunk");
0423
0424 b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0425 m_groupTransform);
0426 b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0427 break;
0428 }
0429 case AttachmentStrategy::Gap: {
0430 ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0431 double gapHlZ = (b.minZ() - a.maxZ()) / 2.0;
0432 double gapMidZ = (b.minZ() + a.maxZ()) / 2.0;
0433
0434 ACTS_VERBOSE(" - Gap half length: " << gapHlZ
0435 << " at z: " << gapMidZ);
0436
0437 double minR = std::min(a.minR(), b.minR());
0438 double maxR = std::max(a.maxR(), b.maxR());
0439
0440 Transform3 gapLocalTransform{Translation3{0, 0, gapMidZ}};
0441 Transform3 gapGlobalTransform = m_groupTransform * gapLocalTransform;
0442 auto gapBounds =
0443 std::make_shared<CylinderVolumeBounds>(minR, maxR, gapHlZ);
0444
0445 auto gap = addGapVolume(gapGlobalTransform, gapBounds);
0446 gapVolumes.emplace_back(*gap, m_groupTransform);
0447
0448 break;
0449 }
0450 default:
0451 ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0452 std::stringstream ss;
0453 ss << strategy;
0454 throw std::invalid_argument("Attachment strategy " + ss.str() +
0455 " not implemented");
0456 }
0457 }
0458 }
0459
0460 return gapVolumes;
0461 }
0462
0463 std::vector<CylinderVolumeStack::VolumeTuple>
0464 CylinderVolumeStack::checkOverlapAndAttachInR(
0465 std::vector<VolumeTuple>& volumes,
0466 CylinderVolumeStack::AttachmentStrategy strategy, const Logger& logger) {
0467 std::vector<VolumeTuple> gapVolumes;
0468 for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0469 std::size_t j = i + 1;
0470 auto& a = volumes.at(i);
0471 auto& b = volumes.at(j);
0472
0473 overlapPrint(AxisDirection::AxisR, a, b, logger);
0474
0475 if (a.maxR() > b.minR()) {
0476 ACTS_ERROR(" -> Overlap in r");
0477 throw std::invalid_argument("Volumes overlap in r");
0478 } else {
0479 ACTS_VERBOSE(" -> No overlap");
0480 }
0481
0482 constexpr auto tolerance = s_onSurfaceTolerance;
0483 if (std::abs(a.maxR() - b.minR()) < tolerance) {
0484 ACTS_VERBOSE("No gap between volumes, no attachment needed");
0485 } else {
0486 double gapWidth = b.minR() - a.maxR();
0487 ACTS_VERBOSE("Gap width: " << gapWidth);
0488
0489 ACTS_VERBOSE("Synchronizing bounds in r with strategy: " << strategy);
0490 switch (strategy) {
0491 case AttachmentStrategy::Midpoint: {
0492 ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0493
0494 a.set({{CylinderVolumeBounds::eMaxR, a.maxR() + gapWidth / 2.0}});
0495 b.set({{CylinderVolumeBounds::eMinR, b.minR() - gapWidth / 2.0}});
0496
0497 break;
0498 }
0499 case AttachmentStrategy::First: {
0500 ACTS_VERBOSE(" -> Strategy: Expand first volume");
0501
0502 a.set({{CylinderVolumeBounds::eMaxR, b.minR()}});
0503
0504 break;
0505 }
0506 case AttachmentStrategy::Second: {
0507 ACTS_VERBOSE(" -> Strategy: Expand second volume");
0508
0509 b.set({{CylinderVolumeBounds::eMinR, a.maxR()}});
0510
0511 break;
0512 }
0513 case AttachmentStrategy::Gap: {
0514 ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0515
0516 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0517 a.maxR(), b.minR(), a.halfLengthZ());
0518 auto gap = addGapVolume(m_groupTransform, gapBounds);
0519
0520 gapVolumes.emplace_back(*gap, m_groupTransform);
0521 break;
0522 }
0523 default:
0524 ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0525 std::stringstream ss;
0526 ss << strategy;
0527 throw std::invalid_argument("Attachment strategy " + ss.str() +
0528 " not implemented");
0529 }
0530 }
0531 }
0532
0533 return gapVolumes;
0534 }
0535
0536 void CylinderVolumeStack::printVolumeSequence(
0537 const std::vector<VolumeTuple>& volumes, const Logger& logger,
0538 Acts::Logging::Level lvl) {
0539 if (!logger().doPrint(lvl)) {
0540 return;
0541 }
0542 for (const auto& vt : volumes) {
0543 std::stringstream ss;
0544 ss << std::fixed;
0545 ss << std::setprecision(3);
0546 ss << std::setfill(' ');
0547
0548 int w = 9;
0549 ss << "z: [ " << std::setw(w) << vt.minZ() << " <- " << std::setw(w)
0550 << vt.midZ() << " -> " << std::setw(w) << vt.maxZ() << " ], r: [ "
0551 << std::setw(w) << vt.minR() << " <-> " << std::setw(w) << vt.maxR()
0552 << " ]";
0553
0554 logger().log(lvl, ss.str());
0555 }
0556 }
0557
0558 void CylinderVolumeStack::checkVolumeAlignment(
0559 const std::vector<VolumeTuple>& volumes, const Logger& logger) {
0560 std::size_t n = 0;
0561 for (auto& vt : volumes) {
0562 ACTS_VERBOSE("Checking volume #"
0563 << n << " at z: " << vt.localTransform.translation()[eZ]);
0564 ACTS_VERBOSE("- Local transform is:\n" << vt.localTransform.matrix());
0565
0566
0567 constexpr auto tolerance = s_onSurfaceTolerance;
0568
0569
0570
0571
0572 if (std::abs(vt.localTransform.rotation().col(eX)[eZ]) >= tolerance ||
0573 std::abs(vt.localTransform.rotation().col(eY)[eZ]) >= tolerance) {
0574 ACTS_ERROR("Volumes are not aligned: rotation is different");
0575 throw std::invalid_argument(
0576 "Volumes are not aligned: rotation is different");
0577 }
0578
0579 ACTS_VERBOSE(" -> Rotation is ok!");
0580
0581
0582 Vector2 translation = vt.localTransform.translation().head<2>();
0583 if (std::abs(translation[0]) > tolerance ||
0584 std::abs(translation[1]) > tolerance) {
0585 ACTS_ERROR("Volumes are not aligned: translation in x or y");
0586 throw std::invalid_argument(
0587 "Volumes are not aligned: translation in x or y");
0588 }
0589 ACTS_VERBOSE(" -> Translation in x/y is ok!");
0590
0591 n++;
0592 }
0593 }
0594
0595 std::pair<double, double> CylinderVolumeStack::synchronizeRBounds(
0596 std::vector<VolumeTuple>& volumes, const Logger& logger) {
0597 const double minR =
0598 std::min_element(volumes.begin(), volumes.end(),
0599 [](const auto& a, const auto& b) {
0600 return a.bounds->get(CylinderVolumeBounds::eMinR) <
0601 b.bounds->get(CylinderVolumeBounds::eMinR);
0602 })
0603 ->bounds->get(CylinderVolumeBounds::eMinR);
0604
0605 const double maxR =
0606 std::max_element(volumes.begin(), volumes.end(),
0607 [](const auto& a, const auto& b) {
0608 return a.bounds->get(CylinderVolumeBounds::eMaxR) <
0609 b.bounds->get(CylinderVolumeBounds::eMaxR);
0610 })
0611 ->bounds->get(CylinderVolumeBounds::eMaxR);
0612 ACTS_VERBOSE("Found: minR: " << minR << " maxR: " << maxR);
0613
0614 for (auto& vt : volumes) {
0615 vt.set({
0616 {CylinderVolumeBounds::eMinR, minR},
0617 {CylinderVolumeBounds::eMaxR, maxR},
0618 });
0619 }
0620
0621 return {minR, maxR};
0622 }
0623
0624 std::pair<double, double> CylinderVolumeStack::synchronizeZBounds(
0625 std::vector<VolumeTuple>& volumes, const Logger& logger) {
0626 const double minZ = std::min_element(volumes.begin(), volumes.end(),
0627 [](const auto& a, const auto& b) {
0628 return a.minZ() < b.minZ();
0629 })
0630 ->minZ();
0631
0632 const double maxZ = std::max_element(volumes.begin(), volumes.end(),
0633 [](const auto& a, const auto& b) {
0634 return a.maxZ() < b.maxZ();
0635 })
0636 ->maxZ();
0637 const double midZ = (minZ + maxZ) / 2.0;
0638 const double hlZ = (maxZ - minZ) / 2.0;
0639 ACTS_DEBUG("Found overall z bounds: [ " << minZ << " <- " << midZ << " -> "
0640 << maxZ << " ]");
0641 const Transform3 transform{Translation3{0, 0, midZ}};
0642
0643 for (auto& vt : volumes) {
0644 vt.set({{CylinderVolumeBounds::eHalfLengthZ, hlZ}});
0645 vt.setLocalTransform(transform, m_groupTransform);
0646 }
0647
0648 return {minZ, maxZ};
0649 }
0650
0651 void CylinderVolumeStack::update(std::shared_ptr<VolumeBounds> volbounds,
0652 std::optional<Transform3> transform,
0653 const Logger& logger) {
0654 ACTS_DEBUG(
0655 "Resizing CylinderVolumeStack with strategy: " << m_resizeStrategy);
0656 ACTS_DEBUG("Currently have " << m_volumes.size() << " children");
0657 ACTS_DEBUG(m_gaps.size() << " gaps");
0658 for (const auto& v : m_volumes) {
0659 ACTS_DEBUG(" - volume bounds: \n" << v->volumeBounds());
0660 ACTS_DEBUG(" transform: \n" << v->transform().matrix());
0661 }
0662
0663 ACTS_DEBUG("New bounds are: \n" << *volbounds);
0664
0665 auto cylBounds = std::dynamic_pointer_cast<CylinderVolumeBounds>(volbounds);
0666 if (cylBounds == nullptr) {
0667 throw std::invalid_argument(
0668 "CylinderVolumeStack requires CylinderVolumeBounds");
0669 }
0670
0671 if (cylBounds == nullptr) {
0672 throw std::invalid_argument("New bounds are nullptr");
0673 }
0674
0675 if (*cylBounds == volumeBounds()) {
0676 ACTS_VERBOSE("Bounds are the same, no resize needed");
0677 return;
0678 }
0679
0680 ACTS_VERBOSE("Group transform is:\n" << m_groupTransform.matrix());
0681 ACTS_VERBOSE("Current transform is:\n" << m_transform.matrix());
0682 if (transform.has_value()) {
0683 ACTS_VERBOSE("Input transform:\n" << transform.value().matrix());
0684 }
0685
0686 VolumeTuple oldVolume{*this, m_transform};
0687 VolumeTuple newVolume{*this, m_transform};
0688 newVolume.updatedBounds = std::make_shared<CylinderVolumeBounds>(*cylBounds);
0689 newVolume.globalTransform = transform.value_or(m_transform);
0690 newVolume.localTransform = m_transform.inverse() * newVolume.globalTransform;
0691
0692 if (!transform.has_value()) {
0693 ACTS_VERBOSE("Local transform does not change");
0694 } else {
0695 ACTS_VERBOSE("Local transform changes from\n"
0696 << m_groupTransform.matrix() << "\nto\n"
0697 << newVolume.localTransform.matrix());
0698 ACTS_VERBOSE("Checking transform consistency");
0699
0700 std::vector<VolumeTuple> volTemp{newVolume};
0701 checkVolumeAlignment(volTemp, logger);
0702 }
0703
0704 checkNoPhiOrBevel(*cylBounds, logger);
0705
0706 const double newMinR = newVolume.minR();
0707 const double newMaxR = newVolume.maxR();
0708 const double newMinZ = newVolume.minZ();
0709 const double newMaxZ = newVolume.maxZ();
0710 const double newMidZ = newVolume.midZ();
0711 const double newHlZ = newVolume.halfLengthZ();
0712
0713 const double oldMinR = oldVolume.minR();
0714 const double oldMaxR = oldVolume.maxR();
0715 const double oldMinZ = oldVolume.minZ();
0716 const double oldMaxZ = oldVolume.maxZ();
0717 const double oldMidZ = oldVolume.midZ();
0718 const double oldHlZ = oldVolume.halfLengthZ();
0719
0720 ACTS_VERBOSE("Previous bounds are: z: [ "
0721 << oldMinZ << " <- " << oldMidZ << " -> " << oldMaxZ << " ] ("
0722 << oldHlZ << "), r: [ " << oldMinR << " <-> " << oldMaxR
0723 << " ]");
0724 ACTS_VERBOSE("New bounds are: z: [ "
0725 << newMinZ << " <- " << newMidZ << " -> " << newMaxZ << " ] ("
0726 << newHlZ << "), r: [ " << newMinR << " <-> " << newMaxR
0727 << " ]");
0728
0729 constexpr auto tolerance = s_onSurfaceTolerance;
0730 auto same = [](double a, double b) { return std::abs(a - b) < tolerance; };
0731
0732 if (!same(newMinZ, oldMinZ) && newMinZ > oldMinZ) {
0733 ACTS_ERROR("Shrinking the stack size in z is not supported: "
0734 << newMinZ << " -> " << oldMinZ);
0735 throw std::invalid_argument("Shrinking the stack in z is not supported");
0736 }
0737
0738 if (!same(newMaxZ, oldMaxZ) && newMaxZ < oldMaxZ) {
0739 ACTS_ERROR("Shrinking the stack size in z is not supported: "
0740 << newMaxZ << " -> " << oldMaxZ);
0741 throw std::invalid_argument("Shrinking the stack in z is not supported");
0742 }
0743
0744 if (!same(newMinR, oldMinR) && newMinR > oldMinR) {
0745 ACTS_ERROR("Shrinking the stack size in r is not supported: "
0746 << newMinR << " -> " << oldMinR);
0747 throw std::invalid_argument("Shrinking the stack in r is not supported");
0748 }
0749
0750 if (!same(newMaxR, oldMaxR) && newMaxR < oldMaxR) {
0751 ACTS_ERROR("Shrinking the stack size in r is not supported: "
0752 << newMaxR << " -> " << oldMaxR);
0753 throw std::invalid_argument("Shrinking the stack is r in not supported");
0754 }
0755
0756 auto isGap = [this](const Volume* vol) {
0757 return std::ranges::any_of(
0758 m_gaps, [&](const auto& gap) { return vol == gap.get(); });
0759 };
0760
0761 if (m_direction == AxisDirection::AxisZ) {
0762 ACTS_VERBOSE("Stack direction is z");
0763
0764 std::vector<VolumeTuple> volumeTuples;
0765 volumeTuples.reserve(m_volumes.size());
0766 std::transform(m_volumes.begin(), m_volumes.end(),
0767 std::back_inserter(volumeTuples),
0768 [this](const auto& volume) {
0769 return VolumeTuple{*volume, m_groupTransform};
0770 });
0771
0772 ACTS_VERBOSE("*** Initial volume configuration:");
0773 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0774
0775 if (!same(newMinR, oldMinR) || !same(newMaxR, oldMaxR)) {
0776 ACTS_VERBOSE("Resize all volumes to new r bounds");
0777 for (auto& volume : volumeTuples) {
0778 volume.set({
0779 {CylinderVolumeBounds::eMinR, newMinR},
0780 {CylinderVolumeBounds::eMaxR, newMaxR},
0781 });
0782 }
0783 ACTS_VERBOSE("*** Volume configuration after r resizing:");
0784 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0785 } else {
0786 ACTS_VERBOSE("R bounds are the same, no r resize needed");
0787 }
0788
0789 if (same(newHlZ, oldHlZ)) {
0790 ACTS_VERBOSE("Halflength z is the same, no z resize needed");
0791 } else {
0792 if (m_resizeStrategy == ResizeStrategy::Expand) {
0793 if (newMinZ < oldMinZ) {
0794 ACTS_VERBOSE("Expanding first volume to new z bounds");
0795
0796 auto& first = volumeTuples.front();
0797 double newMinZFirst = newVolume.minZ();
0798 double newMidZFirst = (newMinZFirst + first.maxZ()) / 2.0;
0799 double newHlZFirst = (first.maxZ() - newMinZFirst) / 2.0;
0800
0801 ACTS_VERBOSE(" -> first z: [ "
0802 << newMinZFirst << " <- " << newMidZFirst << " -> "
0803 << first.maxZ() << " ] (hl: " << newHlZFirst << ")");
0804
0805 first.set({{CylinderVolumeBounds::eHalfLengthZ, newHlZFirst}});
0806 first.setLocalTransform(Transform3{Translation3{0, 0, newMidZFirst}},
0807 m_groupTransform);
0808 }
0809
0810 if (newMaxZ > oldMaxZ) {
0811 ACTS_VERBOSE("Expanding last volume to new z bounds");
0812
0813 auto& last = volumeTuples.back();
0814 double newMaxZLast = newVolume.maxZ();
0815 double newMidZLast = (last.minZ() + newMaxZLast) / 2.0;
0816 double newHlZLast = (newMaxZLast - last.minZ()) / 2.0;
0817
0818 ACTS_VERBOSE(" -> last z: [ " << last.minZ() << " <- " << newMidZLast
0819 << " -> " << newMaxZLast
0820 << " ] (hl: " << newHlZLast << ")");
0821
0822 last.set({{CylinderVolumeBounds::eHalfLengthZ, newHlZLast}});
0823 last.setLocalTransform(Transform3{Translation3{0, 0, newMidZLast}},
0824 m_groupTransform);
0825 }
0826 } else if (m_resizeStrategy == ResizeStrategy::Gap) {
0827 ACTS_VERBOSE("Creating gap volumes to fill the new z bounds");
0828
0829 auto printGapDimensions = [&](const VolumeTuple& gap,
0830 const std::string& prefix = "") {
0831 ACTS_VERBOSE(" -> gap" << prefix << ": [ " << gap.minZ() << " <- "
0832 << gap.midZ() << " -> " << gap.maxZ()
0833 << " ], r: [ " << gap.minR() << " <-> "
0834 << gap.maxR() << " ]");
0835 };
0836
0837 if (!same(newMinZ, oldMinZ) && newMinZ < oldMinZ) {
0838 double gap1MinZ = newVolume.minZ();
0839 double gap1MaxZ = oldVolume.minZ();
0840 double gap1HlZ = (gap1MaxZ - gap1MinZ) / 2.0;
0841 double gap1PZ = (gap1MaxZ + gap1MinZ) / 2.0;
0842
0843
0844 auto& candidate = volumeTuples.front();
0845 if (isGap(candidate.volume)) {
0846 ACTS_VERBOSE("~> Reusing existing gap volume at negative z");
0847
0848 gap1HlZ =
0849 candidate.bounds->get(CylinderVolumeBounds::eHalfLengthZ) +
0850 gap1HlZ;
0851 gap1MaxZ = gap1MinZ + gap1HlZ * 2;
0852 gap1PZ = (gap1MaxZ + gap1MinZ) / 2.0;
0853
0854 printGapDimensions(candidate, " before");
0855 auto gap1Bounds = std::make_shared<CylinderVolumeBounds>(
0856 newMinR, newMaxR, gap1HlZ);
0857 auto gap1Transform = m_groupTransform * Translation3{0, 0, gap1PZ};
0858 candidate.volume->update(std::move(gap1Bounds), gap1Transform);
0859 candidate = VolumeTuple{*candidate.volume, m_groupTransform};
0860 ACTS_VERBOSE("After:");
0861 printGapDimensions(candidate, " after ");
0862
0863 } else {
0864 ACTS_VERBOSE("~> Creating new gap volume at negative z");
0865 auto gap1Bounds = std::make_shared<CylinderVolumeBounds>(
0866 newMinR, newMaxR, gap1HlZ);
0867 auto gap1Transform = m_groupTransform * Translation3{0, 0, gap1PZ};
0868 auto gap1 = addGapVolume(gap1Transform, std::move(gap1Bounds));
0869 volumeTuples.insert(volumeTuples.begin(),
0870 VolumeTuple{*gap1, m_groupTransform});
0871 printGapDimensions(volumeTuples.front());
0872 }
0873 }
0874
0875 if (!same(newMaxZ, oldMaxZ) && newMaxZ > oldMaxZ) {
0876 double gap2MinZ = oldVolume.maxZ();
0877 double gap2MaxZ = newVolume.maxZ();
0878 double gap2HlZ = (gap2MaxZ - gap2MinZ) / 2.0;
0879 double gap2PZ = (gap2MaxZ + gap2MinZ) / 2.0;
0880
0881
0882 auto& candidate = volumeTuples.back();
0883 if (isGap(candidate.volume)) {
0884 ACTS_VERBOSE("~> Reusing existing gap volume at positive z");
0885
0886 gap2HlZ =
0887 candidate.bounds->get(CylinderVolumeBounds::eHalfLengthZ) +
0888 gap2HlZ;
0889 gap2MinZ = newVolume.maxZ() - gap2HlZ * 2;
0890 gap2PZ = (gap2MaxZ + gap2MinZ) / 2.0;
0891
0892 printGapDimensions(candidate, " before");
0893 auto gap2Bounds = std::make_shared<CylinderVolumeBounds>(
0894 newMinR, newMaxR, gap2HlZ);
0895 auto gap2Transform = m_groupTransform * Translation3{0, 0, gap2PZ};
0896
0897 candidate.volume->update(std::move(gap2Bounds), gap2Transform);
0898 candidate = VolumeTuple{*candidate.volume, m_groupTransform};
0899 printGapDimensions(candidate, " after ");
0900 } else {
0901 ACTS_VERBOSE("~> Creating new gap volume at positive z");
0902 auto gap2Bounds = std::make_shared<CylinderVolumeBounds>(
0903 newMinR, newMaxR, gap2HlZ);
0904 auto gap2Transform = m_groupTransform * Translation3{0, 0, gap2PZ};
0905 auto gap2 = addGapVolume(gap2Transform, std::move(gap2Bounds));
0906 volumeTuples.emplace_back(*gap2, m_groupTransform);
0907 printGapDimensions(volumeTuples.back());
0908 }
0909 }
0910 }
0911
0912 ACTS_VERBOSE("*** Volume configuration after z resizing:");
0913 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0914 }
0915
0916 ACTS_VERBOSE("Commit and update outer vector of volumes");
0917 m_volumes.clear();
0918 for (auto& vt : volumeTuples) {
0919 vt.commit(logger);
0920 m_volumes.push_back(vt.volume);
0921 }
0922
0923 } else if (m_direction == AxisDirection::AxisR) {
0924 ACTS_VERBOSE("Stack direction is r");
0925
0926 std::vector<VolumeTuple> volumeTuples;
0927 volumeTuples.reserve(m_volumes.size());
0928 std::transform(m_volumes.begin(), m_volumes.end(),
0929 std::back_inserter(volumeTuples),
0930 [this](const auto& volume) {
0931 return VolumeTuple{*volume, m_groupTransform};
0932 });
0933
0934 ACTS_VERBOSE("*** Initial volume configuration:");
0935 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0936
0937 ACTS_VERBOSE("Resize all volumes to new z bounds and update transforms");
0938 for (auto& volume : volumeTuples) {
0939 volume.set({
0940 {CylinderVolumeBounds::eHalfLengthZ, newHlZ},
0941 });
0942 volume.setLocalTransform(newVolume.localTransform, m_groupTransform);
0943 }
0944
0945 ACTS_VERBOSE("*** Volume configuration after z resizing:");
0946 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0947
0948 if (oldMinR == newMinR && oldMaxR == newMaxR) {
0949 ACTS_VERBOSE("Radii are the same, no r resize needed");
0950 } else {
0951 if (m_resizeStrategy == ResizeStrategy::Expand) {
0952 if (oldMinR > newMinR) {
0953
0954 auto& first = volumeTuples.front();
0955 first.set({
0956 {CylinderVolumeBounds::eMinR, newMinR},
0957 });
0958 ACTS_VERBOSE(" -> z: [ " << first.minZ() << " <- " << first.midZ()
0959 << " -> " << first.maxZ() << " ], r: [ "
0960 << first.minR() << " <-> " << first.maxR()
0961 << " ]");
0962 }
0963 if (oldMaxR < newMaxR) {
0964
0965 auto& last = volumeTuples.back();
0966 last.set({
0967 {CylinderVolumeBounds::eMaxR, newMaxR},
0968 });
0969 ACTS_VERBOSE(" -> z: [ " << last.minZ() << " <- " << last.midZ()
0970 << " -> " << last.maxZ() << " ], r: [ "
0971 << last.minR() << " <-> " << last.maxR()
0972 << " ]");
0973 }
0974 } else if (m_resizeStrategy == ResizeStrategy::Gap) {
0975 auto printGapDimensions = [&](const VolumeTuple& gap,
0976 const std::string& prefix = "") {
0977 ACTS_VERBOSE(" -> gap" << prefix << ": [ " << gap.minZ() << " <- "
0978 << gap.midZ() << " -> " << gap.maxZ()
0979 << " ], r: [ " << gap.minR() << " <-> "
0980 << gap.maxR() << " ]");
0981 };
0982
0983 if (oldMinR > newMinR) {
0984 auto& candidate = volumeTuples.front();
0985 if (isGap(candidate.volume)) {
0986 ACTS_VERBOSE("~> Reusing existing gap volume at inner r");
0987 auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
0988 candidate.volume->volumeBounds());
0989 printGapDimensions(candidate, " before");
0990 candidateCylBounds.set(CylinderVolumeBounds::eMinR, newMinR);
0991 candidate = VolumeTuple{*candidate.volume, m_groupTransform};
0992 printGapDimensions(candidate, " after ");
0993 } else {
0994 ACTS_VERBOSE("~> Creating new gap volume at inner r");
0995 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0996 newMinR, oldMinR, newHlZ);
0997 auto gapTransform = m_groupTransform;
0998 auto gapVolume = addGapVolume(gapTransform, gapBounds);
0999 volumeTuples.insert(volumeTuples.begin(),
1000 VolumeTuple{*gapVolume, m_groupTransform});
1001 auto gap = volumeTuples.front();
1002 printGapDimensions(gap);
1003 }
1004 }
1005 if (oldMaxR < newMaxR) {
1006 auto& candidate = volumeTuples.back();
1007 if (isGap(candidate.volume)) {
1008 ACTS_VERBOSE("~> Reusing existing gap volume at outer r");
1009 auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
1010 candidate.volume->volumeBounds());
1011 printGapDimensions(candidate, " before");
1012 candidateCylBounds.set(CylinderVolumeBounds::eMaxR, newMaxR);
1013 candidate = VolumeTuple{*candidate.volume, m_groupTransform};
1014 printGapDimensions(candidate, " after ");
1015 } else {
1016 ACTS_VERBOSE("~> Creating new gap volume at outer r");
1017 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
1018 oldMaxR, newMaxR, newHlZ);
1019 auto gapTransform = m_groupTransform;
1020 auto gapVolume = addGapVolume(gapTransform, gapBounds);
1021 volumeTuples.emplace_back(*gapVolume, m_groupTransform);
1022 auto gap = volumeTuples.back();
1023 printGapDimensions(gap);
1024 }
1025 }
1026 }
1027
1028 ACTS_VERBOSE("*** Volume configuration after r resizing:");
1029 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
1030 }
1031
1032 ACTS_VERBOSE("Commit and update outer vector of volumes");
1033 m_volumes.clear();
1034 for (auto& vt : volumeTuples) {
1035 vt.commit(logger);
1036 m_volumes.push_back(vt.volume);
1037 }
1038 }
1039
1040 m_transform = newVolume.globalTransform;
1041
1042 m_groupTransform = m_transform;
1043 Volume::update(std::move(cylBounds), std::nullopt, logger);
1044 }
1045
1046 void CylinderVolumeStack::checkNoPhiOrBevel(const CylinderVolumeBounds& bounds,
1047 const Logger& logger) {
1048 if (bounds.get(CylinderVolumeBounds::eHalfPhiSector) != std::numbers::pi) {
1049 ACTS_ERROR(
1050 "CylinderVolumeStack requires all volumes to have a full "
1051 "phi sector");
1052 throw std::invalid_argument(
1053 "CylinderVolumeStack requires all volumes to have a full phi sector");
1054 }
1055
1056 if (bounds.get(CylinderVolumeBounds::eAveragePhi) != 0.0) {
1057 ACTS_ERROR(
1058 "CylinderVolumeStack requires all volumes to have an average "
1059 "phi of 0");
1060 throw std::invalid_argument(
1061 "CylinderVolumeStack requires all volumes to have an average phi of "
1062 "0");
1063 }
1064
1065 if (bounds.get(CylinderVolumeBounds::eBevelMinZ) != 0.0) {
1066 ACTS_ERROR(
1067 "CylinderVolumeStack requires all volumes to have a bevel angle of "
1068 "0");
1069 throw std::invalid_argument(
1070 "CylinderVolumeStack requires all volumes to have a bevel angle of "
1071 "0");
1072 }
1073
1074 if (bounds.get(CylinderVolumeBounds::eBevelMaxZ) != 0.0) {
1075 ACTS_ERROR(
1076 "CylinderVolumeStack requires all volumes to have a bevel angle of "
1077 "0");
1078 throw std::invalid_argument(
1079 "CylinderVolumeStack requires all volumes to have a bevel angle of "
1080 "0");
1081 }
1082 }
1083
1084 std::shared_ptr<Volume> CylinderVolumeStack::addGapVolume(
1085 const Transform3& transform, const std::shared_ptr<VolumeBounds>& bounds) {
1086 auto gapVolume = std::make_shared<Volume>(transform, bounds);
1087 m_gaps.push_back(gapVolume);
1088 return gapVolume;
1089 }
1090
1091 const std::vector<std::shared_ptr<Volume>>& CylinderVolumeStack::gaps() const {
1092 return m_gaps;
1093 }
1094
1095 std::ostream& operator<<(std::ostream& os,
1096 CylinderVolumeStack::AttachmentStrategy strategy) {
1097 switch (strategy) {
1098 case CylinderVolumeStack::AttachmentStrategy::First:
1099 os << "First";
1100 break;
1101 case CylinderVolumeStack::AttachmentStrategy::Second:
1102 os << "Second";
1103 break;
1104 case CylinderVolumeStack::AttachmentStrategy::Midpoint:
1105 os << "Midpoint";
1106 break;
1107 case CylinderVolumeStack::AttachmentStrategy::Gap:
1108 os << "Gap";
1109 break;
1110 }
1111 return os;
1112 }
1113
1114 std::ostream& operator<<(std::ostream& os,
1115 CylinderVolumeStack::ResizeStrategy strategy) {
1116 switch (strategy) {
1117 case CylinderVolumeStack::ResizeStrategy::Expand:
1118 os << "Expand";
1119 break;
1120 case CylinderVolumeStack::ResizeStrategy::Gap:
1121 os << "Gap";
1122 break;
1123 }
1124 return os;
1125 }
1126
1127 }