Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CombinatorialKalmanFilterTests.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file CombinatorialKalmanFilterTests.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2019-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 #include <boost/test/unit_test.hpp>
10 
50 
51 #include <algorithm>
52 #include <cassert>
53 #include <cmath>
54 #include <cstddef>
55 #include <functional>
56 #include <limits>
57 #include <map>
58 #include <memory>
59 #include <ostream>
60 #include <random>
61 #include <string>
62 #include <system_error>
63 #include <unordered_map>
64 #include <utility>
65 #include <vector>
66 
67 namespace Acts {
68 class TrackingGeometry;
69 } // namespace Acts
70 
71 namespace {
72 
73 using namespace Acts::Test;
74 using namespace Acts::UnitLiterals;
75 
76 static const auto pion = Acts::ParticleHypothesis::pion();
77 
78 struct Detector {
79  // expected number of measurements for the given detector
80  size_t numMeasurements = 6u;
81 
82  // geometry
84  std::shared_ptr<const Acts::TrackingGeometry> geometry;
85 
86  // resolutions
87  MeasurementResolution resPixel = {MeasurementType::eLoc01, {25_um, 50_um}};
88  MeasurementResolution resStrip0 = {MeasurementType::eLoc0, {100_um}};
89  MeasurementResolution resStrip1 = {MeasurementType::eLoc1, {150_um}};
92  {Acts::GeometryIdentifier().setVolume(3).setLayer(2), resStrip0},
93  {Acts::GeometryIdentifier().setVolume(3).setLayer(4), resStrip1},
94  {Acts::GeometryIdentifier().setVolume(3).setLayer(6), resStrip0},
95  {Acts::GeometryIdentifier().setVolume(3).setLayer(8), resStrip1},
96  };
97 
99  : store(geoCtx), geometry(store()) {}
100 };
101 
103 template <typename container_t>
104 struct TestContainerAccessor {
105  using Container = container_t;
106  using Key = typename container_t::key_type;
107  using Value = typename container_t::mapped_type;
108 
111  struct Iterator {
112  using BaseIterator = typename container_t::const_iterator;
113 
114  using iterator_category = typename BaseIterator::iterator_category;
115  using value_type = typename BaseIterator::value_type;
116  using difference_type = typename BaseIterator::difference_type;
117  using pointer = typename BaseIterator::pointer;
118  using reference = typename BaseIterator::reference;
119 
120  Iterator& operator++() {
121  ++m_iterator;
122  return *this;
123  }
124 
125  bool operator==(const Iterator& other) const {
126  return m_iterator == other.m_iterator;
127  }
128 
129  bool operator!=(const Iterator& other) const { return !(*this == other); }
130 
131  Acts::SourceLink operator*() const {
132  const auto& sl = m_iterator->second;
133  return Acts::SourceLink{sl};
134  }
135 
136  BaseIterator m_iterator;
137  };
138 
139  // pointer to the container
140  const Container* container = nullptr;
141 
142  // get the range of elements with requested key
143  std::pair<Iterator, Iterator> range(const Acts::Surface& surface) const {
144  assert(container != nullptr);
145  auto [begin, end] = container->equal_range(surface.geometryId());
146  return {Iterator{begin}, Iterator{end}};
147  }
148 };
149 
150 struct Fixture {
151  using StraightPropagator =
156 
158 
159  using KalmanUpdater = Acts::GainMatrixUpdater;
160  using KalmanSmoother = Acts::GainMatrixSmoother;
161  using CombinatorialKalmanFilter =
163  using TestSourceLinkContainer =
164  std::unordered_multimap<Acts::GeometryIdentifier, TestSourceLink>;
165  using TestSourceLinkAccessor = TestContainerAccessor<TestSourceLinkContainer>;
166  using CombinatorialKalmanFilterOptions =
167  Acts::CombinatorialKalmanFilterOptions<TestSourceLinkAccessor::Iterator,
168  Trajectory>;
169 
170  KalmanUpdater kfUpdater;
171  KalmanSmoother kfSmoother;
172 
176 
178 
179  // track parameters before and after the detector
180  std::vector<Acts::CurvilinearTrackParameters> startParameters;
181  std::vector<Acts::CurvilinearTrackParameters> endParameters;
182 
183  // generated measurements
184  TestSourceLinkContainer sourceLinks;
185 
186  // CKF implementation to be tested
187  CombinatorialKalmanFilter ckf;
188  // configuration for the measurement selector
189  Acts::MeasurementSelector::Config measurementSelectorCfg = {
190  // global default: no chi2 cut, only one measurement per surface
192  {{}, {std::numeric_limits<double>::max()}, {1u}}},
193  };
194 
195  Acts::MeasurementSelector measSel{measurementSelectorCfg};
196 
199  extensions.calibrator
200  .template connect<&testSourceLinkCalibrator<Trajectory>>();
201  extensions.updater.template connect<&KalmanUpdater::operator()<Trajectory>>(
202  &kfUpdater);
203  extensions.smoother
204  .template connect<&KalmanSmoother::operator()<Trajectory>>(&kfSmoother);
205  extensions.measurementSelector
206  .template connect<&Acts::MeasurementSelector::select<Trajectory>>(
207  &measSel);
208  return extensions;
209  }
210 
211  std::unique_ptr<const Acts::Logger> logger;
212 
213  Fixture(double bz)
214  : detector(geoCtx),
216  logger(Acts::getDefaultLogger("CkfTest", Acts::Logging::INFO)) {
217  // construct initial parameters
218  // create common covariance matrix from reasonable standard deviations
219  Acts::BoundVector stddev;
220  stddev[Acts::eBoundLoc0] = 100_um;
221  stddev[Acts::eBoundLoc1] = 100_um;
222  stddev[Acts::eBoundTime] = 25_ns;
223  stddev[Acts::eBoundPhi] = 2_degree;
224  stddev[Acts::eBoundTheta] = 2_degree;
225  stddev[Acts::eBoundQOverP] = 1 / 100_GeV;
226  Acts::BoundSquareMatrix cov = stddev.cwiseProduct(stddev).asDiagonal();
227  // all tracks close to the transverse plane along the x axis w/ small
228  // variations in position, direction.
229  Acts::Vector4 mStartPos0(-3_m, 0.0, 0.0, 1_ns);
230  Acts::Vector4 mStartPos1(-3_m, -15_mm, -15_mm, 2_ns);
231  Acts::Vector4 mStartPos2(-3_m, 15_mm, 15_mm, -1_ns);
232  startParameters = {
233  {mStartPos0, 0_degree, 90_degree, 1_e / 1_GeV, cov, pion},
234  {mStartPos1, -1_degree, 91_degree, 1_e / 1_GeV, cov, pion},
235  {mStartPos2, 1_degree, 89_degree, -1_e / 1_GeV, cov, pion},
236  };
237  Acts::Vector4 mEndPos0(3_m, 0.0, 0.0, 1_ns);
238  Acts::Vector4 mEndPos1(3_m, -100_mm, -100_mm, 2_ns);
239  Acts::Vector4 mEndPos2(3_m, 100_mm, 100_mm, -1_ns);
240  endParameters = {
241  {mEndPos0, 0_degree, 90_degree, 1_e / 1_GeV, cov * 100, pion},
242  {mEndPos1, -1_degree, 91_degree, 1_e / 1_GeV, cov * 100, pion},
243  {mEndPos2, 1_degree, 89_degree, -1_e / 1_GeV, cov * 100, pion},
244  };
245 
246  // create some measurements
248  std::default_random_engine rng(421235);
249  for (size_t trackId = 0u; trackId < startParameters.size(); ++trackId) {
250  auto measurements = createMeasurements(
251  measPropagator, geoCtx, magCtx, startParameters[trackId],
252  detector.resolutions, rng, trackId);
253  for (auto& sl : measurements.sourceLinks) {
254  sourceLinks.emplace(sl.m_geometryId, std::move(sl));
255  }
256  }
257  }
258 
259  // Construct a straight-line propagator.
261  std::shared_ptr<const Acts::TrackingGeometry> geo) {
263  cfg.resolvePassive = false;
264  cfg.resolveMaterial = true;
265  cfg.resolveSensitive = true;
268  return StraightPropagator(stepper, std::move(navigator));
269  }
270 
271  // Construct a propagator using a constant magnetic field along z.
273  std::shared_ptr<const Acts::TrackingGeometry> geo, double bz) {
275  cfg.resolvePassive = false;
276  cfg.resolveMaterial = true;
277  cfg.resolveSensitive = true;
279  auto field =
280  std::make_shared<Acts::ConstantBField>(Acts::Vector3(0.0, 0.0, bz));
283  }
284 
285  CombinatorialKalmanFilterOptions makeCkfOptions() const {
286  return CombinatorialKalmanFilterOptions(
287  geoCtx, magCtx, calCtx,
289  TestSourceLinkAccessor::Iterator>{}, // leave the accessor empty,
290  // this will have to be set
291  // before running the CKF
292  getExtensions(), Acts::PropagatorPlainOptions());
293  }
294 };
295 
296 } // namespace
297 
298 BOOST_AUTO_TEST_SUITE(TrackFindingCombinatorialKalmanFilter)
299 
300 BOOST_AUTO_TEST_CASE(ZeroFieldForward) {
301  Fixture f(0_T);
302 
303  auto options = f.makeCkfOptions();
304  // this is the default option. set anyway for consistency
305  options.propagatorPlainOptions.direction = Acts::Direction::Forward;
306  // Construct a plane surface as the target surface
307  auto pSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
308  Acts::Vector3{-3_m, 0., 0.}, Acts::Vector3{1., 0., 0});
309  // Set the target surface
310  options.smoothingTargetSurface = pSurface.get();
311 
312  Fixture::TestSourceLinkAccessor slAccessor;
313  slAccessor.container = &f.sourceLinks;
314  options.sourcelinkAccessor.connect<&Fixture::TestSourceLinkAccessor::range>(
315  &slAccessor);
316 
319 
320  // run the CKF for all initial track states
321  for (size_t trackId = 0u; trackId < f.startParameters.size(); ++trackId) {
322  auto res = f.ckf.findTracks(f.startParameters.at(trackId), options, tc);
323  if (not res.ok()) {
324  BOOST_TEST_INFO(res.error() << " " << res.error().message());
325  }
326  BOOST_REQUIRE(res.ok());
327  }
328 
329  // There should be three track finding results with three initial track states
330  BOOST_CHECK_EQUAL(tc.size(), 3u);
331 
332  // check the found tracks
333  for (size_t trackId = 0u; trackId < f.startParameters.size(); ++trackId) {
334  const auto track = tc.getTrack(trackId);
335  const auto& params = f.startParameters[trackId];
336  BOOST_TEST_INFO("initial parameters before detector:\n" << params);
337 
338  BOOST_CHECK_EQUAL(track.nTrackStates(), f.detector.numMeasurements);
339 
340  // check purity of first found track
341  // find the number of hits not originating from the right track
342  size_t numHits = 0u;
343  size_t nummismatchedHits = 0u;
344  for (const auto trackState : track.trackStatesReversed()) {
345  numHits += 1u;
346  auto sl =
347  trackState.getUncalibratedSourceLink().template get<TestSourceLink>();
348  if (trackId != sl.sourceId) {
349  nummismatchedHits++;
350  }
351  }
352 
353  BOOST_CHECK_EQUAL(numHits, f.detector.numMeasurements);
354  BOOST_CHECK_EQUAL(nummismatchedHits, 0u);
355  }
356 }
357 
358 BOOST_AUTO_TEST_CASE(ZeroFieldBackward) {
359  Fixture f(0_T);
360 
361  auto options = f.makeCkfOptions();
362  options.propagatorPlainOptions.direction = Acts::Direction::Backward;
363  // Construct a plane surface as the target surface
364  auto pSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
365  Acts::Vector3{3_m, 0., 0.}, Acts::Vector3{1., 0., 0});
366  // Set the target surface
367  options.smoothingTargetSurface = pSurface.get();
368 
369  Fixture::TestSourceLinkAccessor slAccessor;
370  slAccessor.container = &f.sourceLinks;
371  options.sourcelinkAccessor.connect<&Fixture::TestSourceLinkAccessor::range>(
372  &slAccessor);
373 
376 
377  // run the CKF for all initial track states
378  for (size_t trackId = 0u; trackId < f.startParameters.size(); ++trackId) {
379  auto res = f.ckf.findTracks(f.endParameters.at(trackId), options, tc);
380  if (not res.ok()) {
381  BOOST_TEST_INFO(res.error() << " " << res.error().message());
382  }
383  BOOST_REQUIRE(res.ok());
384  }
385  // There should be three found tracks with three initial track states
386  BOOST_CHECK_EQUAL(tc.size(), 3u);
387 
388  // check the found tracks
389  for (size_t trackId = 0u; trackId < f.endParameters.size(); ++trackId) {
390  const auto track = tc.getTrack(trackId);
391  const auto& params = f.endParameters[trackId];
392  BOOST_TEST_INFO("initial parameters after detector:\n" << params);
393 
394  BOOST_CHECK_EQUAL(track.nTrackStates(), f.detector.numMeasurements);
395 
396  // check purity of first found track
397  // find the number of hits not originating from the right track
398  size_t numHits = 0u;
399  size_t nummismatchedHits = 0u;
400  for (const auto trackState : track.trackStatesReversed()) {
401  numHits += 1u;
402  auto sl =
403  trackState.getUncalibratedSourceLink().template get<TestSourceLink>();
404  if (trackId != sl.sourceId) {
405  nummismatchedHits++;
406  }
407  }
408 
409  BOOST_CHECK_EQUAL(numHits, f.detector.numMeasurements);
410  BOOST_CHECK_EQUAL(nummismatchedHits, 0u);
411  }
412 }
413 
414 BOOST_AUTO_TEST_SUITE_END()