File indexing completed on 2026-05-07 07:58:49
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/ParticleHypothesis.hpp"
0015 #include "Acts/Propagator/ConstrainedStep.hpp"
0016 #include "Acts/Propagator/NavigationTarget.hpp"
0017 #include "Acts/Propagator/StepperConcept.hpp"
0018 #include "Acts/Propagator/StepperStatistics.hpp"
0019 #include "Acts/Surfaces/CurvilinearSurface.hpp"
0020 #include "Acts/Utilities/Intersection.hpp"
0021 #include "Acts/Utilities/detail/periodic.hpp"
0022
0023 #include <sstream>
0024 #include <string>
0025 #include <tuple>
0026 #include <vector>
0027
0028 namespace Acts {
0029 class IVolumeMaterial;
0030 class MagneticFieldProvider;
0031 }
0032
0033 namespace Acts::Experimental {
0034
0035
0036
0037 using BoundParameterVariation = std::vector<std::pair<std::size_t, double>>;
0038
0039
0040 struct BoundParameterVariationGenerator {
0041 virtual ~BoundParameterVariationGenerator() = default;
0042
0043
0044
0045
0046
0047 virtual BoundParameterVariation variationMap(
0048 const BoundVector& input, const BoundMatrix& covariance) const = 0;
0049 };
0050
0051
0052 struct DeltaBoundParameterVariationGenerator
0053 : public BoundParameterVariationGenerator {
0054
0055 std::vector<BoundVector> deltas;
0056
0057
0058
0059 explicit DeltaBoundParameterVariationGenerator(const double delta)
0060 : deltas(1, BoundVector::Constant(delta)) {}
0061
0062
0063
0064 explicit DeltaBoundParameterVariationGenerator(
0065 const std::vector<double>& deltas_) {
0066 this->deltas.reserve(deltas_.size());
0067 for (const auto& delta : deltas_) {
0068 this->deltas.emplace_back(BoundVector::Constant(delta));
0069 }
0070 }
0071
0072
0073
0074 explicit DeltaBoundParameterVariationGenerator(const BoundVector& delta)
0075 : deltas(1, delta) {}
0076
0077
0078
0079 explicit DeltaBoundParameterVariationGenerator(
0080 std::vector<BoundVector> deltas_)
0081 : deltas(std::move(deltas_)) {}
0082
0083
0084
0085 BoundParameterVariation variationMap(
0086 const BoundVector& ,
0087 const BoundMatrix& ) const override {
0088 BoundParameterVariation variationMap;
0089 variationMap.reserve(deltas.size() * eBoundSize);
0090 for (std::size_t i = 0; i < deltas.size(); ++i) {
0091 for (std::size_t j = 0; j < eBoundSize; ++j) {
0092 variationMap.emplace_back(j, deltas[i][j]);
0093 }
0094 }
0095 return variationMap;
0096 }
0097 };
0098
0099
0100 struct CovarianceBoundParameterVariationGenerator
0101 : public BoundParameterVariationGenerator {
0102
0103 std::vector<BoundVector> sigmaFactors;
0104
0105
0106
0107 explicit CovarianceBoundParameterVariationGenerator(const double sigmaFactor)
0108 : sigmaFactors(1, BoundVector::Constant(sigmaFactor)) {}
0109
0110
0111
0112
0113 explicit CovarianceBoundParameterVariationGenerator(
0114 const std::vector<double>& sigmaFactors_) {
0115 sigmaFactors.reserve(sigmaFactors_.size());
0116 for (const auto& factor : sigmaFactors_) {
0117 sigmaFactors.emplace_back(BoundVector::Constant(factor));
0118 }
0119 }
0120
0121
0122
0123 explicit CovarianceBoundParameterVariationGenerator(
0124 const BoundVector& sigmaFactor)
0125 : sigmaFactors(1, sigmaFactor) {}
0126
0127
0128
0129 explicit CovarianceBoundParameterVariationGenerator(
0130 std::vector<BoundVector> sigmaFactors_)
0131 : sigmaFactors(std::move(sigmaFactors_)) {}
0132
0133
0134
0135
0136 BoundParameterVariation variationMap(
0137 const BoundVector& ,
0138 const BoundMatrix& covariance) const override {
0139 BoundParameterVariation variationMap;
0140 variationMap.reserve(sigmaFactors.size() * eBoundSize);
0141 for (std::size_t i = 0; i < sigmaFactors.size(); ++i) {
0142 for (std::size_t j = 0; j < eBoundSize; ++j) {
0143 variationMap.emplace_back(
0144 j, sigmaFactors[i][j] * std::sqrt(covariance(j, j)));
0145 }
0146 }
0147 return variationMap;
0148 }
0149 };
0150
0151
0152
0153
0154
0155
0156
0157
0158 template <Concepts::SingleStepper stepper_impl_t>
0159 class RiddersStepper final {
0160 public:
0161
0162 using StepperImpl = stepper_impl_t;
0163
0164
0165 using BoundParameters = BoundTrackParameters;
0166
0167 using Jacobian = BoundMatrix;
0168
0169 using Covariance = BoundMatrix;
0170
0171 using BoundState = std::tuple<BoundParameters, Jacobian, double>;
0172
0173
0174 struct Config {
0175
0176 StepperImpl::Config stepperConfig;
0177
0178
0179 std::shared_ptr<const BoundParameterVariationGenerator> parameterVariation{
0180 std::make_shared<CovarianceBoundParameterVariationGenerator>(
0181 std::vector<double>{-1e-3, 1e-3})};
0182
0183
0184
0185 std::size_t maxStepsToCurvilinearSurface = 10;
0186 };
0187
0188
0189 using Options = typename StepperImpl::Options;
0190
0191
0192 struct State {
0193
0194 explicit State(const StepperImpl::State& primaryStepperStateIn)
0195 : primaryStepperState(primaryStepperStateIn) {}
0196
0197
0198 StepperImpl::State primaryStepperState;
0199
0200
0201 std::vector<typename StepperImpl::State> secondaryStepperStates;
0202
0203
0204
0205 BoundParameterVariation variationMap;
0206
0207
0208 bool covTransport = false;
0209
0210 Covariance cov = Covariance::Zero();
0211
0212 Jacobian jacobian = Jacobian::Identity();
0213
0214
0215 double pathAccumulated = 0;
0216
0217
0218
0219 StepperStatistics statistics;
0220
0221
0222
0223 Direction lastStepPropagationDirection = Direction::Forward();
0224
0225 const IVolumeMaterial* lastStepMaterial = nullptr;
0226
0227 double lastSurfaceTolerance = 0.;
0228
0229 ConstrainedStep::Type lastStepConstraintType =
0230 ConstrainedStep::Type::Navigator;
0231 };
0232
0233
0234
0235 explicit RiddersStepper(StepperImpl stepperImpl)
0236 : m_stepperImpl(std::move(stepperImpl)) {}
0237
0238
0239
0240 explicit RiddersStepper(std::shared_ptr<const MagneticFieldProvider> bField)
0241 : m_stepperImpl(std::move(bField)) {}
0242
0243
0244
0245 explicit RiddersStepper(const Config& config)
0246 : m_config(config), m_stepperImpl(config.stepperConfig) {}
0247
0248
0249
0250
0251 State makeState(const Options& options) const {
0252 State state(m_stepperImpl.makeState(options));
0253 return state;
0254 }
0255
0256
0257
0258
0259
0260 void initialize(State& state, const BoundParameters& boundParameters) const {
0261 initialize(state, boundParameters.parameters(),
0262 boundParameters.covariance(),
0263 boundParameters.particleHypothesis(),
0264 boundParameters.referenceSurface());
0265 }
0266
0267
0268
0269
0270
0271
0272
0273
0274 void initialize(State& state, const BoundVector& boundVector,
0275 const std::optional<BoundMatrix>& covariance,
0276 ParticleHypothesis particleHypothesis,
0277 const Surface& surface) const {
0278 state.variationMap.clear();
0279 state.secondaryStepperStates.clear();
0280
0281 m_stepperImpl.initialize(state.primaryStepperState, boundVector,
0282 std::nullopt, particleHypothesis, surface);
0283
0284 if (!covariance.has_value()) {
0285 state.covTransport = false;
0286 state.cov = BoundMatrix::Zero();
0287
0288
0289
0290
0291 return;
0292 }
0293
0294 state.variationMap =
0295 m_config.parameterVariation->variationMap(boundVector, *covariance);
0296
0297 for (const auto& [index, delta] : state.variationMap) {
0298 BoundVector nudgedParams = boundVector;
0299 nudgedParams[index] += delta;
0300
0301 state.secondaryStepperStates.push_back(
0302 m_stepperImpl.makeState(state.primaryStepperState.options));
0303 m_stepperImpl.initialize(state.secondaryStepperStates.back(),
0304 nudgedParams, std::nullopt, particleHypothesis,
0305 surface);
0306 }
0307
0308 state.covTransport = true;
0309 state.cov = *covariance;
0310
0311
0312
0313
0314 }
0315
0316
0317
0318
0319
0320 Result<Vector3> getField(State& state, const Vector3& position) const {
0321 return m_stepperImpl.getField(state.primaryStepperState, position);
0322 }
0323
0324
0325
0326
0327 Vector3 position(const State& state) const {
0328 return m_stepperImpl.position(state.primaryStepperState);
0329 }
0330
0331
0332
0333
0334 Vector3 direction(const State& state) const {
0335 return m_stepperImpl.direction(state.primaryStepperState);
0336 }
0337
0338
0339
0340
0341 double qOverP(const State& state) const {
0342 return m_stepperImpl.qOverP(state.primaryStepperState);
0343 }
0344
0345
0346
0347
0348 double absoluteMomentum(const State& state) const {
0349 return m_stepperImpl.absoluteMomentum(state.primaryStepperState);
0350 }
0351
0352
0353
0354
0355 Vector3 momentum(const State& state) const {
0356 return m_stepperImpl.momentum(state.primaryStepperState);
0357 }
0358
0359
0360
0361
0362 double charge(const State& state) const {
0363 return m_stepperImpl.charge(state.primaryStepperState);
0364 }
0365
0366
0367
0368
0369 const ParticleHypothesis& particleHypothesis(const State& state) const {
0370 return m_stepperImpl.particleHypothesis(state.primaryStepperState);
0371 }
0372
0373
0374
0375
0376 double time(const State& state) const {
0377 return m_stepperImpl.time(state.primaryStepperState);
0378 }
0379
0380
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390
0391
0392 IntersectionStatus updateSurfaceStatus(
0393 State& state, const Surface& surface, std::uint8_t index,
0394 Direction navDir, const BoundaryTolerance& boundaryTolerance,
0395 double surfaceTolerance, ConstrainedStep::Type stype,
0396 const Logger& logger = getDummyLogger()) const {
0397 state.lastSurfaceTolerance = surfaceTolerance;
0398 state.lastStepConstraintType = stype;
0399
0400 IntersectionStatus overallStatus = m_stepperImpl.updateSurfaceStatus(
0401 state.primaryStepperState, surface, index, navDir, boundaryTolerance,
0402 surfaceTolerance, stype, logger);
0403 if (overallStatus == IntersectionStatus::unreachable) {
0404 return overallStatus;
0405 }
0406
0407 const auto combine = [](const IntersectionStatus a,
0408 const IntersectionStatus b) {
0409 using enum IntersectionStatus;
0410 if ((a == unreachable) || (b == unreachable)) {
0411 return unreachable;
0412 }
0413 if ((a == reachable) || (b == reachable)) {
0414 return reachable;
0415 }
0416 return onSurface;
0417 };
0418
0419 for (auto& secondaryState : state.secondaryStepperStates) {
0420 const IntersectionStatus status = m_stepperImpl.updateSurfaceStatus(
0421 secondaryState, surface, index, navDir, BoundaryTolerance::Infinite(),
0422 surfaceTolerance, stype, logger);
0423 overallStatus = combine(overallStatus, status);
0424 }
0425
0426 return overallStatus;
0427 }
0428
0429
0430
0431
0432
0433
0434
0435 void updateStepSize(State& state, const NavigationTarget& target,
0436 Direction direction, ConstrainedStep::Type stype) const {
0437 m_stepperImpl.updateStepSize(state.primaryStepperState, target, direction,
0438 stype);
0439
0440 for (auto& secondaryState : state.secondaryStepperStates) {
0441 m_stepperImpl.updateStepSize(secondaryState, target, direction, stype);
0442 }
0443 }
0444
0445
0446
0447
0448
0449
0450 void updateStepSize(State& state, double stepSize,
0451 ConstrainedStep::Type stype) const {
0452 m_stepperImpl.updateStepSize(state.primaryStepperState, stepSize, stype);
0453
0454 for (auto& secondaryState : state.secondaryStepperStates) {
0455 m_stepperImpl.updateStepSize(secondaryState, stepSize, stype);
0456 }
0457 }
0458
0459
0460
0461
0462
0463 double getStepSize(const State& state, ConstrainedStep::Type stype) const {
0464 return m_stepperImpl.getStepSize(state.primaryStepperState, stype);
0465 }
0466
0467
0468
0469
0470 void releaseStepSize(State& state, ConstrainedStep::Type stype) const {
0471 m_stepperImpl.releaseStepSize(state.primaryStepperState, stype);
0472
0473 for (auto& secondaryState : state.secondaryStepperStates) {
0474 m_stepperImpl.releaseStepSize(secondaryState, stype);
0475 }
0476 }
0477
0478
0479
0480
0481 std::string outputStepSize(const State& state) const {
0482 std::stringstream ss;
0483 ss << state.primaryStepperState.stepSize.toString();
0484 ss << " || ";
0485 for (const auto& secondaryState : state.secondaryStepperStates) {
0486 ss << secondaryState.stepSize.toString() << " | ";
0487 }
0488 return ss.str();
0489 }
0490
0491
0492
0493
0494
0495
0496
0497 Result<BoundState> boundState(
0498 State& state, const Surface& surface, bool transportCovariance = true,
0499 const FreeToBoundCorrection& freeToBoundCorrection =
0500 FreeToBoundCorrection(false)) const {
0501 Result<BoundState> primaryResult = m_stepperImpl.boundState(
0502 state.primaryStepperState, surface, false, freeToBoundCorrection);
0503
0504 if (!transportCovariance) {
0505 std::get<1>(*primaryResult) = state.jacobian;
0506 return primaryResult;
0507 }
0508
0509 std::vector<BoundVector> boundVectors;
0510 std::vector<double> pathLenghts;
0511 boundVectors.reserve(state.secondaryStepperStates.size());
0512 pathLenghts.reserve(state.secondaryStepperStates.size());
0513 for (auto& secondaryState : state.secondaryStepperStates) {
0514 const Result<BoundState> result = m_stepperImpl.boundState(
0515 secondaryState, surface, false, freeToBoundCorrection);
0516 if (!result.ok()) {
0517 return result.error();
0518 }
0519 boundVectors.push_back(std::get<0>(*result).parameters());
0520 pathLenghts.push_back(std::get<2>(*result));
0521 }
0522 const auto& referenceVector = std::get<0>(*primaryResult).parameters();
0523
0524 state.jacobian = BoundMatrix::Zero();
0525 std::array<std::size_t, eBoundSize> numberOfVariationsPerParameter{};
0526
0527 for (std::size_t i = 0; i < state.variationMap.size(); ++i) {
0528 const auto& [index, delta] = state.variationMap[i];
0529 const auto& nudgedVector = boundVectors[i];
0530
0531 const auto diff = [&]() {
0532 BoundVector d = nudgedVector - referenceVector;
0533 d[eBoundPhi] = detail::difference_periodic(nudgedVector[eBoundPhi],
0534 referenceVector[eBoundPhi],
0535 2 * std::numbers::pi);
0536 return d;
0537 };
0538
0539 state.jacobian.col(index) += diff() / delta;
0540 ++numberOfVariationsPerParameter[index];
0541 }
0542 for (std::size_t i = 0; i < eBoundSize; ++i) {
0543 state.jacobian.col(i) /= numberOfVariationsPerParameter[i];
0544 }
0545
0546 state.cov = state.jacobian * state.cov * state.jacobian.transpose();
0547
0548 BoundTrackParameters newParams(surface.getSharedPtr(), referenceVector,
0549 state.cov, particleHypothesis(state));
0550
0551 state.variationMap =
0552 m_config.parameterVariation->variationMap(referenceVector, state.cov);
0553
0554 return Result<BoundState>::success(
0555 BoundState{std::move(newParams), state.jacobian, pathLenghts.front()});
0556 }
0557
0558
0559
0560
0561 bool prepareCurvilinearState(State& state) const {
0562 bool result =
0563 m_stepperImpl.prepareCurvilinearState(state.primaryStepperState);
0564 for (auto& secondaryState : state.secondaryStepperStates) {
0565 result = result && m_stepperImpl.prepareCurvilinearState(secondaryState);
0566 }
0567 return result;
0568 }
0569
0570
0571
0572
0573
0574 BoundState curvilinearState(State& state,
0575 bool transportCovariance = true) const {
0576 if (!transportCovariance) {
0577 return m_stepperImpl.curvilinearState(state.primaryStepperState, false);
0578 }
0579
0580 const auto curvilinearSurface =
0581 CurvilinearSurface(position(state), direction(state)).surface();
0582
0583 for (std::size_t i = 0; i < m_config.maxStepsToCurvilinearSurface; ++i) {
0584 const auto surfaceStatus = updateSurfaceStatus(
0585 state, *curvilinearSurface, 0, state.lastStepPropagationDirection,
0586 BoundaryTolerance::Infinite(), state.lastSurfaceTolerance,
0587 state.lastStepConstraintType);
0588 if (surfaceStatus == IntersectionStatus::onSurface) {
0589 break;
0590 }
0591 if (surfaceStatus == IntersectionStatus::unreachable) {
0592 throw std::runtime_error(
0593 "RiddersStepper: Curvilinear surface is unreachable");
0594 }
0595 const auto stepResult = step(state, state.lastStepPropagationDirection,
0596 state.lastStepMaterial);
0597 if (!stepResult.ok()) {
0598 throw std::runtime_error(
0599 "RiddersStepper: Failed to step towards curvilinear surface: " +
0600 stepResult.error().message());
0601 }
0602 }
0603
0604 return boundState(state, *curvilinearSurface, true).value();
0605 }
0606
0607
0608
0609
0610
0611
0612 void update(State& state, const FreeVector& ,
0613 const BoundVector& boundParams, const BoundMatrix& covariance,
0614 const Surface& surface) const {
0615 initialize(state, boundParams, covariance, particleHypothesis(state),
0616 surface);
0617 }
0618
0619
0620
0621
0622
0623
0624
0625 void update(State& state, const Vector3& position, const Vector3& direction,
0626 double qOverP, double time) const {
0627 const auto curvilinearSurface =
0628 CurvilinearSurface(position, direction).surface();
0629 const std::optional<BoundMatrix> cov =
0630 state.covTransport ? std::optional<BoundMatrix>(state.cov)
0631 : std::nullopt;
0632 initialize(state,
0633 transformFreeToBoundParameters(
0634 position, time, direction, qOverP, *curvilinearSurface,
0635 state.primaryStepperState.options.geoContext)
0636 .value(),
0637 cov, particleHypothesis(state), *curvilinearSurface);
0638 }
0639
0640
0641
0642 void transportCovarianceToCurvilinear(State& state) const {
0643 curvilinearState(state, true);
0644 }
0645
0646
0647
0648
0649
0650 void transportCovarianceToBound(
0651 State& state, const Surface& surface,
0652 const FreeToBoundCorrection& freeToBoundCorrection =
0653 FreeToBoundCorrection(false)) const {
0654 boundState(state, surface, true, freeToBoundCorrection);
0655 }
0656
0657
0658
0659
0660
0661
0662 Result<double> step(State& state, Direction propagationDirection,
0663 const IVolumeMaterial* material) const {
0664 state.lastStepPropagationDirection = propagationDirection;
0665 state.lastStepMaterial = material;
0666
0667 const Result<double> stepResult = m_stepperImpl.step(
0668 state.primaryStepperState, propagationDirection, material);
0669 if (!stepResult.ok()) {
0670 return stepResult.error();
0671 }
0672 for (auto& secondaryState : state.secondaryStepperStates) {
0673 const Result<double> secondaryStepResult =
0674 m_stepperImpl.step(secondaryState, propagationDirection, material);
0675 if (!secondaryStepResult.ok()) {
0676 return secondaryStepResult.error();
0677 }
0678 }
0679 state.pathAccumulated += *stepResult;
0680 return *stepResult;
0681 }
0682
0683 private:
0684
0685 Config m_config;
0686
0687
0688 StepperImpl m_stepperImpl;
0689 };
0690
0691 }