File indexing completed on 2026-05-27 07:24:16
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 #include "detray/definitions/pdg_particle.hpp"
0011 #include "detray/definitions/units.hpp"
0012 #include "detray/geometry/mask.hpp"
0013 #include "detray/geometry/shapes/unbounded.hpp"
0014 #include "detray/material/interaction.hpp"
0015 #include "detray/material/material.hpp"
0016 #include "detray/material/material_slab.hpp"
0017 #include "detray/material/predefined_materials.hpp"
0018 #include "detray/navigation/caching_navigator.hpp"
0019 #include "detray/propagator/actors.hpp"
0020 #include "detray/propagator/actors/parameter_updater.hpp"
0021 #include "detray/propagator/line_stepper.hpp"
0022 #include "detray/propagator/propagator.hpp"
0023 #include "detray/propagator/rk_stepper.hpp"
0024 #include "detray/test/common/bfield.hpp"
0025
0026
0027 #include "detray/test/common/build_telescope_detector.hpp"
0028 #include "detray/test/framework/types.hpp"
0029 #include "detray/test/utils/inspectors.hpp"
0030 #include "detray/test/utils/random_scatterer.hpp"
0031 #include "detray/test/utils/statistics.hpp"
0032
0033
0034 #include <vecmem/memory/host_memory_resource.hpp>
0035
0036
0037 #include <gtest/gtest.h>
0038
0039 using namespace detray;
0040
0041 using test_algebra = test::algebra;
0042 using scalar = test::scalar;
0043 using covariance_t =
0044 typename bound_track_parameters<test_algebra>::covariance_type;
0045 using interactor_t = actor::pointwise_material_interactor<test_algebra>;
0046
0047 static_assert(detray::concepts::actor<interactor_t>);
0048
0049
0050 namespace {
0051 pdg_particle ptc = muon<scalar>();
0052 }
0053
0054
0055 GTEST_TEST(detray_material, telescope_geometry_energy_loss) {
0056 vecmem::host_memory_resource host_mr;
0057
0058
0059 detail::ray<test_algebra> traj{{0.f, 0.f, 0.f}, 0.f, {1.f, 0.f, 0.f}, -1.f};
0060 std::vector<scalar> positions = {0.f, 50.f, 100.f, 150.f, 200.f, 250.f,
0061 300.f, 350.f, 400.f, 450.f, 500.f};
0062
0063 const auto mat = silicon_tml<scalar>();
0064 constexpr scalar thickness{0.17f * unit<scalar>::cm};
0065
0066 tel_det_config<test_algebra, rectangle2D> tel_cfg{20.f * unit<scalar>::mm,
0067 20.f * unit<scalar>::mm};
0068 tel_cfg.positions(positions)
0069 .pilot_track(traj)
0070 .module_material(mat)
0071 .mat_thickness(thickness);
0072
0073 const auto [det, names] =
0074 build_telescope_detector<test_algebra>(host_mr, tel_cfg);
0075
0076 using navigator_t = caching_navigator<decltype(det)>;
0077 using stepper_t = line_stepper<test_algebra>;
0078 using pathlimit_aborter_t = actor::pathlimit_aborter<scalar>;
0079 using actor_chain_t =
0080 actor_chain<pathlimit_aborter_t,
0081 actor::parameter_updater<test_algebra, interactor_t>>;
0082 using propagator_t = propagator<stepper_t, navigator_t, actor_chain_t>;
0083
0084
0085 propagation::config prop_cfg{};
0086 prop_cfg.navigation.intersection.overstep_tolerance =
0087 -100.f * unit<float>::um;
0088 propagator_t p{prop_cfg};
0089
0090 constexpr scalar iniP{10.f * unit<scalar>::GeV};
0091
0092
0093 bound_parameters_vector<test_algebra> bound_vector{};
0094 bound_vector.set_theta(constant<scalar>::pi_2);
0095 bound_vector.set_qop(ptc.charge() / iniP);
0096
0097 auto bound_cov = matrix::identity<covariance_t>();
0098 getter::element(bound_cov, e_bound_qoverp, e_bound_qoverp) = 0.f;
0099
0100
0101 const bound_track_parameters<test_algebra> bound_param(
0102 det.surface(0u).identifier(), bound_vector, bound_cov);
0103
0104 pathlimit_aborter_t::state aborter_state{};
0105 actor::parameter_updater_state<test_algebra> updater_state{prop_cfg,
0106 bound_param};
0107 interactor_t::state interactor_state{};
0108
0109
0110 auto actor_states =
0111 detray::tie(aborter_state, updater_state, interactor_state);
0112
0113 propagator_t::state state(bound_param, det);
0114 state.debug(true);
0115
0116
0117 ASSERT_TRUE(p.propagate(state, actor_states));
0118
0119
0120 const scalar newP{updater_state.bound_params().p(ptc.charge())};
0121
0122
0123 const auto mass = ptc.mass();
0124
0125
0126 const scalar newE{std::hypot(newP, mass)};
0127
0128
0129 const scalar iniE{std::hypot(iniP, mass)};
0130
0131
0132 const scalar new_var_qop{
0133 getter::element(updater_state.bound_params().covariance(), e_bound_qoverp,
0134 e_bound_qoverp)};
0135
0136
0137 interaction<scalar> I;
0138
0139
0140 const scalar cos_inc_ang{1.f};
0141
0142
0143 material_slab<scalar> slab(mat, thickness);
0144
0145
0146 const scalar path_segment{slab.path_segment(cos_inc_ang)};
0147
0148
0149
0150
0151
0152
0153 const scalar dE{
0154 I.compute_energy_loss_bethe_bloch(path_segment, slab.get_material(), ptc,
0155 {ptc, ptc.charge() / iniP}) *
0156 static_cast<scalar>(positions.size())};
0157
0158
0159
0160 EXPECT_NEAR(newE, iniE - dE, 1e-5f) << "dE = " << dE;
0161
0162 const scalar sigma_qop{I.compute_energy_loss_landau_sigma_QOverP(
0163 path_segment, slab.get_material(), ptc, {ptc, ptc.charge() / iniP})};
0164
0165 const scalar dvar_qop{sigma_qop * sigma_qop *
0166 static_cast<scalar>(positions.size() - 1u)};
0167
0168 EXPECT_NEAR(new_var_qop, dvar_qop, 1e-10f);
0169
0170
0171 }
0172
0173
0174 GTEST_TEST(detray_material, telescope_geometry_scattering_angle) {
0175 vecmem::host_memory_resource host_mr;
0176
0177
0178 detail::ray<test_algebra> traj{{0.f, 0.f, 0.f}, 0.f, {1.f, 0.f, 0.f}, -1.f};
0179 std::vector<scalar> positions = {0.f};
0180
0181
0182 EXPECT_EQ(positions.size(), 1u);
0183
0184
0185 const auto mat = silicon_tml<scalar>();
0186 const scalar thickness = 100.f * unit<scalar>::cm;
0187
0188
0189 tel_det_config<test_algebra, rectangle2D> tel_cfg{2000.f * unit<scalar>::mm,
0190 2000.f * unit<scalar>::mm};
0191 tel_cfg.positions(positions)
0192 .pilot_track(traj)
0193 .module_material(mat)
0194 .mat_thickness(thickness);
0195
0196 const auto [det, names] =
0197 build_telescope_detector<test_algebra>(host_mr, tel_cfg);
0198
0199 using navigator_t = caching_navigator<decltype(det)>;
0200 using stepper_t = line_stepper<test_algebra>;
0201 using simulator_t = actor::random_scatterer<test_algebra>;
0202 using pathlimit_aborter_t = actor::pathlimit_aborter<scalar>;
0203 using actor_chain_t =
0204 actor_chain<pathlimit_aborter_t,
0205 actor::parameter_updater<test_algebra, simulator_t>>;
0206 using propagator_t = propagator<stepper_t, navigator_t, actor_chain_t>;
0207
0208
0209 propagation::config prop_cfg{};
0210 prop_cfg.navigation.intersection.overstep_tolerance =
0211 -100.f * unit<float>::um;
0212 propagator_t p{prop_cfg};
0213
0214 constexpr scalar q{-1.f};
0215 constexpr scalar iniP{10.f * unit<scalar>::GeV};
0216
0217
0218 bound_parameters_vector<test_algebra> bound_vector{};
0219 bound_vector.set_theta(constant<scalar>::pi_2);
0220 bound_vector.set_qop(q / iniP);
0221
0222 auto bound_cov = matrix::identity<covariance_t>();
0223 getter::element(bound_cov, e_bound_phi, e_bound_phi) = 0.f;
0224 getter::element(bound_cov, e_bound_theta, e_bound_theta) = 0.f;
0225
0226
0227 const bound_track_parameters<test_algebra> bound_param(
0228 det.surface(0u).identifier(), bound_vector, bound_cov);
0229
0230 std::size_t n_samples{100000u};
0231 std::vector<scalar> phis;
0232 std::vector<scalar> thetas;
0233
0234 for (std::size_t i = 0u; i < n_samples; i++) {
0235 pathlimit_aborter_t::state aborter_state{};
0236
0237 actor::parameter_updater_state<test_algebra> updater_state{prop_cfg,
0238 bound_param};
0239 simulator_t::state simulator_state{i};
0240 simulator_state.do_energy_loss = false;
0241
0242
0243 auto actor_states =
0244 detray::tie(aborter_state, updater_state, simulator_state);
0245
0246 propagator_t::state state(bound_param, det);
0247 state.debug(true);
0248
0249
0250 ASSERT_TRUE(p.propagate(state, actor_states));
0251
0252 const auto& final_param = updater_state.bound_params();
0253
0254
0255 if (i == 0u) {
0256 interactor_t{}.update_angle_variance(
0257 bound_cov, traj.dir(), simulator_state.projected_scattering_angle);
0258 }
0259
0260 phis.push_back(final_param.phi());
0261 thetas.push_back(final_param.theta());
0262 }
0263 scalar phi_variance{statistics::rms(phis, bound_param.phi())};
0264 scalar theta_variance{statistics::rms(thetas, bound_param.theta())};
0265
0266
0267 scalar ref_phi_variance =
0268 getter::element(bound_cov, e_bound_phi, e_bound_phi);
0269 scalar ref_theta_variance =
0270 getter::element(bound_cov, e_bound_theta, e_bound_theta);
0271
0272
0273 EXPECT_NEAR((phi_variance - ref_phi_variance) / ref_phi_variance, 0.f, 1e-2f);
0274 EXPECT_NEAR((theta_variance - ref_theta_variance) / ref_theta_variance, 0.f,
0275 1e-2f);
0276
0277
0278 EXPECT_TRUE(ref_phi_variance > 1e-9f && ref_theta_variance > 1e-9f);
0279 }
0280
0281
0282 GTEST_TEST(detray_material, telescope_geometry_volume_material) {
0283 vecmem::host_memory_resource host_mr;
0284
0285
0286 using bfield_t = bfield::const_field_t<scalar>;
0287 using stepper_t = rk_stepper<bfield_t::view_t, test_algebra>;
0288
0289 using pathlimit_aborter_t = actor::pathlimit_aborter<scalar>;
0290 using actor_chain_t = actor_chain<pathlimit_aborter_t>;
0291
0292 constexpr std::size_t cache_size{navigation::default_cache_size};
0293
0294
0295 test::vector3 B_z{static_cast<scalar>(0.f), static_cast<scalar>(0.f),
0296 2.f * unit<scalar>::T};
0297 const bfield_t const_bfield = create_const_field<scalar>(B_z);
0298
0299
0300 constexpr scalar q{-1.f};
0301 constexpr scalar iniP{10.f * unit<scalar>::GeV};
0302
0303 bound_parameters_vector<test_algebra> bound_vector{};
0304 bound_vector.set_theta(constant<scalar>::pi_2);
0305 bound_vector.set_qop(q / iniP);
0306
0307 auto bound_cov = matrix::identity<covariance_t>();
0308 getter::element(bound_cov, e_bound_qoverp, e_bound_qoverp) = 0.f;
0309
0310
0311 const scalar path_limit = 100 * unit<scalar>::mm;
0312
0313
0314 detail::ray<test_algebra> traj{{0.f, 0.f, 0.f}, 0.f, {1.f, 0.f, 0.f}, -1.f};
0315 std::vector<scalar> positions = {0.f, 5000.f * unit<scalar>::mm};
0316
0317
0318 const auto module_mat = vacuum<scalar>();
0319
0320
0321 tel_det_config<test_algebra, rectangle2D> tel_cfg{50000.f * unit<scalar>::mm,
0322 50000.f * unit<scalar>::mm};
0323 tel_cfg.positions(positions).pilot_track(traj).module_material(module_mat);
0324
0325 std::vector<material<scalar>> vol_mats = {
0326 vacuum<scalar>(), isobutane<scalar>(), silicon<scalar>(),
0327 tungsten<scalar>()};
0328
0329 for (const auto& mat : vol_mats) {
0330 tel_cfg.volume_material(mat);
0331 const auto [det, names] =
0332 build_telescope_detector<test_algebra>(host_mr, tel_cfg);
0333
0334
0335 const bound_track_parameters<test_algebra> bound_param(
0336 det.surface(0u).identifier(), bound_vector, bound_cov);
0337
0338 using navigator_t = caching_navigator<decltype(det), cache_size,
0339 navigation::print_inspector>;
0340
0341 using propagator_t = propagator<stepper_t, navigator_t, actor_chain_t>;
0342
0343
0344 propagation::config prop_cfg{};
0345 prop_cfg.navigation.intersection.overstep_tolerance =
0346 -100.f * unit<float>::um;
0347 propagator_t p{prop_cfg};
0348
0349 propagator_t::stepper_type::magnetic_field_type bfield_view(const_bfield);
0350 propagator_t::state state(bound_param, bfield_view, det);
0351
0352 pathlimit_aborter_t::state abrt_state{path_limit};
0353 auto actor_states = detray::tie(abrt_state);
0354
0355 p.propagate(state, actor_states);
0356
0357 const auto newP = state.stepping()().p(ptc.charge());
0358 const auto mass = ptc.mass();
0359
0360 const auto eloss_approx =
0361 interaction<scalar>().compute_energy_loss_bethe_bloch(
0362 state.stepping().path_length(), mat, ptc, {ptc, bound_param.qop()});
0363
0364 const auto iniE = std::sqrt(iniP * iniP + mass * mass);
0365 const auto newE = std::sqrt(newP * newP + mass * mass);
0366 const auto eloss = iniE - newE;
0367
0368 if (mat == vacuum<scalar>()) {
0369 ASSERT_FLOAT_EQ(float(eloss), 0.f);
0370 } else {
0371 ASSERT_TRUE(eloss > 0.f) << "mat.: " << mat << "\n"
0372 << state.navigation().inspector().to_string();
0373 }
0374
0375 ASSERT_NEAR(eloss, eloss_approx, eloss * 0.01);
0376 }
0377 }