Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
KalmanFitter.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file KalmanFitter.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2016-2023 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 
41 
42 #include <functional>
43 #include <limits>
44 #include <map>
45 #include <memory>
46 
47 namespace Acts {
48 
51  first,
53  last,
56  firstOrLast,
57 };
58 
60 template <typename traj_t>
62  using TrackStateProxy = typename traj_t::TrackStateProxy;
63  using ConstTrackStateProxy = typename traj_t::ConstTrackStateProxy;
64  using Parameters = typename TrackStateProxy::Parameters;
65 
66  using Calibrator =
67  Delegate<void(const GeometryContext&, const CalibrationContext&,
69 
70  using Smoother = Delegate<Result<void>(const GeometryContext&, traj_t&,
71  size_t, const Logger&)>;
72 
74  Direction, const Logger&)>;
75 
77 
79 
84 
87 
90 
94 
98 
101 
104  calibrator.template connect<&detail::voidFitterCalibrator<traj_t>>();
105  updater.template connect<&detail::voidFitterUpdater<traj_t>>();
106  smoother.template connect<&detail::voidFitterSmoother<traj_t>>();
107  outlierFinder.template connect<&detail::voidOutlierFinder<traj_t>>();
109  .template connect<&detail::voidReverseFilteringLogic<traj_t>>();
110  }
111 };
112 
116 template <typename traj_t>
132  const MagneticFieldContext& mctx,
133  std::reference_wrapper<const CalibrationContext> cctx,
134  KalmanFitterExtensions<traj_t> extensions_,
135  const PropagatorPlainOptions& pOptions,
136  const Surface* rSurface = nullptr,
137  bool mScattering = true, bool eLoss = true,
138  bool rFiltering = false, double rfScaling = 1.0,
139  const FreeToBoundCorrection& freeToBoundCorrection_ =
140  FreeToBoundCorrection(false))
141  : geoContext(gctx),
142  magFieldContext(mctx),
143  calibrationContext(cctx),
144  extensions(extensions_),
145  propagatorPlainOptions(pOptions),
146  referenceSurface(rSurface),
147  multipleScattering(mScattering),
148  energyLoss(eLoss),
149  reversedFiltering(rFiltering),
151  freeToBoundCorrection(freeToBoundCorrection_) {}
153  KalmanFitterOptions() = delete;
154 
156  std::reference_wrapper<const GeometryContext> geoContext;
158  std::reference_wrapper<const MagneticFieldContext> magFieldContext;
160  std::reference_wrapper<const CalibrationContext> calibrationContext;
161 
163 
166 
168  const Surface* referenceSurface = nullptr;
169 
172  KalmanFitterTargetSurfaceStrategy::firstOrLast;
173 
175  bool multipleScattering = true;
176 
178  bool energyLoss = true;
179 
182  bool reversedFiltering = false;
183 
189 
193 };
194 
195 template <typename traj_t>
197  // Fitted states that the actor has handled.
198  traj_t* fittedStates{nullptr};
199 
200  // This is the index of the 'tip' of the track stored in multitrajectory.
201  // This corresponds to the last measurement state in the multitrajectory.
202  // Since this KF only stores one trajectory, it is unambiguous.
203  // SIZE_MAX is the start of a trajectory.
204  size_t lastMeasurementIndex = SIZE_MAX;
205 
206  // This is the index of the 'tip' of the states stored in multitrajectory.
207  // This corresponds to the last state in the multitrajectory.
208  // Since this KF only stores one trajectory, it is unambiguous.
209  // SIZE_MAX is the start of a trajectory.
210  size_t lastTrackIndex = SIZE_MAX;
211 
212  // The optional Parameters at the provided surface
213  std::optional<BoundTrackParameters> fittedParameters;
214 
215  // Counter for states with non-outlier measurements
216  size_t measurementStates = 0;
217 
218  // Counter for measurements holes
219  // A hole correspond to a surface with an associated detector element with no
220  // associated measurement. Holes are only taken into account if they are
221  // between the first and last measurements.
222  size_t measurementHoles = 0;
223 
224  // Counter for handled states
225  size_t processedStates = 0;
226 
227  // Indicator if smoothing has been done.
228  bool smoothed = false;
229 
230  // Indicator if navigation direction has been reversed
231  bool reversed = false;
232 
233  // Indicator if track fitting has been done
234  bool finished = false;
235 
236  // Measurement surfaces without hits
237  std::vector<const Surface*> missedActiveSurfaces;
238 
239  // Measurement surfaces handled in both forward and
240  // backward filtering
241  std::vector<const Surface*> passedAgainSurfaces;
242 
244 };
245 
266 template <typename propagator_t, typename traj_t>
269  using KalmanNavigator = typename propagator_t::Navigator;
270 
272  static constexpr bool isDirectNavigator =
274 
275  public:
277  std::unique_ptr<const Logger> _logger =
278  getDefaultLogger("KalmanFitter", Logging::INFO))
279  : m_propagator(std::move(pPropagator)),
280  m_logger{std::move(_logger)},
281  m_actorLogger{m_logger->cloneWithSuffix("Actor")} {}
282 
283  private:
285  propagator_t m_propagator;
286 
288  std::unique_ptr<const Logger> m_logger;
289  std::unique_ptr<const Logger> m_actorLogger;
290 
291  const Logger& logger() const { return *m_logger; }
292 
301  template <typename parameters_t>
302  class Actor {
303  public:
306 
308  const Surface* targetSurface = nullptr;
309 
312  KalmanFitterTargetSurfaceStrategy::firstOrLast;
313 
315  const std::map<GeometryIdentifier, SourceLink>* inputMeasurements = nullptr;
316 
318  bool multipleScattering = true;
319 
321  bool energyLoss = true;
322 
324  bool reversedFiltering = false;
325 
326  // Scale the covariance before the reversed filtering
328 
332 
334  std::shared_ptr<traj_t> outputStates;
335 
337  const Logger* actorLogger{nullptr};
338 
340  const Logger& logger() const { return *actorLogger; }
341 
343 
345  SurfaceReached targetReached{std::numeric_limits<double>::lowest()};
346 
349 
360  template <typename propagator_state_t, typename stepper_t,
361  typename navigator_t>
362  void operator()(propagator_state_t& state, const stepper_t& stepper,
363  const navigator_t& navigator, result_type& result,
364  const Logger& /*logger*/) const {
365  assert(result.fittedStates && "No MultiTrajectory set");
366 
367  if (result.finished) {
368  return;
369  }
370 
371  ACTS_VERBOSE("KalmanFitter step at pos: "
372  << stepper.position(state.stepping).transpose()
373  << " dir: " << stepper.direction(state.stepping).transpose()
374  << " momentum: "
375  << stepper.absoluteMomentum(state.stepping));
376 
377  // Add the measurement surface as external surface to navigator.
378  // We will try to hit those surface by ignoring boundary checks.
379  if constexpr (not isDirectNavigator) {
380  if (result.processedStates == 0) {
381  for (auto measurementIt = inputMeasurements->begin();
382  measurementIt != inputMeasurements->end(); measurementIt++) {
383  navigator.insertExternalSurface(state.navigation,
384  measurementIt->first);
385  }
386  }
387  }
388 
389  // Update:
390  // - Waiting for a current surface
391  auto surface = navigator.currentSurface(state.navigation);
392  std::string direction = state.options.direction.toString();
393  if (surface != nullptr) {
394  // Check if the surface is in the measurement map
395  // -> Get the measurement / calibrate
396  // -> Create the predicted state
397  // -> Check outlier behavior, if non-outlier:
398  // -> Perform the kalman update
399  // -> Fill track state information & update stepper information
400 
401  if (not result.smoothed and not result.reversed) {
402  ACTS_VERBOSE("Perform " << direction << " filter step");
403  auto res = filter(surface, state, stepper, navigator, result);
404  if (!res.ok()) {
405  ACTS_ERROR("Error in " << direction << " filter: " << res.error());
406  result.result = res.error();
407  }
408  }
409  if (result.reversed) {
410  ACTS_VERBOSE("Perform " << direction << " filter step");
411  auto res = reversedFilter(surface, state, stepper, navigator, result);
412  if (!res.ok()) {
413  ACTS_ERROR("Error in " << direction << " filter: " << res.error());
414  result.result = res.error();
415  }
416  }
417  }
418 
419  // Finalization:
420  // when all track states have been handled or the navigation is breaked,
421  // reset navigation&stepping before run reversed filtering or
422  // proceed to run smoothing
423  if (not result.smoothed and not result.reversed) {
424  if (result.measurementStates == inputMeasurements->size() or
425  (result.measurementStates > 0 and
426  navigator.navigationBreak(state.navigation))) {
427  // Remove the missing surfaces that occur after the last measurement
428  result.missedActiveSurfaces.resize(result.measurementHoles);
429  // now get track state proxy for the smoothing logic
430  auto trackStateProxy =
431  result.fittedStates->getTrackState(result.lastMeasurementIndex);
432  if (reversedFiltering ||
433  extensions.reverseFilteringLogic(trackStateProxy)) {
434  // Start to run reversed filtering:
435  // Reverse navigation direction and reset navigation and stepping
436  // state to last measurement
437  ACTS_VERBOSE("Reverse navigation direction.");
438  auto res = reverse(state, stepper, navigator, result);
439  if (!res.ok()) {
440  ACTS_ERROR("Error in reversing navigation: " << res.error());
441  result.result = res.error();
442  }
443  } else {
444  // --> Search the starting state to run the smoothing
445  // --> Call the smoothing
446  // --> Set a stop condition when all track states have been
447  // handled
448  ACTS_VERBOSE("Finalize/run smoothing");
449  auto res = finalize(state, stepper, navigator, result);
450  if (!res.ok()) {
451  ACTS_ERROR("Error in finalize: " << res.error());
452  result.result = res.error();
453  }
454  }
455  }
456  }
457 
458  // Post-finalization:
459  // - Progress to target/reference surface and built the final track
460  // parameters
461  if (result.smoothed or result.reversed) {
462  if (result.smoothed) {
463  // Update state and stepper with material effects
464  // Only for smoothed as reverse filtering will handle this separately
465  materialInteractor(navigator.currentSurface(state.navigation), state,
467  MaterialUpdateStage::FullUpdate);
468  }
469 
470  if (targetSurface == nullptr) {
471  // If no target surface provided:
472  // -> Return an error when using reversed filtering mode
473  // -> Fitting is finished here
474  if (result.reversed) {
475  ACTS_ERROR(
476  "The target surface needed for aborting reversed propagation "
477  "is not provided");
478  result.result =
479  Result<void>(KalmanFitterError::BackwardUpdateFailed);
480  } else {
481  ACTS_VERBOSE(
482  "No target surface set. Completing without fitted track "
483  "parameter");
484  // Remember the track fitting is done
485  result.finished = true;
486  }
487  } else if (targetReached(state, stepper, navigator, *targetSurface,
488  logger())) {
489  ACTS_VERBOSE("Completing with fitted track parameter");
490  // Transport & bind the parameter to the final surface
491  auto res = stepper.boundState(state.stepping, *targetSurface, true,
493  if (!res.ok()) {
494  ACTS_ERROR("Error in " << direction << " filter: " << res.error());
495  result.result = res.error();
496  return;
497  }
498  auto& fittedState = *res;
499  // Assign the fitted parameters
500  result.fittedParameters = std::get<BoundTrackParameters>(fittedState);
501 
502  // Reset smoothed status of states missed in reversed filtering
503  if (result.reversed) {
504  result.fittedStates->applyBackwards(
505  result.lastMeasurementIndex, [&](auto trackState) {
506  auto fSurface = &trackState.referenceSurface();
507  auto surface_it = std::find_if(
508  result.passedAgainSurfaces.begin(),
509  result.passedAgainSurfaces.end(),
510  [=](const Surface* s) { return s == fSurface; });
511  if (surface_it == result.passedAgainSurfaces.end()) {
512  // If reversed filtering missed this surface, then there is
513  // no smoothed parameter
514  trackState.unset(TrackStatePropMask::Smoothed);
515  trackState.typeFlags().set(TrackStateFlag::OutlierFlag);
516  }
517  });
518  }
519  // Remember the track fitting is done
520  result.finished = true;
521  }
522  }
523  }
524 
535  template <typename propagator_state_t, typename stepper_t,
536  typename navigator_t>
537  Result<void> reverse(propagator_state_t& state, const stepper_t& stepper,
538  const navigator_t& navigator,
539  result_type& result) const {
540  // Check if there is a measurement on track
541  if (result.lastMeasurementIndex == SIZE_MAX) {
542  ACTS_ERROR("No point to reverse for a track without measurements.");
543  return KalmanFitterError::ReverseNavigationFailed;
544  }
545 
546  // Remember the navigation direction has been reversed
547  result.reversed = true;
548 
549  // Reverse navigation direction
550  state.options.direction = state.options.direction.invert();
551 
552  // Reset propagator options
553  // TODO Not sure if reset of pathLimit during propagation makes any sense
554  state.options.pathLimit =
555  state.options.direction * std::abs(state.options.pathLimit);
556 
557  // Get the last measurement state and reset navigation&stepping state
558  // based on information on this state
559  auto st = result.fittedStates->getTrackState(result.lastMeasurementIndex);
560 
561  // Update the stepping state
562  stepper.resetState(
563  state.stepping, st.filtered(),
564  reversedFilteringCovarianceScaling * st.filteredCovariance(),
565  st.referenceSurface(), state.options.maxStepSize);
566 
567  // For the last measurement state, smoothed is filtered
568  st.smoothed() = st.filtered();
569  st.smoothedCovariance() = st.filteredCovariance();
570  result.passedAgainSurfaces.push_back(&st.referenceSurface());
571 
572  // Reset navigation state
573  // We do not need to specify a target here since this will be handled
574  // separately in the KF actor
575  navigator.resetState(
576  state.navigation, state.geoContext, stepper.position(state.stepping),
577  state.options.direction * stepper.direction(state.stepping),
578  &st.referenceSurface(), nullptr);
579 
580  // Update material effects for last measurement state in reversed
581  // direction
582  materialInteractor(navigator.currentSurface(state.navigation), state,
583  stepper, navigator, MaterialUpdateStage::FullUpdate);
584 
585  return Result<void>::success();
586  }
587 
599  template <typename propagator_state_t, typename stepper_t,
600  typename navigator_t>
601  Result<void> filter(const Surface* surface, propagator_state_t& state,
602  const stepper_t& stepper, const navigator_t& navigator,
603  result_type& result) const {
604  // Try to find the surface in the measurement surfaces
605  auto sourcelink_it = inputMeasurements->find(surface->geometryId());
606  if (sourcelink_it != inputMeasurements->end()) {
607  // Screen output message
608  ACTS_VERBOSE("Measurement surface " << surface->geometryId()
609  << " detected.");
610  // Transport the covariance to the surface
611  stepper.transportCovarianceToBound(state.stepping, *surface,
612  freeToBoundCorrection);
613 
614  // Update state and stepper with pre material effects
615  materialInteractor(surface, state, stepper, navigator,
616  MaterialUpdateStage::PreUpdate);
617 
618  // do the kalman update (no need to perform covTransport here, hence no
619  // point in performing globalToLocal correction)
620  auto trackStateProxyRes = detail::kalmanHandleMeasurement(
621  *calibrationContext, state, stepper, extensions, *surface,
622  sourcelink_it->second, *result.fittedStates, result.lastTrackIndex,
623  false, logger());
624 
625  if (!trackStateProxyRes.ok()) {
626  return trackStateProxyRes.error();
627  }
628 
629  const auto& trackStateProxy = *trackStateProxyRes;
630  result.lastTrackIndex = trackStateProxy.index();
631 
632  // Update the stepper if it is not an outlier
633  if (trackStateProxy.typeFlags().test(
635  // Update the stepping state with filtered parameters
636  ACTS_VERBOSE("Filtering step successful, updated parameters are:\n"
637  << trackStateProxy.filtered().transpose());
638  // update stepping state using filtered parameters after kalman
639  stepper.update(state.stepping,
641  state.options.geoContext, trackStateProxy),
642  trackStateProxy.filtered(),
643  trackStateProxy.filteredCovariance(), *surface);
644  // We count the state with measurement
645  ++result.measurementStates;
646  }
647 
648  // Update state and stepper with post material effects
649  materialInteractor(surface, state, stepper, navigator,
650  MaterialUpdateStage::PostUpdate);
651  // We count the processed state
652  ++result.processedStates;
653  // Update the number of holes count only when encountering a
654  // measurement
655  result.measurementHoles = result.missedActiveSurfaces.size();
656  // Since we encountered a measurement update the lastMeasurementIndex to
657  // the lastTrackIndex.
658  result.lastMeasurementIndex = result.lastTrackIndex;
659 
660  } else if (surface->associatedDetectorElement() != nullptr ||
661  surface->surfaceMaterial() != nullptr) {
662  // We only create track states here if there is already measurement
663  // detected or if the surface has material (no holes before the first
664  // measurement)
665  if (result.measurementStates > 0 ||
666  surface->surfaceMaterial() != nullptr) {
667  auto trackStateProxyRes = detail::kalmanHandleNoMeasurement(
668  state, stepper, *surface, *result.fittedStates,
669  result.lastTrackIndex, true, logger(), freeToBoundCorrection);
670 
671  if (!trackStateProxyRes.ok()) {
672  return trackStateProxyRes.error();
673  }
674 
675  const auto& trackStateProxy = *trackStateProxyRes;
676  result.lastTrackIndex = trackStateProxy.index();
677 
678  if (trackStateProxy.typeFlags().test(TrackStateFlag::HoleFlag)) {
679  // Count the missed surface
680  result.missedActiveSurfaces.push_back(surface);
681  }
682 
683  ++result.processedStates;
684  }
685  if (surface->surfaceMaterial() != nullptr) {
686  // Update state and stepper with material effects
687  materialInteractor(surface, state, stepper, navigator,
688  MaterialUpdateStage::FullUpdate);
689  }
690  }
691  return Result<void>::success();
692  }
693 
705  template <typename propagator_state_t, typename stepper_t,
706  typename navigator_t>
707  Result<void> reversedFilter(const Surface* surface,
708  propagator_state_t& state,
709  const stepper_t& stepper,
710  const navigator_t& navigator,
711  result_type& result) const {
712  // Try to find the surface in the measurement surfaces
713  auto sourcelink_it = inputMeasurements->find(surface->geometryId());
714  if (sourcelink_it != inputMeasurements->end()) {
715  // Screen output message
716  ACTS_VERBOSE("Measurement surface "
717  << surface->geometryId()
718  << " detected in reversed propagation.");
719 
720  // No reversed filtering for last measurement state, but still update
721  // with material effects
722  if (result.reversed and
723  surface == navigator.startSurface(state.navigation)) {
724  materialInteractor(surface, state, stepper, navigator,
725  MaterialUpdateStage::FullUpdate);
726  return Result<void>::success();
727  }
728 
729  // Transport the covariance to the surface
730  stepper.transportCovarianceToBound(state.stepping, *surface,
731  freeToBoundCorrection);
732 
733  // Update state and stepper with pre material effects
734  materialInteractor(surface, state, stepper, navigator,
735  MaterialUpdateStage::PreUpdate);
736 
737  auto fittedStates = *result.fittedStates;
738 
739  // Add a <mask> TrackState entry multi trajectory. This allocates
740  // storage for all components, which we will set later.
741  TrackStatePropMask mask = TrackStatePropMask::All;
742  const size_t currentTrackIndex = fittedStates.addTrackState(
744 
745  // now get track state proxy back
746  typename traj_t::TrackStateProxy trackStateProxy =
747  fittedStates.getTrackState(currentTrackIndex);
748 
749  // Set the trackStateProxy components with the state from the ongoing
750  // propagation
751  {
752  trackStateProxy.setReferenceSurface(surface->getSharedPtr());
753  // Bind the transported state to the current surface
754  auto res = stepper.boundState(state.stepping, *surface, false,
755  freeToBoundCorrection);
756  if (!res.ok()) {
757  return res.error();
758  }
759  auto& [boundParams, jacobian, pathLength] = *res;
760 
761  // Fill the track state
762  trackStateProxy.predicted() = std::move(boundParams.parameters());
763  if (boundParams.covariance().has_value()) {
764  trackStateProxy.predictedCovariance() =
765  std::move(*boundParams.covariance());
766  }
767 
768  trackStateProxy.jacobian() = std::move(jacobian);
769  trackStateProxy.pathLength() = std::move(pathLength);
770  }
771 
772  // We have predicted parameters, so calibrate the uncalibrated input
773  // measurement
774  extensions.calibrator(state.geoContext, *calibrationContext,
775  sourcelink_it->second, trackStateProxy);
776 
777  // If the update is successful, set covariance and
778  auto updateRes = extensions.updater(state.geoContext, trackStateProxy,
779  state.options.direction, logger());
780  if (!updateRes.ok()) {
781  ACTS_ERROR("Backward update step failed: " << updateRes.error());
782  return updateRes.error();
783  } else {
784  // Update the stepping state with filtered parameters
785  ACTS_VERBOSE(
786  "Backward filtering step successful, updated parameters are:\n"
787  << trackStateProxy.filtered().transpose());
788 
789  // Fill the smoothed parameter for the existing track state
790  result.fittedStates->applyBackwards(
791  result.lastMeasurementIndex, [&](auto trackState) {
792  auto fSurface = &trackState.referenceSurface();
793  if (fSurface == surface) {
794  result.passedAgainSurfaces.push_back(surface);
795  trackState.smoothed() = trackStateProxy.filtered();
796  trackState.smoothedCovariance() =
797  trackStateProxy.filteredCovariance();
798  return false;
799  }
800  return true;
801  });
802 
803  // update stepping state using filtered parameters after kalman
804  // update We need to (re-)construct a BoundTrackParameters instance
805  // here, which is a bit awkward.
806  stepper.update(state.stepping,
808  state.options.geoContext, trackStateProxy),
809  trackStateProxy.filtered(),
810  trackStateProxy.filteredCovariance(), *surface);
811 
812  // Update state and stepper with post material effects
813  materialInteractor(surface, state, stepper, navigator,
814  MaterialUpdateStage::PostUpdate);
815  }
816  } else if (surface->associatedDetectorElement() != nullptr ||
817  surface->surfaceMaterial() != nullptr) {
818  // Transport covariance
819  if (surface->associatedDetectorElement() != nullptr) {
820  ACTS_VERBOSE("Detected hole on " << surface->geometryId()
821  << " in reversed filtering");
822  if (state.stepping.covTransport) {
823  stepper.transportCovarianceToBound(state.stepping, *surface);
824  }
825  } else if (surface->surfaceMaterial() != nullptr) {
826  ACTS_VERBOSE("Detected in-sensitive surface "
827  << surface->geometryId() << " in reversed filtering");
828  if (state.stepping.covTransport) {
829  stepper.transportCovarianceToCurvilinear(state.stepping);
830  }
831  }
832  // Not creating bound state here, so need manually reinitialize
833  // jacobian
834  stepper.setIdentityJacobian(state.stepping);
835  if (surface->surfaceMaterial() != nullptr) {
836  // Update state and stepper with material effects
837  materialInteractor(surface, state, stepper, navigator,
838  MaterialUpdateStage::FullUpdate);
839  }
840  }
841 
842  return Result<void>::success();
843  }
844 
857  template <typename propagator_state_t, typename stepper_t,
858  typename navigator_t>
859  void materialInteractor(const Surface* surface, propagator_state_t& state,
860  const stepper_t& stepper,
861  const navigator_t& navigator,
862  const MaterialUpdateStage& updateStage) const {
863  // Indicator if having material
864  bool hasMaterial = false;
865 
866  if (surface and surface->surfaceMaterial()) {
867  // Prepare relevant input particle properties
868  detail::PointwiseMaterialInteraction interaction(surface, state,
869  stepper);
870  // Evaluate the material properties
871  if (interaction.evaluateMaterialSlab(state, navigator, updateStage)) {
872  // Surface has material at this stage
873  hasMaterial = true;
874 
875  // Evaluate the material effects
876  interaction.evaluatePointwiseMaterialInteraction(multipleScattering,
877  energyLoss);
878 
879  // Screen out material effects info
880  ACTS_VERBOSE("Material effects on surface: "
881  << surface->geometryId()
882  << " at update stage: " << updateStage << " are :");
883  ACTS_VERBOSE("eLoss = "
884  << interaction.Eloss << ", "
885  << "variancePhi = " << interaction.variancePhi << ", "
886  << "varianceTheta = " << interaction.varianceTheta
887  << ", "
888  << "varianceQoverP = " << interaction.varianceQoverP);
889 
890  // Update the state and stepper with material effects
891  interaction.updateState(state, stepper, addNoise);
892  }
893  }
894 
895  if (not hasMaterial) {
896  // Screen out message
897  ACTS_VERBOSE("No material effects on surface: " << surface->geometryId()
898  << " at update stage: "
899  << updateStage);
900  }
901  }
902 
913  template <typename propagator_state_t, typename stepper_t,
914  typename navigator_t>
915  Result<void> finalize(propagator_state_t& state, const stepper_t& stepper,
916  const navigator_t& navigator,
917  result_type& result) const {
918  // Remember you smoothed the track states
919  result.smoothed = true;
920 
921  // Get the indices of the first states (can be either a measurement or
922  // material);
923  size_t firstStateIndex = result.lastMeasurementIndex;
924  // Count track states to be smoothed
925  size_t nStates = 0;
926  result.fittedStates->applyBackwards(
927  result.lastMeasurementIndex, [&](auto st) {
928  bool isMeasurement =
929  st.typeFlags().test(TrackStateFlag::MeasurementFlag);
930  bool isOutlier = st.typeFlags().test(TrackStateFlag::OutlierFlag);
931  // We are excluding non measurement states and outlier here. Those
932  // can decrease resolution because only the smoothing corrected the
933  // very first prediction as filtering is not possible.
934  if (isMeasurement && !isOutlier) {
935  firstStateIndex = st.index();
936  }
937  nStates++;
938  });
939  // Return error if the track has no measurement states (but this should
940  // not happen)
941  if (nStates == 0) {
942  ACTS_ERROR("Smoothing for a track without measurements.");
943  return KalmanFitterError::SmoothFailed;
944  }
945  // Screen output for debugging
946  ACTS_VERBOSE("Apply smoothing on " << nStates
947  << " filtered track states.");
948 
949  // Smooth the track states
950  auto smoothRes =
951  extensions.smoother(state.geoContext, *result.fittedStates,
952  result.lastMeasurementIndex, logger());
953  if (!smoothRes.ok()) {
954  ACTS_ERROR("Smoothing step failed: " << smoothRes.error());
955  return smoothRes.error();
956  }
957 
958  // Return in case no target surface
959  if (targetSurface == nullptr) {
960  return Result<void>::success();
961  }
962 
963  // Obtain the smoothed parameters at first/last measurement state
964  auto firstCreatedState =
965  result.fittedStates->getTrackState(firstStateIndex);
966  auto lastCreatedMeasurement =
967  result.fittedStates->getTrackState(result.lastMeasurementIndex);
968 
969  // Lambda to get the intersection of the free params on the target surface
970  auto target = [&](const FreeVector& freeVector) -> SurfaceIntersection {
971  return targetSurface
972  ->intersect(
973  state.geoContext, freeVector.segment<3>(eFreePos0),
974  state.options.direction * freeVector.segment<3>(eFreeDir0),
975  true, state.options.targetTolerance)
976  .closest();
977  };
978 
979  // The smoothed free params at the first/last measurement state.
980  // (the first state can also be a material state)
981  auto firstParams = MultiTrajectoryHelpers::freeSmoothed(
982  state.options.geoContext, firstCreatedState);
983  auto lastParams = MultiTrajectoryHelpers::freeSmoothed(
984  state.options.geoContext, lastCreatedMeasurement);
985  // Get the intersections of the smoothed free parameters with the target
986  // surface
987  const auto firstIntersection = target(firstParams);
988  const auto lastIntersection = target(lastParams);
989 
990  // Update the stepping parameters - in order to progress to destination.
991  // At the same time, reverse navigation direction for further stepping if
992  // necessary.
993  // @note The stepping parameters is updated to the smoothed parameters at
994  // either the first measurement state or the last measurement state. It
995  // assumes the target surface is not within the first and the last
996  // smoothed measurement state. Also, whether the intersection is on
997  // surface is not checked here.
998  bool useFirstTrackState = true;
999  switch (targetSurfaceStrategy) {
1000  case KalmanFitterTargetSurfaceStrategy::first:
1001  useFirstTrackState = true;
1002  break;
1003  case KalmanFitterTargetSurfaceStrategy::last:
1004  useFirstTrackState = false;
1005  break;
1006  case KalmanFitterTargetSurfaceStrategy::firstOrLast:
1007  useFirstTrackState = std::abs(firstIntersection.pathLength()) <=
1008  std::abs(lastIntersection.pathLength());
1009  break;
1010  default:
1011  ACTS_ERROR("Unknown target surface strategy");
1012  return KalmanFitterError::SmoothFailed;
1013  }
1014  bool reverseDirection = false;
1015  if (useFirstTrackState) {
1016  stepper.resetState(state.stepping, firstCreatedState.smoothed(),
1017  firstCreatedState.smoothedCovariance(),
1018  firstCreatedState.referenceSurface(),
1019  state.options.maxStepSize);
1020  reverseDirection = firstIntersection.pathLength() < 0;
1021  } else {
1022  stepper.resetState(state.stepping, lastCreatedMeasurement.smoothed(),
1023  lastCreatedMeasurement.smoothedCovariance(),
1024  lastCreatedMeasurement.referenceSurface(),
1025  state.options.maxStepSize);
1026  reverseDirection = lastIntersection.pathLength() < 0;
1027  }
1028  // Reverse the navigation direction if necessary
1029  if (reverseDirection) {
1030  ACTS_VERBOSE(
1031  "Reverse navigation direction after smoothing for reaching the "
1032  "target surface");
1033  state.options.direction = state.options.direction.invert();
1034  }
1035  const auto& surface = useFirstTrackState
1036  ? firstCreatedState.referenceSurface()
1037  : lastCreatedMeasurement.referenceSurface();
1038 
1039  ACTS_VERBOSE(
1040  "Smoothing successful, updating stepping state to smoothed "
1041  "parameters at surface "
1042  << surface.geometryId() << ". Prepared to reach the target surface.");
1043 
1044  // Reset the navigation state to enable propagation towards the target
1045  // surface
1046  // Set targetSurface to nullptr as it is handled manually in the actor
1047  navigator.resetState(
1048  state.navigation, state.geoContext, stepper.position(state.stepping),
1049  state.options.direction * stepper.direction(state.stepping), &surface,
1050  nullptr);
1051 
1052  return Result<void>::success();
1053  }
1054  };
1055 
1056  template <typename parameters_t>
1057  class Aborter {
1058  public:
1061 
1062  template <typename propagator_state_t, typename stepper_t,
1063  typename navigator_t, typename result_t>
1064  bool operator()(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
1065  const navigator_t& /*navigator*/, const result_t& result,
1066  const Logger& /*logger*/) const {
1067  if (!result.result.ok() or result.finished) {
1068  return true;
1069  }
1070  return false;
1071  }
1072  };
1073 
1074  public:
1094  template <typename source_link_iterator_t, typename start_parameters_t,
1095  typename parameters_t = BoundTrackParameters,
1096  typename track_container_t, template <typename> class holder_t,
1097  bool _isdn = isDirectNavigator>
1098  auto fit(source_link_iterator_t it, source_link_iterator_t end,
1099  const start_parameters_t& sParameters,
1100  const KalmanFitterOptions<traj_t>& kfOptions,
1102  const -> std::enable_if_t<
1103  !_isdn, Result<typename TrackContainer<track_container_t, traj_t,
1104  holder_t>::TrackProxy>> {
1105  // To be able to find measurements later, we put them into a map
1106  // We need to copy input SourceLinks anyway, so the map can own them.
1107  ACTS_VERBOSE("Preparing " << std::distance(it, end)
1108  << " input measurements");
1109  std::map<GeometryIdentifier, SourceLink> inputMeasurements;
1110  // for (const auto& sl : sourcelinks) {
1111  for (; it != end; ++it) {
1112  SourceLink sl = *it;
1113  const Surface* surface = kfOptions.extensions.surfaceAccessor(sl);
1114  // @TODO: This can probably change over to surface pointers as keys
1115  auto geoId = surface->geometryId();
1116  inputMeasurements.emplace(geoId, std::move(sl));
1117  }
1118 
1119  // Create the ActionList and AbortList
1120  using KalmanAborter = Aborter<parameters_t>;
1121  using KalmanActor = Actor<parameters_t>;
1122 
1123  using KalmanResult = typename KalmanActor::result_type;
1124  using Actors = ActionList<KalmanActor>;
1125  using Aborters = AbortList<KalmanAborter>;
1126 
1127  // Create relevant options for the propagation options
1129  kfOptions.geoContext, kfOptions.magFieldContext);
1130 
1131  // // Set the trivial propagator options
1132  kalmanOptions.setPlainOptions(kfOptions.propagatorPlainOptions);
1133 
1134  // Catch the actor and set the measurements
1135  auto& kalmanActor = kalmanOptions.actionList.template get<KalmanActor>();
1136  kalmanActor.inputMeasurements = &inputMeasurements;
1137  kalmanActor.targetSurface = kfOptions.referenceSurface;
1138  kalmanActor.multipleScattering = kfOptions.multipleScattering;
1139  kalmanActor.energyLoss = kfOptions.energyLoss;
1140  kalmanActor.reversedFiltering = kfOptions.reversedFiltering;
1141  kalmanActor.reversedFilteringCovarianceScaling =
1142  kfOptions.reversedFilteringCovarianceScaling;
1143  kalmanActor.freeToBoundCorrection = kfOptions.freeToBoundCorrection;
1144  kalmanActor.calibrationContext = &kfOptions.calibrationContext.get();
1145  kalmanActor.extensions = kfOptions.extensions;
1146  kalmanActor.actorLogger = m_actorLogger.get();
1147 
1148  typename propagator_t::template action_list_t_result_t<
1150  inputResult;
1151 
1152  auto& r = inputResult.template get<KalmanFitterResult<traj_t>>();
1153 
1154  r.fittedStates = &trackContainer.trackStateContainer();
1155 
1156  // Run the fitter
1157  auto result = m_propagator.template propagate(
1158  sParameters, kalmanOptions, false, std::move(inputResult));
1159 
1160  if (!result.ok()) {
1161  ACTS_ERROR("Propagation failed: " << result.error());
1162  return result.error();
1163  }
1164 
1165  auto& propRes = *result;
1166 
1168  auto kalmanResult = std::move(propRes.template get<KalmanResult>());
1169 
1172  if (kalmanResult.result.ok() and not kalmanResult.measurementStates) {
1173  kalmanResult.result = Result<void>(KalmanFitterError::NoMeasurementFound);
1174  }
1175 
1176  if (!kalmanResult.result.ok()) {
1177  ACTS_ERROR("KalmanFilter failed: "
1178  << kalmanResult.result.error() << ", "
1179  << kalmanResult.result.error().message());
1180  return kalmanResult.result.error();
1181  }
1182 
1183  auto track = trackContainer.getTrack(trackContainer.addTrack());
1184  track.tipIndex() = kalmanResult.lastMeasurementIndex;
1185  if (kalmanResult.fittedParameters) {
1186  const auto& params = kalmanResult.fittedParameters.value();
1187  track.parameters() = params.parameters();
1188  track.covariance() = params.covariance().value();
1189  track.setReferenceSurface(params.referenceSurface().getSharedPtr());
1190  }
1191 
1192  calculateTrackQuantities(track);
1193 
1194  if (trackContainer.hasColumn(hashString("smoothed"))) {
1195  track.template component<bool, hashString("smoothed")>() =
1196  kalmanResult.smoothed;
1197  }
1198  if (trackContainer.hasColumn(hashString("reversed"))) {
1199  track.template component<bool, hashString("reversed")>() =
1200  kalmanResult.reversed;
1201  }
1202 
1203  // Return the converted Track
1204  return track;
1205  }
1206 
1228  template <typename source_link_iterator_t, typename start_parameters_t,
1229  typename parameters_t = BoundTrackParameters,
1230  typename track_container_t, template <typename> class holder_t,
1231  bool _isdn = isDirectNavigator>
1232  auto fit(source_link_iterator_t it, source_link_iterator_t end,
1233  const start_parameters_t& sParameters,
1234  const KalmanFitterOptions<traj_t>& kfOptions,
1235  const std::vector<const Surface*>& sSequence,
1237  const -> std::enable_if_t<
1238  _isdn, Result<typename TrackContainer<track_container_t, traj_t,
1239  holder_t>::TrackProxy>> {
1240  // To be able to find measurements later, we put them into a map
1241  // We need to copy input SourceLinks anyway, so the map can own them.
1242  ACTS_VERBOSE("Preparing " << std::distance(it, end)
1243  << " input measurements");
1244  std::map<GeometryIdentifier, SourceLink> inputMeasurements;
1245  for (; it != end; ++it) {
1246  SourceLink sl = *it;
1247  const Surface* surface = kfOptions.extensions.surfaceAccessor(sl);
1248  // @TODO: This can probably change over to surface pointers as keys
1249  auto geoId = surface->geometryId();
1250  inputMeasurements.emplace(geoId, sl);
1251  }
1252 
1253  // Create the ActionList and AbortList
1254  using KalmanAborter = Aborter<parameters_t>;
1255  using KalmanActor = Actor<parameters_t>;
1256 
1257  using KalmanResult = typename KalmanActor::result_type;
1258  using Actors = ActionList<DirectNavigator::Initializer, KalmanActor>;
1259  using Aborters = AbortList<KalmanAborter>;
1260 
1261  // Create relevant options for the propagation options
1263  kfOptions.geoContext, kfOptions.magFieldContext);
1264 
1265  // Set the trivial propagator options
1266  kalmanOptions.setPlainOptions(kfOptions.propagatorPlainOptions);
1267 
1268  // Catch the actor and set the measurements
1269  auto& kalmanActor = kalmanOptions.actionList.template get<KalmanActor>();
1270  kalmanActor.inputMeasurements = &inputMeasurements;
1271  kalmanActor.targetSurface = kfOptions.referenceSurface;
1272  kalmanActor.targetSurfaceStrategy = kfOptions.referenceSurfaceStrategy;
1273  kalmanActor.multipleScattering = kfOptions.multipleScattering;
1274  kalmanActor.energyLoss = kfOptions.energyLoss;
1275  kalmanActor.reversedFiltering = kfOptions.reversedFiltering;
1276  kalmanActor.reversedFilteringCovarianceScaling =
1277  kfOptions.reversedFilteringCovarianceScaling;
1278  kalmanActor.extensions = kfOptions.extensions;
1279  kalmanActor.actorLogger = m_actorLogger.get();
1280 
1281  // Set the surface sequence
1282  auto& dInitializer =
1283  kalmanOptions.actionList.template get<DirectNavigator::Initializer>();
1284  dInitializer.navSurfaces = sSequence;
1285 
1286  typename propagator_t::template action_list_t_result_t<
1288  inputResult;
1289 
1290  auto& r = inputResult.template get<KalmanFitterResult<traj_t>>();
1291 
1292  r.fittedStates = &trackContainer.trackStateContainer();
1293 
1294  // Run the fitter
1295  auto result = m_propagator.template propagate(
1296  sParameters, kalmanOptions, false, std::move(inputResult));
1297 
1298  if (!result.ok()) {
1299  ACTS_ERROR("Propagation failed: " << result.error());
1300  return result.error();
1301  }
1302 
1303  const auto& propRes = *result;
1304 
1306  auto kalmanResult = propRes.template get<KalmanResult>();
1307 
1310  if (kalmanResult.result.ok() and not kalmanResult.measurementStates) {
1311  kalmanResult.result = Result<void>(KalmanFitterError::NoMeasurementFound);
1312  }
1313 
1314  if (!kalmanResult.result.ok()) {
1315  ACTS_ERROR("KalmanFilter failed: "
1316  << kalmanResult.result.error() << ", "
1317  << kalmanResult.result.error().message());
1318  return kalmanResult.result.error();
1319  }
1320 
1321  auto track = trackContainer.getTrack(trackContainer.addTrack());
1322  track.tipIndex() = kalmanResult.lastMeasurementIndex;
1323  if (kalmanResult.fittedParameters) {
1324  const auto& params = kalmanResult.fittedParameters.value();
1325  track.parameters() = params.parameters();
1326  track.covariance() = params.covariance().value();
1327  track.setReferenceSurface(params.referenceSurface().getSharedPtr());
1328  }
1329 
1330  calculateTrackQuantities(track);
1331 
1332  if (trackContainer.hasColumn(hashString("smoothed"))) {
1333  track.template component<bool, hashString("smoothed")>() =
1334  kalmanResult.smoothed;
1335  }
1336  if (trackContainer.hasColumn(hashString("reversed"))) {
1337  track.template component<bool, hashString("reversed")>() =
1338  kalmanResult.reversed;
1339  }
1340 
1341  // Return the converted Track
1342  return track;
1343  }
1344 };
1345 
1346 } // namespace Acts