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 <G4Track.hh>
13#include <G4UserSteppingAction.hh>
14#include <G4UserTrackingAction.hh>
15#include <G4VProcess.hh>
16#include <G4VSensitiveDetector.hh>
17#include <SHiP/QuantityView.hpp>
18#include <SHiP/SimHit.hpp>
19#include <SHiP/SimParticle.hpp>
20#include <unordered_map>
21#include <utility>
22#include <vector>
23
25
26namespace SHiP::g4 {
27
28// Thread-local storage for current event data (per G4 worker thread)
29inline thread_local std::vector<SimHit> tl_hits;
30inline thread_local std::vector<SimParticle> tl_particles;
31inline thread_local std::unordered_map<int, std::size_t> tl_track_map;
32
33using DetectorIdMap = std::unordered_map<G4LogicalVolume*, int>;
34
35// Track, particle and pre-step-point kinematics of a hit, under `detector_id`.
36inline SimHit make_base_hit(G4Step const* step, int detector_id) {
37 auto const* pre = step->GetPreStepPoint();
38 auto const pos = pre->GetPosition();
39 auto const mom = pre->GetMomentum();
40
41 SimHit hit;
42 hit.detectorId = detector_id;
43 hit.trackId = step->GetTrack()->GetTrackID();
44 hit.pdgCode = step->GetTrack()->GetDefinition()->GetPDGEncoding();
45 ship::view::setPosition(hit, aegir::clhep::position(pos));
46 ship::view::setMomentum(hit, aegir::clhep::momentum(mom));
47 ship::view::setTime(hit, aegir::clhep::time(pre->GetGlobalTime()));
48 return hit;
49}
50
51// As above, with the detector id looked up from the step's logical volume
52// (-1 if the volume is not in `detector_ids`).
53inline SimHit make_base_hit(G4Step const* step,
54 DetectorIdMap const& detector_ids) {
55 auto* lv =
56 step->GetPreStepPoint()->GetTouchable()->GetVolume()->GetLogicalVolume();
57 auto const it = detector_ids.find(lv);
58 return make_base_hit(step, it != detector_ids.end() ? it->second : -1);
59}
60
61class ScoringSD : public G4VSensitiveDetector {
62 public:
63 ScoringSD(G4String const& name, DetectorIdMap detector_ids)
64 : G4VSensitiveDetector(name), detector_ids_{std::move(detector_ids)} {}
65
66 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
67 double const edep = step->GetTotalEnergyDeposit();
68 if (edep <= 0) {
69 return false;
70 }
71
72 auto hit = make_base_hit(step, detector_ids_);
73 ship::view::setEnergyDeposit(hit, aegir::clhep::energy(edep));
74 ship::view::setPathLength(hit, aegir::clhep::length(step->GetStepLength()));
75 tl_hits.push_back(hit);
76 return true;
77 }
78
79 private:
80 DetectorIdMap detector_ids_;
81};
82
83// Records a SimHit when a track first enters the volume, regardless of
84// energy deposit. Optionally filters on kinetic energy threshold.
85// Matches FairShip's exitHadronAbsorber scoring behaviour.
86class CrossingSD : public G4VSensitiveDetector {
87 public:
88 CrossingSD(G4String const& name, DetectorIdMap detector_ids,
89 ship::Energy ke_threshold = ship::Energy::zero())
90 : G4VSensitiveDetector(name),
91 detector_ids_{std::move(detector_ids)},
92 ke_threshold_{aegir::clhep::g4(ke_threshold)} {}
93
94 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
95 if (!step->IsFirstStepInVolume()) {
96 return false;
97 }
98
99 auto const* track = step->GetTrack();
100 if (track->GetKineticEnergy() < ke_threshold_) {
101 return false;
102 }
103
104 auto hit = make_base_hit(step, detector_ids_);
105 hit.energyDeposit = 0;
106 ship::view::setPathLength(hit,
107 aegir::clhep::length(track->GetTrackLength()));
108 tl_hits.push_back(hit);
109 return true;
110 }
111
112 private:
113 DetectorIdMap detector_ids_;
114 double ke_threshold_; // Geant4 internal units
115};
116
117// Kills tracks below kinetic energy threshold.
118// Matches FairShip's PreTrack() stopping behaviour.
119class EnergyCutAction : public G4UserSteppingAction {
120 public:
121 explicit EnergyCutAction(ship::Energy ke_threshold)
122 : ke_threshold_{aegir::clhep::g4(ke_threshold)} {}
123
124 void UserSteppingAction(const G4Step* step) override {
125 auto* track = step->GetTrack();
126 if (track->GetKineticEnergy() < ke_threshold_) {
127 track->SetTrackStatus(fStopAndKill);
128 }
129 }
130
131 private:
132 double ke_threshold_; // Geant4 internal units
133};
134
135class TrackingAction : public G4UserTrackingAction {
136 public:
137 explicit TrackingAction(ship::Energy particle_ke_cut = ship::Energy::zero())
138 : particle_ke_cut_{aegir::clhep::g4(particle_ke_cut)} {}
139
140 void PreUserTrackingAction(const G4Track* track) override {
141 if (particle_ke_cut_ > 0 && track->GetParentID() != 0 &&
142 track->GetKineticEnergy() < particle_ke_cut_) {
143 return;
144 }
145
146 SimParticle p;
147 p.trackId = track->GetTrackID();
148 p.parentId = track->GetParentID();
149 p.pdgCode = track->GetDefinition()->GetPDGEncoding();
150
151 ship::view::setVertex(p, aegir::clhep::position(track->GetPosition()));
152 ship::view::setMomentum(p, aegir::clhep::momentum(track->GetMomentum()));
153 ship::view::setEnergy(p, aegir::clhep::energy(track->GetKineticEnergy()));
154 ship::view::setTime(p, aegir::clhep::time(track->GetGlobalTime()));
155
156 const auto* creator = track->GetCreatorProcess();
157 p.creatorProcess = creator ? creator->GetProcessSubType() : 0;
158
159 tl_track_map[p.trackId] = tl_particles.size();
160 tl_particles.push_back(p);
161 }
162
163 void PostUserTrackingAction(const G4Track* track) override {
164 auto const it = tl_track_map.find(track->GetTrackID());
165 if (it != tl_track_map.end()) {
166 ship::view::setEndpoint(tl_particles[it->second],
167 aegir::clhep::position(track->GetPosition()));
168 }
169 }
170
171 private:
172 double particle_ke_cut_; // Geant4 internal units
173};
174
175} // namespace SHiP::g4
Definition geant4_sim_core.hpp:86
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:94
CrossingSD(G4String const &name, DetectorIdMap detector_ids, ship::Energy ke_threshold=ship::Energy::zero())
Definition geant4_sim_core.hpp:88
Definition geant4_sim_core.hpp:119
EnergyCutAction(ship::Energy ke_threshold)
Definition geant4_sim_core.hpp:121
void UserSteppingAction(const G4Step *step) override
Definition geant4_sim_core.hpp:124
Definition geant4_sim_core.hpp:61
ScoringSD(G4String const &name, DetectorIdMap detector_ids)
Definition geant4_sim_core.hpp:63
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:66
Definition geant4_sim_core.hpp:135
void PostUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:163
void PreUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:140
TrackingAction(ship::Energy particle_ke_cut=ship::Energy::zero())
Definition geant4_sim_core.hpp:137
Definition detector_construction.hpp:33
thread_local std::vector< SimParticle > tl_particles
Definition geant4_sim_core.hpp:30
thread_local std::unordered_map< int, std::size_t > tl_track_map
Definition geant4_sim_core.hpp:31
thread_local std::vector< SimHit > tl_hits
Definition geant4_sim_core.hpp:29
SimHit make_base_hit(G4Step const *step, int detector_id)
Definition geant4_sim_core.hpp:36
std::unordered_map< G4LogicalVolume *, int > DetectorIdMap
Definition geant4_sim_core.hpp:33
ship::Energy energy(G4double v)
Definition clhep_bridge.hpp:34
ship::Length length(G4double v)
Definition clhep_bridge.hpp:28
ship::Vec3< ship::Length > position(G4ThreeVector const &v)
Definition clhep_bridge.hpp:41
ship::Momentum momentum(G4double v)
Definition clhep_bridge.hpp:38
ship::Time time(G4double v)
Definition clhep_bridge.hpp:31
Definition hepmc_status.hpp:17