BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
BDSFieldLoaderBDSIM.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 "BDSArray1DCoords.hh"
19#include "BDSArray2DCoords.hh"
20#include "BDSArray3DCoords.hh"
21#include "BDSArray4DCoords.hh"
22#include "BDSDebug.hh"
23#include "BDSException.hh"
24#include "BDSFieldLoaderBDSIM.hh"
25#include "BDSFieldValue.hh"
26#include "BDSUtilities.hh"
27
28#include "globals.hh"
29#include "G4String.hh"
30
31#include "CLHEP/Units/SystemOfUnits.h"
32
33#include <algorithm>
34#include <array>
35#include <exception>
36#include <fstream>
37#include <iostream>
38#include <map>
39#include <regex>
40#include <sstream>
41#include <string>
42#include <vector>
43
44#ifdef USE_GZSTREAM
45#include "src-external/gzstream/gzstream.h"
46#endif
47
48template <class T>
50 nColumns(0),
51 fv(BDSFieldValue()),
52 result(nullptr),
53 headerMustBePositiveKeys({"nx", "ny", "nz", "nt"}),
54 indexOfFirstFieldValue(0)
55{
56 dimKeyMap = {
57 {BDSDimensionType::x, {"nx", "xmin", "xmax"}},
58 {BDSDimensionType::y, {"ny", "ymin", "ymax"}},
59 {BDSDimensionType::z, {"nz", "zmin", "zmax"}},
60 {BDSDimensionType::t, {"nt", "tmin", "tmax"}}
61 };
62 dimUnitsMap = {
63 {BDSDimensionType::x, CLHEP::cm},
64 {BDSDimensionType::y, CLHEP::cm},
65 {BDSDimensionType::z, CLHEP::cm},
66 {BDSDimensionType::t, CLHEP::s},
67 };
68}
69
70template <class T>
72{;}
73
74template <class T>
76{
77 nColumns = 0;
78 lineData.clear();
79 fv = BDSFieldValue();
80 std::vector<G4String> allKeys = {"nx", "ny", "nz", "nt",
81 "xmin", "xmax", "ymin", "ymax",
82 "zmin", "zmax", "tmin", "tmax"};
83 for (auto& key : allKeys)
84 {header[key] = 0;}
85 for (auto& key : headerMustBePositiveKeys)
86 {header[key] = 1;}
87 result = nullptr;
88 loopOrder = "xyzt";
89 indexOfFirstFieldValue = 0;
90}
91
92template <class T>
93void BDSFieldLoaderBDSIM<T>::Terminate(const G4String& message)
94{
95 file.close();
96 throw BDSException("BDSFieldLoaderBDSIM", message);
97}
98
99template <class T>
101{
102 Load(fileName,1);
103 return static_cast<BDSArray1DCoords*>(result);
104}
105
106template <class T>
108{
109 Load(fileName,2);
110 return static_cast<BDSArray2DCoords*>(result);
111}
112
113template <class T>
115{
116 Load(fileName,3);
117 return static_cast<BDSArray3DCoords*>(result);
118}
119
120template <class T>
122{
123 Load(fileName,4);
124 return result;
125}
126
127template <class T>
128void BDSFieldLoaderBDSIM<T>::Load(const G4String& fileName,
129 const unsigned int nDim)
130{
131 G4String functionName = "BDSIM Field Format> ";
132 CleanUp();
133
134 file.open(fileName);
135 long long unsigned int currentLineNumber = 0;
136 // test if file is valid
137#ifdef USE_GZSTREAM
138 bool validFile = file.rdbuf()->is_open();
139#else
140 bool validFile = file.is_open();
141#endif
142 if (!validFile)
143 {throw BDSException(__METHOD_NAME__, "Invalid file name or no such file named \"" + fileName + "\"");}
144 else
145 {G4cout << functionName << "Loading \"" << fileName << "\"" << G4endl;}
146
147 // temporary variables
148 unsigned long xIndex = 0;
149 unsigned long yIndex = 0;
150 unsigned long zIndex = 0;
151 G4bool intoData = false;
152 std::string line;
153
154 // Number of points in each dimension - not necessarily x,y,z,t.
155 // For example, could be xz
156 // These are always in xyzt order
157 G4int n1 = 1, n2 = 1, n3 = 1, n4 = 1;
158
159 // wrap in vectors for easy assignment
160 G4String nominalOrder = "xyzt";
161
162 float maximumFieldValue = 0;
163 float minimumFieldValue = 0;
164 // read top of the file
165 while (!intoData)
166 {
167 std::getline(file, line);
168
169 currentLineNumber++;
170 // Skip a line if it's only whitespace
171 if (std::all_of(line.begin(), line.end(), isspace))
172 {continue;}
173
174 if (currentLineNumber > 100) // !intoData is always true as we're inside this while loop
175 {
176 G4String msg = functionName+"did not encounter column specification line starting with '!' in the first 100 lines\n";
177 msg += "This does not appear to be a BDSIM-format field map.";
178 Terminate(msg);
179 }
180
181 // now several checks
182 // 1) key > number
183 // 2) key > word
184 // 3) row starting '!' (column row ->then constructs data)
185
186 // key definition
187 // if (line.find(">") != std::string::npos)
188 // NOTE the use of a regex expression here allows us to safely parse the first line
189 // in a tar.gz file which normally has a load of bumf upfront for the tar file. Each
190 // subsequent line comes out normally with the gzstream reader.
191 std::smatch matchHeaderNumber;
192 std::regex keyValue(R"((\w*)\s*>\s*([0-9eE.+-]+))");
193 if (std::regex_search(line, matchHeaderNumber, keyValue))
194 {// must be key definition
195 if (matchHeaderNumber.size() < 2)
196 {Terminate(functionName+"Invalid key definition in field format: " + line);}
197 else
198 {
199 G4String key = G4String(matchHeaderNumber[1]);
200 key = BDS::LowerCase(key);
201
202 // check it's a valid key - header preloaded with valid keys
203 if (header.find(key) == header.end())
204 {Terminate(functionName+"Invalid key \"" + key +"\" in header");}
205
206 G4double value = 0;
207 try
208 {value = std::stod(matchHeaderNumber[2]);}
209 catch (const std::invalid_argument&)
210 {Terminate(functionName+"Invalid argument " + std::string(matchHeaderNumber[2]));}
211 catch (const std::out_of_range&)
212 {Terminate(functionName+"Number out of range " + std::string(matchHeaderNumber[2]));}
213
214 if (std::find(headerMustBePositiveKeys.begin(), headerMustBePositiveKeys.end(), key) != headerMustBePositiveKeys.end())
215 {
216 if (value < 1)
217 {Terminate(functionName+"Number of points in dimension must be greater than 0 -> see \"" + key + "\"");}
218 }
219
220 header[key] = value;
221 continue;
222 }
223 }
224
225 std::smatch matchHeaderString;
226 // matches "key > string" where string is 1-4 characters (not numbers)
227 // can be padded between each part with whitespace \s*
228 // not more than four characters (via \b for word boundary)
229 std::regex keyWord(R"((\w+)\s*>\s*([a-zA-Z]{1,4})\b\s*)");
230 if (std::regex_search(line, matchHeaderString, keyWord))
231 {
232 loopOrder = G4String(matchHeaderString[2]); // member variable
233
234 G4bool test1 = loopOrder == "xyzt";
235 G4bool test2 = loopOrder == "tzyx";
236 if (! (test1 || test2) )
237 {Terminate(functionName+"loopOrder> header variable is not one of (\"xyzt\" or \"tzyx\")");}
238 continue;
239 }
240
241 // if starts with '!' - columns
242 // check all required keys have been built up ok
243 // set nColumns
244 std::regex columnRow("^\\s*!"); // ignore any initial white space and look for '!'
245 if (std::regex_search(line, columnRow))
246 {
247 // we only need to record the number of columns and which ones are
248 // the x,y,z field component ones.
249 std::regex afterExclamation("\\s*!\\s*(.+)");
250 std::smatch match;
251 std::regex_search(line, match, afterExclamation);
252 std::string restOfLine = match[1];
253 std::string columnName;
254 std::istringstream restOfLineSS(restOfLine);
255 std::vector<G4String> columnNames;
256 while (restOfLineSS >> columnName)
257 {
258 nColumns++;
259 if (columnName.find("Fx") != std::string::npos)
260 {xIndex = nColumns; continue;}
261 else if (columnName.find("Fy") != std::string::npos)
262 {yIndex = nColumns; continue;}
263 else if (columnName.find("Fz") != std::string::npos)
264 {zIndex = nColumns; continue;}
265 else
266 {columnNames.emplace_back(columnName);}
267 }
268 lineData.resize(nColumns + 1); // +1 for default value
269 indexOfFirstFieldValue = std::min({xIndex, yIndex, zIndex});
270
271 for (const auto& key : headerMustBePositiveKeys)
272 {
273 if (header[key] < 1)
274 {Terminate(functionName+"Number of points in dimension must be greater than 0 -> see \"" + key + "\"");}
275 }
276
277 if (nColumns < (nDim + 3)) // 3 for field components
278 {Terminate(functionName+"Too few columns for " + std::to_string(nDim) + "D field loading");}
279
280 // we have all the information now, so initialise the container
281 switch (nDim)
282 {
283 case 1:
284 {
285 BDSDimensionType firstDim = BDS::DetermineDimensionType(columnNames[0]);
286 auto keys = dimKeyMap[firstDim];
287 double unit = dimUnitsMap[firstDim];
288 n1 = G4int(header[keys.number]);
289 result = new BDSArray1DCoords(n1,
290 header[keys.min] * unit,
291 header[keys.max] * unit,
292 firstDim);
293 break;
294 }
295 case 2:
296 {
297 BDSDimensionType firstDim = BDS::DetermineDimensionType(columnNames[0]);
298 BDSDimensionType secondDim = BDS::DetermineDimensionType(columnNames[1]);
299 auto fKeys = dimKeyMap[firstDim];
300 double fUnit = dimUnitsMap[firstDim];
301 auto sKeys = dimKeyMap[secondDim];
302 double sUnit = dimUnitsMap[secondDim];
303 n1 = G4int(header[fKeys.number]);
304 n2 = G4int(header[sKeys.number]);
305 result = new BDSArray2DCoords(n1, n2,
306 header[fKeys.min] * fUnit,
307 header[fKeys.max] * fUnit,
308 header[sKeys.min] * sUnit,
309 header[sKeys.max] * sUnit,
310 firstDim,
311 secondDim);
312 break;
313 }
314 case 3:
315 {
316 BDSDimensionType firstDim = BDS::DetermineDimensionType(columnNames[0]);
317 BDSDimensionType secondDim = BDS::DetermineDimensionType(columnNames[1]);
318 BDSDimensionType thirdDim = BDS::DetermineDimensionType(columnNames[2]);
319 auto fKeys = dimKeyMap[firstDim];
320 double fUnit = dimUnitsMap[firstDim];
321 auto sKeys = dimKeyMap[secondDim];
322 double sUnit = dimUnitsMap[secondDim];
323 auto tKeys = dimKeyMap[thirdDim];
324 double tUnit = dimUnitsMap[thirdDim];
325 n1 = G4int(header[fKeys.number]);
326 n2 = G4int(header[sKeys.number]);
327 n3 = G4int(header[tKeys.number]);
328 result = new BDSArray3DCoords(n1, n2, n3,
329 header[fKeys.min] * fUnit,
330 header[fKeys.max] * fUnit,
331 header[sKeys.min] * sUnit,
332 header[sKeys.max] * sUnit,
333 header[tKeys.min] * tUnit,
334 header[tKeys.max] * tUnit,
335 firstDim,
336 secondDim,
337 thirdDim);
338 break;
339 }
340 case 4:
341 {
342 n1 = G4int(header["nx"]);
343 n2 = G4int(header["ny"]);
344 n3 = G4int(header["nz"]);
345 n4 = G4int(header["nt"]);
346 result = new BDSArray4DCoords(n1, n2, n3, n4,
347 header["xmin"] * CLHEP::cm, header["xmax"] * CLHEP::cm,
348 header["ymin"] * CLHEP::cm, header["ymax"] * CLHEP::cm,
349 header["zmin"] * CLHEP::cm, header["zmax"] * CLHEP::cm,
350 header["tmin"] * CLHEP::s, header["tmax"] * CLHEP::s);
351 break;
352 }
353 default:
354 {break;}
355 }
356 intoData = true;
357 continue;
358 }
359 }
360
361 // now only read data - two loops, one for each way of looping
362 if (loopOrder == "tzyx")
363 {
364 for (G4int i = 0; i < n1; i++)
365 {
366 for (G4int j = 0; j < n2; j++)
367 {
368 for (G4int k = 0; k < n3; k++)
369 {
370 for (G4int l = 0; l < n4; l++)
371 {
372 if (!std::getline(file, line))
373 {Terminate(functionName + "unexpected end to file on line number " + std::to_string(currentLineNumber));}
374 currentLineNumber++;
375 if (std::all_of(line.begin(), line.end(), isspace))
376 {continue;}
377 ProcessData(line, xIndex, yIndex, zIndex, currentLineNumber); // changes member fv
378 (*result)(i, j, k, l) = fv;
379 float mag = fv.mag();
380 maximumFieldValue = std::max(maximumFieldValue, mag);
381 minimumFieldValue = std::min(minimumFieldValue, mag);
382 }
383 }
384 }
385 }
386 }
387 else
388 {// xyzt
389 for (G4int l = 0; l < n4; l++)
390 {
391 for (G4int k = 0; k < n3; k++)
392 {
393 for (G4int j = 0; j < n2; j++)
394 {
395 for (G4int i = 0; i < n1; i++)
396 {
397 if (!std::getline(file, line))
398 {Terminate(functionName + "unexpected end to file on line number " + std::to_string(currentLineNumber));}
399 currentLineNumber++;
400 if (std::all_of(line.begin(), line.end(), isspace))
401 {continue;}
402 ProcessData(line, xIndex, yIndex, zIndex, currentLineNumber); // changes member fv
403 (*result)(i, j, k, l) = fv;
404 float mag = fv.mag();
405 maximumFieldValue = std::max(maximumFieldValue, mag);
406 minimumFieldValue = std::min(minimumFieldValue, mag);
407 }
408 }
409 }
410 }
411 }
412
413 file.close();
414 G4cout << functionName << "Loaded " << currentLineNumber << " lines from file" << G4endl;
415 G4cout << functionName << "(Min | Max) field magnitudes in loaded file (before scaling): (" << minimumFieldValue << " | " << maximumFieldValue << ")" << G4endl;
416}
417
418template <class T>
419void BDSFieldLoaderBDSIM<T>::ProcessData(const std::string& line,
420 const unsigned long xIndex,
421 const unsigned long yIndex,
422 const unsigned long zIndex,
423 unsigned long long currentLineNumber)
424{
425 std::istringstream liness(line);
426 G4float value = 0;
427 std::fill(lineData.begin(), lineData.end(), 0); // reset data - technically unnecessary
428
429 // read all columns - indices shifted +1 for default value offset
430 for (unsigned long i = 1; i < nColumns+1; ++i)
431 {
432 liness >> value;
433 if (i < indexOfFirstFieldValue)// coordinates before this index
434 {value *= CLHEP::cm;}
435 if (!std::isfinite(value))
436 {throw BDSException("BDSFieldLoaderBDSIM>", "a not-finite value was found on line " + std::to_string(currentLineNumber));}
437 lineData[i] = value;
438 }
439
440 // Construct field value
441 fv = BDSFieldValue(lineData[xIndex],
442 lineData[yIndex],
443 lineData[zIndex]);
444}
445
446
448
449#ifdef USE_GZSTREAM
450template class BDSFieldLoaderBDSIM<igzstream>;
451#endif
1D array with spatial mapping derived from BDSArray4DCoords.
2D array with spatial mapping derived from BDSArray4DCoords.
3D array with spatial mapping derived from BDSArray4DCoords.
Overlay of 4D array that provides uniform only spatial coordinate mapping.
General exception with possible name of object and message.
Loader for BDSIM format fields.
BDSArray3DCoords * Load3D(const G4String &fileName)
Load a 3D array.
void ProcessData(const std::string &line, const unsigned long xIndex, const unsigned long yIndex, const unsigned long zIndex, unsigned long long currentLineNumber)
void CleanUp()
Ensure any member variables are reset between usages.
BDSArray4DCoords * Load4D(const G4String &fileName)
Load a 4D array.
BDSArray1DCoords * Load1D(const G4String &fileName)
Load a 1D array.
void Terminate(const G4String &message="")
Close file and exit program in case of an error.
void Load(const G4String &fileName, const unsigned int nDim)
General loader for any number of dimensions.
BDSArray2DCoords * Load2D(const G4String &fileName)
Load a 2D array.
BDSDimensionType DetermineDimensionType(G4String dimensionType)
Determine the output format to be used from the input string.
G4String LowerCase(const G4String &str)
Utility function to simplify lots of syntax changes for pedantic g4 changes.