2#include <gemc/gfactory/gfactory.h>
5#include "G4LogicalVolume.hh"
6#include "G4NistManager.hh"
7#include "G4PVPlacement.hh"
8#include "G4RunManagerFactory.hh"
9#include "G4MTRunManager.hh"
10#include "G4SDManager.hh"
11#include "G4VSensitiveDetector.hh"
12#include "G4VUserDetectorConstruction.hh"
23void require(
bool condition,
const char* message) {
24 if (!condition) {
throw std::runtime_error(message); }
27class Sensitive final :
public G4VSensitiveDetector {
29 Sensitive() : G4VSensitiveDetector(
"test_sro") { collectionName.insert(
"hits"); }
30 void Initialize(G4HCofThisEvent* collections)
override {
32 collections->AddHitsCollection(GetCollectionID(0), hits);
33 const auto*
event = G4RunManager::GetRunManager()->GetCurrentEvent();
34 if (
event->GetEventID() % 4) {
35 hits->insert(
new GHit(
nullptr));
36 hits->insert(
new GHit(
nullptr));
39 void EndOfEvent(G4HCofThisEvent*)
override {
40 auto* manager = G4RunManager::GetRunManager();
41 if (manager->GetCurrentRun()->GetRunID() == 1 && manager->GetCurrentEvent()->GetEventID() == 0) {
42 const_cast<G4Event*
>(manager->GetCurrentEvent())->SetEventAborted();
45 G4bool ProcessHits(G4Step*, G4TouchableHistory*)
override {
return false; }
48class Detector final :
public G4VUserDetectorConstruction {
50 explicit Detector(
bool no_hits) : empty(no_hits) {}
51 G4VPhysicalVolume* Construct()
override {
52 auto* material = G4NistManager::Instance()->FindOrBuildMaterial(
"G4_Galactic");
53 auto* solid =
new G4Box(
"world", 1 * CLHEP::m, 1 * CLHEP::m, 1 * CLHEP::m);
54 auto* logical =
new G4LogicalVolume(solid, material,
"world");
55 return new G4PVPlacement(
nullptr, {}, logical,
"world",
nullptr,
false, 0);
57 void ConstructSDandField()
override {
58 if (!empty) { G4SDManager::GetSDMpointer()->AddNewDetector(
new Sensitive); }
64void check_output(
const std::filesystem::path& base,
int run,
int count,
bool serial) {
65 std::set<std::size_t> workers;
66 for (
int crate : {1, 2}) {
67 std::ifstream file(base.string() +
"_r" + std::to_string(
run) +
"_c" + std::to_string(crate) +
".txt");
68 require(file.is_open(),
"Missing crate output file");
69 std::vector<std::tuple<int, int, int>> actual;
71 while (file >> tag && tag ==
"F") {
72 int event, sequence, time;
74 require(
static_cast<bool>(file >>
event >> sequence >> time >> worker),
"Malformed SRO frame");
75 actual.emplace_back(
event, sequence, time);
76 workers.insert(worker);
80 require(tag ==
"E" &&
static_cast<bool>(file >> reason >> safe_time),
"Missing SRO finalization");
81 std::vector<std::tuple<int, int, int>> expected;
85 expected.emplace_back(
event, crate - 1,
event * 10);
86 expected.emplace_back(
event, crate + 1,
event * 10);
90 require(actual == expected,
"Missing, duplicate, misrouted, or unordered SRO payloads");
91 require(reason == (
run == 1 ?
"interrupted" :
"completed"),
"Incorrect SRO end reason");
92 require(safe_time == (
run == 1 ? 0 : count * 10),
"Empty/aborted events produced incorrect safe time");
93 require(!(file >> tag),
"Unexpected records after SRO finalization");
95 if (
run == 0) { require(serial ? workers.size() == 1 : workers.size() > 1,
"Worker coverage missing"); }
99int main(
int argc,
char* argv[]) {
101 const auto scratch = std::filesystem::temp_directory_path() / (
"gemc-sro-" + std::to_string(getpid()));
102 require(std::filesystem::create_directory(scratch),
"Could not create unique SRO test directory");
103 const auto base = scratch /
"output";
107 definitions.defineOption(
GVariable(
"nthreads", 4,
"test workers"),
"test workers");
108 definitions.defineOption(
GVariable(
"also_reject_true_info",
"false",
"true information policy"),
109 "true information policy");
110 definitions.defineSwitch(
"sro_serial",
"Exercise sequential Geant4 execution");
111 definitions.defineSwitch(
"sro_empty",
"Exercise events without hit collections");
112 definitions.defineSwitch(
"sro_test_worker_failure",
"Inject a worker SRO failure");
113 std::vector<std::string> args(argv, argv + argc);
114 args.push_back(
"-gparticle=[{name: geantino, p: 1, theta: 0}]");
115 args.push_back(
"-gstreamer=[{format: sro, filename: '" + base.string() +
116 "', implementation: test_sro_implementation}]");
117 std::vector<char*> pointers;
118 for (
auto& argument : args) { pointers.push_back(argument.data()); }
119 auto options = std::make_shared<GOptions>(
120 static_cast<int>(pointers.size()), pointers.data(), definitions);
121 const bool serial = options->getSwitch(
"sro_serial");
122 const bool empty = options->getSwitch(
"sro_empty");
125 auto digitizers = std::make_shared<gdynamicdigitization::dRoutinesMap>();
127 "test_sro_implementation", options);
128 auto manager = std::unique_ptr<G4RunManager>(G4RunManagerFactory::CreateRunManager(
129 serial ? G4RunManagerType::SerialOnly : G4RunManagerType::MTOnly,
true, 4));
130 if (
auto* mt =
dynamic_cast<G4MTRunManager*
>(manager.get())) { mt->SetEventModulo(1); }
131 manager->SetUserInitialization(
new Detector(empty));
132 manager->SetUserInitialization(
new QBBC);
133 manager->SetUserInitialization(
new GAction(options, digitizers));
134 manager->Initialize();
135 const std::vector<int> counts{64, 16, 8};
136 for (
int run = 0; run < static_cast<int>(counts.size()); ++
run) {
137 manager->BeamOn(counts[
run]);
138 if (!empty) { check_output(base,
run, counts[
run], serial); }
141 require(std::distance(std::filesystem::directory_iterator(scratch),
142 std::filesystem::directory_iterator{})
143 == (empty ? 0 : 6),
"Expected exactly one output file per crate and run");
144 std::filesystem::remove_all(scratch);
145 std::cout <<
"SRO runtime routing, empty events, interruption, and repeated runs passed\n";
148 catch (
const std::exception& error) {
149 std::cerr << error.what() <<
'\n';
Registers GEMC user actions for worker threads, sequential execution, and the master thread.
std::shared_ptr< T > LoadAndRegisterObjectFromLibrary(std::string_view name, const std::shared_ptr< GOptions > &gopts)
Declares GAction, the Geant4 action-initialization entry point for the GEMC actions module.
G4THitsCollection< GHit > GHitsCollection
std::shared_ptr< const gstreamersMap > gstreamersMapPtr(const std::shared_ptr< GOptions > &gopts, int thread_id=-1)
GOptions defineOptions()
Builds the aggregate option set required by the actions subsystem.
int main(int argc, char *argv[])