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