actions
Loading...
Searching...
No Matches
gRunAction.cc
Go to the documentation of this file.
1// gemc
2#include "gRunAction.h"
3#include "gRun.h"
5#include "gutsConventions.h"
6
7// geant4
8#include "G4Threading.hh"
9
10std::mutex GRunAction::completed_run_data_mutex;
11GRunAction::CompletedRunData GRunAction::completed_worker_run_data;
12
13
14// Construct the run action and retain access to shared configuration and
15// digitization services for the current execution context.
16GRunAction::GRunAction(std::shared_ptr<GOptions> gopt,
17 std::shared_ptr<gdynamicdigitization::dRoutinesMap> digi_map,
18 std::shared_ptr<GAnalysisAccumulator> analyzer) : GBase(gopt, GRUNACTION_LOGGER),
19 goptions(std::move(gopt)),
20 digitization_routines_map(std::move(digi_map)),
21 analysis_accumulator(std::move(analyzer)) {
22 const auto desc = std::to_string(G4Threading::G4GetThreadId());
23 log->debug(CONSTRUCTOR, FUNCTION_NAME, desc);
24}
25
26
27// Create the thread-local run object used by Geant4 for this execution context.
28G4Run *GRunAction::GenerateRun() {
29 log->debug(NORMAL, FUNCTION_NAME);
30
31 return new GRun(goptions, digitization_routines_map);
32}
33
34// Initialize run-scoped bookkeeping, determine which streamer categories are
35// needed for this run, and open the appropriate connections.
36void GRunAction::BeginOfRunAction(const G4Run *aRun) {
37 const auto thread_id = G4Threading::G4GetThreadId();
38 const auto run = aRun->GetRunID();
39
40 auto run_header = std::make_unique<GRunHeader>(goptions, run, thread_id);
41 run_data = std::make_unique<GRunDataCollection>(goptions, std::move(run_header));
42 if (analysis_accumulator != nullptr) {
43 analysis_run_number = analysis_accumulator->currentRunNumber();
44 analysis_shard = std::make_unique<GAnalysisShard>();
45 }
46
47 const auto neventsThisRun = aRun->GetNumberOfEventToBeProcessed();
48
49 // Reset the per-run mode flags before scanning the digitization routines.
50 need_a_thread_streamer = false;
51 need_a_run_streamer = false;
52
53 // Inspect the available digitization routines to determine whether this run
54 // requires event-mode publication, run-mode publication, or both.
55 if (digitization_routines_map != nullptr) {
56 for (const auto &[plugin, digiRoutine]: *digitization_routines_map) {
57 if (digiRoutine == nullptr) {
58 log->error(ERR_GDIGIMAP_NOT_EXISTING, FUNCTION_NAME,
59 " null digitization routine registered for plugin ", plugin);
60 }
61
62 if (digiRoutine->collection_mode() == CollectionMode::event) {
63 need_a_thread_streamer = true;
64 } else if (digiRoutine->collection_mode() == CollectionMode::run) {
65 to_normalize[plugin] = digiRoutine->variables_to_normalize();
66 need_a_run_streamer = true;
67 }
68 }
69 } else {
70 log->error(ERR_GDIGIMAP_NOT_EXISTING, FUNCTION_NAME,
71 " digitization_routines_map is null - streamer mode detection skipped.");
72 }
73
74 for (const auto& streamer_definition : gstreamer::getGStreamerDefinition(goptions)) {
75 if (streamer_definition.type == "event") {
76 need_a_thread_streamer = true;
77 break;
78 }
79 }
80
81
82 // Worker threads own event-mode publication, so they lazily build and open
83 // thread-local streamers only when at least one event-mode digitizer exists.
84 if (!IsMaster() && need_a_thread_streamer) {
85 if (gstreamer_threads_map == nullptr) {
86 log->info(1, "Defining thread gstreamers for run ", run, " in thread ", thread_id);
87 gstreamer_threads_map = gstreamer::gstreamersMapPtr(goptions, thread_id);
88 }
89
90 if (gstreamer_threads_map == nullptr) {
91 log->error(1, FUNCTION_NAME, " gstreamer_threads_map is null in thread ", thread_id,
92 " - cannot open connections.");
93 }
94
95 for (const auto &[name, gstreamer]: *gstreamer_threads_map) {
96 if (gstreamer == nullptr) {
98 "Null GStreamer entry ", name, " in thread ", thread_id);
99 }
100
101 if (!gstreamer->openConnection()) {
103 "Failed to open connection for GStreamer ", name,
104 " in thread ", thread_id);
105 }
106
107 log->info(2, FUNCTION_NAME, "Worker thread [", thread_id, "]: opening connection for ",
108 KGRN, name, RST,
109 " for run ", run, ". Number of events to be processed: ", neventsThisRun);
110 }
111 }
112 // The master thread owns run-mode publication, so it opens the run streamers
113 // only when at least one digitizer accumulates payload at run scope.
114 else if (IsMaster() && need_a_run_streamer) {
115 if (gstreamer_run_map == nullptr) {
116 log->info(1, "Defining run gstreamers for run ", run);
117 gstreamer_run_map = gstreamer::gstreamersMapPtr(goptions);
118 }
119
120 if (gstreamer_run_map == nullptr) {
121 log->error(1, FUNCTION_NAME, " gstreamer_run_map is null in master thread ",
122 " - cannot open connections.");
123 }
124
125 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
126 if (gstreamer == nullptr) {
128 "Null GStreamer entry ", name, " in master thread");
129 }
130
131 if (!gstreamer->openConnection()) {
133 "Failed to open connection for GStreamer in master thread ", name);
134 }
135
136 log->info(2, FUNCTION_NAME, "Master Thread: opening connection for ",
137 KGRN, name, RST,
138 " for run ", run, ". Number of events to be processed: ", neventsThisRun);
139 }
140 }
141}
142
143// Close streamers at run end and, when running on the master thread, merge and
144// publish the run-level payload accumulated by workers.
145void GRunAction::EndOfRunAction(const G4Run *aRun) {
146 const auto thread_id = G4Threading::G4GetThreadId();
147 const auto runNumber = aRun->GetRunID();
148 const std::string what_am_i = IsMaster() ? "Master" : "Worker";
149
150 if (!IsMaster() && need_a_thread_streamer) {
151 if (gstreamer_threads_map == nullptr) {
152 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
153 " gstreamer_map is null in thread ", thread_id,
154 " - cannot close connections.");
155 } else {
156 for (const auto &[name, gstreamer]: *gstreamer_threads_map) {
157 log->info(2, FUNCTION_NAME, " ", what_am_i, " [", thread_id, "], for run ", runNumber,
158 " closing connection for gstreamer ", name);
159
160 if (gstreamer == nullptr) {
162 "Null GStreamer entry ", name, " in thread ", thread_id);
163 }
164
165 if (!gstreamer->closeConnection()) {
166 log->error(1, "Failed to close connection for GStreamer ", name, " in thread ", thread_id);
167 }
168 }
169 }
170 }
171
172 // Each execution context writes only to its private shard. The shared accumulator locks once here.
173 if (analysis_accumulator != nullptr && analysis_shard != nullptr) {
174 if (!analysis_shard->empty()) { analysis_accumulator->merge(std::move(*analysis_shard)); }
175 analysis_shard.reset();
176 }
177
178 // Worker threads do not publish merged run data. Instead, they hand their
179 // completed run-level accumulation to the shared pool and return.
180 if (!IsMaster()) {
181 // Only contribute to the master merge pool when a run-mode streamer will drain it;
182 // otherwise the pool is never taken and would accumulate stale prior-run data.
183 if (need_a_run_streamer) { stash_worker_run_data(); }
184 return;
185 }
186
187 if (IsMaster() && need_a_run_streamer) {
188 // Gather all worker-produced run data for this run and merge them into a
189 // single master-side run-data object before publication.
190 auto completed_run_data = take_completed_worker_run_data();
191 log->info(2, FUNCTION_NAME,
192 " master collected ", static_cast<int>(completed_run_data.size()),
193 " worker run_data object(s) for run ", runNumber);
194
195 std::shared_ptr<GRunDataCollection> merged_run_data;
196
197 for (auto &worker_run_data: completed_run_data) {
198 if (worker_run_data == nullptr) {
199 continue;
200 }
201
202 // Create the merged destination lazily only if there is at least one
203 // valid worker contribution to merge.
204 if (merged_run_data == nullptr) {
205 auto merged_header = std::make_unique<GRunHeader>(goptions, runNumber, thread_id);
206 merged_run_data = std::make_shared<GRunDataCollection>(goptions, std::move(merged_header));
207 }
208
209 merged_run_data->merge(*worker_run_data);
210 }
211
212 // Publish the merged run-level payload once, after all workers have contributed.
213 if (merged_run_data != nullptr) {
214 publish_run_data(merged_run_data);
215 }
216
217 if (gstreamer_run_map == nullptr) {
218 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
219 " gstreamer_map is null in master thread - cannot close connections.");
220 }
221
222 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
223 log->info(2, FUNCTION_NAME, " ", what_am_i, " for run ", runNumber,
224 " closing connection for gstreamer ", name);
225
226 if (gstreamer == nullptr) {
228 "Null GStreamer entry ", name, " in master thread");
229 }
230
231 if (!gstreamer->closeConnection()) {
232 log->error(1, "Failed to close connection for GStreamer ", name, " in master thread");
233 }
234 }
235 }
236
237}
238
239// Move this worker's completed run-data object into the protected static pool
240// so it can later be collected by the master thread.
241void GRunAction::stash_worker_run_data() {
242 if (run_data == nullptr) {
243 return;
244 }
245
246 std::scoped_lock lock(completed_run_data_mutex);
247 completed_worker_run_data.emplace_back(std::move(run_data));
248}
249
250// Extract and clear the protected pool of completed worker run-data objects.
251auto GRunAction::take_completed_worker_run_data() -> CompletedRunData {
252 std::scoped_lock lock(completed_run_data_mutex);
253
254 auto result = std::move(completed_worker_run_data);
255 completed_worker_run_data.clear();
256
257 return result;
258}
259
260
261// Publish the merged run-level payload to every configured master-side run streamer.
262void GRunAction::publish_run_data(const std::shared_ptr<GRunDataCollection> &run_data_collaction) const {
263 if (run_data_collaction == nullptr) {
264 log->error(ERR_GRUNACTION_NOT_EXISTING, FUNCTION_NAME,
265 " run_data is null - cannot publish merged run data.");
266 }
267
268 if (gstreamer_run_map == nullptr) {
269 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
270 " no run streamer map available - run data will not be published.");
271 }
272
273 // Normalize once, before publishing: normalize_run_data() mutates run_data_collaction
274 // in place and is NOT idempotent, so running it per streamer would divide the run
275 // observables by events_processed once for every configured run streamer.
276 normalize_run_data(run_data_collaction);
277
278 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
279 if (gstreamer == nullptr) {
280 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
281 " null gstreamer instance for run streamer ", name);
282 }
283
284 gstreamer->publishRunData(run_data_collaction);
285 }
286}
287
288
289void GRunAction::normalize_run_data(const std::shared_ptr<GRunDataCollection> &run_data_collaction) const {
290 if (run_data_collaction == nullptr) {
291 log->error(ERR_GRUNACTION_NOT_EXISTING, FUNCTION_NAME,
292 " run_data_collaction is null - cannot normalize run data.");
293 }
294
295 const int events_processed = run_data_collaction->get_events_processed();
296 if (events_processed <= 0) {
297 log->warning(FUNCTION_NAME,
298 " events_processed is ", events_processed,
299 " - skipping normalization.");
300 return;
301 }
302
303 const double norm = static_cast<double>(events_processed);
304
305 for (auto &[sdName, dataCollection]: run_data_collaction->getMutableDataCollectionMap()) {
306 if (dataCollection == nullptr) {
307 log->warning(FUNCTION_NAME,
308 " detector ", sdName,
309 " has null data collection - skipping.");
310 continue;
311 }
312
313 auto &digitizedData = dataCollection->getMutableDigitizedData();
314 if (digitizedData.empty() || digitizedData.front() == nullptr) {
315 continue;
316 }
317
318 auto &digitized = digitizedData.front();
319 // we are in a const method so we can't loop directky over to_normalize[sdName]
320 // cause this call could modify the map!
321
322 auto it = to_normalize.find(sdName);
323 if (it != to_normalize.end()) {
324 for (const auto &varName: it->second) {
325 const auto intVars = digitized->getIntObservablesMap(0);
326 const auto intIt = intVars.find(varName);
327 if (intIt != intVars.end()) {
328 digitized->includeVariable(varName, static_cast<double>(intIt->second) / norm);
329 continue;
330 }
331
332 const auto dblVars = digitized->getDblObservablesMap(0);
333 const auto dblIt = dblVars.find(varName);
334 if (dblIt != dblVars.end()) {
335 digitized->includeVariable(varName, dblIt->second / norm);
336 }
337 }
338 }
339 }
340}
341
342
343// (Legacy/experimental streaming logic remains commented out below.)
344
345// TODO: 2 more is too much we need some calculation here
346// int nFramesToCreate = neventsThisRun * eventDuration / frameDuration + 2;
347
348// if (stream) {
349// if (frameStreamVerbosity >= GVERBOSITY_SUMMARY) {
350// cout << SROLOGHEADER << " current nframes in the buffer: " << frameRunData.size() << ", new frames to create: " << nFramesToCreate;
351// cout << ", last frame id created: " << lastFrameCreated << endl;
352// }
353//
354// for (int f = lastFrameCreated; f < lastFrameCreated + nFramesToCreate; f++) {
355// GFrameDataCollectionHeader* gframeHeader = new GFrameDataCollectionHeader(f + 1, frameDuration, verbosity);
356// GFrameDataCollection* frameData = new GFrameDataCollection(gframeHeader, verbosity);
357// frameRunData.push_back(frameData);
358// }
359//
360// lastFrameCreated += nFramesToCreate;
361// if (frameStreamVerbosity >= GVERBOSITY_SUMMARY) {
362// cout << SROLOGHEADER << nFramesToCreate << " new frames, buffer size is now " << frameRunData.size();
363// cout << ", last frame id created: " << lastFrameCreated << endl;
364// }
365// }
366
367
368// looping over run data and filling frameRunData
369// need to remember last event number here
370// if (stream) {
371// for (auto eventDataCollection : theRun->getRunData()) {
372// int absoluteEventNumber = eventIndex + eventDataCollection->getEventNumber();
373//
374// // filling frameRunData with this eventDataCollection
375// for (auto [detectorName, gdataCollection] : *eventDataCollection->getDataCollectionMap()) {
376// for (auto hitDigitizedData : *gdataCollection->getDigitizedData()) {
377// int timeAtelectronic = hitDigitizedData->getTimeAtElectronics();
378// if (timeAtelectronic != TIMEATELECTRONICSNOTDEFINED) {
379// int frameIndex = eventFrameIndex(absoluteEventNumber, timeAtelectronic);
380// frameRunData[frameIndex]->addIntegralPayload(formPayload(hitDigitizedData), verbosity);
381// }
382// }
383// }
384// }
385// }
386
387
388// now flushing all frames past eventIndex
389
390// if (stream) {
391// // updating eventIndex
392// eventIndex += neventsThisRun;
393//
394// for (auto [factoryName, streamerFactory] : *gstreamerFactoryMap) {
395// if (streamerFactory->getStreamType() == "stream" && frameRunData.size() > 0) {
396// // need to look for additional frame to flush
397// int nFramesToFlush = nFramesToCreate - 2;
398//
399// if (frameStreamVerbosity >= GVERBOSITY_SUMMARY) { cout << SROLOGHEADER << "number of frames to flush: " << nFramesToFlush << endl; }
400// for (auto fid = 0; fid < nFramesToFlush; fid++) {
401// logSummary("Streaming frame id <" + to_string(frameRunData.front()->getFrameID()) + " using streamer factory >" + factoryName + "<");
402// streamerFactory->publishFrameRunData(goptions, frameRunData.front());
403// delete frameRunData.front();
404// frameRunData.erase(frameRunData.begin());
405// }
406// }
407// }
408// }
409
410
411// determine the frame ID based on event number, eventDuration, frameDuration and number of threads
412// add frameData to frameRunData if it's not present
413// int GRunAction::eventFrameIndex(int eventNumber, double timeAtElectronics) {
414// int absoluteHitTime = eventNumber * eventDuration + timeAtElectronics;
415// int frameID = absoluteHitTime / frameDuration + 1;
416// int frameIndex = -1;
417//
418// // cout << "eventNumber: " << eventNumber << ", absoluteHitTime: " << absoluteHitTime << ", frameID: " << frameID << endl;
419//
420// for (size_t f = 0; f < frameRunData.size(); f++) { if (frameRunData[f]->getFrameID() == frameID) { frameIndex = (int)f; } }
421// // cout << "eventNumber: " << eventNumber << ", absoluteHitTime: " << absoluteHitTime << ", frameIndex: " << frameIndex << endl;
422//
423// return frameIndex;
424// }
425
426// vector<int> GRunAction::formPayload(GDigitizedData* digitizedData) {
427// vector<int> payload;
428//
429// int crate = digitizedData->getIntObservable(CRATESTRINGID);
430// int slot = digitizedData->getIntObservable(SLOTSTRINGID);
431// int channel = digitizedData->getIntObservable(CHANNELSTRINGID);
432// int q = digitizedData->getIntObservable(CHARGEATELECTRONICS);
433// int time = digitizedData->getIntObservable(TIMEATELECTRONICS);
434//
435// payload.push_back(crate);
436// payload.push_back(slot);
437// payload.push_back(channel);
438// payload.push_back(q);
439// payload.push_back(time);
440//
441// return payload;
442// }
443//
444// bool GRunAction::findFrameID(int fid) {
445// for (auto frame : frameRunData) { if (frame->getFrameID() == fid) { return true; } }
446// return false;
447// }
GBase(const std::shared_ptr< GOptions > &gopt, std::string logger_name="")
std::shared_ptr< GLogger > log
GRunAction(std::shared_ptr< GOptions > gopts, std::shared_ptr< gdynamicdigitization::dRoutinesMap > digi_map, std::shared_ptr< GAnalysisAccumulator > analysis_accumulator=nullptr)
Constructs the run action.
Definition gRunAction.cc:16
Thread-local run object created by the GEMC run action.
Definition gRun.h:54
Declares GRunAction, the run-lifecycle action for the GEMC actions module.
constexpr const char * GRUNACTION_LOGGER
Definition gRunAction.h:26
Declares GRun, the thread-local run container used by the GEMC actions module.
Defines error codes used by the GEMC actions module.
#define ERR_GDIGIMAP_NOT_EXISTING
#define ERR_GRUNACTION_NOT_EXISTING
#define ERR_STREAMERMAP_NOT_EXISTING
run
#define FUNCTION_NAME
CONSTRUCTOR
NORMAL
std::shared_ptr< const gstreamersMap > gstreamersMapPtr(const std::shared_ptr< GOptions > &gopts, int thread_id=-1)
vector< GStreamerDefinition > getGStreamerDefinition(const std::shared_ptr< GOptions > &gopts)