BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSIntegratorDipoleFringe.cc
1/*
2Beam Delivery Simulation (BDSIM) Copyright (C) BDSIM Collaboration, 2001 - 2026.
3
4This file is part of BDSIM.
5
6BDSIM is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published
8by the Free Software Foundation version 3 of the License.
9
10BDSIM is distributed in the hope that it will be useful, but
11WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with BDSIM. If not, see <http://www.gnu.org/licenses/>.
17*/
18#include "BDSDebug.hh"
19#include "BDSFieldMagDipole.hh"
20#include "BDSGlobalConstants.hh"
21#include "BDSIntegratorDipoleFringe.hh"
22#include "BDSMagnetStrength.hh"
23#include "BDSStep.hh"
24#include "BDSUtilities.hh"
25
26#include "G4Mag_EqRhs.hh"
27#include "G4ThreeVector.hh"
28
29#include <cmath>
30
31G4double BDSIntegratorDipoleFringe::thinElementLength = -1; // unphysical
32
34 G4double brhoIn,
35 G4Mag_EqRhs* eqOfMIn,
36 G4double minimumRadiusOfCurvatureIn,
37 const G4double& tiltIn):
38 BDSIntegratorDipoleRodrigues2(eqOfMIn, minimumRadiusOfCurvatureIn),
39 rho(1.0),
40 fieldArcLength((*strengthIn)["length"]),
41 fieldAngle((*strengthIn)["angle"]),
42 tilt(tiltIn),
43 finiteTilt(BDS::IsFinite(tiltIn)),
44 multipoleIntegrator(nullptr)
45{
46 if (thinElementLength < 0)
47 {thinElementLength = BDSGlobalConstants::Instance()->ThinElementLength();}
48
49 if (G4bool((*strengthIn)["isentrance"]))
50 {
51 polefaceAngle = (*strengthIn)["e1"];
52 polefaceCurvature = (*strengthIn)["h1"];
53 fringeCorr = BDS::FringeFieldCorrection(strengthIn, true);
54 secondFringeCorr = BDS::SecondFringeFieldCorrection(strengthIn, true);
55 }
56 else // must be exit face
57 {
58 polefaceAngle = (*strengthIn)["e2"];
59 polefaceCurvature = (*strengthIn)["h2"];
60 fringeCorr = BDS::FringeFieldCorrection(strengthIn, false);
61 secondFringeCorr = BDS::SecondFringeFieldCorrection(strengthIn, false);
62 }
63
64 // store if instance is for entrance or exit fringe - determines which element parameters are used in kick
65 // and ordering of kick & dipole transport
66 isEntrance = (*strengthIn)["isentrance"];
67
68 // check if field or scaling is finite, if not then set to be zero strength to later advance as a drift,
69 // otherwise then calculate rho by undo-ing field scaling so rho is truly nominal for matching mad matrices
70 if (!BDS::IsFinite((*strengthIn)["field"]) || !BDS::IsFinite((*strengthIn)["scaling"]))
71 {zeroStrength = true;}
72 else
73 {
74 zeroStrength = false;
75 rho = (std::abs(brhoIn)/(*strengthIn)["field"]) * (*strengthIn)["scaling"];
76 }
77
78 // thin sextupole strength for curved polefaces
79 G4double thinSextStrength = (-polefaceCurvature / rho) * 1.0 / std::pow(std::cos(polefaceAngle),3);
80
81 // prepare magnet strength object for thin sextupole needed for curved polefaces
82 BDSMagnetStrength* sextStrength = new BDSMagnetStrength();
83 (*sextStrength)["k2"] = thinSextStrength;
84 // component strength is normalised by length, no length here to set to 1
85 (*sextStrength)["length"] = 1*CLHEP::m;
86
87 // integrator for thin sextupole
88 multipoleIntegrator = new BDSIntegratorMultipoleThin(sextStrength, brhoIn, eqOfMIn);
89 delete sextStrength;
90
91 BDSFieldMagDipole* dipoleField = new BDSFieldMagDipole(strengthIn);
92 unitField = (dipoleField->FieldValue()).unit();
93 delete dipoleField;
94
95 bx = (*strengthIn)["bx"];
96 by = (*strengthIn)["by"];
97
98 backupStepperMomLimit = BDSGlobalConstants::Instance()->BackupStepperMomLimit();
99}
100
101BDSIntegratorDipoleFringe::~BDSIntegratorDipoleFringe()
102{
103 delete multipoleIntegrator;
104}
105
106void BDSIntegratorDipoleFringe::Stepper(const G4double yIn[6],
107 const G4double dydx[6],
108 const G4double h,
109 G4double yOut[6],
110 G4double yErr[6])
111{
112 // unit normalisation
113 const G4double fcof = eqOfM->FCof();
114
115 BaseStepper(yIn, dydx, h, yOut, yErr, fcof, 1.0);
116}
117
118void BDSIntegratorDipoleFringe::BaseStepper(const G4double yIn[6],
119 const G4double dydx[6],
120 const G4double& h,
121 G4double yOut[6],
122 G4double yErr[6],
123 const G4double& fcof,
124 const G4double& momScaling)
125{
126 // Protect against neutral particles, and zero field: drift through.
128 {
129 AdvanceDriftMag(yIn,h,yOut,yErr);
130 FudgeDistChordToZero(); // see doxygen in header for explanation
131 return;
132 }
133
134 // container for multipole kick output, used as dipole step input
135 G4double yMultipoleOut[6];
136 // copy input coords as initials as multipole kick method not called
137 for (G4int i = 0; i < 3; i++)
138 {
139 yMultipoleOut[i] = yIn[i];
140 yMultipoleOut[i + 3] = yIn[i + 3];
141 }
142 // apply thin multipole kick if finite curvature. Does not step, stepping occurs in dipole integrator
143 if (multipoleIntegrator)
144 {MultipoleStep(yIn, yMultipoleOut, h);}
145
146 // container for copying multipole kick output (entrance fringe) or dipole step output (exit fringe)
147 G4double yTemp[6];
148
149 // only do the dipole transport before the fringe kick if it's an exit fringe, otherwise copy the
150 // coords and continue
151 if (!isEntrance)
152 {
153 // do the dipole kick and step using base class
154 BDSIntegratorDipoleRodrigues2::Stepper(yMultipoleOut, dydx, h, yTemp, yErr); // yErr is correct output variable
155 }
156 else
157 {
158 for (G4int i = 0; i < 6; i++)
159 {yTemp[i] = yMultipoleOut[i];}
160 }
161
162 // only apply the kick if we're taking a step longer than half the length of the item,
163 // in which case, apply the full kick. This appears more robust than scaling the kick
164 // by h / thinElementLength as the precise geometrical length depends on the geometry
165 // ie if there's a beam pipe etc -> more length safetys. The geometry layout should
166 // prevent more than one step begin taken, but occasionally, a very small initial step
167 // can be taken resulting in a double kick.
168 G4double lengthFraction = h / thinElementLength;
169
170 // don't do fringe kick if we're sampling the field for a long step or if it's a half
171 // step inside the thin element. Apply the dipole motion only if this step is in an
172 // entrance fringe, it should have already been applied if is is an exit face
173 if ((h > 1*CLHEP::cm) || (lengthFraction < 0.501))
174 {
175 if (isEntrance)
176 {BDSIntegratorDipoleRodrigues2::Stepper(yTemp, dydx, h, yOut, yErr);}
177 else
178 {
179 for (G4int i = 0; i < 3; i++)
180 {
181 yOut[i] = yTemp[i];
182 yOut[i + 3] = yTemp[i + 3];
183 }
184 }
185 return;
186 }
187
188 // position and momentum post dipole kick.
189 G4ThreeVector pos = G4ThreeVector(yTemp[0], yTemp[1], yTemp[2]);
190 G4ThreeVector mom = G4ThreeVector(yTemp[3], yTemp[4], yTemp[5]);
191
192 BDSStep localPosMom = GlobalToCurvilinear(pos, mom, h, true);
193 if (finiteTilt)
194 {localPosMom = localPosMom.rotateZ(-tilt);}
195 G4ThreeVector localPos = localPosMom.PreStepPoint();
196 G4ThreeVector localMom = localPosMom.PostStepPoint();
197 G4ThreeVector localMomU = localMom.unit();
198
199 // check for forward going paraxial particles - only
200 // if this is an entrance face then the dipole transport hasn't been applied yet, so apply it to ensure
201 // the particle advances along the step and then exit. If it's an exit face, then the dipole transport
202 // has already been applied, so simply copy the output from the dipole transport above and exit.
203 if (localMomU.z() < (1.0-backupStepperMomLimit))
204 {
205 if (isEntrance)
206 {BDSIntegratorDipoleRodrigues2::Stepper(yTemp, dydx, h, yOut, yErr);}
207 else
208 {
209 for (G4int i = 0; i < 3; i++)
210 {
211 yOut[i] = yTemp[i];
212 yOut[i + 3] = yTemp[i + 3];
213 }
214 }
215 return;
216 }
217
218 // calculate new position and momentum kick from fringe effect only
219 G4ThreeVector localCLPosOut;
220 G4ThreeVector localCLMomOut;
221
222 // normalise to particle charge
223 G4double charge = fcof / std::abs(fcof);
224 G4double bendingRad = rho * charge;
225
226 // normalise to momentum - passed in from derived class.
227 bendingRad *= momScaling;
228
229 // rotate by pi/2 if field is only horizontal and tilt is zero - can only be from a vertical kicker
230 if (!BDS::IsFinite(tilt) && BDS::IsFinite(bx) && (!BDS::IsFinite(by)))
231 {
232 // rotate only by the sign of the field and not the magnitude.
233 G4double sign = bx < 0 ? -1 : 1;
234 G4double verticalFringeTiltAngle = 0.5*CLHEP::pi * sign;
235
236 localPos.rotateZ(-verticalFringeTiltAngle);
237 localMomU.rotateZ(-verticalFringeTiltAngle);
238 OneStep(localPos, localMomU, localCLPosOut, localCLMomOut, bendingRad);
239 localCLPosOut.rotateZ(verticalFringeTiltAngle);
240 localCLMomOut.rotateZ(verticalFringeTiltAngle);
241 }
242 else // apply fringe field kick
243 {OneStep(localPos, localMomU, localCLPosOut, localCLMomOut, bendingRad);}
244
245 // convert to global coordinates for output
246 if (finiteTilt)
247 {
248 //localCLPosOut = localCLPosOut.rotateZ(tilt); // we only need the momentum - ignore
249 localCLMomOut = localCLMomOut.rotateZ(tilt);
250 }
251
252 G4ThreeVector globalMom = ConvertAxisToGlobal(localCLMomOut, true);
253 globalMom *= mom.mag();
254 // error along direction of travel really
255 G4ThreeVector globalMomU = globalMom.unit();
256 globalMomU *= 1e-8;
257
258 // container if dipole step still needs to be taken if fringe is an entrance fringe
259 G4double yTempOut[6];
260
261 // copy out values and errors
262 for (G4int i = 0; i < 3; i++)
263 {
264 yTempOut[i] = pos[i];
265 yTempOut[i + 3] = globalMom[i];
266 yErr[i] = globalMomU[i]*1e-10;
267 yErr[i + 3] = 1e-40;
268 }
269
270 // now only do the dipole transport after the fringe kick if it's an entrance fringe, otherwise copy the
271 // coords to the output container
272 if (isEntrance)
273 {
274 // do the dipole kick and step using base class
275 BDSIntegratorDipoleRodrigues2::Stepper(yTempOut, dydx, h, yOut, yErr); // yErr is correct output variable
276 }
277 else
278 {
279 for (G4int i = 0; i < 6; i++)
280 {yOut[i] = yTempOut[i];}
281 }
282}
283
284void BDSIntegratorDipoleFringe::OneStep(const G4ThreeVector& posIn,
285 const G4ThreeVector& momUIn,
286 G4ThreeVector& posOut,
287 G4ThreeVector& momOut,
288 const G4double& bendingRadius) const
289{
290 G4double x0 = posIn.x() / CLHEP::m;
291 G4double y0 = posIn.y() / CLHEP::m;
292 G4double s0 = posIn.z();
293 G4double xp = momUIn.x();
294 G4double yp = momUIn.y();
295 G4double zp = momUIn.z();
296
297 G4double x1 = x0;
298 G4double y1 = y0;
299 G4double s1 = s0;
300 G4double xp1 = xp;
301 G4double yp1 = yp;
302 G4double zp1 = zp;
303
304 G4double X11=0,X12=0,X21=0,X22 = 0;
305 G4double Y11=0,Y12=0,Y21=0,Y22 = 0;
306
307 // fringe field correction using normalised rho
308 G4double fringeFieldCorrection = (fringeCorr/bendingRadius) * (1.0 - secondFringeCorr/bendingRadius);
309
310 // calculate fringe field kick
311 X11 = 1;
312 X21 = std::tan(polefaceAngle) / (bendingRadius / CLHEP::m);
313 X22 = 1;
314
315 Y11 = 1;
316 Y21 = -std::tan(polefaceAngle - fringeFieldCorrection) / (bendingRadius / CLHEP::m);
317 Y22 = 1;
318
319 x1 = X11*x0 + X12*xp;
320 xp1 = X21*x0 + X22*xp;
321 y1 = Y11*y0 + Y12*yp;
322 yp1 = Y21*y0 + Y22*yp;
323
324 // relies on normalised momenta otherwise this will be nan.
325 zp1 = std::sqrt(1 - xp1*xp1 - yp1*yp1);
326 if (std::isnan(zp1))
327 {zp1 = zp;} // ensure not nan
328
329 momOut = G4ThreeVector(xp1, yp1, zp1);
330 posOut = G4ThreeVector(x1*CLHEP::m, y1*CLHEP::m, s1);
331}
332
334 G4double yMultipoleOut[6],
335 const G4double& h)
336{
337 // convert to local curvilinear co-ordinates
338 G4ThreeVector pos = G4ThreeVector(yIn[0], yIn[1], yIn[2]);
339 G4ThreeVector mom = G4ThreeVector(yIn[3], yIn[4], yIn[5]);
340
341 BDSStep localPosMom = GlobalToCurvilinear(pos, mom, h, true);
342 G4ThreeVector localPos = localPosMom.PreStepPoint();
343 G4ThreeVector localMom = localPosMom.PostStepPoint();
344 G4ThreeVector localMomU = localMom.unit();
345
346 // initialise new position and momentum for curvature effect as input coords
347 G4ThreeVector localCLPosOut = localPos;
348 G4ThreeVector localCLMomOut = localMomU;
349
350 // Zero step length as stepping has already been applied in base class stepper
351 multipoleIntegrator->OneStep(localPos, localMomU, localMom.mag(), localCLPosOut, localCLMomOut, 0);
352
353 G4ThreeVector globalMom = ConvertAxisToGlobal(localCLMomOut, true);
354 globalMom *= mom.mag();
355
356 // write out values
357 for (G4int i = 0; i < 3; i++)
358 {
359 yMultipoleOut[i] = pos[i];
360 yMultipoleOut[i + 3] = globalMom[i];
361 }
362}
363
364// fringe field correction terms are BDS namespace methods instead of class methods
365// as the functions are called during BDSIntegratorDipoleFringe construction.
366// The fringe field correction terms should be cached at construction rather than
367// calculated every time the integrator is called.
368
370 G4bool isEntrance)
371{
372 G4double fint;
373 G4double pfAngle;
374 if (isEntrance)
375 {
376 fint = (*strength)["fint"];
377 pfAngle = (*strength)["e1"];
378 }
379 else // must be exit face
380 {
381 fint = (*strength)["fintx"];
382 pfAngle = (*strength)["e2"];
383 }
384 G4double hgap = (*strength)["hgap"];
385 G4double vertGap = 2 * hgap;
386 G4double corrValue = fint * vertGap * (1.0 + std::pow(std::sin(pfAngle),2)) / std::cos(pfAngle);
387 return corrValue;
388}
389
391 G4bool isEntrance)
392{
393 G4double fint;
394 G4double fintK2;
395 G4double pfAngle;
396 if (isEntrance)
397 {
398 fint = (*strength)["fint"];
399 fintK2 = (*strength)["fintk2"];
400 pfAngle = (*strength)["e1"];
401 }
402 else // must be exit face
403 {
404 fint = (*strength)["fintx"];
405 fintK2 = (*strength)["fintxk2"];
406 pfAngle = (*strength)["e2"];
407 }
408 G4double hgap = (*strength)["hgap"];
409 G4double vertGap = 2 * hgap;
410 G4double corrValue = fint * fintK2 * vertGap * std::tan(pfAngle);
411 return corrValue;
412}
BDSStep GlobalToCurvilinear(const G4double fieldArcLength, const G4ThreeVector &unitField, const G4double angle, const G4ThreeVector &position, const G4ThreeVector &unitMomentum, const G4double h, const G4bool useCurvilinearWorld, const G4double FCof, const G4double tilt=0)
G4ThreeVector ConvertAxisToGlobal(const G4ThreeVector &localAxis, const G4bool useCurvilinear=true) const
A uniform dipole field.
static BDSGlobalConstants * Instance()
Access method.
G4double secondFringeCorr
Second fringe field correction term.
void OneStep(const G4ThreeVector &posIn, const G4ThreeVector &momUIn, G4ThreeVector &posOut, G4ThreeVector &momOut, const G4double &bendingRadius) const
G4double rho
Nominal magnet bending radius.
G4double polefaceAngle
Poleface rotation angle.
G4bool isEntrance
store if fringe is entrance or exit
G4double fringeCorr
Fringe field correction term.
void BaseStepper(const G4double yIn[6], const G4double dydx[6], const G4double &h, G4double yOut[6], G4double yErr[6], const G4double &fcof, const G4double &momScaling)
virtual void Stepper(const G4double yIn[6], const G4double dydx[6], const G4double h, G4double yOut[6], G4double yErr[6])
The stepper for integration. Calls base class stepper.
BDSIntegratorDipoleFringe()=delete
Private default constructor to enforce use of supplied constructor.
void MultipoleStep(const G4double yIn[6], G4double yMultipoleOut[6], const G4double &h)
Exact helix through pure dipole field.
G4Mag_EqRhs * eqOfM
Cache of equation of motion. This class does not own it.
virtual void Stepper(const G4double yIn[6], const G4double dydx[], G4double h, G4double yOut[6], G4double yErr[6])
Output error array.
void AdvanceDriftMag(const G4double yIn[], const G4double h, G4double yOut[], G4double yErr[]) const
Error array [6] all 0.
Integrator that ignores the field and uses the analytical solution to a multipole.
void OneStep(const G4ThreeVector &posIn, const G4ThreeVector &momUIn, const G4double momIn, G4ThreeVector &posOut, G4ThreeVector &momOut, G4double h) const
Efficient storage of magnet strengths.
A simple class to represent the positions of a step.
Definition BDSStep.hh:32
G4ThreeVector PostStepPoint() const
Accessor.
Definition BDSStep.hh:42
BDSStep rotateZ(const G4double &angle)
Mirror function to G4ThreeVector::rotateZ(). Returns copy that's rotated.
Definition BDSStep.cc:38
G4ThreeVector PreStepPoint() const
Accessor.
Definition BDSStep.hh:41
Return either G4Tubs or G4CutTubs depending on flat face.
G4double SecondFringeFieldCorrection(BDSMagnetStrength const *strength, G4bool entranceOrExit)
Function to calculate the value of the second fringe field correction term.
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())
G4bool IsFiniteStrength(G4double variable)
G4double FringeFieldCorrection(BDSMagnetStrength const *strength, G4bool entranceOrExit)
Function to calculate the value of the fringe field correction term.