14.6 ActionInitialization
Table of Contents
Registering user actions
In a complete Geant4 application you usually define several user action classes, such as RunAction, EventAction, SteppingAction, TrackingAction, and StackingAction. The ActionInitialization class is the central place where all these actions are created and registered with the Geant4 kernel. Without this registration, your user actions will never be called, even if the classes compile correctly.
The base class for this mechanism is G4VUserActionInitialization. You create your own class that derives from it, then implement specific methods where you instantiate and attach your actions to the run manager. The run manager calls these methods at the appropriate time, both in sequential and multithreaded runs.
A minimal custom action initialization class looks conceptually like this:
// MyActionInitialization.hh
#include "G4VUserActionInitialization.hh"
class MyActionInitialization : public G4VUserActionInitialization
{
public:
MyActionInitialization();
~MyActionInitialization() override;
void Build() const override;
void BuildForMaster() const override;
};
// MyActionInitialization.cc
#include "MyActionInitialization.hh"
#include "MyPrimaryGeneratorAction.hh"
#include "MyRunAction.hh"
#include "MyEventAction.hh"
#include "MySteppingAction.hh"
MyActionInitialization::MyActionInitialization() {}
MyActionInitialization::~MyActionInitialization() {}
void MyActionInitialization::Build() const
{
// Called for each worker thread (or once in sequential mode)
auto primary = new MyPrimaryGeneratorAction();
SetUserAction(primary);
auto runAction = new MyRunAction();
SetUserAction(runAction);
auto eventAction = new MyEventAction(runAction);
SetUserAction(eventAction);
auto steppingAction = new MySteppingAction(eventAction);
SetUserAction(steppingAction);
}
void MyActionInitialization::BuildForMaster() const
{
// Called only in the master thread in multithreaded mode
auto runAction = new MyRunAction();
SetUserAction(runAction);
}
The exact implementation details of the user action classes belong to other chapters. Here the focus is on how you connect them to the simulation through ActionInitialization.
The Build() method is where you register all actions that need to be active in each worker thread. In sequential (single thread) mode, Build() is still called, so you always define at least this method. In multithreaded mode, Geant4 also calls BuildForMaster() once in the master thread, which is intended for actions that manage quantities across the whole run, such as summary statistics that do not depend on per-event data from a specific worker.
When you use the run manager in your main() function, you pass an instance of your action initialization class:
auto runManager = new G4RunManager();
runManager->SetUserInitialization(new MyDetectorConstruction());
runManager->SetUserInitialization(new MyPhysicsList());
runManager->SetUserInitialization(new MyActionInitialization());
After this, when you call /run/initialize (usually indirectly through the macro system), the run manager will invoke BuildForMaster() in the master thread, then Build() in each worker, and your actions will be in place.
The functions SetUserAction(...) are inherited from G4VUserActionInitialization and can accept different action pointer types, such as G4UserRunAction, G4UserEventAction, G4UserSteppingAction*, and so on. Each call tells the kernel, "use this object to handle the corresponding part of the simulation life cycle."
There are some important patterns to keep in mind when registering user actions.
Always create user action objects with new inside Build() or BuildForMaster() and pass the raw pointer to SetUserAction(). Do not create them as local variables that will go out of scope, and do not delete them yourself. Geant4 takes ownership and will delete them at the end of the run.
Frequently, user actions depend on each other. For example, a SteppingAction might need to access event-level accumulators defined in EventAction. In that case, you typically pass pointers between them when constructing:
auto runAction = new MyRunAction();
auto eventAction = new MyEventAction(runAction);
auto steppingAction = new MySteppingAction(eventAction);
SetUserAction(runAction);
SetUserAction(eventAction);
SetUserAction(steppingAction);
This pattern lets your actions share information without using global variables. The same idea applies for analysis classes or managers that your actions might use. These shared objects should be created and wired up inside Build() or BuildForMaster() so that each thread has its own copy when needed.
In multithreaded simulations, BuildForMaster() is often simpler and only sets up a RunAction. The master does not handle events directly, so it does not need EventAction or SteppingAction. In sequential runs, BuildForMaster() is not used, and only Build() is relevant.
The order in which you call SetUserAction() does not matter to Geant4, but for readability and maintenance it is helpful to keep a consistent order such as primary generator, run action, event action, stepping action, tracking action, stacking action. This makes it easier to see at a glance which actions are part of your application and how they are connected.
By keeping all user action registration inside a dedicated ActionInitialization class, your main program remains uncluttered, and you can later modify, enable, or disable specific user actions by editing a single source file instead of chasing references through main() or other parts of the code.
Views: 9
KAHIBARO