BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSIntegratorDipoleQuadrupole.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 "BDSIntegratorDipoleRodrigues2.hh"
21#include "BDSIntegratorDipoleQuadrupole.hh"
22#include "BDSIntegratorQuadrupole.hh"
23#include "BDSMagnetStrength.hh"
24#include "BDSParticleDefinition.hh"
25#include "BDSStep.hh"
26#include "BDSUtilities.hh"
27
28#include "globals.hh"
29#include "G4AffineTransform.hh"
30#include "G4Mag_EqRhs.hh"
31#include "G4MagIntegratorStepper.hh"
32#include "G4PhysicalConstants.hh"
33#include "G4SystemOfUnits.hh"
34#include "G4ThreeVector.hh"
35#include "G4Transform3D.hh"
36
37#include "CLHEP/Units/SystemOfUnits.h"
38
39#include <cmath>
40
42 G4double brhoIn,
43 G4Mag_EqRhs* eqOfMIn,
44 G4double minimumRadiusOfCurvatureIn,
45 const BDSParticleDefinition* designParticle,
46 const G4double& tiltIn):
47 BDSIntegratorMag(eqOfMIn, 6),
48 nominalBRho(brhoIn),
49 eq(dynamic_cast<BDSMagUsualEqRhs*>(eqOfM)),
50 bPrime(std::abs(brhoIn) * (*strengthIn)["k1"] / CLHEP::m2),
51 nominalBeta((*strengthIn)["beta0"]),
52 nominalRho((*strengthIn)["length"]/(*strengthIn)["angle"]),
53 nominalField((*strengthIn)["field"]),
54 fieldRatio(nominalField/ (nominalBRho/nominalRho)),
55 nominalEnergy(designParticle->TotalEnergy()),
56 nominalMass(designParticle->Mass()),
57 nominalCharge(designParticle->Charge()),
58 fieldArcLength((*strengthIn)["length"]),
59 nominalAngle((*strengthIn)["angle"]),
60 tilt(tiltIn),
61 scaling((*strengthIn)["scaling"]),
62 dipole(new BDSIntegratorDipoleRodrigues2(eqOfMIn, minimumRadiusOfCurvatureIn))
63{
64 if (!std::isfinite(nominalRho))
65 {nominalRho = 0;}
66 if (!std::isfinite(fieldRatio))
67 {fieldRatio = 1;}
68 isScaled = scaling == 1 ? false : true;
69 zeroStrength = !BDS::IsFinite((*strengthIn)["field"]);
70 BDSFieldMagDipole* dipoleField = new BDSFieldMagDipole(strengthIn);
71 unitField = (dipoleField->FieldValue()).unit();
72 delete dipoleField;
73 angleForCL = fieldRatio != 1 ? nominalAngle * fieldRatio : nominalAngle;
74 angleForCL /= scaling;
75 if (!std::isfinite(angleForCL))
76 {angleForCL = 0;}
77}
78
79BDSIntegratorDipoleQuadrupole::~BDSIntegratorDipoleQuadrupole()
80{
81 delete dipole;
82}
83
84void BDSIntegratorDipoleQuadrupole::Stepper(const G4double yIn[6],
85 const G4double dydx[6],
86 const G4double h,
87 G4double yOut[6],
88 G4double yErr[6])
89{
90 // charge and unit normalisation
91 const G4double fcof = eqOfM->FCof();
92
93 // Protect against very small steps, neutral particles, and zero field: drift through.
94 if (h < 1e-12 || !BDS::IsFiniteStrength(fcof) || zeroStrength)
95 {
96 AdvanceDriftMag(yIn,h,yOut,yErr);
97 SetDistChord(0);
98 return;
99 }
100
101 // try out a simple dipole step first - dumb stepper has no error but is 2x as fast as normal step
102 dipole->SingleStep(yIn, h, yOut);
103
104 G4double dipoleDC = dipole->DistChord();
105 // We assume that the major effect is the dipole component and the quadrupole
106 // component is low. Therefore, we can safely set distchord from the dipole.
107 SetDistChord(dipoleDC);
108
109 // return just dipole kick for small step size
110 if (h < 1e-4) // 1e-4mm
111 {
112 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
113 SetDistChord(dipole->DistChord());
114 return;
115 }
116
117 // If the particle might spiral, we return and just use the dipole only component
118 // Aimed at particles of much lower momentum than the design energy.
119 G4double radiusOfCurvature = dipole->RadiusOfHelix();
120 if (dipoleDC > 0.3*radiusOfCurvature)
121 {
122 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
123 SetDistChord(dipole->DistChord());
124 return;
125 }
126
127 // Revert to backup for zero angle, finite field dipoles. This could eventually be implemented with the
128 // matrix method by transforming onto the correct trajectory, but for high field, zero angle bends,
129 // the transform angle may be large. In this case, the distchord is large and the tracking is subsequently incorrect.
130 // As zero angle bends are constructed as one magnet segment, this should be solved by splitting the zero angle
131 // beampipe into segments like an ordinary sbend, but for now it's easier and safer to revert to backup.
133 {
134 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
135 SetDistChord(dipole->DistChord());
136 return;
137 }
138
139 // not going to spiral so proceed
140 // convert to true curvilinear
141 G4ThreeVector globalPos = G4ThreeVector(yIn[0], yIn[1], yIn[2]);
142 G4ThreeVector globalMom = G4ThreeVector(yIn[3], yIn[4], yIn[5]);
143
144 // create new position and momentum vectors for matrix output
145 G4ThreeVector localCLPosOut;
146 G4ThreeVector localCLMomOut;
147
148 // calculate relative energy difference
149 G4double nomMomentum = std::abs(nominalBRho * fcof); // safe as brho is nominal and abs(fcof) is scaling factor
150 G4double deltaEnergy = eq->TotalEnergy(globalMom) - nominalEnergy;
151 // deltaE/P0 to match literature.
152 G4double deltaEoverP0 = deltaEnergy / (nomMomentum);
153
154 // get particle charge from reverse of how it's calculated in G4Mag_EqRhs::SetChargeMomentumMass.
155 G4double pcharge = fcof/(eplus*c_light);
156 G4double chargeRatio = std::abs(pcharge /nominalCharge);
157
158 // revert to backup if off-charge
159 if (chargeRatio != 1.0)
160 {
161 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
162 SetDistChord(dipole->DistChord());
163 return;
164 }
165
166 if (isScaled)
167 {
168 // if the field is scaled to account for a central momentum change, the new nominal momentum must used
169 // in calculating the particle momentum offset. If the bunch's central momentum is not altered but the
170 // scaling is applied e.g for deflecting, then the particle will have a large relative momentum offset,
171 // therefore do not use the nominal matrix.
172 G4double nominalMomentum = std::abs(nominalBRho * fcof * fieldRatio);
173 G4double nomEnergy = std::sqrt(std::pow(nominalMomentum, 2) + std::pow(nominalMass,2));
174
175 // relative energy difference offset calculated w.r.t new nominal energy c/o scaling parameter
176 deltaEnergy = eq->TotalEnergy(globalMom) - nomEnergy;
177 // deltaE/P0 to match literature.
178 deltaEoverP0 = deltaEnergy / (nominalMomentum);
179 }
180
181 G4double beta = nominalBeta;
182 G4double rho = nominalRho;
183
184 if (isScaled)
185 {
186 G4double scaledEnergy = std::sqrt(std::pow(nomMomentum * scaling, 2) + std::pow(nominalMass, 2));
187 G4double scaledGamma = scaledEnergy / nominalMass;
188 beta = std::sqrt(1.0 - (1.0 / std::pow(scaledGamma, 2)));
189 }
190
191 // revert to backup if a particle is sufficiently off energy
192 if (std::abs(deltaEoverP0) > nominalMatrixRelativeMomCut)
193 {
194 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
195 SetDistChord(dipole->DistChord());
196 return;
197 //Leave this is as this may be re-investigated in the future.
198
199 /*
200 //transform onto non-nominal trajectory if particle is sufficiently off energy
201 //calculate angle for curvilinear transform
202 G4double particleBRho = globalMom.mag() / eq->FCof();
203 G4double scaleFactor = particleBRho / nominalBRho;
204 angleForTransform *= scaleFactor;
205
206 //calculate rho and beta for use in non-nominal matrix.
207 //rho according to field strength (from input) and particle momentum
208 rho = (globalMom.mag() / fcof) / nominalField;
209 beta = eq->Beta(yIn);
210 */
211 }
212
214 globalPos, globalMom, h, true, fcof, tilt);
215
216 G4ThreeVector localCLPos = localCL.PreStepPoint();
217 G4ThreeVector localCLMom = localCL.PostStepPoint();
218 G4ThreeVector localCLMomU = localCLMom.unit();
219
220 // only proceed with thick matrix if particle is paraxial
221 // judged by forward momentum > 1-limit and |transverse| < limit (default limit=0.1)
222 if (localCLMomU.z() < (1.0 - backupStepperMomLimit) || std::abs(localCLMomU.x()) > backupStepperMomLimit ||
223 std::abs(localCLMomU.y()) > backupStepperMomLimit)
224 {
225 dipole->Stepper(yIn, dydx, h, yOut, yErr); // more accurate step with error
226 SetDistChord(dipole->DistChord());
227 return;
228 }
229
230 // dipole step. Pass in rel. energy diff. to save recalculating.
231 OneStep(localCLPos, localCLMom, localCLMomU, h, fcof, localCLPosOut, localCLMomOut, rho, beta, deltaEoverP0);
232
233 // convert to global coordinates for output
234 BDSStep globalOut = CurvilinearToGlobal(fieldArcLength, unitField, angleForCL,
235 localCLPosOut, localCLMomOut, true, fcof, tilt);
236 G4ThreeVector globalPosOut = globalOut.PreStepPoint();
237 G4ThreeVector globalMomOut = globalOut.PostStepPoint();
238
239 // error along direction of travel really
240 G4ThreeVector globalMomOutU = globalMomOut.unit();
241 globalMomOutU *= 1e-10;
242
243 // chord distance (simple quadratic approx)
244 G4double dc = h*h/(8*radiusOfCurvature);
245 if (std::isnan(dc))
246 {SetDistChord(0);}
247 else
248 {SetDistChord(dc);}
249
250 // write out values and errors
251 for (G4int i = 0; i < 3; i++)
252 {
253 yOut[i] = globalPosOut[i];
254 yOut[i + 3] = globalMomOut[i];
255 yErr[i] = globalMomOutU[i];
256 yErr[i + 3] = 1e-40;
257 }
258}
259
260void BDSIntegratorDipoleQuadrupole::OneStep(const G4ThreeVector& posIn,
261 const G4ThreeVector& momIn,
262 const G4ThreeVector& momUIn,
263 const G4double& h,
264 const G4double& fcof,
265 G4ThreeVector& posOut,
266 G4ThreeVector& momOut,
267 const G4double rho,
268 const G4double beta,
269 const G4double deltaEoverP) const
270{
271 G4double momInMag = momIn.mag();
272 G4double nomMomentum = std::abs(nominalBRho * fcof); // safe as brho is nominal and abs(fcof) is scaling factor
273
274 // quad strength k normalised to charge and nominal momentum
275 // eqOfM->FCof() gives us conversion to MeV,mm and rigidity in Tm correctly
276 // as well as charge of the given particle
277 G4double K1 = std::abs(fcof)*bPrime/nomMomentum;
278 G4bool focussing = K1 >= 0; // depends on charge as well (in eqOfM->FCof())
279
280 // separate focussing strengths for vertical and horizontal axes.
281 // Used by matrix elements so must be derived from nominal values.
282 // strength parameter calculated differently depending on value
283 // default values are for +ve K1
284 G4double invrho2 = std::pow(1.0 / rho, 2);
285 G4double kx2 = invrho2 + K1;
286 G4double kx = std::sqrt(std::abs(kx2));
287 G4double kxl = kx * h;
288
289 G4double ky2 = -K1;
290 G4double ky = std::sqrt(std::abs(ky2));
291 G4double kyl = ky * h;
292
293 G4double x0 = posIn.x();
294 G4double y0 = posIn.y();
295 G4double s0 = posIn.z();
296 G4double xp = momUIn.x();
297 G4double yp = momUIn.y();
298 G4double zp = momUIn.z();
299
300 G4double x1 = x0;
301 G4double y1 = y0;
302 G4double xp1 = xp;
303 G4double yp1 = yp;
304 G4double zp1 = zp;
305
306 G4double X11=0,X12=0,X16=0,X21=0,X22=0,X26=0;
307 G4double Y11=0,Y12=0,Y21=0,Y22=0;
308
309 // matrix elements. All must derived from nominal parameters.
310 if (focussing)
311 {//focussing
312 X11 = std::cos(kxl);
313 X12 = std::sin(kxl)/kx;
314 X21 =-std::abs(kx2)*X12;
315 X22 = X11;
316 X16 = (1.0/beta) * ((1.0/rho) / kx2) * (1 - std::cos(kxl));
317 X26 = (1.0/beta) * (1.0/rho) * X12;
318
319 Y11= std::cosh(kyl);
320 Y12= std::sinh(kyl)/ky;
321 if (std::isnan(Y12)) //y12 can be nan from div by 0 in previous line
322 {Y12 = h;}
323 Y21= std::abs(ky2)*Y12;
324 Y22= Y11;
325 }
326 else
327 {// defocussing
328 if (std::abs(K1) < invrho2)
329 {
330 kx2 = invrho2 - std::abs(K1);
331 kx = std::sqrt(std::abs(kx2));
332 kxl = kx * h;
333 X11 = std::cos(kxl);
334 X12 = std::sin(kxl)/kx; // always +ve
335 X21 = -std::abs(kx2)*X12;
336 X22 = X11;
337 }
338 else
339 {
340 kx2 = std::abs(K1) - invrho2;
341 kx = std::sqrt(std::abs(kx2));
342 kxl = kx * h;
343 X11 = std::cosh(kxl);
344 X12 = std::sinh(kxl)/kx; // always +ve
345 X21 = std::abs(kx2)*X12;
346 X22 = X11;
347 }
348 X16 = (1.0/beta) * ((1.0/rho) / kx2) * (1 - std::cos(kxl));
349 X26 = (1.0/beta) * (1.0/rho) * X12;
350
351 Y11= std::cos(kyl);
352 Y12= std::sin(kyl)/ky;
353 if (std::isnan(Y12)) //y12 can be nan from div by 0 in previous line
354 {Y12 = h;}
355 Y21= -std::abs(ky2)*Y12;
356 Y22= Y11;
357 }
358
359 x1 = X11*x0 + X12*xp + X16*deltaEoverP;
360 xp1 = X21*x0 + X22*xp + X26*deltaEoverP;
361
362 y1 = Y11*y0 + Y12*yp;
363 yp1 = Y21*y0 + Y22*yp;
364
365 G4double s1 = s0 + h;
366
367 // relies on normalised momenta otherwise this will be nan.
368 zp1 = std::sqrt(std::abs(1 - xp1*xp1 - yp1*yp1));
369 if (std::isnan(zp1))
370 {zp1 = zp;} // ensure not nan
371
372 G4ThreeVector momOutUnit = G4ThreeVector(xp1, yp1, zp1);
373 momOut = momOutUnit * momInMag;
374
375 posOut = G4ThreeVector(x1, y1, s1);
376
377}
378
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)
A uniform dipole field.
G4double nominalRho
Cached magnet property, nominal bending radius.
G4double fieldRatio
Ratio of supplied field to nominal field. Needed for over/underpowered magnets.
virtual void Stepper(const G4double y[6], const G4double dydx[6], const G4double h, G4double yOut[6], G4double yErr[6])
const G4double fieldArcLength
Cache of the field arc length.
BDSIntegratorDipoleQuadrupole()=delete
Private default constructor to enforce use of supplied constructor.
void OneStep(const G4ThreeVector &posIn, const G4ThreeVector &momIn, const G4ThreeVector &momUIn, const G4double &h, const G4double &fcof, G4ThreeVector &posOut, G4ThreeVector &momOut, const G4double rho, const G4double beta, const G4double deltaEnergy) const
const G4double nominalCharge
Nominal beam charge.
const G4double bPrime
Cached magnet property, B field gradient for calculating K1.
const G4double nominalAngle
Cache of the field angle.
G4double angleForCL
Angle used for curvilinear transforms.
G4ThreeVector unitField
Cache of the unit field direction.
G4double tilt
Tilt offset transform for field.
const G4double nominalMass
Primary particle mass. Needed for recalculating nominal energy with scaling.
const G4double nominalBeta
Cached nominal relativistic beta of the nominal beam particle.
const G4double scaling
Cache field scaling factor.
G4bool isScaled
Cache if field is scaled.
BDSMagUsualEqRhs * eq
BDSIM's eqRhs class to give access to particle properties.
const G4double nominalBRho
Cached magnet property, nominal magnetic rigidity.
const G4double nominalEnergy
Nominal beam energy.
BDSIntegratorDipoleRodrigues2 * dipole
Backup integrator.
Exact helix through pure dipole field.
void SingleStep(const G4double yIn[6], const G4double &h, G4double yOut[6])
G4double RadiusOfHelix() const
Public accessor for protected variable in base class.
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.
Common functionality to BDSIM integrators.
static G4double nominalMatrixRelativeMomCut
G4Mag_EqRhs * eqOfM
void SetDistChord(G4double distChordIn)
Setter for distChord to private member.
Override G4Mag_UsualEqRhs, provides BDSIM integrators access to particle attributes.
G4double TotalEnergy(const G4double y[])
Calculate total particle energy.
Efficient storage of magnet strengths.
Wrapper for particle definition.
A simple class to represent the positions of a step.
Definition BDSStep.hh:32
G4ThreeVector PostStepPoint() const
Accessor.
Definition BDSStep.hh:42
G4ThreeVector PreStepPoint() const
Accessor.
Definition BDSStep.hh:41
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())
G4bool IsFiniteStrength(G4double variable)
STL namespace.