Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-16 08:19:45

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 #include "Acts/MagneticField/BFieldMapUtils.hpp"
0010 
0011 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0012 #include "Acts/MagneticField/SolenoidBField.hpp"
0013 #include "Acts/MagneticField/ToroidField.hpp"
0014 #include "Acts/Utilities/Axis.hpp"
0015 #include "Acts/Utilities/Grid.hpp"
0016 #include "Acts/Utilities/Helpers.hpp"
0017 #include "Acts/Utilities/VectorHelpers.hpp"
0018 
0019 #include <cmath>
0020 #include <cstddef>
0021 #include <cstdlib>
0022 #include <limits>
0023 #include <stdexcept>
0024 
0025 namespace Acts {
0026 
0027 using VectorHelpers::perp;
0028 using VectorHelpers::phi;
0029 
0030 InterpolatedBFieldMap<
0031     Grid<Vector2, Axis<AxisType::Equidistant>, Axis<AxisType::Equidistant>>>
0032 fieldMapRZ(const std::function<std::size_t(std::array<std::size_t, 2> binsRZ,
0033                                            std::array<std::size_t, 2> nBinsRZ)>&
0034                localToGlobalBin,
0035            std::vector<double> rPos, std::vector<double> zPos,
0036            const std::vector<Vector2>& bField, double lengthUnit,
0037            double BFieldUnit, bool firstQuadrant) {
0038   // [1] Create Grid
0039   const auto [rMin, rMax, rBinCount] = detail::getMinMaxAndBinCount(rPos);
0040   auto [zMin, zMax, zBinCount] = detail::getMinMaxAndBinCount(zPos);
0041 
0042   const std::size_t nBinsR = rBinCount;
0043   std::size_t nBinsZ = zBinCount;
0044 
0045   if (firstQuadrant) {
0046     zMin = -zPos[nBinsZ - 1];
0047     nBinsZ = 2 * nBinsZ - 1;
0048   }
0049 
0050   // Create the axis for the grid
0051   Axis rAxis(rMin * lengthUnit, rMax * lengthUnit, nBinsR);
0052   Axis zAxis(zMin * lengthUnit, zMax * lengthUnit, nBinsZ);
0053 
0054   // Create the grid
0055   Grid grid(Type<Vector2>, std::move(rAxis), std::move(zAxis));
0056   using Grid_t = decltype(grid);
0057 
0058   // [2] Set the bField values
0059   const std::array<std::size_t, 2> nIndices = {{rBinCount, zBinCount}};
0060   for (std::size_t i = 1; i <= nBinsR; ++i) {
0061     for (std::size_t j = 1; j <= nBinsZ; ++j) {
0062       Grid_t::index_t indices = {{i, j}};
0063       // std::vectors begin with 0 and we do not want the user needing to take
0064       // underflow or overflow bins in account this is why we need to subtract
0065       // by one
0066       if (firstQuadrant) {
0067         std::size_t n = std::abs(static_cast<std::ptrdiff_t>(j) -
0068                                  static_cast<std::ptrdiff_t>(zBinCount));
0069 
0070         grid.atLocalBins(indices) =
0071             bField.at(localToGlobalBin({{i - 1, n}}, nIndices)) * BFieldUnit;
0072       } else {
0073         grid.atLocalBins(indices) =
0074             bField.at(localToGlobalBin({{i - 1, j - 1}}, nIndices)) *
0075             BFieldUnit;
0076       }
0077     }
0078   }
0079   grid.setExteriorBins(Vector2::Zero());
0080 
0081   // [3] Create the transformation for the position map (x,y,z) -> (r,z)
0082   auto transformPos = [](const Vector3& pos) {
0083     return Vector2(perp(pos), pos.z());
0084   };
0085 
0086   // [4] Create the transformation for the bField map (Br,Bz) -> (Bx,By,Bz)
0087   auto transformBField = [](const Vector2& field, const Vector3& pos) {
0088     const double rSinTheta2 = pos.x() * pos.x() + pos.y() * pos.y();
0089     double cosPhi = 1.;
0090     double sinPhi = 0.;
0091 
0092     if (rSinTheta2 > std::numeric_limits<double>::min()) {
0093       const double invRsinTheta = 1. / std::sqrt(rSinTheta2);
0094       cosPhi = pos.x() * invRsinTheta;
0095       sinPhi = pos.y() * invRsinTheta;
0096     }
0097 
0098     return Vector3(field.x() * cosPhi, field.x() * sinPhi, field.y());
0099   };
0100 
0101   // [5] Create the mapper & BField Service create field mapping
0102   return InterpolatedBFieldMap<Grid_t>(
0103       {transformPos, transformBField, std::move(grid)});
0104 }
0105 
0106 InterpolatedBFieldMap<
0107     Grid<Vector3, Axis<AxisType::Equidistant>, Axis<AxisType::Equidistant>,
0108          Axis<AxisType::Equidistant>>>
0109 fieldMapXYZ(
0110     const std::function<std::size_t(std::array<std::size_t, 3> binsXYZ,
0111                                     std::array<std::size_t, 3> nBinsXYZ)>&
0112         localToGlobalBin,
0113     std::vector<double> xPos, std::vector<double> yPos,
0114     std::vector<double> zPos, const std::vector<Vector3>& bField,
0115     double lengthUnit, double BFieldUnit, bool firstOctant) {
0116   // [1] Create Grid
0117   auto [xMin, xMax, xBinCount] = detail::getMinMaxAndBinCount(xPos);
0118   auto [yMin, yMax, yBinCount] = detail::getMinMaxAndBinCount(yPos);
0119   auto [zMin, zMax, zBinCount] = detail::getMinMaxAndBinCount(zPos);
0120 
0121   std::size_t nBinsX = xBinCount;
0122   std::size_t nBinsY = yBinCount;
0123   std::size_t nBinsZ = zBinCount;
0124 
0125   if (firstOctant) {
0126     xMin = -xPos[nBinsX - 1];
0127     nBinsX = 2 * nBinsX - 1;
0128     yMin = -yPos[nBinsY - 1];
0129     nBinsY = 2 * nBinsY - 1;
0130     zMin = -zPos[nBinsZ - 1];
0131     nBinsZ = 2 * nBinsZ - 1;
0132   }
0133 
0134   Axis xAxis(xMin * lengthUnit, xMax * lengthUnit, nBinsX);
0135   Axis yAxis(yMin * lengthUnit, yMax * lengthUnit, nBinsY);
0136   Axis zAxis(zMin * lengthUnit, zMax * lengthUnit, nBinsZ);
0137   // Create the grid
0138   Grid grid(Type<Vector3>, std::move(xAxis), std::move(yAxis),
0139             std::move(zAxis));
0140   using Grid_t = decltype(grid);
0141 
0142   // [2] Set the bField values
0143   const std::array<std::size_t, 3> nIndices = {
0144       {xBinCount, yBinCount, zBinCount}};
0145 
0146   auto calcAbsDiff = [](std::size_t val, std::size_t binCount) {
0147     return std::abs(static_cast<std::ptrdiff_t>(val) -
0148                     static_cast<std::ptrdiff_t>(binCount));
0149   };
0150 
0151   for (std::size_t i = 1; i <= nBinsX; ++i) {
0152     for (std::size_t j = 1; j <= nBinsY; ++j) {
0153       for (std::size_t k = 1; k <= nBinsZ; ++k) {
0154         Grid_t::index_t indices = {{i, j, k}};
0155         // std::vectors begin with 0 and we do not want the user needing to take
0156         // underflow or overflow bins in account this is why we need to subtract
0157         // by one
0158         if (firstOctant) {
0159           const std::size_t l = calcAbsDiff(i, xBinCount);
0160           const std::size_t m = calcAbsDiff(j, yBinCount);
0161           const std::size_t n = calcAbsDiff(k, zBinCount);
0162 
0163           grid.atLocalBins(indices) =
0164               bField.at(localToGlobalBin({{l, m, n}}, nIndices)) * BFieldUnit;
0165         } else {
0166           grid.atLocalBins(indices) =
0167               bField.at(localToGlobalBin({{i - 1, j - 1, k - 1}}, nIndices)) *
0168               BFieldUnit;
0169         }
0170       }
0171     }
0172   }
0173   grid.setExteriorBins(Vector3::Zero());
0174 
0175   // [3] Create the transformation for the position map (x,y,z) -> (r,z)
0176   auto transformPos = [](const Vector3& pos) { return pos; };
0177 
0178   // [4] Create the transformation for the BField map (Bx,By,Bz) -> (Bx,By,Bz)
0179   auto transformBField = [](const Vector3& field, const Vector3& /*pos*/) {
0180     return field;
0181   };
0182 
0183   // [5] Create the mapper & BField Service create field mapping
0184   return InterpolatedBFieldMap<Grid_t>(
0185       {transformPos, transformBField, std::move(grid)});
0186 }
0187 
0188 InterpolatedBFieldMap<
0189     Grid<Vector2, Axis<AxisType::Equidistant>, Axis<AxisType::Equidistant>>>
0190 solenoidFieldMap(const std::pair<double, double>& rLim,
0191                  const std::pair<double, double>& zLim,
0192                  const std::pair<std::size_t, std::size_t>& nBins,
0193                  const SolenoidBField& field) {
0194   auto [rMin, rMax] = rLim;
0195   auto [zMin, zMax] = zLim;
0196   const auto [nBinsR, nBinsZ] = nBins;
0197 
0198   double stepZ = std::abs(zMax - zMin) / (nBinsZ - 1);
0199   double stepR = std::abs(rMax - rMin) / (nBinsR - 1);
0200   rMax += stepR;
0201   zMax += stepZ;
0202 
0203   // Create the axis for the grid
0204   Axis rAxis(rMin, rMax, nBinsR);
0205   Axis zAxis(zMin, zMax, nBinsZ);
0206 
0207   // Create the grid
0208   Grid grid(Type<Vector2>, std::move(rAxis), std::move(zAxis));
0209   using Grid_t = decltype(grid);
0210 
0211   // Create the transformation for the position map (x,y,z) -> (r,z)
0212   auto transformPos = [](const Vector3& pos) {
0213     return Vector2(perp(pos), pos.z());
0214   };
0215 
0216   // Create the transformation for the bField map (Br,Bz) -> (Bx,By,Bz)
0217   auto transformBField = [](const Vector2& bField, const Vector3& pos) {
0218     const double rSinTheta2 = pos.x() * pos.x() + pos.y() * pos.y();
0219     double cosPhi = 1.;
0220     double sinPhi = 0.;
0221 
0222     if (rSinTheta2 > std::numeric_limits<double>::min()) {
0223       const double invRsinTheta = 1. / std::sqrt(rSinTheta2);
0224       cosPhi = pos.x() * invRsinTheta;
0225       sinPhi = pos.y() * invRsinTheta;
0226     }
0227 
0228     return Vector3(bField.x() * cosPhi, bField.x() * sinPhi, bField.y());
0229   };
0230 
0231   // iterate over all bins, set their value to the solenoid value at their lower
0232   // left position
0233   for (std::size_t i = 0; i <= nBinsR + 1; i++) {
0234     for (std::size_t j = 0; j <= nBinsZ + 1; j++) {
0235       Grid_t::index_t index({i, j});
0236       if (i == 0 || j == 0 || i == nBinsR + 1 || j == nBinsZ + 1) {
0237         // under or overflow bin, set zero
0238         grid.atLocalBins(index) = Grid_t::value_type(0, 0);
0239       } else {
0240         // regular bin, get lower left boundary
0241         Grid_t::point_t lowerLeft = grid.multiAxis().getLowerLeftBinEdge(index);
0242         // do lookup
0243         Vector2 B = field.getField(Vector2(lowerLeft[0], lowerLeft[1]));
0244         grid.atLocalBins(index) = B;
0245       }
0246     }
0247   }
0248 
0249   // Create the mapper & BField Service create field mapping
0250   InterpolatedBFieldMap<Grid_t> map(
0251       {transformPos, transformBField, std::move(grid)});
0252   return map;
0253 }
0254 
0255 InterpolatedBFieldMap<
0256     Grid<Vector3, Axis<AxisType::Equidistant>, Axis<AxisType::Equidistant>,
0257          Axis<AxisType::Equidistant>>>
0258 toroidFieldMapCyl(
0259     const std::pair<double, double>& rLim,
0260     const std::pair<double, double>& phiLim,
0261     const std::pair<double, double>& zLim,
0262     const std::tuple<std::size_t, std::size_t, std::size_t>& nBins,
0263     const ToroidField& field) {
0264   // Create magnetic field context and cache
0265   MagneticFieldContext ctx;
0266   auto cache = field.makeCache(ctx);
0267 
0268   auto [rMin, rMax] = rLim;
0269   auto [pMin, pMax] = phiLim;  // radians
0270   auto [zMin, zMax] = zLim;
0271   const auto& [nBinsR, nBinsP, nBinsZ] = nBins;
0272 
0273   // Guard against degenerate configs
0274   if (nBinsR < 2 || nBinsP < 2 || nBinsZ < 2) {
0275     throw std::invalid_argument(
0276         "toroidFieldMapCyl: each dimension needs at least 2 bins");
0277   }
0278 
0279   // Follow solenoid style: extend upper edge by one step to accommodate
0280   // overflow bin edges
0281   double stepR = std::abs(rMax - rMin) / static_cast<double>(nBinsR - 1);
0282   double stepP = std::abs(pMax - pMin) / static_cast<double>(nBinsP - 1);
0283   double stepZ = std::abs(zMax - zMin) / static_cast<double>(nBinsZ - 1);
0284   rMax += stepR;
0285   pMax += stepP;
0286   zMax += stepZ;
0287 
0288   // Axes
0289   Axis rAxis(rMin, rMax, nBinsR);
0290   Axis pAxis(pMin, pMax, nBinsP);
0291   Axis zAxis(zMin, zMax, nBinsZ);
0292 
0293   // Grid stores (Br, Bphi, Bz)
0294   Grid grid(Type<Vector3>, std::move(rAxis), std::move(pAxis),
0295             std::move(zAxis));
0296   using Grid_t = decltype(grid);
0297 
0298   // (x,y,z) -> (r,phi,z)
0299   auto transformPos = [](const Vector3& pos) {
0300     const double r2 = pos.x() * pos.x() + pos.y() * pos.y();
0301     const double r = std::sqrt(r2);
0302     double phi = 0.0;
0303     if (r2 > std::numeric_limits<double>::min()) {
0304       phi = std::atan2(pos.y(), pos.x());  // in (-pi, pi]
0305     }
0306     return Vector3(r, phi,
0307                    pos.z());  // note: we reuse Vector3 to carry (r,phi,z)
0308   };
0309 
0310   // (Br,Bphi,Bz) + Cartesian pos -> (Bx,By,Bz)
0311   auto transformBField = [](const Vector3& bCyl, const Vector3& pos) {
0312     const double r2 = pos.x() * pos.x() + pos.y() * pos.y();
0313     double cosPhi = 1.0;
0314     double sinPhi = 0.0;
0315     if (r2 > std::numeric_limits<double>::min()) {
0316       const double invR = 1.0 / std::sqrt(r2);
0317       cosPhi = pos.x() * invR;
0318       sinPhi = pos.y() * invR;
0319     }
0320     const double Br = bCyl.x();
0321     const double Bphi = bCyl.y();
0322     const double Bz = bCyl.z();
0323     // Cyl->Cart
0324     const double Bx = Br * cosPhi - Bphi * sinPhi;
0325     const double By = Br * sinPhi + Bphi * cosPhi;
0326     return Vector3(Bx, By, Bz);
0327   };
0328 
0329   // Fill bins (including under/overflow halo as zeros)
0330   for (std::size_t ir = 0; ir <= nBinsR + 1; ++ir) {
0331     for (std::size_t ip = 0; ip <= nBinsP + 1; ++ip) {
0332       for (std::size_t iz = 0; iz <= nBinsZ + 1; ++iz) {
0333         Grid_t::index_t index({ir, ip, iz});
0334         if (ir == 0 || ip == 0 || iz == 0 || ir == nBinsR + 1 ||
0335             ip == nBinsP + 1 || iz == nBinsZ + 1) {
0336           grid.atLocalBins(index) = Grid_t::value_type(0.0, 0.0, 0.0);
0337         } else {
0338           const Grid_t::point_t ll =
0339               grid.multiAxis().getLowerLeftBinEdge(index);  // (r,phi,z)
0340           const double r = ll[0];
0341           const double phi = ll[1];
0342           const double z = ll[2];
0343 
0344           const double x = r * std::cos(phi);
0345           const double y = r * std::sin(phi);
0346 
0347           // Query original field (Cartesian)
0348           auto res = field.getField(Vector3(x, y, z), cache);
0349 
0350           Vector3 Bxyz(0.0, 0.0, 0.0);
0351           if (res.ok()) {
0352             Bxyz = *res;
0353           }
0354 
0355           // Convert to cylindrical components to store
0356           double cosPhi =
0357               (r > std::numeric_limits<double>::min()) ? std::cos(phi) : 1.0;
0358           double sinPhi =
0359               (r > std::numeric_limits<double>::min()) ? std::sin(phi) : 0.0;
0360 
0361           const double Br = Bxyz.x() * cosPhi + Bxyz.y() * sinPhi;
0362           const double Bphi = -Bxyz.x() * sinPhi + Bxyz.y() * cosPhi;
0363           const double Bz = Bxyz.z();
0364 
0365           grid.atLocalBins(index) = Grid_t::value_type(Br, Bphi, Bz);
0366         }
0367       }
0368     }
0369   }
0370 
0371   // Mapper
0372   InterpolatedBFieldMap<Grid_t> map(
0373       {transformPos, transformBField, std::move(grid)});
0374   return map;
0375 }
0376 
0377 InterpolatedBFieldMap<
0378     Grid<Vector3, Axis<AxisType::Equidistant>, Axis<AxisType::Equidistant>,
0379          Axis<AxisType::Equidistant>>>
0380 toroidFieldMapXYZ(
0381     const std::pair<double, double>& xLim,
0382     const std::pair<double, double>& yLim,
0383     const std::pair<double, double>& zLim,
0384     const std::tuple<std::size_t, std::size_t, std::size_t>& nBins,
0385     const ToroidField& field) {
0386   // Create magnetic field context and cache
0387   MagneticFieldContext ctx;
0388   auto cache = field.makeCache(ctx);
0389 
0390   auto [xMin, xMax] = xLim;
0391   auto [yMin, yMax] = yLim;
0392   auto [zMin, zMax] = zLim;
0393   const auto& [nBinsX, nBinsY, nBinsZ] = nBins;
0394 
0395   if (nBinsX < 2 || nBinsY < 2 || nBinsZ < 2) {
0396     throw std::invalid_argument(
0397         "toroidFieldMapXYZ: each dimension needs at least 2 bins");
0398   }
0399 
0400   double stepX = std::abs(xMax - xMin) / static_cast<double>(nBinsX - 1);
0401   double stepY = std::abs(yMax - yMin) / static_cast<double>(nBinsY - 1);
0402   double stepZ = std::abs(zMax - zMin) / static_cast<double>(nBinsZ - 1);
0403   xMax += stepX;
0404   yMax += stepY;
0405   zMax += stepZ;
0406 
0407   Axis xAxis(xMin, xMax, nBinsX);
0408   Axis yAxis(yMin, yMax, nBinsY);
0409   Axis zAxis(zMin, zMax, nBinsZ);
0410 
0411   Grid grid(Type<Vector3>, std::move(xAxis), std::move(yAxis),
0412             std::move(zAxis));
0413   using Grid_t = decltype(grid);
0414 
0415   // Identity position transform: (x,y,z) -> (x,y,z)
0416   auto transformPos = [](const Vector3& pos) { return pos; };
0417 
0418   // Identity B-field transform: table already stores (Bx,By,Bz)
0419   auto transformBField = [](const Vector3& bField, const Vector3& /*pos*/) {
0420     return bField;
0421   };
0422 
0423   for (std::size_t ix = 0; ix <= nBinsX + 1; ++ix) {
0424     for (std::size_t iy = 0; iy <= nBinsY + 1; ++iy) {
0425       for (std::size_t iz = 0; iz <= nBinsZ + 1; ++iz) {
0426         Grid_t::index_t index({ix, iy, iz});
0427         if (ix == 0 || iy == 0 || iz == 0 || ix == nBinsX + 1 ||
0428             iy == nBinsY + 1 || iz == nBinsZ + 1) {
0429           grid.atLocalBins(index) = Grid_t::value_type(0.0, 0.0, 0.0);
0430         } else {
0431           const Grid_t::point_t ll =
0432               grid.multiAxis().getLowerLeftBinEdge(index);
0433           const double x = ll[0];
0434           const double y = ll[1];
0435           const double z = ll[2];
0436 
0437           auto res = field.getField(Vector3(x, y, z), cache);
0438           Vector3 B(0.0, 0.0, 0.0);
0439           if (res.ok()) {
0440             B = *res;  // already (Bx,By,Bz)
0441           }
0442           grid.atLocalBins(index) = Grid_t::value_type(B[0], B[1], B[2]);
0443         }
0444       }
0445     }
0446   }
0447 
0448   InterpolatedBFieldMap<Grid_t> map(
0449       {transformPos, transformBField, std::move(grid)});
0450   return map;
0451 }
0452 
0453 }  // namespace Acts