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