Geant4 integration example¶
These small examples demonstrate how to offload tracks to Celeritas in a serial or multithreaded environment using:
A concrete G4UserTrackingAction user action class
A concrete G4VFastSimulationModel
A concrete G4VTrackingManager
The Geant4 interface is the only relevant part of Celeritas here. The key components are global SetupOptions and SharedParams, coupled to thread-local SimpleOffload and LocalTransporter. The SimpleOffload provides all of the core methods needed to integrate into a Geant4 application’s UserActions or other user classes.
CMake infrastructure¶
project(CeleritasAccelExample VERSION 0.0.1 LANGUAGES CXX)
cmake_policy(VERSION 3.12...3.22)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/..")
find_package(Celeritas 0.5 REQUIRED)
find_package(Geant4 REQUIRED)
if(NOT CELERITAS_USE_Geant4)
message(SEND_ERROR "This example requires Geant4 support "
"to be enabled in Celeritas")
endif()
include(CudaRdcUtils)
add_executable(simple-offload simple-offload.cc)
cuda_rdc_target_link_libraries(simple-offload
Celeritas::accel
${Geant4_LIBRARIES}
)
if(Geant4_VERSION VERSION_LESS 11.1)
message(WARNING "Fast simulation offload requires Geant4 11.1 or higher")
endif()
add_executable(fastsim-offload fastsim-offload.cc)
cuda_rdc_target_link_libraries(fastsim-offload
Celeritas::accel
${Geant4_LIBRARIES}
)
if(Geant4_VERSION VERSION_LESS 11.0)
message(WARNING "G4VTrackingManager offload requires Geant4 11.0 or higher")
else()
add_executable(trackingmanager-offload trackingmanager-offload.cc)
cuda_rdc_target_link_libraries(trackingmanager-offload
Celeritas::accel
${Geant4_LIBRARIES}
)
endif()
Main Executables¶
The executables are less robust (and minimally documented) versions of the Integrated Geant4 application (celer-g4) app. The use of global variables rather than shared pointers is easier to implement but may be more problematic with experiment frameworks or other apps that use a task-based runner.
Offload using a concrete G4UserTrackingAction¶
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <FTFP_BERT.hh>
#include <G4Box.hh>
#include <G4Electron.hh>
#include <G4Gamma.hh>
#include <G4LogicalVolume.hh>
#include <G4Material.hh>
#include <G4PVPlacement.hh>
#include <G4ParticleDefinition.hh>
#include <G4ParticleGun.hh>
#include <G4ParticleTable.hh>
#include <G4Positron.hh>
#include <G4SystemOfUnits.hh>
#include <G4Threading.hh>
#include <G4ThreeVector.hh>
#include <G4Track.hh>
#include <G4TrackStatus.hh>
#include <G4Types.hh>
#include <G4UserEventAction.hh>
#include <G4UserRunAction.hh>
#include <G4UserTrackingAction.hh>
#include <G4VUserActionInitialization.hh>
#include <G4VUserDetectorConstruction.hh>
#include <G4VUserPrimaryGeneratorAction.hh>
#include <G4Version.hh>
#if G4VERSION_NUMBER >= 1100
# include <G4RunManagerFactory.hh>
#else
# include <G4MTRunManager.hh>
#endif
#include <accel/AlongStepFactory.hh>
#include <accel/LocalTransporter.hh>
#include <accel/SetupOptions.hh>
#include <accel/SharedParams.hh>
#include <accel/SimpleOffload.hh>
#include <corecel/Macros.hh>
#include <corecel/io/Logger.hh>
namespace
{
//---------------------------------------------------------------------------//
// Global shared setup options
celeritas::SetupOptions setup_options;
// Shared data and GPU setup
celeritas::SharedParams shared_params;
// Thread-local transporter
G4ThreadLocal celeritas::LocalTransporter local_transporter;
// Simple interface to running celeritas
G4ThreadLocal celeritas::SimpleOffload simple_offload;
//---------------------------------------------------------------------------//
class DetectorConstruction final : public G4VUserDetectorConstruction
{
public:
DetectorConstruction()
: aluminum_{new G4Material{
"Aluminium", 13., 26.98 * g / mole, 2.700 * g / cm3}}
{
setup_options.make_along_step = celeritas::UniformAlongStepFactory();
setup_options.geometry_output_file = "simple-example.gdml";
}
G4VPhysicalVolume* Construct() final
{
CELER_LOG_LOCAL(status) << "Setting up detector";
auto* box = new G4Box("world", 1000 * cm, 1000 * cm, 1000 * cm);
auto* lv = new G4LogicalVolume(box, aluminum_, "world");
auto* pv = new G4PVPlacement(
0, G4ThreeVector{}, lv, "world", nullptr, false, 0);
return pv;
}
void ConstructSDandField() final {}
private:
G4Material* aluminum_;
};
//---------------------------------------------------------------------------//
class PrimaryGeneratorAction final : public G4VUserPrimaryGeneratorAction
{
public:
PrimaryGeneratorAction()
{
auto g4particle_def
= G4ParticleTable::GetParticleTable()->FindParticle(2112);
gun_.SetParticleDefinition(g4particle_def);
gun_.SetParticleEnergy(100 * GeV);
gun_.SetParticlePosition(G4ThreeVector{0, 0, 0}); // origin
gun_.SetParticleMomentumDirection(G4ThreeVector{1, 0, 0}); // +x
}
// Generate 100 GeV neutrons
void GeneratePrimaries(G4Event* event) final
{
CELER_LOG_LOCAL(status) << "Generating primaries";
gun_.GeneratePrimaryVertex(event);
}
private:
G4ParticleGun gun_;
};
//---------------------------------------------------------------------------//
class RunAction final : public G4UserRunAction
{
public:
void BeginOfRunAction(G4Run const* run) final
{
simple_offload.BeginOfRunAction(run);
}
void EndOfRunAction(G4Run const* run) final
{
simple_offload.EndOfRunAction(run);
}
};
//---------------------------------------------------------------------------//
class EventAction final : public G4UserEventAction
{
public:
void BeginOfEventAction(G4Event const* event) final
{
simple_offload.BeginOfEventAction(event);
}
void EndOfEventAction(G4Event const* event) final
{
simple_offload.EndOfEventAction(event);
}
};
//---------------------------------------------------------------------------//
class TrackingAction final : public G4UserTrackingAction
{
void PreUserTrackingAction(G4Track const* track) final
{
simple_offload.PreUserTrackingAction(const_cast<G4Track*>(track));
}
};
//---------------------------------------------------------------------------//
class ActionInitialization final : public G4VUserActionInitialization
{
public:
void BuildForMaster() const final
{
simple_offload.BuildForMaster(&setup_options, &shared_params);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new RunAction{});
}
void Build() const final
{
simple_offload.Build(
&setup_options, &shared_params, &local_transporter);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new PrimaryGeneratorAction{});
this->SetUserAction(new RunAction{});
this->SetUserAction(new EventAction{});
this->SetUserAction(new TrackingAction{});
}
};
//---------------------------------------------------------------------------//
} // namespace
int main()
{
auto run_manager = [] {
#if G4VERSION_NUMBER >= 1100
return std::unique_ptr<G4RunManager>{
G4RunManagerFactory::CreateRunManager()};
#else
return std::make_unique<G4RunManager>();
#endif
}();
run_manager->SetUserInitialization(new DetectorConstruction{});
run_manager->SetUserInitialization(new FTFP_BERT{/* verbosity = */ 0});
run_manager->SetUserInitialization(new ActionInitialization());
// NOTE: these numbers are appropriate for CPU execution
setup_options.max_num_tracks = 1024;
setup_options.initializer_capacity = 1024 * 128;
// This parameter will eventually be removed
setup_options.max_num_events = 1024;
// Celeritas does not support EmStandard MSC physics above 100 MeV
setup_options.ignore_processes = {"CoulombScat"};
if (G4VERSION_NUMBER >= 1110)
{
// Default Rayleigh scattering 'MinKinEnergyPrim' is no longer
// consistent
setup_options.ignore_processes.push_back("Rayl");
}
run_manager->Initialize();
run_manager->BeamOn(1);
return 0;
}
Offload using a concrete G4VFastSimulationModel¶
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <FTFP_BERT.hh>
#include <G4Box.hh>
#include <G4Electron.hh>
#include <G4FastSimulationPhysics.hh>
#include <G4Gamma.hh>
#include <G4LogicalVolume.hh>
#include <G4Material.hh>
#include <G4PVPlacement.hh>
#include <G4ParticleDefinition.hh>
#include <G4ParticleGun.hh>
#include <G4ParticleTable.hh>
#include <G4Positron.hh>
#include <G4Region.hh>
#include <G4RegionStore.hh>
#include <G4SystemOfUnits.hh>
#include <G4Threading.hh>
#include <G4ThreeVector.hh>
#include <G4Track.hh>
#include <G4TrackStatus.hh>
#include <G4Types.hh>
#include <G4UserEventAction.hh>
#include <G4UserRunAction.hh>
#include <G4UserTrackingAction.hh>
#include <G4VUserActionInitialization.hh>
#include <G4VUserDetectorConstruction.hh>
#include <G4VUserPrimaryGeneratorAction.hh>
#include <G4Version.hh>
#if G4VERSION_NUMBER >= 1100
# include <G4RunManagerFactory.hh>
#else
# include <G4MTRunManager.hh>
#endif
#include <accel/AlongStepFactory.hh>
#include <accel/FastSimulationOffload.hh>
#include <accel/LocalTransporter.hh>
#include <accel/SetupOptions.hh>
#include <accel/SharedParams.hh>
#include <accel/SimpleOffload.hh>
#include <corecel/Macros.hh>
#include <corecel/io/Logger.hh>
namespace
{
//---------------------------------------------------------------------------//
// Global shared setup options
celeritas::SetupOptions setup_options;
// Shared data and GPU setup
celeritas::SharedParams shared_params;
// Thread-local transporter
G4ThreadLocal celeritas::LocalTransporter local_transporter;
// Simple interface to running celeritas
G4ThreadLocal celeritas::SimpleOffload simple_offload;
//---------------------------------------------------------------------------//
class DetectorConstruction final : public G4VUserDetectorConstruction
{
public:
DetectorConstruction()
: aluminum_{new G4Material{
"Aluminium", 13., 26.98 * g / mole, 2.700 * g / cm3}}
{
setup_options.make_along_step = celeritas::UniformAlongStepFactory();
}
G4VPhysicalVolume* Construct() final
{
CELER_LOG_LOCAL(status) << "Setting up detector";
auto* box = new G4Box("world", 1000 * cm, 1000 * cm, 1000 * cm);
auto* lv = new G4LogicalVolume(box, aluminum_, "world");
auto* pv = new G4PVPlacement(
0, G4ThreeVector{}, lv, "world", nullptr, false, 0);
return pv;
}
void ConstructSDandField() final
{
CELER_LOG_LOCAL(status) << "Creating FastSimulationOffload for "
"default region";
G4Region* default_region = G4RegionStore::GetInstance()->GetRegion(
"DefaultRegionForTheWorld");
// Underlying GVFastSimulationModel constructor handles ownership, so
// we can ignore the returned pointer...
new celeritas::FastSimulationOffload("accel::FastSimulationOffload",
default_region,
&shared_params,
&local_transporter);
}
private:
G4Material* aluminum_;
};
//---------------------------------------------------------------------------//
class PrimaryGeneratorAction final : public G4VUserPrimaryGeneratorAction
{
public:
PrimaryGeneratorAction()
{
auto g4particle_def
= G4ParticleTable::GetParticleTable()->FindParticle(2112);
gun_.SetParticleDefinition(g4particle_def);
gun_.SetParticleEnergy(100 * GeV);
gun_.SetParticlePosition(G4ThreeVector{0, 0, 0}); // origin
gun_.SetParticleMomentumDirection(G4ThreeVector{1, 0, 0}); // +x
}
// Generate 100 GeV neutrons
void GeneratePrimaries(G4Event* event) final
{
CELER_LOG_LOCAL(status) << "Generating primaries";
gun_.GeneratePrimaryVertex(event);
}
private:
G4ParticleGun gun_;
};
//---------------------------------------------------------------------------//
class RunAction final : public G4UserRunAction
{
public:
void BeginOfRunAction(G4Run const* run) final
{
simple_offload.BeginOfRunAction(run);
}
void EndOfRunAction(G4Run const* run) final
{
simple_offload.EndOfRunAction(run);
}
};
//---------------------------------------------------------------------------//
class EventAction final : public G4UserEventAction
{
public:
void BeginOfEventAction(G4Event const* event) final
{
simple_offload.BeginOfEventAction(event);
}
};
//---------------------------------------------------------------------------//
class ActionInitialization final : public G4VUserActionInitialization
{
public:
void BuildForMaster() const final
{
simple_offload.BuildForMaster(&setup_options, &shared_params);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new RunAction{});
}
void Build() const final
{
simple_offload.Build(
&setup_options, &shared_params, &local_transporter);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new PrimaryGeneratorAction{});
this->SetUserAction(new RunAction{});
this->SetUserAction(new EventAction{});
}
};
//---------------------------------------------------------------------------//
} // namespace
int main()
{
auto run_manager = [] {
#if G4VERSION_NUMBER >= 1100
return std::unique_ptr<G4RunManager>{
G4RunManagerFactory::CreateRunManager()};
#else
return std::make_unique<G4RunManager>();
#endif
}();
run_manager->SetUserInitialization(new DetectorConstruction{});
// We must add support for fast simulation models to the Physics List
// NOTE: we have to explicitly name the particles and this should be a
// superset of what Celeritas can offload
auto physics_list = new FTFP_BERT{/* verbosity = */ 0};
auto fast_physics = new G4FastSimulationPhysics();
fast_physics->ActivateFastSimulation("e-");
fast_physics->ActivateFastSimulation("e+");
fast_physics->ActivateFastSimulation("gamma");
physics_list->RegisterPhysics(fast_physics);
run_manager->SetUserInitialization(physics_list);
run_manager->SetUserInitialization(new ActionInitialization());
// NOTE: these numbers are appropriate for CPU execution
setup_options.max_num_tracks = 1024;
setup_options.initializer_capacity = 1024 * 128;
// Celeritas does not support EmStandard MSC physics above 100 MeV
setup_options.ignore_processes = {"CoulombScat"};
if (G4VERSION_NUMBER >= 1110)
{
// Default Rayleigh scattering 'MinKinEnergyPrim' is no longer
// consistent
setup_options.ignore_processes.push_back("Rayl");
}
run_manager->Initialize();
run_manager->BeamOn(1);
return 0;
}
Offload using a concrete G4VTrackingManager¶
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <FTFP_BERT.hh>
#include <G4Box.hh>
#include <G4Electron.hh>
#include <G4EmStandardPhysics.hh>
#include <G4Gamma.hh>
#include <G4LogicalVolume.hh>
#include <G4Material.hh>
#include <G4PVPlacement.hh>
#include <G4ParticleDefinition.hh>
#include <G4ParticleGun.hh>
#include <G4ParticleTable.hh>
#include <G4Positron.hh>
#include <G4Region.hh>
#include <G4RegionStore.hh>
#include <G4SystemOfUnits.hh>
#include <G4Threading.hh>
#include <G4ThreeVector.hh>
#include <G4Track.hh>
#include <G4TrackStatus.hh>
#include <G4Types.hh>
#include <G4UserEventAction.hh>
#include <G4UserRunAction.hh>
#include <G4UserTrackingAction.hh>
#include <G4VUserActionInitialization.hh>
#include <G4VUserDetectorConstruction.hh>
#include <G4VUserPrimaryGeneratorAction.hh>
#include <G4Version.hh>
#if G4VERSION_NUMBER >= 1100
# include <G4RunManagerFactory.hh>
#else
# include <G4MTRunManager.hh>
#endif
#include <accel/AlongStepFactory.hh>
#include <accel/LocalTransporter.hh>
#include <accel/SetupOptions.hh>
#include <accel/SharedParams.hh>
#include <accel/SimpleOffload.hh>
#include <accel/TrackingManagerOffload.hh>
#include <corecel/Macros.hh>
#include <corecel/io/Logger.hh>
namespace
{
//---------------------------------------------------------------------------//
// Global shared setup options
celeritas::SetupOptions setup_options;
// Shared data and GPU setup
celeritas::SharedParams shared_params;
// Thread-local transporter
G4ThreadLocal celeritas::LocalTransporter local_transporter;
// Simple interface to running celeritas
G4ThreadLocal celeritas::SimpleOffload simple_offload;
//---------------------------------------------------------------------------//
class DetectorConstruction final : public G4VUserDetectorConstruction
{
public:
DetectorConstruction()
: aluminum_{new G4Material{
"Aluminium", 13., 26.98 * g / mole, 2.700 * g / cm3}}
{
setup_options.make_along_step = celeritas::UniformAlongStepFactory();
}
G4VPhysicalVolume* Construct() final
{
CELER_LOG_LOCAL(status) << "Setting up detector";
auto* box = new G4Box("world", 1000 * cm, 1000 * cm, 1000 * cm);
auto* lv = new G4LogicalVolume(box, aluminum_, "world");
auto* pv = new G4PVPlacement(
0, G4ThreeVector{}, lv, "world", nullptr, false, 0);
return pv;
}
private:
G4Material* aluminum_;
};
//---------------------------------------------------------------------------//
class EMPhysicsConstructor final : public G4EmStandardPhysics
{
public:
using G4EmStandardPhysics::G4EmStandardPhysics;
void ConstructProcess() override
{
CELER_LOG_LOCAL(status) << "Setting up tracking manager offload";
G4EmStandardPhysics::ConstructProcess();
// Add Celeritas tracking manager to electron, positron, gamma.
auto* celer_tracking = new celeritas::TrackingManagerOffload(
&shared_params, &local_transporter);
G4Electron::Definition()->SetTrackingManager(celer_tracking);
G4Positron::Definition()->SetTrackingManager(celer_tracking);
G4Gamma::Definition()->SetTrackingManager(celer_tracking);
}
};
//---------------------------------------------------------------------------//
class PrimaryGeneratorAction final : public G4VUserPrimaryGeneratorAction
{
public:
PrimaryGeneratorAction()
{
auto g4particle_def
= G4ParticleTable::GetParticleTable()->FindParticle(2112);
gun_.SetParticleDefinition(g4particle_def);
gun_.SetParticleEnergy(100 * GeV);
gun_.SetParticlePosition(G4ThreeVector{0, 0, 0}); // origin
gun_.SetParticleMomentumDirection(G4ThreeVector{1, 0, 0}); // +x
}
// Generate 100 GeV neutrons
void GeneratePrimaries(G4Event* event) final
{
CELER_LOG_LOCAL(status) << "Generating primaries";
gun_.GeneratePrimaryVertex(event);
}
private:
G4ParticleGun gun_;
};
//---------------------------------------------------------------------------//
class RunAction final : public G4UserRunAction
{
public:
void BeginOfRunAction(G4Run const* run) final
{
simple_offload.BeginOfRunAction(run);
}
void EndOfRunAction(G4Run const* run) final
{
simple_offload.EndOfRunAction(run);
}
};
//---------------------------------------------------------------------------//
class EventAction final : public G4UserEventAction
{
public:
void BeginOfEventAction(G4Event const* event) final
{
simple_offload.BeginOfEventAction(event);
}
};
//---------------------------------------------------------------------------//
class ActionInitialization final : public G4VUserActionInitialization
{
public:
void BuildForMaster() const final
{
simple_offload.BuildForMaster(&setup_options, &shared_params);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new RunAction{});
}
void Build() const final
{
simple_offload.Build(
&setup_options, &shared_params, &local_transporter);
CELER_LOG_LOCAL(status) << "Constructing user actions";
this->SetUserAction(new PrimaryGeneratorAction{});
this->SetUserAction(new RunAction{});
this->SetUserAction(new EventAction{});
}
};
//---------------------------------------------------------------------------//
} // namespace
int main()
{
auto run_manager = [] {
#if G4VERSION_NUMBER >= 1100
return std::unique_ptr<G4RunManager>{
G4RunManagerFactory::CreateRunManager()};
#else
return std::make_unique<G4RunManager>();
#endif
}();
run_manager->SetUserInitialization(new DetectorConstruction{});
// Use FTFP_BERT, but replace EM constructor with our own that
// overrides ConstructProcess to use Celeritas tracking for e-/e+/g
auto physics_list = new FTFP_BERT{/* verbosity = */ 0};
physics_list->ReplacePhysics(new EMPhysicsConstructor);
run_manager->SetUserInitialization(physics_list);
run_manager->SetUserInitialization(new ActionInitialization());
// NOTE: these numbers are appropriate for CPU execution
setup_options.max_num_tracks = 1024;
setup_options.initializer_capacity = 1024 * 128;
// This parameter will eventually be removed
setup_options.max_num_events = 1024;
// Celeritas does not support EmStandard MSC physics above 100 MeV
setup_options.ignore_processes = {"CoulombScat"};
if (G4VERSION_NUMBER >= 1110)
{
// Default Rayleigh scattering 'MinKinEnergyPrim' is no longer
// consistent
setup_options.ignore_processes.push_back("Rayl");
}
run_manager->Initialize();
run_manager->BeamOn(1);
return 0;
}