File indexing completed on 2026-04-04 07:47:43
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Direction.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/BoundTrackParameters.hpp"
0014 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
0015 #include "Acts/EventData/Types.hpp"
0016 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0017 #include "Acts/Surfaces/Surface.hpp"
0018 #include "Acts/TrackFitting/BetheHeitlerApprox.hpp"
0019 #include "Acts/TrackFitting/GsfComponent.hpp"
0020 #include "Acts/Utilities/AlgebraHelpers.hpp"
0021 #include "Acts/Utilities/Intersection.hpp"
0022 #include "Acts/Utilities/Logger.hpp"
0023 #include "Acts/Utilities/Zip.hpp"
0024
0025 #include <array>
0026 #include <cassert>
0027 #include <cmath>
0028 #include <cstddef>
0029 #include <iomanip>
0030 #include <map>
0031 #include <ostream>
0032 #include <tuple>
0033 #include <vector>
0034
0035 namespace Acts::detail::Gsf {
0036
0037
0038 constexpr static double s_normalizationTolerance = 1.e-4;
0039
0040 template <typename component_range_t, typename projector_t>
0041 bool weightsAreNormalized(const component_range_t &cmps,
0042 const projector_t &proj,
0043 double tol = s_normalizationTolerance) {
0044 double sumOfWeights = 0.0;
0045
0046 for (auto it = cmps.begin(); it != cmps.end(); ++it) {
0047 sumOfWeights += proj(*it);
0048 }
0049
0050 return std::abs(sumOfWeights - 1.0) < tol;
0051 }
0052
0053 template <typename component_range_t, typename projector_t>
0054 void normalizeWeights(component_range_t &cmps, const projector_t &proj) {
0055 double sumOfWeights = 0.0;
0056
0057
0058
0059 for (auto it = cmps.begin(); it != cmps.end(); ++it) {
0060 decltype(auto) cmp = *it;
0061 assert(std::isfinite(proj(cmp)) && "weight not finite in normalization");
0062 sumOfWeights += proj(cmp);
0063 }
0064
0065 assert(sumOfWeights > 0 && "sum of weights is not > 0");
0066
0067 for (auto it = cmps.begin(); it != cmps.end(); ++it) {
0068 decltype(auto) cmp = *it;
0069 proj(cmp) /= sumOfWeights;
0070 }
0071 }
0072
0073
0074
0075
0076
0077 template <typename propagator_state_t, typename stepper_t, typename navigator_t>
0078 class ScopedGsfInfoPrinterAndChecker {
0079 const propagator_state_t &m_state;
0080 const stepper_t &m_stepper;
0081 const navigator_t &m_navigator;
0082 double m_p_initial;
0083 const Logger &m_logger;
0084
0085 const Logger &logger() const { return m_logger; }
0086
0087 void print_component_stats() const {
0088 std::size_t i = 0;
0089 for (auto cmp : m_stepper.constComponentIterable(m_state.stepping)) {
0090 auto getVector = [&](auto idx) {
0091 return cmp.pars().template segment<3>(idx).transpose();
0092 };
0093 ACTS_VERBOSE(" #" << i++ << " pos: " << getVector(eFreePos0) << ", dir: "
0094 << getVector(eFreeDir0) << ", weight: " << cmp.weight()
0095 << ", status: " << cmp.status()
0096 << ", qop: " << cmp.pars()[eFreeQOverP]
0097 << ", det(cov): " << cmp.cov().determinant());
0098 }
0099 }
0100
0101 void checks(bool onStart) const {
0102 const auto cmps = m_stepper.constComponentIterable(m_state.stepping);
0103 [[maybe_unused]] const bool allFinite =
0104 std::all_of(cmps.begin(), cmps.end(),
0105 [](auto cmp) { return std::isfinite(cmp.weight()); });
0106 [[maybe_unused]] const bool allNormalized = weightsAreNormalized(
0107 cmps, [](const auto &cmp) { return cmp.weight(); });
0108 [[maybe_unused]] const bool zeroComponents =
0109 m_stepper.numberComponents(m_state.stepping) == 0;
0110
0111 if (onStart) {
0112 assert(!zeroComponents && "no cmps at the start");
0113 assert(allFinite && "weights not finite at the start");
0114 assert(allNormalized && "not normalized at the start");
0115 } else {
0116 assert(!zeroComponents && "no cmps at the end");
0117 assert(allFinite && "weights not finite at the end");
0118 assert(allNormalized && "not normalized at the end");
0119 }
0120 }
0121
0122 public:
0123 ScopedGsfInfoPrinterAndChecker(const propagator_state_t &state,
0124 const stepper_t &stepper,
0125 const navigator_t &navigator,
0126 const Logger &logger)
0127 : m_state(state),
0128 m_stepper(stepper),
0129 m_navigator(navigator),
0130 m_p_initial(stepper.absoluteMomentum(state.stepping)),
0131 m_logger{logger} {
0132
0133 checks(true);
0134 ACTS_VERBOSE("Gsf step "
0135 << state.stepping.steps << " at mean position "
0136 << stepper.position(state.stepping).transpose()
0137 << " with direction "
0138 << stepper.direction(state.stepping).transpose()
0139 << " and momentum " << stepper.absoluteMomentum(state.stepping)
0140 << " and charge " << stepper.charge(state.stepping));
0141 ACTS_VERBOSE("Propagation is in " << state.options.direction << " mode");
0142 print_component_stats();
0143 }
0144
0145 ~ScopedGsfInfoPrinterAndChecker() {
0146 if (m_navigator.currentSurface(m_state.navigation)) {
0147 const auto p_final = m_stepper.absoluteMomentum(m_state.stepping);
0148 ACTS_VERBOSE("Component status at end of step:");
0149 print_component_stats();
0150 ACTS_VERBOSE("Delta Momentum = " << std::setprecision(5)
0151 << p_final - m_p_initial);
0152 }
0153 checks(false);
0154 }
0155 };
0156
0157 double calculateDeterminant(
0158 const double *fullCalibratedCovariance,
0159 TrackStateTraits<kMeasurementSizeMax, true>::Covariance predictedCovariance,
0160 BoundSubspaceIndices projector, unsigned int calibratedSize);
0161
0162
0163
0164
0165
0166 template <typename traj_t>
0167 void computePosteriorWeights(const traj_t &mt,
0168 const std::vector<TrackIndexType> &tips,
0169 std::map<TrackIndexType, double> &weights) {
0170
0171
0172
0173
0174 const auto minChi2 =
0175 mt.getTrackState(*std::min_element(tips.begin(), tips.end(),
0176 [&](const auto &a, const auto &b) {
0177 return mt.getTrackState(a).chi2() <
0178 mt.getTrackState(b).chi2();
0179 }))
0180 .chi2();
0181
0182
0183 for (auto tip : tips) {
0184 const auto state = mt.getTrackState(tip);
0185 const double chi2 = state.chi2() - minChi2;
0186 const double detR = calculateDeterminant(
0187 state.effectiveCalibratedCovariance().data(),
0188 state.predictedCovariance(), state.projectorSubspaceIndices(),
0189 state.calibratedSize());
0190
0191 if (detR <= 0) {
0192
0193 continue;
0194 }
0195
0196 const double factor = std::sqrt(1. / detR) * safeExp(-0.5 * chi2);
0197
0198 if (!std::isfinite(factor)) {
0199
0200 continue;
0201 }
0202
0203 weights.at(tip) *= factor;
0204 }
0205 }
0206
0207
0208
0209 enum class StatesType { ePredicted, eFiltered, eSmoothed };
0210
0211 inline std::ostream &operator<<(std::ostream &os, StatesType type) {
0212 constexpr static std::array names = {"predicted", "filtered", "smoothed"};
0213 os << names[static_cast<int>(type)];
0214 return os;
0215 }
0216
0217
0218
0219
0220 template <StatesType type, typename traj_t>
0221 struct MultiTrajectoryProjector {
0222 const traj_t &mt;
0223 const std::map<TrackIndexType, double> &weights;
0224
0225 auto operator()(TrackIndexType idx) const {
0226 const auto proxy = mt.getTrackState(idx);
0227 switch (type) {
0228 case StatesType::ePredicted:
0229 return std::tuple(weights.at(idx), proxy.predicted(),
0230 proxy.predictedCovariance());
0231 case StatesType::eFiltered:
0232 return std::tuple(weights.at(idx), proxy.filtered(),
0233 proxy.filteredCovariance());
0234 case StatesType::eSmoothed:
0235 return std::tuple(weights.at(idx), proxy.smoothed(),
0236 proxy.smoothedCovariance());
0237 default:
0238 throw std::invalid_argument(
0239 "Incorrect StatesType, should be ePredicted"
0240 ", eFiltered, or eSmoothed.");
0241 }
0242 }
0243 };
0244
0245
0246
0247
0248 template <typename T>
0249 class Updatable {
0250 T m_tmp{};
0251 T m_val{};
0252
0253 public:
0254 Updatable() : m_tmp(0), m_val(0) {}
0255
0256 T &tmp() { return m_tmp; }
0257 void update() { m_val = m_tmp; }
0258
0259 const T &val() const { return m_val; }
0260 };
0261
0262
0263
0264
0265
0266 void removeLowWeightComponents(std::vector<GsfComponent> &cmps,
0267 double weightCutoff);
0268
0269 template <typename traj_t>
0270 struct TemporaryStates {
0271 traj_t traj;
0272 std::vector<TrackIndexType> tips;
0273 std::map<TrackIndexType, double> weights;
0274
0275 void clear() {
0276 traj.clear();
0277 tips.clear();
0278 weights.clear();
0279 }
0280 };
0281
0282
0283 template <typename traj_t, typename propagator_state_t, typename stepper_t>
0284 void updateStepper(propagator_state_t &state, const stepper_t &stepper,
0285 const TemporaryStates<traj_t> &tmpStates,
0286 double weightCutoff) {
0287 auto cmps = stepper.componentIterable(state.stepping);
0288 for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0289
0290
0291 if (tmpStates.weights.at(idx) < weightCutoff) {
0292 cmp.status() = IntersectionStatus::unreachable;
0293 continue;
0294 }
0295
0296 auto proxy = tmpStates.traj.getTrackState(idx);
0297
0298 cmp.pars() = MultiTrajectoryHelpers::freeFiltered(state.geoContext, proxy);
0299 cmp.cov() = proxy.filteredCovariance();
0300 cmp.weight() = tmpStates.weights.at(idx);
0301 }
0302
0303 stepper.removeMissedComponents(state.stepping);
0304
0305
0306
0307 detail::Gsf::normalizeWeights(
0308 cmps, [&](auto cmp) -> double & { return cmp.weight(); });
0309 }
0310
0311
0312 template <typename propagator_state_t, typename stepper_t>
0313 void updateStepper(propagator_state_t &state, const stepper_t &stepper,
0314 const Surface &surface,
0315 const std::vector<GsfComponent> &componentCache,
0316 const Logger &logger) {
0317
0318 stepper.clearComponents(state.stepping);
0319
0320
0321 for (const auto &[weight, pars, cov] : componentCache) {
0322
0323 BoundTrackParameters bound(surface.getSharedPtr(), pars, cov,
0324 stepper.particleHypothesis(state.stepping));
0325
0326 auto res = stepper.addComponent(state.stepping, std::move(bound), weight);
0327
0328 if (!res.ok()) {
0329 ACTS_ERROR("Error adding component to MultiStepper");
0330 continue;
0331 }
0332
0333 auto &cmp = *res;
0334 auto freeParams = cmp.pars();
0335 cmp.jacToGlobal() = surface.boundToFreeJacobian(
0336 state.geoContext, freeParams.template segment<3>(eFreePos0),
0337 freeParams.template segment<3>(eFreeDir0));
0338 cmp.pathAccumulated() = state.stepping.pathAccumulated;
0339 cmp.jacobian() = BoundMatrix::Identity();
0340 cmp.derivative() = FreeVector::Zero();
0341 cmp.jacTransport() = FreeMatrix::Identity();
0342 }
0343 }
0344
0345 double applyBetheHeitler(
0346 const GeometryContext &geoContext, const Surface &surface,
0347 Direction direction, const BoundTrackParameters &initialParameters,
0348 double initialWeight, const BetheHeitlerApprox &betheHeitlerApprox,
0349 std::vector<BetheHeitlerApprox::Component> &betheHeitlerCache,
0350 double weightCutoff, std::vector<GsfComponent> &componentCache,
0351 std::size_t &nInvalidBetheHeitler, double &maxPathXOverX0,
0352 const Logger &logger);
0353
0354 template <typename traj_t, typename propagator_state_t, typename stepper_t>
0355 void convoluteComponents(
0356 propagator_state_t &state, const stepper_t &stepper,
0357 const TemporaryStates<traj_t> &tmpStates,
0358 const BetheHeitlerApprox &betheHeitlerApprox,
0359 std::vector<BetheHeitlerApprox::Component> &betheHeitlerCache,
0360 double weightCutoff, std::vector<GsfComponent> &componentCache,
0361 std::size_t &nInvalidBetheHeitler, double &maxPathXOverX0,
0362 double &sumPathXOverX0, const Logger &logger) {
0363 const GeometryContext &geoContext = state.options.geoContext;
0364 const Direction direction = state.options.direction;
0365
0366 double pathXOverX0 = 0.0;
0367 auto cmps = stepper.componentIterable(state.stepping);
0368 for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) {
0369 auto proxy = tmpStates.traj.getTrackState(idx);
0370 const Surface &surface = proxy.referenceSurface();
0371
0372 BoundTrackParameters bound(surface.getSharedPtr(), proxy.filtered(),
0373 proxy.filteredCovariance(),
0374 stepper.particleHypothesis(state.stepping));
0375
0376 pathXOverX0 += applyBetheHeitler(
0377 geoContext, surface, direction, bound, tmpStates.weights.at(idx),
0378 betheHeitlerApprox, betheHeitlerCache, weightCutoff, componentCache,
0379 nInvalidBetheHeitler, maxPathXOverX0, logger);
0380 }
0381
0382
0383
0384 sumPathXOverX0 += pathXOverX0 / tmpStates.tips.size();
0385 }
0386
0387
0388 template <typename propagator_state_t, typename stepper_t>
0389 void applyMultipleScattering(propagator_state_t &state,
0390 const stepper_t &stepper, const Surface &surface,
0391 const MaterialUpdateMode &updateMode,
0392 const Logger &logger) {
0393 for (auto cmp : stepper.componentIterable(state.stepping)) {
0394 auto singleState = cmp.singleState(state);
0395 const auto &singleStepper = cmp.singleStepper(stepper);
0396
0397 detail::performMaterialInteraction(singleState, singleStepper, surface,
0398 updateMode, NoiseUpdateMode::addNoise,
0399 true, false, logger);
0400
0401 assert(singleState.stepping.cov.array().isFinite().all() &&
0402 "covariance not finite after multi scattering");
0403 }
0404 }
0405
0406 }