File indexing completed on 2026-08-01 08:17:03
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 <sstream>
0020 #include <stdexcept>
0021
0022 namespace Acts {
0023
0024 void CylinderPortalShell::fill(TrackingVolume& volume) {
0025 for (Face face : {PositiveDisc, NegativeDisc, OuterCylinder, InnerCylinder,
0026 NegativePhiPlane, PositivePhiPlane}) {
0027 const auto& portalAtFace = portal(face);
0028 if (portalAtFace != nullptr) {
0029 portalAtFace->fill(volume);
0030 volume.addPortal(portalAtFace);
0031 }
0032 }
0033 }
0034
0035 SingleCylinderPortalShell::SingleCylinderPortalShell(
0036 const GeometryContext& gctx, 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 std::vector<OrientedSurface> orientedSurfaces =
0047 bounds.orientedSurfaces(m_volume->localToGlobalTransform(gctx));
0048
0049 auto handle = [&](Face face, std::size_t from) {
0050 const auto& source = orientedSurfaces.at(from);
0051 m_portals.at(toUnderlying(face)) =
0052 std::make_shared<Portal>(source.direction, source.surface, *m_volume);
0053 };
0054
0055 if (orientedSurfaces.size() == 6) {
0056
0057 handle(PositiveDisc, positiveFaceXY);
0058 handle(NegativeDisc, negativeFaceXY);
0059 handle(OuterCylinder, tubeOuterCover);
0060 handle(InnerCylinder, tubeInnerCover);
0061 handle(NegativePhiPlane, tubeSectorNegativePhi);
0062 handle(PositivePhiPlane, tubeSectorPositivePhi);
0063 } else if (orientedSurfaces.size() == 5) {
0064
0065 handle(PositiveDisc, positiveFaceXY);
0066 handle(NegativeDisc, negativeFaceXY);
0067 handle(OuterCylinder, tubeOuterCover);
0068
0069 handle(NegativePhiPlane, tubeSectorNegativePhi - 1);
0070 handle(PositivePhiPlane, tubeSectorPositivePhi - 1);
0071 } else if (orientedSurfaces.size() == 4) {
0072
0073 handle(PositiveDisc, positiveFaceXY);
0074 handle(NegativeDisc, negativeFaceXY);
0075 handle(OuterCylinder, tubeOuterCover);
0076 handle(InnerCylinder, tubeInnerCover);
0077 } else if (orientedSurfaces.size() == 3) {
0078
0079 handle(PositiveDisc, positiveFaceXY);
0080 handle(NegativeDisc, negativeFaceXY);
0081 handle(OuterCylinder, tubeOuterCover);
0082 } else {
0083 throw std::invalid_argument("Invalid number of oriented surfaces");
0084 }
0085 }
0086
0087 std::shared_ptr<Portal> SingleCylinderPortalShell::portal(Face face) {
0088 return m_portals.at(toUnderlying(face));
0089 }
0090
0091 void SingleCylinderPortalShell::setPortal(std::shared_ptr<Portal> portal,
0092 Face face) {
0093 if (portal == nullptr) {
0094 throw std::invalid_argument(
0095 "SingleCylinderPortalShell::setPortal: portal "
0096 "cannot be null");
0097 }
0098 if (!portal->isValid()) {
0099 throw std::invalid_argument(
0100 "SingleCylinderPortalShell::setPortal: portal is invalid");
0101 }
0102 m_portals.at(toUnderlying(face)) = std::move(portal);
0103 }
0104
0105 std::size_t SingleCylinderPortalShell::size() const {
0106 std::size_t count = 0;
0107 std::ranges::for_each(
0108 m_portals, [&count](const auto& portal) { count += portal ? 1 : 0; });
0109 return count;
0110 }
0111
0112 void SingleCylinderPortalShell::applyToVolume() {
0113 for (std::size_t i = 0; i < m_portals.size(); i++) {
0114 const auto& portal = m_portals.at(i);
0115 if (portal != nullptr) {
0116 if (!portal->isValid()) {
0117 std::stringstream ss;
0118 ss << static_cast<Face>(i);
0119 throw std::runtime_error{"Invalid portal found in shell at " +
0120 ss.str()};
0121 }
0122 m_volume->addPortal(portal);
0123 }
0124 }
0125 }
0126
0127 bool SingleCylinderPortalShell::isValid() const {
0128 return std::ranges::all_of(m_portals, [](const auto& portal) {
0129 return portal == nullptr || portal->isValid();
0130 });
0131 };
0132
0133 std::string SingleCylinderPortalShell::label() const {
0134 std::stringstream ss;
0135 ss << "CylinderShell(vol=" << m_volume->volumeName() << ")";
0136 return ss.str();
0137 }
0138
0139 CylinderStackPortalShell::CylinderStackPortalShell(
0140 const GeometryContext& gctx, std::vector<CylinderPortalShell*> shells,
0141 AxisDirection direction, const Logger& logger,
0142 PortalMaterialMergePolicy materialPolicy)
0143 : m_direction{direction}, m_shells{std::move(shells)} {
0144 ACTS_VERBOSE("Making cylinder stack shell in " << m_direction
0145 << " direction");
0146 if (std::ranges::any_of(m_shells,
0147 [](const auto* shell) { return shell == nullptr; })) {
0148 ACTS_ERROR("Invalid shell pointer");
0149 throw std::invalid_argument("Invalid shell pointer");
0150 }
0151
0152 ACTS_VERBOSE(" ~> " << label());
0153
0154 if (!std::ranges::all_of(
0155 m_shells, [](const auto* shell) { return shell->isValid(); })) {
0156 ACTS_ERROR("Invalid shell");
0157 throw std::invalid_argument("Invalid shell");
0158 }
0159
0160 auto merge = [&](Face face) {
0161 ACTS_VERBOSE("Merging portals at " << face);
0162 std::vector<std::shared_ptr<Portal>> portals;
0163 std::ranges::transform(m_shells, std::back_inserter(portals),
0164 [face](auto* shell) { return shell->portal(face); });
0165 ACTS_VERBOSE("Portals at " << face << ": " << portals.size());
0166
0167 std::shared_ptr<Portal> merged;
0168 try {
0169 merged = std::accumulate(
0170 std::next(portals.begin()), portals.end(), portals.front(),
0171 [&](const auto& aPortal,
0172 const auto& bPortal) -> std::shared_ptr<Portal> {
0173 if (aPortal == nullptr || bPortal == nullptr) {
0174 throw std::invalid_argument(
0175 "CylinderStackPortalShell: null portal in merge");
0176 }
0177
0178 return std::make_shared<Portal>(Portal::merge(
0179 gctx, *aPortal, *bPortal, direction, logger, materialPolicy));
0180 });
0181 } catch (const PortalMergingException& e) {
0182 std::stringstream ss;
0183 ss << "Failed to merge portals at face " << face << " while building "
0184 << label() << " (stacking in " << m_direction << "). Shells involved:";
0185 for (const auto* shell : m_shells) {
0186 ss << "\n - " << shell->label();
0187 }
0188 ss << "\nUnderlying error: " << e.what();
0189 ACTS_ERROR(ss.str());
0190 throw PortalMergingException{ss.str()};
0191 }
0192
0193 if (merged == nullptr) {
0194 throw std::runtime_error(
0195 "CylinderStackPortalShell: merged portal is null");
0196 }
0197 if (!merged->isValid()) {
0198 throw std::runtime_error(
0199 "CylinderStackPortalShell: merged portal is invalid");
0200 }
0201
0202
0203 for (auto& shell : m_shells) {
0204 shell->setPortal(merged, face);
0205 }
0206 };
0207
0208 auto fuse = [&](Face faceA, Face faceB) {
0209 for (std::size_t i = 1; i < m_shells.size(); i++) {
0210 auto& shellA = m_shells.at(i - 1);
0211 auto& shellB = m_shells.at(i);
0212 ACTS_VERBOSE("Fusing " << shellA->label() << " and " << shellB->label());
0213
0214 auto fused = std::make_shared<Portal>(Portal::fuse(
0215 gctx, *shellA->portal(faceA), *shellB->portal(faceB), logger));
0216
0217 if (fused == nullptr) {
0218 throw std::runtime_error(
0219 "CylinderStackPortalShell: fused portal is null");
0220 }
0221 if (!fused->isValid()) {
0222 throw std::runtime_error(
0223 "CylinderStackPortalShell: fused portal is invalid");
0224 }
0225
0226 shellA->setPortal(fused, faceA);
0227 shellB->setPortal(fused, faceB);
0228
0229 if (!shellA->isValid()) {
0230 throw std::runtime_error(
0231 "CylinderStackPortalShell: shell A is not valid after fusing");
0232 }
0233 if (!shellB->isValid()) {
0234 throw std::runtime_error(
0235 "CylinderStackPortalShell: shell B is not valid after fusing");
0236 }
0237 }
0238 };
0239
0240 if (direction == AxisDirection::AxisR) {
0241 m_hasInnerCylinder = (m_shells.front()->portal(InnerCylinder) != nullptr);
0242
0243 ACTS_VERBOSE("Merging portals at positive and negative discs");
0244 merge(PositiveDisc);
0245 merge(NegativeDisc);
0246
0247 ACTS_VERBOSE("Fusing portals at outer and inner cylinders");
0248 fuse(OuterCylinder, InnerCylinder);
0249
0250 } else if (direction == AxisDirection::AxisZ) {
0251 bool allHaveInnerCylinders = std::ranges::all_of(
0252 m_shells, [](const auto* shell) { return shell->size() == 4; });
0253
0254 bool noneHaveInnerCylinders = std::ranges::all_of(
0255 m_shells, [](const auto* shell) { return shell->size() == 3; });
0256
0257 if (!allHaveInnerCylinders && !noneHaveInnerCylinders) {
0258 ACTS_ERROR("Invalid inner cylinder configuration");
0259 throw std::invalid_argument("Invalid inner cylinder configuration");
0260 }
0261
0262 m_hasInnerCylinder = allHaveInnerCylinders;
0263
0264 ACTS_VERBOSE("Merging portals at outer cylinders");
0265 merge(OuterCylinder);
0266 if (!isValid()) {
0267 throw std::runtime_error(
0268 "CylinderStackPortalShell: shell is not valid after outer merging");
0269 }
0270
0271 if (m_hasInnerCylinder) {
0272 ACTS_VERBOSE("Merging portals at inner cylinders");
0273 merge(InnerCylinder);
0274 if (!isValid()) {
0275 throw std::runtime_error(
0276 "CylinderStackPortalShell: shell is not valid after inner merging");
0277 }
0278 }
0279
0280 ACTS_VERBOSE("Fusing portals at positive and negative discs");
0281 fuse(PositiveDisc, NegativeDisc);
0282 if (!isValid()) {
0283 throw std::runtime_error(
0284 "CylinderStackPortalShell: shell is not valid after disc fusing");
0285 }
0286
0287 } else {
0288 throw std::invalid_argument("Invalid direction");
0289 }
0290
0291 if (!isValid()) {
0292 throw std::runtime_error(
0293 "CylinderStackPortalShell: shell is not valid after construction");
0294 }
0295 }
0296
0297 std::size_t CylinderStackPortalShell::size() const {
0298 return m_hasInnerCylinder ? 4 : 3;
0299 }
0300
0301 std::vector<CylinderStackPortalShell::Face>
0302 CylinderStackPortalShell::mergedFaces(AxisDirection direction) {
0303 using enum CylinderVolumeBounds::Face;
0304 switch (direction) {
0305 case AxisDirection::AxisR:
0306
0307 return {PositiveDisc, NegativeDisc};
0308 case AxisDirection::AxisZ:
0309
0310 return {OuterCylinder, InnerCylinder};
0311 default:
0312 return {};
0313 }
0314 }
0315
0316 std::shared_ptr<Portal> CylinderStackPortalShell::portal(Face face) {
0317 if (m_direction == AxisDirection::AxisR) {
0318 switch (face) {
0319 case NegativeDisc:
0320 return m_shells.front()->portal(NegativeDisc);
0321 case PositiveDisc:
0322 return m_shells.front()->portal(PositiveDisc);
0323 case OuterCylinder:
0324 return m_shells.back()->portal(OuterCylinder);
0325 case InnerCylinder:
0326 return m_shells.front()->portal(InnerCylinder);
0327 case NegativePhiPlane:
0328 [[fallthrough]];
0329 case PositivePhiPlane:
0330 return nullptr;
0331 default:
0332 std::stringstream ss;
0333 ss << "Invalid face: " << face;
0334 throw std::invalid_argument(ss.str());
0335 }
0336
0337 } else {
0338 switch (face) {
0339 case NegativeDisc:
0340 return m_shells.front()->portal(NegativeDisc);
0341 case PositiveDisc:
0342 return m_shells.back()->portal(PositiveDisc);
0343 case OuterCylinder:
0344 [[fallthrough]];
0345 case InnerCylinder:
0346 return m_shells.front()->portal(face);
0347 case NegativePhiPlane:
0348 [[fallthrough]];
0349 case PositivePhiPlane:
0350 return nullptr;
0351 default:
0352 std::stringstream ss;
0353 ss << "Invalid face: " << face;
0354 throw std::invalid_argument(ss.str());
0355 }
0356 }
0357 }
0358
0359 void CylinderStackPortalShell::setPortal(std::shared_ptr<Portal> portal,
0360 Face face) {
0361 if (portal == nullptr) {
0362 throw std::invalid_argument(
0363 "CylinderStackPortalShell::setPortal: portal cannot be null");
0364 }
0365
0366 if (m_direction == AxisDirection::AxisR) {
0367 switch (face) {
0368 case NegativeDisc:
0369 [[fallthrough]];
0370 case PositiveDisc:
0371 for (auto* shell : m_shells) {
0372 shell->setPortal(portal, face);
0373 }
0374 break;
0375 case OuterCylinder:
0376 m_shells.back()->setPortal(std::move(portal), OuterCylinder);
0377 break;
0378 case InnerCylinder:
0379 if (!m_hasInnerCylinder) {
0380 throw std::invalid_argument("Inner cylinder not available");
0381 }
0382 m_shells.front()->setPortal(std::move(portal), InnerCylinder);
0383 break;
0384 default:
0385 std::stringstream ss;
0386 ss << "Invalid face: " << face;
0387 throw std::invalid_argument(ss.str());
0388 }
0389
0390 } else {
0391 switch (face) {
0392 case NegativeDisc:
0393 m_shells.front()->setPortal(std::move(portal), NegativeDisc);
0394 break;
0395 case PositiveDisc:
0396 m_shells.back()->setPortal(std::move(portal), PositiveDisc);
0397 break;
0398 case InnerCylinder:
0399 if (!m_hasInnerCylinder) {
0400 throw std::invalid_argument("Inner cylinder not available");
0401 }
0402 [[fallthrough]];
0403 case OuterCylinder:
0404 for (auto* shell : m_shells) {
0405 shell->setPortal(portal, face);
0406 }
0407 break;
0408 default:
0409 std::stringstream ss;
0410 ss << "Invalid face: " << face;
0411 throw std::invalid_argument(ss.str());
0412 }
0413 }
0414 }
0415
0416 bool CylinderStackPortalShell::isValid() const {
0417 return std::ranges::all_of(m_shells, [](const auto* shell) {
0418 if (shell == nullptr) {
0419 throw std::invalid_argument(
0420 "CylinderStackPortalShell::isValid: shell pointer is null");
0421 }
0422 return shell->isValid();
0423 });
0424 }
0425
0426 std::string CylinderStackPortalShell::label() const {
0427
0428
0429
0430
0431 std::stringstream ss;
0432 ss << "CylinderStackShell(dir=" << m_direction << ", " << m_shells.size()
0433 << " children)";
0434 return ss.str();
0435 }
0436
0437 std::ostream& operator<<(std::ostream& os, CylinderPortalShell::Face face) {
0438 switch (face) {
0439 using enum CylinderVolumeBounds::Face;
0440 case PositiveDisc:
0441 return os << "PositiveDisc";
0442 case NegativeDisc:
0443 return os << "NegativeDisc";
0444 case OuterCylinder:
0445 return os << "OuterCylinder";
0446 case InnerCylinder:
0447 return os << "InnerCylinder";
0448 case NegativePhiPlane:
0449 return os << "NegativePhiPlane";
0450 case PositivePhiPlane:
0451 return os << "PositivePhiPlane";
0452 default:
0453 return os << "Invalid face";
0454 }
0455 }
0456
0457 }