Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:08:08

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