Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CombinatorialKalmanFilter.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file CombinatorialKalmanFilter.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2016-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 
40 #include "Acts/Utilities/Zip.hpp"
41 
42 #include <functional>
43 #include <limits>
44 #include <memory>
45 #include <type_traits>
46 #include <unordered_map>
47 
48 namespace Acts {
49 
52  first,
54  last,
57  firstOrLast,
58 };
59 
66  // Number of passed sensitive surfaces
67  size_t nSensitiveSurfaces = 0;
68  // Number of track states
69  size_t nStates = 0;
70  // Number of (non-outlier) measurements
71  size_t nMeasurements = 0;
72  // Number of outliers
73  size_t nOutliers = 0;
74  // Number of holes
75  size_t nHoles = 0;
76 };
77 
79 template <typename traj_t>
81  using candidate_container_t =
82  typename std::vector<typename traj_t::TrackStateProxy>;
83  using MeasurementSelector =
84  Delegate<Result<std::pair<typename candidate_container_t::iterator,
85  typename candidate_container_t::iterator>>(
86  candidate_container_t& trackStates, bool&, const Logger&)>;
87  using BranchStopper =
89 
94 
97 
100 
103 
105 
108  calibrator.template connect<&detail::voidFitterCalibrator<traj_t>>();
109  updater.template connect<&detail::voidFitterUpdater<traj_t>>();
110  smoother.template connect<&detail::voidFitterSmoother<traj_t>>();
111  branchStopper.connect<voidBranchStopper>();
112  measurementSelector.template connect<voidMeasurementSelector>();
113  }
114 
115  private:
120  static Result<std::pair<
121  typename std::vector<typename traj_t::TrackStateProxy>::iterator,
122  typename std::vector<typename traj_t::TrackStateProxy>::iterator>>
124  typename std::vector<typename traj_t::TrackStateProxy>& candidates,
125  bool& isOutlier, const Logger& logger) {
126  (void)isOutlier;
127  (void)logger;
128  return std::pair{candidates.begin(), candidates.end()};
129  };
130 
134  static bool voidBranchStopper(
135  const CombinatorialKalmanFilterTipState& tipState) {
136  (void)tipState;
137  return false;
138  }
139 };
140 
143 template <typename source_link_iterator_t>
146  const Surface&)>;
147 
152 template <typename source_link_iterator_t, typename traj_t>
154  using SourceLinkIterator = source_link_iterator_t;
156 
171  const GeometryContext& gctx, const MagneticFieldContext& mctx,
172  std::reference_wrapper<const CalibrationContext> cctx,
173  SourceLinkAccessor accessor_,
175  const PropagatorPlainOptions& pOptions, const Surface* rSurface = nullptr,
176  bool mScattering = true, bool eLoss = true, bool rSmoothing = true)
177  : geoContext(gctx),
178  magFieldContext(mctx),
179  calibrationContext(cctx),
180  sourcelinkAccessor(std::move(accessor_)),
181  extensions(extensions_),
182  propagatorPlainOptions(pOptions),
183  smoothingTargetSurface(rSurface),
184  multipleScattering(mScattering),
185  energyLoss(eLoss),
186  smoothing(rSmoothing) {}
187 
190 
192  std::reference_wrapper<const GeometryContext> geoContext;
194  std::reference_wrapper<const MagneticFieldContext> magFieldContext;
196  std::reference_wrapper<const CalibrationContext> calibrationContext;
197 
200 
203 
206 
208  const Surface* filterTargetSurface = nullptr;
209 
211  const Surface* smoothingTargetSurface = nullptr;
212 
216  CombinatorialKalmanFilterTargetSurfaceStrategy::firstOrLast;
217 
219  bool multipleScattering = true;
220 
222  bool energyLoss = true;
223 
225  bool smoothing = true;
226 };
227 
228 template <typename traj_t>
231  traj_t* fittedStates{nullptr};
232 
234  std::shared_ptr<traj_t> stateBuffer;
235  std::vector<typename traj_t::TrackStateProxy> trackStateCandidates;
236 
239  std::vector<MultiTrajectoryTraits::IndexType> lastMeasurementIndices;
240 
243  std::vector<MultiTrajectoryTraits::IndexType> lastTrackIndices;
244 
246  std::unordered_map<MultiTrajectoryTraits::IndexType, BoundTrackParameters>
248 
250  std::vector<std::pair<MultiTrajectoryTraits::IndexType,
253 
256  std::unordered_map<const Surface*, std::unordered_map<size_t, size_t>>
258 
260  bool filtered = false;
261 
263  bool smoothed = false;
264 
267 
269  bool finished = false;
270 
273 
276 };
277 
300 template <typename propagator_t, typename traj_t>
302  public:
304  CombinatorialKalmanFilter() = delete;
307  std::unique_ptr<const Logger> _logger =
309  : m_propagator(std::move(pPropagator)),
310  m_logger(std::move(_logger)),
311  m_actorLogger{m_logger->cloneWithSuffix("Actor")},
312  m_updaterLogger{m_logger->cloneWithSuffix("Updater")},
313  m_smootherLogger{m_logger->cloneWithSuffix("Smoother")} {}
314 
315  private:
316  using KalmanNavigator = typename propagator_t::Navigator;
317 
320 
321  std::unique_ptr<const Logger> m_logger;
322  std::shared_ptr<const Logger> m_actorLogger;
323  std::shared_ptr<const Logger> m_updaterLogger;
324  std::shared_ptr<const Logger> m_smootherLogger;
325 
326  const Logger& logger() const { return *m_logger; }
327 
335  template <typename source_link_accessor_t, typename parameters_t>
336  class Actor {
337  public:
339  using BoundState = std::tuple<parameters_t, BoundMatrix, double>;
340  using CurvilinearState =
341  std::tuple<CurvilinearTrackParameters, BoundMatrix, double>;
342  // The source link container type
345 
347  const Surface* filterTargetSurface = nullptr;
348 
350  const Surface* smoothingTargetSurface = nullptr;
351 
355  CombinatorialKalmanFilterTargetSurfaceStrategy::firstOrLast;
356 
358  bool multipleScattering = true;
359 
361  bool energyLoss = true;
362 
364  bool smoothing = true;
365 
368 
379  template <typename propagator_state_t, typename stepper_t,
380  typename navigator_t>
381  void operator()(propagator_state_t& state, const stepper_t& stepper,
382  const navigator_t& navigator, result_type& result,
383  const Logger& _logger) const {
384  assert(result.fittedStates && "No MultiTrajectory set");
385 
386  if (result.finished) {
387  return;
388  }
389 
390  ACTS_VERBOSE("CombinatorialKalmanFilter step");
391 
392  if (not result.filtered and filterTargetSurface != nullptr and
393  targetReached(state, stepper, navigator, *filterTargetSurface,
394  logger())) {
395  navigator.navigationBreak(state.navigation, true);
396  stepper.releaseStepSize(state.stepping);
397  }
398 
399  // Update:
400  // - Waiting for a current surface
401  auto surface = navigator.currentSurface(state.navigation);
402  if (surface != nullptr and not result.filtered) {
403  // There are three scenarios:
404  // 1) The surface is in the measurement map
405  // -> Select source links
406  // -> Perform the kalman update for selected non-outlier source links
407  // -> Add track states in multitrajectory. Multiple states mean branch
408  // splitting.
409  // -> Call branch stopper to justify each branch
410  // -> If there is non-outlier state, update stepper information
411  // 2) The surface is not in the measurement map but with material or is
412  // an active surface
413  // -> Add a hole or passive material state in multitrajectory
414  // -> Call branch stopper to justify the branch
415  // 3) The surface is neither in the measurement map nor with material
416  // -> Do nothing
417  ACTS_VERBOSE("Perform filter step");
418  auto res = filter(surface, state, stepper, navigator, result);
419  if (!res.ok()) {
420  ACTS_ERROR("Error in filter: " << res.error());
421  result.lastError = res.error();
422  }
423  }
424 
425  // Reset propagation state:
426  // - When navigation breaks and there is still active tip present after
427  // recording&removing track tips on current surface
428  if (navigator.navigationBreak(state.navigation) and not result.filtered) {
429  // Record the tips on current surface as trajectory entry indices
430  // (taking advantage of fact that those tips are consecutive in list of
431  // active tips) and remove those tips from active tips
432  if (not result.activeTips.empty()) {
433  // The last active tip
434  const auto& lastActiveTip = result.activeTips.back().first;
435  // Get the index of previous state
436  auto iprevious =
437  result.fittedStates->getTrackState(lastActiveTip).previous();
438  // Find the track states which have the same previous state and remove
439  // them from active tips
440  while (not result.activeTips.empty()) {
441  const auto& [currentTip, tipState] = result.activeTips.back();
442  if (result.fittedStates->getTrackState(currentTip).previous() !=
443  iprevious) {
444  break;
445  }
446  // Record the tips if there are measurements on the track
447  if (tipState.nMeasurements > 0) {
448  ACTS_VERBOSE("Find track with entry index = "
449  << currentTip << " and there are nMeasurements = "
450  << tipState.nMeasurements
451  << ", nOutliers = " << tipState.nOutliers
452  << ", nHoles = " << tipState.nHoles << " on track");
453  result.lastTrackIndices.emplace_back(currentTip);
454  // Set the lastMeasurementIndex to the last measurement
455  // to ignore the states after it in the rest of the algorithm
456  auto lastMeasurementIndex = currentTip;
457  auto lastMeasurementState =
458  result.fittedStates->getTrackState(lastMeasurementIndex);
459  bool isMeasurement = lastMeasurementState.typeFlags().test(
461  while (!isMeasurement) {
462  lastMeasurementIndex = lastMeasurementState.previous();
463  lastMeasurementState =
464  result.fittedStates->getTrackState(lastMeasurementIndex);
465  isMeasurement = lastMeasurementState.typeFlags().test(
467  }
468  result.lastMeasurementIndices.emplace_back(lastMeasurementIndex);
469  // @TODO: Keep information on tip state around so we don't have to
470  // recalculate it later
471  }
472  // Remove the tip from list of active tips
473  result.activeTips.erase(result.activeTips.end() - 1);
474  }
475  }
476  // If no more active tip, done with filtering; Otherwise, reset
477  // propagation state to track state at last tip of active tips
478  if (result.activeTips.empty()) {
479  ACTS_VERBOSE("Kalman filtering finds "
480  << result.lastTrackIndices.size() << " tracks");
481  result.filtered = true;
482  } else {
483  ACTS_VERBOSE("Propagation jumps to branch with tip = "
484  << result.activeTips.back().first);
485  reset(state, stepper, navigator, result);
486  }
487  }
488 
489  if (endOfWorldReached(state, stepper, navigator, logger()) ||
490  result.pathLimitReached(state, stepper, navigator, logger())) {
491  navigator.targetReached(state.navigation, false);
492  if (result.activeTips.empty()) {
493  // we are already done
494  } else if (result.activeTips.size() == 1) {
495  // this was the last track - we are done
496  ACTS_VERBOSE("Kalman filtering finds "
497  << result.lastTrackIndices.size() << " tracks");
498  result.filtered = true;
499  } else {
500  // remove the active tip and continue with the next
501  result.activeTips.erase(result.activeTips.end() - 1);
502  reset(state, stepper, navigator, result);
503  }
504  }
505 
506  // Post-processing after filtering phase
507  if (result.filtered) {
508  // Return error if filtering finds no tracks
509  if (result.lastTrackIndices.empty()) {
510  // @TODO: Tracks like this should not be in the final output!
511  ACTS_WARNING("No tracks found");
512  result.finished = true;
513  } else {
514  if (not smoothing) {
515  ACTS_VERBOSE("Finish Kalman filtering");
516  // Remember that track finding is done
517  result.finished = true;
518  } else {
519  // Iterate over the found tracks for smoothing and getting the
520  // fitted parameter. This needs to be accomplished in different
521  // propagation steps:
522  // -> first run smoothing for found track indexed with iSmoothed
523  if (not result.smoothed) {
524  ACTS_VERBOSE(
525  "Finalize/run smoothing for track with last measurement "
526  "index = "
527  << result.lastMeasurementIndices.at(result.iSmoothed));
528  // --> Search the starting state to run the smoothing
529  // --> Call the smoothing
530  // --> Set a stop condition when all track states have been
531  // handled
532  auto res = finalize(state, stepper, navigator, result);
533  if (!res.ok()) {
534  ACTS_ERROR("Error in finalize: " << res.error());
535  result.lastError = res.error();
536  }
537  result.smoothed = true;
538 
539  // TODO another ugly control flow hack
540  navigator.preStep(state, stepper);
541  }
542 
543  if (result.smoothed) {
544  // Update state and stepper with material effects
545  materialInteractor(navigator.currentSurface(state.navigation),
547  MaterialUpdateStage::FullUpdate);
548  }
549 
550  // -> then progress to target/reference surface and built the final
551  // track parameters for found track indexed with iSmoothed
552  if (result.smoothed and
553  (smoothingTargetSurface == nullptr or
554  targetReached(state, stepper, navigator,
556  result.pathLimitReached(state, stepper, navigator,
557  logger()))) {
558  ACTS_VERBOSE(
559  "Completing the track with last measurement index = "
560  << result.lastMeasurementIndices.at(result.iSmoothed));
561 
562  if (smoothingTargetSurface != nullptr) {
563  // Transport & bind the parameter to the final surface
564  auto res =
565  stepper.boundState(state.stepping, *smoothingTargetSurface);
566  if (!res.ok()) {
567  ACTS_ERROR("Error in finalize: " << res.error());
568  result.lastError = res.error();
569  } else {
570  auto fittedState = *res;
571  // Assign the fitted parameters
572  result.fittedParameters.emplace(
573  result.lastMeasurementIndices.at(result.iSmoothed),
574  std::get<BoundTrackParameters>(fittedState));
575  }
576  }
577 
578  // If there are more trajectories to handle:
579  // -> set the targetReached status to false
580  // -> set the smoothed status to false
581  // -> update the index of track to be smoothed
582  if (result.iSmoothed < result.lastMeasurementIndices.size() - 1) {
583  result.smoothed = false;
584  result.iSmoothed++;
585  // Reverse navigation direction to start targeting for the rest
586  // tracks
587  state.options.direction = state.options.direction.invert();
588 
589  // TODO this is kinda silly but I dont see a better solution
590  // with the current CKF control flow
591  operator()(state, stepper, navigator, result, _logger);
592  } else {
593  ACTS_VERBOSE("Finish Kalman filtering and smoothing");
594  // Remember that track finding is done
595  result.finished = true;
596  }
597  }
598  } // if run smoothing
599  } // if there are found tracks
600  } // if filtering is done
601  }
602 
613  template <typename propagator_state_t, typename stepper_t,
614  typename navigator_t>
615  void reset(propagator_state_t& state, const stepper_t& stepper,
616  const navigator_t& navigator, result_type& result) const {
617  auto currentState =
618  result.fittedStates->getTrackState(result.activeTips.back().first);
619 
620  // Reset the stepping state
621  stepper.resetState(state.stepping, currentState.filtered(),
622  currentState.filteredCovariance(),
623  currentState.referenceSurface(),
624  state.options.maxStepSize);
625 
626  // Reset the navigation state
627  // Set targetSurface to nullptr for forward filtering; it's only needed
628  // after smoothing
629  navigator.resetState(
630  state.navigation, state.geoContext, stepper.position(state.stepping),
631  state.options.direction * stepper.direction(state.stepping),
632  &currentState.referenceSurface(), nullptr);
633 
634  // No Kalman filtering for the starting surface, but still need
635  // to consider the material effects here
636  materialInteractor(navigator.currentSurface(state.navigation), state,
637  stepper, navigator, MaterialUpdateStage::FullUpdate);
638 
639  detail::setupLoopProtection(state, stepper, result.pathLimitReached, true,
640  logger());
641  }
642 
657  template <typename propagator_state_t, typename stepper_t,
658  typename navigator_t>
659  Result<void> filter(const Surface* surface, propagator_state_t& state,
660  const stepper_t& stepper, const navigator_t& navigator,
661  result_type& result) const {
662  // Initialize the number of branches on current surface
663  size_t nBranchesOnSurface = 0;
664 
665  // Count the number of source links on the surface
666  auto [slBegin, slEnd] = m_sourcelinkAccessor(*surface);
667  if (slBegin != slEnd) {
668  // Screen output message
669  ACTS_VERBOSE("Measurement surface " << surface->geometryId()
670  << " detected.");
671 
672  // Transport the covariance to the surface
673  stepper.transportCovarianceToBound(state.stepping, *surface);
674 
675  // Update state and stepper with pre material effects
676  materialInteractor(surface, state, stepper, navigator,
677  MaterialUpdateStage::PreUpdate);
678 
679  // Bind the transported state to the current surface
680  auto boundStateRes =
681  stepper.boundState(state.stepping, *surface, false);
682  if (!boundStateRes.ok()) {
683  return boundStateRes.error();
684  }
685  auto boundState = *boundStateRes;
686 
687  // Retrieve the previous tip and its state
688  // The states created on this surface will have the common previous tip
689  size_t prevTip = SIZE_MAX;
690  TipState prevTipState;
691  if (not result.activeTips.empty()) {
692  prevTip = result.activeTips.back().first;
693  prevTipState = result.activeTips.back().second;
694  // New state is to be added. Remove the last tip from active tips
695  result.activeTips.erase(result.activeTips.end() - 1);
696  }
697 
698  // Create trackstates for all source links (will be filtered later)
699  // Results are stored in result => no return value
700  createSourceLinkTrackStates(state.geoContext, result, boundState,
701  prevTip, slBegin, slEnd);
702 
703  // Invoke the measurement selector to select compatible measurements
704  // with the predicted track parameter.
705  // It can modify the trackStateCandidates vector, and will return a pair
706  // of iterators marking the range of accepted measurements (track
707  // states)
708  bool isOutlier = false;
709  auto selectorResult = m_extensions.measurementSelector(
710  result.trackStateCandidates, isOutlier, logger());
711 
712  if (!selectorResult.ok()) {
713  ACTS_ERROR("Selection of calibrated measurements failed: "
714  << selectorResult.error());
715  return selectorResult.error();
716  }
717  auto selectedTrackStateRange = *selectorResult;
718 
719  auto procRes = processSelectedTrackStates(
720  state.geoContext, selectedTrackStateRange.first,
721  selectedTrackStateRange.second, result, isOutlier, prevTipState,
722  nBranchesOnSurface);
723 
724  if (!procRes.ok()) {
725  ACTS_ERROR(
726  "Processing of selected track states failed: " << procRes.error())
727  return procRes.error();
728  }
729 
730  if (nBranchesOnSurface > 0 and not isOutlier) {
731  // If there are measurement track states on this surface
732  ACTS_VERBOSE("Filtering step successful with " << nBranchesOnSurface
733  << " branches");
734  // Update stepping state using filtered parameters of last track
735  // state on this surface
736  auto ts = result.fittedStates->getTrackState(
737  result.activeTips.back().first);
738  stepper.update(state.stepping,
740  state.options.geoContext, ts),
741  ts.filtered(), ts.filteredCovariance(), *surface);
742  ACTS_VERBOSE("Stepping state is updated with filtered parameter:");
743  ACTS_VERBOSE("-> " << ts.filtered().transpose()
744  << " of track state with tip = "
745  << result.activeTips.back().first);
746  }
747  // Update state and stepper with post material effects
748  materialInteractor(surface, state, stepper, navigator,
749  MaterialUpdateStage::PostUpdate);
750  } else if (surface->associatedDetectorElement() != nullptr ||
751  surface->surfaceMaterial() != nullptr) {
752  // No splitting on the surface without source links. Set it to one
753  // first, but could be changed later
754  nBranchesOnSurface = 1;
755 
756  // Retrieve the previous tip and its state
757  size_t prevTip = SIZE_MAX;
758  TipState tipState;
759  if (not result.activeTips.empty()) {
760  prevTip = result.activeTips.back().first;
761  tipState = result.activeTips.back().second;
762  }
763 
764  // The surface could be either sensitive or passive
765  bool isSensitive = (surface->associatedDetectorElement() != nullptr);
766  bool isMaterial = (surface->surfaceMaterial() != nullptr);
767  std::string type = isSensitive ? "sensitive" : "passive";
768  ACTS_VERBOSE("Detected " << type
769  << " surface: " << surface->geometryId());
770  if (isSensitive) {
771  // Increment of number of passed sensitive surfaces
772  tipState.nSensitiveSurfaces++;
773  }
774  // Add state if there is already measurement detected on this branch
775  // For in-sensitive surface, only add state when smoothing is
776  // required
777  bool createState = false;
778  if (smoothing) {
779  createState = (tipState.nMeasurements > 0 or isMaterial);
780  } else {
781  createState = (tipState.nMeasurements > 0 and isSensitive);
782  }
783  if (createState) {
784  // New state is to be added. Remove the last tip from active tips now
785  if (not result.activeTips.empty()) {
786  result.activeTips.erase(result.activeTips.end() - 1);
787  }
788  // No source links on surface, add either hole or passive material
789  // TrackState. No storage allocation for uncalibrated/calibrated
790  // measurement and filtered parameter
791  auto stateMask =
792  ~(TrackStatePropMask::Calibrated | TrackStatePropMask::Filtered);
793 
794  // Increment of number of processed states
795  tipState.nStates++;
796  size_t currentTip = SIZE_MAX;
797  if (isSensitive) {
798  // Increment of number of holes
799  tipState.nHoles++;
800  }
801 
802  // Transport & bind the state to the current surface
803  auto res = stepper.boundState(state.stepping, *surface);
804  if (!res.ok()) {
805  ACTS_ERROR("Error in filter: " << res.error());
806  return res.error();
807  }
808  const auto boundState = *res;
809  // Add a hole or material track state to the multitrajectory
810  currentTip = addNonSourcelinkState(stateMask, boundState, result,
811  isSensitive, prevTip);
812 
813  // Check the branch
814  if (not m_extensions.branchStopper(tipState)) {
815  // Remember the active tip and its state
816  result.activeTips.emplace_back(currentTip, tipState);
817  } else {
818  // No branch on this surface
819  nBranchesOnSurface = 0;
820  }
821  }
822 
823  // Update state and stepper with material effects
824  materialInteractor(surface, state, stepper, navigator,
825  MaterialUpdateStage::FullUpdate);
826  } else {
827  // Neither measurement nor material on surface, this branch is still
828  // valid. Count the branch on current surface
829  nBranchesOnSurface = 1;
830  }
831 
832  // Reset current tip if there is no branch on current surface
833  if (nBranchesOnSurface == 0) {
834  ACTS_DEBUG("Branch on surface " << surface->geometryId()
835  << " is stopped");
836  if (not result.activeTips.empty()) {
837  ACTS_VERBOSE("Propagation jumps to branch with tip = "
838  << result.activeTips.back().first);
839  reset(state, stepper, navigator, result);
840  } else {
841  ACTS_VERBOSE("Stop Kalman filtering with "
842  << result.lastMeasurementIndices.size()
843  << " found tracks");
844  result.filtered = true;
845  }
846  }
847 
848  return Result<void>::success();
849  }
850 
858  template <typename source_link_iterator_t>
860  result_type& result,
861  const BoundState& boundState,
862  size_t prevTip,
863  source_link_iterator_t slBegin,
864  source_link_iterator_t slEnd) const {
865  const auto& [boundParams, jacobian, pathLength] = boundState;
866 
867  result.trackStateCandidates.clear();
868  if constexpr (std::is_same_v<
869  typename std::iterator_traits<
870  source_link_iterator_t>::iterator_category,
871  std::random_access_iterator_tag>) {
872  result.trackStateCandidates.reserve(std::distance(slBegin, slEnd));
873  }
874 
875  result.stateBuffer->clear();
876 
877  using PM = TrackStatePropMask;
878 
879  // Calibrate all the source links on the surface since the selection has
880  // to be done based on calibrated measurement
881  for (auto it = slBegin; it != slEnd; ++it) {
882  // get the source link
883  const auto sourceLink = *it;
884 
885  // prepare the track state
886  PM mask = PM::Predicted | PM::Jacobian | PM::Calibrated;
887 
888  if (it != slBegin) {
889  // not the first TrackState, only need uncalibrated and calibrated
890  mask = PM::Calibrated;
891  }
892 
893  ACTS_VERBOSE(
894  "Create temp track state with mask: " << std::bitset<
895  sizeof(std::underlying_type_t<TrackStatePropMask>) * 8>(
896  static_cast<std::underlying_type_t<TrackStatePropMask>>(mask)));
897  size_t tsi = result.stateBuffer->addTrackState(mask, prevTip);
898  // CAREFUL! This trackstate has a previous index that is not in this
899  // MultiTrajectory Visiting brackwards from this track state will
900  // fail!
901  auto ts = result.stateBuffer->getTrackState(tsi);
902 
903  if (it == slBegin) {
904  // only set these for first
905  ts.predicted() = boundParams.parameters();
906  if (boundParams.covariance()) {
907  ts.predictedCovariance() = *boundParams.covariance();
908  }
909  ts.jacobian() = jacobian;
910  } else {
911  // subsequent track states can reuse
912  auto& first = result.trackStateCandidates.front();
913  ts.shareFrom(first, PM::Predicted);
914  ts.shareFrom(first, PM::Jacobian);
915  }
916 
917  ts.pathLength() = pathLength;
918 
919  ts.setReferenceSurface(boundParams.referenceSurface().getSharedPtr());
920 
921  // now calibrate the track state
922  m_extensions.calibrator(gctx, calibrationContext, sourceLink, ts);
923 
924  result.trackStateCandidates.push_back(ts);
925  }
926  }
927 
938  typename std::vector<typename traj_t::TrackStateProxy>::const_iterator
939  begin,
940  typename std::vector<typename traj_t::TrackStateProxy>::const_iterator
941  end,
942  result_type& result, bool isOutlier, const TipState& prevTipState,
943  size_t& nBranchesOnSurface) const {
944  using PM = TrackStatePropMask;
945 
946  std::optional<typename traj_t::TrackStateProxy> firstTrackState{
947  std::nullopt};
948  for (auto it = begin; it != end; ++it) {
949  auto& candidateTrackState = *it;
950 
951  PM mask = PM::All;
952 
953  if (it != begin) {
954  // subsequent track states don't need storage for these
955  mask = ~PM::Predicted & ~PM::Jacobian;
956  }
957 
958  if (isOutlier) {
959  mask &= ~PM::Filtered; // outlier won't have separate filtered
960  // parameters
961  }
962 
963  // copy this trackstate into fitted states MultiTrajectory
964  typename traj_t::TrackStateProxy trackState =
965  result.fittedStates->getTrackState(
966  result.fittedStates->addTrackState(
967  mask, candidateTrackState.previous()));
968  ACTS_VERBOSE(
969  "Create SourceLink output track state #"
970  << trackState.index() << " with mask: "
971  << std::bitset<sizeof(std::underlying_type_t<TrackStatePropMask>) *
972  8>{
973  static_cast<std::underlying_type_t<TrackStatePropMask>>(
974  mask)});
975 
976  if (it != begin) {
977  // assign indices pointing to first track state
978  trackState.shareFrom(*firstTrackState, PM::Predicted);
979  trackState.shareFrom(*firstTrackState, PM::Jacobian);
980  } else {
981  firstTrackState = trackState;
982  }
983 
984  // either copy ALL or everything except for predicted and jacobian
985  trackState.allocateCalibrated(candidateTrackState.calibratedSize());
986  trackState.copyFrom(candidateTrackState, mask, false);
987 
988  auto typeFlags = trackState.typeFlags();
989  if (trackState.referenceSurface().surfaceMaterial() != nullptr) {
990  typeFlags.set(TrackStateFlag::MaterialFlag);
991  }
992  typeFlags.set(TrackStateFlag::ParameterFlag);
993 
994  // Inherit the tip state from the previous and will be updated
995  // later
996  TipState tipState = prevTipState;
997  size_t currentTip = trackState.index();
998 
999  // Increment of number of processedState and passed sensitive surfaces
1000  tipState.nSensitiveSurfaces++;
1001  tipState.nStates++;
1002 
1003  if (isOutlier) {
1004  ACTS_VERBOSE(
1005  "Creating outlier track state with tip = " << currentTip);
1006  // Set the outlier flag
1007  typeFlags.set(TrackStateFlag::OutlierFlag);
1008  // Increment number of outliers
1009  tipState.nOutliers++;
1010  // No Kalman update for outlier
1011  // Set the filtered parameter index to be the same with predicted
1012  // parameter
1013  trackState.shareFrom(PM::Predicted, PM::Filtered);
1014 
1015  } else {
1016  // Kalman update
1017  auto updateRes = m_extensions.updater(
1018  gctx, trackState, Direction::Forward, *updaterLogger);
1019  if (!updateRes.ok()) {
1020  ACTS_ERROR("Update step failed: " << updateRes.error());
1021  return updateRes.error();
1022  }
1023  ACTS_VERBOSE(
1024  "Creating measurement track state with tip = " << currentTip);
1025  // Set the measurement flag
1026  typeFlags.set(TrackStateFlag::MeasurementFlag);
1027  // Increment number of measurements
1028  tipState.nMeasurements++;
1029  }
1030 
1031  // Check if need to stop this branch
1032  if (not m_extensions.branchStopper(tipState)) {
1033  // Put tipstate back into active tips to continue with it
1034  result.activeTips.emplace_back(currentTip, tipState);
1035  // Record the number of branches on surface
1036  nBranchesOnSurface++;
1037  }
1038  }
1039  return Result<void>::success();
1040  }
1041 
1053  const BoundState& boundState,
1054  result_type& result, bool isSensitive,
1055  size_t prevTip) const {
1056  // Add a track state
1057  auto currentTip = result.fittedStates->addTrackState(stateMask, prevTip);
1058  ACTS_VERBOSE(
1059  "Create "
1060  << (isSensitive ? "Hole" : "Material") << " output track state #"
1061  << currentTip << " with mask: "
1062  << std::bitset<sizeof(std::underlying_type_t<TrackStatePropMask>) *
1063  8>{
1064  static_cast<std::underlying_type_t<TrackStatePropMask>>(
1065  stateMask)});
1066  // now get track state proxy back
1067  auto trackStateProxy = result.fittedStates->getTrackState(currentTip);
1068 
1069  const auto& [boundParams, jacobian, pathLength] = boundState;
1070  // Fill the track state
1071  trackStateProxy.predicted() = boundParams.parameters();
1072  if (boundParams.covariance().has_value()) {
1073  trackStateProxy.predictedCovariance() = *boundParams.covariance();
1074  }
1075  trackStateProxy.jacobian() = jacobian;
1076  trackStateProxy.pathLength() = pathLength;
1077  // Set the surface
1078  trackStateProxy.setReferenceSurface(
1079  boundParams.referenceSurface().getSharedPtr());
1080  // Set the filtered parameter index to be the same with predicted
1081  // parameter
1082 
1083  // Set the track state flags
1084  auto typeFlags = trackStateProxy.typeFlags();
1085  if (trackStateProxy.referenceSurface().surfaceMaterial() != nullptr) {
1086  typeFlags.set(TrackStateFlag::MaterialFlag);
1087  }
1088  typeFlags.set(TrackStateFlag::ParameterFlag);
1089  if (isSensitive) {
1090  typeFlags.set(TrackStateFlag::HoleFlag);
1091  }
1092 
1093  trackStateProxy.shareFrom(TrackStatePropMask::Predicted,
1094  TrackStatePropMask::Filtered);
1095 
1096  return currentTip;
1097  }
1098 
1111  template <typename propagator_state_t, typename stepper_t,
1112  typename navigator_t>
1113  void materialInteractor(const Surface* surface, propagator_state_t& state,
1114  const stepper_t& stepper,
1115  const navigator_t& navigator,
1116  const MaterialUpdateStage& updateStage) const {
1117  if (surface == nullptr) {
1118  return;
1119  }
1120 
1121  // Indicator if having material
1122  bool hasMaterial = false;
1123 
1124  if (surface->surfaceMaterial() != nullptr) {
1125  // Prepare relevant input particle properties
1126  detail::PointwiseMaterialInteraction interaction(surface, state,
1127  stepper);
1128  // Evaluate the material properties
1129  if (interaction.evaluateMaterialSlab(state, navigator, updateStage)) {
1130  // Surface has material at this stage
1131  hasMaterial = true;
1132 
1133  // Evaluate the material effects
1135  energyLoss);
1136 
1137  // Screen out material effects info
1138  ACTS_VERBOSE("Material effects on surface: "
1139  << surface->geometryId()
1140  << " at update stage: " << updateStage << " are :");
1141  ACTS_VERBOSE("eLoss = "
1142  << interaction.Eloss * interaction.navDir << ", "
1143  << "variancePhi = " << interaction.variancePhi << ", "
1144  << "varianceTheta = " << interaction.varianceTheta
1145  << ", "
1146  << "varianceQoverP = " << interaction.varianceQoverP);
1147 
1148  // Update the state and stepper with material effects
1149  interaction.updateState(state, stepper, addNoise);
1150  }
1151  }
1152 
1153  if (not hasMaterial) {
1154  // Screen out message
1155  ACTS_VERBOSE("No material effects on surface: " << surface->geometryId()
1156  << " at update stage: "
1157  << updateStage);
1158  }
1159  }
1160 
1171  template <typename propagator_state_t, typename stepper_t,
1172  typename navigator_t>
1173  Result<void> finalize(propagator_state_t& state, const stepper_t& stepper,
1174  const navigator_t& navigator,
1175  result_type& result) const {
1176  // The measurement tip of the track being smoothed
1177  const auto& lastMeasurementIndex =
1178  result.lastMeasurementIndices.at(result.iSmoothed);
1179 
1180  // Get the indices of the first states (can be either a measurement or
1181  // material);
1182  size_t firstStateIndex = lastMeasurementIndex;
1183  // Count track states to be smoothed
1184  size_t nStates = 0;
1185  result.fittedStates->applyBackwards(lastMeasurementIndex, [&](auto st) {
1186  bool isMeasurement =
1187  st.typeFlags().test(TrackStateFlag::MeasurementFlag);
1188  bool isOutlier = st.typeFlags().test(TrackStateFlag::OutlierFlag);
1189  // We are excluding non measurement states and outlier here. Those can
1190  // decrease resolution because only the smoothing corrected the very
1191  // first prediction as filtering is not possible.
1192  if (isMeasurement && !isOutlier) {
1193  firstStateIndex = st.index();
1194  }
1195  nStates++;
1196  });
1197  // Return error if the track has no measurement states (but this should
1198  // not happen)
1199  if (nStates == 0) {
1200  ACTS_ERROR("Smoothing for a track without measurements.");
1201  return CombinatorialKalmanFilterError::SmoothFailed;
1202  }
1203  // Screen output for debugging
1204  ACTS_VERBOSE("Apply smoothing on " << nStates
1205  << " filtered track states.");
1206 
1207  // Smooth the track states
1208  auto smoothRes =
1209  m_extensions.smoother(state.geoContext, *result.fittedStates,
1210  lastMeasurementIndex, *smootherLogger);
1211  if (!smoothRes.ok()) {
1212  ACTS_ERROR("Smoothing step failed: " << smoothRes.error());
1213  return smoothRes.error();
1214  }
1215 
1216  // Return in case no target surface
1217  if (smoothingTargetSurface == nullptr) {
1218  return Result<void>::success();
1219  }
1220 
1221  // Obtain the smoothed parameters at first/last measurement state
1222  auto firstCreatedState =
1223  result.fittedStates->getTrackState(firstStateIndex);
1224  auto lastCreatedMeasurement =
1225  result.fittedStates->getTrackState(lastMeasurementIndex);
1226 
1227  // Lambda to get the intersection of the free params on the target surface
1228  auto target = [&](const FreeVector& freeVector) -> SurfaceIntersection {
1229  return smoothingTargetSurface
1230  ->intersect(
1231  state.geoContext, freeVector.segment<3>(eFreePos0),
1232  state.options.direction * freeVector.segment<3>(eFreeDir0),
1233  true, state.options.targetTolerance)
1234  .closest();
1235  };
1236 
1237  // The smoothed free params at the first/last measurement state.
1238  // (the first state can also be a material state)
1239  auto firstParams = MultiTrajectoryHelpers::freeSmoothed(
1240  state.options.geoContext, firstCreatedState);
1241  auto lastParams = MultiTrajectoryHelpers::freeSmoothed(
1242  state.options.geoContext, lastCreatedMeasurement);
1243  // Get the intersections of the smoothed free parameters with the target
1244  // surface
1245  const auto firstIntersection = target(firstParams);
1246  const auto lastIntersection = target(lastParams);
1247 
1248  // Update the stepping parameters - in order to progress to destination.
1249  // At the same time, reverse navigation direction for further stepping if
1250  // necessary.
1251  // @note The stepping parameters is updated to the smoothed parameters at
1252  // either the first measurement state or the last measurement state. It
1253  // assumes the target surface is not within the first and the last
1254  // smoothed measurement state. Also, whether the intersection is on
1255  // surface is not checked here.
1256  bool useFirstTrackState = true;
1258  case CombinatorialKalmanFilterTargetSurfaceStrategy::first:
1259  useFirstTrackState = true;
1260  break;
1261  case CombinatorialKalmanFilterTargetSurfaceStrategy::last:
1262  useFirstTrackState = false;
1263  break;
1264  case CombinatorialKalmanFilterTargetSurfaceStrategy::firstOrLast:
1265  useFirstTrackState = std::abs(firstIntersection.pathLength()) <=
1266  std::abs(lastIntersection.pathLength());
1267  break;
1268  default:
1269  ACTS_ERROR("Unknown target surface strategy");
1270  return KalmanFitterError::SmoothFailed;
1271  }
1272  bool reverseDirection = false;
1273  if (useFirstTrackState) {
1274  stepper.resetState(state.stepping, firstCreatedState.smoothed(),
1275  firstCreatedState.smoothedCovariance(),
1276  firstCreatedState.referenceSurface(),
1277  state.options.maxStepSize);
1278  reverseDirection = firstIntersection.pathLength() < 0;
1279  } else {
1280  stepper.resetState(state.stepping, lastCreatedMeasurement.smoothed(),
1281  lastCreatedMeasurement.smoothedCovariance(),
1282  lastCreatedMeasurement.referenceSurface(),
1283  state.options.maxStepSize);
1284  reverseDirection = lastIntersection.pathLength() < 0;
1285  }
1286  // Reverse the navigation direction if necessary
1287  if (reverseDirection) {
1288  ACTS_VERBOSE(
1289  "Reverse navigation direction after smoothing for reaching the "
1290  "target surface");
1291  state.options.direction = state.options.direction.invert();
1292  }
1293  const auto& surface = useFirstTrackState
1294  ? firstCreatedState.referenceSurface()
1295  : lastCreatedMeasurement.referenceSurface();
1296 
1297  ACTS_VERBOSE(
1298  "Smoothing successful, updating stepping state to smoothed "
1299  "parameters at surface "
1300  << surface.geometryId() << ". Prepared to reach the target surface.");
1301 
1302  // Reset the navigation state to enable propagation towards the target
1303  // surface
1304  // Set targetSurface to nullptr as it is handled manually in the actor
1305  navigator.resetState(
1306  state.navigation, state.geoContext, stepper.position(state.stepping),
1307  state.options.direction * stepper.direction(state.stepping), &surface,
1308  nullptr);
1309 
1310  detail::setupLoopProtection(state, stepper, result.pathLimitReached, true,
1311  logger());
1312 
1313  return Result<void>::success();
1314  }
1315 
1317 
1319  source_link_accessor_t m_sourcelinkAccessor;
1320 
1322  SurfaceReached targetReached{std::numeric_limits<double>::lowest()};
1323 
1326 
1328  const Logger* actorLogger{nullptr};
1330  const Logger* updaterLogger{nullptr};
1332  const Logger* smootherLogger{nullptr};
1333 
1334  const Logger& logger() const { return *actorLogger; }
1335  };
1336 
1337  template <typename source_link_accessor_t, typename parameters_t>
1338  class Aborter {
1339  public:
1342 
1343  template <typename propagator_state_t, typename stepper_t,
1344  typename navigator_t, typename result_t>
1345  bool operator()(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
1346  const navigator_t& /*navigator*/, const result_t& result,
1347  const Logger& /*logger*/) const {
1348  if (result.finished) {
1349  return true;
1350  }
1351  return false;
1352  }
1353  };
1354 
1355  public:
1378  template <typename source_link_iterator_t, typename start_parameters_t,
1379  typename track_container_t, template <typename> class holder_t,
1380  typename parameters_t = BoundTrackParameters>
1382  const start_parameters_t& initialParameters,
1384  tfOptions,
1386  -> Result<std::vector<
1387  typename std::decay_t<decltype(trackContainer)>::TrackProxy>> {
1388  using TrackContainer = typename std::decay_t<decltype(trackContainer)>;
1389  using SourceLinkAccessor =
1391 
1392  // Create the ActionList and AbortList
1393  using CombinatorialKalmanFilterAborter =
1395  using CombinatorialKalmanFilterActor =
1397  using Actors = ActionList<CombinatorialKalmanFilterActor>;
1398  using Aborters = AbortList<CombinatorialKalmanFilterAborter>;
1399 
1400  // Create relevant options for the propagation options
1401  PropagatorOptions<Actors, Aborters> propOptions(tfOptions.geoContext,
1402  tfOptions.magFieldContext);
1403 
1404  // Set the trivial propagator options
1405  propOptions.setPlainOptions(tfOptions.propagatorPlainOptions);
1406 
1407  // Catch the actor
1408  auto& combKalmanActor =
1409  propOptions.actionList.template get<CombinatorialKalmanFilterActor>();
1410  combKalmanActor.filterTargetSurface = tfOptions.filterTargetSurface;
1411  combKalmanActor.smoothingTargetSurface = tfOptions.smoothingTargetSurface;
1412  combKalmanActor.smoothingTargetSurfaceStrategy =
1413  tfOptions.smoothingTargetSurfaceStrategy;
1414  combKalmanActor.multipleScattering = tfOptions.multipleScattering;
1415  combKalmanActor.energyLoss = tfOptions.energyLoss;
1416  combKalmanActor.smoothing = tfOptions.smoothing;
1417  combKalmanActor.actorLogger = m_actorLogger.get();
1418  combKalmanActor.updaterLogger = m_updaterLogger.get();
1419  combKalmanActor.smootherLogger = m_smootherLogger.get();
1420  combKalmanActor.calibrationContext = &tfOptions.calibrationContext.get();
1421 
1422  // copy source link accessor, calibrator and measurement selector
1423  combKalmanActor.m_sourcelinkAccessor = tfOptions.sourcelinkAccessor;
1424  combKalmanActor.m_extensions = tfOptions.extensions;
1425 
1426  // Run the CombinatorialKalmanFilter.
1427  auto stateBuffer = std::make_shared<traj_t>();
1428 
1429  typename propagator_t::template action_list_t_result_t<
1431  inputResult;
1432 
1433  auto& r =
1434  inputResult.template get<CombinatorialKalmanFilterResult<traj_t>>();
1435 
1436  r.fittedStates = &trackContainer.trackStateContainer();
1437  r.stateBuffer = stateBuffer;
1438  r.stateBuffer->clear();
1439 
1440  auto result = m_propagator.template propagate(
1441  initialParameters, propOptions, false, std::move(inputResult));
1442 
1443  if (!result.ok()) {
1444  ACTS_ERROR("Propagation failed: " << result.error() << " "
1445  << result.error().message()
1446  << " with the initial parameters: \n"
1447  << initialParameters.parameters());
1448  return result.error();
1449  }
1450 
1451  auto& propRes = *result;
1452 
1454  auto combKalmanResult = std::move(
1455  propRes.template get<CombinatorialKalmanFilterResult<traj_t>>());
1456 
1459  // -> filtering for track finding;
1460  // -> surface targeting to get fitted parameters at target surface.
1461  // This is regarded as a failure.
1462  // @TODO: Implement distinguishment between the above two cases if
1463  // necessary
1464  if (combKalmanResult.lastError.ok() and not combKalmanResult.finished) {
1465  combKalmanResult.lastError = Result<void>(
1466  CombinatorialKalmanFilterError::PropagationReachesMaxSteps);
1467  }
1468 
1469  if (!combKalmanResult.lastError.ok()) {
1470  ACTS_ERROR("CombinatorialKalmanFilter failed: "
1471  << combKalmanResult.lastError.error() << " "
1472  << combKalmanResult.lastError.error().message()
1473  << " with the initial parameters: \n"
1474  << initialParameters.parameters());
1475  }
1476 
1477  std::vector<typename TrackContainer::TrackProxy> tracks;
1478 
1479  for (auto tip : combKalmanResult.lastMeasurementIndices) {
1480  auto it = combKalmanResult.fittedParameters.find(tip);
1481  if (it == combKalmanResult.fittedParameters.end()) {
1482  continue;
1483  }
1484 
1485  auto track = trackContainer.getTrack(trackContainer.addTrack());
1486  track.tipIndex() = tip;
1487 
1488  const BoundTrackParameters& parameters = it->second;
1489  track.parameters() = parameters.parameters();
1490  track.covariance() = *parameters.covariance();
1491  track.setReferenceSurface(parameters.referenceSurface().getSharedPtr());
1492 
1493  calculateTrackQuantities(track);
1494 
1495  tracks.push_back(track);
1496  }
1497 
1498  return tracks;
1499  }
1500 };
1501 
1502 } // namespace Acts