File indexing completed on 2026-05-27 07:24:16
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 #include "detray/definitions/units.hpp"
0011 #include "detray/geometry/mask.hpp"
0012 #include "detray/geometry/shapes.hpp"
0013 #include "detray/geometry/shapes/unbounded.hpp"
0014 #include "detray/geometry/surface_descriptor.hpp"
0015 #include "detray/navigation/intersection/helix_intersector.hpp"
0016 #include "detray/propagator/detail/jacobian_engine.hpp"
0017 #include "detray/test/framework/types.hpp"
0018 #include "detray/tracks/tracks.hpp"
0019 #include "detray/utils/axis_rotation.hpp"
0020
0021
0022 #include <vecmem/memory/host_memory_resource.hpp>
0023
0024
0025 #include <gtest/gtest.h>
0026
0027 using namespace detray;
0028
0029
0030 using test_algebra = test::algebra;
0031 using scalar = test::scalar;
0032 using transform3 = test::transform3;
0033 using vector3 = test::vector3;
0034 using intersection_t = intersection2D<surface_descriptor<>, test_algebra,
0035 intersection::contains_pos>;
0036
0037
0038
0039 using annulus_type =
0040 detray::mask<detray::unbounded<detray::annulus2D>, test_algebra>;
0041 using rectangle_type = detray::mask<detray::rectangle2D, test_algebra>;
0042 using trapezoid_type = detray::mask<detray::trapezoid2D, test_algebra>;
0043 using ring_type = detray::mask<detray::ring2D, test_algebra>;
0044 using cylinder_type = detray::mask<detray::cylinder2D, test_algebra>;
0045 using straw_tube_type = detray::mask<detray::line_circular, test_algebra>;
0046 using drift_cell_type = detray::mask<detray::line_square, test_algebra>;
0047
0048
0049 template <typename T>
0050 class detray_propagation_HelixCovarianceTransportValidation
0051 : public ::testing::Test {
0052 public:
0053
0054 const vector3 B{static_cast<scalar>(0.f), static_cast<scalar>(0.f),
0055 1.f * unit<scalar>::T};
0056 const vector3 y_axis{0.f, 1.f, 0.f};
0057 const vector3 z_axis{0.f, 0.f, 1.f};
0058 static constexpr const scalar mask_tolerance{1e-3f};
0059 static constexpr const scalar tolerance{3e-2f};
0060
0061
0062 using mask_type = T;
0063 using local_frame_type = typename mask_type::local_frame;
0064
0065
0066 using first_mask_type = rectangle_type;
0067 using first_local_frame_type = typename first_mask_type::local_frame;
0068
0069
0070 using algebra_type = typename local_frame_type::algebra_type;
0071
0072
0073 using bound_param_vector_t =
0074 typename bound_track_parameters<algebra_type>::parameter_vector_type;
0075 using bound_matrix_t = bound_matrix<algebra_type>;
0076 using bound_to_free_matrix_t = bound_to_free_matrix<algebra_type>;
0077
0078 using free_matrix_t = free_matrix<algebra_type>;
0079 using free_to_bound_matrix_t = free_to_bound_matrix<algebra_type>;
0080
0081 std::tuple<annulus_type, rectangle_type, trapezoid_type, ring_type,
0082 cylinder_type, straw_tube_type, drift_cell_type>
0083 masks = std::make_tuple(
0084 annulus_type{0u, 7.2f * unit<scalar>::mm, 12.0f * unit<scalar>::mm,
0085 0.74195f, 1.33970f, 0.f, -2.f, 2.f},
0086 rectangle_type{0u, 50 * unit<scalar>::mm, 50 * unit<scalar>::mm},
0087 trapezoid_type{0u, 50 * unit<scalar>::mm, 100 * unit<scalar>::mm,
0088 50 * unit<scalar>::mm,
0089 1.f / (2.f * 50 * unit<scalar>::mm)},
0090 ring_type{0u, 0.f, 50.f * unit<scalar>::mm},
0091 cylinder_type{0u, 0.3f * unit<scalar>::mm, -100.f * unit<scalar>::mm,
0092 100.f * unit<scalar>::mm},
0093 straw_tube_type{0u, 50.f * unit<scalar>::mm,
0094 100.f * unit<scalar>::mm},
0095 drift_cell_type{0u, 50.f * unit<scalar>::mm,
0096 100.f * unit<scalar>::mm});
0097
0098
0099 std::vector<transform3> create_transforms(
0100 detail::helix<algebra_type>& reference_helix,
0101 const std::size_t n_planes) {
0102 std::vector<transform3> trfs;
0103
0104
0105 const scalar S{2.f * constant<scalar>::pi /
0106 std::abs(reference_helix.qop() * reference_helix.B())};
0107 const scalar step_size = S / static_cast<scalar>(n_planes);
0108
0109 for (std::size_t i = 0u; i < n_planes; i++) {
0110 const scalar s = step_size * static_cast<scalar>(i);
0111
0112
0113 vector3 trl = reference_helix(s);
0114
0115
0116 vector3 w = reference_helix.dir(s);
0117 vector3 v = vector::cross(z_axis, w);
0118
0119 if (i > 0u &&
0120 (std::is_same_v<local_frame_type, cylindrical2D<algebra_type>> ||
0121 std::is_same_v<local_frame_type, line2D<algebra_type>>)) {
0122 const vector3 r_axis = vector::cross(w, z_axis);
0123
0124
0125 axis_rotation<algebra_type> axis_rot(r_axis,
0126 constant<scalar>::pi / 2.f);
0127
0128
0129 w = axis_rot(w);
0130 EXPECT_NEAR(vector::norm(r_axis), 1.f, tolerance);
0131
0132 v = vector::cross(r_axis, w);
0133
0134
0135
0136
0137
0138 } else {
0139
0140
0141
0142 const vector3 offset{static_cast<scalar>(0.f), static_cast<scalar>(0.f),
0143 10.f * unit<scalar>::mm};
0144 trl = trl + offset;
0145 }
0146
0147
0148 trfs.emplace_back(trl, w, v);
0149 }
0150
0151 return trfs;
0152 }
0153
0154
0155 template <typename departure_mask_type, typename destination_mask_type>
0156 bound_track_parameters<algebra_type> propagate(
0157 const bound_track_parameters<algebra_type>& bound_params,
0158 const transform3& trf_0, const transform3& trf_1,
0159 const departure_mask_type& mask_0, const destination_mask_type& mask_1,
0160 scalar& total_path_length, std::vector<intersection_t>& sfis) {
0161 using departure_frame = typename departure_mask_type::local_frame;
0162 using destination_frame = typename destination_mask_type::local_frame;
0163
0164 using jacobian_engine = detail::jacobian_engine<algebra_type>;
0165
0166 const bound_param_vector_t& bound_vec_0 = bound_params;
0167 const bound_matrix_t& bound_cov_0 = bound_params.covariance();
0168
0169
0170 const auto free_trk_0 =
0171 detail::bound_to_free_vector(trf_0, mask_0, bound_vec_0);
0172
0173
0174 detail::helix<algebra_type> hlx(free_trk_0, B);
0175
0176
0177 const bound_to_free_matrix_t bound_to_free_jacobi =
0178 jacobian_engine::template bound_to_free_jacobian<departure_frame>(
0179 trf_0, mask_0, bound_vec_0);
0180
0181
0182 helix_intersector<typename destination_mask_type::shape, algebra_type>
0183 helix_inters{};
0184 helix_inters.run_rtsafe = false;
0185 const intersection_t is = get_intersection(helix_inters(
0186 hlx, surface_descriptor<>{}, mask_1, trf_1, this->mask_tolerance));
0187
0188 EXPECT_TRUE(is.is_inside()) << is;
0189
0190 sfis.push_back(is);
0191
0192
0193 const auto path_length = is.path();
0194
0195
0196 total_path_length += path_length;
0197
0198
0199 const free_matrix_t transport_jacobi = hlx.jacobian(path_length);
0200
0201
0202 const vector3 r = hlx.pos(path_length);
0203
0204
0205 const vector3 t = hlx.dir(path_length);
0206
0207
0208 const vector3 dtds = hlx.qop() * vector::cross(t, B);
0209
0210
0211 const scalar dqopds = 0.f;
0212
0213
0214 free_track_parameters<algebra_type> free_trk_1;
0215 free_trk_1.set_pos(r);
0216 free_trk_1.set_dir(t);
0217 free_trk_1.set_qop(free_trk_0.qop());
0218
0219
0220 const free_matrix_t path_correction =
0221 jacobian_engine::template path_correction<destination_frame>(
0222 r, t, dtds, dqopds, trf_1);
0223
0224
0225 const free_matrix_t correction_term =
0226 matrix::identity<free_matrix_t>() + path_correction;
0227
0228
0229 const free_to_bound_matrix_t free_to_bound_jacobi =
0230 jacobian_engine::template free_to_bound_jacobian<destination_frame>(
0231 trf_1, free_trk_1);
0232
0233
0234 const bound_param_vector_t bound_vec_1 =
0235 detail::free_to_bound_vector<destination_frame>(trf_1, free_trk_1);
0236
0237
0238 const bound_matrix_t full_jacobi = free_to_bound_jacobi * correction_term *
0239 transport_jacobi * bound_to_free_jacobi;
0240
0241
0242 const bound_matrix_t bound_cov_1 =
0243 full_jacobi * bound_cov_0 * matrix::transpose(full_jacobi);
0244
0245 bound_track_parameters<algebra_type> ret;
0246 ret.set_parameter_vector(bound_vec_1);
0247 ret.set_covariance(bound_cov_1);
0248
0249 return ret;
0250 }
0251
0252 intersection_t get_intersection(
0253 const std::array<intersection_t, 2>& inters) const {
0254 return inters[0];
0255 }
0256
0257 intersection_t get_intersection(const intersection_t& inters) const {
0258 return inters;
0259 }
0260 };
0261
0262 using TestTypes =
0263 ::testing::Types<annulus_type, rectangle_type, trapezoid_type, ring_type,
0264 cylinder_type, straw_tube_type, drift_cell_type>;
0265 TYPED_TEST_SUITE(detray_propagation_HelixCovarianceTransportValidation,
0266 TestTypes, );
0267
0268 TYPED_TEST(detray_propagation_HelixCovarianceTransportValidation,
0269 one_loop_test) {
0270
0271
0272 free_track_parameters<test_algebra> free_trk(
0273 {static_cast<scalar>(0.f), static_cast<scalar>(0.f),
0274 static_cast<scalar>(0.f)},
0275 static_cast<scalar>(0.f),
0276 {0.1f * unit<scalar>::GeV, static_cast<scalar>(0.f),
0277 static_cast<scalar>(0.f)},
0278 static_cast<scalar>(-1.f));
0279
0280 detail::helix<test_algebra> reference_helix(free_trk, this->B);
0281
0282 const std::size_t n_planes = 10u;
0283 std::vector<transform3> trfs =
0284 this->create_transforms(reference_helix, n_planes);
0285 ASSERT_EQ(trfs.size(), 10u);
0286
0287
0288 bound_parameters_vector<test_algebra> bound_vec_0 =
0289 detail::free_to_bound_vector<
0290 typename TestFixture::first_local_frame_type>(trfs[0], free_trk);
0291
0292
0293 auto bound_cov_0 = matrix::zero<
0294 typename bound_track_parameters<test_algebra>::covariance_type>();
0295 getter::element(bound_cov_0, e_bound_loc0, e_bound_loc0) = 1.f;
0296 getter::element(bound_cov_0, e_bound_loc1, e_bound_loc1) = 1.f;
0297 getter::element(bound_cov_0, e_bound_phi, e_bound_phi) = 1.f;
0298
0299 getter::element(bound_cov_0, e_bound_theta, e_bound_theta) = 0.f;
0300 getter::element(bound_cov_0, e_bound_qoverp, e_bound_qoverp) = 1.f;
0301 getter::element(bound_cov_0, e_bound_time, e_bound_time) = 0.f;
0302
0303
0304 bound_track_parameters<test_algebra> bound_params;
0305 bound_params.set_parameter_vector(bound_vec_0);
0306 bound_params.set_covariance(bound_cov_0);
0307
0308
0309 const auto first_mask = std::get<rectangle_type>(this->masks);
0310 const auto test_mask = std::get<typename TestFixture::mask_type>(this->masks);
0311
0312
0313 scalar total_path_length = 0.f;
0314
0315
0316 std::vector<intersection_t> sfis;
0317
0318
0319 for (std::size_t i_p = 0u; i_p < n_planes; i_p++) {
0320 if (i_p == 0) {
0321 bound_params =
0322 this->propagate(bound_params, trfs[i_p], trfs[i_p + 1], first_mask,
0323 test_mask, total_path_length, sfis);
0324 } else if (i_p > 0 && i_p < n_planes - 1) {
0325 bound_params =
0326 this->propagate(bound_params, trfs[i_p], trfs[i_p + 1], test_mask,
0327 test_mask, total_path_length, sfis);
0328
0329 } else if (i_p == n_planes - 1) {
0330 bound_params =
0331 this->propagate(bound_params, trfs[i_p], trfs[0], test_mask,
0332 first_mask, total_path_length, sfis);
0333 }
0334 }
0335
0336
0337 ASSERT_TRUE(total_path_length > 1e-3);
0338 ASSERT_NEAR(total_path_length,
0339 2.f * constant<scalar>::pi /
0340 std::abs(reference_helix.qop() * reference_helix.B()),
0341 this->tolerance);
0342
0343 ASSERT_EQ(sfis.size(), n_planes);
0344 for (std::size_t i = 0u; i < n_planes; i++) {
0345 EXPECT_TRUE(sfis[i].is_inside());
0346 EXPECT_TRUE(sfis[i].is_along());
0347 }
0348
0349
0350 for (unsigned int i = 0u; i < e_bound_size; i++) {
0351 EXPECT_NEAR(bound_vec_0[i], bound_params[i], this->tolerance)
0352 << "i: " << i << "\n"
0353 << std::setprecision(8) << bound_params;
0354 }
0355
0356
0357 for (unsigned int i = 0u; i < e_bound_size; i++) {
0358 for (unsigned int j = 0u; j < e_bound_size; j++) {
0359 EXPECT_NEAR(getter::element(bound_cov_0, i, j),
0360 getter::element(bound_params.covariance(), i, j),
0361 this->tolerance)
0362 << "i: " << i << "\nj: " << j << "\n"
0363 << std::setprecision(8) << bound_params;
0364 }
0365 }
0366 }