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.
38 const std::shared_ptr<GOptions>& gopt,
39 const std::shared_ptr<const gdynamicdigitization::dRoutinesMap>& gdynamicDigitizationMap,
40 std::shared_ptr<GAnalysisAccumulator> analyzer)
41 : GBase(gopt, EVENTDISPENSER_LOGGER), gDigitizationMap(gdynamicDigitizationMap),
42 analysisAccumulator(std::move(analyzer)) {
43 // Retrieve configuration parameters from GOptions.
44 const auto filename = gopt->getOptionalScalarString("run_weights");
45 userRunno = gopt->getRequiredScalarInt("run");
46 neventsToProcess = gopt->getRequiredScalarInt("n");
47
48 // Detect offscreen mode once at construction so processEvents() needs no vis headers.
49 // g4view is only defined when g4display options are included (e.g. in the full gemc app).
50 if (gopt->doesOptionExist("g4view")) {
51 auto driverNode = gopt->getOptionMapInNode("g4view", "driver");
52 if (!driverNode.IsNull() && driverNode.IsDefined()) {
53 offscreen_screenshots = (driverNode.as<std::string>() == "TOOLSSG_OFFSCREEN");
54 }
55 }
56
57 // If there are no events to process, keep the object in an initialized-but-idle state.
58 if (neventsToProcess == 0) return;
59
60 // If no file is provided, use the user-specified run number (single-run mode).
61 if (!filename && neventsToProcess > 0) {
62 runEvents[userRunno] = neventsToProcess;
63 return;
64 }
65 else {
66 // Multi-run mode: a filename was specified; attempt to open the run weights input file.
67 ifstream in(filename->c_str());
68 if (!in) {
69 // Keep behavior unchanged: log error and continue with an empty distribution.
70 log->error(
72 "Error: can't open run weights input file >%1<. Check your spelling. Exiting.", *filename);
73 }
74 else {
75 log->info(1, "Loading run weights from %1", *filename);
76
77 // Read "run weight" pairs, one per line.
78 // The order of insertion into listOfRuns reflects the file order and may be used by clients.
79 int run;
80 double weight;
81 while (in >> run >> weight) {
82 listOfRuns.push_back(run);
83 runWeights[run] = weight;
84 runEvents[run] = 0; // initialize per-run counters before distribution
85 }
86
87 // Distribute the total number of events among runs according to their weights.
88 distributeEvents(neventsToProcess);
89 }
90 in.close();
91
92 // Log summary information: overall distribution table.
93 log->info(
94 0, "EventDispenser initialized with %1 events distributed among %2 runs:", neventsToProcess,
95 runWeights.size());
96 log->info(0, " run\t weight\t n. events");
97 for (const auto& weight : runWeights) {
98 log->info(0, " %1\t %2\t %3", weight.first, weight.second, runEvents[weight.first]);
99 }
100 }
101}
102
103
104// setNumberOfEvents summary:
105// - Clears any existing distribution and assigns all events to the user-selected run number.
106void EventDispenser::setNumberOfEvents(int nevents_to_process) {
107 runEvents.clear();
108 runEvents[userRunno] = nevents_to_process;
109}
110
112 currentRunno.reset();
113}
114
115
116// distributeEvents summary:
117// - Performs stochastic sampling to convert runWeights into integer runEvents counts.
118void EventDispenser::distributeEvents(int nevents_to_process) {
119 // Set up a random number generator drawing from U[0, 1].
120 random_device randomDevice;
121 mt19937 generator(randomDevice());
122 uniform_real_distribution<> randomDistribution(0, 1);
123
124 // Weights in the run-weights file are relative, not required to sum to 1. Compute the total
125 // once and scale each draw by it, so non-normalized weights distribute events correctly.
126 double totalWeight = 0;
127 for (const auto& weight : runWeights) { totalWeight += weight.second; }
128 if (totalWeight <= 0) {
129 log->error(
131 "Run weights sum to %1 (must be > 0). Check your run weights file.", totalWeight);
132 return;
133 }
134
135 // For each event, select a run by comparing a random draw to the cumulative weight intervals.
136 for (int i = 0; i < nevents_to_process; i++) {
137 double randomNumber = randomDistribution(generator) * totalWeight;
138
139 double cumulativeWeight = 0;
140 for (const auto& weight : runWeights) {
141 cumulativeWeight += weight.second;
142 if (randomNumber <= cumulativeWeight) {
143 runEvents[weight.first]++;
144 break;
145 }
146 }
147 }
148}
149
150
151// getTotalNumberOfEvents summary:
152// - Sums all per-run event counts from runEvents.
154 int totalEvents = 0;
155 for (auto rEvents : runEvents) { totalEvents += rEvents.second; }
156 return totalEvents;
157}
158
159
160void EventDispenser::prepareRun(int runNumber) {
161 // Load constants and translation tables if the run number has changed.
162 if (!currentRunno || runNumber != *currentRunno) {
163 // Iterate the (plugin name -> digitization routine) map.
164 // digiRoutine is a std::shared_ptr<GDynamicDigitization>.
165 for (const auto& [plugin, digiRoutine] : *gDigitizationMap) {
166 // The variation is resolved per routine at geometry load (gsystem variation,
167 // or the digitization_variation option override when set).
168 const std::string& variation = digiRoutine->getDigitizationVariation();
169
170 log->debug(
171 NORMAL, "%1Calling %2 loadConstants for run %3 with variation %4", FUNCTION_NAME, plugin,
172 runNumber, variation);
173 if (digiRoutine->loadConstants(runNumber, variation) == false) {
174 log->error(
175 ERR_LOADCONSTANTFAIL, "Failed to load constants for %1 for run %2 with variation %3",
176 plugin, runNumber, variation);
177 }
178
179 log->debug(NORMAL, "%1Calling %2 loadTT for run %3", FUNCTION_NAME, plugin, runNumber);
180 if (digiRoutine->loadTT(runNumber, variation) == false) {
181 log->error(
182 ERR_LOADTTFAIL, "Failed to load translation table for %1 for run %2 with variation %3",
183 plugin, runNumber, variation);
184 }
185 }
186 currentRunno = runNumber;
187 }
188
189 if (analysisAccumulator != nullptr) { analysisAccumulator->setCurrentRunNumber(runNumber); }
190 // Tag the next G4Run with this run number. Guarded because standalone/unit-test
191 // contexts (e.g. the event_dispenser example) may run without a G4RunManager.
192 if (G4RunManager* g4rm = G4RunManager::GetRunManager()) { g4rm->SetRunIDCounter(runNumber); }
193
194 closeOpenGeometryBeforeBeamOn(log);
195}
196
197
198// processEvents summary:
199// - Iterates the run allocation.
200// - For each run, loads run-dependent constants/TT via digitization routines (if run changed).
201// - Dispatches the events to Geant4 via \c /run/beamOn.
203 // Get the Geant4 UI manager pointer used to apply macro commands.
204 G4UImanager* g4uim = G4UImanager::GetUIpointer();
205
206 // Iterate over each run in the run events map.
207 for (auto& run : runEvents) {
208 int runNumber = run.first;
209 int nevents = run.second;
210
211 prepareRun(runNumber);
212 log->info(1, "Starting run %1 with %2 events.", runNumber, nevents);
213
214 // Dispatch all events for this run in a single call.
215 // The command string is a standard Geant4 UI command: \c /run/beamOn <N>.
216 log->info(1, "Processing %1 events in one go", nevents);
217 // Record the moment the first BeamOn is issued so a timing summary can be produced later.
218 if (!beamOnTime.has_value()) { beamOnTime = std::chrono::steady_clock::now(); }
219 g4uim->ApplyCommand("/run/beamOn " + to_string(nevents));
220 // Take the screenshot after BeamOn returns. At this point G4VisManager::EndOfRun()
221 // has already joined the vis subthread (ARM64 offset 0xa35f8: bl thread::join), so
222 // DrawEvent calls are finished — no concurrent scene-graph writes. The transient store
223 // is still intact: the vis subthread's exit cleanup only runs after running=0 is set
224 // inside G4VisManager::EndOfRun(), which completes inside BeamOn before it returns.
225 if (offscreen_screenshots) {
226 g4uim->ApplyCommand("/vis/tsg/offscreen/set/size 3000 2000");
227 g4uim->ApplyCommand("/vis/tsg/offscreen/set/file gemc_run_" + to_string(runNumber) + ".png");
228 g4uim->ApplyCommand("/vis/viewer/rebuild");
229 }
230
231 log->info(1, "Run %1 done with %2 events", runNumber, nevents);
232 }
233
234 return 1;
235}
void prepareRun(int runNumber)
Load detector conditions and set the run context before an externally issued beamOn.
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, std::shared_ptr< GAnalysisAccumulator > analysisAccumulator=nullptr)
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.
GBase(const std::shared_ptr< GOptions > &gopt, std::string logger_name="")
std::shared_ptr< GLogger > log
Event Dispenser module error-code conventions.
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
constexpr int ERR_EVENTDISTRIBUTIONFILENOTFOUND
Run-weight file could not be opened/read.
constexpr const char * to_string(randomModel m) noexcept