File indexing completed on 2026-07-16 08:08:08
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
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
0027
0028
0029 ToroidalField::ToroidalField() : ToroidalField(Config{}) {}
0030
0031 ToroidalField::ToroidalField(Config cfg) : m_cfg(std::move(cfg)) {
0032
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
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
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;
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
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
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
0149
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;
0153 const float rz = 0.5f * Lz;
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
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
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
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
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
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;
0202 const float rz = 0.5f * Lz;
0203 const float zTop = rz - r;
0204 const float zBot = -rz + r;
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
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
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;
0222 const float cz = -rz + r;
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
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
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;
0239 const float cz = +rz - r;
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
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
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 aE, static_cast<float>(m_cfg.ect.b / UnitConstants::m),
0313 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
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
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
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, 0.0f, m_mids_barrel,
0339 m_segs_barrel);
0340 }
0341
0342
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
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, +zECT, m_mids_ect,
0353 m_segs_ect);
0354 }
0355
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, -zECT, m_mids_ect,
0360 m_segs_ect);
0361 }
0362 }
0363
0364 }