actions
Loading...
Searching...
No Matches
gEventAction.cc
Go to the documentation of this file.
1#include "gEventAction.h"
3
4// geant4
5#include "G4Event.hh"
6#include "G4Threading.hh"
7
8// c++
9#include <string>
10
11// gemc
15
16// c++
17#include <algorithm>
18#include <cctype>
19#include <chrono>
20#include <sstream>
21#include <unordered_set>
22
23namespace {
24GGeneratedParticleBank make_generated_particle_bank(const GParticleRecordEvent& particles) {
26 bank.reserve(particles.size());
27
28 for (const auto& particle : particles) {
29 bank.push_back({
30 particle.name,
31 particle.pid,
32 particle.type,
33 particle.multiplicity,
34 particle.p,
35 particle.theta,
36 particle.phi,
37 particle.vx,
38 particle.vy,
39 particle.vz
40 });
41 }
42
43 return bank;
44}
45
46GAncestorBank make_ancestor_bank(const std::vector<GTrackRecord>& records) {
47 GAncestorBank bank;
48 bank.reserve(records.size());
49 for (const auto& record : records) {
50 bank.push_back({
51 record.pid,
52 record.tid,
53 record.mtid,
54 record.kinetic_energy,
55 record.momentum.x(),
56 record.momentum.y(),
57 record.momentum.z(),
58 record.vertex.x(),
59 record.vertex.y(),
60 record.vertex.z()
61 });
62 }
63 return bank;
64}
65
66bool scalar_bool_option_enabled(const std::shared_ptr<GOptions>& goptions, const std::string& name) {
67 std::string value = goptions->getScalarString(name);
68 std::transform(value.begin(), value.end(), value.begin(),
69 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
70 return value == "true" || value == "1" || value == "yes" || value == "on";
71}
72
73// Match a detector against an option containing comma- or whitespace-separated names.
74bool detector_is_listed(const std::shared_ptr<GOptions>& goptions, const std::string& option,
75 const std::string& detector) {
76 std::string detectors = goptions->getScalarString(option);
77 std::replace(detectors.begin(), detectors.end(), ',', ' ');
78
79 std::istringstream names(detectors);
80 std::string name;
81 while (names >> name) {
82 if (name == "all" || name == detector) return true;
83 }
84 return false;
85}
86
87// Convert a substring to a non-negative integer, returning false on any malformed input.
88bool to_non_negative_int(const std::string& text, int& out) {
89 if (text.empty()) return false;
90 size_t pos = 0;
91 int value;
92 try { value = std::stoi(text, &pos); }
93 catch (...) { return false; }
94 if (pos != text.size() || value < 0) return false;
95 out = value;
96 return true;
97}
98}
99
100
101// Construct the event action and keep access to shared configuration plus the
102// non-owning thread-local run action used during event finalization.
103GEventAction::GEventAction(const std::shared_ptr<GOptions>& gopt, GRunAction* run_a,
104 std::shared_ptr<GTrackProvenance> provenance) :
106 goptions(gopt),
107 run_action(run_a),
108 track_provenance(std::move(provenance)) {
109 const auto thread_id = G4Threading::G4GetThreadId();
110 const auto desc = "GEventAction " + std::to_string(thread_id);
111 log->debug(CONSTRUCTOR, FUNCTION_NAME, desc);
112 save_all_ancestors = goptions->getSwitch(SAVE_ALL_ANCESTORS_SWITCH);
113 save_original_track = goptions->getSwitch(SAVE_ORIGINAL_TRACK_SWITCH) || save_all_ancestors;
114
115 // Parse the log_every option of the form N or N-NTH. Anything malformed disables the
116 // feature and is reported once (from thread 0) to avoid duplicated warnings across workers.
117 std::string spec = goptions->getScalarString(LOG_EVERY_OPTION);
118 if (!spec.empty() && spec != UNINITIALIZEDSTRINGQUANTITY) {
119 const auto dash = spec.find('-');
120 std::string n_part = dash == std::string::npos ? spec : spec.substr(0, dash);
121 std::string nth_part = dash == std::string::npos ? std::string() : spec.substr(dash + 1);
122
123 // Effective worker-thread count, mirroring gemc::get_nthreads clamping (0 means all cores).
124 int nthreads = goptions->getScalarInt("nthreads");
125 const int ncores = G4Threading::G4GetNumberOfCores();
126 if (nthreads == 0 || nthreads > ncores) nthreads = ncores;
127
128 int n = 0;
129 if (!to_non_negative_int(n_part, n) || n == 0) {
130 if (thread_id <= 0)
131 log->warning("Ignoring invalid -", LOG_EVERY_OPTION, "=", spec,
132 " : N must be a positive integer.");
133 }
134 else if (dash != std::string::npos) {
135 int nth = 0;
136 if (!to_non_negative_int(nth_part, nth) || nth >= nthreads) {
137 if (thread_id <= 0)
138 log->warning("Ignoring invalid -", LOG_EVERY_OPTION, "=", spec,
139 " : thread id must be in [0, ", nthreads - 1, "].");
140 }
141 else {
142 log_every_n = n;
143 log_every_thread = nth;
144 }
145 }
146 else { log_every_n = n; }
147 }
148}
149
150// Print the periodic "Starting event" line, honoring the log module and optional thread filter.
151// The reported event number, count and rate are all per worker thread: each enabled thread logs
152// every N events it processes, showing its own 1-based event count and average rate (events / second).
153void GEventAction::log_event_start(int thread_id) {
154 if (log_every_n <= 0) return;
155 if (log_every_thread >= 0 && log_every_thread != thread_id) return;
156
157 // Anchor this thread's clock on its first counted event, then count this event.
158 const auto now = std::chrono::steady_clock::now();
159 if (log_events_seen == 0) { log_start_time = now; }
160 ++log_events_seen;
161
162 if (log_events_seen % log_every_n != 0) return;
163
164 const double elapsed_s = std::chrono::duration<double>(now - log_start_time).count();
165 const double rate = elapsed_s > 0.0 ? static_cast<double>(log_events_seen) / elapsed_s : 0.0;
166
167 // log_events_seen is this thread's own 1-based count, not the global Geant4 event id.
168 log->info(0, "Starting event n. ", log_events_seen, " in thread ", thread_id,
169 ". Average rate: ", rate, " events / second");
170}
171
172// Begin-of-event hook used mainly for tracing event and thread identifiers.
173void GEventAction::BeginOfEventAction([[maybe_unused]] const G4Event* event) {
174 const auto thread_id = G4Threading::G4GetThreadId();
175 const auto event_id = event->GetEventID();
176
177 log->debug(NORMAL, FUNCTION_NAME, " event id ", event_id, " in thread ", thread_id);
178 if (track_provenance != nullptr) { track_provenance->clear(); }
179
180 log_event_start(thread_id);
181}
182
183// Finalize the event by reading hit collections, digitizing them, routing the
184// resulting payload according to collection mode, and publishing event-mode output.
185void GEventAction::EndOfEventAction([[maybe_unused]] const G4Event* event) {
186 if (run_action == nullptr) {
188 " run_action is null - cannot access digitization routines or streamers.");
189 return;
190 }
191
192 // Count each processed event once, even when it produces no payload.
193 run_action->increment_run_events_processed();
194
195 const auto thread_id = G4Threading::G4GetThreadId();
196 const auto event_id = event->GetEventID();
197
198 auto gevent_header = std::make_unique<GEventHeader>(goptions, event_id, thread_id);
199 auto eventDataCollection = std::make_shared<GEventDataCollection>(goptions, std::move(gevent_header));
200 eventDataCollection->setGeneratedParticles(
201 make_generated_particle_bank(GPrimaryGeneratorAction::currentGeneratedParticleRecords()));
202 eventDataCollection->setGeneratedTrackedParticles(
204
205 auto* const hcs_this_event = event->GetHCofThisEvent();
206 if (hcs_this_event == nullptr) {
207 if (!eventDataCollection->getGeneratedParticles().empty() ||
208 !eventDataCollection->getGeneratedTrackedParticles().empty()) {
209 publish_event_data(eventDataCollection);
210 }
211 return;
212 }
213
214 const auto digi_map = run_action->get_digitization_routines_map();
215 if (digi_map == nullptr) {
217 " no digitization routines map available in thread ", thread_id);
218 return;
219 }
220
221 bool has_event_mode_payload = false;
222 bool has_run_mode_payload = false;
223 const bool also_reject_true_info = scalar_bool_option_enabled(goptions, "also_reject_true_info");
224 std::unordered_set<int> ancestor_track_ids;
225
226 // Loop over every hit collection produced during this event and dispatch each
227 // collection to the digitization routine registered under its collection name.
228 for (G4int hci = 0; hci < hcs_this_event->GetNumberOfCollections(); ++hci) {
229 auto* const this_ghc = static_cast<GHitsCollection*>(hcs_this_event->GetHC(hci));
230 if (this_ghc == nullptr) {
231 continue;
232 }
233
234 const std::string hcSDName = this_ghc->GetSDname();
235 const bool no_digitized = detector_is_listed(goptions, NO_DIGITIZED_OPTION, hcSDName);
236 const bool no_true_info = detector_is_listed(goptions, NO_TRUE_INFO_OPTION, hcSDName);
237
238 log->info(2, FUNCTION_NAME, " worker ", thread_id,
239 " for event number ", event_id,
240 " for collection number ", hci + 1,
241 " collection name: ", hcSDName);
242
243 // Resolve the digitization routine responsible for this collection.
244 const auto it = digi_map->find(hcSDName);
245 if (it == digi_map->end()) {
247 " no digitization routine registered for collection ", hcSDName,
248 " in thread ", thread_id);
249 continue;
250 }
251
252 const auto& digitization_routine = it->second;
253 if (digitization_routine == nullptr) {
255 " digitization routine is null for collection ", hcSDName,
256 " in thread ", thread_id);
257 continue;
258 }
259
260 const auto collection_mode = digitization_routine->collection_mode();
261 size_t accepted_hit_index = 0;
262
263 // Process all hits in the collection. Event-mode digitizers append to the
264 // event container, while run-mode digitizers append to the run container.
265 for (size_t hitIndex = 0; hitIndex < this_ghc->GetSize(); ++hitIndex) {
266
267 auto* const this_hit = static_cast<GHit*>(this_ghc->GetHit(hitIndex));
268 if (this_hit == nullptr) {
269 continue;
270 }
271 if (save_all_ancestors) {
272 const auto track_ids = this_hit->getTids();
273 ancestor_track_ids.insert(track_ids.begin(), track_ids.end());
274 }
275
276 auto digi_data = no_digitized ? nullptr : digitization_routine->digitizeHit(this_hit, hitIndex);
277 bool hit_accepted = digi_data != nullptr;
278
279 // Apply post-digitization threshold and efficiency policies. Plugins may declare a
280 // policy intrinsic or leave it controlled by -applyThresholds / -applyInefficiencies.
281 // Both are evaluated so the detector's random-number sequence remains stable.
282 if (hit_accepted) {
283 const bool skip_threshold = digitization_routine->apply_thresholds(this_hit, digi_data.get());
284 const bool skip_efficiency = digitization_routine->apply_efficiency(this_hit, digi_data.get());
285 if (skip_threshold || skip_efficiency) {
286 digi_data.reset();
287 hit_accepted = false;
288 }
289 }
290
291 if (collection_mode == CollectionMode::event) {
292 if (hit_accepted) {
293 ++accepted_hit_index;
294 digi_data->includeVariable("hitn", static_cast<int>(accepted_hit_index));
295 run_action->record_analysis_digitized(hcSDName, *digi_data);
296 eventDataCollection->addDetectorDigitizedData(hcSDName, std::move(digi_data));
297 has_event_mode_payload = true;
298 }
299 }
300 else if (collection_mode == CollectionMode::run) {
301 if (hit_accepted) {
302 run_action->record_analysis_digitized(hcSDName, *digi_data);
303 run_action->collect_event_data_collections(
304 hcSDName,
305 std::move(digi_data));
306 has_run_mode_payload = true;
307 }
308 }
309
310 // Event output already requires true information. In GUI analysis mode, request it for
311 // run-mode plugins too so their runtime-defined variables can be discovered without an API schema.
312 const bool collect_true = !no_true_info &&
313 (no_digitized || hit_accepted || !also_reject_true_info) &&
314 (collection_mode == CollectionMode::event || run_action->analysis_enabled());
315 if (collect_true) {
316 const size_t output_hit_index = collection_mode == CollectionMode::event && hit_accepted
317 ? accepted_hit_index : hitIndex + 1;
318 auto true_data = digitization_routine->collectTrueInformation(this_hit, output_hit_index);
319 if (save_original_track && track_provenance != nullptr && true_data != nullptr) {
320 const int tid = this_hit->getTid();
321 const G4ThreeVector op = track_provenance->originalTrackMomentum(tid);
322 true_data->includeVariable("otid", track_provenance->originalTrackId(tid));
323 true_data->includeVariable("opid", track_provenance->originalTrackPid(tid));
324 true_data->includeVariable("opx", op.getX());
325 true_data->includeVariable("opy", op.getY());
326 true_data->includeVariable("opz", op.getZ());
327 }
328 if (true_data != nullptr) { run_action->record_analysis_true(hcSDName, *true_data); }
329 if (collection_mode == CollectionMode::event) {
330 eventDataCollection->addDetectorTrueInfoData(hcSDName, std::move(true_data));
331 has_event_mode_payload = true;
332 }
333 }
334 }
335 }
336
337 if (save_all_ancestors && track_provenance != nullptr) {
338 eventDataCollection->setAncestors(
339 make_ancestor_bank(track_provenance->ancestorsForTracks(ancestor_track_ids)));
340 }
341
342 // Record whether this event contributed at least one run-mode payload entry.
343 if (has_run_mode_payload) {
344 run_action->increment_run_events_with_payload();
345 }
346
347 // Publish event-mode output once, after all collections have been processed.
348 if (has_event_mode_payload ||
349 !eventDataCollection->getAncestors().empty() ||
350 !eventDataCollection->getGeneratedParticles().empty() ||
351 !eventDataCollection->getGeneratedTrackedParticles().empty()) {
352 publish_event_data(eventDataCollection);
353 }
354}
355
356// Send the completed event-data object to every configured worker-thread streamer.
357void GEventAction::publish_event_data(const std::shared_ptr<GEventDataCollection>& event_data) const {
358 if (run_action == nullptr || event_data == nullptr) {
359 return;
360 }
361
362 if (!run_action->has_streamer_threads_map()) {
363 return;
364 }
365
366 const auto gstreamers_threads_map = run_action->get_streamer_threads_map();
367 if (gstreamers_threads_map == nullptr) {
368 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
369 " no thread streamer map available - event will not be published.");
370 return;
371 }
372
373 for (const auto& [name, gstreamer] : *gstreamers_threads_map) {
374 if (gstreamer == nullptr) {
375 log->error(ERR_STREAMERMAP_NOT_EXISTING, FUNCTION_NAME,
376 " null gstreamer instance for streamer ", name);
377 continue;
378 }
379
380 gstreamer->publishEventData(event_data);
381 }
382}
GBase(const std::shared_ptr< GOptions > &gopt, std::string logger_name="")
std::shared_ptr< GLogger > log
void EndOfEventAction(const G4Event *event) override
Called by Geant4 at the end of an event.
GEventAction(const std::shared_ptr< GOptions > &gopt, GRunAction *run_a, std::shared_ptr< GTrackProvenance > provenance=nullptr)
Constructs the event action.
void BeginOfEventAction(const G4Event *event) override
Called by Geant4 at the beginning of an event.
static const GParticleRecordEvent & currentGeneratedParticleRecords()
Returns the current event's full generated-particle records.
static const GParticleRecordEvent & currentGeneratedTrackedParticleRecords()
Returns the current event's Geant4-tracked generated-particle records.
Handles run begin/end callbacks and creates the thread-local GRun object.
Definition gRunAction.h:68
Declares GEventAction, the per-event processing action for the GEMC actions module.
constexpr const char * EVENTACTION_LOGGER
constexpr const char * LOG_EVERY_OPTION
Name of the option controlling periodic per-event log messages.
constexpr const char * SAVE_ORIGINAL_TRACK_SWITCH
constexpr const char * NO_DIGITIZED_OPTION
constexpr const char * NO_TRUE_INFO_OPTION
constexpr const char * SAVE_ALL_ANCESTORS_SWITCH
std::vector< GAncestorData > GAncestorBank
std::vector< GGeneratedParticleData > GGeneratedParticleBank
Declares GPrimaryGeneratorAction, the primary-particle generation action for 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
event
G4THitsCollection< GHit > GHitsCollection
#define FUNCTION_NAME
CONSTRUCTOR
NORMAL
std::vector< GParticleRecord > GParticleRecordEvent
#define UNINITIALIZEDSTRINGQUANTITY