Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-12 09:43:24

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/GeometryContext.hpp"
0013 #include "Acts/Geometry/GeometryIdentifier.hpp"
0014 #include "Acts/Material/AccumulatedMaterialSlab.hpp"
0015 #include "Acts/Material/BinnedSurfaceMaterial.hpp"
0016 #include "Acts/Material/BinnedSurfaceMaterialAccumulator.hpp"
0017 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0018 #include "Acts/Material/IAssignmentFinder.hpp"
0019 #include "Acts/Material/ISurfaceMaterialAccumulator.hpp"
0020 #include "Acts/Material/IntersectionMaterialAssigner.hpp"
0021 #include "Acts/Material/MaterialInteraction.hpp"
0022 #include "Acts/Material/MaterialMapper.hpp"
0023 #include "Acts/Material/MaterialSlab.hpp"
0024 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
0025 #include "Acts/Surfaces/CylinderSurface.hpp"
0026 #include "Acts/Utilities/Diagnostics.hpp"
0027 #include "Acts/Utilities/Enumerate.hpp"
0028 #include "Acts/Utilities/Intersection.hpp"
0029 
0030 #include <map>
0031 #include <numbers>
0032 #include <random>
0033 #include <span>
0034 #include <stdexcept>
0035 
0036 using namespace Acts;
0037 
0038 namespace ActsTests {
0039 
0040 auto tContext = GeometryContext::dangerouslyDefaultConstruct();
0041 
0042 /// @brief Interface for the material mapping that seeks the possible
0043 /// assignment candidates for the material interactiosn
0044 class IntersectSurfacesFinder : public IAssignmentFinder {
0045  public:
0046   std::vector<const Surface*> surfaces;
0047 
0048   /// @brief Interface method for generating assignment candidates for the
0049   /// material interaction assignment to surfaces or volumes
0050   ///
0051   /// @param gctx is the geometry context
0052   /// @param mctx is the magnetic field context
0053   /// @param position is the position of the initial ray
0054   /// @param direction is the direction of initial ray
0055   ///
0056   /// @return a vector of Surface Assignments and Volume Assignments
0057   std::pair<std::vector<IAssignmentFinder::SurfaceAssignment>,
0058             std::vector<IAssignmentFinder::VolumeAssignment>>
0059   assignmentCandidates(const GeometryContext& gctx,
0060                        const MagneticFieldContext& /*ignored*/,
0061                        const Vector3& position,
0062                        const Vector3& direction) const override {
0063     std::vector<IAssignmentFinder::SurfaceAssignment> surfaceAssignments;
0064     std::vector<IAssignmentFinder::VolumeAssignment> volumeAssignments;
0065     // Intersect the surfaces
0066     for (auto& surface : surfaces) {
0067       // Get the intersection
0068       MultiIntersection3D multiIntersection = surface->intersect(
0069           gctx, position, direction, BoundaryTolerance::None());
0070       // One solution, take it
0071       if (multiIntersection.size() == 1u &&
0072           multiIntersection.at(0).status() >= IntersectionStatus::reachable &&
0073           multiIntersection.at(0).pathLength() >= 0.0) {
0074         surfaceAssignments.push_back(
0075             {surface, multiIntersection.at(0).position(), direction});
0076         continue;
0077       }
0078       if (multiIntersection.size() > 1u) {
0079         // Multiple intersections, take the closest
0080         Intersection3D closestForward = multiIntersection.closestForward();
0081         if (closestForward.status() >= IntersectionStatus::reachable &&
0082             closestForward.pathLength() > 0.0) {
0083           surfaceAssignments.push_back(
0084               {surface, closestForward.position(), direction});
0085           continue;
0086         }
0087       }
0088     }
0089     return {surfaceAssignments, volumeAssignments};
0090   }
0091 };
0092 
0093 /// @brief Interface for the material mapping, this is the accumulation step
0094 class MaterialBlender : public ISurfaceMaterialAccumulator {
0095  public:
0096   explicit MaterialBlender(
0097       const std::vector<std::shared_ptr<Surface>>& surfaces = {})
0098       : m_surfaces(surfaces) {}
0099 
0100   /// The state of the material accumulator, this is used
0101   /// to cache information across tracks/events
0102   class State final : public ISurfaceMaterialAccumulator::State {
0103    public:
0104     std::map<const Surface*, AccumulatedMaterialSlab> accumulatedMaterial;
0105   };
0106 
0107   /// Factory for creating the state
0108   std::unique_ptr<ISurfaceMaterialAccumulator::State> createState(
0109       const GeometryContext& /*gctx*/) const override {
0110     auto state = std::make_unique<State>();
0111     for (auto& surface : m_surfaces) {
0112       state->accumulatedMaterial[surface.get()] = AccumulatedMaterialSlab();
0113     }
0114     return state;
0115   };
0116 
0117   /// @brief Accumulate the material interaction on the surface
0118   ///
0119   /// @param state is the state of the accumulator
0120   /// @param interactions is the material interactions, with assigned surfaces
0121   /// @param surfacesWithoutAssignment are the surfaces without assignment
0122   ///
0123   /// @note this the track average over the binned material
0124   void accumulateTracks(ISurfaceMaterialAccumulator::State& state,
0125                         const GeometryContext& /*gctx*/,
0126                         std::span<const TrackAccumulation> tracks,
0127                         detail::WorkerPool* /*pool*/) const override {
0128     for (const auto& track : tracks) {
0129       accumulateTrack(state, *track.interactions);
0130     }
0131   }
0132 
0133   void accumulateTrack(
0134       ISurfaceMaterialAccumulator::State& state,
0135       const std::vector<MaterialInteraction>& interactions) const {
0136     auto cState = static_cast<State*>(&state);
0137     for (const auto& mi : interactions) {
0138       // Get the surface
0139       const Surface* surface = mi.surface;
0140       // Get the accumulated material
0141       auto accMaterial = cState->accumulatedMaterial.find(surface);
0142       if (accMaterial == cState->accumulatedMaterial.end()) {
0143         throw std::invalid_argument(
0144             "Surface material is not found, inconsistent configuration.");
0145       }
0146       // Accumulate the material
0147       accMaterial->second.accumulate(mi.materialSlab);
0148     }
0149     // Average over the track
0150     for (auto& [surface, accumulatedMaterial] : cState->accumulatedMaterial) {
0151       accumulatedMaterial.trackAverage();
0152     }
0153   }
0154 
0155   /// Finalize the surface material maps
0156   ///
0157   /// @param state the state of the accumulator
0158   ///
0159   /// @note this does the run average over the (binned) material
0160   std::map<GeometryIdentifier, std::shared_ptr<const ISurfaceMaterial>>
0161   finalizeMaterial(ISurfaceMaterialAccumulator::State& state,
0162                    const GeometryContext& /*gctx*/) const override {
0163     auto cState = static_cast<State*>(&state);
0164 
0165     std::map<GeometryIdentifier, std::shared_ptr<const ISurfaceMaterial>>
0166         materialMaps;
0167     for (auto& [surface, accumulatedMaterial] : cState->accumulatedMaterial) {
0168       materialMaps[surface->geometryId()] =
0169           std::make_shared<HomogeneousSurfaceMaterial>(
0170               accumulatedMaterial.totalAverage().first);
0171     }
0172     return materialMaps;
0173   }
0174 
0175  private:
0176   std::vector<std::shared_ptr<Surface>> m_surfaces;
0177 };
0178 
0179 BOOST_AUTO_TEST_SUITE(MaterialSuite)
0180 
0181 /// @brief This test checks the data flow of the material mapper, it is not
0182 /// a test of the single components, which are tested individually
0183 ///
0184 /// @note Currently only surface mapping is implemented, volume mapping/assigning
0185 /// is being added in future PRs
0186 BOOST_AUTO_TEST_CASE(MaterialMapperFlowTest) {
0187   // Create a vector of surfaces
0188   std::vector<std::shared_ptr<Surface>> surfaces = {
0189       Surface::makeShared<CylinderSurface>(Transform3::Identity(), 20.0, 100.0),
0190       Surface::makeShared<CylinderSurface>(Transform3::Identity(), 30.0, 100.0),
0191       Surface::makeShared<CylinderSurface>(Transform3::Identity(), 50.0,
0192                                            100.0)};
0193 
0194   for (auto [is, surface] : enumerate(surfaces)) {
0195     surface->assignGeometryId(GeometryIdentifier().withSensitive(is + 1));
0196   }
0197 
0198   // The assigner
0199   auto assigner = std::make_shared<IntersectSurfacesFinder>();
0200   assigner->surfaces = {surfaces[0].get(), surfaces[1].get(),
0201                         surfaces[2].get()};
0202 
0203   // The accumulator - which blends all the material
0204   auto accumulator = std::make_shared<MaterialBlender>(surfaces);
0205 
0206   // Create the mapper
0207   MaterialMapper::Config mmConfig;
0208   mmConfig.assignmentFinder = assigner;
0209   mmConfig.surfaceMaterialAccumulator = accumulator;
0210 
0211   MaterialMapper mapper(mmConfig);
0212 
0213   auto state = mapper.createState(tContext);
0214   BOOST_CHECK(state.get() != nullptr);
0215   BOOST_CHECK(state->surfaceMaterialAccumulatorState.get() != nullptr);
0216 
0217   std::vector<RecordedMaterialTrack> mappedTracks;
0218   std::vector<RecordedMaterialTrack> unmappedTracks;
0219 
0220   // Track loop
0221   Vector3 position(0., 0., 0.);
0222   for (unsigned int it = 0; it < 11; ++it) {
0223     Vector3 direction =
0224         Vector3(0.9 + it * 0.02, 1.1 - it * 0.02, 0.).normalized();
0225     RecordedMaterialTrack mTrack{{position, direction}, {}};
0226     for (unsigned int im = 0; im < 60; ++im) {
0227       MaterialInteraction mi;
0228       mi.materialSlab = MaterialSlab(
0229           Material::fromMassDensity(it + 1, it + 1, it + 1, it + 1, it + 1),
0230           0.1);
0231       mi.position = position + (im + 1) * direction;
0232       mi.direction = direction;
0233       mTrack.second.materialInteractions.push_back(mi);
0234     }
0235     auto [mapped, unmapped] = mapper.mapMaterial(*state, tContext, {}, mTrack);
0236     mappedTracks.push_back(mapped);
0237     unmappedTracks.push_back(unmapped);
0238   }
0239 
0240   // Get the maps
0241   auto maps = mapper.finalizeMaps(*state, tContext);
0242 
0243   BOOST_CHECK(maps.surfaceMaterials.size() == 3);
0244   BOOST_CHECK(maps.volumeMaterials.empty());
0245 }
0246 
0247 BOOST_AUTO_TEST_CASE(MaterialMapperBatchMatchesSerial) {
0248   std::vector<std::shared_ptr<Surface>> surfaces;
0249   for (double r : {20., 30., 50., 80.}) {
0250     surfaces.push_back(
0251         Surface::makeShared<CylinderSurface>(Transform3::Identity(), r, 100.));
0252   }
0253   BinUtility binning(6, -std::numbers::pi, std::numbers::pi, closed,
0254                      AxisDirection::AxisPhi);
0255   binning += BinUtility(5, -100., 100., open, AxisDirection::AxisZ);
0256   std::vector<const Surface*> surfacePtrs;
0257   for (auto [is, surface] : enumerate(surfaces)) {
0258     surface->assignGeometryId(GeometryIdentifier().withSensitive(is + 1));
0259     surface->assignSurfaceMaterial(
0260         std::make_shared<ProtoSurfaceMaterial>(binning));
0261     surfacePtrs.push_back(surface.get());
0262   }
0263 
0264   IntersectionMaterialAssigner::Config assignerConfig;
0265   assignerConfig.surfaces = surfacePtrs;
0266   BinnedSurfaceMaterialAccumulator::Config accumulatorConfig;
0267   accumulatorConfig.materialSurfaces = surfacePtrs;
0268 
0269   MaterialMapper::Config mmConfig;
0270   mmConfig.assignmentFinder =
0271       std::make_shared<IntersectionMaterialAssigner>(assignerConfig);
0272   mmConfig.surfaceMaterialAccumulator =
0273       std::make_shared<BinnedSurfaceMaterialAccumulator>(accumulatorConfig);
0274   MaterialMapper serialMapper(mmConfig);
0275   mmConfig.numThreads = 4;
0276   MaterialMapper parallelMapper(mmConfig);
0277 
0278   // Straight tracks from the origin with interactions along the way, some of
0279   // them leaving through the cylinder ends
0280   std::mt19937 rng(7);
0281   std::uniform_real_distribution<double> uniform(0., 1.);
0282   std::vector<RecordedMaterialTrack> tracks(300);
0283   for (auto& [startDir, recorded] : tracks) {
0284     const double phi = std::numbers::pi * (2 * uniform(rng) - 1);
0285     const double cotTheta = 2 * uniform(rng) - 1;
0286     startDir = {Vector3::Zero(),
0287                 Vector3(std::cos(phi), std::sin(phi), cotTheta).normalized()};
0288     for (double s = 1.; s < 120.; s += 0.5 + 4 * uniform(rng)) {
0289       MaterialInteraction mi;
0290       mi.position = s * startDir.second;
0291       mi.direction = startDir.second;
0292       mi.materialSlab =
0293           MaterialSlab(Material::fromMassDensity(1 + 100 * uniform(rng),
0294                                                  1 + 300 * uniform(rng), 10.,
0295                                                  5., 1 + uniform(rng)),
0296                        0.1 + uniform(rng));
0297       recorded.materialInteractions.push_back(mi);
0298     }
0299   }
0300 
0301   auto serialState = serialMapper.createState(tContext);
0302   std::vector<std::pair<RecordedMaterialTrack, RecordedMaterialTrack>>
0303       serialResults;
0304   for (const auto& track : tracks) {
0305     serialResults.push_back(
0306         serialMapper.mapMaterial(*serialState, tContext, {}, track));
0307   }
0308 
0309   auto parallelState = parallelMapper.createState(tContext);
0310   std::vector<std::pair<RecordedMaterialTrack, RecordedMaterialTrack>>
0311       parallelResults;
0312   for (std::size_t begin = 0; begin < tracks.size(); begin += 64) {
0313     std::vector<const RecordedMaterialTrack*> batch;
0314     for (std::size_t t = begin; t < std::min(begin + 64, tracks.size()); ++t) {
0315       batch.push_back(&tracks[t]);
0316     }
0317     for (auto& result :
0318          parallelMapper.mapMaterial(*parallelState, tContext, {}, batch)) {
0319       parallelResults.push_back(std::move(result));
0320     }
0321   }
0322 
0323   // Identical mapped and unmapped tracks
0324   BOOST_REQUIRE_EQUAL(serialResults.size(), parallelResults.size());
0325   std::size_t nMapped = 0;
0326   for (std::size_t t = 0; t < serialResults.size(); ++t) {
0327     for (auto [serial, parallel] :
0328          {std::pair{&serialResults[t].first, &parallelResults[t].first},
0329           std::pair{&serialResults[t].second, &parallelResults[t].second}}) {
0330       const auto& sInteractions = serial->second.materialInteractions;
0331       const auto& pInteractions = parallel->second.materialInteractions;
0332       BOOST_REQUIRE_EQUAL(sInteractions.size(), pInteractions.size());
0333       for (std::size_t i = 0; i < sInteractions.size(); ++i) {
0334         BOOST_CHECK_EQUAL(sInteractions[i].surface, pInteractions[i].surface);
0335         BOOST_CHECK(sInteractions[i].intersection ==
0336                     pInteractions[i].intersection);
0337       }
0338       BOOST_CHECK_EQUAL(serial->second.materialInX0,
0339                         parallel->second.materialInX0);
0340     }
0341     nMapped += serialResults[t].first.second.materialInteractions.size();
0342   }
0343   BOOST_CHECK_GT(nMapped, 0u);
0344 
0345   // Identical maps
0346   auto serialMaps = serialMapper.finalizeMaps(*serialState, tContext);
0347   auto parallelMaps = parallelMapper.finalizeMaps(*parallelState, tContext);
0348   BOOST_REQUIRE_EQUAL(serialMaps.surfaceMaterials.size(), surfaces.size());
0349   BOOST_REQUIRE_EQUAL(parallelMaps.surfaceMaterials.size(), surfaces.size());
0350   for (const auto& [key, material] : serialMaps.surfaceMaterials) {
0351     auto* sm = dynamic_cast<const BinnedSurfaceMaterial*>(material.get());
0352     auto* pm = dynamic_cast<const BinnedSurfaceMaterial*>(
0353         parallelMaps.surfaceMaterials.at(key).get());
0354     BOOST_REQUIRE(sm != nullptr && pm != nullptr);
0355     BOOST_CHECK(sm->fullMaterial() == pm->fullMaterial());
0356   }
0357 }
0358 
0359 /// Counts the calls of both accumulation methods, overriding only one
0360 template <bool overrideBatch, bool overrideSingle>
0361 class CallCounter : public ISurfaceMaterialAccumulator {
0362  public:
0363   mutable std::size_t batchCalls = 0;
0364   mutable std::size_t singleCalls = 0;
0365 
0366   std::unique_ptr<ISurfaceMaterialAccumulator::State> createState(
0367       const GeometryContext& /*gctx*/) const override {
0368     return std::make_unique<ISurfaceMaterialAccumulator::State>();
0369   }
0370 
0371   void accumulateTracks(ISurfaceMaterialAccumulator::State& state,
0372                         const GeometryContext& gctx,
0373                         std::span<const TrackAccumulation> tracks,
0374                         detail::WorkerPool* pool) const override {
0375     if constexpr (overrideBatch) {
0376       batchCalls += tracks.size();
0377     } else {
0378       ISurfaceMaterialAccumulator::accumulateTracks(state, gctx, tracks, pool);
0379     }
0380   }
0381 
0382   ACTS_PUSH_IGNORE_DEPRECATED()
0383   void accumulate(ISurfaceMaterialAccumulator::State& state,
0384                   const GeometryContext& gctx,
0385                   const std::vector<MaterialInteraction>& interactions,
0386                   const std::vector<IAssignmentFinder::SurfaceAssignment>&
0387                       surfacesWithoutAssignment) const override {
0388     if constexpr (overrideSingle) {
0389       ++singleCalls;
0390     } else {
0391       ISurfaceMaterialAccumulator::accumulate(state, gctx, interactions,
0392                                               surfacesWithoutAssignment);
0393     }
0394   }
0395   ACTS_POP_IGNORE_DEPRECATED()
0396 
0397   std::map<GeometryIdentifier, std::shared_ptr<const ISurfaceMaterial>>
0398   finalizeMaterial(ISurfaceMaterialAccumulator::State& /*state*/,
0399                    const GeometryContext& /*gctx*/) const override {
0400     return {};
0401   }
0402 };
0403 
0404 BOOST_AUTO_TEST_CASE(DeprecatedSingleTrackAccumulation) {
0405   std::vector<MaterialInteraction> interactions;
0406   std::vector<IAssignmentFinder::SurfaceAssignment> empty;
0407   std::vector<ISurfaceMaterialAccumulator::TrackAccumulation> tracks(
0408       3, {&interactions, &empty});
0409 
0410   // An implementation of the deprecated method serves batch calls
0411   CallCounter<false, true> legacy;
0412   auto legacyState = legacy.createState(tContext);
0413   legacy.accumulateTracks(*legacyState, tContext, tracks, nullptr);
0414   BOOST_CHECK_EQUAL(legacy.singleCalls, 3u);
0415 
0416   // The deprecated method does not forward to the batch method
0417   CallCounter<true, false> modern;
0418   auto modernState = modern.createState(tContext);
0419   ACTS_PUSH_IGNORE_DEPRECATED()
0420   BOOST_CHECK_THROW(
0421       modern.accumulate(*modernState, tContext, interactions, empty),
0422       std::logic_error);
0423   ACTS_POP_IGNORE_DEPRECATED()
0424   modern.accumulateTracks(*modernState, tContext, tracks, nullptr);
0425   BOOST_CHECK_EQUAL(modern.batchCalls, 3u);
0426 
0427   // Overriding neither is an error
0428   CallCounter<false, false> broken;
0429   auto brokenState = broken.createState(tContext);
0430   BOOST_CHECK_THROW(
0431       broken.accumulateTracks(*brokenState, tContext, tracks, nullptr),
0432       std::logic_error);
0433 }
0434 
0435 BOOST_AUTO_TEST_CASE(MaterialMapperInvalidTest) {
0436   // The assigner
0437   auto assigner = std::make_shared<IntersectSurfacesFinder>();
0438 
0439   // The accumulator - which blends all the material
0440   auto accumulator = std::make_shared<MaterialBlender>();
0441 
0442   // Create the mapper
0443   MaterialMapper::Config mmConfigInvalid;
0444   mmConfigInvalid.assignmentFinder = nullptr;
0445   mmConfigInvalid.surfaceMaterialAccumulator = accumulator;
0446 
0447   BOOST_CHECK_THROW(auto mapperIA = MaterialMapper(mmConfigInvalid),
0448                     std::invalid_argument);
0449 
0450   // BOOST_CHECK_THROW(MaterialMapper(mmConfigInvalid), std::invalid_argument);
0451 
0452   mmConfigInvalid.assignmentFinder = assigner;
0453   mmConfigInvalid.surfaceMaterialAccumulator = nullptr;
0454 
0455   BOOST_CHECK_THROW(auto mapperIS = MaterialMapper(mmConfigInvalid),
0456                     std::invalid_argument);
0457 }
0458 
0459 BOOST_AUTO_TEST_SUITE_END()
0460 
0461 }  // namespace ActsTests