guts
Loading...
Searching...
No Matches
gutilities.cc
Go to the documentation of this file.
1// gutilities
2#include "gutilities.h"
3#include "gutsConventions.h"
4
5// Numbers / strings with units / io interface to CLHEP units
6#include "CLHEP/Units/PhysicalConstants.h"
7
8// c++
9// algorithm for 'transform'
10#include <algorithm>
11#include <sstream>
12#include <unordered_map>
13#include <iostream>
14#include <fstream>
15#include <vector>
16#include <charconv>
17#include <filesystem>
18
19namespace gutilities {
20
21string removeLeadingAndTrailingSpacesFromString(const std::string& input) {
22 size_t startPos = input.find_first_not_of(" \t"); // Find the first non-whitespace character
23 size_t endPos = input.find_last_not_of(" \t"); // Find the last non-whitespace character
24
25 // If all spaces or empty, return an empty string
26 if (startPos == std::string::npos || endPos == std::string::npos) { return ""; }
27
28 // Return the substring between startPos and endPos
29 return input.substr(startPos, endPos - startPos + 1);
30}
31
32// Fast trim (use yours if you prefer)
33std::string_view removeLeadingAndTrailingSpacesFromString(std::string_view s) {
34 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) s.remove_prefix(1);
35 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) s.remove_suffix(1);
36 return s;
37}
38
39string removeAllSpacesFromString(const std::string& str) {
40 string result = str;
41 result.erase(std::remove(result.begin(), result.end(), ' '), result.end());
42 return result;
43}
44
45string getFileFromPath(const std::string& path) {
46 std::size_t lastSlashPos = path.find_last_of('/');
47 if (lastSlashPos == std::string::npos) {
48 // No slashes found, return the entire path
49 return path;
50 }
51 return path.substr(lastSlashPos + 1);
52}
53
54string getDirFromPath(const std::string& path) {
55 auto lastSlash = path.find_last_of('/');
56 if (lastSlash == std::string::npos) return ".";
57 return path.substr(0, lastSlash);
58}
59
60namespace fs = std::filesystem;
61
62
63vector<std::string> getStringVectorFromString(const std::string& input) {
64 std::vector<std::string> pvalues;
65 std::stringstream plist(input);
66 string tmp;
67 while (plist >> tmp) {
68 string trimmed = removeLeadingAndTrailingSpacesFromString(tmp);
69 if (!trimmed.empty()) { pvalues.push_back(trimmed); }
70 }
71 return pvalues;
72}
73
74// Replace all occurences of specific chars in a string with a string
75string replaceCharInStringWithChars(const std::string& input, const std::string& toReplace,
76 const std::string& replacement) {
77 string output;
78 for (const char& ch : input) {
79 if (toReplace.find(ch) != std::string::npos) { output.append(replacement); }
80 else { output.push_back(ch); }
81 }
82 return output;
83}
84
85string replaceAllStringsWithString(const string& source, const string& from, const string& to) {
86 if (from.empty()) return source; // Avoid infinite loop
87
88 string newString;
89 size_t lastPos = 0;
90 size_t findPos = source.find(from, lastPos);
91
92 while (findPos != string::npos) {
93 // Append part before the match and the replacement string
94 newString.append(source, lastPos, findPos - lastPos);
95 newString += to;
96 lastPos = findPos + from.length();
97 findPos = source.find(from, lastPos);
98 }
99
100 // Append the remaining part of the string after the last occurrence
101 newString += source.substr(lastPos);
102
103 return newString;
104}
105
106
107string fillDigits(const string& word, const string& c, int ndigits) {
108 if (c.empty() || ndigits <= static_cast<int>(word.size())) return word; // Return original if no padding needed
109
110 string filled;
111
112 int toFill = ndigits - static_cast<int>(word.size());
113 filled.reserve(ndigits);
114
115 filled.append(toFill, c[0]); // Use the first character of the string 'c'
116 filled += word;
117
118 return filled;
119}
120
121// add near your includes:
122#include <locale.h> // strtod_l / _strtod_l
123
124// Parse a whole string_view as a double using the "C" numeric locale.
125// Returns true on full-consume success; false otherwise.
126static bool parse_double_clocale(std::string_view sv, double& out) {
127 std::string tmp(sv); // strtod_l needs a 0-terminated buffer
128#if defined(_WIN32)
129 _locale_t loc = _create_locale(LC_NUMERIC, "C");
130 char* end = nullptr;
131 out = _strtod_l(tmp.c_str(), &end, loc);
132 _free_locale(loc);
133 return end == tmp.c_str() + tmp.size();
134#else
135 locale_t loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
136 char* end = nullptr;
137 out = strtod_l(tmp.c_str(), &end, loc);
138 freelocale(loc);
139 return end == tmp.c_str() + tmp.size();
140#endif
141}
142
143
144// --- strict, locale-independent getG4Number: only accepts '*' as unit sep ---
145double getG4Number(const string& v, bool warnIfNotUnit) {
147 if (value.empty()) {
148 std::cerr << FATALERRORL << "empty numeric string.\n";
149 exit(EC__G4NUMBERERROR);
150 }
151
152 // Normalize a single decimal comma to dot when no dot is present
153 if (value.find('.') == string::npos) {
154 size_t firstComma = value.find(',');
155 if (firstComma != string::npos && value.find(',', firstComma + 1) == string::npos) {
156 value = replaceAllStringsWithString(value, ",", ".");
157 }
158 }
159
160 const size_t starCount = static_cast<size_t>(std::count(value.begin(), value.end(), '*'));
161
162 // --- Case 1: no '*' → pure number (strictly no trailing garbage) ---
163 if (value.find('*') == string::npos) {
164 double out = 0.0;
165 // normalize a single decimal comma to dot if needed
166 if (value.find('.') == string::npos) {
167 auto firstComma = value.find(',');
168 if (firstComma != string::npos && value.find(',', firstComma + 1) == string::npos)
169 value = replaceAllStringsWithString(value, ",", ".");
170 }
171 if (!parse_double_clocale(value, out)) {
172 std::cerr << FATALERRORL << "missing '*' before unit or invalid number in <" << v << ">.\n";
173 exit(EC__G4NUMBERERROR);
174 }
175 if (warnIfNotUnit && out != 0.0) {
176 std::cerr << " ! Warning: value " << v << " does not contain units." << std::endl;
177 }
178 return out;
179 }
180
181
182 // --- Case 2: must be exactly one '*' ---
183 if (starCount > 1) {
184 std::cerr << FATALERRORL << "multiple '*' separators are not allowed in <" << v << ">.\n";
185 exit(EC__G4NUMBERERROR);
186 }
187
188 // --- Exactly one '*' → split "<number>*<unit>" ---
189 const size_t pos = value.find('*');
190 string left = removeLeadingAndTrailingSpacesFromString(value.substr(0, pos));
191 string right = removeLeadingAndTrailingSpacesFromString(value.substr(pos + 1));
192 if (left.empty() || right.empty()) {
193 std::cerr << FATALERRORL << "expected '<number>*<unit>', got <" << v << ">.\n";
194 exit(EC__G4NUMBERERROR);
195 }
196
197 // normalize a single decimal comma in the numeric part
198 if (left.find('.') == string::npos) {
199 auto c = left.find(',');
200 if (c != string::npos && left.find(',', c + 1) == string::npos)
201 left = replaceAllStringsWithString(left, ",", ".");
202 }
203
204 double numeric = 0.0;
205 if (!parse_double_clocale(left, numeric)) {
206 std::cerr << FATALERRORL << "invalid numeric part before '*' in <" << v << ">.\n";
207 exit(EC__G4NUMBERERROR);
208 }
209
210 // sanitize unit and proceed with your existing unit table logic...
211 right = replaceAllStringsWithString(right, "µ", "u");
212 string unit = convertToLowercase(right);
213
214 // (keep your unitConversion map and SI prefix handling as-is)
215
216
217 // Unit table (lowercase keys)
218 static const std::unordered_map<string, double> unitConversion = {
219 // length
220 {"m", CLHEP::m}, {"cm", CLHEP::cm}, {"mm", CLHEP::mm},
221 {"um", 1E-6 * CLHEP::m}, {"fm", 1E-15 * CLHEP::m},
222 {"inch", 2.54 * CLHEP::cm}, {"inches", 2.54 * CLHEP::cm},
223 // angle
224 {"deg", CLHEP::deg}, {"degrees", CLHEP::deg}, {"arcmin", CLHEP::deg / 60.0},
225 {"rad", CLHEP::rad}, {"mrad", CLHEP::mrad},
226 // energy
227 {"ev", CLHEP::eV}, {"kev", 1e3 * CLHEP::eV}, {"mev", CLHEP::MeV}, {"gev", CLHEP::GeV},
228 // magnetic field
229 {"t", CLHEP::tesla}, {"tesla", CLHEP::tesla}, {"t/m", CLHEP::tesla / CLHEP::m},
230 {"gauss", CLHEP::gauss}, {"kilogauss", 1000.0 * CLHEP::gauss},
231 // time
232 {"s", CLHEP::s}, {"ns", CLHEP::ns}, {"ms", CLHEP::ms}, {"us", CLHEP::us},
233 // dimensionless
234 {"counts", 1.0}
235 };
236
237 // Exact unit match
238 if (auto it = unitConversion.find(unit); it != unitConversion.end()) {
239 return numeric * it->second;
240 }
241
242 // SI prefix handling: mT, uT, mm, um, etc.
243 auto si_prefix_factor = [](char p) -> double {
244 switch (p) {
245 case 'Y': return 1e24; case 'Z': return 1e21; case 'E': return 1e18;
246 case 'P': return 1e15; case 'T': return 1e12; case 'G': return 1e9;
247 case 'M': return 1e6; case 'k': return 1e3; case 'h': return 1e2;
248 case 'd': return 1e-1; case 'c': return 1e-2; case 'm': return 1e-3;
249 case 'u': return 1e-6; case 'n': return 1e-9; case 'p': return 1e-12;
250 case 'f': return 1e-15; case 'a': return 1e-18; case 'z': return 1e-21; case 'y': return 1e-24;
251 default: return 0.0;
252 }
253 };
254
255 if (unit.size() >= 2) {
256 const double pf = si_prefix_factor(unit.front());
257 if (pf != 0.0) {
258 const string base = unit.substr(1);
259 if (auto it2 = unitConversion.find(base); it2 != unitConversion.end()) {
260 return numeric * pf * it2->second;
261 }
262 }
263 }
264
265 // Unknown unit: warn & return numeric part (keep your legacy behavior)
266 std::cerr << GWARNING << ">" << right << "<: unit not recognized for string <" << v << ">" << std::endl;
267 return numeric;
268}
269
270
271double getG4Number(double input, const string& unit) {
272 string gnumber = std::to_string(input) + "*" + unit;
273 return getG4Number(gnumber, true);
274}
275
276vector<double> getG4NumbersFromStringVector(const vector<string>& vstring, bool warnIfNotUnit) {
277 vector<double> output;
278 output.reserve(vstring.size());
279
280 for (const auto& s : vstring) { output.push_back(getG4Number(s, warnIfNotUnit)); }
281
282 return output;
283}
284
285vector<double> getG4NumbersFromString(const string& vstring, bool warnIfNotUnit) {
286 return getG4NumbersFromStringVector(getStringVectorFromStringWithDelimiter(vstring, ","), warnIfNotUnit);
287}
288
289
290string parseFileAndRemoveComments(const string& filename, const string& commentChars, int verbosity) {
291 // Reading file
292 std::ifstream in(filename);
293 if (!in) {
294 std::cerr << FATALERRORL << "can't open input file " << filename << ". Check your spelling. " << std::endl;
295 exit(EC__FILENOTFOUND);
296 }
297
298 std::stringstream strStream;
299 if (verbosity > 0) { std::cout << std::endl << CIRCLEITEM << " Loading string from " << filename << std::endl; }
300 strStream << in.rdbuf(); // Read the file
301 in.close();
302
303 string parsedString = strStream.str();
304
305 // Removing all occurrences of commentChars
306 size_t nFPos;
307 while ((nFPos = parsedString.find(commentChars)) != string::npos) {
308 size_t firstNL = parsedString.rfind('\n', nFPos);
309 size_t secondNL = parsedString.find('\n', nFPos);
310 parsedString.erase(firstNL, secondNL - firstNL);
311 }
312
313 return parsedString;
314}
315
316string retrieveStringBetweenChars(const string& input, const string& firstDelimiter, const string& secondDelimiter) {
317 size_t firstpos = input.find(firstDelimiter);
318 size_t secondpos = input.find(secondDelimiter);
319
320 if (firstpos == string::npos || secondpos == string::npos) { return ""; }
321 return input.substr(firstpos + firstDelimiter.length(), secondpos - firstpos - firstDelimiter.length());
322}
323
324vector<string> getStringVectorFromStringWithDelimiter(const string& input, const string& x) {
325 vector<string> pvalues;
326 string tmp;
327
328 for (char ch : input) {
329 if (ch != x[0]) { tmp += ch; }
330 else {
331 if (!tmp.empty()) {
332 pvalues.push_back(removeLeadingAndTrailingSpacesFromString(tmp));
333 tmp.clear();
334 }
335 }
336 }
337
338 if (!tmp.empty()) { pvalues.push_back(removeLeadingAndTrailingSpacesFromString(tmp)); }
339
340 return pvalues;
341}
342
343
344// string search for a path with <name> from a possible list of absolute paths
345// returns UNINITIALIZEDSTRINGQUANTITY if not found
346// the filesystem solution does not work on linux systems.
347// TODO: periodically try this?
348//#include <filesystem>
349//
350// string searchForDirInLocations(string dirName, vector <string> possibleLocations) {
351//
352// for (auto trialLocation: possibleLocations) {
353// string possibleDir = trialLocation + "/" + dirName;
354// if (std::filesystem::exists(possibleDir)) {
355// return possibleDir;
356// }
357// }
358// return UNINITIALIZEDSTRINGQUANTITY;
359// }
360//
361// vector <string> getListOfFilesInDirectory(string dirName, vector <string> extensions) {
362//
363// vector <string> fileList;
364//
365// for (const auto &entry: std::filesystem::directory_iterator(dirName)) {
366// for (auto &extension: extensions) {
367// if (entry.path().extension() == extension) {
368// fileList.push_back(entry.path().filename());
369// }
370// }
371// }
372//
373// return fileList;
374// }
375// end of TODO
376
377#include <dirent.h>
378#include <sys/stat.h>
379
380bool directoryExists(const std::string& path) {
381 struct stat info{};
382 if (stat(path.c_str(), &info) != 0) {
383 return false; // Path does not exist
384 }
385 return (info.st_mode & S_IFDIR) != 0; // Check if it's a directory
386}
387
388string searchForDirInLocations(const string& dirName, const vector<string>& possibleLocations) {
389 for (const auto& trialLocation : possibleLocations) {
390 string possibleDir = trialLocation + "/" + dirName;
391 if (directoryExists(possibleDir)) { return possibleDir; }
392 }
393 return "UNINITIALIZEDSTRINGQUANTITY";
394}
395
396
397bool hasExtension(const std::string& filename, const std::vector<std::string>& extensions) {
398 for (const auto& ext : extensions) {
399 if (filename.size() >= ext.size() &&
400 filename.compare(filename.size() - ext.size(), ext.size(), ext) == 0) { return true; }
401 }
402 return false;
403}
404
405vector<string> getListOfFilesInDirectory(const string& dirName, const vector<string>& extensions) {
406 vector<string> fileList;
407
408 DIR* dir = opendir(dirName.c_str());
409 if (dir) {
410 struct dirent* entry;
411 while ((entry = readdir(dir)) != nullptr) {
412 struct stat info{};
413 string filepath = dirName + "/" + entry->d_name;
414 if (stat(filepath.c_str(), &info) == 0 && S_ISREG(info.st_mode)) {
415 string filename = entry->d_name;
416 if (hasExtension(filename, extensions)) { fileList.push_back(filename); }
417 }
418 }
419 closedir(dir);
420 }
421
422 return fileList;
423}
424
425string convertToLowercase(const string& str) {
426 string lower = str;
427 transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
428 return lower;
429}
430
431
432template <class KEY, class VALUE>
433vector<KEY> getKeys(const map<KEY, VALUE>& map) {
434 vector<KEY> keys;
435 keys.reserve(map.size()); // Reserve space for efficiency
436
437 for (const auto& it : map) { keys.push_back(it.first); }
438
439 return keys;
440}
441
442randomModel stringToRandomModel(const std::string& str) {
443 static const std::unordered_map<std::string, randomModel> strToEnum = {
444 {"uniform", uniform},
445 {"gaussian", gaussian},
446 {"cosine", cosine},
447 {"sphere", sphere}
448 };
449
450 auto it = strToEnum.find(str);
451 if (it != strToEnum.end()) { return it->second; }
452 else { throw std::invalid_argument("Invalid string for randomModel: " + str); }
453}
454
455
456G4Colour makeColour(std::string_view code) {
457 if (code.empty()) throw std::invalid_argument("empty colour string");
458 if (code.front() == '#') code.remove_prefix(1);
459 if (code.size() != 6 && code.size() != 7)
460 throw std::invalid_argument("colour must have 6 or 7 hex digits");
461
462 auto hexNibble = [](char c) -> unsigned {
463 if ('0' <= c && c <= '9') return c - '0';
464 c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
465 if ('A' <= c && c <= 'F') return c - 'A' + 10;
466 throw std::invalid_argument("invalid hex digit");
467 };
468
469 // ---- parse RRGGBB ----
470 unsigned rgb = 0;
471 for (int i = 0; i < 6; ++i)
472 rgb = (rgb << 4) | hexNibble(code[i]);
473
474 auto byteToDouble = [](unsigned byte) { return byte / 255.0; };
475 double r = byteToDouble((rgb >> 16) & 0xFF);
476 double g = byteToDouble((rgb >> 8) & 0xFF);
477 double b = byteToDouble(rgb & 0xFF);
478
479 // ---- optional transparency nibble ----
480 double a = 1.0;
481 if (code.size() == 7)
482 a = hexNibble(code[6]) / 15.0;
483
484 return {r, g, b, a}; // G4Colour
485}
486
487std::optional<std::string> searchForFileInLocations(
488 const std::vector<std::string>& locations,
489 std::string_view filename) {
490 namespace fs = std::filesystem;
491
492 for (const auto& loc : locations) {
493 if (loc.empty()) continue;
494
495 fs::path p(loc);
496 fs::path candidate = (!filename.empty() && fs::is_directory(p))
497 ? (p / filename)
498 : p;
499
500 std::error_code ec;
501 const bool ok = fs::exists(candidate, ec) && fs::is_regular_file(candidate, ec);
502 if (ok) return candidate.string();
503 }
504 return std::nullopt;
505}
506
507bool is_unset(std::string_view s) {
509 if (s.empty()) return true;
510 // match your sentinel and YAML nully spellings
511 auto eq = [](std::string_view a, std::string_view b){
512 if (a.size()!=b.size()) return false;
513 for (size_t i=0;i<a.size();++i)
514 if (std::tolower(static_cast<unsigned char>(a[i])) != std::tolower(static_cast<unsigned char>(b[i])))
515 return false;
516 return true;
517 };
518 return eq(s, UNINITIALIZEDSTRINGQUANTITY) || eq(s, "null") || eq(s, "~");
519}
520
521
522}
#define GWARNING
Warning label.
#define EC__FILENOTFOUND
File not found error code.
#define CIRCLEITEM
Symbol for circle item.
#define EC__G4NUMBERERROR
G4 number error code.
#define UNINITIALIZEDSTRINGQUANTITY
Represents an uninitialized string quantity.
#define FATALERRORL
Fatal error label.
string replaceAllStringsWithString(const string &source, const string &from, const string &to)
Replaces all occurrences of a substring with another string.
Definition gutilities.cc:85
double getG4Number(const string &v, bool warnIfNotUnit)
Converts a string representation of a number with optional units to a double.
bool hasExtension(const std::string &filename, const std::vector< std::string > &extensions)
Checks if a filename has one of the specified extensions.
vector< double > getG4NumbersFromString(const string &vstring, bool warnIfNotUnit)
Converts a comma-separated string of numbers with units to a vector of doubles.
string removeAllSpacesFromString(const std::string &str)
Removes all spaces from a string.
Definition gutilities.cc:39
vector< string > getStringVectorFromStringWithDelimiter(const string &input, const string &x)
Splits a string into a vector of substrings using a specified delimiter.
randomModel stringToRandomModel(const std::string &str)
Converts a string to a corresponding randomModel enum value.
vector< string > getListOfFilesInDirectory(const string &dirName, const vector< string > &extensions)
Retrieves a list of files with specific extensions from a directory.
vector< double > getG4NumbersFromStringVector(const vector< string > &vstring, bool warnIfNotUnit)
Converts a vector of strings representing numbers with units to a vector of doubles.
string retrieveStringBetweenChars(const string &input, const string &firstDelimiter, const string &secondDelimiter)
Retrieves a substring between two specified delimiters in a string.
string replaceCharInStringWithChars(const std::string &input, const std::string &toReplace, const std::string &replacement)
Replaces all occurrences of specified characters in a string with another string.
Definition gutilities.cc:75
string fillDigits(const string &word, const string &c, int ndigits)
Pads a string with a specified character until it reaches a desired length.
bool is_unset(std::string_view s)
randomModel
Enumeration of random models.
Definition gutilities.h:271
@ gaussian
Gaussian distribution.
Definition gutilities.h:273
@ uniform
Uniform distribution.
Definition gutilities.h:272
@ sphere
Sphere distribution.
Definition gutilities.h:275
@ cosine
Cosine distribution.
Definition gutilities.h:274
string getDirFromPath(const std::string &path)
Extracts the directory path from a given file path.
Definition gutilities.cc:54
string convertToLowercase(const string &str)
Converts a string to lowercase.
bool directoryExists(const std::string &path)
Checks if a directory exists at the given path.
G4Colour makeColour(std::string_view code)
Convert a hex colour string to G4Colour.
vector< KEY > getKeys(const map< KEY, VALUE > &map)
Retrieves all keys from a map.
string removeLeadingAndTrailingSpacesFromString(const std::string &input)
Removes leading and trailing spaces and tabs from a string.
Definition gutilities.cc:21
std::optional< std::string > searchForFileInLocations(const std::vector< std::string > &locations, std::string_view filename)
string searchForDirInLocations(const string &dirName, const vector< string > &possibleLocations)
Searches for a directory within a list of possible locations.
string parseFileAndRemoveComments(const string &filename, const string &commentChars, int verbosity)
Parses a file and removes all lines containing specified comment characters.
string getFileFromPath(const std::string &path)
Extracts the filename from a given file path.
Definition gutilities.cc:45
vector< std::string > getStringVectorFromString(const std::string &input)
Splits a string into a vector of strings using spaces as delimiters.
Definition gutilities.cc:63