ECCE @ 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/math/distributions/chi_squared.hpp>
10 #include <boost/test/unit_test.hpp>
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <random>
15 #include <vector>
16 
42 
43 using namespace Acts::UnitLiterals;
44 
45 namespace Acts {
46 namespace Test {
47 
48 // A few initialisations and definitionas
49 using SourceLink = MinimalSourceLink;
52 
53 using TrackState = TrackState<SourceLink, BoundParameters>;
54 using Resolution = std::pair<ParID_t, double>;
55 using ElementResolution = std::vector<Resolution>;
56 using VolumeResolution = std::map<GeometryID::Value, ElementResolution>;
57 using DetectorResolution = std::map<GeometryID::Value, VolumeResolution>;
58 
60 
61 std::normal_distribution<double> gauss(0., 1.);
62 std::default_random_engine generator(42);
63 
66 
67 bool debugMode = false;
68 
69 // Create a test context
73 
74 template <ParID_t... params>
75 using MeasurementType = Measurement<SourceLink, params...>;
76 
82  MeasurementCreator() = default;
83 
86 
87  struct this_result {
88  // The measurements
89  std::vector<FittableMeasurement<SourceLink>> measurements;
90 
91  // The outliers
92  std::vector<FittableMeasurement<SourceLink>> outliers;
93  };
94 
96 
104  template <typename propagator_state_t, typename stepper_t>
105  void operator()(propagator_state_t& state, const stepper_t& stepper,
106  result_type& result) const {
107  // monitor the current surface
108  auto surface = state.navigation.currentSurface;
109  if (surface and surface->associatedDetectorElement()) {
110  auto geoID = surface->geoID();
111  auto volumeID = geoID.volume();
112  auto layerID = geoID.layer();
113  // find volume and layer information for this
114  auto vResolution = detectorResolution.find(volumeID);
115  if (vResolution != detectorResolution.end()) {
116  // find layer resolutions
117  auto lResolution = vResolution->second.find(layerID);
118  if (lResolution != vResolution->second.end()) {
119  // Apply global to local
120  Acts::Vector2D lPos;
121  surface->globalToLocal(state.geoContext,
122  stepper.position(state.stepping),
123  stepper.direction(state.stepping), lPos);
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 == eLOC_0) {
129  // push back & move a LOC_0 measurement
130  MeasurementType<eLOC_0> m0(surface->getSharedPtr(), {}, cov1D,
131  lPos[eLOC_0] + dp);
132  result.measurements.push_back(std::move(m0));
133  // push back & move a LOC_0 outlier
134  MeasurementType<eLOC_0> o0(surface->getSharedPtr(), {}, cov1D,
135  lPos[eLOC_0] + sp * 10);
136  result.outliers.push_back(std::move(o0));
137  } else {
138  // push back & move a LOC_1 measurement
139  MeasurementType<eLOC_1> m1(surface->getSharedPtr(), {}, cov1D,
140  lPos[eLOC_1] + dp);
141  result.measurements.push_back(std::move(m1));
142  // push back & move a LOC_1 outlier
143  MeasurementType<eLOC_1> o1(surface->getSharedPtr(), {}, cov1D,
144  lPos[eLOC_1] + 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[eLOC_0].second;
150  double sy = lResolution->second[eLOC_1].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
155  MeasurementType<eLOC_0, eLOC_1> m01(surface->getSharedPtr(), {},
156  cov2D, lPos[eLOC_0] + dx,
157  lPos[eLOC_1] + dy);
158  result.measurements.push_back(std::move(m01));
159  // push back & move a LOC_0, LOC_1 outlier
160  MeasurementType<eLOC_0, eLOC_1> o01(surface->getSharedPtr(), {},
161  cov2D, lPos[eLOC_0] + sx * 10,
162  lPos[eLOC_1] + sy * 10);
163  result.outliers.push_back(std::move(o01));
164  }
165  }
166  }
167  }
168  }
169 };
170 
171 double dX, dY;
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(ePHI, ePHI) += dPhi * dPhi;
204  state.stepping.cov(eTHETA, eTHETA) += 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& trackState) const {
238  double chi2 = trackState.chi2();
239  if (std::abs(chi2) < chi2Tolerance) {
240  return false;
241  }
242  // The measurement dimension
243  size_t ndf = trackState.calibratedSize();
244  // The chisq distribution
245  boost::math::chi_squared chiDist(ndf);
246  // The p-Value
247  double pValue = 1 - boost::math::cdf(chiDist, chi2);
248  // If pValue is NOT significant enough => outlier
249  return pValue > measurementSignificanceCutoff ? false : true;
250  }
251 };
252 
256 BOOST_AUTO_TEST_CASE(kalman_fitter_zero_field) {
257  // Build detector
259  auto detector = cGeometry();
260 
261  // Build navigator for the measurement creatoin
262  Navigator mNavigator(detector);
263  mNavigator.resolvePassive = false;
264  mNavigator.resolveMaterial = true;
265  mNavigator.resolveSensitive = true;
266 
267  // Use straingt line stepper to create the measurements
268  StraightLineStepper mStepper;
269 
270  // Define the measurement propagator
271  using MeasurementPropagator = Propagator<StraightLineStepper, Navigator>;
272 
273  // Build propagator for the measurement creation
274  MeasurementPropagator mPropagator(mStepper, mNavigator);
275  Vector3D mPos(-3_m, 0., 0.), mMom(1_GeV, 0., 0);
276  SingleCurvilinearTrackParameters<NeutralPolicy> mStart(std::nullopt, mPos,
277  mMom, 42_ns);
278 
279  // Create action list for the measurement creation
280  using MeasurementActions = ActionList<MeasurementCreator, DebugOutput>;
281  using MeasurementAborters = AbortList<EndOfWorldReached>;
282 
283  auto pixelResX = Resolution(eLOC_0, 25_um);
284  auto pixelResY = Resolution(eLOC_1, 50_um);
285  auto stripResX = Resolution(eLOC_0, 100_um);
286  auto stripResY = Resolution(eLOC_1, 150_um);
287 
288  ElementResolution pixelElementRes = {pixelResX, pixelResY};
289  ElementResolution stripElementResI = {stripResX};
290  ElementResolution stripElementResO = {stripResY};
291 
292  VolumeResolution pixelVolumeRes;
293  pixelVolumeRes[2] = pixelElementRes;
294  pixelVolumeRes[4] = pixelElementRes;
295 
296  VolumeResolution stripVolumeRes;
297  stripVolumeRes[2] = stripElementResI;
298  stripVolumeRes[4] = stripElementResO;
299  stripVolumeRes[6] = stripElementResI;
300  stripVolumeRes[8] = stripElementResO;
301 
302  DetectorResolution detRes;
303  detRes[2] = pixelVolumeRes;
304  detRes[3] = stripVolumeRes;
305 
306  // Set options for propagator
309  mOptions.debug = debugMode;
310  auto& mCreator = mOptions.actionList.get<MeasurementCreator>();
311  mCreator.detectorResolution = detRes;
312 
313  // Launch and collect - the measurements
314  auto mResult = mPropagator.propagate(mStart, mOptions).value();
315  if (debugMode) {
316  const auto debugString =
317  mResult.template get<DebugOutput::result_type>().debugString;
318  std::cout << ">>>> Measurement creation: " << std::endl;
319  std::cout << debugString;
320  }
321 
322  // Extract measurements from result of propagation.
323  // This vector owns the measurements
324  std::vector<FittableMeasurement<SourceLink>> measurements = std::move(
325  mResult.template get<MeasurementCreator::result_type>().measurements);
326  BOOST_CHECK_EQUAL(measurements.size(), 6u);
327 
328  // Make a vector of source links as input to the KF
329  std::vector<SourceLink> sourcelinks;
330  std::transform(measurements.begin(), measurements.end(),
331  std::back_inserter(sourcelinks),
332  [](const auto& m) { return SourceLink{&m}; });
333 
334  // The KalmanFitter - we use the eigen stepper for covariance transport
335  // Build navigator for the measurement creatoin
336  Navigator rNavigator(detector);
337  rNavigator.resolvePassive = false;
338  rNavigator.resolveMaterial = true;
339  rNavigator.resolveSensitive = true;
340 
341  // Configure propagation with deactivated B-field
342  ConstantBField bField(Vector3D(0., 0., 0.));
343  using RecoStepper = EigenStepper<ConstantBField>;
344  RecoStepper rStepper(bField);
345  using RecoPropagator = Propagator<RecoStepper, Navigator>;
346  RecoPropagator rPropagator(rStepper, rNavigator);
347 
348  // Set initial parameters for the particle track
349  Covariance cov;
350  cov << 1000_um, 0., 0., 0., 0., 0., 0., 1000_um, 0., 0., 0., 0., 0., 0., 0.05,
351  0., 0., 0., 0., 0., 0., 0.05, 0., 0., 0., 0., 0., 0., 0.01, 0., 0., 0.,
352  0., 0., 0., 1.;
353 
354  Vector3D rPos(-3_m, 10_um * gauss(generator), 100_um * gauss(generator));
355  Vector3D rMom(1_GeV, 0.025_GeV * gauss(generator),
356  0.025_GeV * gauss(generator));
357 
358  SingleCurvilinearTrackParameters<ChargedPolicy> rStart(cov, rPos, rMom, 1.,
359  42.);
360 
361  const Surface* rSurface = &rStart.referenceSurface();
362 
363  using Updater = GainMatrixUpdater<BoundParameters>;
364  using Smoother = GainMatrixSmoother<BoundParameters>;
365  using KalmanFitter =
367 
368  MinimalOutlierFinder outlierFinder;
369  outlierFinder.measurementSignificanceCutoff = 0.05;
370 
371  KalmanFitter kFitter(rPropagator,
372  getDefaultLogger("KalmanFilter", Logging::VERBOSE));
373 
375  tgContext, mfContext, calContext, outlierFinder, rSurface);
376 
377  // Fit the track
378  auto fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
379  BOOST_CHECK(fitRes.ok());
380  auto& fittedTrack = *fitRes;
381  auto fittedParameters = fittedTrack.fittedParameters.value();
382 
383  // Make sure it is deterministic
384  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
385  BOOST_CHECK(fitRes.ok());
386  auto& fittedAgainTrack = *fitRes;
387  auto fittedAgainParameters = fittedAgainTrack.fittedParameters.value();
388 
389  CHECK_CLOSE_REL(fittedParameters.parameters().template head<5>(),
390  fittedAgainParameters.parameters().template head<5>(), 1e-5);
391  CHECK_CLOSE_ABS(fittedParameters.parameters().template tail<1>(),
392  fittedAgainParameters.parameters().template tail<1>(), 1e-5);
393 
394  // Change the order of the sourcelinks
395  std::vector<SourceLink> shuffledMeasurements = {
396  sourcelinks[3], sourcelinks[2], sourcelinks[1],
397  sourcelinks[4], sourcelinks[5], sourcelinks[0]};
398 
399  // Make sure it works for shuffled measurements as well
400  fitRes = kFitter.fit(shuffledMeasurements, rStart, kfOptions);
401  BOOST_CHECK(fitRes.ok());
402  auto& fittedShuffledTrack = *fitRes;
403  auto fittedShuffledParameters = fittedShuffledTrack.fittedParameters.value();
404 
405  CHECK_CLOSE_REL(fittedParameters.parameters().template head<5>(),
406  fittedShuffledParameters.parameters().template head<5>(),
407  1e-5);
408  CHECK_CLOSE_ABS(fittedParameters.parameters().template tail<1>(),
409  fittedShuffledParameters.parameters().template tail<1>(),
410  1e-5);
411 
412  // Remove one measurement and find a hole
413  std::vector<SourceLink> measurementsWithHole = {
414  sourcelinks[0], sourcelinks[1], sourcelinks[2], sourcelinks[4],
415  sourcelinks[5]};
416 
417  // Make sure it works for shuffled measurements as well
418  fitRes = kFitter.fit(measurementsWithHole, rStart, kfOptions);
419  BOOST_CHECK(fitRes.ok());
420  auto& fittedWithHoleTrack = *fitRes;
421  auto fittedWithHoleParameters = fittedWithHoleTrack.fittedParameters.value();
422 
423  // Count one hole
424  BOOST_CHECK_EQUAL(fittedWithHoleTrack.missedActiveSurfaces.size(), 1u);
425  // And the parameters should be different
426  //~
427  // BOOST_CHECK(!Acts::Test::checkCloseRel(fittedParameters.parameters().template
428  // head<5>(), ~ fittedWithHoleParameters.parameters().template head<5>(), ~
429  // 1e-6));
430  BOOST_CHECK(!Acts::Test::checkCloseRel(fittedParameters.parameters(),
431  fittedWithHoleParameters.parameters(),
432  1e-6));
433 
434  // Run KF fit in backward filtering mode
435  kfOptions.backwardFiltering = true;
436  // Fit the track
437  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
438  BOOST_CHECK(fitRes.ok());
439  auto fittedWithBwdFiltering = *fitRes;
440  // Check the filtering and smoothing status flag
441  BOOST_CHECK(fittedWithBwdFiltering.forwardFiltered);
442  BOOST_CHECK(not fittedWithBwdFiltering.smoothed);
443 
444  // Count the number of 'smoothed' states
445  auto trackTip = fittedWithBwdFiltering.trackTip;
446  auto mj = fittedWithBwdFiltering.fittedStates;
447  size_t nSmoothed = 0;
448  mj.visitBackwards(trackTip, [&](const auto& state) {
449  if (state.hasSmoothed())
450  nSmoothed++;
451  });
452  BOOST_CHECK_EQUAL(nSmoothed, 6u);
453 
454  // Extract outliers from result of propagation.
455  // This vector owns the outliers
456  std::vector<FittableMeasurement<SourceLink>> outliers = std::move(
457  mResult.template get<MeasurementCreator::result_type>().outliers);
458 
459  // Replace one measurement with outlier
460  std::vector<SourceLink> measurementsWithOneOutlier = {
461  sourcelinks[0], sourcelinks[1], sourcelinks[2],
462  SourceLink{&outliers[3]}, sourcelinks[4], sourcelinks[5]};
463 
464  // Make sure it works with one outlier
465  fitRes = kFitter.fit(measurementsWithOneOutlier, rStart, kfOptions);
466  BOOST_CHECK(fitRes.ok());
467  auto& fittedWithOneOutlier = *fitRes;
468 
469  // Count the number of outliers
470  trackTip = fittedWithOneOutlier.trackTip;
471  mj = fittedWithOneOutlier.fittedStates;
472  size_t nOutliers = 0;
473  mj.visitBackwards(trackTip, [&](const auto& state) {
474  auto typeFlags = state.typeFlags();
475  if (typeFlags.test(TrackStateFlag::OutlierFlag)) {
476  nOutliers++;
477  }
478  });
479  BOOST_CHECK_EQUAL(nOutliers, 1u);
480 }
481 
482 } // namespace Test
483 } // namespace Acts