Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-06-05 08:29:28

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2024 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 http://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Definitions/Tolerance.hpp"
0012 #include "Acts/Geometry/PortalLinkBase.hpp"
0013 #include "Acts/Geometry/TrackingVolume.hpp"
0014 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0015 #include "Acts/Surfaces/CylinderSurface.hpp"
0016 #include "Acts/Surfaces/DiscSurface.hpp"
0017 #include "Acts/Surfaces/PlaneSurface.hpp"
0018 #include "Acts/Utilities/Grid.hpp"
0019 #include "Acts/Utilities/Logger.hpp"
0020 
0021 #include <iosfwd>
0022 
0023 namespace Acts {
0024 
0025 class IGrid;
0026 
0027 template <typename... Axes>
0028   requires(sizeof...(Axes) <= 2)
0029 class GridPortalLinkT;
0030 
0031 /// GridPortalLink implements a subdivided surface where the target volume
0032 /// depends on the position on the surface. The link can be in two states:
0033 /// 1. One-dimensional binning with an associated *direction* to determine which
0034 ///    local coordinate to use for the lookup
0035 /// 2. Two-dimensional binning
0036 /// @note The grid dimensions and boundaries are **required** (and checked) to be
0037 ///       consistent with the surface bounds.
0038 class GridPortalLink : public PortalLinkBase {
0039  protected:
0040   /// Constructor from a surface and a direction, for initialization by derived
0041   /// class
0042   /// @param surface The surface
0043   /// @param direction The binning direction
0044   GridPortalLink(std::shared_ptr<RegularSurface> surface,
0045                  BinningValue direction)
0046       : PortalLinkBase(std::move(surface)), m_direction(direction) {}
0047 
0048  public:
0049   /// Factory function for a one-dimensional grid portal link, which allows
0050   /// using template deduction to figure out the right type
0051   /// @tparam axis_t The axis type
0052   /// @param surface The surface
0053   /// @param direction The binning direction
0054   /// @param axis The axis to use for the binning
0055   /// @note The axis boundaries are checked against the bounds of @p surface.
0056   /// @return A unique pointer to the grid portal link
0057   template <AxisConcept axis_t>
0058   static std::unique_ptr<GridPortalLinkT<axis_t>> make(
0059       std::shared_ptr<RegularSurface> surface, BinningValue direction,
0060       axis_t&& axis) {
0061     using enum BinningValue;
0062     if (dynamic_cast<const CylinderSurface*>(surface.get()) != nullptr) {
0063       if (direction != binZ && direction != binRPhi) {
0064         throw std::invalid_argument{"Invalid binning direction"};
0065       }
0066     } else if (dynamic_cast<const DiscSurface*>(surface.get()) != nullptr &&
0067                direction != binR && direction != binPhi) {
0068       throw std::invalid_argument{"Invalid binning direction"};
0069     }
0070 
0071     return std::make_unique<GridPortalLinkT<axis_t>>(
0072         surface, direction, std::forward<axis_t>(axis));
0073   }
0074 
0075   /// Factory function for a two-dimensional grid portal link, which allows
0076   /// using template deduction to figure out the right type.
0077   /// @tparam axis_1_t The first axis type
0078   /// @tparam axis_2_t The second axis type
0079   /// @param surface The surface
0080   /// @param axis1 The first axis to use for the binning
0081   /// @param axis2 The second axis to use for the binning
0082   /// @note The axis boundaries are checked against the bounds of @p surface.
0083   /// @return A unique pointer to the grid portal link
0084   template <AxisConcept axis_1_t, AxisConcept axis_2_t>
0085   static std::unique_ptr<GridPortalLinkT<axis_1_t, axis_2_t>> make(
0086       std::shared_ptr<RegularSurface> surface, axis_1_t axis1, axis_2_t axis2) {
0087     std::optional<BinningValue> direction;
0088     if (dynamic_cast<const CylinderSurface*>(surface.get()) != nullptr) {
0089       direction = BinningValue::binRPhi;
0090     } else if (dynamic_cast<const DiscSurface*>(surface.get()) != nullptr) {
0091       direction = BinningValue::binR;
0092     }
0093 
0094     return std::make_unique<GridPortalLinkT<axis_1_t, axis_2_t>>(
0095         surface, direction.value(), std::move(axis1), std::move(axis2));
0096   }
0097 
0098   /// Factory function for an automatically sized one-dimensional grid. This produces a single-bin grid with boundaries taken from the bounds of @p surface along @p direction.
0099   /// @param surface The surface
0100   /// @param volume The tracking volume
0101   /// @param direction The binning direction
0102   /// @return A unique pointer to the grid portal link
0103   static std::unique_ptr<GridPortalLink> make(
0104       const std::shared_ptr<RegularSurface>& surface, TrackingVolume& volume,
0105       BinningValue direction);
0106 
0107   /// Merge two grid portal links into a single one. The routine can merge
0108   /// one-dimenaional, tow-dimensional and mixed links. The merge will try to
0109   /// preserve equidistant binning in case bin widths match. Otherwise, the
0110   /// merge falls back to variable binning.
0111   ///
0112   /// 1D merge scenarios:
0113   ///
0114   /// +----------------------------------------------------------+
0115   /// |Colinear                                                  |
0116   /// |                                                          |
0117   /// | +-------+-------+-------+     +-------+-------+-------+  |
0118   /// | |       |       |       |     |       |       |       |  |
0119   /// | |       |       |       |  +  |       |       |       |  |
0120   /// | |       |       |       |     |       |       |       |  |
0121   /// | +-------+-------+-------+     +-------+-------+-------+  |
0122   /// |       +-------+-------+-------+-------+-------+-------+  |
0123   /// |       |       |       |       |       |       |       |  |
0124   /// |    =  |       |       |       |       |       |       |  |
0125   /// |       |       |       |       |       |       |       |  |
0126   /// |       +-------+-------+-------+-------+-------+-------+  |
0127   /// |                                                          |
0128   /// +----------------------------------------------------------+
0129   ///
0130   /// Two grid along a shared direction are merged along their shared direction
0131   ///
0132   /// +-------------------------------------------------+
0133   /// |Parallel                                         |
0134   /// |                                                 |
0135   /// | +-------+      +-------+     +-------+-------+  |
0136   /// | |       |      |       |     |       |       |  |
0137   /// | |       |      |       |     |       |       |  |
0138   /// | |       |      |       |     |       |       |  |
0139   /// | +-------+      +-------+     +-------+-------+  |
0140   /// | |       |      |       |     |       |       |  |
0141   /// | |       |  +   |       |  =  |       |       |  |
0142   /// | |       |      |       |     |       |       |  |
0143   /// | +-------+      +-------+     +-------+-------+  |
0144   /// | |       |      |       |     |       |       |  |
0145   /// | |       |      |       |     |       |       |  |
0146   /// | |       |      |       |     |       |       |  |
0147   /// | +-------+      +-------+     +-------+-------+  |
0148   /// |                                                 |
0149   /// +-------------------------------------------------+
0150   ///
0151   /// Two grids along a shared direction a merged in the direction that is
0152   /// orthogonal to their shared direction.
0153   ///
0154   /// +-------------------------------------------+
0155   /// |Perpendicular                              |
0156   /// |                                           |
0157   /// |  +-------+                                |
0158   /// |  |       |                                |
0159   /// |  |       |                                |
0160   /// |  |       |                                |
0161   /// |  +-------+     +-------+-------+-------+  |
0162   /// |  |       |     |       |       |       |  |
0163   /// |  |       |  +  |       |       |       |  |
0164   /// |  |       |     |       |       |       |  |
0165   /// |  +-------+     +-------+-------+-------+  |
0166   /// |  |       |                                |
0167   /// |  |       |                                |
0168   /// |  |       |     +-------+-------+-------+  |
0169   /// |  +-------+     |       |       |       |  |
0170   /// |                |       |       |       |  |
0171   /// |                |       |       |       |  |
0172   /// |                +-------+-------+-------+  |
0173   /// |                |       |       |       |  |
0174   /// |             =  |       |       |       |  |
0175   /// |                |       |       |       |  |
0176   /// |                +-------+-------+-------+  |
0177   /// |                |       |       |       |  |
0178   /// |                |       |       |       |  |
0179   /// |                |       |       |       |  |
0180   /// |                +-------+-------+-------+  |
0181   /// |                                           |
0182   /// +-------------------------------------------+
0183   ///
0184   /// Two grids whose directions are not shared are merged (ordering does not
0185   /// matter here). The routine will expand one of the grids to match the
0186   /// other's binning, by subdividing the grid in the as-of-yet unbinned
0187   /// direction, while filling all bins with the original bin contents.
0188   /// Afterwards, a conventional mixed-dimension merge is performed.
0189   ///
0190   /// Mixed merge scenarios
0191   /// The order is normalized by always taking the 2D grid as the left hand
0192   /// side. The 1D grid is expanded to match the binning in the as-of-yet
0193   /// unbinned direction with the binning taken from the 2D grid.
0194   ///
0195   /// +-----------------------------------------+
0196   /// |2D + 1D                                  |
0197   /// |                                         |
0198   /// | +-------+-------+     +-------+-------+ |
0199   /// | |       |       |     |       |       | |
0200   /// | |       |       |     |       |       | |
0201   /// | |       |       |     |       |       | |
0202   /// | +-------+-------+     +-------+-------+ |
0203   /// | |       |       |     |       |       | |
0204   /// | |       |       |     |       |       | |
0205   /// | |       |       |     |       |       | |
0206   /// | +-------+-------+  =  +-------+-------+ |
0207   /// | |       |       |     |       |       | |
0208   /// | |       |       |     |       |       | |
0209   /// | |       |       |     |       |       | |
0210   /// | +-------+-------+     +-------+-------+ |
0211   /// |         +             |       |       | |
0212   /// | +-------+-------+     |       |       | |
0213   /// | |       |       |     |       |       | |
0214   /// | |       |       |     +-------+-------+ |
0215   /// | |       |       |                       |
0216   /// | +-------+-------+                       |
0217   /// +-----------------------------------------+
0218   ///
0219   /// +--------------------------------------------------------------+
0220   /// |2D + 1D                                                       |
0221   /// |                                                              |
0222   /// | +-------+-------+    +-------+     +-------+-------+-------+ |
0223   /// | |       |       |    |       |     |       |       |       | |
0224   /// | |       |       |    |       |     |       |       |       | |
0225   /// | |       |       |    |       |     |       |       |       | |
0226   /// | +-------+-------+    +-------+     +-------+-------+-------+ |
0227   /// | |       |       |    |       |     |       |       |       | |
0228   /// | |       |       |  + |       |  =  |       |       |       | |
0229   /// | |       |       |    |       |     |       |       |       | |
0230   /// | +-------+-------+    +-------+     +-------+-------+-------+ |
0231   /// | |       |       |    |       |     |       |       |       | |
0232   /// | |       |       |    |       |     |       |       |       | |
0233   /// | |       |       |    |       |     |       |       |       | |
0234   /// | +-------+-------+    +-------+     +-------+-------+-------+ |
0235   /// |                                                              |
0236   /// +--------------------------------------------------------------+
0237   ///
0238   /// 2D merges
0239   /// The grids need to already share a common axis. If that is not the case,
0240   /// the merge routine returns a `nullptr`. This can be handled by composite
0241   /// merging as a fallback if needed.
0242   /// Ordering and direction does not matter here.
0243   ///
0244   /// +-----------------------------------------+
0245   /// |2D + 2D                                  |
0246   /// |                                         |
0247   /// | +-------+-------+     +-------+-------+ |
0248   /// | |       |       |     |       |       | |
0249   /// | |       |       |     |       |       | |
0250   /// | |       |       |     |       |       | |
0251   /// | +-------+-------+     +-------+-------+ |
0252   /// | |       |       |     |       |       | |
0253   /// | |       |       |  +  |       |       | |
0254   /// | |       |       |     |       |       | |
0255   /// | +-------+-------+     +-------+-------+ |
0256   /// | |       |       |     |       |       | |
0257   /// | |       |       |     |       |       | |
0258   /// | |       |       |     |       |       | |
0259   /// | +-------+-------+     +-------+-------+ |
0260   /// |                                         |
0261   /// |       +-------+-------+-------+-------+ |
0262   /// |       |       |       |       |       | |
0263   /// |       |       |       |       |       | |
0264   /// |       |       |       |       |       | |
0265   /// |       +-------+-------+-------+-------+ |
0266   /// |       |       |       |       |       | |
0267   /// |   =   |       |       |       |       | |
0268   /// |       |       |       |       |       | |
0269   /// |       +-------+-------+-------+-------+ |
0270   /// |       |       |       |       |       | |
0271   /// |       |       |       |       |       | |
0272   /// |       |       |       |       |       | |
0273   /// |       +-------+-------+-------+-------+ |
0274   /// |                                         |
0275   /// +-----------------------------------------+
0276   ///
0277   /// @param a The first grid portal link
0278   /// @param b The second grid portal link
0279   /// @param direction The merging direction
0280   /// @param logger The logger to use for messages
0281   /// @return The merged grid portal link or nullptr if grid merging did not succeed
0282   /// @note The returned pointer can be nullptr, if the grids
0283   ///       given are not mergeable, because their binnings are
0284   ///       not compatible. This is not an error case, and needs
0285   ///       to be handled by th caller! Invalid input is handled
0286   ///       via exceptions.
0287   static std::unique_ptr<PortalLinkBase> merge(
0288       const GridPortalLink& a, const GridPortalLink& b, BinningValue direction,
0289       const Logger& logger = getDummyLogger());
0290 
0291   /// Return the associated grid in a type-erased form
0292   /// @return The grid
0293   virtual const IGrid& grid() const = 0;
0294 
0295   /// Set the volume on all grid bins
0296   /// @param volume The volume to set
0297   virtual void setVolume(TrackingVolume* volume) = 0;
0298 
0299   /// Get the number of dimensions of the grid
0300   /// @return The number of dimensions
0301   virtual unsigned int dim() const = 0;
0302 
0303   /// Expand a 1D grid to a 2D one, by using the provided axis along the
0304   /// *missing* direction.
0305   /// @param other The axis to use for the missing direction,
0306   ///              can be null for auto determination
0307   /// @return A unique pointer to the 2D grid portal link
0308   virtual std::unique_ptr<GridPortalLink> extendTo2d(
0309       const IAxis* other) const = 0;
0310 
0311   /// The binning direction of the grid
0312   /// @note For 2D grids, this will always be the loc0
0313   ///       direction, depending on the surface type.
0314   /// @return The binning direction
0315   BinningValue direction() const { return m_direction; }
0316 
0317   /// Helper function to fill the bin contents after merging.
0318   /// This called by the merging routine, and requires access to the internal
0319   /// grid state.
0320   /// @param a The first grid portal link
0321   /// @param b The second grid portal link
0322   /// @param merged The merged grid portal link
0323   /// @param direction The merging direction
0324   /// @param logger The logger to use for messages
0325   static void fillMergedGrid(const GridPortalLink& a, const GridPortalLink& b,
0326                              GridPortalLink& merged, BinningValue direction,
0327                              const Logger& logger);
0328 
0329   /// Helper function that prints a textual representation of the grid with the
0330   /// volume names.
0331   /// @param os The output stream
0332   void printContents(std::ostream& os) const;
0333 
0334  protected:
0335   /// Helper function to check consistency for grid on a cylinder surface
0336   /// @param cyl The cylinder surface
0337   void checkConsistency(const CylinderSurface& cyl) const;
0338 
0339   /// Helper function to check consistency for grid on a disc surface
0340   /// @param disc The disc surface
0341   void checkConsistency(const DiscSurface& disc) const;
0342 
0343   /// Expand a 1D grid to a 2D one for a cylinder surface
0344   /// @param surface The cylinder surface
0345   /// @param other The axis to use for the missing direction,
0346   ///              can be null for auto determination
0347   /// @return A unique pointer to the 2D grid portal link
0348   std::unique_ptr<GridPortalLink> extendTo2dImpl(
0349       const std::shared_ptr<CylinderSurface>& surface,
0350       const IAxis* other) const;
0351 
0352   /// Expand a 1D grid to a 2D one for a disc surface
0353   /// @param surface The disc surface
0354   /// @param other The axis to use for the missing direction,
0355   ///              can be null for auto determination
0356   /// @return A unique pointer to the 2D grid portal link
0357   std::unique_ptr<GridPortalLink> extendTo2dImpl(
0358       const std::shared_ptr<DiscSurface>& surface, const IAxis* other) const;
0359 
0360   /// Helper enum to declare which local direction to fill
0361   enum class FillDirection {
0362     loc0,
0363     loc1,
0364   };
0365 
0366   /// Helper function to fill a 2D grid from a 1D grid, by extending all
0367   /// bins along the different direction.
0368   /// @param dir The direction to fill
0369   /// @param grid1d The 1D grid
0370   /// @param grid2d The 2D grid
0371   static void fillGrid1dTo2d(FillDirection dir, const GridPortalLink& grid1d,
0372                              GridPortalLink& grid2d);
0373 
0374   /// Index type for type earsed (**slow**) bin access
0375   using IndexType = boost::container::static_vector<std::size_t, 2>;
0376 
0377   /// Helper function to get grid bin count in type-eraased way.
0378   /// @return The number of bins in each direction
0379   virtual IndexType numLocalBins() const = 0;
0380 
0381   /// Helper function to get grid bin content in type-eraased way.
0382   /// @param indices The bin indices
0383   /// @return The tracking volume at the bin
0384   virtual TrackingVolume*& atLocalBins(IndexType indices) = 0;
0385 
0386   /// Helper function to get grid bin content in type-eraased way.
0387   /// @param indices The bin indices
0388   /// @return The tracking volume at the bin
0389   virtual TrackingVolume* atLocalBins(IndexType indices) const = 0;
0390 
0391  private:
0392   BinningValue m_direction;
0393 };
0394 
0395 /// Concrete class deriving from @c GridPortalLink that boxes a concrete grid for lookup.
0396 /// @tparam Axes The axis types of the grid
0397 template <typename... Axes>
0398   requires(sizeof...(Axes) <= 2)
0399 class GridPortalLinkT final : public GridPortalLink {
0400  public:
0401   /// The internal grid type
0402   using GridType = Grid<TrackingVolume*, Axes...>;
0403 
0404   /// The dimension of the grid
0405   static constexpr std::size_t DIM = sizeof...(Axes);
0406 
0407   /// Constructor from a surface, axes and direction
0408   /// @param surface The surface
0409   /// @param direction The binning direction
0410   /// @param axes The axes for the grid
0411   /// @note The axes are checked for consistency with the bounds of @p surface.
0412   GridPortalLinkT(std::shared_ptr<RegularSurface> surface,
0413                   BinningValue direction, Axes&&... axes)
0414       : GridPortalLink(std::move(surface), direction),
0415         m_grid(std::tuple{std::move(axes)...}) {
0416     using enum BinningValue;
0417 
0418     if (const auto* cylinder =
0419             dynamic_cast<const CylinderSurface*>(m_surface.get())) {
0420       checkConsistency(*cylinder);
0421 
0422       if (direction == binRPhi) {
0423         m_projection = &projection<CylinderSurface, binRPhi>;
0424       } else if (direction == binZ) {
0425         m_projection = &projection<CylinderSurface, binZ>;
0426       } else {
0427         throw std::invalid_argument{"Invalid binning direction"};
0428       }
0429 
0430     } else if (const auto* disc =
0431                    dynamic_cast<const DiscSurface*>(m_surface.get())) {
0432       checkConsistency(*disc);
0433 
0434       if (direction == binR) {
0435         m_projection = &projection<DiscSurface, binR>;
0436       } else if (direction == BinningValue::binPhi) {
0437         m_projection = &projection<DiscSurface, binPhi>;
0438       } else {
0439         throw std::invalid_argument{"Invalid binning direction"};
0440       }
0441 
0442     } else {
0443       throw std::logic_error{"Surface type is not supported"};
0444     }
0445   }
0446 
0447   /// Get the grid
0448   /// @return The grid
0449   const GridType& grid() const override { return m_grid; }
0450 
0451   /// Get the grid
0452   /// @return The grid
0453   GridType& grid() { return m_grid; }
0454 
0455   /// Get the number of dimensions of the grid
0456   /// @return The number of dimensions
0457   unsigned int dim() const override { return DIM; }
0458 
0459   /// Prints an identification to the output stream
0460   /// @param os The output stream
0461   void toStream(std::ostream& os) const override {
0462     os << "GridPortalLink<dim=" << dim() << ">";
0463   }
0464 
0465   /// Makes a 2D grid from a 1D grid by extending it using an optional axis
0466   /// @param other The axis to use for the missing direction,
0467   ///              can be null for auto determination
0468   /// @return A unique pointer to the 2D grid portal link
0469   std::unique_ptr<GridPortalLink> extendTo2d(
0470       const IAxis* other) const override {
0471     if constexpr (DIM == 2) {
0472       return std::make_unique<GridPortalLinkT<Axes...>>(*this);
0473     } else {
0474       if (auto cylinder =
0475               std::dynamic_pointer_cast<CylinderSurface>(m_surface)) {
0476         return extendTo2dImpl(cylinder, other);
0477       } else if (auto disc =
0478                      std::dynamic_pointer_cast<DiscSurface>(m_surface)) {
0479         return extendTo2dImpl(disc, other);
0480       } else {
0481         throw std::logic_error{
0482             "Surface type is not supported (this should not happen)"};
0483       }
0484     }
0485   }
0486 
0487   /// Set the volume on all grid bins
0488   /// @param volume The volume to set
0489   void setVolume(TrackingVolume* volume) override {
0490     auto loc = m_grid.numLocalBins();
0491     if constexpr (GridType::DIM == 1) {
0492       for (std::size_t i = 1; i <= loc[0]; i++) {
0493         m_grid.atLocalBins({i}) = volume;
0494       }
0495     } else {
0496       for (std::size_t i = 1; i <= loc[0]; i++) {
0497         for (std::size_t j = 1; j <= loc[1]; j++) {
0498           m_grid.atLocalBins({i, j}) = volume;
0499         }
0500       }
0501     }
0502   }
0503 
0504   /// Lookup the tracking volume at a position on the surface
0505   /// @param gctx The geometry context
0506   /// @param position The local position
0507   /// @param tolerance The tolerance for the lookup
0508   /// @note The position is required to be on the associated surface
0509   /// @return The tracking volume (can be null)
0510   Result<const TrackingVolume*> resolveVolume(
0511       const GeometryContext& gctx, const Vector3& position,
0512       double tolerance = s_onSurfaceTolerance) const override {
0513     auto res = m_surface->globalToLocal(gctx, position, tolerance);
0514     if (!res.ok()) {
0515       return res.error();
0516     }
0517 
0518     const Vector2& local = *res;
0519     return resolveVolume(gctx, local, tolerance);
0520   }
0521 
0522   /// Lookup the tracking volume at a position on the surface
0523   /// @param position The local position
0524   /// @note The position is required to be on the associated surface
0525   /// @return The tracking volume (can be null)
0526   Result<const TrackingVolume*> resolveVolume(
0527       const GeometryContext& /*gctx*/, const Vector2& position,
0528       double /*tolerance*/ = s_onSurfaceTolerance) const override {
0529     assert(surface().insideBounds(position, BoundaryTolerance::None()));
0530     return m_grid.atPosition(m_projection(position));
0531   }
0532 
0533  protected:
0534   /// Type erased access to the number of bins
0535   /// @return The number of bins in each direction
0536   IndexType numLocalBins() const override {
0537     typename GridType::index_t idx = m_grid.numLocalBins();
0538     IndexType result;
0539     for (std::size_t i = 0; i < DIM; i++) {
0540       result.push_back(idx[i]);
0541     }
0542     return result;
0543   }
0544 
0545   /// Type erased local bin access
0546   /// @param indices The bin indices
0547   /// @return The tracking volume at the bin
0548   TrackingVolume*& atLocalBins(IndexType indices) override {
0549     throw_assert(indices.size() == DIM, "Invalid number of indices");
0550     typename GridType::index_t idx;
0551     for (std::size_t i = 0; i < DIM; i++) {
0552       idx[i] = indices[i];
0553     }
0554     return m_grid.atLocalBins(idx);
0555   }
0556 
0557   /// Type erased local bin access
0558   /// @param indices The bin indices
0559   /// @return The tracking volume at the bin
0560   TrackingVolume* atLocalBins(IndexType indices) const override {
0561     throw_assert(indices.size() == DIM, "Invalid number of indices");
0562     typename GridType::index_t idx;
0563     for (std::size_t i = 0; i < DIM; i++) {
0564       idx[i] = indices[i];
0565     }
0566     return m_grid.atLocalBins(idx);
0567   }
0568 
0569  private:
0570   /// Helper function that's assigned to project from the 2D local position to a
0571   /// possible 1D grid.
0572   template <class surface_t, BinningValue direction>
0573   static ActsVector<DIM> projection(const Vector2& position) {
0574     using enum BinningValue;
0575     if constexpr (DIM == 2) {
0576       return position;
0577     } else {
0578       if constexpr (std::is_same_v<surface_t, CylinderSurface>) {
0579         static_assert(direction == binRPhi || direction == binZ,
0580                       "Invalid binning direction");
0581 
0582         if constexpr (direction == binRPhi) {
0583           return ActsVector<1>{position[0]};
0584         } else if constexpr (direction == binZ) {
0585           return ActsVector<1>{position[1]};
0586         }
0587       } else if constexpr (std::is_same_v<surface_t, DiscSurface>) {
0588         static_assert(direction == binR || direction == binPhi,
0589                       "Invalid binning direction");
0590 
0591         if constexpr (direction == binR) {
0592           return ActsVector<1>{position[0]};
0593         } else if constexpr (direction == binPhi) {
0594           return ActsVector<1>{position[1]};
0595         }
0596       } else if constexpr (std::is_same_v<surface_t, PlaneSurface>) {
0597         static_assert(direction == binX || direction == binY,
0598                       "Invalid binning direction");
0599 
0600         if constexpr (direction == binX) {
0601           return ActsVector<1>{position[0]};
0602         } else if constexpr (direction == binY) {
0603           return ActsVector<1>{position[1]};
0604         }
0605       }
0606     }
0607   }
0608 
0609   GridType m_grid;
0610 
0611   /// Stores a function pointer that can project from 2D to 1D if needed
0612   ActsVector<DIM> (*m_projection)(const Vector2& position);
0613 };
0614 
0615 }  // namespace Acts