eventDispenser
Loading...
Searching...
No Matches
eventDispenser.cc
Go to the documentation of this file.
1// Implements EventDispenser: run-weight parsing, event distribution, and per-run dispatch through Geant4.
2//
3// Doxygen documentation for the public API is maintained in eventDispenser.h.
4// This implementation file keeps only short, non-Doxygen summaries and inline clarifying comments.
5
8#include "eventDispenser.h"
10
11// c++
12#include <fstream>
13#include <random>
14#include <utility>
15
16// geant4
17#include "G4GeometryManager.hh"
18#include "G4UImanager.hh"
19#include "G4RunManager.hh"
20
21using namespace std;
22
23namespace {
24void closeOpenGeometryBeforeBeamOn(const std::shared_ptr<GLogger>& log) {
25 auto* geometryManager = G4GeometryManager::GetInstanceIfExist();
26 if (!geometryManager || geometryManager->IsGeometryClosed()) { return; }
27
28 log->info(1, "Geometry is open before BeamOn; closing it before event processing.");
29 geometryManager->CloseGeometry();
30}
31}
32
33// Constructor summary:
34// - Reads configuration (number of events, run number, optional run-weight file).
35// - Builds runWeights/runEvents/listOfRuns when weights are provided.
36// - Otherwise, falls back to single-run mode.
37EventDispenser::EventDispenser(const std::shared_ptr<GOptions>& gopt,
38 const std::shared_ptr<const gdynamicdigitization::dRoutinesMap>& gdynamicDigitizationMap)
39 : GBase(gopt, EVENTDISPENSER_LOGGER), gDigitizationMap(gdynamicDigitizationMap) {
40 // Retrieve configuration parameters from GOptions.
41 string filename = gopt->getScalarString("run_weights");
42 userRunno = gopt->getScalarInt("run");
43 neventsToProcess = gopt->getScalarInt("n");
44
45 // Detect offscreen mode once at construction so processEvents() needs no vis headers.
46 // g4view is only defined when g4display options are included (e.g. in the full gemc app).
47 if (gopt->doesOptionExist("g4view")) {
48 auto driverNode = gopt->getOptionMapInNode("g4view", "driver");
49 if (!driverNode.IsNull() && driverNode.IsDefined()) {
50 offscreen_screenshots = (driverNode.as<std::string>() == "TOOLSSG_OFFSCREEN");
51 }
52 }
53
54 // If there are no events to process, keep the object in an initialized-but-idle state.
55 if (neventsToProcess == 0) return;
56
57 // If no file is provided, use the user-specified run number (single-run mode).
58 if (filename == UNINITIALIZEDSTRINGQUANTITY && neventsToProcess > 0) {
59 runEvents[userRunno] = neventsToProcess;
60 return;
61 }
62 else {
63 // Multi-run mode: a filename was specified; attempt to open the run weights input file.
64 ifstream in(filename.c_str());
65 if (!in) {
66 // Keep behavior unchanged: log error and continue with an empty distribution.
68 "Error: can't open run weights input file >", filename, "<. Check your spelling. Exiting.");
69 }
70 else {
71 log->info(1, "Loading run weights from ", filename);
72
73 // Read "run weight" pairs, one per line.
74 // The order of insertion into listOfRuns reflects the file order and may be used by clients.
75 int run;
76 double weight;
77 while (in >> run >> weight) {
78 listOfRuns.push_back(run);
79 runWeights[run] = weight;
80 runEvents[run] = 0; // initialize per-run counters before distribution
81 }
82
83 // Distribute the total number of events among runs according to their weights.
84 distributeEvents(neventsToProcess);
85 }
86 in.close();
87
88 // Log summary information: overall distribution table.
89 log->info(0, "EventDispenser initialized with ", neventsToProcess, " events distributed among ",
90 runWeights.size(), " runs:");
91 log->info(0, " run\t weight\t n. events");
92 for (const auto& weight : runWeights) {
93 log->info(0, " ", weight.first, "\t ", weight.second, "\t ", runEvents[weight.first]);
94 }
95 }
96}
97
98
99// setNumberOfEvents summary:
100// - Clears any existing distribution and assigns all events to the user-selected run number.
101void EventDispenser::setNumberOfEvents(int nevents_to_process) {
102 runEvents.clear();
103 runEvents[userRunno] = nevents_to_process;
104}
105
107 currentRunno = -1;
108}
109
110
111// distributeEvents summary:
112// - Performs stochastic sampling to convert runWeights into integer runEvents counts.
113void EventDispenser::distributeEvents(int nevents_to_process) {
114 // Set up a random number generator drawing from U[0, 1].
115 random_device randomDevice;
116 mt19937 generator(randomDevice());
117 uniform_real_distribution<> randomDistribution(0, 1);
118
119 // Weights in the run-weights file are relative, not required to sum to 1. Compute the total
120 // once and scale each draw by it, so non-normalized weights distribute events correctly.
121 double totalWeight = 0;
122 for (const auto& weight : runWeights) { totalWeight += weight.second; }
123 if (totalWeight <= 0) {
125 "Run weights sum to ", totalWeight, " (must be > 0). Check your run weights file.");
126 return;
127 }
128
129 // For each event, select a run by comparing a random draw to the cumulative weight intervals.
130 for (int i = 0; i < nevents_to_process; i++) {
131 double randomNumber = randomDistribution(generator) * totalWeight;
132
133 double cumulativeWeight = 0;
134 for (const auto& weight : runWeights) {
135 cumulativeWeight += weight.second;
136 if (randomNumber <= cumulativeWeight) {
137 runEvents[weight.first]++;
138 break;
139 }
140 }
141 }
142}
143
144
145// getTotalNumberOfEvents summary:
146// - Sums all per-run event counts from runEvents.
148 int totalEvents = 0;
149 for (auto rEvents : runEvents) { totalEvents += rEvents.second; }
150 return totalEvents;
151}
152
153
154// processEvents summary:
155// - Iterates the run allocation.
156// - For each run, loads run-dependent constants/TT via digitization routines (if run changed).
157// - Dispatches the events to Geant4 via \c /run/beamOn.
159 // Get the Geant4 UI manager pointer used to apply macro commands.
160 G4UImanager* g4uim = G4UImanager::GetUIpointer();
161
162 // Iterate over each run in the run events map.
163 for (auto& run : runEvents) {
164 int runNumber = run.first;
165 int nevents = run.second;
166
167 // Load constants and translation tables if the run number has changed.
168 if (runNumber != currentRunno) {
169 // Iterate the (plugin name -> digitization routine) map.
170 // digiRoutine is a std::shared_ptr<GDynamicDigitization>.
171 for (const auto& [plugin, digiRoutine] : *gDigitizationMap) {
172 // The variation is resolved per routine at geometry load (gsystem variation,
173 // or the digitization_variation option override when set).
174 const std::string& variation = digiRoutine->getDigitizationVariation();
175
176 log->debug(NORMAL, FUNCTION_NAME, "Calling ", plugin, " loadConstants for run ", runNumber,
177 " with variation ", variation);
178 if (digiRoutine->loadConstants(runNumber, variation) == false) {
180 "Failed to load constants for ", plugin, " for run ", runNumber, " with variation ",
181 variation);
182 }
183
184 log->debug(NORMAL, FUNCTION_NAME, "Calling ", plugin, " loadTT for run ", runNumber);
185 if (digiRoutine->loadTT(runNumber, variation) == false) {
187 "Failed to load translation table for ", plugin, " for run ", runNumber,
188 " with variation ", variation);
189 }
190 }
191 currentRunno = runNumber;
192 }
193
194 log->info(1, "Starting run ", runNumber, " with ", nevents, " events.");
195 // Tag the next G4Run with this run number. Guarded because standalone/unit-test
196 // contexts (e.g. the event_dispenser example) may run without a G4RunManager.
197 if (G4RunManager* g4rm = G4RunManager::GetRunManager()) { g4rm->SetRunIDCounter(runNumber); }
198
199 // Dispatch all events for this run in a single call.
200 // The command string is a standard Geant4 UI command: \c /run/beamOn <N>.
201 log->info(1, "Processing ", nevents, " events in one go");
202 closeOpenGeometryBeforeBeamOn(log);
203 // Record the moment the first BeamOn is issued so a timing summary can be produced later.
204 if (!beamOnTime.has_value()) { beamOnTime = std::chrono::steady_clock::now(); }
205 g4uim->ApplyCommand("/run/beamOn " + to_string(nevents));
206 // Take the screenshot after BeamOn returns. At this point G4VisManager::EndOfRun()
207 // has already joined the vis subthread (ARM64 offset 0xa35f8: bl thread::join), so
208 // DrawEvent calls are finished — no concurrent scene-graph writes. The transient store
209 // is still intact: the vis subthread's exit cleanup only runs after running=0 is set
210 // inside G4VisManager::EndOfRun(), which completes inside BeamOn before it returns.
211 if (offscreen_screenshots) {
212 g4uim->ApplyCommand("/vis/tsg/offscreen/set/size 3000 2000");
213 g4uim->ApplyCommand("/vis/tsg/offscreen/set/file gemc_run_" + to_string(runNumber) + ".png");
214 g4uim->ApplyCommand("/vis/viewer/rebuild");
215 }
216
217 log->info(1, "Run ", runNumber, " done with ", nevents, " events");
218 }
219
220 return 1;
221}
void resetRunContext()
Force per-run digitization setup to run again on the next event batch.
int processEvents()
Processes all runs by initializing digitization routines and dispatching events.
EventDispenser(const std::shared_ptr< GOptions > &gopt, const std::shared_ptr< const gdynamicdigitization::dRoutinesMap > &gdynamicDigitizationMap)
Constructs an EventDispenser and prepares the run event distribution.
void setNumberOfEvents(int nevts)
Sets the total number of events to process in single-run mode.
int getTotalNumberOfEvents() const
Computes the total number of events across all runs.
std::shared_ptr< GLogger > log
void debug(debug_type type, Args &&... args) const
void info(int level, Args &&... args) const
void error(int exit_code, Args &&... args) const
Event Dispenser module error-code conventions.
#define ERR_EVENTDISTRIBUTIONFILENOTFOUND
Run-weight file could not be opened/read.
Declares the EventDispenser class.
Public declaration of the Event Dispenser module command-line / configuration options.
constexpr const char * EVENTDISPENSER_LOGGER
Logger name used by this module when creating a GLogger through the base infrastructure.
constexpr int ERR_LOADCONSTANTFAIL
constexpr int ERR_LOADTTFAIL
run
#define FUNCTION_NAME
NORMAL
#define UNINITIALIZEDSTRINGQUANTITY
constexpr const char * to_string(randomModel m) noexcept