aegir
Phlex-based simulation framework for the SHiP experiment.
Loading...
Searching...
No Matches
geant4_sim_core.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// geant4_sim_core.hpp — Shared G4 user action classes and thread-local storage
6
7#pragma once
8
9#include <G4LogicalVolume.hh>
10#include <G4Step.hh>
11#include <G4StepPoint.hh>
12#include <G4SystemOfUnits.hh>
13#include <G4Track.hh>
14#include <G4UserSteppingAction.hh>
15#include <G4UserTrackingAction.hh>
16#include <G4VProcess.hh>
17#include <G4VSensitiveDetector.hh>
18#include <SHiP/SimHit.hpp>
19#include <SHiP/SimParticle.hpp>
20#include <unordered_map>
21#include <vector>
22
23namespace SHiP::g4 {
24
25// Thread-local storage for current event data (per G4 worker thread)
26inline thread_local std::vector<SimHit> tl_hits;
27inline thread_local std::vector<SimParticle> tl_particles;
28inline thread_local std::unordered_map<int, std::size_t> tl_track_map;
29
30using DetectorIdMap = std::unordered_map<G4LogicalVolume*, int>;
31
32inline SimHit make_base_hit(G4Step const* step,
33 DetectorIdMap const& detector_ids) {
34 auto* pre = step->GetPreStepPoint();
35 auto pos = pre->GetPosition();
36 auto mom = pre->GetMomentum();
37 auto* lv = pre->GetTouchable()->GetVolume()->GetLogicalVolume();
38
39 SimHit hit;
40 auto it = detector_ids.find(lv);
41 hit.detectorId = it != detector_ids.end() ? it->second : -1;
42 hit.trackId = step->GetTrack()->GetTrackID();
43 hit.pdgCode = step->GetTrack()->GetDefinition()->GetPDGEncoding();
44 hit.position = {pos.x() / mm, pos.y() / mm, pos.z() / mm};
45 hit.momentum = {mom.x() / GeV, mom.y() / GeV, mom.z() / GeV};
46 hit.time = pre->GetGlobalTime() / ns;
47 return hit;
48}
49
50class ScoringSD : public G4VSensitiveDetector {
51 public:
52 ScoringSD(G4String const& name, DetectorIdMap detector_ids)
53 : G4VSensitiveDetector(name), detector_ids_{std::move(detector_ids)} {}
54
55 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
56 double edep = step->GetTotalEnergyDeposit();
57 if (edep <= 0) return false;
58
59 auto hit = make_base_hit(step, detector_ids_);
60 hit.energyDeposit = edep / GeV;
61 hit.pathLength = step->GetStepLength() / mm;
62 tl_hits.push_back(hit);
63 return true;
64 }
65
66 private:
67 DetectorIdMap detector_ids_;
68};
69
70// Records a SimHit when a track first enters the volume, regardless of
71// energy deposit. Optionally filters on kinetic energy threshold.
72// Matches FairShip's exitHadronAbsorber scoring behaviour.
73class CrossingSD : public G4VSensitiveDetector {
74 public:
75 CrossingSD(G4String const& name, DetectorIdMap detector_ids,
76 double ke_threshold_gev = 0.0)
77 : G4VSensitiveDetector(name),
78 detector_ids_{std::move(detector_ids)},
79 ke_threshold_{ke_threshold_gev * GeV} {}
80
81 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
82 if (!step->IsFirstStepInVolume()) return false;
83
84 auto* track = step->GetTrack();
85 if (track->GetKineticEnergy() < ke_threshold_) return false;
86
87 auto hit = make_base_hit(step, detector_ids_);
88 hit.energyDeposit = 0;
89 hit.pathLength = track->GetTrackLength() / mm;
90 tl_hits.push_back(hit);
91 return true;
92 }
93
94 private:
95 DetectorIdMap detector_ids_;
96 double ke_threshold_;
97};
98
99// Kills tracks below kinetic energy threshold.
100// Matches FairShip's PreTrack() stopping behaviour.
101class EnergyCutAction : public G4UserSteppingAction {
102 public:
103 explicit EnergyCutAction(double ke_threshold_gev)
104 : ke_threshold_{ke_threshold_gev * GeV} {}
105
106 void UserSteppingAction(const G4Step* step) override {
107 auto* track = step->GetTrack();
108 if (track->GetKineticEnergy() < ke_threshold_) {
109 track->SetTrackStatus(fStopAndKill);
110 }
111 }
112
113 private:
114 double ke_threshold_;
115};
116
117class TrackingAction : public G4UserTrackingAction {
118 public:
119 explicit TrackingAction(double particle_ke_cut_gev = 0.0)
120 : particle_ke_cut_{particle_ke_cut_gev * GeV} {}
121
122 void PreUserTrackingAction(const G4Track* track) override {
123 if (particle_ke_cut_ > 0 && track->GetParentID() != 0 &&
124 track->GetKineticEnergy() < particle_ke_cut_)
125 return;
126
127 SimParticle p;
128 p.trackId = track->GetTrackID();
129 p.parentId = track->GetParentID();
130 p.pdgCode = track->GetDefinition()->GetPDGEncoding();
131
132 auto pos = track->GetPosition();
133 auto mom = track->GetMomentum();
134 p.vertex = {pos.x() / mm, pos.y() / mm, pos.z() / mm};
135 p.momentum = {mom.x() / GeV, mom.y() / GeV, mom.z() / GeV};
136 p.energy = track->GetKineticEnergy() / GeV;
137 p.time = track->GetGlobalTime() / ns;
138
139 auto* creator = track->GetCreatorProcess();
140 p.creatorProcess = creator ? creator->GetProcessSubType() : 0;
141
142 tl_track_map[p.trackId] = tl_particles.size();
143 tl_particles.push_back(p);
144 }
145
146 void PostUserTrackingAction(const G4Track* track) override {
147 auto it = tl_track_map.find(track->GetTrackID());
148 if (it != tl_track_map.end()) {
149 auto pos = track->GetPosition();
150 tl_particles[it->second].endpoint = {pos.x() / mm, pos.y() / mm,
151 pos.z() / mm};
152 }
153 }
154
155 private:
156 double particle_ke_cut_;
157};
158
159} // namespace SHiP::g4
Definition geant4_sim_core.hpp:73
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:81
CrossingSD(G4String const &name, DetectorIdMap detector_ids, double ke_threshold_gev=0.0)
Definition geant4_sim_core.hpp:75
Definition geant4_sim_core.hpp:101
void UserSteppingAction(const G4Step *step) override
Definition geant4_sim_core.hpp:106
EnergyCutAction(double ke_threshold_gev)
Definition geant4_sim_core.hpp:103
Definition geant4_sim_core.hpp:50
ScoringSD(G4String const &name, DetectorIdMap detector_ids)
Definition geant4_sim_core.hpp:52
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:55
Definition geant4_sim_core.hpp:117
TrackingAction(double particle_ke_cut_gev=0.0)
Definition geant4_sim_core.hpp:119
void PostUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:146
void PreUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:122
Definition detector_construction.hpp:31
thread_local std::vector< SimParticle > tl_particles
Definition geant4_sim_core.hpp:27
thread_local std::unordered_map< int, std::size_t > tl_track_map
Definition geant4_sim_core.hpp:28
thread_local std::vector< SimHit > tl_hits
Definition geant4_sim_core.hpp:26
SimHit make_base_hit(G4Step const *step, DetectorIdMap const &detector_ids)
Definition geant4_sim_core.hpp:32
std::unordered_map< G4LogicalVolume *, int > DetectorIdMap
Definition geant4_sim_core.hpp:30