utilities
Loading...
Searching...
No Matches
gemcUtilities.cc
Go to the documentation of this file.
1#include "gemcUtilities.h"
2#include "gemcConventions.h"
3
4// Implementation notes:
5// - Doxygen documentation is kept authoritative in gemcUtilities.h.
6// - This file only provides brief, non-Doxygen comments to clarify intent and flow.
7
8// geant4 headers
9#include "G4Threading.hh"
10#include "G4UImanager.hh"
11#include "G4UnitsTable.hh"
12
13// gemc
14#include "glogger.h"
15#include "gtouchable.h"
16#include "gutilities.h"
17#include "g4SceneProperties.h"
18#include "g4display_options.h"
19
20#include <algorithm>
21#include <cctype>
22#include <cstdlib>
23#include <filesystem>
24#include <fstream>
25#include <iostream>
26#include <numbers>
27#include <sstream>
28
29namespace {
30bool scalarOptionIsTrue(const std::shared_ptr<GOptions>& gopts, const std::string& name) {
31 if (gopts == nullptr) { return false; }
32
33 std::string value = gopts->getOptionalScalarString(name).value_or("");
34 std::transform(value.begin(), value.end(), value.begin(),
35 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
36
37 if (value == "true" || value == "yes" || value == "on" || value == "1") {
38 return true;
39 }
40 if (value == "false" || value == "no" || value == "off" || value == "0" || value == "null") {
41 return false;
42 }
43
44 std::cerr << guts::FATALERRORL << "The option " << name
45 << " accepts only true/false/yes/no/on/off/1/0." << std::endl;
47}
48
49std::string rootExtentForFieldCommand(const std::shared_ptr<GOptions>& gopts) {
50 if (gopts == nullptr) { return ""; }
51
52 std::string rootDefinition = gopts->getRequiredScalarString("root");
53 for (auto& c : rootDefinition) {
54 if (c == ',') { c = ' '; }
55 }
56
57 const auto tokens = gutilities::getStringVectorFromString(rootDefinition);
58 if (tokens.size() < 5 || tokens[0] != "G4Box") { return ""; }
59
60 const double dx = gutilities::getG4Number(tokens[1]);
61 const double dy = gutilities::getG4Number(tokens[2]);
62 const double dz = gutilities::getG4Number(tokens[3]);
63 if (dx <= 0 || dy <= 0 || dz <= 0) { return ""; }
64
65 std::ostringstream command;
66 command << "/vis/set/extentForField "
67 << -dx << " " << dx << " "
68 << -dy << " " << dy << " "
69 << -dz << " " << dz << " mm";
70 return command.str();
71}
72}
73
74namespace gemc {
75 // return the number of cores from options.
76 // if 0 is given, returns max number of available cores
77 int get_nthreads(const std::shared_ptr<GOptions>& gopts, const std::shared_ptr<GLogger>& log) {
78 int useThreads = gopts->getRequiredScalarInt("nthreads");
79
80 // Geant4 provides a platform-specific core count helper.
81 int ncores = G4Threading::G4GetNumberOfCores();
82
83 // Clamp user request:
84 // - 0 means "use all available cores"
85 // - values larger than available cores are clamped
86 if (useThreads == 0 || useThreads > ncores) useThreads = ncores;
87
88 log->info(0, "Using %1 threads out of %2 available cores.", useThreads, ncores);
89
90 return useThreads;
91 }
92
93 std::vector<std::string> verbosity_commands([[maybe_unused]] const std::shared_ptr<GOptions>& gopts,
94 [[maybe_unused]] const std::shared_ptr<GLogger>& log) {
95 std::vector<std::string> cmds;
96
97 // --- Always-quiet commands ---
98 // These commands reduce Geant4 output noise for typical production runs.
99 cmds.emplace_back("/control/verbose 0");
100 cmds.emplace_back("/hit/verbose 0");
101
102 cmds.emplace_back("/process/verbose 0");
103 cmds.emplace_back("/process/setVerbose 0 all");
104 cmds.emplace_back("/process/had/verbose 0");
105 cmds.emplace_back("/process/had/deex/verbose 0");
106 cmds.emplace_back("/process/had/cascade 0");
107 cmds.emplace_back("/process/em/verbose 0");
108 cmds.emplace_back("/process/eLoss/verbose 0");
109
110 cmds.emplace_back("/tracking/verbose 0");
111 cmds.emplace_back("/geometry/navigator/verbose 0");
112
113 cmds.emplace_back("/event/verbose 0");
114 cmds.emplace_back("/event/stack/verbose 0");
115
116 cmds.emplace_back("/cuts/verbose 0");
117
118 cmds.emplace_back("/run/particle/verbose 0");
119 cmds.emplace_back("/run/verbose 0");
120
121 cmds.emplace_back("/material/verbose 0");
122
123 cmds.emplace_back("/vis/verbose 0");
124 cmds.emplace_back("/particle/verbose 0");
125
126 // cmds.emplace_back("/control/cout/ignoreInitializationCout 1");
127 // cmds.emplace_back("/control/cout/useBuffer 1"); // keep MT output tidy?
128
129 return cmds;
130 }
131
132 std::vector<std::string> initial_commands(const std::shared_ptr<GOptions>& gopts,
133 [[maybe_unused]] const std::shared_ptr<GLogger>& log,
134 bool configure_visualization) {
135 // check_overlaps is typically provided by the Geant4 system options set.
136 auto check_overlaps = gopts->getRequiredScalarInt("check_overlaps"); // from g4system options
137 auto gui = gopts->getSwitch("gui");
138 auto g4view = g4display::getG4View(gopts);
139
140 std::vector<std::string> cmds;
141
142 // Batch mode: optionally schedule geometry overlap checks before initialization.
143 // Geant4 overlap checks use the current "/geometry/test/..." configuration.
144 if (check_overlaps == 2) {
145 log->info(0, "Running /geometry/test/run with 50 points.");
146 cmds.emplace_back("/geometry/test/resolution 50");
147 cmds.emplace_back("/geometry/test/run");
148 }
149 else if (check_overlaps >= 100) {
150 log->info(0, "Running /geometry/test/run with %1 points.", check_overlaps);
151 cmds.emplace_back("/geometry/test/resolution " + std::to_string(check_overlaps));
152 cmds.emplace_back("/geometry/test/run");
153 }
154
155 // In GUI mode without startup geometry, defer initialization until the setup
156 // tab has selected and loaded a real system. Initializing the synthetic ROOT
157 // world here leaves stale world/vis state behind after setup-tab reloads.
158 if (gui && !configure_visualization) { return cmds; }
159
160 // A re-initialize is required when:
161 // - physics changes
162 // - geometry changes
163 cmds.emplace_back("/run/initialize");
164
165 if (!configure_visualization) return cmds;
166
167 // Create the viewer and its attached scene only after /run/initialize has built the world.
168 G4SceneProperties g4SceneProperties(gopts);
169 for (const auto& command : g4SceneProperties.scene_commands(gopts)) {
170 cmds.emplace_back(command);
171 }
172
173 // If there is no GUI, or no need for batch screenshot, initialization commands are enough.
174 if (!gui && g4view.driver != "TOOLSSG_OFFSCREEN") return cmds;
175
176 // do not draw volumes in batch screenshot
177 if (g4view.driver != "TOOLSSG_OFFSCREEN") {
178 auto g4camera = g4display::getG4Camera(gopts);
179 auto g4light = g4display::getG4Light(gopts);
180 const double toDegrees = 180.0 / std::numbers::pi;
181 double thetaValue = gutilities::getG4Number(g4camera.theta) * toDegrees;
182 double phiValue = gutilities::getG4Number(g4camera.phi) * toDegrees;
183 double lightThetaValue = gutilities::getG4Number(g4light.theta) * toDegrees;
184 double lightPhiValue = gutilities::getG4Number(g4light.phi) * toDegrees;
185 if (lightThetaValue == 0.0 && lightPhiValue == 0.0) {
186 lightThetaValue = thetaValue;
187 lightPhiValue = phiValue;
188 }
189
190 // Disable auto refresh and quieten vis messages whilst scene and trajectories are established.
191 cmds.emplace_back("/vis/viewer/set/autoRefresh false");
192 cmds.emplace_back("/vis/viewer/set/viewpointThetaPhi " + std::to_string(thetaValue) + " " + std::to_string(phiValue));
193 cmds.emplace_back("/vis/viewer/set/lightsThetaPhi " + std::to_string(lightThetaValue) + " " + std::to_string(lightPhiValue));
194 }
195
196 // GUI / batch screenshot mode: set up a minimal visualization scene with trajectories and hits.
197 cmds.emplace_back("/vis/scene/add/trajectories smooth");
198 cmds.emplace_back("/vis/modeling/trajectories/create/drawByCharge");
199 cmds.emplace_back("/vis/modeling/trajectories/drawByCharge-0/default/setDrawStepPts true");
200 cmds.emplace_back("/vis/modeling/trajectories/drawByCharge-0/default/setStepPtsSize 2");
201 // Draw optical photons in cyan so they are distinct from other neutral particles.
202 cmds.emplace_back("/vis/modeling/trajectories/create/drawByParticleID");
203 cmds.emplace_back("/vis/modeling/trajectories/drawByParticleID-0/set opticalphoton cyan");
204
205 cmds.emplace_back("/vis/scene/add/hits");
206 cmds.emplace_back("/vis/scene/endOfEventAction accumulate 10000");
207 cmds.push_back("/vis/viewer/set/background " + g4view.background);
208 cmds.push_back("/vis/viewer/set/numberOfCloudPoints " + std::to_string(g4view.cloudPoints));
209 const auto decorations = g4display::getG4Decorations(gopts);
210 for (const auto& command : g4SceneProperties.addSceneDecorations(gopts)) { cmds.emplace_back(command); }
211 for (const auto& command : g4SceneProperties.addSceneTexts(gopts)) { cmds.emplace_back(command); }
212 if (decorations.eventID) { cmds.emplace_back("/vis/scene/add/eventID"); }
213
214 if (scalarOptionIsTrue(gopts, "show_auxiliary_edges")) {
215 cmds.emplace_back("/vis/viewer/set/auxiliaryEdge 1");
216 cmds.emplace_back("/vis/viewer/set/hiddenEdge 1");
217 }
218
219 const int fieldLinePoints = gopts->getRequiredScalarInt("show_field_lines");
220 if (fieldLinePoints > 0) {
221 if (const auto extent = rootExtentForFieldCommand(gopts); !extent.empty()) {
222 cmds.emplace_back(extent);
223 }
224 cmds.emplace_back("/vis/scene/add/magneticField " + std::to_string(fieldLinePoints));
225 }
226
227 // do not draw volumes in batch screenshot
228 if (g4view.driver != "TOOLSSG_OFFSCREEN") {
229 // Re-enable refresh and flush once configuration is complete.
230 cmds.emplace_back("/vis/viewer/set/autoRefresh true");
231 cmds.emplace_back("/vis/viewer/flush");
232 }
233
234 // cmds.emplace_back("/tracking/verbose 2");
235 return cmds;
236 }
237
238 // initialize G4MTRunManager
239 void run_manager_commands([[maybe_unused]] const std::shared_ptr<GOptions>& gopts,
240 const std::shared_ptr<GLogger>& log, const std::vector<std::string>& commands) {
241 auto* g4uim = G4UImanager::GetUIpointer();
242
243 // Apply commands sequentially so the UI manager sees the same order as a macro file.
244 for (const auto& cmd : commands) {
245 log->info(2, "Executing UIManager command: %1", cmd);
246 g4uim->ApplyCommand(cmd);
247 }
248 }
249
250 void execute_macro(const std::string& filename, const std::shared_ptr<GLogger>& log) {
251 std::error_code error;
252 if (!std::filesystem::is_regular_file(filename, error) || !std::ifstream(filename)) {
253 log->error(EXIT_FAILURE, "Cannot read Geant4 macro file: %1", filename);
254 }
255
256 log->info(0, "Executing Geant4 macro: %1", filename);
257 auto* uim = G4UImanager::GetUIpointer();
258 // Pass the filename directly so paths containing spaces are not parsed as command arguments.
259 uim->ExecuteMacroFile(filename.c_str());
260 if (const auto status = uim->GetLastReturnCode(); status != 0) {
261 log->error(EXIT_FAILURE, "Geant4 macro failed: %1 (command status %2)", filename, status);
262 }
263 }
264
266 new G4UnitDefinition("milligray", "milliGy", "Dose", gemc_units::milligray);
267 new G4UnitDefinition("microgray", "microGy", "Dose", gemc_units::microgray);
268 new G4UnitDefinition("nanogray", "nanoGy", "Dose", gemc_units::nanogray);
269 new G4UnitDefinition("picogray", "picoGy", "Dose", gemc_units::picogray);
270 }
271
272
273#include <unistd.h> // needed for get_pid
274
275 void start_random_engine(const std::shared_ptr<GOptions>& gopts, const std::shared_ptr<GLogger>& log) {
276 auto randomEngineName = gopts->getRequiredScalarString("randomEngine");
277 auto configuredSeed = gopts->getOptionalScalarInt("seed");
278 G4int seed{};
279
280 // If the user did not set a seed, derive one using several fast-changing sources.
281 // This helps reduce accidental seed reuse across runs.
282 if (!configuredSeed) {
283 auto timed = time(NULL);
284 auto clockd = clock();
285 auto getpidi = getpid();
286 seed = (G4int)( timed - clockd - getpidi );
287 log->info(1, "Using random seed %1", seed);
288 } else {
289 seed = *configuredSeed;
290 log->info(1, "User defined seed %1", seed);
291 }
292
293
294 // The names come from the CLHEP library, can be found with
295 // grep ": public HepRandomEngine" *.h $CLHEP_BASE_DIR/include/CLHEP/Random/* | awk -Fclass '{print $2}' | awk -F: '{print $1}'
296 //
297 // Select the engine implementation based on the configured string.
298 if (randomEngineName == "DRand48Engine")
299 G4Random::setTheEngine(new CLHEP::DRand48Engine(seed));
300 else if (randomEngineName == "DualRand")
301 G4Random::setTheEngine(new CLHEP::DualRand(seed));
302 else if (randomEngineName == "Hurd160Engine")
303 G4Random::setTheEngine(new CLHEP::Hurd160Engine(seed));
304 else if (randomEngineName == "HepJamesRandom")
305 G4Random::setTheEngine(new CLHEP::HepJamesRandom(seed));
306 else if (randomEngineName == "MTwistEngine")
307 G4Random::setTheEngine(new CLHEP::MTwistEngine(seed));
308 else if (randomEngineName == "MixMaxRng")
309 G4Random::setTheEngine(new CLHEP::MixMaxRng(seed));
310 else if (randomEngineName == "RandEngine")
311 G4Random::setTheEngine(new CLHEP::RandEngine(seed));
312 else if (randomEngineName == "RanecuEngine")
313 G4Random::setTheEngine(new CLHEP::RanecuEngine(seed));
314 else if (randomEngineName == "Ranlux64Engine")
315 G4Random::setTheEngine(new CLHEP::Ranlux64Engine(seed));
316 else if (randomEngineName == "RanluxEngine")
317 G4Random::setTheEngine(new CLHEP::RanluxEngine(seed));
318 else if (randomEngineName == "RanshiEngine")
319 G4Random::setTheEngine(new CLHEP::RanshiEngine(seed));
320 else if (randomEngineName == "Hurd288Engine")
321 G4Random::setTheEngine(new CLHEP::Hurd288Engine(seed));
322 else if (randomEngineName == "TripleRand")
323 G4Random::setTheEngine(new CLHEP::TripleRand(seed));
324 else {
326 "Random engine >%1< not found. Exiting.", randomEngineName);
327 }
328
329 // Apply the seed after selecting the engine so the engine instance is active.
330 log->info(0, "Starting random engine %1 with seed %2", randomEngineName, seed);
331 G4Random::setTheSeed(seed);
332 }
333}
std::vector< std::string > addSceneTexts(const std::shared_ptr< GOptions > &gopts)
std::vector< std::string > addSceneDecorations(const std::shared_ptr< GOptions > &gopts)
std::vector< std::string > scene_commands(const std::shared_ptr< GOptions > &gopts)
Conventional constants used by GEMC utility helpers.
Utility helpers for runtime setup, command preparation, and random-engine initialization.
constexpr int EC__RANDOMENGINENOTFOUND
Error code used when the configured random engine name is not recognized.
void define_new_gemc_units()
std::vector< std::string > initial_commands(const std::shared_ptr< GOptions > &gopts, const std::shared_ptr< GLogger > &log, bool configure_visualization)
Build a list of Geant4 UI commands needed at startup.
void start_random_engine(const std::shared_ptr< GOptions > &gopts, const std::shared_ptr< GLogger > &log)
Select and start the random engine, then seed it.
std::vector< std::string > verbosity_commands(const std::shared_ptr< GOptions > &gopts, const std::shared_ptr< GLogger > &log)
Build a list of Geant4 UI commands that reduce verbosity across subsystems.
void run_manager_commands(const std::shared_ptr< GOptions > &gopts, const std::shared_ptr< GLogger > &log, const std::vector< std::string > &commands)
Execute a sequence of Geant4 UI commands through the UI manager.
void execute_macro(const std::string &filename, const std::shared_ptr< GLogger > &log)
Execute a Geant4 macro file, terminating with an error if it cannot complete.
int get_nthreads(const std::shared_ptr< GOptions > &gopts, const std::shared_ptr< GLogger > &log)
Determine the number of worker threads to use for the run.
G4Light getG4Light(const std::shared_ptr< GOptions > &gopts)
G4View getG4View(const std::shared_ptr< GOptions > &gopts)
G4Camera getG4Camera(const std::shared_ptr< GOptions > &gopts)
G4Decorations getG4Decorations(const std::shared_ptr< GOptions > &gopts)
constexpr G4double milligray
constexpr G4double nanogray
constexpr G4double microgray
constexpr G4double picogray
GEMC runtime setup, command, and translation utilities.
constexpr int EC__NOOPTIONFOUND
double getG4Number(const string &v, bool warnIfNotUnit=false)
vector< std::string > getStringVectorFromString(const std::string &input)
constexpr char FATALERRORL[]