Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
MultiEigenStepperLoop.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file MultiEigenStepperLoop.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2021 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #pragma once
10 
11 // Workaround for building on clang+libstdc++
13 
30 
31 #include <algorithm>
32 #include <cmath>
33 #include <cstddef>
34 #include <functional>
35 #include <limits>
36 #include <numeric>
37 #include <sstream>
38 #include <vector>
39 
40 #include <boost/container/small_vector.hpp>
41 
42 #include "MultiStepperError.hpp"
43 
44 namespace Acts {
45 
46 using namespace Acts::UnitLiterals;
47 
51  template <typename component_range_t>
52  static Vector3 toVector3(const component_range_t& comps,
53  const FreeIndices i) {
54  return std::accumulate(
55  comps.begin(), comps.end(), Vector3{Vector3::Zero()},
56  [i](const auto& sum, const auto& cmp) -> Vector3 {
57  return sum + cmp.weight * cmp.state.pars.template segment<3>(i);
58  });
59  }
60 
61  template <typename stepper_state_t>
62  static Vector3 position(const stepper_state_t& s) {
63  return toVector3(s.components, eFreePos0);
64  }
65 
66  template <typename stepper_state_t>
67  static Vector3 direction(const stepper_state_t& s) {
68  return toVector3(s.components, eFreeDir0).normalized();
69  }
70 
71  // TODO: Maybe we can cache this value and only update it when the parameters
72  // change
73  template <typename stepper_state_t>
74  static ActsScalar qOverP(const stepper_state_t& s) {
75  return std::accumulate(
76  s.components.begin(), s.components.end(), ActsScalar{0.},
77  [](const auto& sum, const auto& cmp) -> ActsScalar {
78  return sum + cmp.weight * cmp.state.pars[eFreeQOverP];
79  });
80  }
81 
82  template <typename stepper_state_t>
83  static ActsScalar absoluteMomentum(const stepper_state_t& s) {
84  return std::accumulate(
85  s.components.begin(), s.components.end(), ActsScalar{0.},
86  [&s](const auto& sum, const auto& cmp) -> ActsScalar {
87  return sum + cmp.weight * s.particleHypothesis.extractMomentum(
88  cmp.state.pars[eFreeQOverP]);
89  });
90  }
91 
92  template <typename stepper_state_t>
93  static Vector3 momentum(const stepper_state_t& s) {
94  return std::accumulate(
95  s.components.begin(), s.components.end(), Vector3::Zero().eval(),
96  [&s](const auto& sum, const auto& cmp) -> Vector3 {
97  return sum + cmp.weight *
98  s.particleHypothesis.extractMomentum(
99  cmp.state.pars[eFreeQOverP]) *
100  cmp.state.pars.template segment<3>(eFreeDir0);
101  });
102  }
103 
104  template <typename stepper_state_t>
105  static ActsScalar charge(const stepper_state_t& s) {
106  return std::accumulate(
107  s.components.begin(), s.components.end(), ActsScalar{0.},
108  [&s](const auto& sum, const auto& cmp) -> ActsScalar {
109  return sum + cmp.weight * s.particleHypothesis.extractCharge(
110  cmp.state.pars[eFreeQOverP]);
111  });
112  }
113 
114  template <typename stepper_state_t>
115  static ActsScalar time(const stepper_state_t& s) {
116  return std::accumulate(
117  s.components.begin(), s.components.end(), ActsScalar{0.},
118  [](const auto& sum, const auto& cmp) -> ActsScalar {
119  return sum + cmp.weight * cmp.state.pars[eFreeTime];
120  });
121  }
122 
123  template <typename stepper_state_t>
124  static FreeVector pars(const stepper_state_t& s) {
125  return std::accumulate(s.components.begin(), s.components.end(),
126  FreeVector{FreeVector::Zero()},
127  [](const auto& sum, const auto& cmp) -> FreeVector {
128  return sum + cmp.weight * cmp.state.pars;
129  });
130  }
131 
132  template <typename stepper_state_t>
133  static FreeVector cov(const stepper_state_t& s) {
134  return std::accumulate(s.components.begin(), s.components.end(),
135  FreeMatrix{FreeMatrix::Zero()},
136  [](const auto& sum, const auto& cmp) -> FreeMatrix {
137  return sum + cmp.weight * cmp.state.cov;
138  });
139  }
140 };
141 
143  template <typename component_range_t>
144  static const auto& maxAbsoluteMomentumIt(const component_range_t& cmps) {
145  return *std::max_element(cmps.begin(), cmps.end(),
146  [&](const auto& a, const auto& b) {
147  return std::abs(a.state.pars[eFreeQOverP]) >
148  std::abs(b.state.pars[eFreeQOverP]);
149  });
150  }
151 
152  template <typename stepper_state_t>
153  static Vector3 position(const stepper_state_t& s) {
154  return maxAbsoluteMomentumIt(s.components)
155  .state.pars.template segment<3>(eFreePos0);
156  }
157 
158  template <typename stepper_state_t>
159  static Vector3 direction(const stepper_state_t& s) {
160  return maxAbsoluteMomentumIt(s.components)
161  .state.pars.template segment<3>(eFreeDir0);
162  }
163 
164  template <typename stepper_state_t>
165  static ActsScalar qOverP(const stepper_state_t& s) {
166  const auto& cmp = maxAbsoluteMomentumIt(s.components);
167  return cmp.state.pars[eFreeQOverP];
168  }
169 
170  template <typename stepper_state_t>
171  static ActsScalar absoluteMomentum(const stepper_state_t& s) {
172  const auto& cmp = maxAbsoluteMomentumIt(s.components);
173  return std::abs(cmp.state.absCharge / cmp.state.pars[eFreeQOverP]);
174  }
175 
176  template <typename stepper_state_t>
177  static Vector3 momentum(const stepper_state_t& s) {
178  const auto& cmp = maxAbsoluteMomentumIt(s.components);
179  return std::abs(cmp.state.absCharge / cmp.state.pars[eFreeQOverP]) *
180  cmp.state.pars.template segment<3>(eFreeDir0);
181  }
182 
183  template <typename stepper_state_t>
184  static ActsScalar charge(const stepper_state_t& s) {
185  return maxAbsoluteMomentumIt(s.components).state.absCharge;
186  }
187 
188  template <typename stepper_state_t>
189  static ActsScalar time(const stepper_state_t& s) {
190  return maxAbsoluteMomentumIt(s.components).state.pars[eFreeTime];
191  }
192 
193  template <typename stepper_state_t>
194  static FreeVector pars(const stepper_state_t& s) {
195  return maxAbsoluteMomentumIt(s.components).state.pars;
196  }
197 
198  template <typename stepper_state_t>
199  static FreeVector cov(const stepper_state_t& s) {
200  return maxAbsoluteMomentumIt(s.components).state.cov;
201  }
202 };
203 
217 template <typename extensionlist_t = StepperExtensionList<DefaultExtension>,
218  typename component_reducer_t = WeightedComponentReducerLoop,
219  typename auctioneer_t = detail::VoidAuctioneer>
221  : public EigenStepper<extensionlist_t, auctioneer_t> {
224  std::size_t m_stepLimitAfterFirstComponentOnSurface = 50;
225 
227  std::unique_ptr<const Acts::Logger> m_logger;
228 
231  template <typename T>
232  using SmallVector = boost::container::small_vector<T, 16>;
233 
234  public:
237 
240 
242  using typename SingleStepper::Covariance;
243  using typename SingleStepper::Jacobian;
244 
246  using BoundState =
247  std::tuple<MultiComponentBoundTrackParameters, Jacobian, ActsScalar>;
248 
252 
254  using Reducer = component_reducer_t;
255 
257  static constexpr int maxComponents = std::numeric_limits<int>::max();
258 
260  MultiEigenStepperLoop(std::shared_ptr<const MagneticFieldProvider> bField,
261  std::unique_ptr<const Logger> logger =
263  : EigenStepper<extensionlist_t, auctioneer_t>(std::move(bField)),
264  m_logger(std::move(logger)) {}
265 
266  struct State {
268  struct Component {
272  };
273 
276 
279 
280  bool covTransport = false;
281  double pathAccumulated = 0.;
282  std::size_t steps = 0;
283 
285  std::reference_wrapper<const GeometryContext> geoContext;
286 
288  std::reference_wrapper<const MagneticFieldContext> magContext;
289 
292  std::optional<std::size_t> stepCounterAfterFirstComponentOnSurface;
293 
295  State() = delete;
296 
306  explicit State(const GeometryContext& gctx,
307  const MagneticFieldContext& mctx,
308  const std::shared_ptr<const MagneticFieldProvider>& bfield,
309  const MultiComponentBoundTrackParameters& multipars,
310  double ssize = std::numeric_limits<double>::max())
311  : particleHypothesis(multipars.particleHypothesis()),
312  geoContext(gctx),
313  magContext(mctx) {
314  if (multipars.components().empty()) {
315  throw std::invalid_argument(
316  "Cannot construct MultiEigenStepperLoop::State with empty "
317  "multi-component parameters");
318  }
319 
320  const auto surface = multipars.referenceSurface().getSharedPtr();
321 
322  for (auto i = 0ul; i < multipars.components().size(); ++i) {
323  const auto& [weight, singlePars] = multipars[i];
324  components.push_back(
325  {SingleState(gctx, bfield->makeCache(mctx), singlePars, ssize),
326  weight, Intersection3D::Status::onSurface});
327  }
328 
329  if (std::get<2>(multipars.components().front())) {
330  covTransport = true;
331  }
332  }
333  };
334 
336  State makeState(std::reference_wrapper<const GeometryContext> gctx,
337  std::reference_wrapper<const MagneticFieldContext> mctx,
339  double ssize = std::numeric_limits<double>::max()) const {
340  return State(gctx, mctx, SingleStepper::m_bField, par, ssize);
341  }
342 
350  void resetState(
351  State& state, const BoundVector& boundParams,
352  const BoundSquareMatrix& cov, const Surface& surface,
353  const double stepSize = std::numeric_limits<double>::max()) const {
354  for (auto& component : state.components) {
355  SingleStepper::resetState(component.state, boundParams, cov, surface,
356  stepSize);
357  }
358  }
359 
362  template <typename stepping_t, typename navigation_t, typename options_t,
363  typename geoctx_t>
365  stepping_t& stepping;
366  navigation_t& navigation;
367  options_t& options;
368  geoctx_t& geoContext;
369 
370  SinglePropState(stepping_t& s, navigation_t& n, options_t& o, geoctx_t& g)
371  : stepping(s), navigation(n), options(o), geoContext(g) {}
372  };
373 
377  template <typename component_t>
379  static_assert(std::is_same_v<std::remove_const_t<component_t>,
380  typename State::Component>);
381 
382  component_t& cmp;
383 
384  ComponentProxyBase(component_t& c) : cmp(c) {}
385 
386  // These are the const accessors, which are shared between the mutable
387  // ComponentProxy and the ConstComponentProxy
388  auto status() const { return cmp.status; }
389  auto weight() const { return cmp.weight; }
390  auto pathAccumulated() const { return cmp.state.pathAccumulated; }
391  const auto& pars() const { return cmp.state.pars; }
392  const auto& derivative() const { return cmp.state.derivative; }
393  const auto& jacTransport() const { return cmp.state.jacTransport; }
394  const auto& cov() const { return cmp.state.cov; }
395  const auto& jacobian() const { return cmp.state.jacobian; }
396  const auto& jacToGlobal() const { return cmp.state.jacToGlobal; }
397 
398  template <typename propagator_state_t>
399  auto singleState(const propagator_state_t& state) const {
400  using DeducedStepping = decltype(state.stepping.components.front().state);
401  static_assert(std::is_same_v<SingleState, DeducedStepping>);
402 
403  return SinglePropState<
404  const SingleState, const decltype(state.navigation),
405  const decltype(state.options), const decltype(state.geoContext)>(
406  cmp.state, state.navigation, state.options, state.geoContext);
407  }
408 
409  const auto& singleStepper(const MultiEigenStepperLoop& stepper) const {
410  return static_cast<const SingleStepper&>(stepper);
411  }
412  };
413 
418  using ConstComponentProxy =
420 
425  struct ComponentProxy : ComponentProxyBase<typename State::Component> {
427 
428  // Import the const accessors from ComponentProxyBase
429  using Base::cmp;
430  using Base::cov;
431  using Base::derivative;
432  using Base::jacobian;
433  using Base::jacToGlobal;
434  using Base::jacTransport;
435  using Base::pars;
436  using Base::pathAccumulated;
437  using Base::singleState;
438  using Base::singleStepper;
439  using Base::status;
440  using Base::weight;
441 
442  // The multi-component state of the stepper
443  const State& all_state;
444 
446  : Base(c), all_state(s) {}
447 
448  // These are the mutable accessors, the const ones are inherited from the
449  // ComponentProxyBase
450  auto& status() { return cmp.status; }
451  auto& weight() { return cmp.weight; }
452  auto& pathAccumulated() { return cmp.state.pathAccumulated; }
453  auto& pars() { return cmp.state.pars; }
454  auto& derivative() { return cmp.state.derivative; }
455  auto& jacTransport() { return cmp.state.jacTransport; }
456  auto& cov() { return cmp.state.cov; }
457  auto& jacobian() { return cmp.state.jacobian; }
458  auto& jacToGlobal() { return cmp.state.jacToGlobal; }
459 
460  template <typename propagator_state_t>
461  auto singleState(propagator_state_t& state) {
462  using DeducedStepping = decltype(state.stepping.components.front().state);
463  static_assert(std::is_same_v<SingleState, DeducedStepping>);
464 
465  return SinglePropState<SingleState, decltype(state.navigation),
466  decltype(state.options),
467  decltype(state.geoContext)>(
468  cmp.state, state.navigation, state.options, state.geoContext);
469  }
470 
472  const Surface& surface, bool transportCov,
473  const FreeToBoundCorrection& freeToBoundCorrection) {
474  return detail::boundState(
475  all_state.geoContext, cov(), jacobian(), jacTransport(), derivative(),
476  jacToGlobal(), pars(), all_state.particleHypothesis,
477  all_state.covTransport && transportCov, cmp.state.pathAccumulated,
478  surface, freeToBoundCorrection);
479  }
480 
481  void update(const FreeVector& freeParams, const BoundVector& boundParams,
482  const Covariance& covariance, const Surface& surface) {
483  cmp.state.pars = freeParams;
484  cmp.state.cov = covariance;
485  cmp.state.jacToGlobal =
486  surface.boundToFreeJacobian(all_state.geoContext, boundParams);
487  }
488  };
489 
495  struct Iterator {
496  using difference_type = std::ptrdiff_t;
497  using value_type = ComponentProxy;
498  using reference = ComponentProxy;
499  using pointer = void;
500  using iterator_category = std::forward_iterator_tag;
501 
502  typename decltype(state.components)::iterator it;
503  const State& s;
504 
505  // clang-format off
506  auto& operator++() { ++it; return *this; }
507  auto operator!=(const Iterator& other) const { return it != other.it; }
508  auto operator==(const Iterator& other) const { return it == other.it; }
509  auto operator*() const { return ComponentProxy(*it, s); }
510  // clang-format on
511  };
512 
513  struct Iterable {
514  using iterator = Iterator;
515 
516  State& s;
517 
518  // clang-format off
519  auto begin() { return Iterator{s.components.begin(), s}; }
520  auto end() { return Iterator{s.components.end(), s}; }
521  // clang-format on
522  };
523 
524  return Iterable{state};
525  }
526 
531  auto constComponentIterable(const State& state) const {
532  struct ConstIterator {
533  using difference_type = std::ptrdiff_t;
535  using reference = ConstComponentProxy;
536  using pointer = void;
537  using iterator_category = std::forward_iterator_tag;
538 
539  typename decltype(state.components)::const_iterator it;
540  const State& s;
541 
542  // clang-format off
543  auto& operator++() { ++it; return *this; }
544  auto operator!=(const ConstIterator& other) const { return it != other.it; }
545  auto operator==(const ConstIterator& other) const { return it == other.it; }
546  auto operator*() const { return ConstComponentProxy{*it}; }
547  // clang-format on
548  };
549 
550  struct Iterable {
551  using iterator = ConstIterator;
552  const State& s;
553 
554  // clang-format off
555  auto begin() const { return ConstIterator{s.components.cbegin(), s}; }
556  auto end() const { return ConstIterator{s.components.cend(), s}; }
557  // clang-format on
558  };
559 
560  return Iterable{state};
561  }
562 
566  std::size_t numberComponents(const State& state) const {
567  return state.components.size();
568  }
569 
574  auto new_end = std::remove_if(
575  state.components.begin(), state.components.end(), [](const auto& cmp) {
576  return cmp.status == Intersection3D::Status::missed;
577  });
578 
579  state.components.erase(new_end, state.components.end());
580  }
581 
586  ActsScalar sumOfWeights = 0.0;
587  for (const auto& cmp : state.components) {
588  sumOfWeights += cmp.weight;
589  }
590  for (auto& cmp : state.components) {
591  cmp.weight /= sumOfWeights;
592  }
593  }
594 
598  void clearComponents(State& state) const { state.components.clear(); }
599 
613  const BoundTrackParameters& pars,
614  double weight) const {
615  state.components.push_back(
616  {SingleState(state.geoContext,
617  SingleStepper::m_bField->makeCache(state.magContext),
618  pars),
619  weight, Intersection3D::Status::onSurface});
620 
621  return ComponentProxy{state.components.back(), state};
622  }
623 
632  Result<Vector3> getField(State& state, const Vector3& pos) const {
633  return SingleStepper::getField(state.components.front().state, pos);
634  }
635 
639  Vector3 position(const State& state) const {
640  return Reducer::position(state);
641  }
642 
646  Vector3 direction(const State& state) const {
647  return Reducer::direction(state);
648  }
649 
653  double qOverP(const State& state) const { return Reducer::qOverP(state); }
654 
658  double absoluteMomentum(const State& state) const {
659  return Reducer::absoluteMomentum(state);
660  }
661 
665  Vector3 momentum(const State& state) const {
666  return Reducer::momentum(state);
667  }
668 
672  double charge(const State& state) const { return Reducer::charge(state); }
673 
678  return state.particleHypothesis;
679  }
680 
684  double time(const State& state) const { return Reducer::time(state); }
685 
697  Intersection3D::Status updateSurfaceStatus(
698  State& state, const Surface& surface, Direction navDir,
699  const BoundaryCheck& bcheck,
700  ActsScalar surfaceTolerance = s_onSurfaceTolerance,
701  const Logger& logger = getDummyLogger()) const {
702  using Status = Intersection3D::Status;
703 
704  std::array<int, 4> counts = {0, 0, 0, 0};
705 
706  for (auto& component : state.components) {
707  component.status = detail::updateSingleSurfaceStatus<SingleStepper>(
708  *this, component.state, surface, navDir, bcheck, surfaceTolerance,
709  logger);
710  ++counts[static_cast<std::size_t>(component.status)];
711  }
712 
713  // If at least one component is on a surface, we can remove all missed
714  // components before the step. If not, we must keep them for the case that
715  // all components miss and we need to retarget
716  if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
717  removeMissedComponents(state);
718  reweightComponents(state);
719  }
720 
721  ACTS_VERBOSE("Component status wrt "
722  << surface.geometryId() << " at {"
723  << surface.center(state.geoContext).transpose() << "}:\t"
724  << [&]() {
725  std::stringstream ss;
726  for (auto& component : state.components) {
727  ss << component.status << "\t";
728  }
729  return ss.str();
730  }());
731 
732  // Switch on stepCounter if one or more components reached a surface, but
733  // some are still in progress of reaching the surface
734  if (!state.stepCounterAfterFirstComponentOnSurface &&
735  counts[static_cast<std::size_t>(Status::onSurface)] > 0 &&
736  counts[static_cast<std::size_t>(Status::reachable)] > 0) {
737  state.stepCounterAfterFirstComponentOnSurface = 0;
738  ACTS_VERBOSE("started stepCounterAfterFirstComponentOnSurface");
739  }
740 
741  // If there are no components onSurface, but the counter is switched on
742  // (e.g., if the navigator changes the target surface), we need to switch it
743  // off again
744  if (state.stepCounterAfterFirstComponentOnSurface &&
745  counts[static_cast<std::size_t>(Status::onSurface)] == 0) {
746  state.stepCounterAfterFirstComponentOnSurface.reset();
747  ACTS_VERBOSE("switch off stepCounterAfterFirstComponentOnSurface");
748  }
749 
750  // This is a 'any_of' criterium. As long as any of the components has a
751  // certain state, this determines the total state (in the order of a
752  // somewhat importance)
753  if (counts[static_cast<std::size_t>(Status::reachable)] > 0) {
754  return Status::reachable;
755  } else if (counts[static_cast<std::size_t>(Status::onSurface)] > 0) {
756  state.stepCounterAfterFirstComponentOnSurface.reset();
757  return Status::onSurface;
758  } else if (counts[static_cast<std::size_t>(Status::unreachable)] > 0) {
759  return Status::unreachable;
760  } else {
761  return Status::missed;
762  }
763  }
764 
776  template <typename object_intersection_t>
777  void updateStepSize(State& state, const object_intersection_t& oIntersection,
778  Direction direction, bool release = true) const {
779  const Surface& surface = *oIntersection.representation();
780 
781  for (auto& component : state.components) {
782  auto intersection = surface.intersect(
783  component.state.geoContext, SingleStepper::position(component.state),
784  direction * SingleStepper::direction(component.state),
785  true)[oIntersection.index()];
786 
787  SingleStepper::updateStepSize(component.state, intersection, direction,
788  release);
789  }
790  }
791 
798  void setStepSize(State& state, double stepSize,
800  bool release = true) const {
801  for (auto& component : state.components) {
802  SingleStepper::setStepSize(component.state, stepSize, stype, release);
803  }
804  }
805 
813  double getStepSize(const State& state, ConstrainedStep::Type stype) const {
814  return std::min_element(state.components.begin(), state.components.end(),
815  [=](const auto& a, const auto& b) {
816  return std::abs(a.state.stepSize.value(stype)) <
817  std::abs(b.state.stepSize.value(stype));
818  })
819  ->state.stepSize.value(stype);
820  }
821 
825  void releaseStepSize(State& state) const {
826  for (auto& component : state.components) {
827  SingleStepper::releaseStepSize(component.state);
828  }
829  }
830 
834  std::string outputStepSize(const State& state) const {
835  std::stringstream ss;
836  for (const auto& component : state.components) {
837  ss << component.state.stepSize.toString() << " || ";
838  }
839 
840  return ss.str();
841  }
842 
846  double overstepLimit(const State& state) const {
847  // A dynamic overstep limit could sit here
848  return SingleStepper::overstepLimit(state.components.front().state);
849  }
850 
872  State& state, const Surface& surface, bool transportCov = true,
873  const FreeToBoundCorrection& freeToBoundCorrection =
874  FreeToBoundCorrection(false)) const;
875 
890  CurvilinearState curvilinearState(State& state,
891  bool transportCov = true) const;
892 
899  for (auto& component : state.components) {
901  }
902  }
903 
916  State& state, const Surface& surface,
917  const FreeToBoundCorrection& freeToBoundCorrection =
918  FreeToBoundCorrection(false)) const {
919  for (auto& component : state.components) {
920  SingleStepper::transportCovarianceToBound(component.state, surface,
921  freeToBoundCorrection);
922  }
923  }
924 
934  template <typename propagator_state_t, typename navigator_t>
935  Result<double> step(propagator_state_t& state,
936  const navigator_t& navigator) const;
937 };
938 
939 } // namespace Acts
940 
941 #include "MultiEigenStepperLoop.ipp"