Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-28 07:45:39

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 // This file is part of the ACTS project.
0010 //
0011 // Copyright (C) 2016 CERN
0012 //
0013 // MPL 2.0: https://mozilla.org/MPL/2.0/
0014 
0015 #include "Acts/MagneticField/ToroidField.hpp"
0016 
0017 #include "Acts/Definitions/Units.hpp"
0018 
0019 #include <cmath>
0020 #include <numbers>
0021 #include <stdexcept>
0022 
0023 namespace Acts {
0024 
0025 //-------------------------
0026 // Ctors
0027 //-------------------------
0028 
0029 ToroidField::ToroidField() : ToroidField(Config{}) {}
0030 
0031 ToroidField::ToroidField(Config cfg) : m_cfg(std::move(cfg)) {
0032   // Fill default signs if not provided
0033   const int nC = m_cfg.layout.nCoils;
0034   if (m_cfg.barrelSigns.empty()) {
0035     m_cfg.barrelSigns.assign(nC, +1);
0036   }
0037   if (m_cfg.ectSigns.empty()) {
0038     m_cfg.ectSigns.assign(2 * nC, +1);
0039   }
0040   // Sanity on sizes
0041   if (static_cast<int>(m_cfg.barrelSigns.size()) != nC) {
0042     throw std::invalid_argument(
0043         "ToroidField: barrelSigns size must equal nCoils");
0044   }
0045   if (static_cast<int>(m_cfg.ectSigns.size()) != 2 * nC) {
0046     throw std::invalid_argument(
0047         "ToroidField: ectSigns size must equal 2*nCoils");
0048   }
0049 
0050   buildGeometry();
0051 }
0052 
0053 //-------------------------
0054 // MagneticFieldProvider
0055 //-------------------------
0056 
0057 MagneticFieldProvider::Cache ToroidField::makeCache(
0058     const MagneticFieldContext& mctx) const {
0059   static_cast<void>(mctx);
0060   return MagneticFieldProvider::Cache(std::in_place_type<Cache>);
0061 }
0062 
0063 Result<Vector3> ToroidField::getField(
0064     const Vector3& position, MagneticFieldProvider::Cache& cache) const {
0065   (void)cache;
0066 
0067   const double X = position.x();
0068   const double Y = position.y();
0069   const double Z = position.z();
0070 
0071   double bx = 0.0;
0072   double by = 0.0;
0073   double bz = 0.0;
0074 
0075   constexpr double mu0 = 4e-7 * std::numbers::pi;  // [T·m/A]
0076   const double prefBarrel =
0077       (mu0 * static_cast<double>(m_cfg.barrel.Nturns) * m_cfg.barrel.I) /
0078       (4.0 * std::numbers::pi);
0079   const double prefECT =
0080       (mu0 * static_cast<double>(m_cfg.ect.Nturns) * m_cfg.ect.I) /
0081       (4.0 * std::numbers::pi);
0082   const double eps = m_cfg.layout.eps;
0083 
0084   // Split computations
0085   accumulateBarrelField(X, Y, Z, eps, prefBarrel, bx, by, bz);
0086   accumulateEndcapField(X, Y, Z, eps, prefECT, bx, by, bz);
0087 
0088   return Result<Vector3>::success(Vector3(bx, by, bz));
0089 }
0090 
0091 //-------------------------
0092 // Private helpers
0093 //-------------------------
0094 
0095 void ToroidField::accumulateBarrelField(double X, double Y, double Z,
0096                                         double eps, double pref, double& bx,
0097                                         double& by, double& bz) const {
0098   for (std::size_t i = 0; i < m_mids_barrel.size(); ++i) {
0099     const auto& mid = m_mids_barrel[i];
0100     const auto& dl = m_segs_barrel[i];
0101 
0102     const double rx = X - mid[0];
0103     const double ry = Y - mid[1];
0104     const double rz = Z - mid[2];
0105 
0106     const double r2 = rx * rx + ry * ry + rz * rz + eps;
0107     const double invr = 1.0 / std::sqrt(r2);
0108     const double invr3 = invr / r2;
0109 
0110     const double cx = dl[1] * rz - dl[2] * ry;
0111     const double cy = dl[2] * rx - dl[0] * rz;
0112     const double cz = dl[0] * ry - dl[1] * rx;
0113 
0114     bx += pref * cx * invr3;
0115     by += pref * cy * invr3;
0116     bz += pref * cz * invr3;
0117   }
0118 }
0119 
0120 void ToroidField::accumulateEndcapField(double X, double Y, double Z,
0121                                         double eps, double pref, double& bx,
0122                                         double& by, double& bz) const {
0123   for (std::size_t i = 0; i < m_mids_ect.size(); ++i) {
0124     const auto& mid = m_mids_ect[i];
0125     const auto& dl = m_segs_ect[i];
0126 
0127     const double rx = X - mid[0];
0128     const double ry = Y - mid[1];
0129     const double rz = Z - mid[2];
0130 
0131     const double r2 = rx * rx + ry * ry + rz * rz + eps;
0132     const double invr = 1.0 / std::sqrt(r2);
0133     const double invr3 = invr / r2;
0134 
0135     const double cx = dl[1] * rz - dl[2] * ry;
0136     const double cy = dl[2] * rx - dl[0] * rz;
0137     const double cz = dl[0] * ry - dl[1] * rx;
0138 
0139     bx += pref * cx * invr3;
0140     by += pref * cy * invr3;
0141     bz += pref * cz * invr3;
0142   }
0143 }
0144 
0145 // End-cap racetrack with LONG straights along z (kept for parity with
0146 // header-only version)
0147 std::vector<std::array<float, 2>> ToroidField::ectRacetrackRadial(
0148     float Lrho, float Lz, int nArc, int nStraight, bool close) {
0149   const float rr = 0.5f * Lrho;  // radial half-span
0150   const float rz = 0.5f * Lz;    // axial half-length
0151   std::vector<std::array<float, 2>> pts;
0152   pts.reserve(
0153       static_cast<std::size_t>(2 * nArc + 2 * nStraight + (close ? 1 : 0)));
0154 
0155   // Straight at ρ = +rr : z from +rz -> -rz (exclude endpoint)
0156   for (int i = 0; i < nStraight; ++i) {
0157     const float t = static_cast<float>(i) / static_cast<float>(nStraight);
0158     const float z = (+rz) * (1.0f - t) + (-rz) * t;
0159     pts.push_back({+rr, z});
0160   }
0161   // Inner arc at z = -rz : θ: +π/2 → -π/2 (exclude endpoint) ; ρ = rr*sin(θ)
0162   for (int i = 0; i < nArc; ++i) {
0163     const float th = static_cast<float>(std::numbers::pi / 2) +
0164                      (static_cast<float>(i) / static_cast<float>(nArc)) *
0165                          static_cast<float>(-std::numbers::pi);
0166     const float rho = rr * std::sin(th);
0167     const float z = -rz;
0168     pts.push_back({rho, z});
0169   }
0170   // Straight at ρ = -rr : z from -rz -> +rz (exclude endpoint)
0171   for (int i = 0; i < nStraight; ++i) {
0172     const float t = static_cast<float>(i) / static_cast<float>(nStraight);
0173     const float z = (-rz) * (1.0f - t) + (+rz) * t;
0174     pts.push_back({-rr, z});
0175   }
0176   // Outer arc at z = +rz : θ: -π/2 → +π/2 (close if requested)
0177   for (int i = 0; i < nArc; ++i) {
0178     const float th = -static_cast<float>(std::numbers::pi) / 2 +
0179                      (static_cast<float>(i) / static_cast<float>(nArc)) *
0180                          static_cast<float>(std::numbers::pi);
0181     const float rho = rr * std::sin(th);
0182     const float z = +rz;
0183     pts.push_back({rho, z});
0184   }
0185   if (close &&
0186       (pts.front()[0] != pts.back()[0] || pts.front()[1] != pts.back()[1])) {
0187     pts.push_back(pts.front());
0188   }
0189   return pts;
0190 }
0191 
0192 // Rounded-rectangle racetrack in local (ρ, z)
0193 std::vector<std::array<double, 2>> ToroidField::racetrackRZ(double a, double b,
0194                                                             double Lz, int nArc,
0195                                                             int nStraight,
0196                                                             bool close) {
0197   const double r = 0.5 * b;     // corner radius
0198   const double rz = 0.5 * Lz;   // axial half-length
0199   const double zTop = rz - r;   // top straight z
0200   const double zBot = -rz + r;  // bottom straight z
0201 
0202   std::vector<std::array<double, 2>> pts;
0203   pts.reserve(
0204       static_cast<std::size_t>(2 * nArc + 2 * nStraight + (close ? 1 : 0)));
0205 
0206   // +ρ straight (top → bottom), ρ = +a/2
0207   for (int i = 0; i < nStraight; ++i) {
0208     const double t = static_cast<double>(i) / static_cast<double>(nStraight);
0209     const double z = zTop * (1.0 - t) + zBot * t;
0210     pts.push_back({+0.5 * a, z});
0211   }
0212   // bottom-right quarter
0213   for (int i = 0; i < nArc; ++i) {
0214     const double t = static_cast<double>(i) / static_cast<double>(nArc);
0215     const double th = -std::numbers::pi / 2 - t * (std::numbers::pi / 2);
0216     const double cx = +0.5 * a - r;  // ρ-center
0217     const double cz = -rz + r;       // z-center
0218     const double rho = cx + r * std::cos(th);
0219     const double z = cz + r * std::sin(th);
0220     pts.push_back({rho, z});
0221   }
0222   // −ρ straight (bottom → top), ρ = −a/2
0223   for (int i = 0; i < nStraight; ++i) {
0224     const double t = static_cast<double>(i) / static_cast<double>(nStraight);
0225     const double z = zBot * (1.0 - t) + zTop * t;
0226     pts.push_back({-0.5 * a, z});
0227   }
0228   // top-left quarter
0229   for (int i = 0; i < nArc; ++i) {
0230     const double t = static_cast<double>(i) / static_cast<double>(nArc);
0231     const double th = +std::numbers::pi / 2 - t * (std::numbers::pi / 2);
0232     const double cx = -0.5 * a + r;  // ρ-center
0233     const double cz = +rz - r;       // z-center
0234     const double rho = cx + r * std::cos(th);
0235     const double z = cz + r * std::sin(th);
0236     pts.push_back({rho, z});
0237   }
0238   if (close &&
0239       (pts.front()[0] != pts.back()[0] || pts.front()[1] != pts.back()[1])) {
0240     pts.push_back(pts.front());
0241   }
0242   return pts;
0243 }
0244 
0245 void ToroidField::buildSegsMidsRZ(const std::vector<std::array<double, 2>>& rz,
0246                                   std::vector<std::array<double, 2>>& d_rz,
0247                                   std::vector<std::array<double, 2>>& m_rz) {
0248   d_rz.clear();
0249   m_rz.clear();
0250   d_rz.reserve(rz.size() - 1);
0251   m_rz.reserve(rz.size() - 1);
0252   for (std::size_t i = 0; i + 1 < rz.size(); ++i) {
0253     const double dr = rz[i + 1][0] - rz[i][0];
0254     const double dz = rz[i + 1][1] - rz[i][1];
0255     d_rz.push_back({dr, dz});
0256     m_rz.push_back(
0257         {0.5f * (rz[i + 1][0] + rz[i][0]), 0.5 * (rz[i + 1][1] + rz[i][1])});
0258   }
0259 }
0260 
0261 void ToroidField::mapRingToXYZ(double l,
0262                                const std::vector<std::array<double, 2>>& m_rz,
0263                                const std::vector<std::array<double, 2>>& d_rz,
0264                                double phi, int sign, double zShift,
0265                                std::vector<std::array<double, 3>>& mids_out,
0266                                std::vector<std::array<double, 3>>& segs_out) {
0267   const double ct = std::cos(phi);
0268   const double st = std::sin(phi);
0269   const double s = (sign >= 0) ? 1.0 : -1.0;
0270 
0271   for (const auto& rm : m_rz) {
0272     const double rho = rm[0];
0273     const double zz = rm[1] + zShift;
0274     const double rxy = l + rho;
0275     mids_out.push_back({rxy * ct, rxy * st, zz});
0276   }
0277   for (const auto& dlrz : d_rz) {
0278     const double dr = dlrz[0];
0279     const double dz = dlrz[1];
0280     segs_out.push_back({s * (dr * ct), s * (dr * st), s * dz});
0281   }
0282 }
0283 
0284 void ToroidField::buildGeometry() {
0285   // ---- Barrel base curve ----
0286   const double rEndB = 0.5 * m_cfg.barrel.b;
0287   const double lB = 0.5 * (m_cfg.barrel.R_in + m_cfg.barrel.R_out);
0288   const double aB = (m_cfg.barrel.R_out - m_cfg.barrel.R_in) - 2.0 * rEndB;
0289 
0290   const auto rz_barrel =
0291       racetrackRZ(aB, m_cfg.barrel.b, m_cfg.barrel.c, m_cfg.layout.nArc,
0292                   m_cfg.layout.nStraight, m_cfg.layout.closeLoop);
0293   std::vector<std::array<double, 2>> d_rzB;
0294   std::vector<std::array<double, 2>> m_rzB;
0295   buildSegsMidsRZ(rz_barrel, d_rzB, m_rzB);
0296 
0297   // ---- ECT base curve ----
0298   const double lE = 0.5 * (m_cfg.ect.R_in + m_cfg.ect.R_out) / UnitConstants::m;
0299   const double rEndECT = 0.5 * m_cfg.ect.b / UnitConstants::m;
0300   const double aE =
0301       (m_cfg.ect.R_out - m_cfg.ect.R_in) / UnitConstants::m - 2.0 * rEndECT;
0302   const auto rz_ect = racetrackRZ(
0303       /*a=*/aE, /*b=*/m_cfg.ect.b / UnitConstants::m,
0304       /*Lz=*/m_cfg.ect.c / UnitConstants::m, m_cfg.layout.nArc,
0305       m_cfg.layout.nStraight, m_cfg.layout.closeLoop);
0306   std::vector<std::array<double, 2>> d_rzE;
0307   std::vector<std::array<double, 2>> m_rzE;
0308   buildSegsMidsRZ(rz_ect, d_rzE, m_rzE);
0309 
0310   // ---- Angles ----
0311   const int nC = m_cfg.layout.nCoils;
0312   const double th0 = static_cast<float>(m_cfg.layout.theta0);
0313   const double dth = static_cast<float>(m_cfg.layout.thetaStep);
0314 
0315   // ---- Reserve & fill ----
0316   m_mids_barrel.clear();
0317   m_segs_barrel.clear();
0318   m_mids_ect.clear();
0319   m_segs_ect.clear();
0320 
0321   m_mids_barrel.reserve(static_cast<std::size_t>(nC) * m_rzB.size());
0322   m_segs_barrel.reserve(static_cast<std::size_t>(nC) * d_rzB.size());
0323   m_mids_ect.reserve(static_cast<std::size_t>(2 * nC) * m_rzE.size());
0324   m_segs_ect.reserve(static_cast<std::size_t>(2 * nC) * d_rzE.size());
0325 
0326   // Barrel rings with signs
0327   for (int k = 0; k < nC; ++k) {
0328     const double phi = th0 + static_cast<double>(k) * dth;
0329     const int sign = m_cfg.barrelSigns[k];
0330     mapRingToXYZ(lB, m_rzB, d_rzB, phi, sign, /*zShift=*/0.0, m_mids_barrel,
0331                  m_segs_barrel);
0332   }
0333 
0334   // Endcap centers (overlap placement)
0335   const float zECT =
0336       0.5f * static_cast<float>(m_cfg.barrel.c / UnitConstants::m) -
0337       0.5f * static_cast<float>(m_cfg.ect.c / UnitConstants::m) +
0338       2.0f * static_cast<float>(m_cfg.ect.gap / UnitConstants::m);
0339 
0340   // +z endcap (indices 0..nC-1 in ectSigns)
0341   for (int k = 0; k < nC; ++k) {
0342     const double phi = th0 + static_cast<double>(k) * dth;
0343     const int sign = m_cfg.ectSigns[k];
0344     mapRingToXYZ(lE, m_rzE, d_rzE, phi, sign, /*zShift=*/+zECT, m_mids_ect,
0345                  m_segs_ect);
0346   }
0347   // -z endcap (indices nC..2*nC-1 in ectSigns)
0348   for (int k = 0; k < nC; ++k) {
0349     const double phi = th0 + static_cast<double>(k) * dth;
0350     const int sign = m_cfg.ectSigns[nC + k];
0351     mapRingToXYZ(lE, m_rzE, d_rzE, phi, sign, /*zShift=*/-zECT, m_mids_ect,
0352                  m_segs_ect);
0353   }
0354 }
0355 
0356 }  // namespace Acts