File indexing completed on 2026-04-20 07:46:56
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Geant4/SensitiveSteppingAction.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/GeometryIdentifier.hpp"
0013 #include "Acts/Propagator/detail/SteppingLogger.hpp"
0014 #include "Acts/Surfaces/Surface.hpp"
0015 #include "ActsExamples/EventData/SimParticle.hpp"
0016 #include "ActsExamples/Geant4/AlgebraConverters.hpp"
0017 #include "ActsExamples/Geant4/EventStore.hpp"
0018 #include "ActsExamples/Geant4/SensitiveSurfaceMapper.hpp"
0019 #include "ActsExamples/Geant4/UnitConversion.hpp"
0020 #include "ActsFatras/EventData/Barcode.hpp"
0021
0022 #include <algorithm>
0023 #include <array>
0024 #include <cstddef>
0025 #include <string>
0026 #include <unordered_map>
0027 #include <utility>
0028
0029 #include <G4RunManager.hh>
0030 #include <G4Step.hh>
0031 #include <G4StepPoint.hh>
0032 #include <G4Track.hh>
0033 #include <G4UnitsTable.hh>
0034 #include <G4VPhysicalVolume.hh>
0035 #include <G4VTouchable.hh>
0036 #include <boost/describe.hpp>
0037
0038 BOOST_DESCRIBE_ENUM(G4StepStatus, fWorldBoundary, fGeomBoundary,
0039 fAtRestDoItProc, fAlongStepDoItProc, fPostStepDoItProc,
0040 fUserDefinedLimit, fExclusivelyForcedProc, fUndefined);
0041
0042 BOOST_DESCRIBE_ENUM(G4ProcessType, fNotDefined, fTransportation,
0043 fElectromagnetic, fOptical, fHadronic, fPhotolepton_hadron,
0044 fDecay, fGeneral, fParameterisation, fUserDefined,
0045 fParallel, fPhonon, fUCN);
0046
0047 BOOST_DESCRIBE_ENUM(G4TrackStatus, fAlive, fStopButAlive, fStopAndKill,
0048 fKillTrackAndSecondaries, fSuspend, fPostponeToNextEvent);
0049
0050 namespace ActsExamples::Geant4 {
0051
0052 namespace {
0053
0054 std::array<Acts::Vector4, 4u> kinematicsOfStep(const G4Step& step) {
0055 assert(step.GetPreStepPoint() != nullptr);
0056 assert(step.GetPostStepPoint() != nullptr);
0057 const G4StepPoint& preStepPoint = *step.GetPreStepPoint();
0058 const G4StepPoint& postStepPoint = *step.GetPostStepPoint();
0059
0060 const Acts::Vector4 preStepPosition =
0061 convertPosition(preStepPoint.GetPosition(), preStepPoint.GetGlobalTime());
0062
0063 const Acts::Vector4 preStepMomentum = convertMomentum(
0064 preStepPoint.GetMomentum(), preStepPoint.GetTotalEnergy());
0065 const Acts::Vector4 postStepPosition = convertPosition(
0066 postStepPoint.GetPosition(), postStepPoint.GetGlobalTime());
0067
0068 const Acts::Vector4 postStepMomentum = convertMomentum(
0069 postStepPoint.GetMomentum(), postStepPoint.GetTotalEnergy());
0070
0071 return {preStepPosition, preStepMomentum, postStepPosition, postStepMomentum};
0072 }
0073
0074 ActsFatras::Hit hitFromStep(const G4Step& step, SimBarcode particleId,
0075 Acts::GeometryIdentifier geoId,
0076 std::int32_t index) {
0077 const auto [preStepPosition, preStepMomentum, postStepPosition,
0078 postStepMomentum] = kinematicsOfStep(step);
0079
0080 return ActsFatras::Hit(geoId, particleId,
0081 0.5 * (preStepPosition + postStepPosition),
0082 preStepMomentum, postStepMomentum, index);
0083 }
0084
0085 Acts::detail::Step stepFromG4Step(const G4Step& step) {
0086 Acts::detail::Step pStep;
0087 const auto [preStepPosition, preStepMomentum, postStepPosition,
0088 postStepMomentum] = kinematicsOfStep(step);
0089
0090 pStep.navDir = Acts::Direction::Forward();
0091 pStep.position = 0.5 * (preStepPosition + postStepPosition).block<3, 1>(0, 0);
0092 pStep.momentum = 0.5 * (preStepMomentum + postStepMomentum).block<3, 1>(0, 0);
0093 pStep.nTotalTrials = 1;
0094 return pStep;
0095 }
0096
0097 }
0098
0099 SensitiveSteppingAction::SensitiveSteppingAction(
0100 const Config& cfg, std::unique_ptr<const Acts::Logger> logger)
0101 : G4UserSteppingAction(), m_cfg(cfg), m_logger(std::move(logger)) {}
0102
0103 void SensitiveSteppingAction::UserSteppingAction(const G4Step* stepPtr) {
0104 assert(stepPtr != nullptr);
0105 const G4Step& step = *stepPtr;
0106
0107
0108 assert(step.GetTrack() != nullptr);
0109 const G4Track& track = *step.GetTrack();
0110 G4PrimaryParticle* primaryParticle =
0111 track.GetDynamicParticle()->GetPrimaryParticle();
0112
0113
0114 assert(step.GetPreStepPoint() != nullptr);
0115 assert(step.GetPostStepPoint() != nullptr);
0116 const G4StepPoint& preStepPoint = *step.GetPreStepPoint();
0117 const G4StepPoint& postStepPoint = *step.GetPostStepPoint();
0118
0119
0120 const double absCharge =
0121 std::abs(track.GetParticleDefinition()->GetPDGCharge());
0122 if (!m_cfg.charged && absCharge > 0.) {
0123 return;
0124 }
0125
0126
0127 if (!m_cfg.neutral && absCharge == 0.) {
0128 return;
0129 }
0130
0131
0132 if (!m_cfg.primary && primaryParticle != nullptr) {
0133 return;
0134 }
0135
0136
0137 if (!m_cfg.secondary && primaryParticle == nullptr) {
0138 return;
0139 }
0140
0141
0142 const G4VPhysicalVolume* volume = track.GetVolume();
0143 if (volume == nullptr) {
0144 throw std::runtime_error("No volume found, terminate simulation");
0145 }
0146 const std::string volumeName = volume->GetName();
0147 ACTS_VERBOSE("Check whether volume " << volumeName << " is sensitive");
0148 if (!m_cfg.stepLogging &&
0149 volumeName.find(SensitiveSurfaceMapper::mappingPrefix) ==
0150 std::string::npos) {
0151 return;
0152 }
0153
0154
0155 const G4VTouchable* touchable = track.GetTouchable();
0156
0157 Acts::GeometryIdentifier geoId{};
0158 const Acts::Surface* surface = nullptr;
0159
0160
0161 const auto surfaceMap_itr = m_surfaceMapping.find(volume);
0162 if (surfaceMap_itr != m_surfaceMapping.end()) {
0163 const auto& surfacesToG4Vol = surfaceMap_itr->second;
0164 ACTS_VERBOSE("Found " << surfacesToG4Vol.size()
0165 << " candidate surfaces for volume " << volumeName);
0166 const Acts::Vector3 volumePos =
0167 convertPosition(touchable->GetTranslation());
0168 const auto lookUp_itr = surfacesToG4Vol.find(volumePos);
0169 if (lookUp_itr == surfacesToG4Vol.end() && !m_cfg.stepLogging) {
0170 ACTS_ERROR("No candidate surfaces found for volume " << volumeName);
0171 return;
0172 }
0173 surface = lookUp_itr->second;
0174 geoId = surface->geometryId();
0175 ACTS_VERBOSE("Replica assignment successful -> to surface " << geoId);
0176 } else if (!m_cfg.stepLogging) {
0177 ACTS_ERROR("No candidate surfaces found for volume " << volumeName);
0178 return;
0179 }
0180
0181 if (!eventStore().trackIdMapping.contains(track.GetTrackID())) {
0182 return;
0183 }
0184
0185
0186 const SimBarcode particleId =
0187 eventStore().trackIdMapping.at(track.GetTrackID());
0188 if (!m_cfg.stepLogging && surface != nullptr) {
0189 ACTS_VERBOSE("Step of " << particleId << " in sensitive volume " << geoId);
0190 } else if (m_cfg.stepLogging) {
0191 if (!eventStore().propagationRecords.contains(track.GetTrackID())) {
0192
0193 const double absMomentum =
0194 track.GetMomentum().mag() * convertEnergyToActs;
0195
0196 PropagationSummary iSummary(Acts::BoundTrackParameters::createCurvilinear(
0197 convertPosition(track.GetVertexPosition(), 0.),
0198 convertDirection(track.GetVertexMomentumDirection()),
0199 absCharge / absMomentum, std::nullopt,
0200 Acts::ParticleHypothesis::pion()));
0201
0202 eventStore().propagationRecords.insert({track.GetTrackID(), iSummary});
0203 }
0204 PropagationSummary& pSummary =
0205 eventStore().propagationRecords.at(track.GetTrackID());
0206
0207
0208 pSummary.nSteps += 1;
0209
0210 const double currentTrackLength =
0211 track.GetTrackLength() * convertLengthToActs;
0212 const double currentStepLength = currentTrackLength - pSummary.pathLength;
0213 pSummary.pathLength = currentTrackLength;
0214
0215
0216 Acts::detail::Step pStep = stepFromG4Step(step);
0217 pStep.geoID = geoId;
0218 pStep.surface = surface != nullptr ? surface->getSharedPtr() : nullptr;
0219
0220 if (!pSummary.steps.empty() && pSummary.steps.back().geoID == geoId &&
0221 pSummary.steps.back().surface != nullptr) {
0222 auto& lastStep = pSummary.steps.back();
0223 lastStep.stepSize = Acts::ConstrainedStep(currentStepLength);
0224 lastStep.position = 0.5 * (pStep.position + lastStep.position);
0225 lastStep.momentum = 0.5 * (pStep.momentum + lastStep.momentum);
0226 } else {
0227
0228 pStep.stepSize = Acts::ConstrainedStep(currentStepLength);
0229 pSummary.steps.emplace_back(std::move(pStep));
0230 }
0231
0232 return;
0233 }
0234
0235
0236 if (!eventStore().particleHitCount.contains(particleId)) {
0237 eventStore().particleHitCount[particleId] = 0;
0238 }
0239
0240
0241 const bool preOnBoundary = preStepPoint.GetStepStatus() == fGeomBoundary;
0242 const bool postOnBoundary = postStepPoint.GetStepStatus() == fGeomBoundary ||
0243 postStepPoint.GetStepStatus() == fWorldBoundary;
0244 const bool particleStopped = (postStepPoint.GetKineticEnergy() == 0.0);
0245 const bool particleDecayed =
0246 (postStepPoint.GetProcessDefinedStep()->GetProcessType() == fDecay);
0247
0248 const auto print = [](auto s) {
0249 return boost::describe::enum_to_string(s, "unmatched");
0250 };
0251
0252 ACTS_VERBOSE("status: pre="
0253 << print(preStepPoint.GetStepStatus())
0254 << ", post=" << print(postStepPoint.GetStepStatus())
0255 << ", post E_kin=" << std::boolalpha
0256 << postStepPoint.GetKineticEnergy() << ", process_type="
0257 << print(postStepPoint.GetProcessDefinedStep()->GetProcessType())
0258 << ", particle="
0259 << track.GetParticleDefinition()->GetParticleName()
0260 << ", process_name="
0261 << postStepPoint.GetProcessDefinedStep()->GetProcessName()
0262 << ", track status=" << print(track.GetTrackStatus()));
0263
0264
0265
0266 if (preOnBoundary && postOnBoundary) {
0267 ACTS_VERBOSE("-> merge single step to hit");
0268 ++eventStore().particleHitCount[particleId];
0269 eventStore().hits.push_back(
0270 hitFromStep(step, particleId, geoId,
0271 eventStore().particleHitCount.at(particleId) - 1));
0272
0273 eventStore().numberGeantSteps += 1ul;
0274 eventStore().maxStepsForHit = std::max(eventStore().maxStepsForHit, 1ul);
0275 return;
0276 }
0277
0278
0279
0280 if (postOnBoundary || particleStopped || particleDecayed) {
0281 ACTS_VERBOSE("-> merge buffer to hit");
0282 auto& buffer = eventStore().hitBuffer;
0283 buffer.push_back(hitFromStep(step, particleId, geoId, -1));
0284
0285 const Acts::Vector4 pos4 =
0286 0.5 * (buffer.front().fourPosition() + buffer.back().fourPosition());
0287
0288 ++eventStore().particleHitCount[particleId];
0289 eventStore().hits.emplace_back(
0290 geoId, particleId, pos4, buffer.front().momentum4Before(),
0291 buffer.back().momentum4After(),
0292 eventStore().particleHitCount.at(particleId) - 1);
0293
0294 assert(std::ranges::all_of(
0295 buffer, [&](const auto& h) { return h.geometryId() == geoId; }));
0296 assert(std::ranges::all_of(
0297 buffer, [&](const auto& h) { return h.particleId() == particleId; }));
0298
0299 eventStore().numberGeantSteps += buffer.size();
0300 eventStore().maxStepsForHit =
0301 std::max(eventStore().maxStepsForHit, buffer.size());
0302
0303 buffer.clear();
0304 return;
0305 }
0306
0307
0308
0309 if (!postOnBoundary) {
0310
0311 eventStore().hitBuffer.push_back(hitFromStep(step, particleId, geoId, -1));
0312 return;
0313 }
0314
0315 assert(false && "should never reach this");
0316 }
0317
0318 }