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