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