File indexing completed on 2026-09-17 08:21:05
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Common.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/MultiTrajectory.hpp"
0014 #include "Acts/EventData/Types.hpp"
0015 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
0016 #include "Acts/Surfaces/Surface.hpp"
0017 #include "Acts/TrackFitting/BetheHeitlerApprox.hpp"
0018 #include "Acts/TrackFitting/GsfComponent.hpp"
0019 #include "Acts/TrackFitting/GsfOptions.hpp"
0020 #include "Acts/TrackFitting/detail/GsfComponentMerging.hpp"
0021 #include "Acts/TrackFitting/detail/GsfUtils.hpp"
0022 #include "Acts/Utilities/Helpers.hpp"
0023
0024 #include <map>
0025
0026 namespace Acts::detail::Gsf {
0027
0028 template <typename traj_t>
0029 struct GsfResult {
0030
0031 traj_t* fittedStates{nullptr};
0032
0033
0034 TrackIndexType currentTip = kTrackIndexInvalid;
0035
0036
0037 TrackIndexType lastMeasurementTip = kTrackIndexInvalid;
0038
0039
0040
0041 std::vector<std::tuple<double, BoundVector, BoundMatrix>>
0042 lastMeasurementComponents;
0043
0044
0045 const Acts::Surface* lastMeasurementSurface = nullptr;
0046
0047
0048 std::size_t measurementStates = 0;
0049 std::size_t measurementHoles = 0;
0050 std::size_t processedStates = 0;
0051
0052 std::vector<const Surface*> visitedSurfaces;
0053 std::vector<const Surface*> surfacesVisitedBwdAgain;
0054
0055
0056 Updatable<std::size_t> nInvalidBetheHeitler;
0057 Updatable<double> maxPathXOverX0;
0058 Updatable<double> sumPathXOverX0;
0059
0060
0061 std::vector<BetheHeitlerApprox::Component> betheHeitlerCache;
0062
0063
0064 std::vector<GsfComponent> componentCache;
0065 };
0066
0067
0068 template <typename traj_t>
0069 struct GsfActor {
0070
0071 GsfActor() = default;
0072
0073
0074 using result_type = GsfResult<traj_t>;
0075
0076
0077 struct Config {
0078
0079 std::size_t maxComponents = 16;
0080
0081
0082 const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
0083
0084
0085
0086 const BetheHeitlerApprox* bethe_heitler_approx = nullptr;
0087
0088
0089 bool multipleScattering = true;
0090
0091
0092 double weightCutoff = 1.0e-4;
0093
0094
0095
0096
0097 bool disableAllMaterialHandling = false;
0098
0099
0100 bool abortOnError = false;
0101
0102
0103
0104 std::optional<std::size_t> numberMeasurements;
0105
0106
0107 GsfExtensions<traj_t> extensions;
0108
0109
0110
0111
0112 bool inReversePass = false;
0113
0114
0115 ComponentMergeMethod mergeMethod = ComponentMergeMethod::eMaxWeight;
0116
0117 const Logger* logger{nullptr};
0118
0119
0120 const CalibrationContext* calibrationContext{nullptr};
0121
0122 } m_cfg;
0123
0124 const Logger& logger() const { return *m_cfg.logger; }
0125
0126 using TemporaryStates = detail::Gsf::TemporaryStates<traj_t>;
0127
0128 using FiltProjector = MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0129
0130
0131
0132
0133
0134
0135
0136
0137
0138
0139 template <typename propagator_state_t, typename stepper_t,
0140 typename navigator_t>
0141 Result<void> act(propagator_state_t& state, const stepper_t& stepper,
0142 const navigator_t& navigator, result_type& result,
0143 const Logger& ) const {
0144 assert(result.fittedStates && "No MultiTrajectory set");
0145
0146
0147
0148 const ScopedGsfInfoPrinterAndChecker printer(state, stepper, navigator,
0149 logger());
0150
0151
0152 if (navigator.currentSurface(state.navigation) == nullptr) {
0153 return Result<void>::success();
0154 }
0155
0156 const auto& surface = *navigator.currentSurface(state.navigation);
0157 ACTS_VERBOSE("Step is at surface " << surface.geometryId());
0158
0159
0160
0161 [[maybe_unused]] auto stepperComponents =
0162 stepper.constComponentIterable(state.stepping);
0163 assert(weightsAreNormalized(stepperComponents,
0164 [](const auto& cmp) { return cmp.weight(); }));
0165
0166
0167
0168
0169 using Status [[maybe_unused]] = IntersectionStatus;
0170 assert(std::all_of(
0171 stepperComponents.begin(), stepperComponents.end(),
0172 [](const auto& cmp) { return cmp.status() == Status::onSurface; }));
0173
0174
0175
0176 const bool visited = rangeContainsValue(result.visitedSurfaces, &surface);
0177
0178 if (visited) {
0179 ACTS_VERBOSE("Already visited surface, return");
0180 return Result<void>::success();
0181 }
0182
0183 result.visitedSurfaces.push_back(&surface);
0184
0185
0186 const auto foundSourceLink =
0187 m_cfg.inputMeasurements->find(surface.geometryId());
0188 const bool haveMaterial =
0189 surface.hasMaterial() && !m_cfg.disableAllMaterialHandling;
0190 const bool haveMeasurement =
0191 foundSourceLink != m_cfg.inputMeasurements->end();
0192
0193 ACTS_VERBOSE(std::boolalpha << "haveMaterial " << haveMaterial
0194 << ", haveMeasurement: " << haveMeasurement);
0195
0196
0197
0198
0199
0200
0201 if (!haveMaterial && !haveMeasurement) {
0202
0203 if (result.processedStates > 0 && surface.isSensitive()) {
0204 TemporaryStates tmpStates;
0205 Result<void> res = noMeasurementUpdate(state, stepper, surface, result,
0206 tmpStates, true);
0207 if (!res.ok()) {
0208 if (m_cfg.abortOnError) {
0209 std::abort();
0210 }
0211 return res.error();
0212 }
0213 }
0214 return Result<void>::success();
0215 }
0216
0217
0218
0219
0220 if (haveMeasurement) {
0221 result.maxPathXOverX0.update();
0222 result.sumPathXOverX0.update();
0223 result.nInvalidBetheHeitler.update();
0224 }
0225
0226 for (auto cmp : stepper.componentIterable(state.stepping)) {
0227 Result<void> transportRes =
0228 cmp.singleStepper(stepper).transportCovarianceToBound(cmp.state(),
0229 surface);
0230 if (!transportRes.ok()) {
0231 if (m_cfg.abortOnError) {
0232 std::abort();
0233 }
0234 return transportRes.error();
0235 }
0236 }
0237
0238 if (m_cfg.multipleScattering && haveMaterial) {
0239 if (haveMeasurement) {
0240 const Result<void> materialInteractionRes = applyMultipleScattering(
0241 state, stepper, surface,
0242 determineMaterialUpdateMode(state, navigator,
0243 MaterialUpdateMode::PreUpdate),
0244 logger());
0245 if (!materialInteractionRes.ok()) {
0246 return materialInteractionRes.error();
0247 }
0248 } else {
0249 const Result<void> materialInteractionRes = applyMultipleScattering(
0250 state, stepper, surface,
0251 determineMaterialUpdateMode(state, navigator,
0252 MaterialUpdateMode::FullUpdate),
0253 logger());
0254 if (!materialInteractionRes.ok()) {
0255 return materialInteractionRes.error();
0256 }
0257 }
0258 }
0259
0260
0261
0262
0263 if (!haveMaterial) {
0264 TemporaryStates tmpStates;
0265
0266 auto res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0267 foundSourceLink->second);
0268
0269 if (!res.ok()) {
0270 if (m_cfg.abortOnError) {
0271 std::abort();
0272 }
0273 return res.error();
0274 }
0275
0276 updateStepper(state, stepper, tmpStates, m_cfg.weightCutoff);
0277 }
0278
0279
0280
0281 else {
0282 TemporaryStates tmpStates;
0283 Result<void> res;
0284
0285 if (haveMeasurement) {
0286 res = kalmanUpdate(state, stepper, surface, result, tmpStates,
0287 foundSourceLink->second);
0288 } else {
0289 res = noMeasurementUpdate(state, stepper, surface, result, tmpStates,
0290 false);
0291 }
0292
0293 if (!res.ok()) {
0294 if (m_cfg.abortOnError) {
0295 std::abort();
0296 }
0297 return res.error();
0298 }
0299
0300
0301 std::vector<GsfComponent>& componentCache = result.componentCache;
0302 componentCache.clear();
0303
0304 double pathXOverX0 = 0.0;
0305 for (const TrackIndexType idx : tmpStates.tips) {
0306 auto proxy = tmpStates.traj.getTrackState(idx);
0307
0308 const BoundTrackParameters bound(
0309 surface.getSharedPtr(), proxy.filtered(),
0310 proxy.filteredCovariance(),
0311 stepper.particleHypothesis(state.stepping));
0312
0313 pathXOverX0 += applyBetheHeitler(
0314 state.options.geoContext, surface, state.options.direction, bound,
0315 tmpStates.weights.at(idx), *m_cfg.bethe_heitler_approx,
0316 result.betheHeitlerCache, m_cfg.weightCutoff, componentCache,
0317 result.nInvalidBetheHeitler.tmp(), result.maxPathXOverX0.tmp(),
0318 logger());
0319 }
0320
0321
0322 result.sumPathXOverX0.tmp() += pathXOverX0 / tmpStates.tips.size();
0323
0324 if (componentCache.empty()) {
0325 ACTS_WARNING(
0326 "No components left after applying energy loss. "
0327 "Is the weight cutoff "
0328 << m_cfg.weightCutoff << " too high?");
0329 ACTS_WARNING("Return to propagator without applying energy loss");
0330 return Result<void>::success();
0331 }
0332
0333
0334 const auto finalCmpNumber = std::min(
0335 static_cast<std::size_t>(stepper.maxComponents), m_cfg.maxComponents);
0336 m_cfg.extensions.mixtureReducer(componentCache, finalCmpNumber, surface);
0337
0338 removeLowWeightComponents(componentCache, m_cfg.weightCutoff);
0339
0340 updateStepper(state, stepper, surface, componentCache);
0341 }
0342
0343
0344 if (m_cfg.multipleScattering && haveMaterial && haveMeasurement) {
0345 const Result<void> materialInteractionRes = applyMultipleScattering(
0346 state, stepper, surface,
0347 determineMaterialUpdateMode(state, navigator,
0348 MaterialUpdateMode::PostUpdate),
0349 logger());
0350 if (!materialInteractionRes.ok()) {
0351 return materialInteractionRes.error();
0352 }
0353 }
0354
0355 return Result<void>::success();
0356 }
0357
0358 template <typename propagator_state_t, typename stepper_t,
0359 typename navigator_t>
0360 bool checkAbort(propagator_state_t& , const stepper_t& ,
0361 const navigator_t& , const result_type& result,
0362 const Logger& ) const {
0363 if (m_cfg.numberMeasurements &&
0364 result.measurementStates == m_cfg.numberMeasurements) {
0365 ACTS_VERBOSE("Stop navigation because all measurements are found");
0366 return true;
0367 }
0368
0369 return false;
0370 }
0371
0372
0373
0374 template <typename propagator_state_t, typename stepper_t>
0375 Result<void> kalmanUpdate(propagator_state_t& state, const stepper_t& stepper,
0376 const Surface& surface, result_type& result,
0377 TemporaryStates& tmpStates,
0378 const SourceLink& sourceLink) const {
0379
0380 std::vector<TrackIndexType> allTips;
0381 allTips.reserve(stepper.numberComponents(state.stepping));
0382
0383 for (auto cmp : stepper.componentIterable(state.stepping)) {
0384 auto singleState = cmp.singleState(state);
0385 const auto& singleStepper = cmp.singleStepper(stepper);
0386
0387
0388
0389
0390
0391
0392
0393 TrackStatePropMask mask = TrackStatePropMask::Predicted |
0394 TrackStatePropMask::Jacobian |
0395 TrackStatePropMask::Calibrated;
0396 typename traj_t::TrackStateProxy trackStateProxy =
0397 tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0398 typename traj_t::ConstTrackStateProxy trackStateProxyConst{
0399 trackStateProxy};
0400
0401
0402
0403 {
0404 trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0405
0406 auto res =
0407 singleStepper.boundState(singleState.stepping, surface, false);
0408 if (!res.ok()) {
0409 ACTS_DEBUG("Propagate to surface " << surface.geometryId()
0410 << " failed: " << res.error());
0411 return res.error();
0412 }
0413 const auto& [boundParams, jacobian, pathLength] = *res;
0414
0415
0416 trackStateProxy.predicted() = boundParams.parameters();
0417 trackStateProxy.predictedCovariance() = singleState.stepping.cov;
0418
0419 trackStateProxy.jacobian() = jacobian;
0420 trackStateProxy.pathLength() = pathLength;
0421 }
0422
0423
0424
0425 m_cfg.extensions.calibrator(state.geoContext, *m_cfg.calibrationContext,
0426 sourceLink, trackStateProxy);
0427
0428 if (!m_cfg.extensions.outlierFinder(trackStateProxyConst)) {
0429
0430 trackStateProxy.addComponents(TrackStatePropMask::Filtered);
0431
0432 auto updateRes = m_cfg.extensions.updater(state.geoContext,
0433 trackStateProxy, logger());
0434 if (!updateRes.ok()) {
0435 ACTS_DEBUG("Update step failed: " << updateRes.error());
0436 return updateRes.error();
0437 }
0438
0439 tmpStates.tips.push_back(trackStateProxy.index());
0440 tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0441 }
0442
0443 allTips.push_back(trackStateProxy.index());
0444 }
0445
0446 const bool isOutlier = tmpStates.tips.empty();
0447
0448 if (!isOutlier) {
0449 computePosteriorWeights(tmpStates.traj, tmpStates.tips,
0450 tmpStates.weights);
0451 normalizeWeights(tmpStates.tips, [&](auto idx) -> double& {
0452 return tmpStates.weights.at(idx);
0453 });
0454 } else {
0455 auto cmps = stepper.componentIterable(state.stepping);
0456 for (const auto [cmp, idx] : zip(cmps, allTips)) {
0457 typename traj_t::TrackStateProxy trackStateProxy =
0458 tmpStates.traj.getTrackState(idx);
0459
0460
0461
0462 trackStateProxy.shareFrom(trackStateProxy,
0463 TrackStatePropMask::Predicted,
0464 TrackStatePropMask::Filtered);
0465
0466 tmpStates.tips.push_back(trackStateProxy.index());
0467 tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0468 }
0469 }
0470
0471
0472 ++result.processedStates;
0473 if (!isOutlier) {
0474 ++result.measurementStates;
0475 }
0476
0477 updateMultiTrajectory(result, tmpStates, surface,
0478 TrackStateType()
0479 .setHasParameters()
0480 .setHasMaterial(surface.hasMaterial())
0481 .setHasMeasurement()
0482 .setIsOutlier(isOutlier));
0483
0484 result.lastMeasurementTip = result.currentTip;
0485 result.lastMeasurementSurface = &surface;
0486
0487
0488
0489 result.lastMeasurementComponents.clear();
0490
0491 FiltProjector proj{tmpStates.traj, tmpStates.weights};
0492 for (const auto& idx : tmpStates.tips) {
0493 const auto& [w, p, c] = proj(idx);
0494
0495 if (w > 0.0) {
0496 result.lastMeasurementComponents.push_back({w, p, c});
0497 }
0498 }
0499
0500
0501 return Result<void>::success();
0502 }
0503
0504 template <typename propagator_state_t, typename stepper_t>
0505 Result<void> noMeasurementUpdate(propagator_state_t& state,
0506 const stepper_t& stepper,
0507 const Surface& surface, result_type& result,
0508 TemporaryStates& tmpStates,
0509 bool doCovTransport) const {
0510 for (auto cmp : stepper.componentIterable(state.stepping)) {
0511 auto& singleState = cmp.state();
0512 const auto& singleStepper = cmp.singleStepper(stepper);
0513
0514
0515
0516 TrackStatePropMask mask =
0517 TrackStatePropMask::Predicted | TrackStatePropMask::Jacobian;
0518 typename traj_t::TrackStateProxy trackStateProxy =
0519 tmpStates.traj.makeTrackState(mask, kTrackIndexInvalid);
0520
0521
0522
0523 {
0524 trackStateProxy.setReferenceSurface(surface.getSharedPtr());
0525
0526 auto res =
0527 singleStepper.boundState(singleState, surface, doCovTransport);
0528 if (!res.ok()) {
0529 return res.error();
0530 }
0531 const auto& [boundParams, jacobian, pathLength] = *res;
0532
0533
0534 trackStateProxy.predicted() = boundParams.parameters();
0535 trackStateProxy.predictedCovariance() = singleState.cov;
0536
0537 trackStateProxy.jacobian() = jacobian;
0538 trackStateProxy.pathLength() = pathLength;
0539
0540
0541
0542 trackStateProxy.shareFrom(trackStateProxy,
0543 TrackStatePropMask::Predicted,
0544 TrackStatePropMask::Filtered);
0545 }
0546
0547 tmpStates.tips.push_back(trackStateProxy.index());
0548 tmpStates.weights[trackStateProxy.index()] = cmp.weight();
0549 }
0550
0551 const bool precedingMeasurementExists = result.processedStates > 0;
0552 const bool isHole = surface.isSensitive();
0553
0554
0555 ++result.processedStates;
0556 if (precedingMeasurementExists && isHole) {
0557 ++result.measurementHoles;
0558 }
0559
0560 updateMultiTrajectory(result, tmpStates, surface,
0561 TrackStateType()
0562 .setHasParameters()
0563 .setHasMaterial(surface.hasMaterial())
0564 .setIsHole(isHole));
0565
0566 return Result<void>::success();
0567 }
0568
0569 void updateMultiTrajectory(result_type& result,
0570 const TemporaryStates& tmpStates,
0571 const Surface& surface,
0572 TrackStateType type) const {
0573 using PrtProjector =
0574 MultiTrajectoryProjector<StatesType::ePredicted, traj_t>;
0575 using FltProjector =
0576 MultiTrajectoryProjector<StatesType::eFiltered, traj_t>;
0577
0578 if (!m_cfg.inReversePass) {
0579 assert(!tmpStates.tips.empty() &&
0580 "No components to update multi-trajectory");
0581
0582 const auto firstCmpProxy =
0583 tmpStates.traj.getTrackState(tmpStates.tips.front());
0584
0585
0586
0587 auto combinedStateMask = TrackStatePropMask::Predicted;
0588 if (type.isMeasurement()) {
0589 combinedStateMask |=
0590 TrackStatePropMask::Calibrated | TrackStatePropMask::Filtered;
0591 } else if (type.isOutlier()) {
0592 combinedStateMask |= TrackStatePropMask::Calibrated;
0593 }
0594 auto combinedState = result.fittedStates->makeTrackState(
0595 combinedStateMask, result.currentTip);
0596 result.currentTip = combinedState.index();
0597
0598
0599 auto copyMask = TrackStatePropMask::None;
0600 if (ACTS_CHECK_BIT(combinedStateMask, TrackStatePropMask::Calibrated)) {
0601
0602 copyMask |= TrackStatePropMask::Calibrated;
0603 }
0604 combinedState.copyFrom(firstCmpProxy, copyMask);
0605 combinedState.typeFlags() = type;
0606
0607 auto [prtMean, prtCov] = mergeGaussianMixture(
0608 tmpStates.tips, PrtProjector{tmpStates.traj, tmpStates.weights},
0609 surface, m_cfg.mergeMethod);
0610 combinedState.predicted() = prtMean;
0611 combinedState.predictedCovariance() = prtCov;
0612
0613 if (type.isMeasurement()) {
0614 auto [fltMean, fltCov] = mergeGaussianMixture(
0615 tmpStates.tips, FltProjector{tmpStates.traj, tmpStates.weights},
0616 surface, m_cfg.mergeMethod);
0617 combinedState.filtered() = fltMean;
0618 combinedState.filteredCovariance() = fltCov;
0619 } else {
0620 combinedState.shareFrom(TrackStatePropMask::Predicted,
0621 TrackStatePropMask::Filtered);
0622 }
0623
0624 } else {
0625 assert((result.currentTip != kTrackIndexInvalid && "tip not valid"));
0626
0627 result.fittedStates->applyBackwards(
0628 result.currentTip, [&](auto trackState) {
0629 if (&trackState.referenceSurface() != &surface) {
0630 return true;
0631 }
0632
0633 result.surfacesVisitedBwdAgain.push_back(&surface);
0634
0635
0636
0637 if (trackState.typeFlags().isMeasurement()) {
0638 const auto [smtMean, smtCov] = mergeGaussianMixture(
0639 tmpStates.tips,
0640 FltProjector{tmpStates.traj, tmpStates.weights}, surface,
0641 m_cfg.mergeMethod);
0642
0643 trackState.addComponents(TrackStatePropMask::Smoothed);
0644 trackState.smoothed() = smtMean;
0645 trackState.smoothedCovariance() = smtCov;
0646 }
0647
0648 return false;
0649 });
0650 }
0651 }
0652
0653
0654
0655 void setOptions(const GsfOptions<traj_t>& options) {
0656 m_cfg.maxComponents = options.maxComponents;
0657 m_cfg.extensions = options.extensions;
0658 m_cfg.abortOnError = options.abortOnError;
0659 m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling;
0660 m_cfg.weightCutoff = options.weightCutoff;
0661 m_cfg.mergeMethod = options.componentMergeMethod;
0662 m_cfg.calibrationContext = &options.calibrationContext.get();
0663 }
0664 };
0665
0666 }