Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-15 08:18:20

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/Algebra.hpp"
0012 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0013 #include "Acts/MagneticField/MagneticFieldError.hpp"
0014 #include "Acts/MagneticField/MagneticFieldProvider.hpp"
0015 #include "Acts/Utilities/Interpolation.hpp"
0016 #include "Acts/Utilities/Result.hpp"
0017 
0018 #include <functional>
0019 #include <optional>
0020 #include <vector>
0021 
0022 namespace Acts {
0023 
0024 /// @addtogroup magnetic_field
0025 /// @{
0026 
0027 /// @brief Base class for interpolated magnetic field providers
0028 ///
0029 /// This class can be used for non-trivial magnetic field implementations.
0030 ///
0031 /// The key idea here is to calculate an interpolated value of the magnetic
0032 /// field from a grid of known field values. In 3D, this means the
0033 /// interpolation is done from the 8 corner points of a *field cell*. The field
0034 /// cell can be retrieved for any given position. Since during typical access
0035 /// patterns, e.g. the propagation, subsequent steps are relatively likely to
0036 /// not cross the field cell boundary, the field cell can be cached.
0037 ///
0038 /// @image html bfield/field_cell.svg "Illustration of the field cell concept. Subsequent steps are clustered in the same field cell. The field cell only needs to be refetched when the propagation crosses into the next grid region." width=60%
0039 ///
0040 class InterpolatedMagneticField : public MagneticFieldProvider {
0041  public:
0042   /// @brief get the number of bins for all axes of the field map
0043   ///
0044   /// @return vector returning number of bins for all field map axes
0045   virtual std::vector<std::size_t> getNBins() const = 0;
0046 
0047   /// @brief get the minimum value of all axes of the field map
0048   ///
0049   /// @return vector returning the minima of all field map axes
0050   virtual std::vector<double> getMin() const = 0;
0051 
0052   /// @brief get the maximum value of all axes of the field map
0053   ///
0054   /// @return vector returning the maxima of all field map axes
0055   virtual std::vector<double> getMax() const = 0;
0056 
0057   /// @brief check whether given 3D position is inside look-up domain
0058   ///
0059   /// @param [in] position global 3D position
0060   /// @return @c true if position is inside the defined look-up grid,
0061   ///         otherwise @c false
0062   virtual bool isInside(const Vector3& position) const = 0;
0063 
0064   /// Get a field value without checking if the lookup position is within the
0065   /// interpolation domain.
0066   ///
0067   /// @param position The lookup position in 3D
0068   /// @return The field value at @p position
0069   virtual Vector3 getFieldUnchecked(const Vector3& position) const = 0;
0070 };
0071 
0072 /// Interpolates magnetic field value from field values on a given grid
0073 ///
0074 /// This class implements a magnetic field service which is initialized by a
0075 /// field map defined by:
0076 /// - a list of field values on a regular grid in some n-dimensional space,
0077 /// - a transformation of global 3D coordinates onto this n-dimensional
0078 /// space.
0079 /// - a transformation of local n-Dimensional magnetic field coordinates into
0080 /// global (cartesian) 3D coordinates
0081 ///
0082 /// The magnetic field value for a given global position is then determined by:
0083 /// - mapping the position onto the grid,
0084 /// - looking up the magnetic field values on the closest grid points,
0085 /// - doing a linear interpolation of these magnetic field values.
0086 ///
0087 /// Internally, this class uses a *field interpolation cell* to speed up
0088 /// lookups. This cell contains the interpolation points so the grid does not
0089 /// have to be consulted for each lookup. Explicit methods to create such a
0090 /// field cell are provided, but field cell creation is automatically handled
0091 /// by @ref Acts::InterpolatedBFieldMap::makeCache, opaque to the client.
0092 ///
0093 /// This class can leverage spatial symmetries in the magnetic field
0094 /// distribution. For cylindrically symmetric fields (e.g., solenoids, toroids),
0095 /// a 2D rz map can be used instead of a full 3D xyz map, significantly reducing
0096 /// memory
0097 /// requirements and improving performance. Helper functions @ref Acts::fieldMapRZ
0098 /// and @ref Acts::fieldMapXYZ are provided to construct field maps with the
0099 /// appropriate symmetries.
0100 ///
0101 /// @tparam grid_t The Grid type which provides the field storage and
0102 /// interpolation
0103 template <typename grid_t>
0104 class InterpolatedBFieldMap : public InterpolatedMagneticField {
0105  public:
0106   /// Type alias for magnetic field grid
0107   using Grid = grid_t;
0108   /// Type alias for magnetic field vector type
0109   using FieldType = typename Grid::value_type;
0110   /// Dimensionality of the position space for field interpolation
0111   static constexpr std::size_t DIM_POS = Grid::DIM;
0112 
0113   /// @brief struct representing smallest grid unit in magnetic field grid
0114   ///
0115   /// This type encapsulates all required information to perform linear
0116   /// interpolation of magnetic field values within a confined spatial region
0117   /// (hyper-box). The cell stores field values at all corner points and
0118   /// performs interpolation for any position within the cell boundaries.
0119   /// This allows for efficient repeated lookups within the same grid cell
0120   /// without consulting the full grid structure.
0121   struct FieldCell {
0122     /// number of corner points defining the confining hyper-box
0123     static constexpr unsigned int N = 1 << DIM_POS;
0124 
0125    public:
0126     /// @brief default constructor
0127     ///
0128     /// @param [in] lowerLeft   generalized lower-left corner of hyper box
0129     ///                         (containing the minima of the hyper box along
0130     ///                         each Dimension)
0131     /// @param [in] upperRight  generalized upper-right corner of hyper box
0132     ///                         (containing the maxima of the hyper box along
0133     ///                         each Dimension)
0134     /// @param [in] fieldValues field values at the hyper box corners sorted in
0135     ///                         the canonical order defined in Acts::interpolate
0136     FieldCell(std::array<double, DIM_POS> lowerLeft,
0137               std::array<double, DIM_POS> upperRight,
0138               std::array<Vector3, N> fieldValues)
0139         : m_lowerLeft(std::move(lowerLeft)),
0140           m_upperRight(std::move(upperRight)),
0141           m_fieldValues(std::move(fieldValues)) {}
0142 
0143     /// @brief retrieve field at given position
0144     ///
0145     /// @param [in] position global 3D position
0146     /// @return magnetic field value at the given position
0147     ///
0148     /// @pre The given @c position must lie within the current field cell.
0149     Vector3 getField(const Vector<DIM_POS>& position) const {
0150       // defined in Interpolation.hpp
0151       return interpolate(position, m_lowerLeft, m_upperRight, m_fieldValues);
0152     }
0153 
0154     /// @brief check whether given 3D position is inside this field cell
0155     ///
0156     /// @param [in] position global 3D position
0157     /// @return @c true if position is inside the current field cell,
0158     ///         otherwise @c false
0159     bool isInside(const Vector<DIM_POS>& position) const {
0160       for (unsigned int i = 0; i < DIM_POS; ++i) {
0161         if (position[i] < m_lowerLeft[i] || position[i] >= m_upperRight[i]) {
0162           return false;
0163         }
0164       }
0165       return true;
0166     }
0167 
0168    private:
0169     /// generalized lower-left corner of the confining hyper-box
0170     std::array<double, DIM_POS> m_lowerLeft;
0171 
0172     /// generalized upper-right corner of the confining hyper-box
0173     std::array<double, DIM_POS> m_upperRight;
0174 
0175     /// @brief magnetic field vectors at the hyper-box corners
0176     ///
0177     /// @note These values must be order according to the prescription detailed
0178     ///       in Acts::interpolate.
0179     std::array<Vector3, N> m_fieldValues;
0180   };
0181 
0182   /// @brief Cache for field cell to improve performance of field lookups
0183   ///
0184   /// This cache stores the current field cell which contains the interpolation
0185   /// data for a confined region of space. By caching the cell, subsequent
0186   /// lookups at nearby positions (e.g., during track propagation) can avoid
0187   /// expensive grid queries. The cache automatically updates when a position
0188   /// outside the current cell is queried.
0189   struct Cache {
0190     /// @brief Constructor with magnetic field context
0191     explicit Cache(const MagneticFieldContext& /*mctx*/) {}
0192 
0193     /// Stored field cell containing interpolation data
0194     std::optional<FieldCell> fieldCell;
0195     /// Flag indicating if the cache has been initialized
0196     bool initialized = false;
0197   };
0198 
0199   /// @brief  Config structure for the interpolated B field map
0200   struct Config {
0201     /// @brief mapping of global 3D coordinates (cartesian)
0202     /// onto grid space
0203     std::function<Vector<DIM_POS>(const Vector3&)> transformPos;
0204 
0205     /// @brief calculating the global 3D coordinates
0206     /// (cartesian) of the magnetic field with the local n dimensional field and
0207     /// the global 3D position as input
0208     std::function<Vector3(const FieldType&, const Vector3&)> transformBField;
0209 
0210     /// @brief grid storing magnetic field values
0211     Grid grid;
0212 
0213     /// @brief global B-field scaling factor
0214     ///
0215     /// @note Negative values for @p scale are accepted and will invert the
0216     ///       direction of the magnetic field.
0217     double scale = 1.;
0218   };
0219 
0220   /// @brief default constructor
0221   ///
0222   /// @param cfg Configuration containing grid and scaling factor
0223   explicit InterpolatedBFieldMap(Config cfg) : m_cfg{std::move(cfg)} {
0224     typename Grid::index_t minBin{};
0225     minBin.fill(1);
0226     m_lowerLeft = m_cfg.grid.multiAxis().getLowerLeftBinEdge(minBin);
0227     m_upperRight = m_cfg.grid.multiAxis().getLowerLeftBinEdge(
0228         m_cfg.grid.multiAxis().getNBins());
0229   }
0230 
0231   /// @brief retrieve field cell for given position
0232   ///
0233   /// @param [in] position global 3D position
0234   /// @return field cell containing the given global position
0235   ///
0236   /// @pre The given @c position must lie within the range of the underlying
0237   ///      magnetic field map.
0238   Result<FieldCell> getFieldCell(const Vector3& position) const {
0239     const auto& gridPosition = m_cfg.transformPos(position);
0240     const auto& indices =
0241         m_cfg.grid.multiAxis().getLocalBinsFromPoint(gridPosition);
0242     const auto& lowerLeft = m_cfg.grid.multiAxis().getLowerLeftBinEdge(indices);
0243     const auto& upperRight =
0244         m_cfg.grid.multiAxis().getUpperRightBinEdge(indices);
0245 
0246     // loop through all corner points
0247     constexpr std::size_t nCorners = 1 << DIM_POS;
0248     std::array<Vector3, nCorners> neighbors{};
0249     const auto& cornerIndices =
0250         m_cfg.grid.multiAxis().getClosestPointsIndices(gridPosition);
0251 
0252     if (!isInsideLocal(gridPosition)) {
0253       return MagneticFieldError::OutOfBounds;
0254     }
0255 
0256     std::size_t i = 0;
0257     for (std::size_t index : cornerIndices) {
0258       neighbors.at(i++) = m_cfg.transformBField(m_cfg.grid.at(index), position);
0259     }
0260 
0261     assert(i == nCorners);
0262 
0263     return FieldCell(lowerLeft, upperRight, std::move(neighbors));
0264   }
0265 
0266   /// @brief get the number of bins for all axes of the field map
0267   ///
0268   /// @return vector returning number of bins for all field map axes
0269   std::vector<std::size_t> getNBins() const final {
0270     auto nBinsArray = m_cfg.grid.multiAxis().getNBins();
0271     return std::vector<std::size_t>(nBinsArray.begin(), nBinsArray.end());
0272   }
0273 
0274   /// @brief get the minimum value of all axes of the field map
0275   ///
0276   /// @return vector returning the minima of all field map axes
0277   std::vector<double> getMin() const final {
0278     return std::vector<double>(m_lowerLeft.begin(), m_lowerLeft.end());
0279   }
0280 
0281   /// @brief get the maximum value of all axes of the field map
0282   ///
0283   /// @return vector returning the maxima of all field map axes
0284   std::vector<double> getMax() const final {
0285     return std::vector<double>(m_upperRight.begin(), m_upperRight.end());
0286   }
0287 
0288   /// @brief check whether given 3D position is inside look-up domain
0289   ///
0290   /// @param [in] position global 3D position
0291   /// @return @c true if position is inside the defined look-up grid,
0292   ///         otherwise @c false
0293   bool isInside(const Vector3& position) const final {
0294     return isInsideLocal(m_cfg.transformPos(position));
0295   }
0296 
0297   /// @brief check whether given 3D position is inside look-up domain
0298   ///
0299   /// @param [in] gridPosition local N-D position
0300   /// @return @c true if position is inside the defined look-up grid,
0301   ///         otherwise @c false
0302   bool isInsideLocal(const Vector<DIM_POS>& gridPosition) const {
0303     for (unsigned int i = 0; i < DIM_POS; ++i) {
0304       if (gridPosition[i] < m_lowerLeft[i] ||
0305           gridPosition[i] >= m_upperRight[i]) {
0306         return false;
0307       }
0308     }
0309     return true;
0310   }
0311 
0312   /// @brief Get a const reference on the underlying grid structure
0313   ///
0314   /// @return grid reference
0315   const Grid& getGrid() const { return m_cfg.grid; }
0316 
0317   /// @copydoc MagneticFieldProvider::makeCache(const MagneticFieldContext&) const
0318   MagneticFieldProvider::Cache makeCache(
0319       const MagneticFieldContext& mctx) const final {
0320     return MagneticFieldProvider::Cache{std::in_place_type<Cache>, mctx};
0321   }
0322 
0323   /// @brief retrieve field at given position
0324   ///
0325   /// @param [in] position global 3D position
0326   /// @return magnetic field value at the given position
0327   ///
0328   /// @pre The given @c position must lie within the range of the underlying
0329   ///      magnetic field map.
0330   Result<Vector3> getField(const Vector3& position) const {
0331     const auto gridPosition = m_cfg.transformPos(position);
0332     if (!isInsideLocal(gridPosition)) {
0333       return Result<Vector3>::failure(MagneticFieldError::OutOfBounds);
0334     }
0335 
0336     return Result<Vector3>::success(
0337         m_cfg.transformBField(m_cfg.grid.interpolate(gridPosition), position));
0338   }
0339 
0340   /// Get magnetic field value without bounds checking (faster).
0341   ///
0342   /// @param position Global 3D position for the lookup
0343   /// @return Magnetic field value at the given position
0344   ///
0345   /// @warning No bounds checking is performed. The caller must ensure
0346   ///          the position is within the valid range of the field map.
0347   Vector3 getFieldUnchecked(const Vector3& position) const final {
0348     const auto gridPosition = m_cfg.transformPos(position);
0349     return m_cfg.transformBField(m_cfg.grid.interpolate(gridPosition),
0350                                  position);
0351   }
0352 
0353   /// @copydoc MagneticFieldProvider::getField(const Vector3&,MagneticFieldProvider::Cache&) const
0354   Result<Vector3> getField(const Vector3& position,
0355                            MagneticFieldProvider::Cache& cache) const final {
0356     Cache& lcache = cache.as<Cache>();
0357     const auto gridPosition = m_cfg.transformPos(position);
0358     if (!lcache.fieldCell || !(*lcache.fieldCell).isInside(gridPosition)) {
0359       auto res = getFieldCell(position);
0360       if (!res.ok()) {
0361         return Result<Vector3>::failure(res.error());
0362       }
0363       lcache.fieldCell = *res;
0364     }
0365     return Result<Vector3>::success((*lcache.fieldCell).getField(gridPosition));
0366   }
0367 
0368  private:
0369   Config m_cfg;
0370 
0371   typename Grid::point_t m_lowerLeft;
0372   typename Grid::point_t m_upperRight;
0373 };
0374 
0375 /// @}
0376 
0377 }  // namespace Acts