Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-08 07:47:10

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include "Acts/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& /*position*/,
0043       const Vector3& /*direction*/) const override {
0044     return m_element;
0045   }
0046 
0047   std::vector<const Surface*>& lookup(std::size_t /*bin*/) override {
0048     return m_element;
0049   }
0050 
0051   const std::vector<const Surface*>& lookup(
0052       std::size_t /*bin*/) const override {
0053     return m_element;
0054   }
0055 
0056   std::span<const Surface* const> lookup(
0057       const GeometryContext& /*gctx*/, const Vector3& /*position*/,
0058       const Vector3& /*direction*/) const override {
0059     return m_element;
0060   }
0061 
0062   const std::vector<const Surface*>& neighbors(
0063       const Vector3& /*position*/,
0064       const Vector3& /*direction*/) const override {
0065     return m_element;
0066   }
0067 
0068   std::span<const Surface* const> neighbors(
0069       const GeometryContext& /*gctx*/, const Vector3& /*position*/,
0070       const Vector3& /*direction*/) const override {
0071     return m_element;
0072   }
0073 
0074   std::size_t size() const override { return 1; }
0075 
0076   Vector3 getBinCenter(std::size_t /*bin*/) 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& /*gctx*/,
0085             std::span<const Surface* const> /*surfaces*/) override {}
0086 
0087   bool isValidBin(std::size_t /*bin*/) 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 }  // namespace
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& /*gctx*/,
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       // Do not display negative zeroes
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     // hacky temporary solution until removal due deprecation
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     // using 1e-6 to avoid division by zero, the actual value does not matter as
0255     // long as it is small compared to the angles we want to distinguish
0256     const double neighborDistanceReal = std::min<double>(
0257         m_maxNeighborDistance,
0258         std::max<double>(1, 1 / (1e-6 + std::abs(normal.dot(direction)))));
0259     // clamp value to range before converting to std::uint8_t to avoid overflow
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   // needs to be a tuple for the grid_helper functions
0320   std::tuple<Axis1, Axis2> m_axes;
0321   std::vector<AxisDirection> m_binValues;
0322   std::uint8_t m_maxNeighborDistance{};
0323 
0324   // legacy grid for filling and for deprecated lookup methods.
0325   // TODO: remove this once deprecated lookup methods are removed and filling is
0326   // done directly into the neighbor cache
0327   std::vector<std::vector<const Surface*>> m_fillingGrid;
0328 
0329   // containers to store the surfaces in the custom grid
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   /// map surface center to grid
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   /// flood fill neighboring bins given a starting bin
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   /// calculate neighbors for every bin and store in map
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 }  // namespace
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 }  // namespace Acts