File indexing completed on 2025-12-15 09:24:02
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Direction.hpp"
0013 #include "Acts/Definitions/TrackParametrization.hpp"
0014 #include "Acts/EventData/MultiComponentTrackParameters.hpp"
0015 #include "Acts/EventData/TrackParameters.hpp"
0016 #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
0017 #include "Acts/MagneticField/MagneticFieldProvider.hpp"
0018 #include "Acts/Material/IVolumeMaterial.hpp"
0019 #include "Acts/Propagator/ConstrainedStep.hpp"
0020 #include "Acts/Propagator/StepperConcept.hpp"
0021 #include "Acts/Propagator/StepperOptions.hpp"
0022 #include "Acts/Propagator/StepperStatistics.hpp"
0023 #include "Acts/Propagator/detail/LoopStepperUtils.hpp"
0024 #include "Acts/Propagator/detail/SteppingHelper.hpp"
0025 #include "Acts/Surfaces/Surface.hpp"
0026 #include "Acts/Utilities/Intersection.hpp"
0027 #include "Acts/Utilities/Logger.hpp"
0028 #include "Acts/Utilities/Result.hpp"
0029
0030 #include <algorithm>
0031 #include <cmath>
0032 #include <cstddef>
0033 #include <limits>
0034 #include <sstream>
0035 #include <vector>
0036
0037 #include <boost/container/small_vector.hpp>
0038
0039 namespace Acts {
0040
0041 using namespace Acts::UnitLiterals;
0042
0043 namespace detail {
0044
0045 struct MaxMomentumComponent {
0046 template <typename component_range_t>
0047 auto operator()(const component_range_t& cmps) const {
0048 return std::max_element(cmps.begin(), cmps.end(),
0049 [&](const auto& a, const auto& b) {
0050 return std::abs(a.state.pars[eFreeQOverP]) >
0051 std::abs(b.state.pars[eFreeQOverP]);
0052 });
0053 }
0054 };
0055
0056 struct MaxWeightComponent {
0057 template <typename component_range_t>
0058 auto operator()(const component_range_t& cmps) {
0059 return std::max_element(
0060 cmps.begin(), cmps.end(),
0061 [&](const auto& a, const auto& b) { return a.weight < b.weight; });
0062 }
0063 };
0064
0065 template <typename component_chooser_t>
0066 struct SingleComponentReducer {
0067 template <typename stepper_state_t>
0068 static Vector3 position(const stepper_state_t& s) {
0069 return component_chooser_t{}(s.components)
0070 ->state.pars.template segment<3>(eFreePos0);
0071 }
0072
0073 template <typename stepper_state_t>
0074 static Vector3 direction(const stepper_state_t& s) {
0075 return component_chooser_t{}(s.components)
0076 ->state.pars.template segment<3>(eFreeDir0);
0077 }
0078
0079 template <typename stepper_state_t>
0080 static double qOverP(const stepper_state_t& s) {
0081 const auto cmp = component_chooser_t{}(s.components);
0082 return cmp->state.pars[eFreeQOverP];
0083 }
0084
0085 template <typename stepper_state_t>
0086 static double absoluteMomentum(const stepper_state_t& s) {
0087 const auto cmp = component_chooser_t{}(s.components);
0088 return s.particleHypothesis.extractMomentum(cmp->state.pars[eFreeQOverP]);
0089 }
0090
0091 template <typename stepper_state_t>
0092 static Vector3 momentum(const stepper_state_t& s) {
0093 const auto cmp = component_chooser_t{}(s.components);
0094 return s.particleHypothesis.extractMomentum(cmp->state.pars[eFreeQOverP]) *
0095 cmp->state.pars.template segment<3>(eFreeDir0);
0096 }
0097
0098 template <typename stepper_state_t>
0099 static double charge(const stepper_state_t& s) {
0100 const auto cmp = component_chooser_t{}(s.components);
0101 return s.particleHypothesis.extractCharge(cmp->state.pars[eFreeQOverP]);
0102 }
0103
0104 template <typename stepper_state_t>
0105 static double time(const stepper_state_t& s) {
0106 return component_chooser_t{}(s.components)->state.pars[eFreeTime];
0107 }
0108
0109 template <typename stepper_state_t>
0110 static FreeVector pars(const stepper_state_t& s) {
0111 return component_chooser_t{}(s.components)->state.pars;
0112 }
0113
0114 template <typename stepper_state_t>
0115 static FreeVector cov(const stepper_state_t& s) {
0116 return component_chooser_t{}(s.components)->state.cov;
0117 }
0118 };
0119
0120 }
0121
0122 using MaxMomentumReducerLoop =
0123 detail::SingleComponentReducer<detail::MaxMomentumComponent>;
0124 using MaxWeightReducerLoop =
0125 detail::SingleComponentReducer<detail::MaxWeightComponent>;
0126
0127
0128
0129
0130
0131
0132
0133
0134
0135
0136
0137 template <Concepts::SingleStepper single_stepper_t,
0138 typename component_reducer_t = MaxWeightReducerLoop>
0139 class MultiStepperLoop : public single_stepper_t {
0140
0141
0142 std::size_t m_stepLimitAfterFirstComponentOnSurface = 50;
0143
0144
0145 std::unique_ptr<const Acts::Logger> m_logger;
0146
0147
0148
0149 template <typename T>
0150 using SmallVector = boost::container::small_vector<T, 16>;
0151
0152 public:
0153
0154 using SingleStepper = single_stepper_t;
0155
0156
0157 using SingleOptions = typename SingleStepper::Options;
0158
0159
0160 using SingleState = typename SingleStepper::State;
0161
0162
0163 using SingleConfig = typename SingleStepper::Config;
0164
0165
0166 using typename SingleStepper::Covariance;
0167 using typename SingleStepper::Jacobian;
0168
0169
0170 using BoundState =
0171 std::tuple<MultiComponentBoundTrackParameters, Jacobian, double>;
0172
0173
0174 using Reducer = component_reducer_t;
0175
0176
0177 static constexpr int maxComponents = std::numeric_limits<int>::max();
0178
0179 struct Config : public SingleStepper::Config {
0180
0181
0182 std::size_t stepLimitAfterFirstComponentOnSurface = 50;
0183 };
0184
0185 struct Options : public SingleOptions {
0186 Options(const GeometryContext& gctx, const MagneticFieldContext& mctx)
0187 : SingleOptions(gctx, mctx) {}
0188
0189 void setPlainOptions(const StepperPlainOptions& options) {
0190 static_cast<StepperPlainOptions&>(*this) = options;
0191 }
0192 };
0193
0194 struct State {
0195
0196 struct Component {
0197
0198 SingleState state;
0199
0200 double weight;
0201
0202 IntersectionStatus status;
0203
0204
0205
0206
0207
0208 Component(SingleState state_, double weight_, IntersectionStatus status_)
0209 : state(std::move(state_)), weight(weight_), status(status_) {}
0210 };
0211
0212 Options options;
0213
0214
0215 ParticleHypothesis particleHypothesis = ParticleHypothesis::pion();
0216
0217
0218 SmallVector<Component> components;
0219
0220 bool covTransport = false;
0221 double pathAccumulated = 0.;
0222 std::size_t steps = 0;
0223
0224
0225
0226 std::optional<std::size_t> stepCounterAfterFirstComponentOnSurface;
0227
0228
0229 StepperStatistics statistics;
0230
0231
0232
0233
0234
0235
0236 explicit State(const Options& optionsIn) : options(optionsIn) {}
0237 };
0238
0239
0240
0241
0242
0243 explicit MultiStepperLoop(std::shared_ptr<const MagneticFieldProvider> bField,
0244 std::unique_ptr<const Logger> logger =
0245 getDefaultLogger("GSF", Logging::INFO))
0246 : SingleStepper(std::move(bField)), m_logger(std::move(logger)) {}
0247
0248
0249
0250
0251 explicit MultiStepperLoop(const Config& config,
0252 std::unique_ptr<const Logger> logger =
0253 getDefaultLogger("MultiStepperLoop",
0254 Logging::INFO))
0255 : SingleStepper(config),
0256 m_stepLimitAfterFirstComponentOnSurface(
0257 config.stepLimitAfterFirstComponentOnSurface),
0258 m_logger(std::move(logger)) {}
0259
0260
0261
0262
0263 State makeState(const Options& options) const {
0264 State state(options);
0265 return state;
0266 }
0267
0268
0269
0270
0271 void initialize(State& state,
0272 const MultiComponentBoundTrackParameters& par) const {
0273 if (par.components().empty()) {
0274 throw std::invalid_argument(
0275 "Cannot construct MultiEigenStepperLoop::State with empty "
0276 "multi-component parameters");
0277 }
0278
0279 state.particleHypothesis = par.particleHypothesis();
0280
0281 const auto surface = par.referenceSurface().getSharedPtr();
0282
0283 for (auto i = 0ul; i < par.components().size(); ++i) {
0284 const auto& [weight, singlePars] = par[i];
0285 auto& cmp =
0286 state.components.emplace_back(SingleStepper::makeState(state.options),
0287 weight, IntersectionStatus::onSurface);
0288 SingleStepper::initialize(cmp.state, singlePars);
0289 }
0290
0291 if (std::get<2>(par.components().front())) {
0292 state.covTransport = true;
0293 }
0294 }
0295
0296
0297
0298
0299
0300 using ConstComponentProxy =
0301 detail::LoopComponentProxyBase<const typename State::Component,
0302 MultiStepperLoop>;
0303
0304
0305
0306
0307
0308 using ComponentProxy =
0309 detail::LoopComponentProxy<typename State::Component, MultiStepperLoop>;
0310
0311
0312
0313
0314
0315
0316
0317 auto componentIterable(State& state) const {
0318 struct Iterator {
0319 using difference_type [[maybe_unused]] = std::ptrdiff_t;
0320 using value_type [[maybe_unused]] = ComponentProxy;
0321 using reference [[maybe_unused]] = ComponentProxy;
0322 using pointer [[maybe_unused]] = void;
0323 using iterator_category [[maybe_unused]] = std::forward_iterator_tag;
0324
0325 typename decltype(state.components)::iterator it;
0326 const State& s;
0327
0328
0329 auto& operator++() { ++it; return *this; }
0330 auto operator==(const Iterator& other) const { return it == other.it; }
0331 auto operator*() const { return ComponentProxy(*it, s); }
0332
0333 };
0334
0335 struct Iterable {
0336 State& s;
0337
0338
0339 auto begin() { return Iterator{s.components.begin(), s}; }
0340 auto end() { return Iterator{s.components.end(), s}; }
0341
0342 };
0343
0344 return Iterable{state};
0345 }
0346
0347
0348
0349
0350
0351
0352
0353 auto constComponentIterable(const State& state) const {
0354 struct ConstIterator {
0355 using difference_type [[maybe_unused]] = std::ptrdiff_t;
0356 using value_type [[maybe_unused]] = ConstComponentProxy;
0357 using reference [[maybe_unused]] = ConstComponentProxy;
0358 using pointer [[maybe_unused]] = void;
0359 using iterator_category [[maybe_unused]] = std::forward_iterator_tag;
0360
0361 typename decltype(state.components)::const_iterator it;
0362 const State& s;
0363
0364
0365 auto& operator++() { ++it; return *this; }
0366 auto operator==(const ConstIterator& other) const { return it == other.it; }
0367 auto operator*() const { return ConstComponentProxy{*it}; }
0368
0369 };
0370
0371 struct Iterable {
0372 const State& s;
0373
0374
0375 auto begin() const { return ConstIterator{s.components.cbegin(), s}; }
0376 auto end() const { return ConstIterator{s.components.cend(), s}; }
0377
0378 };
0379
0380 return Iterable{state};
0381 }
0382
0383
0384
0385
0386
0387 std::size_t numberComponents(const State& state) const {
0388 return state.components.size();
0389 }
0390
0391
0392
0393
0394 void removeMissedComponents(State& state) const {
0395 auto new_end = std::remove_if(
0396 state.components.begin(), state.components.end(), [](const auto& cmp) {
0397 return cmp.status == IntersectionStatus::unreachable;
0398 });
0399
0400 state.components.erase(new_end, state.components.end());
0401 }
0402
0403
0404
0405
0406 void reweightComponents(State& state) const {
0407 double sumOfWeights = 0.0;
0408 for (const auto& cmp : state.components) {
0409 sumOfWeights += cmp.weight;
0410 }
0411 for (auto& cmp : state.components) {
0412 cmp.weight /= sumOfWeights;
0413 }
0414 }
0415
0416
0417
0418
0419 void clearComponents(State& state) const { state.components.clear(); }
0420
0421
0422
0423
0424
0425
0426
0427
0428
0429
0430
0431
0432
0433
0434 Result<ComponentProxy> addComponent(State& state,
0435 const BoundTrackParameters& pars,
0436 double weight) const {
0437 auto& cmp =
0438 state.components.emplace_back(SingleStepper::makeState(state.options),
0439 weight, IntersectionStatus::onSurface);
0440 SingleStepper::initialize(cmp.state, pars);
0441
0442 return ComponentProxy{state.components.back(), state};
0443 }
0444
0445
0446
0447
0448
0449
0450
0451
0452
0453
0454 Result<Vector3> getField(State& state, const Vector3& pos) const {
0455 return SingleStepper::getField(state.components.front().state, pos);
0456 }
0457
0458
0459
0460
0461
0462 Vector3 position(const State& state) const {
0463 return Reducer::position(state);
0464 }
0465
0466
0467
0468
0469
0470 Vector3 direction(const State& state) const {
0471 return Reducer::direction(state);
0472 }
0473
0474
0475
0476
0477
0478 double qOverP(const State& state) const { return Reducer::qOverP(state); }
0479
0480
0481
0482
0483
0484 double absoluteMomentum(const State& state) const {
0485 return Reducer::absoluteMomentum(state);
0486 }
0487
0488
0489
0490
0491
0492 Vector3 momentum(const State& state) const {
0493 return Reducer::momentum(state);
0494 }
0495
0496
0497
0498
0499
0500 double charge(const State& state) const { return Reducer::charge(state); }
0501
0502
0503
0504
0505
0506 ParticleHypothesis particleHypothesis(const State& state) const {
0507 return state.particleHypothesis;
0508 }
0509
0510
0511
0512
0513
0514 double time(const State& state) const { return Reducer::time(state); }
0515
0516
0517
0518
0519
0520
0521
0522
0523
0524
0525
0526
0527
0528
0529
0530 IntersectionStatus updateSurfaceStatus(
0531 State& state, const Surface& surface, std::uint8_t index,
0532 Direction navDir, const BoundaryTolerance& boundaryTolerance,
0533 double surfaceTolerance, ConstrainedStep::Type stype,
0534 const Logger& logger = getDummyLogger()) const {
0535 using Status = IntersectionStatus;
0536
0537 std::array<int, 3> counts = {0, 0, 0};
0538
0539 for (auto& component : state.components) {
0540 component.status = detail::updateSingleSurfaceStatus<SingleStepper>(
0541 *this, component.state, surface, index, navDir, boundaryTolerance,
0542 surfaceTolerance, stype, logger);
0543 ++counts[static_cast<std::size_t>(component.status)];
0544 }
0545
0546
0547
0548
0549 if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
0550 removeMissedComponents(state);
0551 reweightComponents(state);
0552 }
0553
0554 ACTS_VERBOSE("Component status wrt "
0555 << surface.geometryId() << " at {"
0556 << surface.center(state.options.geoContext).transpose()
0557 << "}:\t" << [&]() {
0558 std::stringstream ss;
0559 for (auto& component : state.components) {
0560 ss << component.status << "\t";
0561 }
0562 return ss.str();
0563 }());
0564
0565
0566
0567 if (!state.stepCounterAfterFirstComponentOnSurface &&
0568 counts[static_cast<std::size_t>(Status::onSurface)] > 0 &&
0569 counts[static_cast<std::size_t>(Status::reachable)] > 0) {
0570 state.stepCounterAfterFirstComponentOnSurface = 0;
0571 ACTS_VERBOSE("started stepCounterAfterFirstComponentOnSurface");
0572 }
0573
0574
0575
0576
0577 if (state.stepCounterAfterFirstComponentOnSurface &&
0578 counts[static_cast<std::size_t>(Status::onSurface)] == 0) {
0579 state.stepCounterAfterFirstComponentOnSurface.reset();
0580 ACTS_VERBOSE("switch off stepCounterAfterFirstComponentOnSurface");
0581 }
0582
0583
0584
0585
0586 if (counts[static_cast<std::size_t>(Status::reachable)] > 0) {
0587 return Status::reachable;
0588 } else if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
0589 state.stepCounterAfterFirstComponentOnSurface.reset();
0590 return Status::onSurface;
0591 } else {
0592 return Status::unreachable;
0593 }
0594 }
0595
0596
0597
0598
0599
0600
0601
0602
0603
0604
0605
0606
0607 template <typename object_intersection_t>
0608 void updateStepSize(State& state, const object_intersection_t& oIntersection,
0609 Direction direction, ConstrainedStep::Type stype) const {
0610 const Surface& surface = *oIntersection.object();
0611
0612 for (auto& component : state.components) {
0613 auto intersection = surface.intersect(
0614 component.state.options.geoContext,
0615 SingleStepper::position(component.state),
0616 direction * SingleStepper::direction(component.state),
0617 BoundaryTolerance::None())[oIntersection.index()];
0618
0619 SingleStepper::updateStepSize(component.state, intersection, direction,
0620 stype);
0621 }
0622 }
0623
0624
0625
0626
0627
0628
0629 void updateStepSize(State& state, double stepSize,
0630 ConstrainedStep::Type stype) const {
0631 for (auto& component : state.components) {
0632 SingleStepper::updateStepSize(component.state, stepSize, stype);
0633 }
0634 }
0635
0636
0637
0638
0639
0640
0641
0642
0643
0644 double getStepSize(const State& state, ConstrainedStep::Type stype) const {
0645 return std::min_element(state.components.begin(), state.components.end(),
0646 [=](const auto& a, const auto& b) {
0647 return std::abs(a.state.stepSize.value(stype)) <
0648 std::abs(b.state.stepSize.value(stype));
0649 })
0650 ->state.stepSize.value(stype);
0651 }
0652
0653
0654
0655
0656
0657 void releaseStepSize(State& state, ConstrainedStep::Type stype) const {
0658 for (auto& component : state.components) {
0659 SingleStepper::releaseStepSize(component.state, stype);
0660 }
0661 }
0662
0663
0664
0665
0666
0667 std::string outputStepSize(const State& state) const {
0668 std::stringstream ss;
0669 for (const auto& component : state.components) {
0670 ss << component.state.stepSize.toString() << " || ";
0671 }
0672
0673 return ss.str();
0674 }
0675
0676
0677
0678
0679
0680
0681
0682
0683
0684
0685
0686
0687
0688
0689
0690
0691
0692
0693
0694
0695
0696 Result<BoundState> boundState(
0697 State& state, const Surface& surface, bool transportCov = true,
0698 const FreeToBoundCorrection& freeToBoundCorrection =
0699 FreeToBoundCorrection(false)) const;
0700
0701
0702
0703
0704
0705
0706
0707
0708 bool prepareCurvilinearState(State& state) const {
0709 (void)state;
0710 return true;
0711 }
0712
0713
0714
0715
0716
0717
0718
0719
0720
0721
0722
0723
0724
0725
0726
0727 BoundState curvilinearState(State& state, bool transportCov = true) const;
0728
0729
0730
0731
0732
0733
0734 void transportCovarianceToCurvilinear(State& state) const {
0735 for (auto& component : state.components) {
0736 SingleStepper::transportCovarianceToCurvilinear(component.state);
0737 }
0738 }
0739
0740
0741
0742
0743
0744
0745
0746
0747
0748
0749
0750
0751 void transportCovarianceToBound(
0752 State& state, const Surface& surface,
0753 const FreeToBoundCorrection& freeToBoundCorrection =
0754 FreeToBoundCorrection(false)) const {
0755 for (auto& component : state.components) {
0756 SingleStepper::transportCovarianceToBound(component.state, surface,
0757 freeToBoundCorrection);
0758 }
0759 }
0760
0761
0762
0763
0764
0765
0766
0767
0768
0769
0770
0771 Result<double> step(State& state, Direction propDir,
0772 const IVolumeMaterial* material) const;
0773 };
0774
0775 }
0776
0777 #include "MultiStepperLoop.ipp"