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
35inline SimHit make_base_hit(G4Step const* step,
36 DetectorIdMap const& detector_ids) {
37 auto* pre = step->GetPreStepPoint();
38 auto pos = pre->GetPosition();
39 auto mom = pre->GetMomentum();
40 auto* lv = pre->GetTouchable()->GetVolume()->GetLogicalVolume();
41
42 SimHit hit;
43 auto it = detector_ids.find(lv);
44 hit.detectorId = it != detector_ids.end() ? it->second : -1;
45 hit.trackId = step->GetTrack()->GetTrackID();
46 hit.pdgCode = step->GetTrack()->GetDefinition()->GetPDGEncoding();
47 ship::view::setPosition(hit, aegir::clhep::position(pos));
48 ship::view::setMomentum(hit, aegir::clhep::momentum(mom));
49 ship::view::setTime(hit, aegir::clhep::time(pre->GetGlobalTime()));
50 return hit;
51}
52
53class ScoringSD : public G4VSensitiveDetector {
54 public:
55 ScoringSD(G4String const& name, DetectorIdMap detector_ids)
56 : G4VSensitiveDetector(name), detector_ids_{std::move(detector_ids)} {}
57
58 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
59 double edep = step->GetTotalEnergyDeposit();
60 if (edep <= 0) return false;
61
62 auto hit = make_base_hit(step, detector_ids_);
63 ship::view::setEnergyDeposit(hit, aegir::clhep::energy(edep));
64 ship::view::setPathLength(hit, aegir::clhep::length(step->GetStepLength()));
65 tl_hits.push_back(hit);
66 return true;
67 }
68
69 private:
70 DetectorIdMap detector_ids_;
71};
72
73// Records a SimHit when a track first enters the volume, regardless of
74// energy deposit. Optionally filters on kinetic energy threshold.
75// Matches FairShip's exitHadronAbsorber scoring behaviour.
76class CrossingSD : public G4VSensitiveDetector {
77 public:
78 CrossingSD(G4String const& name, DetectorIdMap detector_ids,
79 ship::Energy ke_threshold = ship::Energy::zero())
80 : G4VSensitiveDetector(name),
81 detector_ids_{std::move(detector_ids)},
82 ke_threshold_{aegir::clhep::g4(ke_threshold)} {}
83
84 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
85 if (!step->IsFirstStepInVolume()) return false;
86
87 auto* track = step->GetTrack();
88 if (track->GetKineticEnergy() < ke_threshold_) return false;
89
90 auto hit = make_base_hit(step, detector_ids_);
91 hit.energyDeposit = 0;
92 ship::view::setPathLength(hit,
93 aegir::clhep::length(track->GetTrackLength()));
94 tl_hits.push_back(hit);
95 return true;
96 }
97
98 private:
99 DetectorIdMap detector_ids_;
100 double ke_threshold_; // Geant4 internal units
101};
102
103// Kills tracks below kinetic energy threshold.
104// Matches FairShip's PreTrack() stopping behaviour.
105class EnergyCutAction : public G4UserSteppingAction {
106 public:
107 explicit EnergyCutAction(ship::Energy ke_threshold)
108 : ke_threshold_{aegir::clhep::g4(ke_threshold)} {}
109
110 void UserSteppingAction(const G4Step* step) override {
111 auto* track = step->GetTrack();
112 if (track->GetKineticEnergy() < ke_threshold_) {
113 track->SetTrackStatus(fStopAndKill);
114 }
115 }
116
117 private:
118 double ke_threshold_; // Geant4 internal units
119};
120
121class TrackingAction : public G4UserTrackingAction {
122 public:
123 explicit TrackingAction(ship::Energy particle_ke_cut = ship::Energy::zero())
124 : particle_ke_cut_{aegir::clhep::g4(particle_ke_cut)} {}
125
126 void PreUserTrackingAction(const G4Track* track) override {
127 if (particle_ke_cut_ > 0 && track->GetParentID() != 0 &&
128 track->GetKineticEnergy() < particle_ke_cut_)
129 return;
130
131 SimParticle p;
132 p.trackId = track->GetTrackID();
133 p.parentId = track->GetParentID();
134 p.pdgCode = track->GetDefinition()->GetPDGEncoding();
135
136 ship::view::setVertex(p, aegir::clhep::position(track->GetPosition()));
137 ship::view::setMomentum(p, aegir::clhep::momentum(track->GetMomentum()));
138 ship::view::setEnergy(p, aegir::clhep::energy(track->GetKineticEnergy()));
139 ship::view::setTime(p, aegir::clhep::time(track->GetGlobalTime()));
140
141 auto* creator = track->GetCreatorProcess();
142 p.creatorProcess = creator ? creator->GetProcessSubType() : 0;
143
144 tl_track_map[p.trackId] = tl_particles.size();
145 tl_particles.push_back(p);
146 }
147
148 void PostUserTrackingAction(const G4Track* track) override {
149 auto it = tl_track_map.find(track->GetTrackID());
150 if (it != tl_track_map.end()) {
151 ship::view::setEndpoint(tl_particles[it->second],
152 aegir::clhep::position(track->GetPosition()));
153 }
154 }
155
156 private:
157 double particle_ke_cut_; // Geant4 internal units
158};
159
160} // namespace SHiP::g4
Definition geant4_sim_core.hpp:76
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:84
CrossingSD(G4String const &name, DetectorIdMap detector_ids, ship::Energy ke_threshold=ship::Energy::zero())
Definition geant4_sim_core.hpp:78
Definition geant4_sim_core.hpp:105
EnergyCutAction(ship::Energy ke_threshold)
Definition geant4_sim_core.hpp:107
void UserSteppingAction(const G4Step *step) override
Definition geant4_sim_core.hpp:110
Definition geant4_sim_core.hpp:53
ScoringSD(G4String const &name, DetectorIdMap detector_ids)
Definition geant4_sim_core.hpp:55
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition geant4_sim_core.hpp:58
Definition geant4_sim_core.hpp:121
void PostUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:148
void PreUserTrackingAction(const G4Track *track) override
Definition geant4_sim_core.hpp:126
TrackingAction(ship::Energy particle_ke_cut=ship::Energy::zero())
Definition geant4_sim_core.hpp:123
Definition detector_construction.hpp:30
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, DetectorIdMap const &detector_ids)
Definition geant4_sim_core.hpp:35
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 math_utils.hpp:12