File indexing completed on 2025-07-03 07:52:20
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Detector/detail/CylindricalDetectorHelper.hpp"
0010
0011 #include "Acts/Definitions/Direction.hpp"
0012 #include "Acts/Definitions/Tolerance.hpp"
0013 #include "Acts/Detector/DetectorVolume.hpp"
0014 #include "Acts/Detector/Portal.hpp"
0015 #include "Acts/Detector/detail/DetectorVolumeConsistency.hpp"
0016 #include "Acts/Detector/detail/PortalHelper.hpp"
0017 #include "Acts/Geometry/CutoutCylinderVolumeBounds.hpp"
0018 #include "Acts/Surfaces/CylinderBounds.hpp"
0019 #include "Acts/Surfaces/CylinderSurface.hpp"
0020 #include "Acts/Surfaces/DiscSurface.hpp"
0021 #include "Acts/Surfaces/PlanarBounds.hpp"
0022 #include "Acts/Surfaces/PlaneSurface.hpp"
0023 #include "Acts/Surfaces/RadialBounds.hpp"
0024 #include "Acts/Surfaces/RectangleBounds.hpp"
0025 #include "Acts/Surfaces/Surface.hpp"
0026 #include "Acts/Surfaces/SurfaceBounds.hpp"
0027 #include "Acts/Utilities/Axis.hpp"
0028 #include "Acts/Utilities/BinningType.hpp"
0029 #include "Acts/Utilities/Enumerate.hpp"
0030 #include "Acts/Utilities/Grid.hpp"
0031 #include "Acts/Utilities/Helpers.hpp"
0032 #include "Acts/Utilities/StringHelpers.hpp"
0033
0034 #include <algorithm>
0035 #include <cmath>
0036 #include <cstddef>
0037 #include <iterator>
0038 #include <map>
0039 #include <numbers>
0040 #include <ostream>
0041 #include <ranges>
0042 #include <stdexcept>
0043 #include <string>
0044 #include <tuple>
0045 #include <utility>
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073 namespace {
0074
0075
0076
0077
0078
0079
0080
0081
0082
0083 Acts::Experimental::PortalReplacement createDiscReplacement(
0084 const Acts::Transform3& transform, const std::vector<double>& rBoundaries,
0085 const std::vector<double>& phiBoundaries, unsigned int index,
0086 Acts::Direction dir) {
0087
0088 Acts::AxisDirection stitchValue = phiBoundaries.size() == 2u
0089 ? Acts::AxisDirection::AxisR
0090 : Acts::AxisDirection::AxisPhi;
0091
0092 auto [minRit, maxRit] = std::ranges::minmax_element(rBoundaries);
0093 auto [sectorPhi, avgPhi] = Acts::range_medium(phiBoundaries);
0094
0095
0096 auto bounds = std::make_unique<Acts::RadialBounds>(*minRit, *maxRit,
0097 0.5 * sectorPhi, avgPhi);
0098
0099 auto surface = Acts::Surface::makeShared<Acts::DiscSurface>(
0100 transform, std::move(bounds));
0101
0102 const auto& stitchBoundaries =
0103 (stitchValue == Acts::AxisDirection::AxisR) ? rBoundaries : phiBoundaries;
0104 return Acts::Experimental::PortalReplacement(
0105 std::make_shared<Acts::Experimental::Portal>(surface), index, dir,
0106 stitchBoundaries, stitchValue);
0107 }
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119 Acts::Experimental::PortalReplacement createCylinderReplacement(
0120 const Acts::Transform3& transform, double r,
0121 const std::vector<double>& zBoundaries,
0122 const std::vector<double>& phiBoundaries, unsigned int index,
0123 Acts::Direction dir) {
0124
0125 Acts::AxisDirection stitchValue = phiBoundaries.size() == 2u
0126 ? Acts::AxisDirection::AxisZ
0127 : Acts::AxisDirection::AxisPhi;
0128 auto [lengthZ, medZ] = Acts::range_medium(zBoundaries);
0129 auto [sectorPhi, avgPhi] = Acts::range_medium(phiBoundaries);
0130
0131
0132 auto bounds = std::make_unique<Acts::CylinderBounds>(r, 0.5 * lengthZ,
0133 0.5 * sectorPhi, avgPhi);
0134
0135 auto surface = Acts::Surface::makeShared<Acts::CylinderSurface>(
0136 transform, std::move(bounds));
0137
0138
0139 const auto& stitchBoundaries =
0140 (stitchValue == Acts::AxisDirection::AxisZ) ? zBoundaries : phiBoundaries;
0141 return Acts::Experimental::PortalReplacement(
0142 std::make_shared<Acts::Experimental::Portal>(surface), index, dir,
0143 stitchBoundaries, stitchValue);
0144 }
0145
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155
0156 Acts::Experimental::PortalReplacement createSectorReplacement(
0157 const Acts::GeometryContext& gctx, const Acts::Vector3& volumeCenter,
0158 const Acts::Surface& refSurface, const std::vector<double>& boundaries,
0159 Acts::AxisDirection binning, unsigned int index, Acts::Direction dir) {
0160
0161 const auto& refTransform = refSurface.transform(gctx);
0162 auto refRotation = refTransform.rotation();
0163
0164 const auto& boundValues = refSurface.bounds().values();
0165 std::unique_ptr<Acts::PlanarBounds> bounds = nullptr;
0166
0167
0168 Acts::Transform3 transform = Acts::Transform3::Identity();
0169 if (binning == Acts::AxisDirection::AxisR) {
0170
0171 auto [range, medium] = Acts::range_medium(boundaries);
0172
0173
0174
0175 Acts::Vector3 pCenter = volumeCenter + medium * refRotation.col(1u);
0176 transform.pretranslate(pCenter);
0177
0178 double halfX =
0179 0.5 * (boundValues[Acts::RectangleBounds::BoundValues::eMaxX] -
0180 boundValues[Acts::RectangleBounds::BoundValues::eMinX]);
0181
0182 bounds = std::make_unique<Acts::RectangleBounds>(halfX, 0.5 * range);
0183 } else if (binning == Acts::AxisDirection::AxisZ) {
0184
0185 auto [range, medium] = Acts::range_medium(boundaries);
0186
0187 const auto& surfaceCenter = refSurface.center(gctx);
0188 Acts::Vector3 centerDiffs = (surfaceCenter - volumeCenter);
0189 double centerR = centerDiffs.dot(refRotation.col(2));
0190
0191 Acts::Vector3 pCenter = volumeCenter + centerR * refRotation.col(2);
0192 transform.pretranslate(pCenter);
0193
0194 double halfY =
0195 0.5 * (boundValues[Acts::RectangleBounds::BoundValues::eMaxY] -
0196 boundValues[Acts::RectangleBounds::BoundValues::eMinY]);
0197 bounds = std::make_unique<Acts::RectangleBounds>(0.5 * range, halfY);
0198 }
0199
0200 transform.prerotate(refRotation);
0201
0202 auto surface = Acts::Surface::makeShared<Acts::PlaneSurface>(
0203 transform, std::move(bounds));
0204
0205 Acts::Experimental::PortalReplacement pRep = {
0206 std::make_shared<Acts::Experimental::Portal>(surface), index, dir,
0207 boundaries, binning};
0208 return pRep;
0209 }
0210
0211
0212
0213
0214
0215
0216
0217 void checkVolumes(
0218 const Acts::GeometryContext& gctx,
0219 const std::vector<std::shared_ptr<Acts::Experimental::DetectorVolume>>&
0220 volumes) {
0221
0222
0223 std::string message = "CylindricalDetectorHelper: ";
0224 if (volumes.size() < 2u) {
0225 message += std::string("not enough volume given (") +
0226 std::to_string(volumes.size());
0227 message += std::string(" ), when required >=2.");
0228 throw std::invalid_argument(message.c_str());
0229 }
0230
0231 for (auto [iv, v] : Acts::enumerate(volumes)) {
0232
0233 if (v == nullptr) {
0234 message += "nullptr detector instead of volume " + std::to_string(iv);
0235 throw std::invalid_argument(message.c_str());
0236 }
0237
0238 if (v->volumeBounds().type() != Acts::VolumeBounds::BoundsType::eCylinder) {
0239 message += "non-cylindrical volume bounds detected for volume " +
0240 std::to_string(iv);
0241 throw std::invalid_argument(message.c_str());
0242 }
0243 }
0244
0245 Acts::Experimental::detail::DetectorVolumeConsistency::checkRotationAlignment(
0246 gctx, volumes);
0247 }
0248
0249
0250
0251
0252
0253
0254
0255
0256 void checkBounds(
0257 [[maybe_unused]] const Acts::GeometryContext& gctx,
0258 const std::vector<std::shared_ptr<Acts::Experimental::DetectorVolume>>&
0259 volumes,
0260 const std::vector<std::array<unsigned int, 2u>>& refCur) {
0261
0262 auto refValues = volumes[0u]->volumeBounds().values();
0263 for (auto [iv, v] : Acts::enumerate(volumes)) {
0264 if (iv > 0u) {
0265 auto curValues = v->volumeBounds().values();
0266 for (auto [r, c] : refCur) {
0267 if (std::abs(refValues[r] - curValues[c]) >
0268 Acts::s_onSurfaceTolerance) {
0269 std::string message = "CylindricalDetectorHelper: '";
0270 message += volumes[iv - 1]->name();
0271 if (r != c) {
0272 message += "' does not attach to '";
0273 } else {
0274 message += "' does not match with '";
0275 }
0276 message += volumes[iv]->name();
0277 message += "'\n";
0278 message += " - at bound values ";
0279 message += std::to_string(refValues[r]);
0280 message += " / " + std::to_string(curValues[c]);
0281 throw std::runtime_error(message.c_str());
0282 }
0283 }
0284 refValues = curValues;
0285 }
0286 }
0287 }
0288
0289 }
0290
0291 Acts::Experimental::DetectorComponent::PortalContainer
0292 Acts::Experimental::detail::CylindricalDetectorHelper::connectInR(
0293 const GeometryContext& gctx,
0294 std::vector<std::shared_ptr<Experimental::DetectorVolume>>& volumes,
0295 const std::vector<unsigned int>& selectedOnly,
0296 Acts::Logging::Level logLevel) {
0297
0298 checkVolumes(gctx, volumes);
0299
0300
0301
0302
0303 std::vector<std::array<unsigned int, 2u>> checks = {
0304 {1u, 0u}, {3u, 3u}, {4u, 4u}};
0305
0306 if (selectedOnly.empty()) {
0307 checks.push_back({2u, 2u});
0308 }
0309 checkBounds(gctx, volumes, checks);
0310
0311
0312 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0313
0314 ACTS_DEBUG("Connect " << volumes.size() << " detector volumes in R.");
0315
0316
0317 DetectorComponent::PortalContainer dShell;
0318
0319
0320 std::vector<double> rBoundaries = {};
0321 auto refValues = volumes[0u]->volumeBounds().values();
0322
0323
0324 rBoundaries.push_back(refValues[CylinderVolumeBounds::BoundValues::eMinR]);
0325 rBoundaries.push_back(refValues[CylinderVolumeBounds::BoundValues::eMaxR]);
0326
0327
0328 bool connectR = selectedOnly.empty() || rangeContainsValue(selectedOnly, 2u);
0329
0330
0331 double phiSector =
0332 refValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector];
0333 double avgPhi = refValues[CylinderVolumeBounds::BoundValues::eAveragePhi];
0334
0335
0336 for (unsigned int iv = 1; iv < volumes.size(); ++iv) {
0337 refValues = volumes[iv]->volumeBounds().values();
0338
0339 rBoundaries.push_back(refValues[CylinderVolumeBounds::BoundValues::eMaxR]);
0340
0341 if (connectR) {
0342 ACTS_VERBOSE("Connect volume '" << volumes[iv - 1]->name() << "' to "
0343 << volumes[iv]->name() << "'.");
0344
0345
0346 auto innerCylinder = volumes[iv - 1]->portalPtrs()[2u];
0347 auto outerCylinder = volumes[iv]->portalPtrs()[3u];
0348 auto fusedCylinder = Portal::fuse(innerCylinder, outerCylinder);
0349 volumes[iv - 1]->updatePortal(fusedCylinder, 2u);
0350 volumes[iv]->updatePortal(fusedCylinder, 3u);
0351 }
0352 }
0353
0354
0355 if (connectR) {
0356
0357 if (volumes[0u]->portals().size() == 4u ||
0358 volumes[0u]->portals().size() == 6u) {
0359 dShell[3u] = volumes[0u]->portalPtrs()[3u];
0360 }
0361 dShell[2u] = volumes[volumes.size() - 1u]->portalPtrs()[2u];
0362 }
0363
0364
0365
0366
0367 bool sectorsPresent = volumes[volumes.size() - 1u]->portals().size() > 4u;
0368
0369
0370
0371 std::vector<PortalReplacement> pReplacements = {};
0372
0373
0374 std::vector<Acts::Direction> discDirs = {Acts::Direction::Forward(),
0375 Acts::Direction::Backward()};
0376 for (const auto [iu, idir] : enumerate(discDirs)) {
0377 if (selectedOnly.empty() || rangeContainsValue(selectedOnly, iu)) {
0378 const Surface& refSurface = volumes[0u]->portals()[iu]->surface();
0379 const Transform3& refTransform = refSurface.transform(gctx);
0380 pReplacements.push_back(createDiscReplacement(
0381 refTransform, rBoundaries, {avgPhi - phiSector, avgPhi + phiSector},
0382 iu, idir));
0383 }
0384 }
0385
0386
0387 if (sectorsPresent) {
0388 ACTS_VERBOSE("Sector planes are present, they need replacement.");
0389
0390 std::vector<Acts::Direction> sectorDirs = {Acts::Direction::Forward(),
0391 Acts::Direction::Backward()};
0392 Acts::Vector3 vCenter = volumes[0u]->transform(gctx).translation();
0393 for (const auto [iu, idir] : enumerate(sectorDirs)) {
0394
0395
0396 if (selectedOnly.empty() || rangeContainsValue(selectedOnly, iu + 4u)) {
0397
0398 const Surface& refSurface =
0399 volumes[volumes.size() - 1u]->portals()[iu + 4u]->surface();
0400 pReplacements.push_back(createSectorReplacement(
0401 gctx, vCenter, refSurface, rBoundaries, Acts::AxisDirection::AxisR,
0402 iu + 4ul, idir));
0403 }
0404 }
0405 } else {
0406 ACTS_VERBOSE(
0407 "No sector planes present, full 2 * std::numbers::pi cylindrical "
0408 "geometry.");
0409 }
0410
0411
0412 PortalHelper::attachExternalNavigationDelegates(gctx, volumes, pReplacements);
0413
0414
0415 ACTS_VERBOSE("Portals of " << volumes.size() << " volumes need updating.");
0416
0417 for (auto& iv : volumes) {
0418 ACTS_VERBOSE("- update portals of volume '" << iv->name() << "'.");
0419 for (auto& [p, i, dir, boundaries, binning] : pReplacements) {
0420
0421 dShell[i] = p;
0422
0423
0424
0425
0426
0427 std::size_t nPortals = iv->portals().size();
0428 bool innerPresent = (nPortals == 3u || nPortals == 5u);
0429 int iOffset = (innerPresent && i > 2u) ? -1 : 0;
0430 ACTS_VERBOSE("-- update portal with index "
0431 << i + iOffset << " (including offset " << iOffset << ")");
0432 iv->updatePortal(p, static_cast<unsigned int>(i + iOffset));
0433 }
0434 }
0435
0436 return dShell;
0437 }
0438
0439 Acts::Experimental::DetectorComponent::PortalContainer
0440 Acts::Experimental::detail::CylindricalDetectorHelper::connectInZ(
0441 const Acts::GeometryContext& gctx,
0442 std::vector<std::shared_ptr<Acts::Experimental::DetectorVolume>>& volumes,
0443 const std::vector<unsigned int>& selectedOnly,
0444 Acts::Logging::Level logLevel) {
0445
0446 checkVolumes(gctx, volumes);
0447
0448
0449 std::vector<std::array<unsigned int, 2u>> checks = {{3u, 3u}, {4u, 4u}};
0450
0451
0452 if (selectedOnly.empty()) {
0453 checks.push_back({0u, 0u});
0454 checks.push_back({1u, 1u});
0455 }
0456 checkBounds(gctx, volumes, checks);
0457
0458
0459 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0460
0461 ACTS_DEBUG("Connect " << volumes.size() << " detector volumes in Z.");
0462
0463
0464 DetectorComponent::PortalContainer dShell;
0465
0466
0467
0468
0469 const bool connectZ =
0470 selectedOnly.empty() || rangeContainsValue(selectedOnly, 1u);
0471
0472 const auto rotation = volumes[0u]->transform(gctx).rotation();
0473
0474 std::vector<Vector3> zBoundaries3D = {};
0475
0476
0477
0478
0479
0480 auto addZboundary3D = [&](const Experimental::DetectorVolume& volume,
0481 int side) -> void {
0482 const auto boundValues = volume.volumeBounds().values();
0483 double halflengthZ =
0484 boundValues[CylinderVolumeBounds::BoundValues::eHalfLengthZ];
0485 zBoundaries3D.push_back(volume.transform(gctx).translation() +
0486 side * halflengthZ * rotation.col(2));
0487 };
0488
0489
0490 addZboundary3D(*volumes[0u].get(), -1);
0491 addZboundary3D(*volumes[0u].get(), 1);
0492 for (unsigned int iv = 1; iv < volumes.size(); ++iv) {
0493
0494 addZboundary3D(*volumes[iv].get(), 1u);
0495
0496 if (connectZ) {
0497 ACTS_VERBOSE("Connect volume '" << volumes[iv - 1]->name() << "' to "
0498 << volumes[iv]->name() << "'.");
0499
0500 auto& pDisc = volumes[iv - 1]->portalPtrs()[1u];
0501 auto& nDisc = volumes[iv]->portalPtrs()[0u];
0502
0503 Vector3 pPosition = pDisc->surface().center(gctx);
0504 Vector3 nPosition = nDisc->surface().center(gctx);
0505 if (!pPosition.isApprox(nPosition)) {
0506 std::string message = "CylindricalDetectorHelper: '";
0507 message += volumes[iv - 1]->name();
0508 message += "' does not attach to '";
0509 message += volumes[iv]->name();
0510 message += "'\n";
0511 message += " - along z with values ";
0512 message += Acts::toString(pPosition);
0513 message += " / " + Acts::toString(nPosition);
0514 throw std::runtime_error(message.c_str());
0515 }
0516 auto fusedDisc = Portal::fuse(pDisc, nDisc);
0517 volumes[iv - 1]->updatePortal(fusedDisc, 1u);
0518 volumes[iv]->updatePortal(fusedDisc, 0u);
0519 }
0520 }
0521
0522
0523 if (connectZ) {
0524 dShell[0u] = volumes[0u]->portalPtrs()[0u];
0525 dShell[1u] = volumes[volumes.size() - 1u]->portalPtrs()[1u];
0526 }
0527
0528
0529 Vector3 combinedCenter =
0530 0.5 * (zBoundaries3D[zBoundaries3D.size() - 1u] + zBoundaries3D[0u]);
0531
0532 ACTS_VERBOSE("New combined center calculated at "
0533 << toString(combinedCenter));
0534
0535
0536 std::vector<double> zBoundaries = {};
0537 for (const auto& zb3D : zBoundaries3D) {
0538 auto proj3D = (zb3D - combinedCenter).dot(rotation.col(2));
0539 double zBoundary = std::copysign((zb3D - combinedCenter).norm(), proj3D);
0540 zBoundaries.push_back(zBoundary);
0541 }
0542
0543 Transform3 combinedTransform = Transform3::Identity();
0544 combinedTransform.pretranslate(combinedCenter);
0545 combinedTransform.rotate(rotation);
0546
0547
0548 const auto& refVolume = volumes[0u];
0549 const auto refValues = refVolume->volumeBounds().values();
0550
0551
0552 double minR = refValues[CylinderVolumeBounds::BoundValues::eMinR];
0553 double maxR = refValues[CylinderVolumeBounds::BoundValues::eMaxR];
0554 double phiSector =
0555 refValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector];
0556 double avgPhi = refValues[CylinderVolumeBounds::BoundValues::eAveragePhi];
0557
0558
0559 std::size_t nPortals = volumes[volumes.size() - 1u]->portals().size();
0560 bool innerPresent = (nPortals != 3u && nPortals != 5u);
0561 bool sectorsPresent = nPortals > 4u;
0562
0563
0564
0565 std::vector<PortalReplacement> pReplacements = {};
0566
0567
0568 std::vector<Acts::Direction> cylinderDirs = {Acts::Direction::Backward()};
0569
0570 std::vector<double> cylinderR = {maxR};
0571 if (innerPresent) {
0572 ACTS_VERBOSE("Inner surface present, tube geometry detected.");
0573 cylinderDirs.push_back(Direction::Forward());
0574 cylinderR.push_back(minR);
0575 } else {
0576 ACTS_VERBOSE("No inner surface present, solid cylinder geometry detected.");
0577 }
0578
0579 unsigned int iSecOffset = innerPresent ? 4u : 3u;
0580
0581 for (const auto [iu, idir] : enumerate(cylinderDirs)) {
0582 if (selectedOnly.empty() || rangeContainsValue(selectedOnly, iu + 2u)) {
0583 pReplacements.push_back(createCylinderReplacement(
0584 combinedTransform, cylinderR[iu], zBoundaries,
0585 {avgPhi - phiSector, avgPhi + phiSector}, iu + 2u, idir));
0586 }
0587 }
0588
0589
0590 if (sectorsPresent) {
0591 ACTS_VERBOSE("Sector planes are present, they need replacement.");
0592
0593 std::vector<Acts::Direction> sectorDirs = {Acts::Direction::Forward(),
0594 Acts::Direction::Backward()};
0595 for (const auto [iu, idir] : enumerate(sectorDirs)) {
0596
0597 if (selectedOnly.empty() || rangeContainsValue(selectedOnly, iu + 4u)) {
0598 const Surface& refSurface =
0599 volumes[0u]->portals()[iu + iSecOffset]->surface();
0600 pReplacements.push_back(createSectorReplacement(
0601 gctx, combinedCenter, refSurface, zBoundaries,
0602 Acts::AxisDirection::AxisZ, iu + 4ul, idir));
0603 }
0604 }
0605 } else {
0606 ACTS_VERBOSE(
0607 "No sector planes present, full 2 * std::numbers::pi cylindrical "
0608 "geometry.");
0609 }
0610
0611
0612 PortalHelper::attachExternalNavigationDelegates(gctx, volumes, pReplacements);
0613
0614
0615 ACTS_VERBOSE("Portals of " << volumes.size() << " volumes need updating.");
0616 for (auto& iv : volumes) {
0617 ACTS_VERBOSE("- update portals of volume '" << iv->name() << "'.");
0618 for (auto& [p, i, dir, boundaries, binning] : pReplacements) {
0619
0620
0621
0622 int iOffset = (i > 2u && !innerPresent) ? -1 : 0;
0623 ACTS_VERBOSE("-- update portal with index " << i);
0624 iv->updatePortal(p, static_cast<unsigned int>(i + iOffset));
0625
0626 dShell[i] = p;
0627 }
0628 }
0629
0630 return dShell;
0631 }
0632
0633 Acts::Experimental::DetectorComponent::PortalContainer
0634 Acts::Experimental::detail::CylindricalDetectorHelper::connectInPhi(
0635 const Acts::GeometryContext& gctx,
0636 std::vector<std::shared_ptr<Acts::Experimental::DetectorVolume>>& volumes,
0637 const std::vector<unsigned int>& ,
0638 Acts::Logging::Level logLevel) {
0639
0640 checkVolumes(gctx, volumes);
0641
0642
0643 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0644
0645 ACTS_DEBUG("Connect " << volumes.size() << " detector volumes in phi.");
0646
0647
0648 DetectorComponent::PortalContainer dShell;
0649
0650
0651 std::size_t nPortals = volumes[volumes.size() - 1u]->portals().size();
0652 bool innerPresent = (nPortals != 3u && nPortals != 5u);
0653
0654 Transform3 refTransform = volumes[0u]->transform(gctx);
0655
0656
0657 unsigned int iSecOffset = innerPresent ? 4u : 3u;
0658 std::vector<double> phiBoundaries = {};
0659 auto refValues = volumes[0u]->volumeBounds().values();
0660 phiBoundaries.push_back(
0661 refValues[CylinderVolumeBounds::BoundValues::eAveragePhi] -
0662 refValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector]);
0663 phiBoundaries.push_back(
0664 refValues[CylinderVolumeBounds::BoundValues::eAveragePhi] +
0665 refValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector]);
0666
0667 for (unsigned int iv = 1; iv < volumes.size(); ++iv) {
0668 ACTS_VERBOSE("Connect volume '" << volumes[iv - 1]->name() << "' to "
0669 << volumes[iv]->name() << "'.");
0670
0671
0672 auto& rSector = volumes[iv - 1]->portalPtrs()[iSecOffset + 1u];
0673 auto& lSector = volumes[iv]->portalPtrs()[iSecOffset];
0674 auto fusedSector = Portal::fuse(rSector, lSector);
0675 volumes[iv - 1]->updatePortal(fusedSector, iSecOffset + 1u);
0676 volumes[iv]->updatePortal(fusedSector, iSecOffset);
0677
0678 auto curValues = volumes[iv]->volumeBounds().values();
0679
0680 double lowPhi =
0681 curValues[CylinderVolumeBounds::BoundValues::eAveragePhi] -
0682 curValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector];
0683 double highPhi =
0684 curValues[CylinderVolumeBounds::BoundValues::eAveragePhi] +
0685 curValues[CylinderVolumeBounds::BoundValues::eHalfPhiSector];
0686
0687 if (std::abs(phiBoundaries[phiBoundaries.size() - 1u] - lowPhi) >
0688 Acts::s_onSurfaceTolerance) {
0689 std::string message = "CylindricalDetectorHelper: '";
0690 message += volumes[iv - 1]->name();
0691 message += "' does not attach to '";
0692 message += volumes[iv]->name();
0693 message += "'\n";
0694 message += " - within phi sectors ";
0695 message += std::to_string(lowPhi);
0696 message +=
0697 " / " + std::to_string(phiBoundaries[phiBoundaries.size() - 1u]);
0698 throw std::runtime_error(message.c_str());
0699 }
0700
0701 phiBoundaries.push_back(highPhi);
0702
0703 refValues = curValues;
0704 }
0705
0706
0707
0708 std::vector<PortalReplacement> pReplacements = {};
0709
0710 pReplacements.push_back(createDiscReplacement(
0711 refTransform,
0712 {refValues[CylinderVolumeBounds::BoundValues::eMinR],
0713 refValues[CylinderVolumeBounds::BoundValues::eMaxR]},
0714 phiBoundaries, 0u, Acts::Direction::Forward()));
0715
0716
0717 pReplacements.push_back(createDiscReplacement(
0718 refTransform,
0719 {refValues[CylinderVolumeBounds::BoundValues::eMinR],
0720 refValues[CylinderVolumeBounds::BoundValues::eMaxR]},
0721 phiBoundaries, 1u, Acts::Direction::Backward()));
0722
0723
0724 pReplacements.push_back(createCylinderReplacement(
0725 refTransform, refValues[CylinderVolumeBounds::BoundValues::eMaxR],
0726 {-refValues[CylinderVolumeBounds::BoundValues::eHalfLengthZ],
0727 refValues[CylinderVolumeBounds::BoundValues::eHalfLengthZ]},
0728 phiBoundaries, 2u, Acts::Direction::Backward()));
0729
0730
0731
0732 if (refValues[CylinderVolumeBounds::BoundValues::eMinR] > 0.) {
0733
0734 pReplacements.push_back(createCylinderReplacement(
0735 refTransform, refValues[CylinderVolumeBounds::BoundValues::eMinR],
0736 {-refValues[CylinderVolumeBounds::BoundValues::eHalfLengthZ],
0737 refValues[CylinderVolumeBounds::BoundValues::eHalfLengthZ]},
0738 phiBoundaries, 3u, Acts::Direction::Forward()));
0739 }
0740
0741
0742 PortalHelper::attachExternalNavigationDelegates(gctx, volumes, pReplacements);
0743
0744 ACTS_VERBOSE("Portals of " << volumes.size() << " volumes need updating.");
0745 for (auto& iv : volumes) {
0746 ACTS_VERBOSE("- update portals of volume '" << iv->name() << "'.");
0747 for (auto& [p, i, dir, boundaries, binning] : pReplacements) {
0748 ACTS_VERBOSE("-- update portal with index " << i);
0749 iv->updatePortal(p, static_cast<unsigned int>(i));
0750
0751 dShell[i] = p;
0752 }
0753 }
0754
0755
0756 return dShell;
0757 }
0758
0759 Acts::Experimental::DetectorComponent::PortalContainer
0760 Acts::Experimental::detail::CylindricalDetectorHelper::wrapInZR(
0761 const Acts::GeometryContext& gctx,
0762 std::vector<std::shared_ptr<Acts::Experimental::DetectorVolume>>& volumes,
0763 Acts::Logging::Level logLevel) {
0764
0765 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0766
0767 ACTS_DEBUG("Wrapping volumes in Z-R.");
0768
0769
0770 if (volumes.size() != 2u) {
0771 throw std::invalid_argument(
0772 "Wrapping the detector volume requires exactly 2 volumes.");
0773 }
0774
0775
0776 DetectorComponent::PortalContainer dShell;
0777
0778
0779 dShell[0u] = volumes[1u]->portalPtrs()[0u];
0780 dShell[1u] = volumes[1u]->portalPtrs()[1u];
0781 dShell[2u] = volumes[1u]->portalPtrs()[2u];
0782
0783
0784 auto& outerCover = volumes[0u]->portalPtrs()[2u];
0785 auto& innerCover = volumes[1u]->portalPtrs()[3u];
0786 auto fusedCover = Portal::fuse(outerCover, innerCover);
0787 volumes[0u]->updatePortal(fusedCover, 2u);
0788 volumes[1u]->updatePortal(fusedCover, 3u);
0789
0790
0791 auto& firstDiscN = volumes[1u]->portalPtrs()[4u];
0792 auto& secondDiscN = volumes[0u]->portalPtrs()[0u];
0793 auto fusedDiscN = Portal::fuse(firstDiscN, secondDiscN);
0794 volumes[1u]->updatePortal(fusedDiscN, 4u);
0795 volumes[0u]->updatePortal(fusedDiscN, 0u);
0796
0797
0798 auto& firstDiscP = volumes[0u]->portalPtrs()[1u];
0799 auto& secondDiscP = volumes[1u]->portalPtrs()[5u];
0800 auto fusedDiscP = Portal::fuse(firstDiscP, secondDiscP);
0801 volumes[0u]->updatePortal(fusedDiscP, 1u);
0802 volumes[1u]->updatePortal(fusedDiscP, 5u);
0803
0804
0805 if (volumes[0u]->portalPtrs().size() == 4u &&
0806 volumes[1u]->portalPtrs().size() == 8u) {
0807 const auto* cylVolBounds =
0808 dynamic_cast<const CylinderVolumeBounds*>(&volumes[0u]->volumeBounds());
0809 const auto* ccylVolBounds = dynamic_cast<const CutoutCylinderVolumeBounds*>(
0810 &volumes[1u]->volumeBounds());
0811 if (cylVolBounds == nullptr || ccylVolBounds == nullptr) {
0812 throw std::invalid_argument(
0813 "Wrapping the detector volume requires a cylinder and a cutout "
0814 "cylinder volume.");
0815 }
0816
0817 double hlZ = cylVolBounds->get(
0818 Acts::CylinderVolumeBounds::BoundValues::eHalfLengthZ);
0819 double HlZ = ccylVolBounds->get(
0820 Acts::CutoutCylinderVolumeBounds::BoundValues::eHalfLengthZ);
0821 double innerR = cylVolBounds->get(CylinderVolumeBounds::BoundValues::eMinR);
0822
0823 std::vector<PortalReplacement> pReplacements;
0824 pReplacements.push_back(createCylinderReplacement(
0825 volumes[0u]->transform(gctx), innerR, {-HlZ, -hlZ, hlZ, HlZ},
0826 {-std::numbers::pi, std::numbers::pi}, 3u, Direction::Forward()));
0827 std::vector<std::shared_ptr<DetectorVolume>> zVolumes = {
0828 volumes[1u], volumes[0u], volumes[1u]};
0829
0830 PortalHelper::attachExternalNavigationDelegates(gctx, zVolumes,
0831 pReplacements);
0832 auto& [p, i, dir, boundaries, binning] = pReplacements[0u];
0833
0834 volumes[1u]->updatePortal(p, 6u);
0835 volumes[0u]->updatePortal(p, 3u);
0836 volumes[1u]->updatePortal(p, 7u);
0837
0838 dShell[3u] = p;
0839 }
0840
0841 return dShell;
0842 }
0843
0844 Acts::Experimental::DetectorComponent::PortalContainer
0845 Acts::Experimental::detail::CylindricalDetectorHelper::connectInR(
0846 const GeometryContext& gctx,
0847 const std::vector<DetectorComponent::PortalContainer>& containers,
0848 const std::vector<unsigned int>& selectedOnly,
0849 Acts::Logging::Level logLevel) noexcept(false) {
0850
0851 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0852
0853 ACTS_DEBUG("Connect " << containers.size() << " proto containers in R.");
0854
0855
0856 DetectorComponent::PortalContainer dShell;
0857
0858
0859 for (unsigned int ic = 1; ic < containers.size(); ++ic) {
0860 auto& formerContainer = containers[ic - 1];
0861 auto& currentContainer = containers[ic];
0862
0863 if (!formerContainer.contains(2u)) {
0864 throw std::invalid_argument(
0865 "CylindricalDetectorHelper: proto container has no outer cover, can "
0866 "not be connected in R");
0867 }
0868 if (!currentContainer.contains(3u)) {
0869 throw std::invalid_argument(
0870 "CylindricalDetectorHelper: proto container has no inner cover, can "
0871 "not be connected in R");
0872 }
0873
0874
0875 std::shared_ptr<Portal> innerCylinder = containers[ic - 1].find(2u)->second;
0876
0877 auto innerAttachedVolumes =
0878 innerCylinder->attachedDetectorVolumes()[Direction::Backward().index()];
0879 std::shared_ptr<Portal> outerCylinder = containers[ic].find(3u)->second;
0880 auto outerAttachedVolume =
0881 outerCylinder->attachedDetectorVolumes()[Direction::Forward().index()];
0882 auto fusedCylinder = Portal::fuse(innerCylinder, outerCylinder);
0883
0884
0885 std::ranges::for_each(innerAttachedVolumes,
0886 [&](std::shared_ptr<DetectorVolume>& av) {
0887 av->updatePortal(fusedCylinder, 2u);
0888 });
0889 std::ranges::for_each(outerAttachedVolume,
0890 [&](std::shared_ptr<DetectorVolume>& av) {
0891 av->updatePortal(fusedCylinder, 3u);
0892 });
0893 }
0894
0895
0896 if (containers[0u].contains(3u)) {
0897 dShell[3u] = containers[0u].find(3u)->second;
0898 }
0899 dShell[2u] = containers[containers.size() - 1u].find(2u)->second;
0900
0901 auto sideVolumes = PortalHelper::stripSideVolumes(
0902 containers, {0u, 1u, 4u, 5u}, selectedOnly, logLevel);
0903
0904 for (auto [s, volumes] : sideVolumes) {
0905 auto pR = connectInR(gctx, volumes, {s});
0906 if (pR.contains(s)) {
0907 dShell[s] = pR.find(s)->second;
0908 }
0909 }
0910
0911
0912 return dShell;
0913 }
0914
0915 Acts::Experimental::DetectorComponent::PortalContainer
0916 Acts::Experimental::detail::CylindricalDetectorHelper::connectInZ(
0917 const GeometryContext& gctx,
0918 const std::vector<DetectorComponent::PortalContainer>& containers,
0919 const std::vector<unsigned int>& selectedOnly,
0920 Acts::Logging::Level logLevel) noexcept(false) {
0921
0922 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
0923
0924 ACTS_DEBUG("Connect " << containers.size() << " proto containers in Z.");
0925
0926
0927 DetectorComponent::PortalContainer dShell;
0928
0929 for (unsigned int ic = 1; ic < containers.size(); ++ic) {
0930 auto& formerContainer = containers[ic - 1];
0931 auto& currentContainer = containers[ic];
0932
0933 if (!formerContainer.contains(1u)) {
0934 throw std::invalid_argument(
0935 "CylindricalDetectorHelper: proto container has no negative disc, "
0936 "can not be connected in Z");
0937 }
0938 if (!currentContainer.contains(0u)) {
0939 throw std::invalid_argument(
0940 "CylindricalDetectorHelper: proto container has no positive disc, "
0941 "can not be connected in Z");
0942 }
0943
0944 std::shared_ptr<Portal> pDisc = formerContainer.find(1u)->second;
0945 auto pAttachedVolumes =
0946 pDisc->attachedDetectorVolumes()[Direction::Backward().index()];
0947
0948 std::shared_ptr<Portal> nDisc = currentContainer.find(0u)->second;
0949 auto nAttachedVolumes =
0950 nDisc->attachedDetectorVolumes()[Direction::Forward().index()];
0951
0952 auto fusedDisc = Portal::fuse(pDisc, nDisc);
0953
0954 std::ranges::for_each(pAttachedVolumes,
0955 [&](std::shared_ptr<DetectorVolume>& av) {
0956 av->updatePortal(fusedDisc, 1u);
0957 });
0958 std::ranges::for_each(nAttachedVolumes,
0959 [&](std::shared_ptr<DetectorVolume>& av) {
0960 av->updatePortal(fusedDisc, 0u);
0961 });
0962 }
0963
0964
0965 dShell[0u] = containers[0u].find(0u)->second;
0966 dShell[1u] = containers[containers.size() - 1u].find(1u)->second;
0967
0968
0969 std::vector<unsigned int> nominalSides = {2u, 4u, 5u};
0970 if (containers[0u].contains(3u)) {
0971 nominalSides.push_back(3u);
0972 }
0973
0974
0975 auto sideVolumes = PortalHelper::stripSideVolumes(containers, nominalSides,
0976 selectedOnly, logLevel);
0977
0978 ACTS_VERBOSE("There remain " << sideVolumes.size()
0979 << " side volume packs to be connected");
0980 for (auto [s, volumes] : sideVolumes) {
0981 ACTS_VERBOSE(" - connect " << volumes.size() << " at selected side " << s);
0982 auto pR = connectInZ(gctx, volumes, {s}, logLevel);
0983 if (pR.contains(s)) {
0984 dShell[s] = pR.find(s)->second;
0985 }
0986 }
0987
0988
0989 return dShell;
0990 }
0991
0992 Acts::Experimental::DetectorComponent::PortalContainer
0993 Acts::Experimental::detail::CylindricalDetectorHelper::connectInPhi(
0994 [[maybe_unused]] const GeometryContext& gctx,
0995 [[maybe_unused]] const std::vector<DetectorComponent::PortalContainer>&
0996 containers,
0997 [[maybe_unused]] const std::vector<unsigned int>& selectedOnly,
0998 [[maybe_unused]] Acts::Logging::Level logLevel) noexcept(false) {
0999 throw std::invalid_argument(
1000 "CylindricalDetectorHelper: container connection in phi not implemented "
1001 "yet.");
1002 DetectorComponent::PortalContainer dShell;
1003
1004 return dShell;
1005 }
1006
1007 Acts::Experimental::DetectorComponent::PortalContainer
1008 Acts::Experimental::detail::CylindricalDetectorHelper::wrapInZR(
1009 [[maybe_unused]] const GeometryContext& gctx,
1010 const std::vector<DetectorComponent::PortalContainer>& containers,
1011 Acts::Logging::Level logLevel) {
1012 if (containers.size() != 2u) {
1013 throw std::invalid_argument(
1014 "CylindricalDetectorHelper: wrapping must take exactly two "
1015 "containers.");
1016 }
1017
1018
1019 auto innerContainer = containers.front();
1020
1021 auto outerContainer = containers.back();
1022 std::shared_ptr<DetectorVolume> wrappingVolume = nullptr;
1023 for (const auto& [key, value] : outerContainer) {
1024 auto attachedVolumes = value->attachedDetectorVolumes();
1025 for (const auto& ava : attachedVolumes) {
1026 for (const auto& av : ava) {
1027 if (wrappingVolume == nullptr && av != nullptr) {
1028 wrappingVolume = av;
1029 } else if (wrappingVolume != nullptr && av != wrappingVolume) {
1030 throw std::invalid_argument(
1031 "CylindricalDetectorHelper: wrapping container must represent a "
1032 "single volume.");
1033 }
1034 }
1035 }
1036 }
1037 if (wrappingVolume == nullptr) {
1038 throw std::invalid_argument(
1039 "CylindricalDetectorHelper: wrapping volume could not be "
1040 "determined.");
1041 }
1042
1043
1044 ACTS_LOCAL_LOGGER(getDefaultLogger("CylindricalDetectorHelper", logLevel));
1045
1046 ACTS_DEBUG("Wrapping a container with volume `" << wrappingVolume->name()
1047 << "'.");
1048
1049 DetectorComponent::PortalContainer dShell;
1050
1051
1052 dShell[0u] = wrappingVolume->portalPtrs()[0u];
1053 dShell[1u] = wrappingVolume->portalPtrs()[1u];
1054 dShell[2u] = wrappingVolume->portalPtrs()[2u];
1055
1056
1057 auto& innerCover = innerContainer[2u];
1058 auto innerAttachedVolumes =
1059 innerCover->attachedDetectorVolumes()[Direction::Backward().index()];
1060 auto& innerTube = wrappingVolume->portalPtrs()[3u];
1061 auto fusedCover = Portal::fuse(innerCover, innerTube);
1062
1063 std::ranges::for_each(innerAttachedVolumes,
1064 [&](std::shared_ptr<DetectorVolume>& av) {
1065 av->updatePortal(fusedCover, 2u);
1066 });
1067 wrappingVolume->updatePortal(fusedCover, 3u);
1068
1069
1070
1071 auto& firstDiscN = innerContainer[0u];
1072
1073 auto firstNAttachedVolumes =
1074 firstDiscN->attachedDetectorVolumes()[Direction::Forward().index()];
1075
1076 auto& secondDiscN = wrappingVolume->portalPtrs()[4u];
1077 auto fusedDiscN = Portal::fuse(firstDiscN, secondDiscN);
1078
1079 std::ranges::for_each(firstNAttachedVolumes,
1080 [&](std::shared_ptr<DetectorVolume>& av) {
1081 av->updatePortal(fusedDiscN, 0u);
1082 });
1083 wrappingVolume->updatePortal(fusedDiscN, 4u);
1084
1085
1086 auto& firstDiscP = innerContainer[1u];
1087 auto firstPAttachedVolumes =
1088 firstDiscP->attachedDetectorVolumes()[Direction::Backward().index()];
1089
1090 auto& secondDiscP = wrappingVolume->portalPtrs()[5u];
1091 auto fusedDiscP = Portal::fuse(firstDiscP, secondDiscP);
1092
1093 std::ranges::for_each(firstPAttachedVolumes,
1094 [&](std::shared_ptr<DetectorVolume>& av) {
1095 av->updatePortal(fusedDiscP, 1u);
1096 });
1097
1098 wrappingVolume->updatePortal(fusedDiscP, 5u);
1099
1100
1101 if (innerContainer.size() == 4u &&
1102 wrappingVolume->portalPtrs().size() == 8u) {
1103
1104 auto& centralSegment = innerContainer[3u];
1105 auto centralValues = centralSegment->surface().bounds().values();
1106 double centralHalfLengthZ =
1107 centralValues[CylinderBounds::BoundValues::eHalfLengthZ];
1108
1109 auto& nSegment = wrappingVolume->portalPtrs()[6u];
1110 auto nValues = nSegment->surface().bounds().values();
1111 double nHalfLengthZ = nValues[CylinderBounds::BoundValues::eHalfLengthZ];
1112 auto& pSegment = wrappingVolume->portalPtrs()[7u];
1113 auto pValues = pSegment->surface().bounds().values();
1114 double pHalfLengthZ = pValues[CylinderBounds::BoundValues::eHalfLengthZ];
1115
1116 auto sideVolumes =
1117 PortalHelper::stripSideVolumes({innerContainer}, {3u}, {3u}, logLevel);
1118
1119
1120 std::vector<std::shared_ptr<DetectorVolume>> innerVolumes = {
1121 wrappingVolume->getSharedPtr()};
1122
1123 std::vector<double> zBoundaries = {-centralHalfLengthZ - 2 * nHalfLengthZ,
1124 centralHalfLengthZ};
1125
1126 for (auto& svs : sideVolumes) {
1127 for (auto& v : svs.second) {
1128 const auto* cylVolBounds =
1129 dynamic_cast<const CylinderVolumeBounds*>(&v->volumeBounds());
1130 if (cylVolBounds == nullptr) {
1131 throw std::invalid_argument(
1132 "CylindricalDetectorHelper: side volume must be a cylinder.");
1133 }
1134 double hlZ =
1135 cylVolBounds->get(CylinderVolumeBounds::BoundValues::eHalfLengthZ);
1136 zBoundaries.push_back(zBoundaries.back() + 2 * hlZ);
1137 innerVolumes.push_back(v);
1138 }
1139 }
1140
1141 zBoundaries.push_back(zBoundaries.back() + 2 * pHalfLengthZ);
1142 innerVolumes.push_back(wrappingVolume);
1143 }
1144
1145
1146 return dShell;
1147 }