EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
KalmanFitterTests.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file KalmanFitterTests.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2016-2019 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 #include <boost/test/unit_test.hpp>
10 
36 
37 #include <algorithm>
38 #include <cmath>
39 #include <random>
40 #include <vector>
41 
42 #include <boost/math/distributions/chi_squared.hpp>
43 
44 using namespace Acts::UnitLiterals;
45 
46 namespace Acts {
47 namespace Test {
48 
49 // A few initialisations and definitionas
50 using SourceLink = MinimalSourceLink;
51 using Jacobian = BoundMatrix;
53 
54 using Resolution = std::pair<BoundIndices, double>;
55 using ElementResolution = std::vector<Resolution>;
56 using VolumeResolution = std::map<GeometryIdentifier::Value, ElementResolution>;
57 using DetectorResolution =
58  std::map<GeometryIdentifier::Value, VolumeResolution>;
59 
60 std::normal_distribution<double> gauss(0., 1.);
61 std::default_random_engine generator(42);
62 
63 ActsSymMatrixD<1> cov1D;
65 
66 // Create a test context
70 
71 template <BoundIndices... params>
73 
77 struct MeasurementCreator {
79  MeasurementCreator() = default;
80 
82  DetectorResolution detectorResolution;
83 
84  struct this_result {
85  // The measurements
86  std::vector<FittableMeasurement<SourceLink>> measurements;
87 
88  // The outliers
89  std::vector<FittableMeasurement<SourceLink>> outliers;
90  };
91 
93 
101  template <typename propagator_state_t, typename stepper_t>
102  void operator()(propagator_state_t& state, const stepper_t& stepper,
103  result_type& result) const {
104  // monitor the current surface
105  auto surface = state.navigation.currentSurface;
106  if (surface and surface->associatedDetectorElement()) {
107  auto geoID = surface->geometryId();
108  auto volumeID = geoID.volume();
109  auto layerID = geoID.layer();
110  // find volume and layer information for this
111  auto vResolution = detectorResolution.find(volumeID);
112  if (vResolution != detectorResolution.end()) {
113  // find layer resolutions
114  auto lResolution = vResolution->second.find(layerID);
115  if (lResolution != vResolution->second.end()) {
116  // Apply global to local
117  Acts::Vector2D lPos =
118  surface
119  ->globalToLocal(state.geoContext,
120  stepper.position(state.stepping),
121  stepper.direction(state.stepping))
122  .value();
123 
124  if (lResolution->second.size() == 1) {
125  double sp = lResolution->second[0].second;
126  cov1D << sp * sp;
127  double dp = sp * gauss(generator);
128  if (lResolution->second[0].first == eBoundLoc0) {
129  // push back & move a LOC_0 measurement
130  MeasurementType<eBoundLoc0> m0(surface->getSharedPtr(), {}, cov1D,
131  lPos[eBoundLoc0] + dp);
132  result.measurements.push_back(std::move(m0));
133  // push back & move a LOC_0 outlier
134  MeasurementType<eBoundLoc0> o0(surface->getSharedPtr(), {}, cov1D,
135  lPos[eBoundLoc0] + sp * 10);
136  result.outliers.push_back(std::move(o0));
137  } else {
138  // push back & move a LOC_1 measurement
139  MeasurementType<eBoundLoc1> m1(surface->getSharedPtr(), {}, cov1D,
140  lPos[eBoundLoc1] + dp);
141  result.measurements.push_back(std::move(m1));
142  // push back & move a LOC_1 outlier
143  MeasurementType<eBoundLoc1> o1(surface->getSharedPtr(), {}, cov1D,
144  lPos[eBoundLoc1] + sp * 10);
145  result.outliers.push_back(std::move(o1));
146  }
147  } else if (lResolution->second.size() == 2) {
148  // Create the measurment and move it
149  double sx = lResolution->second[eBoundLoc0].second;
150  double sy = lResolution->second[eBoundLoc1].second;
151  cov2D << sx * sx, 0., 0., sy * sy;
152  double dx = sx * gauss(generator);
153  double dy = sy * gauss(generator);
154  // push back & move a LOC_0, LOC_1 measurement
156  surface->getSharedPtr(), {}, cov2D, lPos[eBoundLoc0] + dx,
157  lPos[eBoundLoc1] + dy);
158  result.measurements.push_back(std::move(m01));
159  // push back & move a LOC_0, LOC_1 outlier
161  surface->getSharedPtr(), {}, cov2D, lPos[eBoundLoc0] + sx * 10,
162  lPos[eBoundLoc1] + sy * 10);
163  result.outliers.push_back(std::move(o01));
164  }
165  }
166  }
167  }
168  }
169 };
170 
171 double dX, dY;
172 Vector3D pos;
173 const Surface* sur;
174 
181  MaterialScattering() = default;
182 
191  template <typename propagator_state_t, typename stepper_t>
192  void operator()(propagator_state_t& state, const stepper_t& stepper) const {
193  // Check if there is a surface with material and a covariance is existing
194  if (state.navigation.currentSurface &&
195  state.navigation.currentSurface->surfaceMaterial() &&
196  state.stepping.cov != Covariance::Zero()) {
197  // Sample angles
198  std::normal_distribution<double> scatterAngle(
199  0., 0.017); //< \approx 1 degree
200  double dPhi = scatterAngle(generator), dTheta = scatterAngle(generator);
201 
202  // Update the covariance
203  state.stepping.cov(eBoundPhi, eBoundPhi) += dPhi * dPhi;
204  state.stepping.cov(eBoundTheta, eBoundTheta) += dTheta * dTheta;
205 
206  // Update the angles
207  auto direction = stepper.direction(state.stepping);
208  double theta = std::acos(direction.z());
209  double phi = std::atan2(direction.y(), direction.x());
210 
211  state.stepping.update(
212  stepper.position(state.stepping),
213  {std::sin(theta + dTheta) * std::cos(phi + dPhi),
214  std::sin(theta + dTheta) * std::sin(phi + dPhi),
215  std::cos(theta + dTheta)},
216  std::max(stepper.momentum(state.stepping) -
218  0.));
219  }
220  }
221 };
222 
225  double measurementSignificanceCutoff = 0;
227  double chi2Tolerance = 10e-5;
228 
236  template <typename track_state_t>
237  bool operator()(const track_state_t& state) const {
238  // Can't determine if it's an outlier if no calibrated measurement or no
239  // predicted parameters
240  if (not state.hasCalibrated() or not state.hasPredicted()) {
241  return false;
242  }
243 
244  // The predicted parameters coefficients
245  const auto& predicted = state.predicted();
246  // The predicted parameters covariance
247  const auto& predicted_covariance = state.predictedCovariance();
248 
249  // Calculate the chi2 using predicted parameters and calibrated measurement
250  double chi2 = std::numeric_limits<double>::max();
252  state.calibrated(), state.calibratedCovariance(),
253  state.calibratedSize(),
254  [&](const auto calibrated, const auto calibrated_covariance) {
255  constexpr size_t measdim = decltype(calibrated)::RowsAtCompileTime;
256  using par_t = ActsVectorD<measdim>;
257 
258  // Take the projector (measurement mapping function)
260  state.projector().template topLeftCorner<measdim, eBoundSize>();
261 
262  // Calculate the residual
263  const par_t residual = calibrated - H * predicted;
264 
265  // Calculate the chi2
266  chi2 = (residual.transpose() *
267  ((calibrated_covariance +
268  H * predicted_covariance * H.transpose()))
269  .inverse() *
270  residual)
271  .eval()(0, 0);
272  });
273 
274  // In case the chi2 is too small
275  if (std::abs(chi2) < chi2Tolerance) {
276  return false;
277  }
278  // The chisq distribution
279  boost::math::chi_squared chiDist(state.calibratedSize());
280  // The p-Value
281  double pValue = 1 - boost::math::cdf(chiDist, chi2);
282  // If pValue is NOT significant enough => outlier
283  return pValue > measurementSignificanceCutoff ? false : true;
284  }
285 };
286 
290 BOOST_AUTO_TEST_CASE(kalman_fitter_zero_field) {
291  // Build detector
293  auto detector = cGeometry();
294 
295  // Build navigator for the measurement creatoin
296  Navigator mNavigator(detector);
297  mNavigator.resolvePassive = false;
298  mNavigator.resolveMaterial = true;
299  mNavigator.resolveSensitive = true;
300 
301  // Use straingt line stepper to create the measurements
302  StraightLineStepper mStepper;
303 
304  // Define the measurement propagator
305  using MeasurementPropagator = Propagator<StraightLineStepper, Navigator>;
306 
307  // Build propagator for the measurement creation
308  MeasurementPropagator mPropagator(mStepper, mNavigator);
309  Vector4D mPos4(-3_m, 0., 0., 42_ns);
310  Vector3D mDir(1, 0., 0);
311  NeutralCurvilinearTrackParameters mStart(mPos4, mDir, 1 / 1_GeV);
312 
313  // Create action list for the measurement creation
314  using MeasurementActions = ActionList<MeasurementCreator>;
315  using MeasurementAborters = AbortList<EndOfWorldReached>;
316 
317  auto pixelResX = Resolution(eBoundLoc0, 25_um);
318  auto pixelResY = Resolution(eBoundLoc1, 50_um);
319  auto stripResX = Resolution(eBoundLoc0, 100_um);
320  auto stripResY = Resolution(eBoundLoc1, 150_um);
321 
322  ElementResolution pixelElementRes = {pixelResX, pixelResY};
323  ElementResolution stripElementResI = {stripResX};
324  ElementResolution stripElementResO = {stripResY};
325 
326  VolumeResolution pixelVolumeRes;
327  pixelVolumeRes[2] = pixelElementRes;
328  pixelVolumeRes[4] = pixelElementRes;
329 
330  VolumeResolution stripVolumeRes;
331  stripVolumeRes[2] = stripElementResI;
332  stripVolumeRes[4] = stripElementResO;
333  stripVolumeRes[6] = stripElementResI;
334  stripVolumeRes[8] = stripElementResO;
335 
336  DetectorResolution detRes;
337  detRes[2] = pixelVolumeRes;
338  detRes[3] = stripVolumeRes;
339 
340  // Set options for propagator
343  auto& mCreator = mOptions.actionList.get<MeasurementCreator>();
344  mCreator.detectorResolution = detRes;
345 
346  // Launch and collect - the measurements
347  auto mResult = mPropagator.propagate(mStart, mOptions).value();
348 
349  // Extract measurements from result of propagation.
350  // This vector owns the measurements
351  std::vector<FittableMeasurement<SourceLink>> measurements = std::move(
352  mResult.template get<MeasurementCreator::result_type>().measurements);
353  BOOST_CHECK_EQUAL(measurements.size(), 6u);
354 
355  // Make a vector of source links as input to the KF
356  std::vector<SourceLink> sourcelinks;
357  std::transform(measurements.begin(), measurements.end(),
358  std::back_inserter(sourcelinks),
359  [](const auto& m) { return SourceLink{&m}; });
360 
361  // The KalmanFitter - we use the eigen stepper for covariance transport
362  // Build navigator for the measurement creatoin
363  Navigator rNavigator(detector);
364  rNavigator.resolvePassive = false;
365  rNavigator.resolveMaterial = true;
366  rNavigator.resolveSensitive = true;
367 
368  // Configure propagation with deactivated B-field
369  ConstantBField bField(Vector3D(0., 0., 0.));
370  using RecoStepper = EigenStepper<ConstantBField>;
371  RecoStepper rStepper(bField);
372  using RecoPropagator = Propagator<RecoStepper, Navigator>;
373  RecoPropagator rPropagator(rStepper, rNavigator);
374 
375  // Set initial parameters for the particle track
376  Covariance cov;
377  cov << 1000_um, 0., 0., 0., 0., 0., 0., 1000_um, 0., 0., 0., 0., 0., 0., 0.05,
378  0., 0., 0., 0., 0., 0., 0.05, 0., 0., 0., 0., 0., 0., 0.01, 0., 0., 0.,
379  0., 0., 0., 1.;
380 
381  Vector4D rPos4(-3_m, 10_um * gauss(generator), 100_um * gauss(generator),
382  42_ns);
383  Vector3D rDir(1_GeV, 0.025 * gauss(generator), 0.025 * gauss(generator));
384  CurvilinearTrackParameters rStart(rPos4, rDir, 1_e / 1_GeV, cov);
385 
386  const Surface* rSurface = &rStart.referenceSurface();
387 
388  using Updater = GainMatrixUpdater;
389  using Smoother = GainMatrixSmoother;
390  using KalmanFitter =
392 
393  MinimalOutlierFinder outlierFinder;
394  outlierFinder.measurementSignificanceCutoff = 0.05;
395  auto kfLogger = getDefaultLogger("KalmanFilter", Logging::VERBOSE);
396 
398  tgContext, mfContext, calContext, outlierFinder, LoggerWrapper{*kfLogger},
399  PropagatorPlainOptions(), rSurface);
400 
401  KalmanFitter kFitter(rPropagator);
402 
403  // Fit the track
404  auto fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
405  BOOST_CHECK(fitRes.ok());
406  auto& fittedTrack = *fitRes;
407  auto fittedParameters = fittedTrack.fittedParameters.value();
408 
409  // Calculate global track parameters covariance matrix
410  const auto& [trackParamsCov, stateRowIndices] =
411  detail::globalTrackParametersCovariance(fittedTrack.fittedStates,
412  fittedTrack.trackTip);
413 
414  // Check the size of the global track parameters size
415  BOOST_CHECK_EQUAL(stateRowIndices.size(), 6);
416  BOOST_CHECK_EQUAL(stateRowIndices.at(fittedTrack.trackTip), 30);
417  BOOST_CHECK_EQUAL(trackParamsCov.rows(), 6 * eBoundSize);
418 
419  // Make sure it is deterministic
420  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
421  BOOST_CHECK(fitRes.ok());
422  auto& fittedAgainTrack = *fitRes;
423  auto fittedAgainParameters = fittedAgainTrack.fittedParameters.value();
424 
425  CHECK_CLOSE_REL(fittedParameters.parameters().template head<5>(),
426  fittedAgainParameters.parameters().template head<5>(), 1e-5);
427  CHECK_CLOSE_ABS(fittedParameters.parameters().template tail<1>(),
428  fittedAgainParameters.parameters().template tail<1>(), 1e-5);
429 
430  // Fit without target surface
431  kfOptions.referenceSurface = nullptr;
432  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
433  BOOST_CHECK(fitRes.ok());
434  auto fittedWithoutTargetSurface = *fitRes;
435  // Check if there is no fitted parameters
436  BOOST_CHECK(fittedWithoutTargetSurface.fittedParameters == std::nullopt);
437 
438  // Reset the target surface
439  kfOptions.referenceSurface = rSurface;
440 
441  // Change the order of the sourcelinks
442  std::vector<SourceLink> shuffledMeasurements = {
443  sourcelinks[3], sourcelinks[2], sourcelinks[1],
444  sourcelinks[4], sourcelinks[5], sourcelinks[0]};
445 
446  // Make sure it works for shuffled measurements as well
447  fitRes = kFitter.fit(shuffledMeasurements, rStart, kfOptions);
448  BOOST_CHECK(fitRes.ok());
449  auto& fittedShuffledTrack = *fitRes;
450  auto fittedShuffledParameters = fittedShuffledTrack.fittedParameters.value();
451 
452  CHECK_CLOSE_REL(fittedParameters.parameters().template head<5>(),
453  fittedShuffledParameters.parameters().template head<5>(),
454  1e-5);
455  CHECK_CLOSE_ABS(fittedParameters.parameters().template tail<1>(),
456  fittedShuffledParameters.parameters().template tail<1>(),
457  1e-5);
458 
459  // Remove one measurement and find a hole
460  std::vector<SourceLink> measurementsWithHole = {
461  sourcelinks[0], sourcelinks[1], sourcelinks[2], sourcelinks[4],
462  sourcelinks[5]};
463 
464  // Make sure it works for shuffled measurements as well
465  fitRes = kFitter.fit(measurementsWithHole, rStart, kfOptions);
466  BOOST_CHECK(fitRes.ok());
467  auto& fittedWithHoleTrack = *fitRes;
468  auto fittedWithHoleParameters = fittedWithHoleTrack.fittedParameters.value();
469 
470  // Calculate global track parameters covariance matrix
471  const auto& [holeTrackTrackParamsCov, holeTrackStateRowIndices] =
472  detail::globalTrackParametersCovariance(fittedWithHoleTrack.fittedStates,
473  fittedWithHoleTrack.trackTip);
474 
475  // Check the size of the global track parameters size
476  BOOST_CHECK_EQUAL(holeTrackStateRowIndices.size(), 6);
477  BOOST_CHECK_EQUAL(holeTrackStateRowIndices.at(fittedWithHoleTrack.trackTip),
478  30);
479  BOOST_CHECK_EQUAL(holeTrackTrackParamsCov.rows(), 6 * eBoundSize);
480 
481  // Count one hole
482  BOOST_CHECK_EQUAL(fittedWithHoleTrack.missedActiveSurfaces.size(), 1u);
483  // And the parameters should be different
484  //~
485  // BOOST_CHECK(!Acts::Test::checkCloseRel(fittedParameters.parameters().template
486  // head<5>(), ~ fittedWithHoleParameters.parameters().template head<5>(), ~
487  // 1e-6));
488  BOOST_CHECK(!Acts::Test::checkCloseRel(fittedParameters.parameters(),
489  fittedWithHoleParameters.parameters(),
490  1e-6));
491 
492  // Run KF fit in backward filtering mode
493  kfOptions.backwardFiltering = true;
494  // Fit the track
495  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
496  BOOST_CHECK(fitRes.ok());
497  auto fittedWithBwdFiltering = *fitRes;
498  // Check the filtering and smoothing status flag
499  BOOST_CHECK(fittedWithBwdFiltering.forwardFiltered);
500  BOOST_CHECK(not fittedWithBwdFiltering.smoothed);
501 
502  // Count the number of 'smoothed' states
503  auto trackTip = fittedWithBwdFiltering.trackTip;
504  auto mj = fittedWithBwdFiltering.fittedStates;
505  size_t nSmoothed = 0;
506  mj.visitBackwards(trackTip, [&](const auto& state) {
507  if (state.hasSmoothed())
508  nSmoothed++;
509  });
510  BOOST_CHECK_EQUAL(nSmoothed, 6u);
511 
512  // Reset to use smoothing formalism
513  kfOptions.backwardFiltering = false;
514 
515  // Extract outliers from result of propagation.
516  // This vector owns the outliers
517  std::vector<FittableMeasurement<SourceLink>> outliers = std::move(
518  mResult.template get<MeasurementCreator::result_type>().outliers);
519 
520  // Replace one measurement with outlier
521  std::vector<SourceLink> measurementsWithOneOutlier = {
522  sourcelinks[0], sourcelinks[1], sourcelinks[2],
523  SourceLink{&outliers[3]}, sourcelinks[4], sourcelinks[5]};
524 
525  // Make sure it works with one outlier
526  fitRes = kFitter.fit(measurementsWithOneOutlier, rStart, kfOptions);
527  BOOST_CHECK(fitRes.ok());
528  auto& fittedWithOneOutlier = *fitRes;
529 
530  // Count the number of outliers
531  trackTip = fittedWithOneOutlier.trackTip;
532  mj = fittedWithOneOutlier.fittedStates;
533  size_t nOutliers = 0;
534  mj.visitBackwards(trackTip, [&](const auto& state) {
535  auto typeFlags = state.typeFlags();
536  if (typeFlags.test(TrackStateFlag::OutlierFlag)) {
537  nOutliers++;
538  }
539  });
540  BOOST_CHECK_EQUAL(nOutliers, 1u);
541 }
542 
543 } // namespace Test
544 } // namespace Acts