File indexing completed on 2025-01-18 09:12:29
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/unit_test.hpp>
0011
0012 #include "Acts/Definitions/ParticleData.hpp"
0013 #include "Acts/MagneticField/ConstantBField.hpp"
0014 #include "Acts/Propagator/EigenStepper.hpp"
0015 #include "Acts/Propagator/Navigator.hpp"
0016 #include "Acts/Propagator/Propagator.hpp"
0017 #include "Acts/Propagator/StraightLineStepper.hpp"
0018 #include "Acts/Tests/CommonHelpers/CylindricalTrackingGeometry.hpp"
0019 #include "Acts/Utilities/Logger.hpp"
0020 #include "Acts/Utilities/UnitVectors.hpp"
0021 #include "ActsFatras/Kernel/InteractionList.hpp"
0022 #include "ActsFatras/Kernel/Simulation.hpp"
0023 #include "ActsFatras/Physics/Decay/NoDecay.hpp"
0024 #include "ActsFatras/Physics/StandardInteractions.hpp"
0025 #include "ActsFatras/Selectors/ParticleSelectors.hpp"
0026 #include "ActsFatras/Selectors/SurfaceSelectors.hpp"
0027
0028 #include <algorithm>
0029 #include <random>
0030
0031 using namespace Acts::UnitLiterals;
0032
0033 namespace {
0034
0035
0036 struct SplitEnergyLoss {
0037 double splitMomentumMin = 5_GeV;
0038
0039 template <typename generator_t>
0040 bool operator()(generator_t& ,
0041 const Acts::MaterialSlab& ,
0042 ActsFatras::Particle& particle,
0043 std::vector<ActsFatras::Particle>& generated) const {
0044 const auto p = particle.absoluteMomentum();
0045 if (splitMomentumMin < p) {
0046 particle.setAbsoluteMomentum(0.5 * p);
0047 const auto pid = particle.particleId().makeDescendant();
0048 generated.push_back(
0049 particle.withParticleId(pid).setAbsoluteMomentum(0.5 * p));
0050 }
0051
0052 return false;
0053 }
0054 };
0055
0056
0057
0058 using Navigator = Acts::Navigator;
0059 using MagneticField = Acts::ConstantBField;
0060
0061 using ChargedStepper = Acts::EigenStepper<>;
0062 using ChargedPropagator = Acts::Propagator<ChargedStepper, Navigator>;
0063
0064 using NeutralStepper = Acts::StraightLineStepper;
0065 using NeutralPropagator = Acts::Propagator<NeutralStepper, Navigator>;
0066
0067
0068
0069 using Generator = std::ranlux48;
0070
0071 using ChargedSelector = ActsFatras::EveryParticle;
0072 using ChargedInteractions =
0073 ActsFatras::InteractionList<ActsFatras::detail::StandardScattering,
0074 SplitEnergyLoss>;
0075 using ChargedSimulation =
0076 ActsFatras::SingleParticleSimulation<ChargedPropagator, ChargedInteractions,
0077 ActsFatras::EverySurface,
0078 ActsFatras::NoDecay>;
0079
0080 using NeutralSelector = ActsFatras::EveryParticle;
0081 using NeutralInteractions = ActsFatras::InteractionList<>;
0082 using NeutralSimulation =
0083 ActsFatras::SingleParticleSimulation<NeutralPropagator, NeutralInteractions,
0084 ActsFatras::NoSurface,
0085 ActsFatras::NoDecay>;
0086
0087 using Simulation = ActsFatras::Simulation<ChargedSelector, ChargedSimulation,
0088 NeutralSelector, NeutralSimulation>;
0089
0090
0091
0092 const auto rangePdg =
0093 boost::unit_test::data::make(std::vector<Acts::PdgParticle>{
0094 Acts::PdgParticle::eElectron,
0095 Acts::PdgParticle::eMuon,
0096 Acts::PdgParticle::ePionPlus,
0097 Acts::PdgParticle::ePionZero,
0098 });
0099 const auto rangePhi = boost::unit_test::data::make(std::vector<double>{
0100 -135_degree,
0101 -45_degree,
0102 45_degree,
0103 135_degree,
0104 });
0105 const auto rangeEta = boost::unit_test::data::make(std::vector<double>{
0106 -1.0,
0107 0.0,
0108 1.0,
0109 3.0,
0110 });
0111 const auto rangeP = boost::unit_test::data::make(std::vector<double>{
0112 1_GeV,
0113 10_GeV,
0114 });
0115 const auto rangeNumParticles = boost::unit_test::data::make(std::vector<int>{
0116 1,
0117 2,
0118 });
0119
0120 const auto dataset =
0121 rangePdg * rangePhi * rangeEta * rangeP * rangeNumParticles;
0122
0123
0124 template <typename Container>
0125 void sortByParticleId(Container& container) {
0126 std::ranges::sort(container, std::less{},
0127 [](const auto& c) { return c.particleId(); });
0128 }
0129 template <typename Container>
0130 bool areParticleIdsUnique(const Container& sortedByParticleId) {
0131
0132 auto ret =
0133 std::adjacent_find(sortedByParticleId.begin(), sortedByParticleId.end(),
0134 [](const auto& lhs, const auto& rhs) {
0135 return lhs.particleId() == rhs.particleId();
0136 });
0137 return ret == sortedByParticleId.end();
0138 }
0139 template <typename Container, typename Value>
0140 bool containsParticleId(const Container& sortedByParticleId,
0141 const Value& value) {
0142 return std::binary_search(sortedByParticleId.begin(),
0143 sortedByParticleId.end(), value,
0144 [](const auto& lhs, const auto& rhs) {
0145 return lhs.particleId() < rhs.particleId();
0146 });
0147 }
0148
0149 }
0150
0151 BOOST_DATA_TEST_CASE(FatrasSimulation, dataset, pdg, phi, eta, p,
0152 numParticles) {
0153 Acts::GeometryContext geoCtx;
0154 Acts::MagneticFieldContext magCtx;
0155 Acts::Logging::Level logLevel = Acts::Logging::Level::DEBUG;
0156
0157
0158 Acts::Test::CylindricalTrackingGeometry geoBuilder(geoCtx);
0159 auto trackingGeometry = geoBuilder();
0160
0161
0162 Navigator navigator({trackingGeometry});
0163 ChargedStepper chargedStepper(
0164 std::make_shared<Acts::ConstantBField>(Acts::Vector3{0, 0, 1_T}));
0165 ChargedPropagator chargedPropagator(std::move(chargedStepper), navigator);
0166 NeutralPropagator neutralPropagator(NeutralStepper(), navigator);
0167
0168
0169 ChargedSimulation simulatorCharged(
0170 std::move(chargedPropagator),
0171 Acts::getDefaultLogger("ChargedSimulation", logLevel));
0172 NeutralSimulation simulatorNeutral(
0173 std::move(neutralPropagator),
0174 Acts::getDefaultLogger("NeutralSimulation", logLevel));
0175 Simulation simulator(std::move(simulatorCharged),
0176 std::move(simulatorNeutral));
0177
0178
0179
0180 Generator generator;
0181
0182 std::vector<ActsFatras::Particle> input;
0183 std::vector<ActsFatras::Particle> simulatedInitial;
0184 std::vector<ActsFatras::Particle> simulatedFinal;
0185 std::vector<ActsFatras::Hit> hits;
0186
0187
0188 for (auto i = numParticles; 0 < i; --i) {
0189 const auto pid = ActsFatras::Barcode().setVertexPrimary(42).setParticle(i);
0190 const auto particle =
0191 ActsFatras::Particle(pid, pdg)
0192 .setDirection(Acts::makeDirectionFromPhiEta(phi, eta))
0193 .setAbsoluteMomentum(p);
0194 input.push_back(std::move(particle));
0195 }
0196 BOOST_TEST_INFO(input.front());
0197 BOOST_CHECK_EQUAL(input.size(), numParticles);
0198
0199
0200 auto result = simulator.simulate(geoCtx, magCtx, generator, input,
0201 simulatedInitial, simulatedFinal, hits);
0202
0203
0204 BOOST_CHECK(result.ok());
0205
0206
0207 BOOST_CHECK_EQUAL(simulatedInitial.size(), simulatedFinal.size());
0208 for (std::size_t i = 0; i < simulatedInitial.size(); ++i) {
0209 const auto& initialParticle = simulatedInitial[i];
0210 const auto& finalParticle = simulatedFinal[i];
0211
0212 BOOST_CHECK_EQUAL(initialParticle.particleId(), finalParticle.particleId());
0213 BOOST_CHECK_EQUAL(initialParticle.process(), finalParticle.process());
0214 BOOST_CHECK_EQUAL(initialParticle.pdg(), finalParticle.pdg());
0215 BOOST_CHECK_EQUAL(initialParticle.charge(), finalParticle.charge());
0216 BOOST_CHECK_EQUAL(initialParticle.mass(), finalParticle.mass());
0217 }
0218
0219
0220
0221 BOOST_CHECK_LE(input.size(), simulatedInitial.size());
0222 BOOST_CHECK_LE(input.size(), simulatedFinal.size());
0223
0224 if (Acts::findCharge(pdg) != 0) {
0225 BOOST_CHECK_LT(0u, hits.size());
0226 }
0227
0228
0229 sortByParticleId(input);
0230 sortByParticleId(simulatedInitial);
0231 sortByParticleId(simulatedFinal);
0232 sortByParticleId(hits);
0233
0234
0235 BOOST_CHECK(areParticleIdsUnique(input));
0236 BOOST_CHECK(areParticleIdsUnique(simulatedInitial));
0237 BOOST_CHECK(areParticleIdsUnique(simulatedFinal));
0238
0239
0240 for (const auto& particle : input) {
0241 BOOST_CHECK(containsParticleId(simulatedInitial, particle));
0242 BOOST_CHECK(containsParticleId(simulatedFinal, particle));
0243 }
0244
0245 for (const auto& hit : hits) {
0246 BOOST_CHECK(containsParticleId(simulatedInitial, hit));
0247 BOOST_CHECK(containsParticleId(simulatedFinal, hit));
0248 }
0249 }