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