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