Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-06 09:25:01

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