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