gfields
Loading...
Searching...
No Matches
gfield_multipoles.cc
Go to the documentation of this file.
1// CLHEP units
2#include "CLHEP/Units/PhysicalConstants.h"
3
4// geant4 headers
5#include "G4ThreeVector.hh"
6
7// gfields
8#include "gfield_multipoles.h"
9#include "gfieldConventions.h"
10
11// gemv
12// #include "gutilities.h"
13
14
15// tells the DLL how to create a GFieldFactory in each plugin .so/.dylib
16extern "C" GField* GFieldFactory(const std::shared_ptr<GOptions>& g) { return static_cast<GField*>(new GField_MultipolesFactory(g)); }
17
18
19// Implementation notes (non-Doxygen):
20// - This follows common accelerator multipole conventions for transverse fields.
21// - Strength is defined at a reference radius of 1 m (see r0 below).
22// - References (background reading):
23// - https://cds.cern.ch/record/1333874/files/1.pdf
24// - https://uspas.fnal.gov/materials/12MSU/magnet_elements.pdf
25// - https://cas.web.cern.ch/sites/default/files/lectures/bruges-2009/wolski-1.pdf
26void GField_MultipolesFactory::GetFieldValue(const double pos[3], G4double* bfield) const {
27 // Evaluate an ideal multipole field at the query point in the lab frame.
28 // The computation is performed in a magnet-local frame and then rotated back.
29
30 // ======= Configuration / conventions =======
31 // strength: Tesla at reference radius r0 for all multipole orders
32 const double r0 = CLHEP::m; // reference radius; make this a member if you want
33
34 // ======= Basic checks =======
35 if (pole_number < 2 || (pole_number % 2) != 0) {
36 log->error(
38 "Pole number must be an even integer >= 2 (2=dipole,4=quadrupole,...)");
39 }
40
41 // ======= Positions and local frame =======
42 const G4ThreeVector x0(pos[0], pos[1], pos[2]); // query point (lab)
43 const G4ThreeVector x1(origin[0], origin[1], origin[2]); // magnet origin (lab)
44
45 // shift to magnet-centered coordinates and "unroll" the magnet by -rotation_angle
46 G4ThreeVector p = x0 - x1;
47 if (rotaxis == 0) p.rotateX(-rotation_angle);
48 else if (rotaxis == 1) p.rotateY(-rotation_angle);
49 else if (rotaxis == 2) p.rotateZ(-rotation_angle);
50
51 // Identify transverse plane (u,v) ⟂ to the axis, and the axial coordinate w (unused here)
52 double u = 0.0, v = 0.0;
53 switch (rotaxis) {
54 case 0: u = p.y();
55 v = p.z();
56 break; // axis = X ⇒ transverse plane = (Y,Z)
57 case 1: u = p.z();
58 v = p.x();
59 break; // axis = Y ⇒ transverse plane = (Z,X)
60 case 2: u = p.x();
61 v = p.y();
62 break; // axis = Z ⇒ transverse plane = (X,Y)
63 default: break;
64 }
65
66 const double r = std::hypot(u, v); // transverse radius
67 const double phi = std::atan2(v, u); // azimuth in transverse plane
68
69 // ======= Axial (solenoid-like) mode if explicitly requested =======
70 if (longitudinal) {
71 // Uniform axial field aligned with rotaxis; not a multipole.
72 G4ThreeVector B_local(0, 0, 0);
73 switch (rotaxis) {
74 case 0: B_local.setX(strength);
75 break;
76 case 1: B_local.setY(strength);
77 break;
78 case 2: B_local.setZ(strength);
79 break;
80 default: break;
81 }
82
83 // Roll back to lab
84 G4ThreeVector B_lab = B_local;
85 if (rotaxis == 0) B_lab.rotateX(+rotation_angle);
86 else if (rotaxis == 1) B_lab.rotateY(+rotation_angle);
87 else if (rotaxis == 2) B_lab.rotateZ(+rotation_angle);
88
89 bfield[0] = B_lab.x();
90 bfield[1] = B_lab.y();
91 bfield[2] = B_lab.z();
92
93 log->info(
94 2, "Axial field mode (solenoid-like). Strength: %1 T, Field: (%2, %3, %4)", strength, bfield[0],
95 bfield[1], bfield[2]);
96 return;
97 }
98
99 // ======= Transverse multipole (standard accelerator definition) =======
100 const int n = pole_number / 2; // 1=dipole, 2=quadrupole, 3=sextupole, ...
101 const int a = n - 1; // power of r
102
103 // r^a scaling with reference radius r0; for dipole (a=0) this is 1
104 double ra = 1.0;
105 if (a > 0) {
106 // If r=0 and a>0, |B|=0 (for ideal multipoles).
107 if (r == 0.0) {
108 bfield[0] = bfield[1] = bfield[2] = 0.0;
109 return;
110 }
111 ra = std::pow(r / r0, static_cast<double>(a));
112 }
113
114 // "Normal" multipole angular dependence:
115 // Bu = strength * r^(n-1) * cos((n-1)*phi)
116 // Bv = strength * r^(n-1) * sin((n-1)*phi)
117 // This yields a constant transverse dipole for n=1.
118 const double Bu = strength * ra * std::cos(a * phi);
119 const double Bv = strength * ra * std::sin(a * phi);
120
121 // Place (Bu,Bv) into the correct transverse components of B_local; axial component = 0
122 G4ThreeVector B_local(0, 0, 0);
123 switch (rotaxis) {
124 case 0: B_local.setY(Bu);
125 B_local.setZ(Bv);
126 break; // axis X → (Y,Z)
127 case 1: B_local.setZ(Bu);
128 B_local.setX(Bv);
129 break; // axis Y → (Z,X)
130 case 2: B_local.setX(Bu);
131 B_local.setY(Bv);
132 break; // axis Z → (X,Y)
133 default: break;
134 }
135
136 // Rotate (roll) back to lab
137 G4ThreeVector B_lab = B_local;
138 if (rotaxis == 0) B_lab.rotateX(+rotation_angle);
139 else if (rotaxis == 1) B_lab.rotateY(+rotation_angle);
140 else if (rotaxis == 2) B_lab.rotateZ(+rotation_angle);
141
142 // Output
143 bfield[0] = B_lab.x();
144 bfield[1] = B_lab.y();
145 bfield[2] = B_lab.z();
146
147 log->info(
148 2,
149 "Pole Number: %1, n: %2, Strength: %3, Requested at: (%4, %5, %6), Rotation angle: %7, Rotation "
150 "axis: %8, longitudinal: %9, Field: (%10, %11, %12)", pole_number, n, strength, pos[0], pos[1], pos[2],
151 rotation_angle, rotaxis, longitudinal, bfield[0], bfield[1], bfield[2]);
152}
153
154
155// Load and cache multipole parameters from the provided field definition (non-Doxygen summary).
157 gfield_definitions = gfd;
158
159 pole_number = get_field_parameter_int("pole_number");
160 origin[0] = get_field_parameter_double("vx");
161 origin[1] = get_field_parameter_double("vy");
162 origin[2] = get_field_parameter_double("vz");
163 rotation_angle = get_field_parameter_double("rotation_angle");
164 auto rot_axis_option = gfield_definitions.field_parameters["rotaxis"];
165 longitudinal = false;
166
167 // Longitudinal mode: return a uniform axial field aligned with rotaxis.
168 if (gfield_definitions.field_parameters["longitudinal"] == "true") {
169 longitudinal = true;
170 log->info(1, "Longitudinal field");
171 }
172 else { log->info(1, "Transverse field"); }
173
174 // Parse rotaxis as a single axis character.
175 if (rot_axis_option == "X" || rot_axis_option == "x") { rotaxis = 0; }
176 else if (rot_axis_option == "Y" || rot_axis_option == "y") { rotaxis = 1; }
177 else if (rot_axis_option == "Z" || rot_axis_option == "z") { rotaxis = 2; }
178 else {
179 log->error(
181 "GField_MultipolesFactory::load_field_definitions: Rotation axis %1 not supported. Exiting.",
182 gfield_definitions.field_parameters["rotaxis"]);
183 }
184
185 log->info(1, "Rotation axis: %1", rotaxis);
186 strength = get_field_parameter_double("strength");
187}
std::shared_ptr< GLogger > log
Factory class implementing an ideal multipole magnetic field.
void GetFieldValue(const double pos[3], G4double *bfield) const override
Evaluate the magnetic field B at a given position.
void load_field_definitions(GFieldDefinition gfd) override
Load and cache field-definition parameters for fast field evaluation.
Abstract base class representing a magnetic field.
Definition gfield.h:105
GFieldDefinition gfield_definitions
Stored field definition used for configuration and logging.
Definition gfield.h:191
double get_field_parameter_double(const std::string &key)
Convenience accessor for floating-point parameters stored in field_parameters.
Definition gfield.h:157
int get_field_parameter_int(const std::string &key)
Convenience accessor for integer-valued parameters stored in field_parameters.
Definition gfield.h:150
GField * GFieldFactory(const std::shared_ptr< GOptions > &g)
constexpr int ERR_WRONG_FIELD_ROTATION
constexpr int ERR_WRONG_POLE_NUMBER
Lightweight configuration carrier used to load and configure a GField plugin.
Definition gfield.h:27