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