Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-18 07:47:37

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.lowerLeftBinEdge(minBin);
0227     m_upperRight = m_cfg.grid.lowerLeftBinEdge(m_cfg.grid.numLocalBins());
0228   }
0229 
0230   /// @brief retrieve field cell for given position
0231   ///
0232   /// @param [in] position global 3D position
0233   /// @return field cell containing the given global position
0234   ///
0235   /// @pre The given @c position must lie within the range of the underlying
0236   ///      magnetic field map.
0237   Result<FieldCell> getFieldCell(const Vector3& position) const {
0238     const auto& gridPosition = m_cfg.transformPos(position);
0239     const auto& indices = m_cfg.grid.localBinsFromPosition(gridPosition);
0240     const auto& lowerLeft = m_cfg.grid.lowerLeftBinEdge(indices);
0241     const auto& upperRight = m_cfg.grid.upperRightBinEdge(indices);
0242 
0243     // loop through all corner points
0244     constexpr std::size_t nCorners = 1 << DIM_POS;
0245     std::array<Vector3, nCorners> neighbors{};
0246     const auto& cornerIndices = m_cfg.grid.closestPointsIndices(gridPosition);
0247 
0248     if (!isInsideLocal(gridPosition)) {
0249       return MagneticFieldError::OutOfBounds;
0250     }
0251 
0252     std::size_t i = 0;
0253     for (std::size_t index : cornerIndices) {
0254       neighbors.at(i++) = m_cfg.transformBField(m_cfg.grid.at(index), position);
0255     }
0256 
0257     assert(i == nCorners);
0258 
0259     return FieldCell(lowerLeft, upperRight, std::move(neighbors));
0260   }
0261 
0262   /// @brief get the number of bins for all axes of the field map
0263   ///
0264   /// @return vector returning number of bins for all field map axes
0265   std::vector<std::size_t> getNBins() const final {
0266     auto nBinsArray = m_cfg.grid.numLocalBins();
0267     return std::vector<std::size_t>(nBinsArray.begin(), nBinsArray.end());
0268   }
0269 
0270   /// @brief get the minimum value of all axes of the field map
0271   ///
0272   /// @return vector returning the minima of all field map axes
0273   std::vector<double> getMin() const final {
0274     return std::vector<double>(m_lowerLeft.begin(), m_lowerLeft.end());
0275   }
0276 
0277   /// @brief get the maximum value of all axes of the field map
0278   ///
0279   /// @return vector returning the maxima of all field map axes
0280   std::vector<double> getMax() const final {
0281     return std::vector<double>(m_upperRight.begin(), m_upperRight.end());
0282   }
0283 
0284   /// @brief check whether given 3D position is inside look-up domain
0285   ///
0286   /// @param [in] position global 3D position
0287   /// @return @c true if position is inside the defined look-up grid,
0288   ///         otherwise @c false
0289   bool isInside(const Vector3& position) const final {
0290     return isInsideLocal(m_cfg.transformPos(position));
0291   }
0292 
0293   /// @brief check whether given 3D position is inside look-up domain
0294   ///
0295   /// @param [in] gridPosition local N-D position
0296   /// @return @c true if position is inside the defined look-up grid,
0297   ///         otherwise @c false
0298   bool isInsideLocal(const Vector<DIM_POS>& gridPosition) const {
0299     for (unsigned int i = 0; i < DIM_POS; ++i) {
0300       if (gridPosition[i] < m_lowerLeft[i] ||
0301           gridPosition[i] >= m_upperRight[i]) {
0302         return false;
0303       }
0304     }
0305     return true;
0306   }
0307 
0308   /// @brief Get a const reference on the underlying grid structure
0309   ///
0310   /// @return grid reference
0311   const Grid& getGrid() const { return m_cfg.grid; }
0312 
0313   /// @copydoc MagneticFieldProvider::makeCache(const MagneticFieldContext&) const
0314   MagneticFieldProvider::Cache makeCache(
0315       const MagneticFieldContext& mctx) const final {
0316     return MagneticFieldProvider::Cache{std::in_place_type<Cache>, mctx};
0317   }
0318 
0319   /// @brief retrieve field at given position
0320   ///
0321   /// @param [in] position global 3D position
0322   /// @return magnetic field value at the given position
0323   ///
0324   /// @pre The given @c position must lie within the range of the underlying
0325   ///      magnetic field map.
0326   Result<Vector3> getField(const Vector3& position) const {
0327     const auto gridPosition = m_cfg.transformPos(position);
0328     if (!isInsideLocal(gridPosition)) {
0329       return Result<Vector3>::failure(MagneticFieldError::OutOfBounds);
0330     }
0331 
0332     return Result<Vector3>::success(
0333         m_cfg.transformBField(m_cfg.grid.interpolate(gridPosition), position));
0334   }
0335 
0336   /// Get magnetic field value without bounds checking (faster).
0337   ///
0338   /// @param position Global 3D position for the lookup
0339   /// @return Magnetic field value at the given position
0340   ///
0341   /// @warning No bounds checking is performed. The caller must ensure
0342   ///          the position is within the valid range of the field map.
0343   Vector3 getFieldUnchecked(const Vector3& position) const final {
0344     const auto gridPosition = m_cfg.transformPos(position);
0345     return m_cfg.transformBField(m_cfg.grid.interpolate(gridPosition),
0346                                  position);
0347   }
0348 
0349   /// @copydoc MagneticFieldProvider::getField(const Vector3&,MagneticFieldProvider::Cache&) const
0350   Result<Vector3> getField(const Vector3& position,
0351                            MagneticFieldProvider::Cache& cache) const final {
0352     Cache& lcache = cache.as<Cache>();
0353     const auto gridPosition = m_cfg.transformPos(position);
0354     if (!lcache.fieldCell || !(*lcache.fieldCell).isInside(gridPosition)) {
0355       auto res = getFieldCell(position);
0356       if (!res.ok()) {
0357         return Result<Vector3>::failure(res.error());
0358       }
0359       lcache.fieldCell = *res;
0360     }
0361     return Result<Vector3>::success((*lcache.fieldCell).getField(gridPosition));
0362   }
0363 
0364  private:
0365   Config m_cfg;
0366 
0367   typename Grid::point_t m_lowerLeft;
0368   typename Grid::point_t m_upperRight;
0369 };
0370 
0371 /// @}
0372 
0373 }  // namespace Acts