Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:10:52

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