File indexing completed on 2026-11-12 09:43:24
0001
0002
0003
0004
0005
0006
0007
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
0043
0044 class IntersectSurfacesFinder : public IAssignmentFinder {
0045 public:
0046 std::vector<const Surface*> surfaces;
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057 std::pair<std::vector<IAssignmentFinder::SurfaceAssignment>,
0058 std::vector<IAssignmentFinder::VolumeAssignment>>
0059 assignmentCandidates(const GeometryContext& gctx,
0060 const MagneticFieldContext& ,
0061 const Vector3& position,
0062 const Vector3& direction) const override {
0063 std::vector<IAssignmentFinder::SurfaceAssignment> surfaceAssignments;
0064 std::vector<IAssignmentFinder::VolumeAssignment> volumeAssignments;
0065
0066 for (auto& surface : surfaces) {
0067
0068 MultiIntersection3D multiIntersection = surface->intersect(
0069 gctx, position, direction, BoundaryTolerance::None());
0070
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
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
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
0101
0102 class State final : public ISurfaceMaterialAccumulator::State {
0103 public:
0104 std::map<const Surface*, AccumulatedMaterialSlab> accumulatedMaterial;
0105 };
0106
0107
0108 std::unique_ptr<ISurfaceMaterialAccumulator::State> createState(
0109 const GeometryContext& ) 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
0118
0119
0120
0121
0122
0123
0124 void accumulateTracks(ISurfaceMaterialAccumulator::State& state,
0125 const GeometryContext& ,
0126 std::span<const TrackAccumulation> tracks,
0127 detail::WorkerPool* ) 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
0139 const Surface* surface = mi.surface;
0140
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
0147 accMaterial->second.accumulate(mi.materialSlab);
0148 }
0149
0150 for (auto& [surface, accumulatedMaterial] : cState->accumulatedMaterial) {
0151 accumulatedMaterial.trackAverage();
0152 }
0153 }
0154
0155
0156
0157
0158
0159
0160 std::map<GeometryIdentifier, std::shared_ptr<const ISurfaceMaterial>>
0161 finalizeMaterial(ISurfaceMaterialAccumulator::State& state,
0162 const GeometryContext& ) 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
0182
0183
0184
0185
0186 BOOST_AUTO_TEST_CASE(MaterialMapperFlowTest) {
0187
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
0199 auto assigner = std::make_shared<IntersectSurfacesFinder>();
0200 assigner->surfaces = {surfaces[0].get(), surfaces[1].get(),
0201 surfaces[2].get()};
0202
0203
0204 auto accumulator = std::make_shared<MaterialBlender>(surfaces);
0205
0206
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
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
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
0279
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
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, ¶llelResults[t].first},
0329 std::pair{&serialResults[t].second, ¶llelResults[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
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
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& ) 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& ,
0399 const GeometryContext& ) 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
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
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
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
0437 auto assigner = std::make_shared<IntersectSurfacesFinder>();
0438
0439
0440 auto accumulator = std::make_shared<MaterialBlender>();
0441
0442
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
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 }