File indexing completed on 2026-09-13 08:21:08
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Propagator/ConstrainedStep.hpp"
0012 #include "Acts/Propagator/PropagatorState.hpp"
0013 #include "Acts/Propagator/StandardAborters.hpp"
0014 #include "Acts/Surfaces/Surface.hpp"
0015 #include "ActsFatras/EventData/Particle.hpp"
0016 #include "ActsFatras/Kernel/SingleParticleSimulationResult.hpp"
0017
0018 #include <algorithm>
0019 #include <cassert>
0020 #include <cmath>
0021
0022 namespace ActsFatras::detail {
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036 template <typename generator_t, typename decay_t, typename interactions_t,
0037 typename hit_surface_selector_t>
0038 struct SimulationActor {
0039 using result_type = SingleParticleSimulationResult;
0040
0041
0042 generator_t *generator = nullptr;
0043
0044 decay_t decay;
0045
0046 interactions_t interactions;
0047
0048 hit_surface_selector_t selectHitSurface;
0049
0050 Particle initialParticle;
0051
0052
0053 double properTimeRelativeTolerance = 1e-3;
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064 template <typename propagator_state_t, typename stepper_t,
0065 typename navigator_t>
0066 Acts::Result<void> act(propagator_state_t &state, stepper_t &stepper,
0067 navigator_t &navigator, result_type &result,
0068 const Acts::Logger &logger) const {
0069 assert(generator != nullptr && "The generator pointer must be valid");
0070
0071 if (state.stage == Acts::PropagatorStage::prePropagation) {
0072
0073
0074 result.particle =
0075 makeParticle(initialParticle, state, stepper, navigator);
0076 result.properTimeLimit =
0077 decay.generateProperTimeLimit(*generator, initialParticle);
0078 return Acts::Result<void>::success();
0079 }
0080
0081
0082 if (!result.isAlive) {
0083 return Acts::Result<void>::success();
0084 }
0085
0086 if (Acts::EndOfWorldReached{}.checkAbort(state, stepper, navigator,
0087 logger)) {
0088 result.isAlive = false;
0089 return Acts::Result<void>::success();
0090 }
0091
0092
0093
0094
0095
0096 result.particle = makeParticle(result.particle, state, stepper, navigator);
0097
0098
0099 if (std::isfinite(result.properTimeLimit) &&
0100 (result.properTimeLimit - result.particle.properTime() <
0101 result.properTimeLimit * properTimeRelativeTolerance)) {
0102 auto descendants = decay.run(generator, result.particle);
0103 for (const auto &descendant : descendants) {
0104 result.generatedParticles.emplace_back(descendant);
0105 }
0106 result.isAlive = false;
0107 return Acts::Result<void>::success();
0108 }
0109
0110
0111 if (std::isfinite(result.properTimeLimit)) {
0112 assert(result.particle.mass() > 0.0 && "Particle must have mass");
0113
0114
0115
0116
0117 const auto properTimeDiff =
0118 result.properTimeLimit - result.particle.properTime();
0119
0120
0121 const auto stepSize = properTimeDiff *
0122 result.particle.absoluteMomentum() /
0123 result.particle.mass();
0124 stepper.releaseStepSize(state.stepping,
0125 Acts::ConstrainedStep::Type::User);
0126 stepper.updateStepSize(state.stepping, stepSize,
0127 Acts::ConstrainedStep::Type::User);
0128 }
0129
0130
0131 if (std::isnan(result.x0Limit) || std::isnan(result.l0Limit)) {
0132 armPointLikeInteractions(initialParticle, result);
0133 }
0134
0135
0136 if (state.stage == Acts::PropagatorStage::postPropagation) {
0137 return Acts::Result<void>::success();
0138 }
0139
0140 if (!navigator.currentSurface(state.navigation)) {
0141 return Acts::Result<void>::success();
0142 }
0143 const Acts::Surface &surface = *navigator.currentSurface(state.navigation);
0144
0145
0146
0147 const Particle before = result.particle;
0148
0149
0150 if (surface.hasMaterial()) {
0151
0152
0153
0154 auto lpResult = surface.globalToLocal(state.geoContext, before.position(),
0155 before.direction());
0156 if (lpResult.ok()) {
0157 const Acts::Vector2 local = lpResult.value();
0158 Acts::MaterialSlab slab = surface.materialSlab(local);
0159
0160 if (!slab.isVacuum()) {
0161
0162 auto normal = surface.normal(state.geoContext, before.position(),
0163 before.direction());
0164
0165
0166 auto cosIncidenceInv = normal.norm() / normal.dot(before.direction());
0167
0168 slab.scaleThickness(std::abs(cosIncidenceInv));
0169
0170 interact(slab, result);
0171 }
0172 }
0173 }
0174 Particle &after = result.particle;
0175
0176
0177 if (selectHitSurface(surface)) {
0178 result.hits.emplace_back(
0179 surface.geometryId(), before.particleId(),
0180
0181 0.5 * (before.fourPosition() + after.fourPosition()),
0182 before.fourMomentum(), after.fourMomentum(), result.hits.size());
0183
0184 after.setNumberOfHits(result.hits.size());
0185 }
0186
0187 if (after.absoluteMomentum() == 0.0) {
0188 result.isAlive = false;
0189 return Acts::Result<void>::success();
0190 }
0191
0192
0193 stepper.update(state.stepping, after.position(), after.direction(),
0194 after.qOverP(), after.time());
0195
0196 return Acts::Result<void>::success();
0197 }
0198
0199 template <typename propagator_state_t, typename stepper_t,
0200 typename navigator_t>
0201 bool checkAbort(propagator_state_t & , const stepper_t & ,
0202 const navigator_t & , const result_type &result,
0203 const Acts::Logger & ) const {
0204
0205 return !result.isAlive;
0206 }
0207
0208
0209 template <typename propagator_state_t, typename stepper_t,
0210 typename navigator_t>
0211 Particle makeParticle(const Particle &previous, propagator_state_t &state,
0212 stepper_t &stepper, navigator_t &navigator) const {
0213
0214
0215
0216
0217 const auto deltaLabTime = stepper.time(state.stepping) - previous.time();
0218
0219
0220
0221
0222 const auto gammaInv = previous.mass() / previous.energy();
0223 const auto properTime = previous.properTime() + gammaInv * deltaLabTime;
0224 const Acts::Surface *currentSurface = nullptr;
0225 if (navigator.currentSurface(state.navigation) != nullptr) {
0226 currentSurface = navigator.currentSurface(state.navigation);
0227 }
0228
0229 return Particle(previous)
0230 .setPosition4(stepper.position(state.stepping),
0231 stepper.time(state.stepping))
0232 .setDirection(stepper.direction(state.stepping))
0233 .setAbsoluteMomentum(stepper.absoluteMomentum(state.stepping))
0234 .setProperTime(properTime)
0235 .setReferenceSurface(currentSurface);
0236 }
0237
0238
0239 void armPointLikeInteractions(const Particle &particle,
0240 result_type &result) const {
0241 auto selection = interactions.armPointLike(*generator, particle);
0242 result.x0Limit = selection.x0Limit;
0243 result.l0Limit = selection.l0Limit;
0244 result.x0Process = selection.x0Process;
0245 result.l0Process = selection.l0Process;
0246 }
0247
0248
0249
0250
0251
0252 void interact(const Acts::MaterialSlab &slab, result_type &result) const {
0253
0254
0255 auto runContinuousPartial = [&, this](float fraction) {
0256 Acts::MaterialSlab partialSlab = slab;
0257 partialSlab.scaleThickness(fraction);
0258
0259 const auto x0 = result.particle.pathInX0() + partialSlab.thicknessInX0();
0260 const auto l0 = result.particle.pathInX0() + partialSlab.thicknessInL0();
0261 bool retval = false;
0262 if (interactions.runContinuous(*(this->generator), partialSlab,
0263 result.particle,
0264 result.generatedParticles)) {
0265 result.isAlive = false;
0266 retval = true;
0267 }
0268
0269
0270
0271
0272
0273 result.particle.setMaterialPassed(x0, l0);
0274 return retval;
0275 };
0276
0277
0278 const auto slabX0 = slab.thicknessInX0();
0279 const auto slabL0 = slab.thicknessInL0();
0280
0281
0282 const auto x0Dist = result.x0Limit - result.particle.pathInX0();
0283 const auto l0Dist = result.l0Limit - result.particle.pathInL0();
0284
0285
0286
0287
0288
0289
0290
0291
0292
0293
0294
0295
0296
0297
0298
0299
0300
0301 const float fracX0 =
0302 std::clamp(static_cast<float>(x0Dist / slabX0), 0.0f, 1.0f);
0303 const float fracL0 =
0304 std::clamp(static_cast<float>(l0Dist / slabL0), 0.0f, 1.0f);
0305
0306 const float frac = std::min(fracX0, fracL0);
0307
0308
0309 if (0.0f < frac) {
0310
0311 if (runContinuousPartial(frac)) {
0312 return;
0313 }
0314 }
0315
0316 if (frac < 1.0f) {
0317
0318 const std::size_t process =
0319 (fracX0 < fracL0) ? result.x0Process : result.l0Process;
0320
0321 if (interactions.runPointLike(*generator, process, result.particle,
0322 result.generatedParticles)) {
0323 result.isAlive = false;
0324 return;
0325 }
0326
0327 if (runContinuousPartial(1.0 - frac)) {
0328 return;
0329 }
0330
0331
0332
0333
0334
0335
0336
0337 armPointLikeInteractions(result.particle, result);
0338 }
0339 }
0340 };
0341
0342 }