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