BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
BDSPTCOneTurnMap.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 "BDSException.hh"
21#include "BDSGlobalConstants.hh"
22#include "BDSParticleCoordsFullGlobal.hh"
23#include "BDSParticleDefinition.hh"
24#include "BDSPTCOneTurnMap.hh"
25#include "BDSTrajectoryPrimary.hh"
26#include "BDSUtilities.hh"
27
28#include "globals.hh" // Geant4 typedefs
29
30#include "CLHEP/Units/SystemOfUnits.h"
31
32#include <cmath>
33#include <fstream>
34#include <set>
35#include <sstream>
36#include <string>
37
38BDSPTCOneTurnMap::BDSPTCOneTurnMap(const G4String& maptableFile,
39 const BDSParticleDefinition* designParticle):
40 initialPrimaryMomentum(0),
41 beamOffsetS0(false),
42 lastTurnNumber(0),
43 xLastTurn(0),
44 pxLastTurn(0),
45 yLastTurn(0),
46 pyLastTurn(0),
47 deltaPLastTurn(0)
48{
49 referenceMomentum = designParticle->Momentum();
50 mass = designParticle->Mass();
51
52 G4String filePath = BDS::GetFullPath(maptableFile);
53 G4cout << __METHOD_NAME__ << "Using map table " << filePath << G4endl;
54 std::ifstream infile(filePath);
55 if (!infile)
56 {throw BDSException(__METHOD_NAME__, "Failed to read maptable: \"" + maptableFile + "\"");}
57
58 // The columns of the maptable TFS (read into below with the stringstream).
59 G4String name = "";
60 G4double coefficient = 0;
61 G4int nVector = 0;
62 G4int dimensionality = 0;
63 G4int totalOrder = 0;
64 G4int nx = 0;
65 G4int npx = 0;
66 G4int ny = 0;
67 G4int npy = 0;
68 G4int ndeltaP = 0;
69 G4int nt = 0;
70
71 G4String line = "";
72 while (std::getline(infile, line))
73 {
74 if (line.at(0) == '@' || line.at(0) == '*' || line.at(0) == '$')
75 {continue;}
76 std::istringstream stream(line);
77
78 stream >> name >> coefficient >> nVector >> dimensionality >> totalOrder >>
79 nx >> npx >> ny >> npy >> ndeltaP >> nt;
80
81 PTCMapTerm term{coefficient, nx, npx, ny, npy, ndeltaP};
82
83 switch (nVector)
84 {
85 case 1:
86 {xTerms.push_back(term); break;}
87 case 2:
88 {pxTerms.push_back(term); break;}
89 case 3:
90 {yTerms.push_back(term); break;}
91 case 4:
92 {pyTerms.push_back(term); break;}
93 case 5:
94 {deltaPTerms.push_back(term); break;}
95 default:
96 {
97 throw BDSException(__METHOD_NAME__, "Unrecognised PTC term index - maptable file is perhaps malformed.");
98 break;
99 }
100 }
101 }
102#ifdef BDSDEBUG
103 G4cout << __METHOD_NAME__ << "> Loaded Map:" << maptableFile << G4endl;
104#endif
105}
106
108 G4bool beamOffsetS0In)
109{
110 lastTurnNumber = BDSGlobalConstants::Instance()->TurnsTaken();
111 initialPrimaryMomentum = std::sqrt(std::pow(coords.local.totalEnergy, 2) - std::pow(mass, 2));
112 // Converting to PTC Coordinates:
113 xLastTurn = coords.local.x / CLHEP::m;
114 pxLastTurn = coords.global.xp * initialPrimaryMomentum / referenceMomentum;
115 yLastTurn = coords.local.y / CLHEP::m;
116 pyLastTurn = coords.global.yp * initialPrimaryMomentum / referenceMomentum;
117 deltaPLastTurn = (initialPrimaryMomentum - referenceMomentum) / referenceMomentum;
118
119 turnsScattered.clear();
120
121 // If we're using curvilinear then S0 != 0 and we shouldn't apply
122 // map on first turn. record this setting here for the teleporter's
123 // consideration.
124 beamOffsetS0 = beamOffsetS0In;
125}
126
128 G4double& px,
129 G4double& y,
130 G4double& py,
131 G4double& pz,
132 G4int turnsTaken)
133{
134 G4double xOut = 0.0;
135 G4double yOut = 0.0;
136 G4double pxOut = 0.0;
137 G4double pyOut = 0.0;
138 G4double pzOut = 0.0;
139 G4double deltaPOut = 0.0;
140
141 // In short: lastTurnNumber exists to prevent the map being
142 // applied multiple times in one turn.
143 // The terminator is placed before the teleporter, so on the "first
144 // turn" in the teleporter (where this method is called),
145 // TurnsTaken() will actually return 2. So lastTurnNumber, will be
146 // one less than returned by TurnsTaken(), until it is incremented
147 // below. If (lastTurnNumber ==
148 // turnsTaken (turn number seen in the TeleporterIntegrator), then
149 // the map has already been applied on this turn. In which case,
150 // return the cached values below.
151 if (lastTurnNumber < turnsTaken)
152 {
153#ifdef BDSDEBUG
154 G4cout << __METHOD_NAME__ << "Applying Map: " << G4endl;
155 G4cout << "Before map application: " << G4endl;
156 G4cout << "xLastTurn = " << xLastTurn << G4endl;
157 G4cout << "pxLastTurn = " << pxLastTurn << G4endl;
158 G4cout << "yLastTurn = " << yLastTurn << G4endl;
159 G4cout << "pyLastTurn = " << pyLastTurn << G4endl;
160#endif
161
162 lastTurnNumber = turnsTaken;
163 xOut = Evaluate(xTerms,
164 xLastTurn, pxLastTurn,
165 yLastTurn, pyLastTurn,
166 deltaPLastTurn);
167 pxOut = Evaluate(pxTerms,
168 xLastTurn, pxLastTurn,
169 yLastTurn, pyLastTurn,
170 deltaPLastTurn);
171 yOut = Evaluate(yTerms,
172 xLastTurn, pxLastTurn,
173 yLastTurn, pyLastTurn,
174 deltaPLastTurn);
175 pyOut = Evaluate(pyTerms,
176 xLastTurn, pxLastTurn,
177 yLastTurn, pyLastTurn,
178 deltaPLastTurn);
179 deltaPOut = Evaluate(deltaPTerms,
180 xLastTurn, pxLastTurn,
181 yLastTurn, pyLastTurn,
182 deltaPLastTurn);
183 // Cache results for next turn. Do it here, before we convert to BDSIM coordinates.
184 xLastTurn = xOut;
185 pxLastTurn = pxOut;
186 yLastTurn = yOut;
187 pyLastTurn = pyOut;
188 deltaPLastTurn = deltaPOut;
189#ifdef BDSDEBUG
190 G4cout << "After map application: " << G4endl;
191 G4cout << "xOut = " << xOut << G4endl;
192 G4cout << "pxOut = " << pxOut << G4endl;
193 G4cout << "yOut = " << yOut << G4endl;
194 G4cout << "pyOut = " << pyOut << G4endl;
195#endif
196 }
197 else
198 {
199 xOut = xLastTurn;
200 pxOut = pxLastTurn;
201 yOut = yLastTurn;
202 pyOut = pyLastTurn;
203 deltaPOut = deltaPLastTurn;
204#ifdef BDSDEBUG
205 G4cout << __METHOD_NAME__ << "Returning Cached Values:" << G4endl;
206 G4cout << "xOut = " << xOut << G4endl;
207 G4cout << "pxOut = " << pxOut << G4endl;
208 G4cout << "yOut = " << yOut << G4endl;
209 G4cout << "pyOut = " << pyOut << G4endl;
210#endif
211 }
212
213
214 // Now convert to BDSIM units:
215 // Convert local positions from metres to millimetres.
216 xOut *= CLHEP::m;
217 yOut *= CLHEP::m;
218
219 // PTC momenta are scaled w.r.t the _REFERENCE_MOMENTUM_.
220 pxOut *= referenceMomentum;
221 pyOut *= referenceMomentum;
222 // by defn the particle has the initial primary momentum, which we
223 // used to calculate pz.
224 pzOut = std::sqrt(std::pow(initialPrimaryMomentum, 2)
225 - std::pow(px, 2)
226 - std::pow(py, 2));
227
228 // Now set output for arguments passed by reference.
229 x = xOut;
230 px = pxOut;
231 y = yOut;
232 py = pyOut;
233 pz = pzOut;
234
235#ifdef BDSDEBUG
236 G4cout << __METHOD_NAME__ << "Converting PTC to BDSIM " << G4endl;
237 G4cout << "xOut = " << xOut << G4endl;
238 G4cout << "pxOut = " << pxOut << G4endl;
239 G4cout << "yOut = " << yOut << G4endl;
240 G4cout << "pyOut = " << pyOut << G4endl;
241 G4cout << "initialPrimaryMomentum = " << initialPrimaryMomentum << G4endl;
242 G4cout << "referenceMomentum = " << referenceMomentum << G4endl;
243 G4cout << "pzOut = " << pz << G4endl;
244#endif
245}
246
247G4double BDSPTCOneTurnMap::Evaluate(std::vector<PTCMapTerm>& terms,
248 G4double x,
249 G4double px,
250 G4double y,
251 G4double py,
252 G4double deltaP) const
253{
254 G4double result = 0;
255 for (const auto& term : terms)
256 {
257 result += (term.coefficient
258 * std::pow(x, term.nx)
259 * std::pow(px, term.npx)
260 * std::pow(y, term.ny)
261 * std::pow(py, term.npy)
262 * std::pow(deltaP, term.ndeltaP));
263 }
264 return result;
265}
266
268 G4int turnsTaken)
269{
270 // We have to use the externally provided turnsTaken rather than
271 // internal lastTurnNumber so that the OTM is definitely not applied
272 // in this case (because lastTurnNumber member of this class will
273 // not have the same value for multiple applications on the same
274 // turn) 2 and not 1 because teleporter comes after
275 // terminator, where the turn number is incremented.
276 G4bool offsetBeamS0AndOnFirstTurn = beamOffsetS0 && turnsTaken == 2;
277
278 // We reset the public static bool hasScatteredThisTurn at the end
279 // of this method. But what if the stepper is applied again on this
280 // turn? then we would be lead to believe it did not scatter when
281 // it in fact did, so we cache the turns in which it scattered so
282 // that this method returns the same result for calls on the same
283 // turn for the same primary. This is necessary because we can't
284 // force the Teleporter stepper to be called just once.
285 G4bool didScatterThisTurn = BDSTrajectoryPrimary::hasScatteredThisTurn ||
286 turnsScattered.count(turnsTaken);
287
288 // We always insert it into the set as nothing happens if it already exists, so
289 // we're safe inserting it every time for simplicity.
290 if (didScatterThisTurn)
291 {turnsScattered.insert(turnsTaken);}
292
293 // Have some tolerance for dealing with primaries far off momentum.
294 G4double ratioOffReference = std::abs((momentum - referenceMomentum) / referenceMomentum);
295 G4double tolerance = 0.05; // arbitrarily chosen 5%
296 G4bool tooFarOffMomentum = ratioOffReference > tolerance;
297
298 G4bool should = !offsetBeamS0AndOnFirstTurn && !didScatterThisTurn && !tooFarOffMomentum;
299
300#ifdef BDSDEBUG
301 G4cout << __METHOD_NAME__
302 << "scatteredThisTurn = " << BDS::BoolToString(didScatterThisTurn)
303 << G4endl;
304 G4cout << __METHOD_NAME__
305 << "beamOffsetS0 = " << BDS::BoolToString(beamOffsetS0) << G4endl;
306 G4cout << __METHOD_NAME__ << "turnsTaken = " << turnsTaken << G4endl;
307 G4cout << __METHOD_NAME__ << "Is on first turn with S0 != 0?"
308 << BDS::BoolToString(offsetBeamS0AndOnFirstTurn) << G4endl;
309 G4cout << __METHOD_NAME__
310 << "tooFarOffMomentum = " << BDS::BoolToString(tooFarOffMomentum)
311 << G4endl;
312 G4cout << __METHOD_NAME__ << "ShouldApply = " << BDS::BoolToString(should)
313 << G4endl;
314#endif
315 // Reset the static public bool as we (maybe) start the next turn.
317 return should;
318}
319
320void BDSPTCOneTurnMap::UpdateCoordinates(G4ThreeVector localPosition,
321 G4ThreeVector localMomentum,
322 G4int turnsTaken)
323{
324 if (lastTurnNumber < turnsTaken)
325 {
326 // This method is called in the integrator if the OTM is active but
327 // NOT applicable. So given that the TeleporterIntegrator will be called
328 // multiple times, whatever happens here, it should not suddenly
329 // make the OTM applicable for subsequent calls to the Teleporter
330 // stepper on the same turn.
331 xLastTurn = localPosition.x() / CLHEP::m;
332 yLastTurn = localPosition.y() / CLHEP::m;
333 pxLastTurn = localMomentum.x() / referenceMomentum;
334 pyLastTurn = localMomentum.y() / referenceMomentum;
335 G4double totalMomentum = localMomentum.mag();
336 deltaPLastTurn = (totalMomentum - referenceMomentum) / referenceMomentum;
337 // deltaPLastTurn assumed to not change between turns for 5D map.
338 lastTurnNumber = turnsTaken;
339#ifdef BDSDEBUG
340 G4cout << __METHOD_NAME__
341 << "Updating map coords without use of map:" << G4endl;
342 G4cout << "xLastTurn = " << xLastTurn << G4endl;
343 G4cout << "yLastTurn = " << yLastTurn << G4endl;
344 G4cout << "pxLastTurn = " << pxLastTurn << G4endl;
345 G4cout << "pyLastTurn = " << pyLastTurn << G4endl;
346#endif
347 }
348#ifdef BDSDEBUG
349 G4cout << __METHOD_NAME__
350 << "Skipping further coordinate updates without use of map" << G4endl;
351#endif
352}
General exception with possible name of object and message.
Definition: BDSException.hh:35
static BDSGlobalConstants * Instance()
Access method.
G4bool ShouldApplyToPrimary(G4double momentum, G4int turnstaken)
Decides whether or not this should be applied. Can add more.
void GetThisTurn(G4double &x, G4double &px, G4double &y, G4double &py, G4double &pz, G4int turnstaken)
Return the coordinates for this turn.
BDSPTCOneTurnMap()=delete
Default constructor.
void UpdateCoordinates(G4ThreeVector localPosition, G4ThreeVector localMomentum, G4int turnstaken)
Update coordinates if the last turn is greater than the number of turns taken.
void SetInitialPrimaryCoordinates(const BDSParticleCoordsFullGlobal &coords, G4bool offsetS0)
Load initial coordinates where the beam started and convert to PTC coordinates.
A set of particle coordinates in both local and global.
Wrapper for particle definition.
G4double Mass() const
Accessor.
G4double Momentum() const
Accessor.
static G4bool hasScatteredThisTurn
G4String GetFullPath(G4String filename, bool excludeNameFromPath=false, bool useCWDForPrefix=false)