File indexing completed on 2026-05-27 07:24:15
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011
0012 #include "detray/definitions/track_parametrization.hpp"
0013 #include "detray/geometry/identifier.hpp"
0014 #include "detray/geometry/surface.hpp"
0015 #include "detray/navigation/caching_navigator.hpp"
0016 #include "detray/propagator/actor_chain.hpp"
0017 #include "detray/propagator/actors/aborters.hpp"
0018 #include "detray/propagator/propagator.hpp"
0019 #include "detray/tracks/free_track_parameters.hpp"
0020 #include "detray/tracks/helix.hpp"
0021 #include "detray/utils/logging.hpp"
0022
0023
0024 #include "detray/io/csv/intersection2D.hpp"
0025 #include "detray/io/csv/track_parameters.hpp"
0026 #include "detray/io/utils/file_handle.hpp"
0027
0028
0029 #include "detray/test/utils/inspectors.hpp"
0030 #include "detray/test/utils/step_tracer.hpp"
0031 #include "detray/test/validation/detector_scan_utils.hpp"
0032 #include "detray/test/validation/material_validation_utils.hpp"
0033 #include "detray/test/validation/navigation_validation_config.hpp"
0034
0035
0036 #include "detray/plugins/svgtools/styling/styling.hpp"
0037
0038
0039 #include <vecmem/memory/host_memory_resource.hpp>
0040
0041
0042 #include <algorithm>
0043 #include <iomanip>
0044 #include <iterator>
0045 #include <memory>
0046 #include <ranges>
0047 #include <sstream>
0048
0049 namespace detray::navigation_validator {
0050
0051
0052 struct min_dist_to_boundary {
0053 template <typename mask_group_t, typename idx_range_t, typename point_t>
0054 DETRAY_HOST_DEVICE inline auto operator()(const mask_group_t &mask_group,
0055 const idx_range_t idx_range,
0056 const point_t &loc_p) const {
0057 using scalar_t = typename mask_group_t::value_type::scalar_type;
0058
0059 scalar_t min_dist{detail::invalid_value<scalar_t>()};
0060 for (const auto &mask : detray::ranges::subrange(mask_group, idx_range)) {
0061 min_dist = math::min(min_dist, mask.min_dist_to_boundary(loc_p));
0062 }
0063
0064 return min_dist;
0065 }
0066 };
0067
0068
0069 struct empty_bfield {};
0070
0071
0072 struct surface_stats {
0073 std::size_t n_portals{0u};
0074 std::size_t n_sensitives{0u};
0075 std::size_t n_passives{0u};
0076
0077
0078 std::size_t n_total() const { return n_portals + n_sensitives + n_passives; }
0079
0080
0081
0082
0083 template <typename sf_descriptor_t>
0084 bool count(const sf_descriptor_t &sf_desc, const bool verbose = true) {
0085 return count(sf_desc.identifier(), verbose);
0086 }
0087
0088 bool count(geometry::identifier geo_id, const bool verbose = true) {
0089 switch (geo_id.id()) {
0090 using enum surface_id;
0091 case e_portal: {
0092 n_portals++;
0093 return true;
0094 }
0095 case e_sensitive: {
0096 n_sensitives++;
0097 return true;
0098 }
0099 case e_passive: {
0100 n_passives++;
0101 return true;
0102 }
0103 default: {
0104 if (verbose) {
0105 DETRAY_ERROR_HOST("Surface type unknown " << geo_id);
0106 }
0107 return false;
0108 }
0109 }
0110 }
0111
0112
0113
0114 surface_stats &operator+=(const surface_stats &other) {
0115 n_portals += other.n_portals;
0116 n_sensitives += other.n_sensitives;
0117 n_passives += other.n_passives;
0118
0119 return *this;
0120 }
0121 };
0122
0123
0124 struct track_statistics {
0125 std::size_t n_tracks{0u};
0126 std::size_t n_tracks_w_holes{0u};
0127 std::size_t n_tracks_w_extra{0u};
0128 std::size_t n_good_tracks{0u};
0129 std::size_t n_max_missed_per_trk{0u};
0130 std::size_t n_max_extra_per_trk{0u};
0131 };
0132
0133
0134 template <typename stepper_t, typename... actor_ts, typename detector_t,
0135 typename bfield_t = empty_bfield>
0136 inline auto record_propagation(
0137 const typename detector_t::geometry_context ctx,
0138 vecmem::memory_resource *host_mr, const detector_t &det,
0139 const propagation::config &cfg,
0140 const free_track_parameters<typename detector_t::algebra_type> &track,
0141 const pdg_particle<typename detector_t::scalar_type> ptc_hypo =
0142 muon<typename detector_t::scalar_type>(),
0143 const bfield_t &bfield = {},
0144 const navigation::direction nav_dir = navigation::direction::e_forward,
0145 typename actor_chain<actor_ts...>::state_ref_tuple state_tuple = {},
0146 const std::array<dscalar<typename detector_t::algebra_type>, e_bound_size>
0147 &stddevs = {}) {
0148 using algebra_t = typename detector_t::algebra_type;
0149 using scalar_t = dscalar<algebra_t>;
0150
0151
0152 using intersection_t = intersection2D<typename detector_t::surface_type,
0153 algebra_t, intersection::contains_pos>;
0154
0155
0156 using object_tracer_t =
0157 navigation::object_tracer<intersection_t, dvector,
0158 navigation::status::e_on_object,
0159 navigation::status::e_on_portal>;
0160
0161
0162 using nav_print_inspector_t = navigation::print_inspector;
0163
0164 using inspector_t =
0165 aggregate_inspector<object_tracer_t, nav_print_inspector_t>;
0166
0167
0168 using navigator_t =
0169 caching_navigator<detector_t, navigation::default_cache_size, inspector_t,
0170 intersection_t>;
0171
0172
0173 using step_tracer_t = actor::step_tracer<algebra_t, dvector>;
0174 using pathlimit_aborter_t = actor::pathlimit_aborter<scalar_t>;
0175 using material_tracer_t =
0176 material_validator::material_tracer<scalar_t, dvector>;
0177 using actor_chain_t = actor_chain<pathlimit_aborter_t, actor_ts...,
0178 step_tracer_t, material_tracer_t>;
0179 using propagator_t = propagator<stepper_t, navigator_t, actor_chain_t>;
0180
0181
0182 propagator_t prop{cfg};
0183
0184
0185 using updater_state_t = actor::parameter_updater_state<algebra_t>;
0186 static constexpr bool has_param_transport{
0187 detail::has_type_v<updater_state_t, typename actor_chain_t::state_tuple>};
0188
0189 typename pathlimit_aborter_t::state pathlimit_aborter_state{
0190 cfg.stepping.path_limit};
0191 typename step_tracer_t::state step_tracer_state{*host_mr};
0192 typename material_tracer_t::state mat_tracer_state{*host_mr};
0193
0194
0195 auto setup_actor_states =
0196 []<std::size_t... indices>(
0197 typename pathlimit_aborter_t::state &s1,
0198 typename step_tracer_t::state &s2,
0199 typename material_tracer_t::state &s3,
0200 typename actor_chain<actor_ts...>::state_ref_tuple &t,
0201 std::index_sequence<indices...> ) {
0202 return detray::tie(s1, detail::get<indices>(t)..., s2, s3);
0203 };
0204 constexpr auto idx_seq{std::make_index_sequence<detail::tuple_size_v<
0205 typename actor_chain<actor_ts...>::state_ref_tuple>>{}};
0206
0207 auto actor_states =
0208 setup_actor_states(pathlimit_aborter_state, step_tracer_state,
0209 mat_tracer_state, state_tuple, idx_seq);
0210
0211 std::unique_ptr<typename propagator_t::state> propagation{nullptr};
0212 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
0213 propagation =
0214 std::make_unique<typename propagator_t::state>(track, det, ctx);
0215 } else {
0216 typename propagator_t::stepper_type::magnetic_field_type bfield_view(
0217 bfield);
0218 propagation = std::make_unique<typename propagator_t::state>(
0219 track, bfield_view, det, ctx);
0220 }
0221
0222
0223 if constexpr (has_param_transport) {
0224 if (!stddevs.empty() &&
0225 !std::ranges::all_of(stddevs, [](scalar_t s) { return s == 0.f; })) {
0226 auto &updater_state = detail::get<updater_state_t>(actor_states);
0227 updater_state.init(track);
0228
0229
0230 bound_track_parameters<algebra_t> bound_param =
0231 updater_state.bound_params();
0232
0233
0234 std::random_device rd{};
0235 std::mt19937 generator{rd()};
0236
0237 for (std::size_t i = 0u; i < e_bound_size; i++) {
0238
0239 if (stddevs[i] != static_cast<scalar_t>(0)) {
0240 bound_param[i] = std::normal_distribution<scalar_t>(
0241 bound_param[i], stddevs[i])(generator);
0242 }
0243
0244 getter::element(bound_param.covariance(), i, i) =
0245 stddevs[i] * stddevs[i];
0246 }
0247 assert(!bound_param.is_invalid());
0248
0249
0250 updater_state =
0251 actor::parameter_updater_state<algebra_t>{cfg, bound_param};
0252 }
0253 }
0254
0255
0256 auto &nav_inspector = propagation->navigation().inspector();
0257 auto &obj_tracer = nav_inspector.template get<object_tracer_t>();
0258 auto &nav_printer = nav_inspector.template get<navigation::print_inspector>();
0259
0260
0261 auto &step_printer = propagation->stepping().inspector();
0262
0263
0264
0265 if (nav_dir == navigation::direction::e_backward) {
0266 using fw_propagator_t =
0267 propagator<stepper_t, navigator_t,
0268 actor_chain<pathlimit_aborter_t, actor_ts...>>;
0269 std::unique_ptr<typename fw_propagator_t::state> fw_propagation{nullptr};
0270 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
0271 fw_propagation =
0272 std::make_unique<typename fw_propagator_t::state>(track, det, ctx);
0273 } else {
0274 typename fw_propagator_t::stepper_type::magnetic_field_type bfield_view(
0275 bfield);
0276 fw_propagation = std::make_unique<typename fw_propagator_t::state>(
0277 track, bfield_view, det, ctx);
0278 }
0279
0280
0281
0282 auto copy_actor_states =
0283 []<std::size_t... indices>(
0284 typename pathlimit_aborter_t::state &s1,
0285 typename actor_chain<actor_ts...>::state_ref_tuple &t,
0286 std::index_sequence<indices...> ) {
0287 using fw_state_tuple_t =
0288 typename actor_chain<pathlimit_aborter_t,
0289 actor_ts...>::state_tuple;
0290 return fw_state_tuple_t{s1, detail::get<indices>(t)...};
0291 };
0292
0293
0294 auto setup_fw_actor_states =
0295 []<std::size_t... indices>(
0296 typename actor_chain<pathlimit_aborter_t, actor_ts...>::state_tuple
0297 &t,
0298 std::index_sequence<indices...> ) {
0299 return detray::tie(detail::get<indices>(t)...);
0300 };
0301
0302 auto fw_state_tuple =
0303 copy_actor_states(pathlimit_aborter_state, state_tuple, idx_seq);
0304 constexpr auto fw_idx_seq{std::make_index_sequence<
0305 detail::tuple_size_v<decltype(fw_state_tuple)>>{}};
0306 auto fw_actor_states = setup_fw_actor_states(fw_state_tuple, fw_idx_seq);
0307
0308
0309 fw_propagation->set_particle(update_particle_hypothesis(ptc_hypo, track));
0310
0311
0312
0313 if constexpr (has_param_transport) {
0314 detail::get<updater_state_t>(fw_actor_states).always_update(true);
0315 }
0316
0317 const bool fw_success =
0318 fw_propagator_t{cfg}.propagate(*fw_propagation, fw_actor_states);
0319
0320 if (!fw_success) {
0321 DETRAY_ERROR_HOST(
0322 "Could not propagate to end of track to "
0323 "prepare backward propagation");
0324 }
0325
0326
0327 propagation->stepping()() = fw_propagation->stepping()();
0328 propagation->navigation().set_volume(fw_propagation->navigation().volume());
0329
0330
0331
0332 if constexpr (has_param_transport) {
0333 auto &updater_state = detail::get<updater_state_t>(actor_states);
0334 updater_state = detail::get<updater_state_t>(fw_actor_states);
0335 }
0336 }
0337
0338
0339 propagation->navigation().set_direction(nav_dir);
0340 propagation->set_particle(update_particle_hypothesis(ptc_hypo, track));
0341
0342 bool success = prop.propagate(*propagation, actor_states);
0343
0344 return std::make_tuple(success, std::move(obj_tracer),
0345 std::move(step_tracer_state).release_step_data(),
0346 std::move(mat_tracer_state).release_material_record(),
0347 std::move(mat_tracer_state).release_material_steps(),
0348 std::move(nav_printer), std::move(step_printer));
0349 }
0350
0351
0352
0353
0354
0355
0356
0357
0358
0359
0360
0361
0362
0363
0364
0365
0366 template <typename truth_trace_t, typename recorded_trace_t, typename traj_t,
0367 concepts::algebra algebra_t>
0368 auto compare_traces(
0369 const detray::test::navigation_validation_config<algebra_t> &cfg,
0370 truth_trace_t &truth_trace, recorded_trace_t &recorded_trace,
0371 const traj_t &traj, std::size_t trk_no,
0372 std::fstream *debug_file = nullptr) {
0373 using nav_record_t = typename recorded_trace_t::value_type;
0374 using truth_record_t = typename truth_trace_t::value_type;
0375 using intersection_t = typename truth_record_t::intersection_type;
0376
0377
0378
0379 std::size_t max_entries{math::max(recorded_trace.size(), truth_trace.size())};
0380
0381
0382 std::stringstream debug_stream;
0383 std::stringstream matching_stream;
0384
0385
0386 surface_stats missed_stats_nav{};
0387 surface_stats missed_stats_tr{};
0388 bool matching_traces{true};
0389 std::size_t n_errors{0u};
0390 std::vector<intersection_t> missed_intersections{};
0391
0392
0393 auto handle_counting_error = [&matching_stream, &n_errors](const bool is_OK) {
0394 if (!is_OK) {
0395 matching_stream << "FATAL: Encountered surface of "
0396 "unknown type in intersection "
0397 "trace. Validate the geometry";
0398 ++n_errors;
0399 }
0400 };
0401
0402
0403
0404 auto is_swapped_surfaces = [&recorded_trace, &truth_trace](const long j) {
0405 const auto idx_j{static_cast<std::size_t>(j)};
0406 const std::size_t next_idx{idx_j + 1u};
0407
0408 if (next_idx < truth_trace.size() && next_idx < recorded_trace.size()) {
0409 const auto ¤t_nav_inters =
0410 recorded_trace.at(idx_j).intersection.surface().identifier();
0411 const auto ¤t_truth_inters =
0412 truth_trace.at(idx_j).intersection.surface().identifier();
0413
0414 const auto &next_nav_inters =
0415 recorded_trace.at(next_idx).intersection.surface().identifier();
0416 const auto &next_truth_inters =
0417 truth_trace.at(next_idx).intersection.surface().identifier();
0418
0419 return ((current_nav_inters == next_truth_inters) &&
0420 (next_nav_inters == current_truth_inters));
0421 } else {
0422 return false;
0423 }
0424 };
0425
0426
0427
0428 for (long i = 0; i < static_cast<long>(max_entries); ++i) {
0429
0430 const auto idx{static_cast<std::size_t>(i)};
0431
0432
0433
0434
0435 const bool nav_has_next = (idx < recorded_trace.size());
0436 detray::geometry::identifier nav_inters{};
0437 if (nav_has_next) {
0438 nav_inters = recorded_trace.at(idx).intersection.surface().identifier();
0439 }
0440
0441 const bool truth_has_next = (idx < truth_trace.size());
0442 detray::geometry::identifier truth_inters{};
0443 if (truth_has_next) {
0444 truth_inters = truth_trace.at(idx).intersection.surface().identifier();
0445 }
0446
0447
0448 bool found_same_surfaces =
0449 (nav_has_next && truth_has_next && (nav_inters == truth_inters));
0450
0451 matching_traces = matching_traces && found_same_surfaces;
0452
0453 if (!found_same_surfaces) {
0454
0455
0456 long missed_pt_nav{0};
0457 long missed_sn_nav{0};
0458 long missed_ps_nav{0};
0459
0460
0461 long missed_pt_tr{0};
0462 long missed_sn_tr{0};
0463 long missed_ps_tr{0};
0464
0465
0466 auto count_one_missed = []<typename insers_t>(
0467 const insers_t &intr, long int &missed_pt,
0468 long int &missed_sn, long int &missed_ps) {
0469 switch (intr.id()) {
0470 using enum surface_id;
0471 case e_portal: {
0472 missed_pt++;
0473 break;
0474 }
0475 case e_sensitive: {
0476 missed_sn++;
0477 break;
0478 }
0479 case e_passive: {
0480 missed_ps++;
0481 break;
0482 }
0483 default: {
0484 throw std::runtime_error("Unknown surface type during counting");
0485 }
0486 }
0487 };
0488
0489
0490 auto compare_and_equalize =
0491 [&i, &handle_counting_error, &is_swapped_surfaces, &count_one_missed,
0492 &missed_intersections]<typename trace_t, typename other_trace_t>(
0493 trace_t &trace, typename trace_t::iterator last_missed_itr,
0494 other_trace_t &other_trace, surface_stats &missed_stats,
0495 long int &missed_pt, long int &missed_sn, long int &missed_ps) {
0496
0497 auto first_missed = std::begin(trace) + i;
0498 const auto n_check{std::distance(first_missed, last_missed_itr)};
0499 assert(n_check > 0);
0500
0501
0502
0503 for (long j = i; j < i + n_check; ++j) {
0504 const auto &sfi =
0505 trace.at(static_cast<std::size_t>(j)).intersection;
0506
0507
0508
0509 if (sfi.surface().is_portal() && is_swapped_surfaces(j)) {
0510 ++j;
0511 continue;
0512 }
0513
0514 missed_intersections.push_back(sfi);
0515
0516 using record_t = typename other_trace_t::value_type;
0517 other_trace.insert(other_trace.begin() + i, record_t{});
0518
0519
0520 const bool valid{missed_stats.count(sfi.surface())};
0521 handle_counting_error(valid);
0522
0523
0524 count_one_missed(sfi.surface(), missed_pt, missed_sn, missed_ps);
0525 }
0526
0527 assert(missed_pt >= 0);
0528 assert(missed_sn >= 0);
0529 assert(missed_ps >= 0);
0530
0531 const long n_missed{missed_pt + missed_sn + missed_ps};
0532
0533 assert(n_missed > 0);
0534
0535
0536 i += (n_missed - 1);
0537 };
0538
0539
0540
0541
0542
0543
0544 auto search_nav_on_truth = [nav_inters](const truth_record_t &tr) {
0545 return tr.intersection.surface().identifier() == nav_inters;
0546 };
0547
0548
0549 auto search_truth_on_nav = [truth_inters](const nav_record_t &nr) {
0550 return nr.intersection.surface().identifier() == truth_inters;
0551 };
0552
0553
0554
0555
0556 if (is_swapped_surfaces(i)) {
0557
0558 matching_traces = true;
0559 if (!recorded_trace.at(idx).intersection.surface().is_portal() ||
0560 !truth_trace.at(idx).intersection.surface().is_portal()) {
0561 DETRAY_WARN_HOST(
0562 "Track " << trk_no << ", inters. " << idx
0563 << ": Surfaces are swapped in trace, "
0564 "possibly due to overlaps/large mask tolerances.");
0565 }
0566
0567 ++i;
0568 } else if (auto last_missed_by_nav = std::ranges::find_if(
0569 std::ranges::begin(truth_trace) + i,
0570 std::ranges::end(truth_trace), search_nav_on_truth);
0571 last_missed_by_nav != std::end(truth_trace)) {
0572
0573 compare_and_equalize(truth_trace, last_missed_by_nav, recorded_trace,
0574 missed_stats_nav, missed_pt_nav, missed_sn_nav,
0575 missed_ps_nav);
0576
0577 } else if (auto last_missed_by_tr = std::ranges::find_if(
0578 std::ranges::begin(recorded_trace) + i,
0579 std::ranges::end(recorded_trace), search_truth_on_nav);
0580 last_missed_by_tr != std::end(recorded_trace)) {
0581
0582 compare_and_equalize(recorded_trace, last_missed_by_tr, truth_trace,
0583 missed_stats_tr, missed_pt_tr, missed_sn_tr,
0584 missed_ps_tr);
0585
0586 } else if (!truth_has_next) {
0587
0588
0589
0590 truth_trace.push_back(truth_record_t{});
0591
0592 const bool valid{missed_stats_tr.count(nav_inters)};
0593 handle_counting_error(valid);
0594 if (valid) {
0595
0596 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
0597 missed_ps_tr);
0598 }
0599 } else if (!nav_has_next) {
0600
0601
0602
0603 recorded_trace.push_back(nav_record_t{});
0604
0605 const bool valid{missed_stats_nav.count(truth_inters)};
0606 handle_counting_error(valid);
0607
0608 if (valid) {
0609
0610 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
0611 missed_ps_nav);
0612 }
0613 } else {
0614
0615
0616 bool valid{missed_stats_tr.count(nav_inters)};
0617 valid = valid && missed_stats_nav.count(truth_inters);
0618 handle_counting_error(valid);
0619
0620 if (valid) {
0621
0622 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
0623 missed_ps_nav);
0624
0625 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
0626 missed_ps_tr);
0627 }
0628 }
0629
0630
0631 auto print_err_extra_sf = [&matching_stream, max_entries, i](
0632 const std::string &sf_type, long n_sf) {
0633 matching_stream << "\n -> Detray navigator found " << n_sf
0634 << " additional " << sf_type << "(s) at: " << i << "/"
0635 << max_entries << " (Inserted dummy record(s))";
0636 };
0637
0638 auto print_err_missed = [&matching_stream, max_entries, i](
0639 const std::string &sf_type, long n_sf) {
0640 matching_stream << "\n -> Detray navigator missed " << n_sf << " "
0641 << sf_type << "(s) at: " << i << "/" << max_entries
0642 << ": "
0643 << " (Inserted dummy record(s))";
0644 };
0645
0646 if (missed_pt_tr > 0) {
0647 print_err_extra_sf("portal", missed_pt_tr);
0648 }
0649 if (missed_sn_tr > 0) {
0650 print_err_extra_sf("sensitive", missed_sn_tr);
0651 }
0652 if (missed_ps_tr > 0) {
0653 print_err_extra_sf("passive", missed_ps_tr);
0654 }
0655
0656 if (missed_pt_nav > 0) {
0657 print_err_missed("portal", missed_pt_nav);
0658 }
0659 if (missed_sn_nav > 0) {
0660 print_err_missed("sensitive", missed_sn_nav);
0661 }
0662 if (missed_ps_nav > 0) {
0663 print_err_missed("passive", missed_ps_nav);
0664 }
0665
0666
0667
0668 assert(matching_traces ||
0669 (missed_pt_tr + missed_sn_tr + missed_ps_tr + missed_pt_nav +
0670 missed_sn_nav + missed_ps_nav >
0671 0));
0672 }
0673
0674
0675 max_entries = math::max(recorded_trace.size(), truth_trace.size());
0676 }
0677
0678 matching_stream << "\n\n -> Detray navigator skipped "
0679 << missed_stats_nav.n_total() << " surface(s) and found "
0680 << missed_stats_tr.n_total() << " extra surface(s).\n";
0681
0682
0683 debug_stream << std::left;
0684 for (std::size_t intr_idx = 0u; intr_idx < max_entries; ++intr_idx) {
0685 debug_stream << " -------Intersection ( " << intr_idx << " )\n";
0686 if (intr_idx < truth_trace.size()) {
0687 debug_stream << std::setw(20) << "\n Reference: "
0688 << truth_trace.at(intr_idx).intersection << ", vol id: "
0689 << truth_trace.at(intr_idx).intersection.surface().volume()
0690 << std::endl;
0691 } else {
0692 debug_stream << "\n Reference: -" << std::endl;
0693 }
0694 if (intr_idx < recorded_trace.size()) {
0695 debug_stream << std::setw(20) << "\n Detray navigator: "
0696 << recorded_trace.at(intr_idx).intersection << std::endl
0697 << std::endl;
0698 } else {
0699 debug_stream << "\n Detray navigator: -\n" << std::endl;
0700 }
0701 }
0702
0703
0704 const bool any_error{(missed_stats_nav.n_total() != 0u) ||
0705 (missed_stats_tr.n_total() != 0u) || (n_errors != 0u)};
0706
0707
0708 if (any_error) {
0709 if (cfg.verbose()) {
0710 DETRAY_ERROR_HOST("--------\n" << matching_stream.str());
0711 }
0712 if (debug_file) {
0713 *debug_file << "\n>>>>>>>>>>>>>>>>>>\nFAILURE\n<<<<<<<<<<<<<<<<<<\n"
0714 << "\nSUMMARY:\n--------\n"
0715 << "Track no. " << trk_no << "/" << cfg.n_tracks() << ":\n"
0716 << traj << matching_stream.str() << "\n--------\n"
0717 << "\nFull Trace:\n\n"
0718 << debug_stream.str();
0719 }
0720 }
0721
0722
0723 if (n_errors != 0u) {
0724 std::stringstream err_str{};
0725 err_str << "Errors during matching: " << n_errors << "\n"
0726 << matching_stream.str();
0727
0728 DETRAY_FATAL_HOST(err_str.str());
0729 throw std::runtime_error(err_str.str());
0730 }
0731
0732
0733 const bool is_size{recorded_trace.size() == truth_trace.size()};
0734 if (!is_size) {
0735 std::stringstream err_str{};
0736 err_str << "Intersection traces have different number "
0737 "of surfaces after matching! Please check unmatched elements\n"
0738 << " -> Truth: " << truth_trace.size()
0739 << "\n -> Nav. : " << recorded_trace.size() << "\n"
0740 << debug_stream.str();
0741
0742 DETRAY_FATAL_HOST(err_str.str());
0743 throw std::runtime_error(err_str.str());
0744 }
0745
0746
0747
0748 if ((missed_stats_nav.n_total() > 1u) && cfg.verbose()) {
0749 DETRAY_WARN_HOST("Detray navigator skipped multiple surfaces: "
0750 << missed_stats_nav.n_total() << "\n");
0751 }
0752 if ((missed_stats_tr.n_total() > 1u) && cfg.verbose()) {
0753 DETRAY_WARN_HOST("Detray navigator found multiple extra surfaces: "
0754 << missed_stats_tr.n_total() << "\n");
0755 }
0756
0757
0758 if (!matching_traces) {
0759 bool is_counting_error{false};
0760
0761 if (missed_stats_nav.n_total() + missed_stats_tr.n_total() == 0) {
0762 is_counting_error = true;
0763
0764 const std::string msg{
0765 "Difference to truth trace was not counted correctly"};
0766 if (cfg.fail_on_diff()) {
0767 DETRAY_FATAL_HOST("" << msg);
0768 throw std::runtime_error(msg);
0769 } else {
0770 DETRAY_ERROR_HOST("" << msg);
0771 }
0772 }
0773
0774
0775 std::size_t n_miss_truth{0u};
0776 std::size_t n_miss_nav{0u};
0777 for (std::size_t i = 0u; i < truth_trace.size(); ++i) {
0778 const auto truth_desc{truth_trace.at(i).intersection.surface()};
0779 const auto nav_desc{recorded_trace.at(i).intersection.surface()};
0780
0781 if (truth_desc.identifier().is_invalid()) {
0782 n_miss_truth++;
0783 }
0784 if (nav_desc.identifier().is_invalid()) {
0785 n_miss_nav++;
0786 }
0787 if (truth_desc.identifier() != nav_desc.identifier() &&
0788 !(truth_desc.identifier().is_invalid() ||
0789 nav_desc.identifier().is_invalid())) {
0790
0791 if (!is_swapped_surfaces(static_cast<long>(i))) {
0792 n_miss_truth++;
0793 n_miss_nav++;
0794 } else {
0795
0796
0797 ++i;
0798 }
0799 }
0800 }
0801
0802 if (n_miss_truth != missed_stats_tr.n_total()) {
0803 is_counting_error = true;
0804 const std::string msg{
0805 "Missed truth surfaces not counted correctly. Was " +
0806 std::to_string(missed_stats_tr.n_total()) + ", should be " +
0807 std::to_string(n_miss_truth)};
0808 if (cfg.fail_on_diff()) {
0809 DETRAY_FATAL_HOST("" << msg);
0810 throw std::runtime_error(msg);
0811 } else {
0812 DETRAY_ERROR_HOST("" << msg);
0813 }
0814 }
0815 if (n_miss_nav != missed_stats_nav.n_total()) {
0816 is_counting_error = true;
0817 const std::string msg{
0818 "Missed navigation surfaces not counted correctly. Was " +
0819 std::to_string(missed_stats_nav.n_total()) + ", should be " +
0820 std::to_string(n_miss_nav)};
0821 if (cfg.fail_on_diff()) {
0822 DETRAY_FATAL_HOST("" << msg);
0823 throw std::runtime_error(msg);
0824 } else {
0825 DETRAY_ERROR_HOST("" << msg);
0826 }
0827 }
0828
0829 if (is_counting_error) {
0830 n_errors++;
0831
0832 if (debug_file) {
0833 *debug_file << "\n>>>>>>>>>>>>>>>>>>\nCOUNTING "
0834 "ERROR\n<<<<<<<<<<<<<<<<<<\n"
0835 << "\nSUMMARY:\n--------\n"
0836 << "Track no. " << trk_no << "/" << cfg.n_tracks() << ":\n"
0837 << traj << matching_stream.str() << "\n--------\n"
0838 << "\nFull Trace:\n\n"
0839 << debug_stream.str();
0840 }
0841 }
0842 }
0843
0844 const bool success{!any_error && is_size};
0845 return std::make_tuple(success, missed_stats_nav, missed_stats_tr, n_errors,
0846 missed_intersections);
0847 }
0848
0849
0850
0851 template <typename record_t>
0852 auto write_tracks(const std::string &track_param_file_name,
0853 const dvector<dvector<record_t>> &intersection_traces) {
0854 using algebra_t = typename record_t::algebra_type;
0855 using scalar_t = dscalar<algebra_t>;
0856 using track_param_t = free_track_parameters<algebra_t>;
0857
0858 std::vector<std::vector<std::pair<scalar_t, track_param_t>>> track_params{};
0859
0860 for (const auto &trace : intersection_traces) {
0861 track_params.push_back({});
0862 track_params.back().reserve(trace.size());
0863
0864 for (const auto &record : trace) {
0865 track_params.back().emplace_back(
0866 record.charge,
0867 track_param_t{record.pos, 0.f, record.p_mag * record.dir,
0868 record.charge});
0869 }
0870 }
0871
0872
0873 io::csv::write_free_track_params(track_param_file_name, track_params);
0874 }
0875
0876
0877
0878 template <typename detector_t, typename track_t, typename intersection_t>
0879 auto write_dist_to_boundary(
0880 const detector_t &det, const typename detector_t::name_map &names,
0881 const std::string &file_name,
0882 const std::vector<std::pair<track_t, std::vector<intersection_t>>>
0883 &missed_intersections) {
0884 typename detector_t::geometry_context gctx{};
0885
0886
0887 std::ios_base::openmode io_mode = std::ios::trunc | std::ios::out;
0888 detray::io::file_handle dist_file{file_name, io_mode};
0889 *dist_file << "track_id,volume_id,volume_name,phi,eta,path,dist,inside_wo_"
0890 "tol,sf_type"
0891 << std::endl;
0892
0893 for (const auto &[i, entry] :
0894 detray::views::enumerate(missed_intersections)) {
0895 const auto &missed_inters_vec = entry.second;
0896
0897 for (const auto &missed_sfi : missed_inters_vec) {
0898 const auto &track = entry.first;
0899 const auto sf = geometry::surface{det, missed_sfi.surface().identifier()};
0900 const auto vol = tracking_volume{det, sf.volume()};
0901
0902 const auto dist =
0903 sf.template visit_mask<min_dist_to_boundary>(missed_sfi.local());
0904 const auto glob_pos = sf.local_to_global(gctx, missed_sfi.local(),
0905 track.dir(missed_sfi.path()));
0906
0907 *dist_file << i << "," << sf.volume() << ", " << vol.name(names) << ","
0908 << vector::phi(glob_pos) << ", " << vector::eta(glob_pos)
0909 << "," << missed_sfi.path() << ", " << dist << ", "
0910 << std::boolalpha << sf.is_inside(missed_sfi.local(), 0.f)
0911 << ", " << static_cast<int>(sf.shape_id()) << std::endl;
0912 }
0913 }
0914 }
0915
0916
0917 inline auto print_efficiency(std::size_t n_tracks,
0918 const surface_stats &n_surfaces,
0919 const surface_stats &n_miss_nav,
0920 const surface_stats &n_miss_truth,
0921 std::size_t n_fatal_error,
0922 std::size_t n_matching_error) {
0923
0924 constexpr int cw{20};
0925
0926
0927 if (n_miss_nav.n_total() > 0u || n_miss_truth.n_total() > 0u ||
0928 n_fatal_error > 0u || n_matching_error > 0u) {
0929 std::clog << std::left << "-----------------------------------"
0930 << "Error Statistic:"
0931 << "\nTotal number of tracks: " << n_tracks
0932 << "\n\nTotal number of surfaces: " << n_surfaces.n_total()
0933 << std::setw(cw) << "\n portals: " << n_surfaces.n_portals
0934 << std::setw(cw)
0935 << "\n sensitives: " << n_surfaces.n_sensitives
0936 << std::setw(cw) << "\n passives: " << n_surfaces.n_passives
0937 << "\n\n -> missed by navigator: " << n_miss_nav.n_total()
0938 << std::setw(cw) << "\n portals: " << n_miss_nav.n_portals
0939 << std::setw(cw)
0940 << "\n sensitives: " << n_miss_nav.n_sensitives
0941 << std::setw(cw) << "\n passives: " << n_miss_nav.n_passives
0942 << "\n\n -> found in add. by navigator: "
0943 << n_miss_truth.n_total() << std::setw(cw)
0944 << "\n portals: " << n_miss_truth.n_portals << std::setw(cw)
0945 << "\n sensitives: " << n_miss_truth.n_sensitives
0946 << std::setw(cw)
0947 << "\n passives: " << n_miss_truth.n_passives
0948 << "\n\nFatal propagation failures: " << n_fatal_error
0949 << "\nErrors during truth matching: " << n_matching_error;
0950 } else {
0951 std::clog << "-----------------------------------\n"
0952 << "Tested " << n_tracks << " tracks: OK\n"
0953 << "total number of surfaces: " << n_surfaces.n_total();
0954 }
0955
0956 assert(n_miss_nav.n_total() <= n_surfaces.n_total());
0957 assert(n_miss_nav.n_portals <= n_surfaces.n_portals);
0958 assert(n_miss_nav.n_sensitives <= n_surfaces.n_sensitives);
0959 assert(n_miss_nav.n_passives <= n_surfaces.n_passives);
0960
0961
0962 auto print_eff = [&n_surfaces](const std::string &sf_type,
0963 const std::size_t n_sf,
0964 const std::size_t n_missed) {
0965
0966 const auto n_sig{
0967 2 + static_cast<int>(math::ceil(math::log10(n_surfaces.n_total())))};
0968
0969 const auto k{static_cast<double>(n_sf - n_missed)};
0970 const auto n{static_cast<double>(n_sf)};
0971
0972
0973 const auto eff{k / n};
0974
0975
0976
0977 const double var_bayesian{(k + 1.) * (k + 2.) / ((n + 2.) * (n + 3.)) -
0978 std::pow((k + 1.), 2) / std::pow((n + 2.), 2)};
0979
0980
0981 std::clog << "\n"
0982 << sf_type << " finding eff.: " << std::fixed
0983 << std::setprecision(n_sig) << 100. * eff << " \u00b1 "
0984 << 100. * math::sqrt(var_bayesian) << "%";
0985 };
0986
0987 std::clog << std::endl;
0988 if (n_surfaces.n_portals != 0u) {
0989 print_eff("Portal sf.", n_surfaces.n_portals, n_miss_nav.n_portals);
0990 }
0991 if (n_surfaces.n_sensitives != 0u) {
0992 print_eff("Sensitive sf.", n_surfaces.n_sensitives,
0993 n_miss_nav.n_sensitives);
0994 }
0995 if (n_surfaces.n_passives != 0u) {
0996 print_eff("Passive sf.", n_surfaces.n_passives, n_miss_nav.n_passives);
0997 }
0998
0999 std::clog << std::endl;
1000 if (n_surfaces.n_total() != 0u) {
1001 print_eff("Surface", n_surfaces.n_total(), n_miss_nav.n_total());
1002 } else {
1003 DETRAY_ERROR_HOST("No surfaces found in truth data!");
1004 }
1005
1006 std::clog << "\n-----------------------------------\n" << std::endl;
1007 }
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035 template <typename stepper_t, typename... actor_ts, typename detector_t,
1036 typename field_view_t, typename intersection_t,
1037 concepts::algebra algebra_t>
1038 auto compare_to_navigation(
1039 const detray::test::navigation_validation_config<algebra_t> &cfg,
1040 vecmem::host_memory_resource &host_mr, const detector_t &det,
1041 const typename detector_t::name_map &names,
1042 const typename detector_t::geometry_context ctx,
1043 const field_view_t field_view, const propagation::config &prop_cfg,
1044 std::vector<dvector<navigation::detail::candidate_record<intersection_t>>>
1045 &truth_traces,
1046 const std::vector<free_track_parameters<algebra_t>> &tracks,
1047 dvector<typename actor_chain<actor_ts...>::state_ref_tuple> state_tuples =
1048 {{}},
1049 const dvector<std::array<dscalar<algebra_t>, e_bound_size>>
1050 &stddevs_per_track = {{0.f}}) {
1051 using scalar_t = dscalar<algebra_t>;
1052
1053 assert(truth_traces.size() == tracks.size());
1054 assert(!state_tuples.empty());
1055 assert(!stddevs_per_track.empty());
1056
1057
1058 std::ios_base::openmode io_mode = std::ios::trunc | std::ios::out;
1059 detray::io::file_handle debug_file{
1060 "navigation_validation_" + cfg.name() + ".txt", io_mode};
1061
1062 const std::size_t n_samples{truth_traces.size()};
1063
1064
1065 std::size_t n_trk_holes{0};
1066 std::size_t n_matching_error{0u};
1067 std::size_t n_fatal_error{0u};
1068
1069
1070 track_statistics trk_stats{};
1071
1072
1073 surface_stats n_surfaces{};
1074
1075 surface_stats n_miss_nav{};
1076
1077 surface_stats n_miss_truth{};
1078
1079
1080 std::vector<dvector<detail::step_data<algebra_t>>> step_traces{};
1081 std::vector<material_validator::material_record<scalar_t>> mat_records{};
1082 std::vector<dvector<material_validator::material_params<scalar_t>>>
1083 mat_traces{};
1084
1085 mat_records.reserve(n_samples);
1086 mat_traces.reserve(n_samples);
1087 step_traces.reserve(n_samples);
1088
1089
1090 for (std::size_t i = 0u; i < n_samples; ++i) {
1091 const auto &track = tracks.at(i);
1092 auto &truth_trace = truth_traces.at(i);
1093 auto state_tuple =
1094 state_tuples.size() > i ? state_tuples.at(i) : state_tuples.at(0);
1095 const auto &stddevs = stddevs_per_track.size() > i
1096 ? stddevs_per_track.at(i)
1097 : std::array<scalar_t, e_bound_size>{0.f};
1098
1099 assert(!truth_traces.empty());
1100
1101
1102 auto [success, obj_tracer, step_trace, mat_record, mat_trace, nav_printer,
1103 step_printer] =
1104 record_propagation<stepper_t, actor_ts...>(
1105 ctx, &host_mr, det, prop_cfg, track, cfg.ptc_hypothesis(),
1106 field_view, cfg.navigation_direction(), state_tuple, stddevs);
1107
1108
1109 if (!success) {
1110 std::stringstream strm{};
1111
1112 strm << "Track " << i << ": Propagation aborted! "
1113 << nav_printer.fata_error_msg << std::endl;
1114
1115 DETRAY_FATAL_HOST(strm.str());
1116 *debug_file << "FATAL: " << strm.str();
1117
1118 n_fatal_error++;
1119 }
1120
1121
1122 step_traces.push_back(std::move(step_trace));
1123 mat_records.push_back(std::move(mat_record));
1124 mat_traces.push_back(mat_trace);
1125
1126
1127 using obj_tracer_t = decltype(obj_tracer);
1128 using record_t = typename obj_tracer_t::candidate_record_t;
1129
1130 detray::dvector<record_t> recorded_trace{};
1131 recorded_trace.reserve(obj_tracer.trace().size());
1132
1133 if (cfg.collect_sensitives_only()) {
1134 for (const auto &rec : obj_tracer.trace()) {
1135 if (rec.intersection.surface().is_sensitive()) {
1136 recorded_trace.push_back(rec);
1137 }
1138 }
1139 } else {
1140 std::ranges::copy(obj_tracer.trace(), std::back_inserter(recorded_trace));
1141 }
1142
1143
1144 if (recorded_trace.size() < truth_trace.size()) {
1145 n_trk_holes++;
1146 }
1147
1148
1149 detray::detail::helix ideal_traj{track, field_view};
1150 auto [traces_match, n_miss_trace_nav, n_miss_trace_truth, n_error_trace,
1151 missed_inters] = compare_traces(cfg, truth_trace, recorded_trace,
1152 ideal_traj, i, &(*debug_file));
1153
1154 if (!traces_match || !success) {
1155
1156 *debug_file << "TEST TRACK " << i << ":\n\n"
1157 << nav_printer.to_string() << step_printer.to_string();
1158
1159
1160 if (n_miss_trace_truth.n_total() != 0u ||
1161 n_miss_trace_nav.n_total() != 0u || n_error_trace != 0u) {
1162
1163 if (!cfg.display_only_missed() || (n_miss_trace_nav.n_total() != 0u)) {
1164 detray::detector_scanner::display_error(
1165 ctx, det, names, cfg.name(), ideal_traj, truth_trace,
1166 cfg.svg_style(), i, n_samples, recorded_trace,
1167 {dindex_invalid, dindex_invalid}, cfg.verbose());
1168 }
1169 }
1170 }
1171
1172
1173 if (truth_trace.size() != recorded_trace.size()) {
1174 throw std::runtime_error(
1175 "ERROR: Track " + std::to_string(i) +
1176 ": Trace comparison failed: Trace do not have the same "
1177 "size after intersection matching");
1178 }
1179
1180
1181 if (n_error_trace != 0u) {
1182 throw std::runtime_error(
1183 "FATAL: Track " + std::to_string(i) +
1184 ": Error during track comparison. Please check log "
1185 "files");
1186 }
1187
1188
1189 n_miss_nav += n_miss_trace_nav;
1190 n_miss_truth += n_miss_trace_truth;
1191 n_matching_error += n_error_trace;
1192
1193
1194 for (std::size_t j = 0; j < truth_trace.size(); ++j) {
1195 n_surfaces.count(truth_trace[j].intersection.surface(), cfg.verbose());
1196 }
1197
1198
1199 if (n_miss_trace_nav.n_sensitives != 0u) {
1200 trk_stats.n_tracks_w_holes++;
1201 }
1202 if (n_error_trace == 0u && n_miss_trace_nav.n_total() == 0u &&
1203 n_miss_trace_truth.n_total() == 0u) {
1204 trk_stats.n_good_tracks++;
1205 }
1206
1207 if (n_miss_trace_truth.n_total() != 0u) {
1208 trk_stats.n_tracks_w_extra++;
1209 }
1210
1211
1212 trk_stats.n_max_missed_per_trk = math::max(trk_stats.n_max_missed_per_trk,
1213 n_miss_trace_nav.n_sensitives);
1214 trk_stats.n_max_extra_per_trk =
1215 math::max(trk_stats.n_max_extra_per_trk, n_miss_trace_truth.n_total());
1216
1217
1218 trk_stats.n_tracks++;
1219 }
1220
1221
1222 const auto n_tracks{static_cast<double>(trk_stats.n_tracks)};
1223 const auto n_tracks_w_holes{static_cast<double>(trk_stats.n_tracks_w_holes)};
1224 const auto n_tracks_w_extra{static_cast<double>(trk_stats.n_tracks_w_extra)};
1225 const auto n_good_tracks{static_cast<double>(trk_stats.n_good_tracks)};
1226 const auto n_max_holes_per_trk{
1227 static_cast<double>(trk_stats.n_max_missed_per_trk)};
1228 const auto n_max_extra_per_trk{
1229 static_cast<double>(trk_stats.n_max_extra_per_trk)};
1230
1231
1232 if (static_cast<double>(n_trk_holes) > n_tracks_w_holes) {
1233 throw std::runtime_error("Number of tracks with holes is underestimated!");
1234 }
1235
1236
1237 if (cfg.verbose()) {
1238 print_efficiency(trk_stats.n_tracks, n_surfaces, n_miss_nav, n_miss_truth,
1239 n_fatal_error, n_matching_error);
1240 }
1241
1242
1243 constexpr int cw{35};
1244
1245 std::clog << std::left << std::setw(cw)
1246 << "No. Tracks with miss. surfaces: " << n_tracks_w_holes << "/"
1247 << n_tracks << " (" << 100. * n_tracks_w_holes / n_tracks << "%)"
1248 << std::endl;
1249 std::clog << std::left << std::setw(cw)
1250 << "No. Tracks with add. surfaces: " << n_tracks_w_extra << "/"
1251 << n_tracks << " (" << 100. * n_tracks_w_extra / n_tracks << "%)"
1252 << std::endl;
1253 std::clog << std::left << std::setw(cw)
1254 << "No. Good Tracks (exact match): " << n_good_tracks << "/"
1255 << n_tracks << " (" << 100. * n_good_tracks / n_tracks << "%)\n"
1256 << std::endl;
1257 std::clog << std::left << std::setw(cw + 5)
1258 << "Max no. of miss. surfaces per track: " << n_max_holes_per_trk
1259 << " (Mean: "
1260 << ((n_tracks_w_holes == 0)
1261 ? "-"
1262 : std::to_string(
1263 static_cast<double>(n_miss_nav.n_sensitives) /
1264 n_tracks_w_holes))
1265 << ")" << std::endl;
1266 std::clog << std::left << std::setw(cw + 5)
1267 << "Max no. of add. surfaces per track: " << n_max_extra_per_trk
1268 << " (Mean: "
1269 << ((n_tracks_w_extra == 0)
1270 ? "-"
1271 : std::to_string(
1272 static_cast<double>(n_miss_truth.n_total()) /
1273 n_tracks_w_extra))
1274 << ")" << std::endl;
1275 std::clog << "\n-----------------------------------" << std::endl;
1276
1277 return std::tuple{trk_stats, n_surfaces, n_miss_nav, n_miss_truth,
1278 step_traces, mat_traces, mat_records};
1279 }
1280
1281 }