BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
BDSFieldQuery.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 "BDSAuxiliaryNavigator.hh"
20#include "BDSDebug.hh"
21#include "BDSException.hh"
22#include "BDSFieldQuery.hh"
23#include "BDSFieldQueryInfo.hh"
24#include "BDSUtilities.hh"
25#include "BDSWarning.hh"
26
27#include "globals.hh"
28#include "G4Field.hh"
29#include "G4FieldManager.hh"
30#include "G4LogicalVolume.hh"
31#include "G4Navigator.hh"
32#include "G4String.hh"
33#include "G4ThreeVector.hh"
34#include "G4Types.hh"
35#include "G4VPhysicalVolume.hh"
36
37#include "CLHEP/Units/SystemOfUnits.h"
38
39#include <cmath>
40#include <fstream>
41#include <iomanip>
42#include <vector>
43
44G4Navigator* BDSFieldQuery::navigator = new G4Navigator();
45
46BDSFieldQuery::BDSFieldQuery():
47 queryMagnetic(false),
48 queryElectric(false),
49 writeX(false),
50 writeY(false),
51 writeZ(false),
52 writeT(false)
53{
54 CleanUp();
55}
56
57BDSFieldQuery::~BDSFieldQuery()
58{;}
59
60void BDSFieldQuery::AttachWorldVolumeToNavigator(G4VPhysicalVolume* worldPVIn)
61{
62 navigator->SetWorldVolume(worldPVIn);
63}
64
66{
67 CloseFiles();
68 queryMagnetic = false;
69 queryElectric = false;
70 writeX = false;
71 writeY = false;
72 writeZ = false;
73 writeT = false;
74 navigator->ResetStackAndState();
75}
76
77void BDSFieldQuery::QueryFields(const std::vector<BDSFieldQueryInfo*>& fieldQueries)
78{
79 for (const auto& fieldQuery : fieldQueries)
80 {QueryField(fieldQuery);}
81}
82
84{
85 CleanUp();
86
87 if (!query)
88 {return;} // protection - now we assume this pointer is always valid in the rest of this class
89
90 // warn the user if a fieldObject is specified. In this context (bdsim) this has no effect
91 // derived class may change this
92 if (query->checkParameters)
94
95 if (query->SpecificPoints())
96 {
98 return;
99 }
100
101 G4cout << "FieldQuery> \"" << query->name << "\" with N (x,y,z,t) points: ("
102 << query->xInfo.n << ", " << query->yInfo.n << ", " << query->zInfo.n << ", " << query->tInfo.n
103 << ")";
104 PrintBAndEInfo(query);
105
106 if (query->printTransform)
107 {G4cout << query->globalTransform << G4endl;}
108
110 BDSAuxiliaryNavigator::ResetNavigatorStates();
111
112 G4double xMin = query->xInfo.min;
113 G4double nStepsX = query->xInfo.n == 1 ? 1 : (G4double)query->xInfo.n-1;
114 G4double xStep = (query->xInfo.max - query->xInfo.min) / nStepsX;
115 if (std::isnan(xStep))
116 {xStep = 1.0;}
117 CheckNStepsAndRange(query->xInfo, "x", query->name);
118
119 G4double yMin = query->yInfo.min;
120 G4double nStepsY = query->yInfo.n == 1 ? 1 : (G4double)query->yInfo.n-1;
121 G4double yStep = (query->yInfo.max - query->yInfo.min) / nStepsY;
122 if (std::isnan(yStep))
123 {yStep = 1.0;}
124 CheckNStepsAndRange(query->yInfo, "y", query->name);
125
126 G4double zMin = query->zInfo.min;
127 G4double nStepsZ = query->zInfo.n == 1 ? 1 : (G4double)query->zInfo.n-1;
128 G4double zStep = (query->zInfo.max - query->zInfo.min) / nStepsZ;
129 if (std::isnan(zStep))
130 {zStep = 1.0;}
131 CheckNStepsAndRange(query->zInfo, "z", query->name);
132
133 G4double tMin = query->tInfo.min;
134 G4double nStepsT = query->yInfo.n == 1 ? 1 : (G4double)query->tInfo.n-1;
135 G4double tStep = (query->tInfo.max - query->tInfo.min) / nStepsT;
136 if (std::isnan(tStep))
137 {tStep = 1.0;}
138 CheckNStepsAndRange(query->tInfo, "t", query->name);
139
140 G4ThreeVector xyzGlobal = G4ThreeVector();
141 const G4AffineTransform& localToGlobalTransform = query->globalTransform;
142 G4AffineTransform globalToLocalTransform = localToGlobalTransform.Inverse();
143
144 G4ThreeVector generalUnitZ(0,0,1);
145 localToGlobalTransform.ApplyAxisTransform(generalUnitZ);
146
147 OpenFiles(query);
148
149 G4double globalFieldValue[6];
150 G4double localFieldValue[6];
151
152 G4double tLocal = tMin;
153 for (G4int i = 0; i < query->tInfo.n; i++)
154 {
155 G4double zLocal = zMin;
156 for (G4int j = 0; j < query->zInfo.n; j++)
157 {
158 G4double yLocal = yMin;
159 for (G4int k = 0; k < query->yInfo.n; k++)
160 {
161 G4double xLocal = xMin;
162 for (G4int l = 0; l < query->xInfo.n; l++)
163 {
164 xyzGlobal = LocalToGlobalPoint(localToGlobalTransform, xLocal, yLocal, zLocal);
165 GetFieldValue(xyzGlobal, generalUnitZ, tLocal, globalFieldValue);
166 GlobalToLocalAxisField(globalToLocalTransform,
167 globalFieldValue,
168 localFieldValue);
169 WriteFieldValue({xLocal, yLocal, zLocal}, tLocal, localFieldValue);
170
171 xLocal += xStep;
172 }
173 yLocal += yStep;
174 }
175 zLocal += zStep;
176 }
177 tLocal += tStep;
178 }
179
180 CloseFiles();
181 G4cout << "FieldQuery> Complete" << G4endl;
182}
183
185 const G4String& dimensionName,
186 const G4String& queryName) const
187{
188 const G4String& d = dimensionName;
189 G4bool nonZeroRange = BDS::IsFinite(std::abs(dimensionInfo.max - dimensionInfo.min));
190 if (dimensionInfo.n > 1 && !nonZeroRange)
191 {
192 G4String msg = "Problem in query \"" + queryName + "\": n"+d+" > 1, but |"+d;
193 msg += "max - "+d+"min| = 0 -> cannot take more than 1 step in 0 range.";
194 throw BDSException(__METHOD_NAME__, msg);
195 }
196 if (dimensionInfo.n == 1 && nonZeroRange)
197 {BDS::Warning("Only 1 point to query in \"" + queryName + "\" over a non-zero range - check n"+d);}
198}
199
201{
202 if (!(query->fieldObject.empty()))
203 {
204 G4String msg = "\"fieldObject\" variable is specified in query definition \"" + query->name;
205 msg += "\" - this has no effect\nInstead use \"referenceElement\"";
206 BDS::Warning(msg);
207 }
208}
209
211{
212 const std::vector<BDSFourVector<G4double>> points = query->pointsToQuery;
213
214 G4cout << "FieldQuery> \"" << query->name << "\" with N points: "
215 << points.size() << ", writing to file";
216 PrintBAndEInfo(query);
217
218 OpenFiles(query);
219 G4ThreeVector xyz;
220 G4double t;
221 G4ThreeVector generalUnitZ(0,0,1);
222 G4double globalFieldValue[6];
223 for (auto const& xyzt : points)
224 {
225 xyz = G4ThreeVector(xyzt.x(), xyzt.y(), xyzt.z());
226 t = xyzt.t();
227 GetFieldValue(xyz, generalUnitZ, t, globalFieldValue);
228 WriteFieldValue(xyz, t, globalFieldValue);
229 }
230 CloseFiles();
231 G4cout << "FieldQuery> Complete" << G4endl;
232}
233
235{
236 G4cout << ", writing to file";
237 if (query->queryMagnetic && query->queryElectric)
238 {G4cout << "s B: \"" << query->outfileMagnetic << "\" and E: \"" << query->outfileElectric << "\"" << G4endl;}
239 else if (query->queryMagnetic)
240 {G4cout << " B: \"" << query->outfileMagnetic << "\"" << G4endl;}
241 else
242 {G4cout << " E: \"" << query->outfileElectric << "\"" << G4endl;}
243}
244
246{
247 if (!(query->pointsColumnNames.empty()))
248 {
249 std::map<G4String, G4bool*> columnNamesToFlag = { {"x", &writeX},
250 {"y", &writeY},
251 {"z", &writeZ},
252 {"t", &writeT} };
253 for (const auto& columnName : query->pointsColumnNames)
254 { *(columnNamesToFlag[columnName]) = true;}
255 BDS::Warning(__METHOD_NAME__, "the number of points and min max values in the output header will not be correct when using points");
256 }
257 else
258 {
259 writeX = query->xInfo.n > 1;
260 writeY = query->yInfo.n > 1;
261 writeZ = query->zInfo.n > 1;
262 writeT = query->tInfo.n > 1;
263 }
264
265 if (query->queryMagnetic)
266 {
267 if (BDS::FileExists(query->outfileMagnetic) && !(query->overwriteExistingFiles))
268 {
269 G4String msg = "\"" + query->outfileMagnetic + "\" file already exists and \"overwriteExistingFiles\" in query \"";
270 msg += query->name + "\" is false";
271 throw BDSException(__METHOD_NAME__, msg);
272 }
273 queryMagnetic = true;
274 oFileMagnetic.open(query->outfileMagnetic);
275 WriteHeader(oFileMagnetic, query);
276 }
277
278 if (query->queryElectric)
279 {
280 if (BDS::FileExists(query->outfileElectric) && !(query->overwriteExistingFiles))
281 {
282 G4String msg = "\"" + query->outfileElectric + "\" file already exists and \"overwriteExistingFiles\" in query \"";
283 msg += query->name + "\" is false";
284 throw BDSException(__METHOD_NAME__, msg);
285 }
286 queryElectric = true;
287 oFileElectric.open(query->outfileElectric);
288 WriteHeader(oFileElectric, query);
289 }
290}
291
292void BDSFieldQuery::WriteHeader(std::ofstream& out,
293 const BDSFieldQueryInfo* query) const
294{
295 G4String columns = "! ";
296 if (writeX)
297 {
298 out << "nx> " << query->xInfo.n << "\n";
299 out << "xmin> " << query->xInfo.min/CLHEP::cm << "\n";
300 out << "xmax> " << query->xInfo.max/CLHEP::cm << "\n";
301 columns += " X ";
302 }
303 if (writeY)
304 {
305 out << "ny> " << query->yInfo.n << "\n";
306 out << "ymin> " << query->yInfo.min/CLHEP::cm << "\n";
307 out << "ymax> " << query->yInfo.max/CLHEP::cm << "\n";
308 columns += " Y ";
309 }
310 if (writeZ)
311 {
312 out << "nz> " << query->zInfo.n << "\n";
313 out << "zmin> " << query->zInfo.min/CLHEP::cm << "\n";
314 out << "zmax> " << query->zInfo.max/CLHEP::cm << "\n";
315 columns += " Z ";
316 }
317 if (writeT)
318 {
319 out << "nt> " << query->tInfo.n << "\n";
320 out << "tmin> " << query->tInfo.min/CLHEP::s << "\n";
321 out << "tmax> " << query->tInfo.max/CLHEP::s << "\n";
322 columns += " T ";
323 }
324 columns += " Fx Fy Fz\n";
325 out << columns;
326}
327
329{
330 if (oFileMagnetic.is_open())
331 {oFileMagnetic.close();}
332 if (oFileElectric.is_open())
333 {oFileElectric.close();}
334}
335
336G4ThreeVector BDSFieldQuery::LocalToGlobalPoint(const G4AffineTransform& localToGlobalTransform,
337 G4double xLocal,
338 G4double yLocal,
339 G4double zLocal) const
340{
341 G4ThreeVector xyzGlobal = G4ThreeVector(xLocal, yLocal, zLocal);
342 localToGlobalTransform.ApplyPointTransform(xyzGlobal);
343 return xyzGlobal;
344}
345
346void BDSFieldQuery::GlobalToLocalAxisField(const G4AffineTransform& globalToLocalTransform,
347 const G4double globalBEField[6],
348 G4double localBEField[6])
349{
350 G4ThreeVector B(globalBEField[0], globalBEField[1], globalBEField[2]);
351 G4ThreeVector E(globalBEField[3], globalBEField[4], globalBEField[5]);
352 globalToLocalTransform.ApplyAxisTransform(B);
353 globalToLocalTransform.ApplyAxisTransform(E);
354 for (G4int i = 0; i < 3; i++)
355 {
356 localBEField[i] = B[i];
357 localBEField[i+3] = E[i];
358 }
359}
360
361void BDSFieldQuery::GetFieldValue(const G4ThreeVector& globalXYZ,
362 const G4ThreeVector& globalDirection,
363 G4double tGlobal,
364 G4double fieldValue[6])
365{
366 // always update as 0 as can't predict behaviour of Geant4 - takes fieldValue* as
367 // variable array length, so we rely on it definitely writing E field as well as B??
368 for (G4int i = 0; i < 6; i++)
369 {fieldValue[i] = 0;}
370 G4VPhysicalVolume* pv = navigator->LocateGlobalPointAndSetup(globalXYZ, &globalDirection, /*relativeSearch*/false);
371 if (!pv)
372 {return;} // this would happen if we query a point outside the world
373 G4LogicalVolume* lv = pv->GetLogicalVolume();
374 G4FieldManager* fm = lv->GetFieldManager();
375 if (fm)
376 {
377 const G4Field* field = fm->GetDetectorField();
378 G4double position[4] = {globalXYZ.x(), globalXYZ.y(),globalXYZ.z(), tGlobal};
379 field->GetFieldValue(position, fieldValue);
380 }
381}
382
383void BDSFieldQuery::WriteFieldValue(const G4ThreeVector& xyzLocal,
384 G4double tLocal,
385 const G4double fieldValue[6])
386{
387 if (queryMagnetic)
388 {
389 if (writeX)
390 {oFileMagnetic << std::setprecision(6) << std::setw(10) << xyzLocal.x() / CLHEP::cm << " ";}
391 if (writeY)
392 {oFileMagnetic << std::setprecision(6) << std::setw(10) << xyzLocal.y() / CLHEP::cm << " ";}
393 if (writeZ)
394 {oFileMagnetic << std::setprecision(6) << std::setw(10) << xyzLocal.z() / CLHEP::cm << " ";}
395 if (writeT)
396 {oFileMagnetic << std::setprecision(6) << std::setw(10) << tLocal / CLHEP::s << " ";}
397 for (G4int i = 0; i < 3; i++)
398 {oFileMagnetic << std::setprecision(6) << std::setw(15) << fieldValue[i] / CLHEP::tesla << " ";}
399 oFileMagnetic << "\n";
400 }
401 if (queryElectric)
402 {
403 if (writeX)
404 {oFileElectric << std::setprecision(6) << std::setw(10) << xyzLocal.x() / CLHEP::cm << " ";}
405 if (writeY)
406 {oFileElectric << std::setprecision(6) << std::setw(10) << xyzLocal.y() / CLHEP::cm << " ";}
407 if (writeZ)
408 {oFileElectric << std::setprecision(6) << std::setw(10) << xyzLocal.z() / CLHEP::cm << " ";}
409 if (writeT)
410 {oFileElectric << std::setprecision(6) << std::setw(10) << tLocal / CLHEP::s << " ";}
411 for (G4int i = 3; i < 6; i++)
412 {oFileElectric << std::setprecision(6) << std::setw(15) << fieldValue[i] / (CLHEP::volt/CLHEP::m) << " ";}
413 oFileElectric << "\n";
414 }
415}
General exception with possible name of object and message.
Definition: BDSException.hh:35
Holder class for all information required for a field query.
G4bool checkParameters
For internal testing use only.
G4bool SpecificPoints() const
Whether to query a specific set of points.
G4String fieldObject
Optional for use in interpolator.
static G4Navigator * navigator
One navigator used - static so we can externally link it up to the world PV.
static void AttachWorldVolumeToNavigator(G4VPhysicalVolume *worldPVIn)
Setup the navigator w.r.t. to a world volume.
virtual void OpenFiles(const BDSFieldQueryInfo *query)
Open potentially both electric and magnetic field map files.
virtual void WriteHeader(std::ofstream &out, const BDSFieldQueryInfo *query) const
Utility to write required header information.
void QueryFields(const std::vector< BDSFieldQueryInfo * > &fieldQueries)
Vector version of above function. Unique output files for each query.
virtual void CleanUp()
Reset any member variables used during a query. Closes any files if open.
G4ThreeVector LocalToGlobalPoint(const G4AffineTransform &localToGlobalTransform, G4double xLocal, G4double yLocal, G4double zLocal) const
Apply a transform to the coordinates. Does not apply to time.
void CheckNStepsAndRange(const BDSFieldQueryInfo::QueryDimensionInfo &dimensionInfo, const G4String &dimensionName, const G4String &queryName) const
Throw an exception if the number of steps is >1 and the difference between max and min is 0.
virtual void CheckIfFieldObjectSpecified(const BDSFieldQueryInfo *query) const
Warn the user if the fieldObject variable is use when it shouldn't be.
void QuerySpecificPoints(const BDSFieldQueryInfo *query)
Different algorithm where we query a specific list of points defined in the query info object.
virtual void CloseFiles()
Close any files if open.
virtual void PrintBAndEInfo(const BDSFieldQueryInfo *query) const
Print out whether B and E files are being generated and which ones.
virtual void QueryField(const BDSFieldQueryInfo *query)
Query the field in the Geant4 model according to information in query.
virtual void GetFieldValue(const G4ThreeVector &globalXYZ, const G4ThreeVector &globalDirection, G4double tGlobal, G4double fieldValue[6])
virtual void WriteFieldValue(const G4ThreeVector &xyzGlobal, G4double tGlobal, const G4double fieldValue[6])
Write an entry ta line of the output file(s). The array is assumed to be Bx,By,Bz,...
void GlobalToLocalAxisField(const G4AffineTransform &globalToLocalTransform, const G4double globalBEField[6], G4double localBEField[6])
Convert a global field axis to a local one.
G4bool FileExists(const G4String &filename)
Checks if filename exists.
G4bool IsFinite(G4double value, G4double tolerance=std::numeric_limits< double >::epsilon())