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,
19 std::shared_ptr<GSROFactory> sro) : GBase(gopt, GRUNACTION_LOGGER),
20 goptions(std::move(gopt)),
21 digitization_routines_map(std::move(digi_map)),
22 analysis_accumulator(std::move(analyzer)),
23 sro_factory(std::move(sro)) {
24 const auto desc = std::to_string(G4Threading::G4GetThreadId());
25 log->debug(CONSTRUCTOR, "%1%2", FUNCTION_NAME, desc);
26}
27
28
29// Create the thread-local run object used by Geant4 for this execution context.
30G4Run *GRunAction::GenerateRun() {
31 log->debug(NORMAL, "%1", FUNCTION_NAME);
32
33 return new GRun(goptions, digitization_routines_map);
34}
35
36// Initialize run-scoped bookkeeping, determine which streamer categories are
37// needed for this run, and open the appropriate connections.
38void GRunAction::BeginOfRunAction(const G4Run *aRun) {
39 const auto thread_id = G4Threading::G4GetThreadId();
40 const auto run = aRun->GetRunID();
41
42 auto run_header = std::make_unique<GRunHeader>(goptions, run, thread_id);
43 run_data = std::make_unique<GRunDataCollection>(goptions, std::move(run_header));
44 if (analysis_accumulator != nullptr) {
45 analysis_run_number = analysis_accumulator->currentRunNumber();
46 analysis_shard = std::make_unique<GAnalysisShard>();
47 }
48
49 const auto neventsThisRun = aRun->GetNumberOfEventToBeProcessed();
50 if (sro_factory && IsMaster()) {
51 try { sro_factory->begin_run(run, static_cast<GSROEventId>(neventsThisRun)); }
52 catch (const std::exception& error) {
53 log->error(gstreamer::ERR_PUBLISH_ERROR, "SRO begin run: %1", error.what());
54 }
55 }
56
57 // Reset the per-run mode flags before scanning the digitization routines.
58 need_a_thread_streamer = false;
59 need_a_run_streamer = false;
60
61 // Inspect the available digitization routines to determine whether this run
62 // requires event-mode publication, run-mode publication, or both.
63 if (digitization_routines_map != nullptr) {
64 for (const auto &[plugin, digiRoutine]: *digitization_routines_map) {
65 if (digiRoutine == nullptr) {
66 log->error(
67 gaction::ERR_GDIGIMAP_NOT_EXISTING, "%1 null digitization routine registered for plugin %2",
68 FUNCTION_NAME, plugin);
69 }
70
71 if (digiRoutine->collection_mode() == CollectionMode::event) {
72 need_a_thread_streamer = true;
73 } else if (digiRoutine->collection_mode() == CollectionMode::run) {
74 to_normalize[plugin] = digiRoutine->variables_to_normalize();
75 need_a_run_streamer = true;
76 }
77 }
78 } else {
79 log->error(
81 "%1 digitization_routines_map is null - streamer mode detection skipped.", FUNCTION_NAME);
82 }
83
84 for (const auto& streamer_definition : gstreamer::getGStreamerDefinition(goptions)) {
85 if (streamer_definition.type == "event") {
86 need_a_thread_streamer = true;
87 break;
88 }
89 }
90
91
92 // Worker threads own event-mode publication, so they lazily build and open
93 // thread-local streamers only when at least one event-mode digitizer exists.
94 if (!IsMaster() && need_a_thread_streamer) {
95 if (gstreamer_threads_map == nullptr) {
96 log->info(1, "Defining thread gstreamers for run %1 in thread %2", run, thread_id);
97 gstreamer_threads_map = gstreamer::gstreamersMapPtr(goptions, thread_id);
98 }
99
100 if (gstreamer_threads_map == nullptr) {
101 log->error(
102 1, "%1 gstreamer_threads_map is null in thread %2 - cannot open connections.", FUNCTION_NAME,
103 thread_id);
104 }
105
106 for (const auto &[name, gstreamer]: *gstreamer_threads_map) {
107 if (gstreamer == nullptr) {
108 log->error(
109 gaction::ERR_STREAMERMAP_NOT_EXISTING, "Null GStreamer entry %1 in thread %2", name,
110 thread_id);
111 }
112
113 if (!gstreamer->openConnection()) {
114 log->error(
116 "Failed to open connection for GStreamer %1 in thread %2", name, thread_id);
117 }
118
119 log->info(
120 2,
121 "%1Worker thread [%2]: opening connection for %3%4%5 for run %6. Number of events to be "
122 "processed: %7", FUNCTION_NAME, thread_id, guts::KGRN, name, guts::RST, run, neventsThisRun);
123 }
124 }
125 // The master thread owns run-mode publication, so it opens the run streamers
126 // only when at least one digitizer accumulates payload at run scope.
127 else if (IsMaster() && need_a_run_streamer) {
128 if (gstreamer_run_map == nullptr) {
129 log->info(1, "Defining run gstreamers for run %1", run);
130 gstreamer_run_map = gstreamer::gstreamersMapPtr(goptions);
131 }
132
133 if (gstreamer_run_map == nullptr) {
134 log->error(
135 1, "%1 gstreamer_run_map is null in master thread - cannot open connections.", FUNCTION_NAME);
136 }
137
138 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
139 if (gstreamer == nullptr) {
140 log->error(
141 gaction::ERR_STREAMERMAP_NOT_EXISTING, "Null GStreamer entry %1 in master thread", name);
142 }
143
144 if (!gstreamer->openConnection()) {
145 log->error(
147 "Failed to open connection for GStreamer in master thread %1", name);
148 }
149
150 log->info(
151 2,
152 "%1Master Thread: opening connection for %2%3%4 for run %5. Number of events to be "
153 "processed: %6", FUNCTION_NAME, guts::KGRN, name, guts::RST, run, neventsThisRun);
154 }
155 }
156}
157
158// Close streamers at run end and, when running on the master thread, merge and
159// publish the run-level payload accumulated by workers.
160void GRunAction::EndOfRunAction(const G4Run *aRun) {
161 const auto thread_id = G4Threading::G4GetThreadId();
162 const auto runNumber = aRun->GetRunID();
163 const std::string what_am_i = IsMaster() ? "Master" : "Worker";
164 // In MT Geant4 reaches the master here after worker run actions; sequential mode owns both roles.
165 if (sro_factory && IsMaster()) {
166 try { sro_factory->finish_run(aRun->GetNumberOfEvent() == aRun->GetNumberOfEventToBeProcessed()); }
167 catch (const std::exception& error) {
168 // finish_run has already joined all crate/progress threads before reporting the first error.
169 log->error(gstreamer::ERR_PUBLISH_ERROR, "SRO finish run: %1", error.what());
170 }
171 }
172
173 if (!IsMaster() && need_a_thread_streamer) {
174 if (gstreamer_threads_map == nullptr) {
175 log->error(
177 "%1 gstreamer_map is null in thread %2 - cannot close connections.", FUNCTION_NAME, thread_id);
178 } else {
179 for (const auto &[name, gstreamer]: *gstreamer_threads_map) {
180 log->info(
181 2, "%1 %2 [%3], for run %4 closing connection for gstreamer %5", FUNCTION_NAME, what_am_i,
182 thread_id, runNumber, name);
183
184 if (gstreamer == nullptr) {
185 log->error(
186 gaction::ERR_STREAMERMAP_NOT_EXISTING, "Null GStreamer entry %1 in thread %2", name,
187 thread_id);
188 }
189
190 if (!gstreamer->closeConnection()) {
191 log->error(1, "Failed to close connection for GStreamer %1 in thread %2", name, thread_id);
192 }
193 }
194 }
195 }
196
197 // Each execution context writes only to its private shard. The shared accumulator locks once here.
198 if (analysis_accumulator != nullptr && analysis_shard != nullptr) {
199 if (!analysis_shard->empty()) { analysis_accumulator->merge(std::move(*analysis_shard)); }
200 analysis_shard.reset();
201 }
202
203 // Worker threads do not publish merged run data. Instead, they hand their
204 // completed run-level accumulation to the shared pool and return.
205 if (!IsMaster()) {
206 // Only contribute to the master merge pool when a run-mode streamer will drain it;
207 // otherwise the pool is never taken and would accumulate stale prior-run data.
208 if (need_a_run_streamer) { stash_worker_run_data(); }
209 return;
210 }
211
212 if (IsMaster() && need_a_run_streamer) {
213 // Gather all worker-produced run data for this run and merge them into a
214 // single master-side run-data object before publication.
215 auto completed_run_data = take_completed_worker_run_data();
216 log->info(
217 2, "%1 master collected %2 worker run_data object(s) for run %3", FUNCTION_NAME,
218 static_cast<int>(completed_run_data.size()), runNumber);
219
220 std::shared_ptr<GRunDataCollection> merged_run_data;
221
222 for (auto &worker_run_data: completed_run_data) {
223 if (worker_run_data == nullptr) {
224 continue;
225 }
226
227 // Create the merged destination lazily only if there is at least one
228 // valid worker contribution to merge.
229 if (merged_run_data == nullptr) {
230 auto merged_header = std::make_unique<GRunHeader>(goptions, runNumber, thread_id);
231 merged_run_data = std::make_shared<GRunDataCollection>(goptions, std::move(merged_header));
232 }
233
234 merged_run_data->merge(*worker_run_data);
235 }
236
237 // Publish the merged run-level payload once, after all workers have contributed.
238 if (merged_run_data != nullptr) {
239 publish_run_data(merged_run_data);
240 }
241
242 if (gstreamer_run_map == nullptr) {
243 log->error(
245 "%1 gstreamer_map is null in master thread - cannot close connections.", FUNCTION_NAME);
246 }
247
248 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
249 log->info(
250 2, "%1 %2 for run %3 closing connection for gstreamer %4", FUNCTION_NAME, what_am_i, runNumber,
251 name);
252
253 if (gstreamer == nullptr) {
254 log->error(
255 gaction::ERR_STREAMERMAP_NOT_EXISTING, "Null GStreamer entry %1 in master thread", name);
256 }
257
258 if (!gstreamer->closeConnection()) {
259 log->error(1, "Failed to close connection for GStreamer %1 in master thread", name);
260 }
261 }
262 }
263
264}
265
266// Move this worker's completed run-data object into the protected static pool
267// so it can later be collected by the master thread.
268void GRunAction::stash_worker_run_data() {
269 if (run_data == nullptr) {
270 return;
271 }
272
273 std::scoped_lock lock(completed_run_data_mutex);
274 completed_worker_run_data.emplace_back(std::move(run_data));
275}
276
277// Extract and clear the protected pool of completed worker run-data objects.
278auto GRunAction::take_completed_worker_run_data() -> CompletedRunData {
279 std::scoped_lock lock(completed_run_data_mutex);
280
281 auto result = std::move(completed_worker_run_data);
282 completed_worker_run_data.clear();
283
284 return result;
285}
286
287
288// Publish the merged run-level payload to every configured master-side run streamer.
289void GRunAction::publish_run_data(const std::shared_ptr<GRunDataCollection> &run_data_collaction) const {
290 if (run_data_collaction == nullptr) {
291 log->error(
292 gaction::ERR_GRUNACTION_NOT_EXISTING, "%1 run_data is null - cannot publish merged run data.",
293 FUNCTION_NAME);
294 }
295
296 if (gstreamer_run_map == nullptr) {
297 log->error(
299 "%1 no run streamer map available - run data will not be published.", FUNCTION_NAME);
300 }
301
302 // Normalize once, before publishing: normalize_run_data() mutates run_data_collaction
303 // in place and is NOT idempotent, so running it per streamer would divide the run
304 // observables by events_processed once for every configured run streamer.
305 normalize_run_data(run_data_collaction);
306
307 for (const auto &[name, gstreamer]: *gstreamer_run_map) {
308 if (gstreamer == nullptr) {
309 log->error(
310 gaction::ERR_STREAMERMAP_NOT_EXISTING, "%1 null gstreamer instance for run streamer %2",
311 FUNCTION_NAME, name);
312 }
313
314 gstreamer->publishRunData(run_data_collaction);
315 }
316}
317
318
319void GRunAction::normalize_run_data(const std::shared_ptr<GRunDataCollection> &run_data_collaction) const {
320 if (run_data_collaction == nullptr) {
321 log->error(
322 gaction::ERR_GRUNACTION_NOT_EXISTING, "%1 run_data_collaction is null - cannot normalize run data.",
323 FUNCTION_NAME);
324 }
325
326 const int events_processed = run_data_collaction->get_events_processed();
327 if (events_processed <= 0) {
328 log->warning("%1 events_processed is %2 - skipping normalization.", FUNCTION_NAME, events_processed);
329 return;
330 }
331
332 const double norm = static_cast<double>(events_processed);
333
334 for (auto &[sdName, dataCollection]: run_data_collaction->getMutableDataCollectionMap()) {
335 if (dataCollection == nullptr) {
336 log->warning("%1 detector %2 has null data collection - skipping.", FUNCTION_NAME, sdName);
337 continue;
338 }
339
340 auto &digitizedData = dataCollection->getMutableDigitizedData();
341 if (digitizedData.empty() || digitizedData.front() == nullptr) {
342 continue;
343 }
344
345 auto &digitized = digitizedData.front();
346 // we are in a const method so we can't loop directky over to_normalize[sdName]
347 // cause this call could modify the map!
348
349 auto it = to_normalize.find(sdName);
350 if (it != to_normalize.end()) {
351 for (const auto &varName: it->second) {
352 const auto intVars = digitized->getIntObservablesMap(0);
353 const auto intIt = intVars.find(varName);
354 if (intIt != intVars.end()) {
355 digitized->includeVariable(varName, static_cast<double>(intIt->second) / norm);
356 continue;
357 }
358
359 const auto dblVars = digitized->getDblObservablesMap(0);
360 const auto dblIt = dblVars.find(varName);
361 if (dblIt != dblVars.end()) {
362 digitized->includeVariable(varName, dblIt->second / norm);
363 }
364 }
365 }
366 }
367}
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, std::shared_ptr< GSROFactory > sro_factory=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:29
Declares GRun, the thread-local run container used by the GEMC actions module.
std::uint64_t GSROEventId
Defines error codes used by the GEMC actions module.
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)
constexpr int ERR_GDIGIMAP_NOT_EXISTING
constexpr int ERR_STREAMERMAP_NOT_EXISTING
constexpr int ERR_GRUNACTION_NOT_EXISTING
constexpr int ERR_PUBLISH_ERROR
constexpr char RST[]
constexpr char KGRN[]