File indexing completed on 2026-07-16 08:07:47
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/CylinderPortalShell.hpp"
0010
0011 #include "Acts/Geometry/BoundarySurfaceFace.hpp"
0012 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0013 #include "Acts/Geometry/Portal.hpp"
0014 #include "Acts/Geometry/PortalLinkBase.hpp"
0015 #include "Acts/Geometry/TrackingVolume.hpp"
0016
0017 #include <algorithm>
0018 #include <numeric>
0019 #include <stdexcept>
0020
0021 #include <boost/algorithm/string/join.hpp>
0022
0023 namespace Acts {
0024
0025 void CylinderPortalShell::fill(TrackingVolume& volume) {
0026 for (Face face : {PositiveDisc, NegativeDisc, OuterCylinder, InnerCylinder,
0027 NegativePhiPlane, PositivePhiPlane}) {
0028 const auto& portalAtFace = portal(face);
0029 if (portalAtFace != nullptr) {
0030 portalAtFace->fill(volume);
0031 volume.addPortal(portalAtFace);
0032 }
0033 }
0034 }
0035
0036 SingleCylinderPortalShell::SingleCylinderPortalShell(TrackingVolume& volume)
0037 : m_volume{&volume} {
0038 if (m_volume->volumeBounds().type() != VolumeBounds::BoundsType::eCylinder) {
0039 throw std::invalid_argument(
0040 "CylinderPortalShell: Invalid volume bounds type");
0041 }
0042
0043 const auto& bounds =
0044 dynamic_cast<const CylinderVolumeBounds&>(m_volume->volumeBounds());
0045
0046 ACTS_PUSH_IGNORE_DEPRECATED()
0047 std::vector<OrientedSurface> orientedSurfaces =
0048 bounds.orientedSurfaces(m_volume->transform());
0049 ACTS_POP_IGNORE_DEPRECATED()
0050
0051 auto handle = [&](Face face, std::size_t from) {
0052 const auto& source = orientedSurfaces.at(from);
0053 m_portals.at(toUnderlying(face)) =
0054 std::make_shared<Portal>(source.direction, source.surface, *m_volume);
0055 };
0056
0057 if (orientedSurfaces.size() == 6) {
0058
0059 handle(PositiveDisc, positiveFaceXY);
0060 handle(NegativeDisc, negativeFaceXY);
0061 handle(OuterCylinder, tubeOuterCover);
0062 handle(InnerCylinder, tubeInnerCover);
0063 handle(NegativePhiPlane, tubeSectorNegativePhi);
0064 handle(PositivePhiPlane, tubeSectorPositivePhi);
0065 } else if (orientedSurfaces.size() == 5) {
0066
0067 handle(PositiveDisc, positiveFaceXY);
0068 handle(NegativeDisc, negativeFaceXY);
0069 handle(OuterCylinder, tubeOuterCover);
0070
0071 handle(NegativePhiPlane, tubeSectorNegativePhi - 1);
0072 handle(PositivePhiPlane, tubeSectorPositivePhi - 1);
0073 } else if (orientedSurfaces.size() == 4) {
0074
0075 handle(PositiveDisc, positiveFaceXY);
0076 handle(NegativeDisc, negativeFaceXY);
0077 handle(OuterCylinder, tubeOuterCover);
0078 handle(InnerCylinder, tubeInnerCover);
0079 } else if (orientedSurfaces.size() == 3) {
0080
0081 handle(PositiveDisc, positiveFaceXY);
0082 handle(NegativeDisc, negativeFaceXY);
0083 handle(OuterCylinder, tubeOuterCover);
0084 } else {
0085 throw std::invalid_argument("Invalid number of oriented surfaces");
0086 }
0087 }
0088
0089 std::shared_ptr<Portal> SingleCylinderPortalShell::portal(Face face) {
0090 return m_portals.at(toUnderlying(face));
0091 }
0092
0093 void SingleCylinderPortalShell::setPortal(std::shared_ptr<Portal> portal,
0094 Face face) {
0095 assert(portal != nullptr);
0096 assert(portal->isValid());
0097 m_portals.at(toUnderlying(face)) = std::move(portal);
0098 }
0099
0100 std::size_t SingleCylinderPortalShell::size() const {
0101 std::size_t count = 0;
0102 std::ranges::for_each(
0103 m_portals, [&count](const auto& portal) { count += portal ? 1 : 0; });
0104 return count;
0105 }
0106
0107 void SingleCylinderPortalShell::applyToVolume() {
0108 for (std::size_t i = 0; i < m_portals.size(); i++) {
0109 const auto& portal = m_portals.at(i);
0110 if (portal != nullptr) {
0111 if (!portal->isValid()) {
0112 std::stringstream ss;
0113 ss << static_cast<Face>(i);
0114 throw std::runtime_error{"Invalid portal found in shell at " +
0115 ss.str()};
0116 }
0117 m_volume->addPortal(portal);
0118 }
0119 }
0120 }
0121
0122 bool SingleCylinderPortalShell::isValid() const {
0123 return std::ranges::all_of(m_portals, [](const auto& portal) {
0124 return portal == nullptr || portal->isValid();
0125 });
0126 };
0127
0128 std::string SingleCylinderPortalShell::label() const {
0129 std::stringstream ss;
0130 ss << "CylinderShell(vol=" << m_volume->volumeName() << ")";
0131 return ss.str();
0132 }
0133
0134 CylinderStackPortalShell::CylinderStackPortalShell(
0135 const GeometryContext& gctx, std::vector<CylinderPortalShell*> shells,
0136 AxisDirection direction, const Logger& logger)
0137 : m_direction{direction}, m_shells{std::move(shells)} {
0138 ACTS_VERBOSE("Making cylinder stack shell in " << m_direction
0139 << " direction");
0140 if (std::ranges::any_of(m_shells,
0141 [](const auto* shell) { return shell == nullptr; })) {
0142 ACTS_ERROR("Invalid shell pointer");
0143 throw std::invalid_argument("Invalid shell pointer");
0144 }
0145
0146 ACTS_VERBOSE(" ~> " << label());
0147
0148 if (!std::ranges::all_of(
0149 m_shells, [](const auto* shell) { return shell->isValid(); })) {
0150 ACTS_ERROR("Invalid shell");
0151 throw std::invalid_argument("Invalid shell");
0152 }
0153
0154 auto merge = [&](Face face) {
0155 std::vector<std::shared_ptr<Portal>> portals;
0156 std::ranges::transform(m_shells, std::back_inserter(portals),
0157 [face](auto* shell) { return shell->portal(face); });
0158
0159 auto merged = std::accumulate(
0160 std::next(portals.begin()), portals.end(), portals.front(),
0161 [&](const auto& aPortal,
0162 const auto& bPortal) -> std::shared_ptr<Portal> {
0163 assert(aPortal != nullptr);
0164 assert(bPortal != nullptr);
0165
0166 return std::make_shared<Portal>(
0167 Portal::merge(gctx, *aPortal, *bPortal, direction, logger));
0168 });
0169
0170 assert(merged != nullptr);
0171 assert(merged->isValid());
0172
0173
0174 for (auto& shell : m_shells) {
0175 shell->setPortal(merged, face);
0176 }
0177 };
0178
0179 auto fuse = [&](Face faceA, Face faceB) {
0180 for (std::size_t i = 1; i < m_shells.size(); i++) {
0181 auto& shellA = m_shells.at(i - 1);
0182 auto& shellB = m_shells.at(i);
0183 ACTS_VERBOSE("Fusing " << shellA->label() << " and " << shellB->label());
0184
0185 auto fused = std::make_shared<Portal>(Portal::fuse(
0186 gctx, *shellA->portal(faceA), *shellB->portal(faceB), logger));
0187
0188 assert(fused != nullptr && "Invalid fused portal");
0189 assert(fused->isValid() && "Fused portal is invalid");
0190
0191 shellA->setPortal(fused, faceA);
0192 shellB->setPortal(fused, faceB);
0193
0194 assert(shellA->isValid() && "Shell A is not valid after fusing");
0195 assert(shellB->isValid() && "Shell B is not valid after fusing");
0196 }
0197 };
0198
0199 if (direction == AxisDirection::AxisR) {
0200 ACTS_VERBOSE("Merging portals at positive and negative discs");
0201 merge(PositiveDisc);
0202 merge(NegativeDisc);
0203
0204 ACTS_VERBOSE("Fusing portals at outer and inner cylinders");
0205 fuse(OuterCylinder, InnerCylinder);
0206
0207 } else if (direction == AxisDirection::AxisZ) {
0208 bool allHaveInnerCylinders = std::ranges::all_of(
0209 m_shells, [](const auto* shell) { return shell->size() == 4; });
0210
0211 bool noneHaveInnerCylinders = std::ranges::all_of(
0212 m_shells, [](const auto* shell) { return shell->size() == 3; });
0213
0214 if (!allHaveInnerCylinders && !noneHaveInnerCylinders) {
0215 ACTS_ERROR("Invalid inner cylinder configuration");
0216 throw std::invalid_argument("Invalid inner cylinder configuration");
0217 }
0218
0219 m_hasInnerCylinder = allHaveInnerCylinders;
0220
0221 ACTS_VERBOSE("Merging portals at outer cylinders");
0222 merge(OuterCylinder);
0223 assert(isValid() && "Shell is not valid after outer merging");
0224
0225 if (m_hasInnerCylinder) {
0226 ACTS_VERBOSE("Merging portals at inner cylinders");
0227 merge(InnerCylinder);
0228 assert(isValid() && "Shell is not valid after inner merging");
0229 }
0230
0231 ACTS_VERBOSE("Fusing portals at positive and negative discs");
0232 fuse(PositiveDisc, NegativeDisc);
0233 assert(isValid() && "Shell is not valid after disc fusing");
0234
0235 } else {
0236 throw std::invalid_argument("Invalid direction");
0237 }
0238
0239 assert(isValid() && "Shell is not valid after construction");
0240 }
0241
0242 std::size_t CylinderStackPortalShell::size() const {
0243 return m_hasInnerCylinder ? 4 : 3;
0244 }
0245
0246 std::shared_ptr<Portal> CylinderStackPortalShell::portal(Face face) {
0247 if (m_direction == AxisDirection::AxisR) {
0248 switch (face) {
0249 case NegativeDisc:
0250 return m_shells.front()->portal(NegativeDisc);
0251 case PositiveDisc:
0252 return m_shells.front()->portal(PositiveDisc);
0253 case OuterCylinder:
0254 return m_shells.back()->portal(OuterCylinder);
0255 case InnerCylinder:
0256 return m_shells.front()->portal(InnerCylinder);
0257 case NegativePhiPlane:
0258 [[fallthrough]];
0259 case PositivePhiPlane:
0260 return nullptr;
0261 default:
0262 std::stringstream ss;
0263 ss << "Invalid face: " << face;
0264 throw std::invalid_argument(ss.str());
0265 }
0266
0267 } else {
0268 switch (face) {
0269 case NegativeDisc:
0270 return m_shells.front()->portal(NegativeDisc);
0271 case PositiveDisc:
0272 return m_shells.back()->portal(PositiveDisc);
0273 case OuterCylinder:
0274 [[fallthrough]];
0275 case InnerCylinder:
0276 return m_shells.front()->portal(face);
0277 case NegativePhiPlane:
0278 [[fallthrough]];
0279 case PositivePhiPlane:
0280 return nullptr;
0281 default:
0282 std::stringstream ss;
0283 ss << "Invalid face: " << face;
0284 throw std::invalid_argument(ss.str());
0285 }
0286 }
0287 }
0288
0289 void CylinderStackPortalShell::setPortal(std::shared_ptr<Portal> portal,
0290 Face face) {
0291 assert(portal != nullptr);
0292
0293 if (m_direction == AxisDirection::AxisR) {
0294 switch (face) {
0295 case NegativeDisc:
0296 [[fallthrough]];
0297 case PositiveDisc:
0298 for (auto* shell : m_shells) {
0299 shell->setPortal(portal, face);
0300 }
0301 break;
0302 case OuterCylinder:
0303 m_shells.back()->setPortal(std::move(portal), OuterCylinder);
0304 break;
0305 case InnerCylinder:
0306 if (!m_hasInnerCylinder) {
0307 throw std::invalid_argument("Inner cylinder not available");
0308 }
0309 m_shells.front()->setPortal(std::move(portal), InnerCylinder);
0310 break;
0311 default:
0312 std::stringstream ss;
0313 ss << "Invalid face: " << face;
0314 throw std::invalid_argument(ss.str());
0315 }
0316
0317 } else {
0318 switch (face) {
0319 case NegativeDisc:
0320 m_shells.front()->setPortal(std::move(portal), NegativeDisc);
0321 break;
0322 case PositiveDisc:
0323 m_shells.back()->setPortal(std::move(portal), PositiveDisc);
0324 break;
0325 case InnerCylinder:
0326 if (!m_hasInnerCylinder) {
0327 throw std::invalid_argument("Inner cylinder not available");
0328 }
0329 [[fallthrough]];
0330 case OuterCylinder:
0331 for (auto* shell : m_shells) {
0332 shell->setPortal(portal, face);
0333 }
0334 break;
0335 default:
0336 std::stringstream ss;
0337 ss << "Invalid face: " << face;
0338 throw std::invalid_argument(ss.str());
0339 }
0340 }
0341 }
0342
0343 bool CylinderStackPortalShell::isValid() const {
0344 return std::ranges::all_of(m_shells, [](const auto* shell) {
0345 assert(shell != nullptr);
0346 return shell->isValid();
0347 });
0348 }
0349
0350 std::string CylinderStackPortalShell::label() const {
0351 std::stringstream ss;
0352 ss << "CylinderStackShell(dir=" << m_direction << ", children=";
0353
0354 std::vector<std::string> labels;
0355 std::ranges::transform(m_shells, std::back_inserter(labels),
0356 [](const auto* shell) { return shell->label(); });
0357 ss << boost::algorithm::join(labels, "|");
0358 ss << ")";
0359 return ss.str();
0360 }
0361
0362 std::ostream& operator<<(std::ostream& os, CylinderPortalShell::Face face) {
0363 switch (face) {
0364 using enum CylinderVolumeBounds::Face;
0365 case PositiveDisc:
0366 return os << "PositiveDisc";
0367 case NegativeDisc:
0368 return os << "NegativeDisc";
0369 case OuterCylinder:
0370 return os << "OuterCylinder";
0371 case InnerCylinder:
0372 return os << "InnerCylinder";
0373 case NegativePhiPlane:
0374 return os << "NegativePhiPlane";
0375 case PositivePhiPlane:
0376 return os << "PositivePhiPlane";
0377 default:
0378 return os << "Invalid face";
0379 }
0380 }
0381
0382 }