File indexing completed on 2026-05-27 07:24:13
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011
0012 #include "detray/geometry/surface.hpp"
0013 #include "detray/tracks/ray.hpp"
0014 #include "detray/utils/logging.hpp"
0015
0016
0017 #include "detray/io/utils/file_handle.hpp"
0018
0019
0020 #include "detray/test/common/track_generators.hpp"
0021 #include "detray/test/framework/fixture_base.hpp"
0022 #include "detray/test/framework/types.hpp"
0023 #include "detray/test/framework/whiteboard.hpp"
0024 #include "detray/test/validation/detector_scan_utils.hpp"
0025 #include "detray/test/validation/detector_scanner.hpp"
0026 #include "detray/test/validation/material_validation_utils.hpp"
0027
0028
0029 #include <ios>
0030 #include <iostream>
0031 #include <memory>
0032 #include <string>
0033
0034 namespace detray::test {
0035
0036
0037
0038
0039 template <typename detector_t>
0040 class material_scan : public test::fixture_base<> {
0041 using algebra_t = typename detector_t::algebra_type;
0042 using point2_t = dpoint2D<algebra_t>;
0043 using scalar_t = dscalar<algebra_t>;
0044 using ray_t = detail::ray<algebra_t>;
0045 using track_generator_t = uniform_track_generator<ray_t>;
0046
0047 public:
0048 using fixture_type = test::fixture_base<>;
0049
0050 struct config : public fixture_type::configuration {
0051 using trk_gen_config_t = typename track_generator_t::configuration;
0052
0053 std::string m_name{"material_scan"};
0054 trk_gen_config_t m_trk_gen_cfg{};
0055
0056 std::string m_material_file{"material_scan"};
0057
0058 bool m_overlaps_removal{true};
0059
0060 float m_overlaps_tol{1e-4f * unit<float>::mm};
0061
0062
0063
0064 const std::string &name() const { return m_name; }
0065 trk_gen_config_t &track_generator() { return m_trk_gen_cfg; }
0066 const trk_gen_config_t &track_generator() const { return m_trk_gen_cfg; }
0067 const std::string &material_file() const { return m_material_file; }
0068 bool overlaps_removal() const { return m_overlaps_removal; }
0069 float overlaps_tol() const { return m_overlaps_tol; }
0070
0071
0072
0073
0074 config &name(const std::string &n) {
0075 m_name = n;
0076 return *this;
0077 }
0078 config &material_file(const std::string &f) {
0079 m_material_file = f;
0080 return *this;
0081 }
0082 config &overlaps_removal(const bool o) {
0083 m_overlaps_removal = o;
0084 return *this;
0085 }
0086 config &overlaps_tol(const scalar_t tol) {
0087 m_overlaps_tol = static_cast<float>(tol);
0088 return *this;
0089 }
0090
0091 };
0092
0093 explicit material_scan(const detector_t &det,
0094 const typename detector_t::name_map &names,
0095 const config &cfg = {},
0096 std::shared_ptr<test::whiteboard> wb = nullptr,
0097 const typename detector_t::geometry_context &gctx = {})
0098 : m_cfg{cfg},
0099 m_gctx{gctx},
0100 m_det{det},
0101 m_names{names},
0102 m_whiteboard{std::move(wb)} {
0103 if (!m_whiteboard) {
0104 throw std::invalid_argument("No white board was passed to " +
0105 m_cfg.name());
0106 }
0107 }
0108
0109
0110 void TestBody() override {
0111 using material_record_t = material_validator::material_record<scalar_t>;
0112
0113 std::size_t n_tracks{0u};
0114 auto ray_generator = track_generator_t(m_cfg.track_generator());
0115
0116 DETRAY_INFO_HOST("Running material scan on: "
0117 << m_det.name(m_names) << "\n(" << ray_generator.size()
0118 << " rays) ...\n");
0119
0120
0121 dvector<free_track_parameters<algebra_t>> tracks{};
0122 tracks.reserve(ray_generator.size());
0123
0124 dvector<material_record_t> mat_records{};
0125 mat_records.reserve(ray_generator.size());
0126
0127 for (const auto ray : ray_generator) {
0128
0129 auto intersection_record =
0130 detector_scanner::run<detray::ray_scan>(m_gctx, m_det, ray);
0131
0132
0133 if (m_cfg.overlaps_removal()) {
0134 detector_scanner::overlaps_removal(intersection_record,
0135 m_cfg.overlaps_tol());
0136 }
0137
0138 if (intersection_record.empty()) {
0139 DETRAY_FATAL_HOST("Intersection trace empty for ray "
0140 << n_tracks << "/" << ray_generator.size() << ": "
0141 << ray);
0142 break;
0143 }
0144
0145
0146 tracks.push_back({ray.pos(), 0.f, ray.dir(), 0.f});
0147
0148
0149 material_record_t mat_record{};
0150 mat_record.eta = vector::eta(ray.dir());
0151 mat_record.phi = vector::phi(ray.dir());
0152
0153
0154 for (const auto &[i, record] :
0155 detray::views::enumerate(intersection_record)) {
0156
0157 if (i < intersection_record.size() - 1) {
0158 const auto ¤t_intr = record.intersection;
0159 const auto &next_intr = intersection_record[i + 1].intersection;
0160
0161 const bool is_same_intrs{next_intr == current_intr};
0162 const bool current_is_pt{current_intr.surface().is_portal()};
0163 const bool next_is_pt{next_intr.surface().is_portal()};
0164
0165 const bool is_dummy_record{i == 0};
0166
0167
0168
0169 if ((is_same_intrs && next_is_pt && current_is_pt) ||
0170 is_dummy_record) {
0171 continue;
0172 }
0173 }
0174
0175
0176
0177 if (detail::is_invalid_value(record.intersection.volume_link())) {
0178 continue;
0179 }
0180
0181 const auto sf = geometry::surface{m_det, record.intersection.surface()};
0182
0183 if (!sf.has_material()) {
0184 continue;
0185 }
0186
0187 const auto &p = record.intersection.local();
0188 const auto mat_params =
0189 sf.template visit_material<material_validator::get_material_params>(
0190 point2_t{p[0], p[1]}, cos_angle(m_gctx, sf, ray.dir(), p));
0191
0192 const scalar_t seg{mat_params.path};
0193 const scalar_t t{mat_params.thickness};
0194 const scalar_t mx0{mat_params.mat_X0};
0195 const scalar_t ml0{mat_params.mat_L0};
0196
0197 if (mx0 > 0.f) {
0198 mat_record.sX0 += seg / mx0;
0199 mat_record.tX0 += t / mx0;
0200 } else {
0201 DETRAY_ERROR_HOST(
0202 "Encountered invalid X_0: " << mx0 << "\nOn surface: " << sf);
0203 }
0204 if (ml0 > 0.f) {
0205 mat_record.sL0 += seg / ml0;
0206 mat_record.tL0 += t / ml0;
0207 } else {
0208 DETRAY_ERROR_HOST(
0209 "Encountered invalid L_0: " << ml0 << "\nOn surface: " << sf);
0210 }
0211 }
0212
0213 if (mat_record.sX0 == 0.f || mat_record.sL0 == 0.f ||
0214 mat_record.tX0 == 0.f || mat_record.tL0 == 0.f) {
0215 DETRAY_VERBOSE_HOST("No material recorded for ray "
0216 << n_tracks << "/" << ray_generator.size() << ": "
0217 << ray);
0218 }
0219
0220 mat_records.push_back(mat_record);
0221
0222 ++n_tracks;
0223 }
0224
0225 std::clog << "-----------------------------------\n"
0226 << "Tested " << n_tracks << " tracks\n"
0227 << "-----------------------------------\n"
0228 << std::endl;
0229
0230
0231 const std::string det_name{m_det.name(m_names)};
0232 const auto material_path{std::filesystem::path{m_cfg.material_file()}};
0233 const auto data_path{material_path.parent_path()};
0234
0235 std::string file_name{det_name + "_" + material_path.stem().string() +
0236 ".csv"};
0237 material_validator::write_material(data_path / file_name, mat_records);
0238
0239
0240 m_whiteboard->add(det_name + "_material_scan", std::move(mat_records));
0241 m_whiteboard->add(det_name + "_material_scan_tracks", std::move(tracks));
0242 }
0243
0244 private:
0245
0246 config m_cfg;
0247
0248 typename detector_t::geometry_context m_gctx{};
0249
0250 const detector_t &m_det;
0251
0252 const typename detector_t::name_map &m_names;
0253
0254 std::shared_ptr<test::whiteboard> m_whiteboard{nullptr};
0255 };
0256
0257 }