ECCE @ EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FatrasSimulationTests.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file FatrasSimulationTests.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2020 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 <algorithm>
10 #include <boost/test/data/test_case.hpp>
11 #include <boost/test/unit_test.hpp>
12 #include <random>
13 
27 
28 using namespace Acts::UnitLiterals;
29 
30 namespace {
31 
33 struct SplitEnergyLoss {
34  double splitMomentumMin = 5_GeV;
35 
36  template <typename generator_t>
37  bool operator()(generator_t&, const Acts::MaterialProperties&,
39  std::vector<ActsFatras::Particle>& generated) const {
40  const auto p = particle.absMomentum();
41  if (splitMomentumMin < p) {
42  particle.setAbsMomentum(0.5 * p);
43  const auto pid = particle.particleId().makeDescendant();
44  generated.push_back(particle.withParticleId(pid).setAbsMomentum(0.5 * p));
45  }
46  // never break
47  return false;
48  }
49 };
50 
51 // setup propagator-related types
52 // use the default navigation
53 using Navigator = Acts::Navigator;
54 using MagneticField = Acts::ConstantBField;
55 // propagate charged particles numerically in a constant magnetic field
56 using ChargedStepper = Acts::EigenStepper<MagneticField>;
57 using ChargedPropagator = Acts::Propagator<ChargedStepper, Navigator>;
58 // propagate neutral particles with just straight lines
59 using NeutralStepper = Acts::StraightLineStepper;
60 using NeutralPropagator = Acts::Propagator<NeutralStepper, Navigator>;
61 
62 // setup simulator-related types
63 // the random number generator type
64 using Generator = std::ranlux48;
65 // all charged particles w/ a mock-up physics list and hits everywhere
66 using ChargedSelector = ActsFatras::ChargedSelector;
67 using ChargedPhysicsList =
69  SplitEnergyLoss>;
70 using ChargedSimulator =
71  ActsFatras::ParticleSimulator<ChargedPropagator, ChargedPhysicsList,
73 // all neutral particles w/o physics and no hits
74 using NeutralSelector = ActsFatras::NeutralSelector;
75 using NeutralSimulator =
78 // full simulator type for charged and neutrals
79 using Simulator = ActsFatras::Simulator<ChargedSelector, ChargedSimulator,
80  NeutralSelector, NeutralSimulator>;
81 
82 // parameters for data-driven test cases
83 
84 const auto rangePdg =
85  boost::unit_test::data::make(std::vector<Acts::PdgParticle>{
90  });
91 const auto rangePhi = boost::unit_test::data::make(std::vector<double>{
92  -135_degree,
93  -45_degree,
94  45_degree,
95  135_degree,
96 });
97 const auto rangeEta = boost::unit_test::data::make(std::vector<double>{
98  -1.0,
99  0.0,
100  1.0,
101  3.0,
102 });
103 const auto rangeP = boost::unit_test::data::make(std::vector<double>{
104  1_GeV,
105  10_GeV,
106 });
107 const auto rangeNumParticles = boost::unit_test::data::make(std::vector<int>{
108  1,
109  2,
110 });
111 
112 const auto dataset =
113  rangePdg * rangePhi * rangeEta * rangeP * rangeNumParticles;
114 
115 // helper functions for tests
116 template <typename Container>
117 void sortByParticleId(Container& container) {
118  std::sort(container.begin(), container.end(),
119  [](const auto& lhs, const auto& rhs) {
120  return lhs.particleId() < rhs.particleId();
121  });
122 }
123 template <typename Container>
124 bool areParticleIdsUnique(const Container& sortedByParticleId) {
125  // assumes the container is sorted by particle id
126  auto ret =
127  std::adjacent_find(sortedByParticleId.begin(), sortedByParticleId.end(),
128  [](const auto& lhs, const auto& rhs) {
129  return lhs.particleId() == rhs.particleId();
130  });
131  return ret == sortedByParticleId.end();
132 }
133 template <typename Container, typename Value>
134 bool containsParticleId(const Container& sortedByParticleId,
135  const Value& value) {
136  return std::binary_search(sortedByParticleId.begin(),
137  sortedByParticleId.end(), value,
138  [](const auto& lhs, const auto& rhs) {
139  return lhs.particleId() < rhs.particleId();
140  });
141 }
142 
143 } // namespace
144 
145 BOOST_DATA_TEST_CASE(FatrasSimulation, dataset, pdg, phi, eta, p,
146  numParticles) {
147  using namespace Acts::UnitLiterals;
148 
149  Acts::GeometryContext geoCtx;
152 
153  // construct the example detector
154  Acts::Test::CylindricalTrackingGeometry geoBuilder(geoCtx);
155  auto trackingGeometry = geoBuilder();
156 
157  // construct the propagators
158  Navigator navigator(trackingGeometry);
159  ChargedStepper chargedStepper(Acts::ConstantBField(0, 0, 1_T));
160  ChargedPropagator chargedPropagator(std::move(chargedStepper), navigator);
161  NeutralPropagator neutralPropagator(NeutralStepper(), navigator);
162 
163  // construct the simulator
164  ChargedSimulator simulatorCharged(std::move(chargedPropagator), logLevel);
165  NeutralSimulator simulatorNeutral(std::move(neutralPropagator), logLevel);
166  Simulator simulator(std::move(simulatorCharged), std::move(simulatorNeutral));
167 
168  // prepare simulation call parameters
169  // random number generator
171  // input/ output particle and hits containers
172  std::vector<ActsFatras::Particle> input;
173  std::vector<ActsFatras::Particle> simulatedInitial;
174  std::vector<ActsFatras::Particle> simulatedFinal;
175  std::vector<ActsFatras::Hit> hits;
176 
177  // create input particles. particle number should ne non-zero.
178  for (auto i = numParticles; 0 < i; --i) {
179  const auto pid = ActsFatras::Barcode().setVertexPrimary(42).setParticle(i);
180  const auto particle =
183  .setAbsMomentum(p);
184  input.push_back(std::move(particle));
185  }
186  BOOST_TEST_INFO(input.front());
187  BOOST_TEST(input.size() == numParticles);
188 
189  // run the simulation
190  auto result = simulator.simulate(geoCtx, magCtx, generator, input,
191  simulatedInitial, simulatedFinal, hits);
192 
193  // should always succeed
194  BOOST_TEST(result.ok());
195 
196  // ensure simulated particle containers have consistent content
197  BOOST_TEST(simulatedInitial.size() == simulatedFinal.size());
198  for (std::size_t i = 0; i < simulatedInitial.size(); ++i) {
199  const auto& initialParticle = simulatedInitial[i];
200  const auto& finalParticle = simulatedFinal[i];
201  // particle identify should not change during simulation
202  BOOST_TEST(initialParticle.particleId() == finalParticle.particleId());
203  BOOST_TEST(initialParticle.process() == finalParticle.process());
204  BOOST_TEST(initialParticle.pdg() == finalParticle.pdg());
205  BOOST_TEST(initialParticle.charge() == finalParticle.charge());
206  BOOST_TEST(initialParticle.mass() == finalParticle.mass());
207  }
208 
209  // we have no particle cuts and should not loose any particles.
210  // might end up with more due to secondaries
211  BOOST_TEST(input.size() <= simulatedInitial.size());
212  BOOST_TEST(input.size() <= simulatedFinal.size());
213  // there should be some hits if we started with a charged particle
214  if (ActsFatras::findCharge(pdg) != 0) {
215  BOOST_TEST(0u < hits.size());
216  }
217 
218  // sort all outputs by particle id to simply further tests
219  sortByParticleId(input);
220  sortByParticleId(simulatedInitial);
221  sortByParticleId(simulatedFinal);
222  sortByParticleId(hits);
223 
224  // check that all particle ids are unique
225  BOOST_TEST(areParticleIdsUnique(input));
226  BOOST_TEST(areParticleIdsUnique(simulatedInitial));
227  BOOST_TEST(areParticleIdsUnique(simulatedFinal));
228  // hits must necessarily contain particle id duplicates
229  // check that every input particles is simulated
230  for (const auto& particle : input) {
231  BOOST_TEST(containsParticleId(simulatedInitial, particle));
232  BOOST_TEST(containsParticleId(simulatedFinal, particle));
233  }
234  // check that all hits can be associated to a particle
235  for (const auto& hit : hits) {
236  BOOST_TEST(containsParticleId(simulatedInitial, hit));
237  BOOST_TEST(containsParticleId(simulatedFinal, hit));
238  }
239 }