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