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