Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-01 08:17:03

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
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     // Fully equipped cylinder
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     // Phi sector but no inner cylinder (rMin == 0)
0065     handle(PositiveDisc, positiveFaceXY);
0066     handle(NegativeDisc, negativeFaceXY);
0067     handle(OuterCylinder, tubeOuterCover);
0068     // Skip inner tube cover, requires offsetting
0069     handle(NegativePhiPlane, tubeSectorNegativePhi - 1);
0070     handle(PositivePhiPlane, tubeSectorPositivePhi - 1);
0071   } else if (orientedSurfaces.size() == 4) {
0072     // No phi sector but rMin > 0
0073     handle(PositiveDisc, positiveFaceXY);
0074     handle(NegativeDisc, negativeFaceXY);
0075     handle(OuterCylinder, tubeOuterCover);
0076     handle(InnerCylinder, tubeInnerCover);
0077   } else if (orientedSurfaces.size() == 3) {
0078     // No phi sector and rMin == 0
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     // reset merged portal on all shells
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       // Discs are merged, cylinders are fused
0307       return {PositiveDisc, NegativeDisc};
0308     case AxisDirection::AxisZ:
0309       // Cylinders are merged, discs are fused
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   // Deliberately shallow: only describe this shell, not the whole subtree.
0428   // Expanding child labels recursively makes error messages for non-trivial
0429   // geometries unreadable. The immediate children are reported separately where
0430   // relevant (e.g. in merge-failure messages).
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 }  // namespace Acts