File indexing completed on 2026-09-23 08:18:54
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 #include "Acts/Utilities/StringHelpers.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 explicit VolumeTuple(const GeometryContext& gctx, Volume& volume_,
0035 const Transform3& groupTransform)
0036 : volume{&volume_},
0037 localTransform{groupTransform.inverse() *
0038 volume_.localToGlobalTransform(gctx)},
0039 globalTransform{volume_.localToGlobalTransform(gctx)} {
0040 bounds = dynamic_cast<const CylinderVolumeBounds*>(&volume_.volumeBounds());
0041 assert(bounds != nullptr);
0042 updatedBounds = std::make_shared<CylinderVolumeBounds>(*bounds);
0043 }
0044
0045 double midZ() const { return localTransform.translation()[eZ]; }
0046 double halfLengthZ() const {
0047 return updatedBounds->get(CylinderVolumeBounds::eHalfLengthZ);
0048 }
0049 double minZ() const { return midZ() - halfLengthZ(); }
0050 double maxZ() const { return midZ() + halfLengthZ(); }
0051
0052 double minR() const {
0053 return updatedBounds->get(CylinderVolumeBounds::eMinR);
0054 }
0055 double maxR() const {
0056 return updatedBounds->get(CylinderVolumeBounds::eMaxR);
0057 }
0058 double midR() const { return (minR() + maxR()) / 2.0; }
0059
0060 void set(std::initializer_list<
0061 std::pair<CylinderVolumeBounds::BoundValues, double>>
0062 keyValues) {
0063 updatedBounds->set(keyValues);
0064 }
0065
0066 void setLocalTransform(const Transform3& transform,
0067 const Transform3& groupTransform) {
0068 localTransform = transform;
0069 globalTransform = groupTransform * localTransform;
0070 transformDirty = true;
0071 }
0072
0073 void commit(const GeometryContext& gctx, const Logger& logger) {
0074
0075 auto copy = std::make_shared<CylinderVolumeBounds>(*updatedBounds);
0076
0077 std::optional<Transform3> transform = std::nullopt;
0078 if (transformDirty) {
0079 transform = globalTransform;
0080 }
0081
0082 volume->update(gctx, std::move(updatedBounds), transform, logger);
0083 bounds = copy.get();
0084 updatedBounds = std::move(copy);
0085 transformDirty = false;
0086 }
0087 };
0088
0089 CylinderVolumeStack::CylinderVolumeStack(const GeometryContext& gctx,
0090 std::vector<Volume*>& volumes,
0091 AxisDirection direction,
0092 VolumeAttachmentStrategy strategy,
0093 VolumeResizeStrategy resizeStrategy,
0094 const Logger& logger)
0095 : CylinderVolumeStack{
0096 gctx, volumes, direction, strategy, {resizeStrategy, resizeStrategy},
0097 logger} {}
0098
0099 CylinderVolumeStack::CylinderVolumeStack(
0100 const GeometryContext& gctx, std::vector<Volume*>& volumes,
0101 AxisDirection direction, VolumeAttachmentStrategy strategy,
0102 std::pair<VolumeResizeStrategy, VolumeResizeStrategy> resizeStrategies,
0103 const Logger& logger)
0104 : VolumeStack(volumes, direction,
0105 {resizeStrategies.first, resizeStrategies.second}) {
0106 initializeOuterVolume(gctx, direction, strategy, logger);
0107 }
0108
0109 void CylinderVolumeStack::initializeOuterVolume(
0110 const GeometryContext& gctx, AxisDirection direction,
0111 VolumeAttachmentStrategy strategy, const Logger& logger) {
0112 ACTS_DEBUG("Creating CylinderVolumeStack from "
0113 << m_volumes.size() << " volumes in direction "
0114 << axisDirectionName(direction));
0115 if (m_volumes.empty()) {
0116 throw std::invalid_argument(
0117 "CylinderVolumeStack requires at least one volume");
0118 }
0119
0120 if (direction != Acts::AxisDirection::AxisZ &&
0121 direction != Acts::AxisDirection::AxisR) {
0122 throw std::invalid_argument(axisDirectionName(direction) +
0123 " is not supported ");
0124 }
0125
0126
0127 m_groupTransform = m_volumes.front()->localToGlobalTransform(gctx);
0128 ACTS_VERBOSE("Initial group transform is:\n" << m_groupTransform.matrix());
0129
0130 std::vector<VolumeTuple> volumeTuples;
0131 volumeTuples.reserve(m_volumes.size());
0132
0133 for (const auto& volume : m_volumes) {
0134 const auto* cylinderBounds =
0135 dynamic_cast<const CylinderVolumeBounds*>(&volume->volumeBounds());
0136 if (cylinderBounds == nullptr) {
0137 throw std::invalid_argument{
0138 "CylinderVolumeStack requires all volumes to "
0139 "have CylinderVolumeBounds"};
0140 }
0141
0142 checkNoPhiSector(*cylinderBounds, logger);
0143
0144 volumeTuples.emplace_back(gctx, *volume, m_groupTransform);
0145 }
0146
0147 ACTS_DEBUG("*** Initial volume configuration:");
0148 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0149
0150 if (m_volumes.size() == 1) {
0151 ACTS_VERBOSE("Only one volume, returning");
0152 setTransform(m_volumes.front()->localToGlobalTransform(gctx));
0153 const auto* cylBounds = dynamic_cast<const CylinderVolumeBounds*>(
0154 &m_volumes.front()->volumeBounds());
0155 assert(cylBounds != nullptr && "Volume bounds are not cylinder bounds");
0156 Volume::update(gctx, std::make_shared<CylinderVolumeBounds>(*cylBounds),
0157 std::nullopt, logger);
0158 ACTS_VERBOSE(
0159 "Transform is now: " << toString(localToGlobalTransform(gctx)));
0160 return;
0161 }
0162
0163 ACTS_VERBOSE("Checking volume alignment");
0164 checkVolumeAlignment(volumeTuples, logger);
0165
0166 if (direction == Acts::AxisDirection::AxisZ) {
0167 ACTS_VERBOSE("Sorting by volume z position");
0168 std::ranges::sort(volumeTuples, {}, [](const auto& v) {
0169 return v.localTransform.translation()[eZ];
0170 });
0171
0172 ACTS_VERBOSE("Checking for overlaps and attaching volumes in z");
0173 std::vector<VolumeTuple> gapVolumes =
0174 checkOverlapAndAttachInZ(gctx, volumeTuples, strategy, logger);
0175
0176 ACTS_VERBOSE("Appending "
0177 << gapVolumes.size()
0178 << " gap volumes to the end of the volume vector");
0179 std::ranges::copy(gapVolumes, std::back_inserter(volumeTuples));
0180
0181 ACTS_VERBOSE("*** Volume configuration after z attachment:");
0182 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0183
0184 ACTS_VERBOSE("Synchronizing bounds in r");
0185 const auto [minR, maxR] = synchronizeRBounds(volumeTuples, logger);
0186
0187 for (auto& vt : volumeTuples) {
0188 ACTS_VERBOSE("Updated bounds for volume at z: "
0189 << vt.localTransform.translation()[eZ]);
0190 ACTS_VERBOSE(*vt.updatedBounds);
0191
0192 vt.commit(gctx, logger);
0193 }
0194
0195 ACTS_VERBOSE("*** Volume configuration after r synchronization:");
0196 printVolumeSequence(volumeTuples, logger, Acts::Logging::VERBOSE);
0197
0198 std::ranges::sort(volumeTuples, {}, [](const auto& v) { return v.midZ(); });
0199
0200 m_volumes.clear();
0201 for (const auto& vt : volumeTuples) {
0202 m_volumes.push_back(vt.volume);
0203 }
0204
0205 ACTS_DEBUG("*** Volume configuration after final z sorting:");
0206 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0207
0208 double minZ = volumeTuples.front().minZ();
0209 double maxZ = volumeTuples.back().maxZ();
0210
0211 double midZ = (minZ + maxZ) / 2.0;
0212 double hlZ = (maxZ - minZ) / 2.0;
0213
0214 Volume::update(gctx,
0215 std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0216 m_groupTransform * Translation3{0, 0, midZ}, logger);
0217 ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0218 ACTS_DEBUG("Outer transform / new group transform is:\n"
0219 << toString(localToGlobalTransform(gctx)));
0220
0221
0222
0223 m_groupTransform = localToGlobalTransform(gctx);
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(gctx, volumeTuples, strategy, logger);
0232
0233 ACTS_VERBOSE("Appending "
0234 << gapVolumes.size()
0235 << " gap volumes to the end of the volume vector");
0236 std::ranges::copy(gapVolumes, 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(gctx, 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 Volume::update(gctx,
0270 std::make_shared<CylinderVolumeBounds>(minR, maxR, hlZ),
0271 m_groupTransform * Translation3{0, 0, midZ}, logger);
0272
0273 ACTS_DEBUG("Outer bounds are:\n" << volumeBounds());
0274 ACTS_DEBUG("Outer transform is:\n"
0275 << toString(localToGlobalTransform(gctx)));
0276
0277
0278
0279 m_groupTransform = localToGlobalTransform(gctx);
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(const GeometryContext& gctx,
0327 std::vector<VolumeTuple>& volumes,
0328 VolumeAttachmentStrategy strategy,
0329 const Logger& logger) {
0330
0331 std::vector<VolumeTuple> gapVolumes;
0332 for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0333 std::size_t j = i + 1;
0334 auto& a = volumes.at(i);
0335 auto& b = volumes.at(j);
0336
0337 overlapPrint(AxisDirection::AxisZ, a, b, logger);
0338
0339 if (a.maxZ() > b.minZ()) {
0340 ACTS_ERROR(" -> Overlap in z");
0341 throw std::invalid_argument("Volumes overlap in z");
0342 } else {
0343 ACTS_VERBOSE(" -> No overlap");
0344 }
0345
0346 constexpr auto tolerance = s_onSurfaceTolerance;
0347 if (std::abs(a.maxZ() - b.minZ()) < tolerance) {
0348 ACTS_VERBOSE("No gap between volumes, no attachment needed");
0349 } else {
0350 double gapWidth = b.minZ() - a.maxZ();
0351 ACTS_VERBOSE("Gap width: " << gapWidth);
0352
0353 ACTS_VERBOSE("Synchronizing bounds in z with strategy: " << strategy);
0354 switch (strategy) {
0355 case VolumeAttachmentStrategy::Midpoint: {
0356 ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0357
0358 double aZMidNew = (a.minZ() + a.maxZ()) / 2.0 + gapWidth / 4.0;
0359 double aHlZNew = a.halfLengthZ() + gapWidth / 4.0;
0360 ACTS_VERBOSE(" - New halflength for first volume: " << aHlZNew);
0361 ACTS_VERBOSE(" - New bounds for first volume: ["
0362 << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0363 << (aZMidNew + aHlZNew) << "]");
0364
0365 assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0366 "Volume shrunk");
0367 assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0368
0369 double bZMidNew = (b.minZ() + b.maxZ()) / 2.0 - gapWidth / 4.0;
0370 double bHlZNew = b.halfLengthZ() + gapWidth / 4.0;
0371 ACTS_VERBOSE(" - New halflength for second volume: " << bHlZNew);
0372 ACTS_VERBOSE(" - New bounds for second volume: ["
0373 << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0374 << (bZMidNew + bHlZNew) << "]");
0375
0376 assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0377 assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0378 "Volume shrunk");
0379
0380 a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0381 m_groupTransform);
0382 a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0383
0384 b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0385 m_groupTransform);
0386 b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0387
0388 break;
0389 }
0390 case VolumeAttachmentStrategy::First: {
0391 ACTS_VERBOSE(" -> Strategy: Expand first volume");
0392 double aZMidNew = (a.minZ() + b.minZ()) / 2.0;
0393 double aHlZNew = (b.minZ() - a.minZ()) / 2.0;
0394 ACTS_VERBOSE(" - Gap width: " << gapWidth);
0395 ACTS_VERBOSE(" - New bounds for first volume: ["
0396 << (aZMidNew - aHlZNew) << " <- " << aZMidNew << " -> "
0397 << (aZMidNew + aHlZNew) << "]");
0398
0399 assert(std::abs(a.minZ() - (aZMidNew - aHlZNew)) < 1e-9 &&
0400 "Volume shrunk");
0401 assert(aHlZNew >= a.halfLengthZ() && "Volume shrunk");
0402
0403 a.setLocalTransform(Transform3{Translation3{0, 0, aZMidNew}},
0404 m_groupTransform);
0405 a.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, aHlZNew);
0406
0407 break;
0408 }
0409 case VolumeAttachmentStrategy::Second: {
0410 ACTS_VERBOSE(" -> Strategy: Expand second volume");
0411 double bZMidNew = (a.maxZ() + b.maxZ()) / 2.0;
0412 double bHlZNew = (b.maxZ() - a.maxZ()) / 2.0;
0413 ACTS_VERBOSE(" - New halflength for second volume: " << bHlZNew);
0414 ACTS_VERBOSE(" - New bounds for second volume: ["
0415 << (bZMidNew - bHlZNew) << " <- " << bZMidNew << " -> "
0416 << (bZMidNew + bHlZNew) << "]");
0417
0418 assert(bHlZNew >= b.halfLengthZ() && "Volume shrunk");
0419 assert(std::abs(b.maxZ() - (bZMidNew + bHlZNew)) < 1e-9 &&
0420 "Volume shrunk");
0421
0422 b.setLocalTransform(Transform3{Translation3{0, 0, bZMidNew}},
0423 m_groupTransform);
0424 b.updatedBounds->set(CylinderVolumeBounds::eHalfLengthZ, bHlZNew);
0425 break;
0426 }
0427 case VolumeAttachmentStrategy::Gap: {
0428 ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0429 double gapHlZ = (b.minZ() - a.maxZ()) / 2.0;
0430 double gapMidZ = (b.minZ() + a.maxZ()) / 2.0;
0431
0432 ACTS_VERBOSE(" - Gap half length: " << gapHlZ
0433 << " at z: " << gapMidZ);
0434
0435 double minR = std::min(a.minR(), b.minR());
0436 double maxR = std::max(a.maxR(), b.maxR());
0437
0438 Transform3 gapLocalTransform{Translation3{0, 0, gapMidZ}};
0439 Transform3 gapGlobalTransform = m_groupTransform * gapLocalTransform;
0440 auto gapBounds =
0441 std::make_shared<CylinderVolumeBounds>(minR, maxR, gapHlZ);
0442
0443 auto gap = addGapVolume(gapGlobalTransform, gapBounds);
0444 gapVolumes.emplace_back(gctx, *gap, m_groupTransform);
0445
0446 break;
0447 }
0448 default:
0449 ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0450 std::stringstream ss;
0451 ss << strategy;
0452 throw std::invalid_argument("Attachment strategy " + ss.str() +
0453 " not implemented");
0454 }
0455 }
0456 }
0457
0458 return gapVolumes;
0459 }
0460
0461 std::vector<CylinderVolumeStack::VolumeTuple>
0462 CylinderVolumeStack::checkOverlapAndAttachInR(const GeometryContext& gctx,
0463 std::vector<VolumeTuple>& volumes,
0464 VolumeAttachmentStrategy strategy,
0465 const Logger& logger) {
0466 std::vector<VolumeTuple> gapVolumes;
0467 for (std::size_t i = 0; i < volumes.size() - 1; i++) {
0468 std::size_t j = i + 1;
0469 auto& a = volumes.at(i);
0470 auto& b = volumes.at(j);
0471
0472 overlapPrint(AxisDirection::AxisR, a, b, logger);
0473
0474 if (a.maxR() > b.minR()) {
0475 ACTS_ERROR(" -> Overlap in r");
0476 throw std::invalid_argument("Volumes overlap in r");
0477 } else {
0478 ACTS_VERBOSE(" -> No overlap");
0479 }
0480
0481 constexpr auto tolerance = s_onSurfaceTolerance;
0482 if (std::abs(a.maxR() - b.minR()) < tolerance) {
0483 ACTS_VERBOSE("No gap between volumes, no attachment needed");
0484 } else {
0485 double gapWidth = b.minR() - a.maxR();
0486 ACTS_VERBOSE("Gap width: " << gapWidth);
0487
0488 ACTS_VERBOSE("Synchronizing bounds in r with strategy: " << strategy);
0489 switch (strategy) {
0490 case VolumeAttachmentStrategy::Midpoint: {
0491 ACTS_VERBOSE(" -> Strategy: Expand both volumes to midpoint");
0492
0493 a.set({{CylinderVolumeBounds::eMaxR, a.maxR() + gapWidth / 2.0}});
0494 b.set({{CylinderVolumeBounds::eMinR, b.minR() - gapWidth / 2.0}});
0495
0496 break;
0497 }
0498 case VolumeAttachmentStrategy::First: {
0499 ACTS_VERBOSE(" -> Strategy: Expand first volume");
0500
0501 a.set({{CylinderVolumeBounds::eMaxR, b.minR()}});
0502
0503 break;
0504 }
0505 case VolumeAttachmentStrategy::Second: {
0506 ACTS_VERBOSE(" -> Strategy: Expand second volume");
0507
0508 b.set({{CylinderVolumeBounds::eMinR, a.maxR()}});
0509
0510 break;
0511 }
0512 case VolumeAttachmentStrategy::Gap: {
0513 ACTS_VERBOSE(" -> Strategy: Create a gap volume");
0514
0515 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0516 a.maxR(), b.minR(), a.halfLengthZ());
0517 auto gap = addGapVolume(m_groupTransform, gapBounds);
0518
0519 gapVolumes.emplace_back(gctx, *gap, m_groupTransform);
0520 break;
0521 }
0522 default:
0523 ACTS_ERROR("Attachment strategy " << strategy << " not implemented");
0524 std::stringstream ss;
0525 ss << strategy;
0526 throw std::invalid_argument("Attachment strategy " + ss.str() +
0527 " not implemented");
0528 }
0529 }
0530 }
0531
0532 return gapVolumes;
0533 }
0534
0535 void CylinderVolumeStack::printVolumeSequence(
0536 const std::vector<VolumeTuple>& volumes, const Logger& logger,
0537 Acts::Logging::Level lvl) {
0538 if (!logger().doPrint(lvl)) {
0539 return;
0540 }
0541 for (const auto& vt : volumes) {
0542 std::stringstream ss;
0543 ss << std::fixed;
0544 ss << std::setprecision(3);
0545 ss << std::setfill(' ');
0546
0547 int w = 9;
0548 ss << "z: [ " << std::setw(w) << vt.minZ() << " <- " << std::setw(w)
0549 << vt.midZ() << " -> " << std::setw(w) << vt.maxZ() << " ], r: [ "
0550 << std::setw(w) << vt.minR() << " <-> " << std::setw(w) << vt.maxR()
0551 << " ]";
0552
0553 logger().log(lvl, ss.str());
0554 }
0555 }
0556
0557 void CylinderVolumeStack::checkVolumeAlignment(
0558 const std::vector<VolumeTuple>& volumes, const Logger& logger) {
0559 std::size_t n = 0;
0560 for (auto& vt : volumes) {
0561 ACTS_VERBOSE("Checking volume #"
0562 << n << " at z: " << vt.localTransform.translation()[eZ]);
0563 ACTS_VERBOSE("- Local transform is:\n" << vt.localTransform.matrix());
0564
0565
0566 constexpr auto tolerance = s_onSurfaceTolerance;
0567
0568
0569
0570
0571 if (std::abs(vt.localTransform.rotation().col(eX)[eZ]) >= tolerance ||
0572 std::abs(vt.localTransform.rotation().col(eY)[eZ]) >= tolerance) {
0573 ACTS_ERROR("Volumes are not aligned: rotation is different");
0574 throw std::invalid_argument(
0575 "Volumes are not aligned: rotation is different");
0576 }
0577
0578 ACTS_VERBOSE(" -> Rotation is ok!");
0579
0580
0581 Vector2 translation = vt.localTransform.translation().head<2>();
0582 if (std::abs(translation[0]) > tolerance ||
0583 std::abs(translation[1]) > tolerance) {
0584 ACTS_ERROR("Volumes are not aligned: translation in x or y");
0585 throw std::invalid_argument(
0586 "Volumes are not aligned: translation in x or y");
0587 }
0588 ACTS_VERBOSE(" -> Translation in x/y is ok!");
0589
0590 n++;
0591 }
0592 }
0593
0594 std::pair<double, double> CylinderVolumeStack::synchronizeRBounds(
0595 std::vector<VolumeTuple>& volumes, const Logger& logger) {
0596 const double minR =
0597 std::ranges::min_element(volumes, [](const auto& a, const auto& b) {
0598 return a.bounds->get(CylinderVolumeBounds::eMinR) <
0599 b.bounds->get(CylinderVolumeBounds::eMinR);
0600 })->bounds->get(CylinderVolumeBounds::eMinR);
0601
0602 const double maxR =
0603 std::ranges::max_element(volumes, [](const auto& a, const auto& b) {
0604 return a.bounds->get(CylinderVolumeBounds::eMaxR) <
0605 b.bounds->get(CylinderVolumeBounds::eMaxR);
0606 })->bounds->get(CylinderVolumeBounds::eMaxR);
0607 ACTS_VERBOSE("Found: minR: " << minR << " maxR: " << maxR);
0608
0609 for (auto& vt : volumes) {
0610 vt.set({
0611 {CylinderVolumeBounds::eMinR, minR},
0612 {CylinderVolumeBounds::eMaxR, maxR},
0613 });
0614 }
0615
0616 return {minR, maxR};
0617 }
0618
0619 std::pair<double, double> CylinderVolumeStack::synchronizeZBounds(
0620 std::vector<VolumeTuple>& volumes, const Logger& logger) {
0621 const double minZ =
0622 std::ranges::min_element(volumes, [](const auto& a, const auto& b) {
0623 return a.minZ() < b.minZ();
0624 })->minZ();
0625
0626 const double maxZ =
0627 std::ranges::max_element(volumes, [](const auto& a, const auto& b) {
0628 return a.maxZ() < b.maxZ();
0629 })->maxZ();
0630 const double midZ = (minZ + maxZ) / 2.0;
0631 const double hlZ = (maxZ - minZ) / 2.0;
0632 ACTS_DEBUG("Found overall z bounds: [ " << minZ << " <- " << midZ << " -> "
0633 << maxZ << " ]");
0634 const Transform3 transform{Translation3{0, 0, midZ}};
0635
0636 for (auto& vt : volumes) {
0637 vt.set({{CylinderVolumeBounds::eHalfLengthZ, hlZ}});
0638 vt.setLocalTransform(transform, m_groupTransform);
0639 }
0640
0641 return {minZ, maxZ};
0642 }
0643
0644 void CylinderVolumeStack::update(const GeometryContext& gctx,
0645 std::shared_ptr<VolumeBounds> volbounds,
0646 std::optional<Transform3> transform,
0647 const Logger& logger) {
0648 ACTS_DEBUG(
0649 "Resizing CylinderVolumeStack with strategy: " << m_resizeStrategies);
0650 ACTS_DEBUG("Currently have " << m_volumes.size() << " children");
0651 ACTS_DEBUG(m_gaps.size() << " gaps");
0652 for (const auto& v : m_volumes) {
0653 ACTS_DEBUG(" - volume bounds: \n" << v->volumeBounds());
0654 ACTS_DEBUG(" transform: \n"
0655 << v->localToGlobalTransform(gctx).matrix());
0656 }
0657
0658 ACTS_DEBUG("New bounds are: \n" << *volbounds);
0659
0660 auto cylBounds = std::dynamic_pointer_cast<CylinderVolumeBounds>(volbounds);
0661 if (cylBounds == nullptr) {
0662 throw std::invalid_argument(
0663 "CylinderVolumeStack requires CylinderVolumeBounds");
0664 }
0665
0666 if (cylBounds == nullptr) {
0667 throw std::invalid_argument("New bounds are nullptr");
0668 }
0669
0670 if (*cylBounds == volumeBounds()) {
0671 ACTS_VERBOSE("Bounds are the same, no resize needed");
0672 return;
0673 }
0674
0675 ACTS_VERBOSE("Group transform is:\n" << toString(m_groupTransform));
0676 ACTS_VERBOSE("Current transform is:\n"
0677 << toString(localToGlobalTransform(gctx)));
0678 if (transform.has_value()) {
0679 ACTS_VERBOSE("Input transform:\n" << toString(transform.value()));
0680 }
0681
0682 VolumeTuple oldVolume{gctx, *this, localToGlobalTransform(gctx)};
0683 VolumeTuple newVolume{gctx, *this, localToGlobalTransform(gctx)};
0684 newVolume.updatedBounds = std::make_shared<CylinderVolumeBounds>(*cylBounds);
0685 newVolume.globalTransform = transform.value_or(localToGlobalTransform(gctx));
0686 newVolume.localTransform =
0687 globalToLocalTransform(gctx) * 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 checkNoPhiSector(*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::ranges::transform(m_volumes, std::back_inserter(volumeTuples),
0766 [this, &gctx](const auto& volume) {
0767 return VolumeTuple{gctx, *volume,
0768 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 (!same(newMinZ, oldMinZ) && 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(gctx, std::move(gap1Bounds),
0839 gap1Transform);
0840 candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0841 ACTS_VERBOSE("After:");
0842 printGapDimensions(candidate, " after ");
0843
0844 } else {
0845 ACTS_VERBOSE("~> Creating new gap volume at negative z");
0846 auto gap1Bounds = std::make_shared<CylinderVolumeBounds>(
0847 newMinR, newMaxR, gap1HlZ);
0848 auto gap1Transform = m_groupTransform * Translation3{0, 0, gap1PZ};
0849 auto gap1 = addGapVolume(gap1Transform, std::move(gap1Bounds));
0850 volumeTuples.insert(volumeTuples.begin(),
0851 VolumeTuple{gctx, *gap1, m_groupTransform});
0852 printGapDimensions(volumeTuples.front());
0853 }
0854 }
0855 }
0856
0857 if (!same(newMaxZ, oldMaxZ) && newMaxZ > oldMaxZ) {
0858 if (secondStrategy == VolumeResizeStrategy::Expand) {
0859 ACTS_VERBOSE("Expanding last volume to new z bounds");
0860
0861 auto& last = volumeTuples.back();
0862 double newMaxZLast = newVolume.maxZ();
0863 double newMidZLast = (last.minZ() + newMaxZLast) / 2.0;
0864 double newHlZLast = (newMaxZLast - last.minZ()) / 2.0;
0865
0866 ACTS_VERBOSE(" -> last z: [ " << last.minZ() << " <- " << newMidZLast
0867 << " -> " << newMaxZLast
0868 << " ] (hl: " << newHlZLast << ")");
0869
0870 last.set({{CylinderVolumeBounds::eHalfLengthZ, newHlZLast}});
0871 last.setLocalTransform(Transform3{Translation3{0, 0, newMidZLast}},
0872 m_groupTransform);
0873 } else if (secondStrategy == VolumeResizeStrategy::Gap) {
0874 ACTS_VERBOSE("Creating gap volumes to fill the new z bounds at maxZ");
0875
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(gctx, std::move(gap2Bounds),
0898 gap2Transform);
0899 candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0900 printGapDimensions(candidate, " after ");
0901 } else {
0902 ACTS_VERBOSE("~> Creating new gap volume at positive z");
0903 auto gap2Bounds = std::make_shared<CylinderVolumeBounds>(
0904 newMinR, newMaxR, gap2HlZ);
0905 auto gap2Transform = m_groupTransform * Translation3{0, 0, gap2PZ};
0906 auto gap2 = addGapVolume(gap2Transform, std::move(gap2Bounds));
0907 volumeTuples.emplace_back(gctx, *gap2, m_groupTransform);
0908 printGapDimensions(volumeTuples.back());
0909 }
0910 }
0911 }
0912
0913 ACTS_VERBOSE("*** Volume configuration after z resizing:");
0914 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0915 }
0916
0917 ACTS_VERBOSE("Commit and update outer vector of volumes");
0918 m_volumes.clear();
0919 for (auto& vt : volumeTuples) {
0920 vt.commit(gctx, logger);
0921 m_volumes.push_back(vt.volume);
0922 }
0923
0924 } else if (m_direction == AxisDirection::AxisR) {
0925 ACTS_VERBOSE("Stack direction is r");
0926
0927 std::vector<VolumeTuple> volumeTuples;
0928 volumeTuples.reserve(m_volumes.size());
0929 std::ranges::transform(m_volumes, std::back_inserter(volumeTuples),
0930 [this, &gctx](const auto& volume) {
0931 return VolumeTuple{gctx, *volume,
0932 m_groupTransform};
0933 });
0934
0935 ACTS_VERBOSE("*** Initial volume configuration:");
0936 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0937
0938 ACTS_VERBOSE("Resize all volumes to new z bounds and update transforms");
0939 for (auto& volume : volumeTuples) {
0940 volume.set({
0941 {CylinderVolumeBounds::eHalfLengthZ, newHlZ},
0942 });
0943 volume.setLocalTransform(newVolume.localTransform, m_groupTransform);
0944 }
0945
0946 ACTS_VERBOSE("*** Volume configuration after z resizing:");
0947 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
0948
0949 if (same(oldMinR, newMinR) && same(oldMaxR, newMaxR)) {
0950 ACTS_VERBOSE("Radii are the same, no r resize needed");
0951 } else {
0952 auto printGapDimensions = [&](const VolumeTuple& gap,
0953 const std::string& prefix = "") {
0954 ACTS_VERBOSE(" -> gap" << prefix << ": [ " << gap.minZ() << " <- "
0955 << gap.midZ() << " -> " << gap.maxZ()
0956 << " ], r: [ " << gap.minR() << " <-> "
0957 << gap.maxR() << " ]");
0958 };
0959
0960 if (!same(oldMinR, newMinR) && oldMinR > newMinR) {
0961 if (firstStrategy == VolumeResizeStrategy::Expand) {
0962
0963 auto& first = volumeTuples.front();
0964 first.set({
0965 {CylinderVolumeBounds::eMinR, newMinR},
0966 });
0967 ACTS_VERBOSE(" -> z: [ " << first.minZ() << " <- " << first.midZ()
0968 << " -> " << first.maxZ() << " ], r: [ "
0969 << first.minR() << " <-> " << first.maxR()
0970 << " ]");
0971 } else if (firstStrategy == VolumeResizeStrategy::Gap) {
0972 auto& candidate = volumeTuples.front();
0973 if (isGap(candidate.volume)) {
0974 ACTS_VERBOSE("~> Reusing existing gap volume at inner r");
0975 auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
0976 candidate.volume->volumeBounds());
0977 printGapDimensions(candidate, " before");
0978 candidateCylBounds.set(CylinderVolumeBounds::eMinR, newMinR);
0979 candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
0980 printGapDimensions(candidate, " after ");
0981 } else {
0982 ACTS_VERBOSE("~> Creating new gap volume at inner r");
0983 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
0984 newMinR, oldMinR, newHlZ);
0985 auto gapTransform = newVolume.globalTransform;
0986 auto gapVolume = addGapVolume(gapTransform, gapBounds);
0987 volumeTuples.insert(
0988 volumeTuples.begin(),
0989 VolumeTuple{gctx, *gapVolume, m_groupTransform});
0990 auto gap = volumeTuples.front();
0991 printGapDimensions(gap);
0992 }
0993 }
0994 }
0995
0996 if (!same(oldMaxR, newMaxR) && oldMaxR < newMaxR) {
0997 if (secondStrategy == VolumeResizeStrategy::Expand) {
0998
0999 auto& last = volumeTuples.back();
1000 last.set({
1001 {CylinderVolumeBounds::eMaxR, newMaxR},
1002 });
1003 ACTS_VERBOSE(" -> z: [ " << last.minZ() << " <- " << last.midZ()
1004 << " -> " << last.maxZ() << " ], r: [ "
1005 << last.minR() << " <-> " << last.maxR()
1006 << " ]");
1007 } else if (secondStrategy == VolumeResizeStrategy::Gap) {
1008 auto& candidate = volumeTuples.back();
1009 if (isGap(candidate.volume)) {
1010 ACTS_VERBOSE("~> Reusing existing gap volume at outer r");
1011 auto& candidateCylBounds = dynamic_cast<CylinderVolumeBounds&>(
1012 candidate.volume->volumeBounds());
1013 printGapDimensions(candidate, " before");
1014 candidateCylBounds.set(CylinderVolumeBounds::eMaxR, newMaxR);
1015 candidate = VolumeTuple{gctx, *candidate.volume, m_groupTransform};
1016 printGapDimensions(candidate, " after ");
1017 } else {
1018 ACTS_VERBOSE("~> Creating new gap volume at outer r");
1019 auto gapBounds = std::make_shared<CylinderVolumeBounds>(
1020 oldMaxR, newMaxR, newHlZ);
1021 auto gapTransform = newVolume.globalTransform;
1022 auto gapVolume = addGapVolume(gapTransform, gapBounds);
1023 volumeTuples.emplace_back(gctx, *gapVolume,
1024 newVolume.globalTransform);
1025 auto gap = volumeTuples.back();
1026 printGapDimensions(gap);
1027 }
1028 }
1029 }
1030
1031 ACTS_VERBOSE("*** Volume configuration after r resizing:");
1032 printVolumeSequence(volumeTuples, logger, Acts::Logging::DEBUG);
1033 }
1034
1035 ACTS_VERBOSE("Commit and update outer vector of volumes");
1036 m_volumes.clear();
1037 for (auto& vt : volumeTuples) {
1038 vt.commit(gctx, logger);
1039 m_volumes.push_back(vt.volume);
1040 }
1041 }
1042
1043 Volume::update(gctx, std::move(cylBounds), newVolume.globalTransform, logger);
1044
1045 m_groupTransform = localToGlobalTransform(gctx);
1046 }
1047
1048 void CylinderVolumeStack::checkNoPhiSector(const CylinderVolumeBounds& bounds,
1049 const Logger& logger) {
1050 if (bounds.get(CylinderVolumeBounds::eHalfPhiSector) != std::numbers::pi) {
1051 ACTS_ERROR(
1052 "CylinderVolumeStack requires all volumes to have a full "
1053 "phi sector");
1054 throw std::invalid_argument(
1055 "CylinderVolumeStack requires all volumes to have a full phi sector");
1056 }
1057
1058 if (bounds.get(CylinderVolumeBounds::eAveragePhi) != 0.0) {
1059 ACTS_ERROR(
1060 "CylinderVolumeStack requires all volumes to have an average "
1061 "phi of 0");
1062 throw std::invalid_argument(
1063 "CylinderVolumeStack requires all volumes to have an average phi of "
1064 "0");
1065 }
1066 }
1067
1068 }