14.2. EventAction
Table of Contents
Beginning of an event
The event action is the user hook that lets you perform tasks at the start and at the end of every event. In Geant4 this is implemented by creating a class that derives from G4UserEventAction and overriding its two main virtual methods, BeginOfEventAction(const G4Event event) and EndOfEventAction(const G4Event event). The run manager calls these methods automatically for each event, after the primary particles have been generated and before or after tracking has taken place.
At the beginning of an event you normally use BeginOfEventAction to reset or initialise any per event data that you plan to accumulate during stepping or tracking. Typical examples are energy deposited in one detector, hit counters, or temporary vectors where you store interaction positions. Since events are processed independently, these quantities must be cleared to zero or to an empty state at the start of every event, otherwise values from previous events will carry over and corrupt your results.
A common pattern is to keep event level variables as data members of your EventAction class and provide setter or adder methods that can be called from other user actions, usually from SteppingAction. For example, you might define a member variable G4double fEdep = 0.; and a method AddEdep(G4double edep) that simply does fEdep += edep;. In BeginOfEventAction you then set fEdep = 0.; so that each event starts with a clean accumulator. The stepping action can then call eventAction->AddEdep(stepEdep); on every step that deposits energy.
To know which event you are working on, you can use the event ID. Inside BeginOfEventAction, the pointer event gives access to the G4Event object, and you can retrieve the ID with
$$
\text{G4int } id = event\text{->GetEventID();}
$$
If you want to perform periodic logging or debugging, you can print a message only for selected events, for example for every 1000th event using if (id % 1000 == 0) to avoid flooding the console.
The event object also owns the primary vertex and primary particle information. While the details of primary generation are handled elsewhere, in BeginOfEventAction you can inspect the primary particles, for example to record their initial positions or energies, or to tag special events. You can access the number of primary vertices with event->GetNumberOfPrimaryVertex() and then loop over them if needed.
When you use multithreading, each worker thread has its own instance of the event action. This means that per event variables held as data members of your EventAction class are naturally thread local and do not require explicit synchronisation, as long as you do not share them between threads. Global objects must not be modified here unless you use proper thread protection.
There are also connections between event action and analysis. You typically create or book histograms and ntuples in run level actions, but in BeginOfEventAction you decide what will be filled later in the event. For example, you may reset indices or flags that control whether an event contributes to a particular histogram. It is important to keep BeginOfEventAction light and fast, because it is executed for every event and can become a performance bottleneck if it does heavy I/O or complex geometry queries.
Always reset all per event accumulators in BeginOfEventAction, and never perform heavy disk I/O or expensive operations here for every event.
End of an event
The main purpose of EndOfEventAction(const G4Event* event) is to collect everything that happened during the event and turn it into final, event level results. This is where you typically decide what to record in your analysis output. Quantities that were accumulated during stepping, such as total energy deposition in a detector, time of a first hit, or number of secondary particles, are read here and used to fill histograms, ntuples, or other output structures.
The typical workflow is: at the beginning of the event you reset accumulators, during tracking your stepping and sensitive detector classes add information to these accumulators or to hit collections, and at the end of the event you read the final values and send them to the analysis manager. For example, if fEdep holds the total energy deposited in a volume in this event, then in EndOfEventAction you can write
$$
\text{analysisManager->FillH1(hid, fEdep);}
$$
to fill a one dimensional histogram with one entry per event, or store it as a column in an ntuple. It is often useful to apply event level cuts here, for example to record data only if fEdep is above a threshold or if a coincidence condition has been satisfied. This keeps output files smaller and makes later analysis more efficient.
The G4Event object provides access to hit collections produced by sensitive detectors. Each event has a G4HCofThisEvent, the hit collection of this event, which you can obtain with
$$
\text{G4HCofThisEvent* hce = event->GetHCofThisEvent();}
$$
From this container you can retrieve individual hit collections using their collection IDs, which you usually cache once during initialisation. You can then loop over the hits, sum energies, find earliest times, or extract positions. This is a common pattern in detector simulations where you want to convert many individual hits into a single event observable such as total energy in a crystal or the position reconstructed from multiple sensor hits.
If you are using the Geant4 analysis system, EndOfEventAction is the correct place to call the methods of G4AnalysisManager to fill histograms and ntuples with per event variables like energy, position, time, or particle identifiers. You should avoid creating or closing files here, because those are run level tasks and belong to RunAction.
Another common task in EndOfEventAction is logging and debugging. Using the event ID you can print summaries of interesting events such as those that deposit exceptionally high energy, generate many secondaries, or satisfy trigger conditions. However, you must be careful not to print every event, because that will dramatically slow down your simulation and produce huge logs.
In parallel or multithreaded runs, each worker thread executes EndOfEventAction independently. You must ensure that any objects you modify are either thread local or are accessed in a thread safe way. The Geant4 analysis manager already handles per thread data and merges results at the end of the run, so you can safely call its fill methods here without extra protection.
Finally, the event action can interact with other user actions through shared pointers or references. For example, your stepping action may call methods on the event action to update flags or counters, then EndOfEventAction inspects those and decides what to record. This pattern keeps per step logic simple and centralises the final event decision making in one place.
Use EndOfEventAction to transform step level and hit level data into final event observables, then record them. Avoid opening or closing analysis files or printing information for every event.
Views: 8
KAHIBARO