File indexing completed on 2026-04-08 07:47:10
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Surfaces/SurfaceArray.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/GeometryContext.hpp"
0013 #include "Acts/Surfaces/CylinderBounds.hpp"
0014 #include "Acts/Surfaces/Surface.hpp"
0015 #include "Acts/Utilities/Helpers.hpp"
0016 #include "Acts/Utilities/IAxis.hpp"
0017 #include "Acts/Utilities/Ranges.hpp"
0018 #include "Acts/Utilities/detail/grid_helper.hpp"
0019
0020 #include <limits>
0021 #include <map>
0022 #include <ranges>
0023 #include <utility>
0024
0025 namespace Acts {
0026
0027 SurfaceArray::SurfaceArray(std::unique_ptr<ISurfaceGridLookup> gridLookup,
0028 std::vector<std::shared_ptr<const Surface>> surfaces,
0029 const Transform3& transform)
0030 : m_gridLookup(std::move(gridLookup)),
0031 m_surfaces(std::move(surfaces)),
0032 m_surfacesRawPointers(unpackSmartPointers(m_surfaces)),
0033 m_transform(transform) {}
0034
0035 namespace {
0036
0037 struct SingleElementLookupImpl final : SurfaceArray::ISurfaceGridLookup {
0038 explicit SingleElementLookupImpl(const Surface* element)
0039 : m_element({element}) {}
0040
0041 const std::vector<const Surface*>& lookup(
0042 const Vector3& ,
0043 const Vector3& ) const override {
0044 return m_element;
0045 }
0046
0047 std::vector<const Surface*>& lookup(std::size_t ) override {
0048 return m_element;
0049 }
0050
0051 const std::vector<const Surface*>& lookup(
0052 std::size_t ) const override {
0053 return m_element;
0054 }
0055
0056 std::span<const Surface* const> lookup(
0057 const GeometryContext& , const Vector3& ,
0058 const Vector3& ) const override {
0059 return m_element;
0060 }
0061
0062 const std::vector<const Surface*>& neighbors(
0063 const Vector3& ,
0064 const Vector3& ) const override {
0065 return m_element;
0066 }
0067
0068 std::span<const Surface* const> neighbors(
0069 const GeometryContext& , const Vector3& ,
0070 const Vector3& ) const override {
0071 return m_element;
0072 }
0073
0074 std::size_t size() const override { return 1; }
0075
0076 Vector3 getBinCenter(std::size_t ) const override {
0077 return Vector3(0, 0, 0);
0078 }
0079
0080 std::vector<const IAxis*> getAxes() const override { return {}; }
0081
0082 const Surface* surfaceRepresentation() const override { return nullptr; }
0083
0084 void fill(const GeometryContext& ,
0085 std::span<const Surface* const> ) override {}
0086
0087 bool isValidBin(std::size_t ) const override { return true; }
0088
0089 std::array<std::size_t, 2> numLocalBins() const override { return {1, 1}; }
0090
0091 std::uint8_t maxNeighborDistance() const override { return 0; }
0092
0093 std::span<const Surface* const> at(
0094 std::array<std::size_t, 2> gridIndices,
0095 std::uint8_t neighborDistance) const override {
0096 if (gridIndices != std::array<std::size_t, 2>{0, 0} ||
0097 neighborDistance != 0) {
0098 throw std::out_of_range(
0099 "SingleElementLookupImpl only contains one bin with zero neighbor "
0100 "distance");
0101 }
0102 return m_element;
0103 }
0104
0105 private:
0106 std::vector<const Surface*> m_element;
0107 };
0108
0109 }
0110
0111 SurfaceArray::SurfaceArray(std::shared_ptr<const Surface> srf)
0112 : m_gridLookup(std::make_unique<SingleElementLookupImpl>(srf.get())),
0113 m_surfaces({std::move(srf)}) {
0114 m_surfacesRawPointers.push_back(m_surfaces.at(0).get());
0115 }
0116
0117 std::ostream& SurfaceArray::toStream(const GeometryContext& ,
0118 std::ostream& sl) const {
0119 sl << std::fixed << std::setprecision(4);
0120 sl << "SurfaceArray:" << std::endl;
0121 sl << " - no surfaces: " << m_surfaces.size() << std::endl;
0122
0123 const std::vector<const IAxis*> axes = m_gridLookup->getAxes();
0124
0125 for (const auto [j, axis] : enumerate(axes)) {
0126 const AxisBoundaryType bdt = axis->getBoundaryType();
0127 sl << " - axis " << (j + 1) << std::endl;
0128 sl << " - boundary type: ";
0129 if (bdt == AxisBoundaryType::Open) {
0130 sl << "open";
0131 }
0132 if (bdt == AxisBoundaryType::Bound) {
0133 sl << "bound";
0134 }
0135 if (bdt == AxisBoundaryType::Closed) {
0136 sl << "closed";
0137 }
0138 sl << std::endl;
0139 sl << " - type: " << (axis->isEquidistant() ? "equidistant" : "variable")
0140 << std::endl;
0141 sl << " - n bins: " << axis->getNBins() << std::endl;
0142 sl << " - bin edges: [ ";
0143 const std::vector<double> binEdges = axis->getBinEdges();
0144 for (const auto [i, binEdge] : enumerate(binEdges)) {
0145 if (i > 0) {
0146 sl << ", ";
0147 }
0148
0149 sl << ((std::abs(binEdge) >= 5e-4) ? binEdge : 0.0);
0150 }
0151 sl << " ]" << std::endl;
0152 }
0153 return sl;
0154 }
0155
0156 namespace {
0157
0158 template <class Axis1, class Axis2>
0159 struct SurfaceGridLookupImpl final : SurfaceArray::ISurfaceGridLookup {
0160 SurfaceGridLookupImpl(std::shared_ptr<RegularSurface> representative,
0161 double tolerance, std::tuple<Axis1, Axis2> axes,
0162 std::vector<AxisDirection> binValues = {},
0163 std::uint8_t maxNeighborDistance = 1)
0164 : m_representative(std::move(representative)),
0165 m_tolerance(tolerance),
0166 m_axes(std::move(axes)),
0167 m_binValues(std::move(binValues)),
0168 m_maxNeighborDistance(maxNeighborDistance) {
0169 m_fillingGrid.resize(size());
0170 }
0171
0172 void fill(const GeometryContext& gctx,
0173 std::span<const Surface* const> surfaces) override {
0174 for (const Surface* surface : surfaces) {
0175 const std::optional<std::size_t> globalBin =
0176 fillSurfaceToBinMapping(gctx, *surface);
0177 if (!globalBin.has_value()) {
0178 continue;
0179 }
0180
0181 fillBinToSurfaceMapping(gctx, *surface, *globalBin);
0182 }
0183
0184 for (std::vector<const Surface*>& binSurfaces : m_fillingGrid) {
0185 std::ranges::sort(binSurfaces);
0186 const auto last = std::ranges::unique(binSurfaces);
0187 binSurfaces.erase(last.begin(), last.end());
0188 binSurfaces.shrink_to_fit();
0189 }
0190
0191 checkGrid(surfaces);
0192
0193 populateNeighborCache();
0194 }
0195
0196 const std::vector<const Surface*>& lookup(
0197 const Vector3& position, const Vector3& direction) const override {
0198 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0199 const std::optional<GridIndex> localBins = findLocalBin2D(
0200 gctx, position, direction, std::numeric_limits<double>::infinity());
0201 if (!localBins.has_value()) {
0202 static std::vector<const Surface*> emptyVector;
0203 return emptyVector;
0204 }
0205 const std::size_t globalBin = globalBinFromLocalBins2D(*localBins);
0206 return m_fillingGrid.at(globalBin);
0207 }
0208
0209 std::vector<const Surface*>& lookup(std::size_t globalBin) override {
0210 return m_fillingGrid.at(globalBin);
0211 }
0212
0213 const std::vector<const Surface*>& lookup(
0214 std::size_t globalBin) const override {
0215 return m_fillingGrid.at(globalBin);
0216 }
0217
0218 std::span<const Surface* const> lookup(
0219 const GeometryContext& gctx, const Vector3& position,
0220 const Vector3& direction) const override {
0221 const std::optional<GridIndex> localBins = findLocalBin2D(
0222 gctx, position, direction, std::numeric_limits<double>::infinity());
0223 if (!localBins.has_value()) {
0224 return {};
0225 }
0226 const std::size_t globalBin = globalBinFromLocalBins3D(*localBins, 0);
0227 return m_neighborSurfacePacks.at(globalBin);
0228 }
0229
0230 const std::vector<const Surface*>& neighbors(
0231 const Vector3& position, const Vector3& direction) const override {
0232
0233 static thread_local std::vector<const Surface*> neighborsVector;
0234 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0235 const std::span<const Surface* const> neighborsSpan =
0236 neighbors(gctx, position, direction);
0237 neighborsVector.assign(neighborsSpan.begin(), neighborsSpan.end());
0238 return neighborsVector;
0239 }
0240
0241 std::span<const Surface* const> neighbors(
0242 const GeometryContext& gctx, const Vector3& position,
0243 const Vector3& direction) const override {
0244 const std::optional<Vector2> surfaceLocal = findSurfaceLocal(
0245 gctx, position, direction, std::numeric_limits<double>::infinity());
0246 if (!surfaceLocal.has_value()) {
0247 return {};
0248 }
0249
0250 const GridPoint gridLocal = surfaceToGridLocal(*surfaceLocal);
0251 const GridIndex localBins = localBinsFromPosition2D(gridLocal);
0252
0253 const Vector3 normal = m_representative->normal(gctx, *surfaceLocal);
0254
0255
0256 const double neighborDistanceReal = std::min<double>(
0257 m_maxNeighborDistance,
0258 std::max<double>(1, 1 / (1e-6 + std::abs(normal.dot(direction)))));
0259
0260 const std::uint8_t neighborDistance =
0261 clampValue<std::uint8_t>(neighborDistanceReal);
0262
0263 const std::size_t globalBin =
0264 globalBinFromLocalBins3D(localBins, neighborDistance);
0265
0266 return m_neighborSurfacePacks.at(globalBin);
0267 }
0268
0269 std::size_t size() const override {
0270 const GridIndex nBins = numLocalBins2D();
0271 return (nBins[0] + 2) * (nBins[1] + 2);
0272 }
0273
0274 std::vector<AxisDirection> binningValues() const override {
0275 return m_binValues;
0276 }
0277
0278 Vector3 getBinCenter(std::size_t bin) const override {
0279 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0280 const GridPoint gridLocal = binCenter(localBinsFromGlobalBin2D(bin));
0281 const Vector2 surfaceLocal = gridToSurfaceLocal(gridLocal);
0282 return m_representative->localToGlobal(gctx, surfaceLocal);
0283 }
0284
0285 std::vector<const IAxis*> getAxes() const override {
0286 return {&std::get<0>(m_axes), &std::get<1>(m_axes)};
0287 }
0288
0289 const Surface* surfaceRepresentation() const override {
0290 return m_representative.get();
0291 }
0292
0293 bool isValidBin(std::size_t globalBin) const override {
0294 const GridIndex indices = localBinsFromGlobalBin2D(globalBin);
0295 return isValidBin(indices);
0296 }
0297
0298 std::array<std::size_t, 2> numLocalBins() const override {
0299 return numLocalBins2D();
0300 }
0301
0302 std::uint8_t maxNeighborDistance() const override {
0303 return m_maxNeighborDistance;
0304 }
0305
0306 std::span<const Surface* const> at(
0307 std::array<std::size_t, 2> gridIndices,
0308 std::uint8_t neighborDistance) const override {
0309 return m_neighborSurfacePacks.at(
0310 globalBinFromLocalBins3D(gridIndices, neighborDistance));
0311 }
0312
0313 private:
0314 using GridIndex = std::array<std::size_t, 2>;
0315 using GridPoint = std::array<double, 2>;
0316
0317 std::shared_ptr<RegularSurface> m_representative;
0318 double m_tolerance{};
0319
0320 std::tuple<Axis1, Axis2> m_axes;
0321 std::vector<AxisDirection> m_binValues;
0322 std::uint8_t m_maxNeighborDistance{};
0323
0324
0325
0326
0327 std::vector<std::vector<const Surface*>> m_fillingGrid;
0328
0329
0330 std::vector<const Surface*> m_surfacePacks;
0331 std::vector<std::span<const Surface* const>> m_neighborSurfacePacks;
0332
0333 bool isValidBin(const GridIndex& indices) const {
0334 const GridIndex nBins = numLocalBins2D();
0335 for (std::size_t i = 0; i < indices.size(); ++i) {
0336 const std::size_t idx = indices.at(i);
0337 if (idx <= 0 || idx >= nBins.at(i) + 1) {
0338 return false;
0339 }
0340 }
0341 return true;
0342 }
0343
0344 GridIndex numLocalBins2D() const {
0345 return {std::get<0>(m_axes).getNBins(), std::get<1>(m_axes).getNBins()};
0346 }
0347
0348 GridIndex localBinsFromPosition2D(const GridPoint& point) const {
0349 return detail::grid_helper::getLocalBinIndices(point, m_axes);
0350 }
0351
0352 GridIndex localBinsFromGlobalBin2D(std::size_t globalBin) const {
0353 return detail::grid_helper::getLocalBinIndices(globalBin, m_axes);
0354 }
0355
0356 std::size_t globalBinFromLocalBins2D(const GridIndex& localBins) const {
0357 return detail::grid_helper::getGlobalBin(localBins, m_axes);
0358 }
0359
0360 std::size_t globalBinFromLocalBins3D(const GridIndex& localBins,
0361 std::uint8_t neighborDistance) const {
0362 const std::size_t globalGridBin =
0363 detail::grid_helper::getGlobalBin(localBins, m_axes);
0364 return globalGridBin * (m_maxNeighborDistance + 1) + neighborDistance;
0365 }
0366
0367 GridPoint binCenter(const GridIndex& localBins) const {
0368 return detail::grid_helper::getBinCenter(localBins, m_axes);
0369 }
0370
0371
0372 std::optional<std::size_t> fillSurfaceToBinMapping(
0373 const GeometryContext& gctx, const Surface& surface) {
0374 const Vector3 position =
0375 surface.referencePosition(gctx, AxisDirection::AxisR);
0376 const Vector3 normal = m_representative->normal(gctx, position);
0377 const std::optional<Vector2> surfaceLocal =
0378 findSurfaceLocal(gctx, position, normal, m_tolerance);
0379 if (!surfaceLocal.has_value()) {
0380 return std::nullopt;
0381 }
0382 const GridPoint gridLocal = surfaceToGridLocal(*surfaceLocal);
0383 const GridIndex localBins = localBinsFromPosition2D(gridLocal);
0384 const std::size_t globalBin = globalBinFromLocalBins2D(localBins);
0385 m_fillingGrid.at(globalBin).push_back(&surface);
0386 return globalBin;
0387 }
0388
0389
0390 void fillBinToSurfaceMapping(const GeometryContext& gctx,
0391 const Surface& surface, std::size_t startBin) {
0392 const GridIndex startIndices = localBinsFromGlobalBin2D(startBin);
0393 const auto startNeighborIndices =
0394 detail::grid_helper::neighborHoodIndices(startIndices, 1u, m_axes);
0395
0396 std::set<std::size_t> visited({startBin});
0397 std::vector<std::size_t> queue(startNeighborIndices.begin(),
0398 startNeighborIndices.end());
0399
0400 while (!queue.empty()) {
0401 const std::size_t current = queue.back();
0402 queue.pop_back();
0403 if (visited.contains(current)) {
0404 continue;
0405 }
0406
0407 const GridIndex currentIndices = localBinsFromGlobalBin2D(current);
0408 visited.insert(current);
0409
0410 const GridPoint gridLocal = binCenter(currentIndices);
0411 const Vector2 surfaceLocal = gridToSurfaceLocal(gridLocal);
0412 const Vector3 normal = m_representative->normal(gctx, surfaceLocal);
0413 const Vector3 global =
0414 m_representative->localToGlobal(gctx, surfaceLocal, normal);
0415
0416 const Intersection3D intersection =
0417 surface.intersect(gctx, global, normal, BoundaryTolerance::None())
0418 .closest();
0419 if (!intersection.isValid() ||
0420 std::abs(intersection.pathLength()) > m_tolerance) {
0421 continue;
0422 }
0423 m_fillingGrid.at(current).push_back(&surface);
0424
0425 const auto neighborIndices =
0426 detail::grid_helper::neighborHoodIndices(currentIndices, 1u, m_axes);
0427 queue.insert(queue.end(), neighborIndices.begin(), neighborIndices.end());
0428 }
0429 }
0430
0431
0432 void populateNeighborCache() {
0433 m_surfacePacks.clear();
0434 m_neighborSurfacePacks.clear();
0435
0436 using SurfacePackRange = std::pair<std::size_t, std::size_t>;
0437 std::vector<SurfacePackRange> neighborSurfacePacks;
0438 neighborSurfacePacks.resize(size() * (m_maxNeighborDistance + 1));
0439
0440 std::vector<const Surface*> surfacePack;
0441 std::map<std::vector<const Surface*>, SurfacePackRange> surfacesToPackRange;
0442 for (std::size_t inputGlobalBin = 0; inputGlobalBin < m_fillingGrid.size();
0443 ++inputGlobalBin) {
0444 const GridIndex indices = localBinsFromGlobalBin2D(inputGlobalBin);
0445
0446 if (!isValidBin(indices)) {
0447 continue;
0448 }
0449
0450 for (std::uint8_t neighborDistance = 0;
0451 neighborDistance <= m_maxNeighborDistance; ++neighborDistance) {
0452 surfacePack.clear();
0453
0454 for (const std::size_t idx : detail::grid_helper::neighborHoodIndices(
0455 indices, neighborDistance, m_axes)) {
0456 const std::vector<const Surface*>& binContent = m_fillingGrid.at(idx);
0457 std::copy(binContent.begin(), binContent.end(),
0458 std::back_inserter(surfacePack));
0459 }
0460
0461 std::ranges::sort(surfacePack);
0462 const auto last = std::ranges::unique(surfacePack);
0463 surfacePack.erase(last.begin(), last.end());
0464
0465 const std::size_t outputGlobalBin =
0466 globalBinFromLocalBins3D(indices, neighborDistance);
0467
0468 if (const auto it = surfacesToPackRange.find(surfacePack);
0469 it != surfacesToPackRange.end()) {
0470 neighborSurfacePacks[outputGlobalBin] = it->second;
0471 } else {
0472 const SurfacePackRange surfacePackRange = {
0473 m_surfacePacks.size(),
0474 m_surfacePacks.size() + surfacePack.size()};
0475 m_surfacePacks.insert(m_surfacePacks.end(), surfacePack.begin(),
0476 surfacePack.end());
0477 surfacesToPackRange[surfacePack] = surfacePackRange;
0478 neighborSurfacePacks[outputGlobalBin] = surfacePackRange;
0479 }
0480 }
0481 }
0482
0483 m_surfacePacks.shrink_to_fit();
0484
0485 m_neighborSurfacePacks.reserve(neighborSurfacePacks.size());
0486 std::ranges::transform(neighborSurfacePacks,
0487 std::back_inserter(m_neighborSurfacePacks),
0488 [this](const SurfacePackRange& range) {
0489 return std::span<const Surface* const>(
0490 m_surfacePacks.data() + range.first,
0491 m_surfacePacks.data() + range.second);
0492 });
0493 }
0494
0495 void checkGrid(std::span<const Surface* const> surfaces) {
0496 const std::set<const Surface*> allSurfaces(surfaces.begin(),
0497 surfaces.end());
0498
0499 std::set<const Surface*> seenSurface;
0500 for (std::size_t globalBin = 0; globalBin < m_fillingGrid.size();
0501 ++globalBin) {
0502 for (const Surface* surface : m_fillingGrid.at(globalBin)) {
0503 seenSurface.insert(surface);
0504 }
0505 }
0506
0507 if (allSurfaces != seenSurface) {
0508 std::set<const Surface*> diff;
0509 std::ranges::set_difference(allSurfaces, seenSurface,
0510 std::inserter(diff, diff.begin()));
0511
0512 throw std::logic_error(std::format(
0513 "SurfaceArray grid does not contain all surfaces provided! "
0514 "{} surfaces not seen",
0515 diff.size()));
0516 }
0517 }
0518
0519 const CylinderBounds* getCylinderBounds() const {
0520 return dynamic_cast<const CylinderBounds*>(&m_representative->bounds());
0521 }
0522
0523 Vector2 gridToSurfaceLocal(const GridPoint& gridLocal) const {
0524 Vector2 surfaceLocal = {gridLocal[0], gridLocal[1]};
0525 if (const CylinderBounds* bounds = getCylinderBounds(); bounds != nullptr) {
0526 surfaceLocal[0] *= bounds->get(CylinderBounds::eR);
0527 }
0528 return surfaceLocal;
0529 }
0530
0531 GridPoint surfaceToGridLocal(const Vector2& local) const {
0532 GridPoint gridLocal = {local[0], local[1]};
0533 if (const CylinderBounds* bounds = getCylinderBounds(); bounds != nullptr) {
0534 gridLocal[0] /= bounds->get(CylinderBounds::eR);
0535 }
0536 return gridLocal;
0537 }
0538
0539 std::optional<Vector2> findSurfaceLocal(const GeometryContext& gctx,
0540 const Vector3& position,
0541 const Vector3& direction,
0542 double tolerance) const {
0543 const Intersection3D intersection =
0544 m_representative
0545 ->intersect(gctx, position, direction,
0546 BoundaryTolerance::Infinite())
0547 .closest();
0548 if (!intersection.isValid() ||
0549 std::abs(intersection.pathLength()) > tolerance) {
0550 return std::nullopt;
0551 }
0552 const Vector2 surfaceLocal =
0553 m_representative
0554 ->globalToLocal(gctx, intersection.position(), direction)
0555 .value();
0556 return surfaceLocal;
0557 }
0558
0559 std::optional<GridIndex> findLocalBin2D(const GeometryContext& gctx,
0560 const Vector3& position,
0561 const Vector3& direction,
0562 double tolerance) const {
0563 const std::optional<Vector2> surfaceLocal =
0564 findSurfaceLocal(gctx, position, direction, tolerance);
0565 if (!surfaceLocal.has_value()) {
0566 return std::nullopt;
0567 }
0568 const GridPoint gridLocal = surfaceToGridLocal(*surfaceLocal);
0569 return localBinsFromPosition2D(gridLocal);
0570 }
0571 };
0572
0573 }
0574
0575 std::unique_ptr<SurfaceArray::ISurfaceGridLookup>
0576 SurfaceArray::makeSurfaceGridLookup(
0577 std::shared_ptr<RegularSurface> representative, double tolerance,
0578 std::tuple<const IAxis&, const IAxis&> axes,
0579 std::uint8_t maxNeighborDistance) {
0580 const auto& [iAxisA, iAxisB] = axes;
0581
0582 return iAxisA.visit([&]<typename axis_a_t>(const axis_a_t& axisA) {
0583 return iAxisB.visit(
0584 [&]<typename axis_b_t>(const axis_b_t& axisB)
0585 -> std::unique_ptr<SurfaceArray::ISurfaceGridLookup> {
0586 return std::make_unique<SurfaceGridLookupImpl<axis_a_t, axis_b_t>>(
0587 representative, tolerance,
0588 std::tuple<axis_a_t, axis_b_t>{axisA, axisB},
0589 std::vector<AxisDirection>(), maxNeighborDistance);
0590 });
0591 });
0592 }
0593
0594 SurfaceArray::SurfaceArray(const GeometryContext& gctx,
0595 std::vector<std::shared_ptr<const Surface>> surfaces,
0596 std::shared_ptr<RegularSurface> representative,
0597 double tolerance,
0598 std::tuple<const IAxis&, const IAxis&> axes) {
0599 m_transform = representative->localToGlobalTransform(gctx);
0600 m_gridLookup =
0601 makeSurfaceGridLookup(std::move(representative), tolerance, axes);
0602 m_surfaces = std::move(surfaces);
0603 m_surfacesRawPointers =
0604 m_surfaces |
0605 std::views::transform(
0606 [](const std::shared_ptr<const Surface>& sp) { return sp.get(); }) |
0607 Ranges::to<std::vector>;
0608 m_gridLookup->fill(gctx, m_surfacesRawPointers);
0609 }
0610
0611 }