13#include "G4ThreeVector.hh"
14#include "CLHEP/Units/SystemOfUnits.h"
35std::string GField_AsciiMapFactory::param_string(
const std::string& key,
const std::string& dflt)
const {
40double GField_AsciiMapFactory::param_g4number(
const std::string& key,
const std::string& dflt)
const {
44std::string GField_AsciiMapFactory::field_maps_directory()
const {
48 if (dladdr(
reinterpret_cast<const void*
>(&
GFieldFactory), &info) != 0 && info.dli_fname !=
nullptr) {
49 const std::string plugin_path = info.dli_fname;
50 const auto slash = plugin_path.find_last_of(
'/');
51 if (slash != std::string::npos) {
return plugin_path.substr(0, slash) +
"/../fields"; }
53 log->
warning(
"GField_AsciiMapFactory: could not resolve plugin path; using ./fields for the map.");
57int GField_AsciiMapFactory::axis_of_coordinate(
const std::string& name)
const {
59 case Symmetry::dipole_x:
60 case Symmetry::dipole_y:
61 case Symmetry::dipole_z:
62 if (name ==
"longitudinal")
return 0;
63 if (name ==
"transverse")
return 1;
68 if (name ==
"transverse")
return 0;
69 if (name ==
"longitudinal")
return 1;
71 case Symmetry::phi_segmented:
72 if (name ==
"azimuthal")
return 0;
73 if (name ==
"transverse")
return 1;
74 if (name ==
"longitudinal")
return 2;
76 case Symmetry::cartesian_3d:
77 case Symmetry::cartesian_3d_quadrant:
78 if (name ==
"X")
return 0;
79 if (name ==
"Y")
return 1;
80 if (name ==
"Z")
return 2;
91void GField_AsciiMapFactory::load_coordinate(
const std::string& key) {
92 const std::string value = param_string(key,
"");
95 "GField_AsciiMapFactory: missing coordinate <", key,
"> for field <",
101 if (tokens.size() != 4) {
103 "GField_AsciiMapFactory: coordinate <", key,
"> must be \"name, npoints, min, max\". Got <",
107 const std::string name = tokens[0];
108 const int npoints = std::stoi(tokens[1]);
112 const int axis = axis_of_coordinate(name);
115 "GField_AsciiMapFactory: coordinate name <", name,
"> is not valid for symmetry <",
120 "GField_AsciiMapFactory: coordinate <", name,
"> needs at least 2 points; got ", npoints,
".");
123 np[axis] =
static_cast<unsigned>(npoints);
124 startMap[axis] = min;
126 cellSize[axis] = (max - min) / (npoints - 1);
130 const std::string unit = [&]() {
131 for (
const std::string& expr : {tokens[3], tokens[2]}) {
132 const auto star = expr.find(
'*');
133 if (star != std::string::npos) {
137 return std::string(
"mm");
143void GField_AsciiMapFactory::load_map_file() {
144 const std::string map_name = param_string(
"map",
"");
145 if (map_name.empty()) {
154 std::string path = map_name;
155 if (map_name.find(
'/') == std::string::npos) {
156 std::vector<std::string> candidates;
157 if (
const std::string dir = param_string(
"dir",
""); !dir.empty()) { candidates.push_back(dir); }
159 candidates.push_back(field_maps_directory());
162 for (
const auto& dir : candidates) {
163 const std::string trial = dir +
"/" + map_name;
164 if (std::ifstream(trial).good()) { path = trial;
break; }
168 "GField_AsciiMapFactory: cannot find map <", map_name,
"> for field <",
173 std::ifstream in(path);
177 log->
info(1,
"Loading ASCII field map <", path,
"> with symmetry <",
181 std::size_t total = np[0];
182 for (
int d = 1; d < ndim; ++d) { total *= np[d]; }
184 const double field_scale = param_g4number(
"scale",
"1") *
187 B1.assign(total, 0.0f);
188 if (ncomp >= 2) { B2.assign(total, 0.0f); }
189 if (ncomp >= 3) { B3.assign(total, 0.0f); }
191 const double tolerance = 0.001;
193 std::size_t read_points = 0;
196 while (std::getline(in,
line)) {
199 if (trimmed.empty() || trimmed[0] ==
'#') {
continue; }
201 std::istringstream tokens(trimmed);
205 unsigned grid_index[3] = {0, 0, 0};
207 for (
int c = 0; c < ndim; ++c) {
209 if (!(tokens >> raw)) { line_ok =
false;
break; }
210 const double coord = raw * columns[c].unitFactor;
211 const int axis = columns[c].axis;
212 const unsigned i =
static_cast<unsigned>(
213 std::floor((coord - startMap[axis] + cellSize[axis] / 2) / cellSize[axis]));
214 grid_index[axis] = i;
216 const double expected = startMap[axis] + i * cellSize[axis];
217 if (cellSize[axis] != 0.0 && std::fabs(expected - coord) > tolerance * std::fabs(cellSize[axis])) {
218 log->
warning(
"GField_AsciiMapFactory: ", path,
":", line_number,
219 " axis ", axis,
" value ", coord,
" does not match grid point ", expected,
".");
223 double comp[3] = {0.0, 0.0, 0.0};
224 for (
int k = 0; k < ncomp && line_ok; ++k) {
225 if (!(tokens >> comp[k])) { line_ok =
false; }
229 " has fewer than ", ndim + ncomp,
" numbers.");
232 std::size_t flat = (ndim == 2) ? idx2(grid_index[0], grid_index[1])
233 : idx3(grid_index[0], grid_index[1], grid_index[2]);
235 log->
warning(
"GField_AsciiMapFactory: ", path,
":", line_number,
" maps outside the grid; skipped.");
239 B1[flat] =
static_cast<float>(comp[0] * field_scale);
240 if (ncomp >= 2) { B2[flat] =
static_cast<float>(comp[1] * field_scale); }
241 if (ncomp >= 3) { B3[flat] =
static_cast<float>(comp[2] * field_scale); }
245 if (read_points != total) {
246 log->
warning(
"GField_AsciiMapFactory: ", path,
" provided ", read_points,
247 " points but the grid expects ", total,
".");
256 const std::string sym = param_string(
"symmetry",
"");
257 if (sym ==
"dipole-x") { symmetry = Symmetry::dipole_x; ndim = 2; ncomp = 1; }
258 else if (sym ==
"dipole-y") { symmetry = Symmetry::dipole_y; ndim = 2; ncomp = 1; }
259 else if (sym ==
"dipole-z") { symmetry = Symmetry::dipole_z; ndim = 2; ncomp = 1; }
260 else if (sym ==
"cylindrical-x") { symmetry = Symmetry::cyl_x; ndim = 2; ncomp = 2; }
261 else if (sym ==
"cylindrical-y") { symmetry = Symmetry::cyl_y; ndim = 2; ncomp = 2; }
262 else if (sym ==
"cylindrical-z") { symmetry = Symmetry::cyl_z; ndim = 2; ncomp = 2; }
263 else if (sym ==
"phi-segmented") { symmetry = Symmetry::phi_segmented; ndim = 3; ncomp = 3; }
264 else if (sym ==
"cartesian_3D") { symmetry = Symmetry::cartesian_3d; ndim = 3; ncomp = 3; }
265 else if (sym ==
"cartesian_3D_quadrant") { symmetry = Symmetry::cartesian_3d_quadrant; ndim = 3; ncomp = 3; }
268 "GField_AsciiMapFactory: unknown symmetry <", sym,
"> for field <",
274 columns.reserve(ndim);
275 load_coordinate(
"coordinate1");
276 load_coordinate(
"coordinate2");
277 if (ndim == 3) { load_coordinate(
"coordinate3"); }
280 linear = (param_string(
"interpolation",
"linear") !=
"none");
283 mapOrigin[0] = param_g4number(
"vx",
"0");
284 mapOrigin[1] = param_g4number(
"vy",
"0");
285 mapOrigin[2] = param_g4number(
"vz",
"0");
286 mapRotation[0] = param_g4number(
"rx",
"0*deg");
287 mapRotation[1] = param_g4number(
"ry",
"0*deg");
288 mapRotation[2] = param_g4number(
"rz",
"0*deg");
289 sinAlpha = std::sin(mapRotation[0]); cosAlpha = std::cos(mapRotation[0]);
290 sinBeta = std::sin(mapRotation[1]); cosBeta = std::cos(mapRotation[1]);
291 sinGamma = std::sin(mapRotation[2]); cosGamma = std::cos(mapRotation[2]);
302 bfield[0] = bfield[1] = bfield[2] = 0.0;
304 if (std::isnan(pos[0]) || std::isnan(pos[1]) || std::isnan(pos[2])) {
305 log->
warning(
"GField_AsciiMapFactory: field coordinates requested are nan.");
310 const double x[3] = {pos[0] - mapOrigin[0], pos[1] - mapOrigin[1], pos[2] - mapOrigin[2]};
313 case Symmetry::dipole_x:
314 case Symmetry::dipole_y:
315 case Symmetry::dipole_z:
316 value_dipole(x, bfield);
318 case Symmetry::cyl_x:
319 case Symmetry::cyl_y:
320 case Symmetry::cyl_z:
321 value_cylindrical(x, bfield);
323 case Symmetry::phi_segmented:
324 value_phi_segmented(x, bfield);
326 case Symmetry::cartesian_3d:
327 case Symmetry::cartesian_3d_quadrant:
328 value_cartesian3d(x, bfield);
333 pos[0] / cm,
", ", pos[1] / cm,
", ", pos[2] / cm,
") cm = (",
334 bfield[0] / gauss,
", ", bfield[1] / gauss,
", ", bfield[2] / gauss,
") gauss");
338void GField_AsciiMapFactory::value_dipole(
const double x[3],
double* bfield)
const {
340 double LC = 0.0, TC = 0.0;
341 if (symmetry == Symmetry::dipole_z) { TC = std::fabs(x[0]); LC = x[1]; }
342 else if (symmetry == Symmetry::dipole_x) { TC = std::fabs(x[1]); LC = x[2]; }
343 else { TC = std::fabs(x[0]); LC = x[2]; }
345 if (LC < startMap[0] || TC < startMap[1]) {
return; }
346 unsigned IL =
static_cast<unsigned>(std::floor((LC - startMap[0]) / cellSize[0]));
347 unsigned IT =
static_cast<unsigned>(std::floor((TC - startMap[1]) / cellSize[1]));
348 if (IL >= np[0] - 1 || IT >= np[1] - 1) {
return; }
352 if (std::fabs(startMap[0] + IL * cellSize[0] - LC) > std::fabs(startMap[0] + (IL + 1) * cellSize[0] - LC)) IL++;
353 if (std::fabs(startMap[1] + IT * cellSize[1] - TC) > std::fabs(startMap[1] + (IT + 1) * cellSize[1] - TC)) IT++;
354 b = B1[idx2(IL, IT)];
357 const double xlr = (LC - (startMap[0] + IL * cellSize[0])) / cellSize[0];
358 const double xtr = (TC - (startMap[1] + IT * cellSize[1])) / cellSize[1];
359 const double b10 = B1[idx2(IL, IT)] * (1.0 - xtr) + B1[idx2(IL, IT + 1)] * xtr;
360 const double b11 = B1[idx2(IL + 1, IT)] * (1.0 - xtr) + B1[idx2(IL + 1, IT + 1)] * xtr;
361 b = b10 * (1.0 - xlr) + b11 * xlr;
364 if (symmetry == Symmetry::dipole_x) { bfield[0] = b; }
365 else if (symmetry == Symmetry::dipole_y) { bfield[1] = b; }
366 else { bfield[2] = b; }
368 rotate_field(bfield);
372void GField_AsciiMapFactory::value_cylindrical(
const double x[3],
double* bfield)
const {
374 double LC = 0.0, TC = 0.0, phi = 0.0;
375 if (symmetry == Symmetry::cyl_z) {
376 LC = x[2]; TC = std::sqrt(x[0] * x[0] + x[1] * x[1]); phi = G4ThreeVector(x[0], x[1], x[2]).phi();
378 else if (symmetry == Symmetry::cyl_x) {
379 LC = x[0]; TC = std::sqrt(x[1] * x[1] + x[2] * x[2]); phi = G4ThreeVector(x[1], x[2], x[0]).phi();
382 LC = x[1]; TC = std::sqrt(x[0] * x[0] + x[2] * x[2]); phi = G4ThreeVector(x[2], x[0], x[1]).phi();
385 if (TC < startMap[0] || LC < startMap[1]) {
return; }
386 unsigned IT =
static_cast<unsigned>(std::floor((TC - startMap[0]) / cellSize[0]));
387 unsigned IL =
static_cast<unsigned>(std::floor((LC - startMap[1]) / cellSize[1]));
388 if (IT >= np[0] - 1 || IL >= np[1] - 1) {
return; }
390 double b1 = 0.0, b2 = 0.0;
392 if (std::fabs(startMap[0] + IT * cellSize[0] - TC) > std::fabs(startMap[0] + (IT + 1) * cellSize[0] - TC)) IT++;
393 if (std::fabs(startMap[1] + IL * cellSize[1] - LC) > std::fabs(startMap[1] + (IL + 1) * cellSize[1] - LC)) IL++;
394 b1 = B1[idx2(IT, IL)];
395 b2 = B2[idx2(IT, IL)];
398 const double xtr = (TC - (startMap[0] + IT * cellSize[0])) / cellSize[0];
399 const double xlr = (LC - (startMap[1] + IL * cellSize[1])) / cellSize[1];
400 const double b10 = B1[idx2(IT, IL)] * (1.0 - xtr) + B1[idx2(IT + 1, IL)] * xtr;
401 const double b11 = B1[idx2(IT, IL + 1)] * (1.0 - xtr) + B1[idx2(IT + 1, IL + 1)] * xtr;
402 b1 = b10 * (1.0 - xlr) + b11 * xlr;
403 const double b20 = B2[idx2(IT, IL)] * (1.0 - xtr) + B2[idx2(IT + 1, IL)] * xtr;
404 const double b21 = B2[idx2(IT, IL + 1)] * (1.0 - xtr) + B2[idx2(IT + 1, IL + 1)] * xtr;
405 b2 = b20 * (1.0 - xlr) + b21 * xlr;
408 if (symmetry == Symmetry::cyl_z) {
409 bfield[0] = b1 * std::cos(phi); bfield[1] = b1 * std::sin(phi); bfield[2] = b2;
411 else if (symmetry == Symmetry::cyl_x) {
412 bfield[0] = b2; bfield[1] = b1 * std::cos(phi); bfield[2] = b1 * std::sin(phi);
415 bfield[1] = b2; bfield[0] = b1 * std::sin(phi); bfield[2] = b1 * std::cos(phi);
418 rotate_field(bfield);
422void GField_AsciiMapFactory::value_phi_segmented(
const double x[3],
double* bfield)
const {
425 double aC = std::atan2(x[1], x[0]) * rad;
426 if (aC < 0) { aC += 360 * deg; }
427 const double tC = std::sqrt(x[0] * x[0] + x[1] * x[1]);
428 const double lC = x[2];
432 while (aLC / deg > 30) { aLC -= 60 * deg; }
433 const double dphi = aC - aLC;
434 const double aaLC = std::fabs(aLC);
435 const int sign = (aLC >= 0 ? 1 : -1);
437 if (aC < startMap[0] || tC < startMap[1] || lC < startMap[2]) {
return; }
438 unsigned aI =
static_cast<unsigned>(std::floor((aaLC - startMap[0]) / cellSize[0]));
439 unsigned tI =
static_cast<unsigned>(std::floor((tC - startMap[1]) / cellSize[1]));
440 unsigned lI =
static_cast<unsigned>(std::floor((lC - startMap[2]) / cellSize[2]));
441 if (aI >= np[0] - 1 || tI >= np[1] - 1 || lI >= np[2] - 1) {
return; }
443 double mfield[3] = {0.0, 0.0, 0.0};
445 if (std::fabs(startMap[0] + aI * cellSize[0] - aaLC) > std::fabs(startMap[0] + (aI + 1) * cellSize[0] - aaLC)) aI++;
446 if (std::fabs(startMap[1] + tI * cellSize[1] - tC) > std::fabs(startMap[1] + (tI + 1) * cellSize[1] - tC)) tI++;
447 if (std::fabs(startMap[2] + lI * cellSize[2] - lC) > std::fabs(startMap[2] + (lI + 1) * cellSize[2] - lC)) lI++;
448 mfield[0] = B1[idx3(aI, tI, lI)];
449 mfield[1] = B2[idx3(aI, tI, lI)];
450 mfield[2] = B3[idx3(aI, tI, lI)];
453 const double xaz = (aaLC - (startMap[0] + aI * cellSize[0])) / cellSize[0];
454 const double xtr = (tC - (startMap[1] + tI * cellSize[1])) / cellSize[1];
455 const double xlr = (lC - (startMap[2] + lI * cellSize[2])) / cellSize[2];
457 const std::vector<float>* comps[3] = {&B1, &B2, &B3};
458 for (
int k = 0; k < 3; ++k) {
459 const std::vector<float>& B = *comps[k];
460 const double b00 = B[idx3(aI, tI, lI)] * (1 - xaz) + B[idx3(aI + 1, tI, lI)] * xaz;
461 const double b01 = B[idx3(aI, tI, lI + 1)] * (1 - xaz) + B[idx3(aI + 1, tI, lI + 1)] * xaz;
462 const double b10 = B[idx3(aI, tI + 1, lI)] * (1 - xaz) + B[idx3(aI + 1, tI + 1, lI)] * xaz;
463 const double b11 = B[idx3(aI, tI + 1, lI + 1)] * (1 - xaz) + B[idx3(aI + 1, tI + 1, lI + 1)] * xaz;
464 const double b0 = b00 * (1 - xtr) + b10 * xtr;
465 const double b1 = b01 * (1 - xtr) + b11 * xtr;
466 mfield[k] = b0 * (1 - xlr) + b1 * xlr;
471 bfield[0] = sign * mfield[0] * std::cos(dphi / rad) - mfield[1] * std::sin(dphi / rad);
472 bfield[1] = sign * mfield[0] * std::sin(dphi / rad) + mfield[1] * std::cos(dphi / rad);
473 bfield[2] = sign * mfield[2];
475 rotate_field(bfield);
479void GField_AsciiMapFactory::value_cartesian3d(
const double x[3],
double* bfield)
const {
481 double XX = x[0], YY = x[1], ZZ = x[2];
483 if (symmetry == Symmetry::cartesian_3d_quadrant) {
485 if (x[0] >= 0 && x[1] >= 0) { XX = x[0]; YY = x[1]; }
486 else if (x[0] >= 0 && x[1] < 0) { XX = -x[1]; YY = x[0]; }
487 else if (x[0] < 0 && x[1] < 0) { XX = -x[0]; YY = -x[1]; }
488 else { XX = x[1]; YY = -x[0]; }
490 if (XX < 0 || YY < 0) {
return; }
493 if (XX < startMap[0] || YY < startMap[1] || ZZ < startMap[2]) {
return; }
494 if (XX >= endMap[0] || YY >= endMap[1] || ZZ >= endMap[2]) {
return; }
495 const unsigned IXX =
static_cast<unsigned>(std::floor((XX - startMap[0]) / cellSize[0]));
496 const unsigned IYY =
static_cast<unsigned>(std::floor((YY - startMap[1]) / cellSize[1]));
497 const unsigned IZZ =
static_cast<unsigned>(std::floor((ZZ - startMap[2]) / cellSize[2]));
499 double B[3] = {0.0, 0.0, 0.0};
501 unsigned ix = IXX, iy = IYY, iz = IZZ;
502 if (std::fabs(startMap[0] + ix * cellSize[0] - XX) > std::fabs(startMap[0] + (ix + 1) * cellSize[0] - XX)) ix++;
503 if (std::fabs(startMap[1] + iy * cellSize[1] - YY) > std::fabs(startMap[1] + (iy + 1) * cellSize[1] - YY)) iy++;
504 if (std::fabs(startMap[2] + iz * cellSize[2] - ZZ) > std::fabs(startMap[2] + (iz + 1) * cellSize[2] - ZZ)) iz++;
505 B[0] = B1[idx3(ix, iy, iz)];
506 B[1] = B2[idx3(ix, iy, iz)];
507 B[2] = B3[idx3(ix, iy, iz)];
510 const double Xd = (XX - (startMap[0] + IXX * cellSize[0])) / cellSize[0];
511 const double Yd = (YY - (startMap[1] + IYY * cellSize[1])) / cellSize[1];
512 const double Zd = (ZZ - (startMap[2] + IZZ * cellSize[2])) / cellSize[2];
514 const std::vector<float>* comps[3] = {&B1, &B2, &B3};
515 for (
int k = 0; k < 3; ++k) {
516 const std::vector<float>& Bk = *comps[k];
517 const double c00 = Bk[idx3(IXX, IYY, IZZ)] * (1 - Xd) + Bk[idx3(IXX + 1, IYY, IZZ)] * Xd;
518 const double c01 = Bk[idx3(IXX, IYY, IZZ + 1)] * (1 - Xd) + Bk[idx3(IXX + 1, IYY, IZZ + 1)] * Xd;
519 const double c10 = Bk[idx3(IXX, IYY + 1, IZZ)] * (1 - Xd) + Bk[idx3(IXX + 1, IYY + 1, IZZ)] * Xd;
520 const double c11 = Bk[idx3(IXX, IYY + 1, IZZ + 1)] * (1 - Xd) + Bk[idx3(IXX + 1, IYY + 1, IZZ + 1)] * Xd;
521 const double c0 = c00 * (1 - Yd) + c10 * Yd;
522 const double c1 = c01 * (1 - Yd) + c11 * Yd;
523 B[k] = c0 * (1 - Zd) + c1 * Zd;
527 if (symmetry == Symmetry::cartesian_3d_quadrant) {
529 if (x[0] >= 0 && x[1] >= 0) { bfield[0] = B[0]; bfield[1] = B[1]; }
530 else if (x[0] >= 0 && x[1] < 0) { bfield[0] = B[1]; bfield[1] = -B[0]; }
531 else if (x[0] < 0 && x[1] < 0) { bfield[0] = -B[0]; bfield[1] = -B[1]; }
532 else { bfield[0] = -B[1]; bfield[1] = B[0]; }
536 bfield[0] = B[0]; bfield[1] = B[1]; bfield[2] = B[2];
539 rotate_field(bfield);
543void GField_AsciiMapFactory::rotate_field(
double* bfield)
const {
545 if (mapRotation[0] != 0) {
546 const double yPrime = bfield[1] * cosAlpha + bfield[2] * sinAlpha;
547 const double zPrime = -bfield[1] * sinAlpha + bfield[2] * cosAlpha;
548 bfield[1] = yPrime; bfield[2] = zPrime;
550 if (mapRotation[1] != 0) {
551 const double xPrime = bfield[0] * cosBeta - bfield[2] * sinBeta;
552 const double zPrime = bfield[0] * sinBeta + bfield[2] * cosBeta;
553 bfield[0] = xPrime; bfield[2] = zPrime;
555 if (mapRotation[2] != 0) {
556 const double xPrime = bfield[0] * cosGamma + bfield[1] * sinGamma;
557 const double yPrime = -bfield[0] * sinGamma + bfield[1] * cosGamma;
558 bfield[0] = xPrime; bfield[1] = yPrime;
std::shared_ptr< GLogger > log
Concrete GField reading a magnetic field from an ASCII map and a YAML definition.
void load_field_definitions(GFieldDefinition gfd) override
Parse the YAML definition, build the grid and read the map file.
void GetFieldValue(const double pos[3], G4double *bfield) const override
Compute the field at the lab-frame point pos, writing {Bx,By,Bz} (Geant4 units) into bfield.
Abstract base class representing a magnetic field.
GFieldDefinition gfield_definitions
Stored field definition used for configuration and logging.
void warning(Args &&... args) const
void info(int level, Args &&... args) const
void error(int exit_code, Args &&... args) const
GField * GFieldFactory(const std::shared_ptr< GOptions > &g)
#define ERR_WRONG_FIELD_SYMMETRY
#define ERR_MAP_FILE_NOT_FOUND
#define ERR_WRONG_COORDINATE_DEF
double getG4Number(const string &v, bool warnIfNotUnit=false)
vector< string > getStringVectorFromStringWithDelimiter(const string &input, const string &x)
string removeLeadingAndTrailingSpacesFromString(const std::string &input)
Lightweight configuration carrier used to load and configure a GField plugin.
std::string name
Field name key used by GMagneto maps.
std::map< std::string, std::string > field_parameters
Field parameters stored as string expressions (often including unit expressions).