12#include "detray/core/concepts.hpp"
13#include "detray/definitions/track_parametrization.hpp"
14#include "detray/geometry/identifier.hpp"
15#include "detray/geometry/surface.hpp"
16#include "detray/navigation/caching_navigator.hpp"
17#include "detray/propagator/actor_chain.hpp"
18#include "detray/propagator/actors/aborters.hpp"
19#include "detray/propagator/propagator.hpp"
20#include "detray/tracks/free_track_parameters.hpp"
21#include "detray/tracks/helix.hpp"
22#include "detray/utils/logging.hpp"
25#include "detray/io/csv/intersection2D.hpp"
26#include "detray/io/csv/track_parameters.hpp"
27#include "detray/io/utils/file_handle.hpp"
30#include "detray/test/utils/inspectors.hpp"
31#include "detray/test/utils/step_tracer.hpp"
32#include "detray/test/validation/detector_scan_utils.hpp"
33#include "detray/test/validation/material_validation_utils.hpp"
34#include "detray/test/validation/navigation_validation_config.hpp"
37#include "detray/plugins/svgtools/styling/styling.hpp"
40#include <vecmem/memory/host_memory_resource.hpp>
51namespace detray::navigation_validator {
60 template <
typename mask_group_t,
typename idx_range_t,
typename po
int_t>
61 DETRAY_HOST_DEVICE
inline auto operator()(
const mask_group_t &mask_group,
62 const idx_range_t idx_range,
63 const point_t &loc_p)
const {
64 using scalar_t =
typename mask_group_t::value_type::scalar_type;
66 scalar_t min_dist{detail::invalid_value<scalar_t>()};
96 template <
typename sf_descriptor_t>
97 bool count(
const sf_descriptor_t &sf_desc,
const bool verbose =
true) {
98 return count(sf_desc.identifier(), verbose);
106 switch (geo_id.
id()) {
122 DETRAY_ERROR_HOST(
"Surface type unknown " << geo_id);
159template <
typename stepper_t,
typename... actor_ts,
161inline auto record_propagation(
162 const typename detector_t::geometry_context ctx,
163 vecmem::memory_resource *host_mr,
const detector_t &det,
168 const std::optional<bfield_t> &bfield = {},
169 const navigation::direction nav_dir = navigation::direction::e_forward,
170 typename actor_chain<actor_ts...>::state_ref_tuple state_tuple = {}) {
171 using algebra_t =
typename detector_t::algebra_type;
175 using intersection_t = intersection2D<
typename detector_t::surface_type,
176 algebra_t, intersection::contains_pos>;
179 using object_tracer_t =
180 navigation::object_tracer<detector_t,
dvector,
181 navigation::status::e_on_object,
182 navigation::status::e_on_portal>;
185 using nav_print_inspector_t = navigation::print_inspector;
188 aggregate_inspector<object_tracer_t, nav_print_inspector_t>;
192 caching_navigator<detector_t, navigation::default_cache_size, inspector_t,
196 using step_tracer_t = actor::step_tracer<algebra_t, dvector>;
197 using pathlimit_aborter_t = actor::pathlimit_aborter<scalar_t>;
198 using material_tracer_t = material_validator::material_tracer<dvector>;
200 using state_ref_tuple_t =
typename actor_chain<actor_ts...>::state_ref_tuple;
201 using actor_chain_t = actor_chain<pathlimit_aborter_t, actor_ts...,
202 step_tracer_t, material_tracer_t>;
203 using propagator_t = propagator<stepper_t, navigator_t, actor_chain_t>;
206 propagator_t prop{cfg};
209 using updater_state_t = actor::parameter_updater_state<algebra_t>;
210 static constexpr bool has_param_transport{
211 detail::has_type_v<updater_state_t, typename actor_chain_t::state_tuple>};
213 typename pathlimit_aborter_t::state pathlimit_aborter_state{
215 typename step_tracer_t::state step_tracer_state{*host_mr};
216 typename material_tracer_t::state mat_tracer_state{*host_mr};
219 auto setup_actor_states = []<std::size_t... indices>(
220 typename pathlimit_aborter_t::state &s1,
221 typename step_tracer_t::state &s2,
222 typename material_tracer_t::state &s3,
223 state_ref_tuple_t &t,
224 std::index_sequence<indices...> ) {
225 return detray::tie(s1, detail::get<indices>(t)..., s2, s3);
227 constexpr auto idx_seq{
228 std::make_index_sequence<detail::tuple_size_v<state_ref_tuple_t>>{}};
231 setup_actor_states(pathlimit_aborter_state, step_tracer_state,
232 mat_tracer_state, state_tuple, idx_seq);
234 std::unique_ptr<typename propagator_t::state> propagation{
nullptr};
235 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
237 std::make_unique<typename propagator_t::state>(track, det, ctx);
239 if (!bfield.has_value()) {
240 DETRAY_FATAL_HOST(
"No bfield passed for RKN stepper!");
241 std::exit(EXIT_FAILURE);
243 propagation = std::make_unique<typename propagator_t::state>(track, *bfield,
248 auto &nav_inspector = propagation->navigation().inspector();
253 auto &step_printer = propagation->stepping().inspector();
257 if (nav_dir == navigation::direction::e_backward) {
258 using fw_actor_chain_t = actor_chain<actor_ts..., pathlimit_aborter_t>;
259 using fw_propagator_t =
260 propagator<stepper_t, navigator_t, fw_actor_chain_t>;
262 std::unique_ptr<typename fw_propagator_t::state> fw_propagation{
nullptr};
263 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
265 std::make_unique<typename fw_propagator_t::state>(track, det, ctx);
267 if (!bfield.has_value()) {
268 DETRAY_FATAL_HOST(
"No bfield passed for RKN stepper!");
269 std::exit(EXIT_FAILURE);
271 fw_propagation = std::make_unique<typename fw_propagator_t::state>(
272 track, *bfield, det, ctx);
277 auto copy_actor_states = []<std::size_t... indices>(
278 typename pathlimit_aborter_t::state &s1,
279 state_ref_tuple_t &t,
280 std::index_sequence<indices...> ) {
281 using fw_state_tuple_t =
typename fw_actor_chain_t::state_tuple;
282 return fw_state_tuple_t{
285 detail::tuple_element_t<indices + 1u, fw_state_tuple_t> &>(t)...};
289 auto setup_fw_actor_states = []<std::size_t... indices>(
290 typename fw_actor_chain_t::state_tuple &t,
291 std::index_sequence<indices...> ) {
295 auto fw_state_tuple =
296 copy_actor_states(pathlimit_aborter_state, state_tuple, idx_seq);
297 constexpr auto fw_idx_seq{std::make_index_sequence<
298 detail::tuple_size_v<
decltype(fw_state_tuple)>>{}};
299 auto fw_actor_states = setup_fw_actor_states(fw_state_tuple, fw_idx_seq);
306 if constexpr (has_param_transport) {
307 auto &updater_state = detail::get<updater_state_t &>(fw_actor_states);
308 updater_state.always_update(
true);
311 const bool fw_success =
312 fw_propagator_t{cfg}.propagate(*fw_propagation, fw_actor_states);
316 "Could not propagate to end of track to prepare backward "
321 propagation->stepping()() = fw_propagation->stepping()();
322 propagation->navigation().set_volume(fw_propagation->navigation().volume());
326 if constexpr (has_param_transport) {
327 auto &updater_state = detail::get<updater_state_t &>(actor_states);
328 updater_state = detail::get<updater_state_t &>(fw_actor_states);
333 bound_track_parameters<algebra_t> &last_bound =
334 updater_state.bound_params();
335 const geometry::identifier last_sf_id{
336 fw_propagation->navigation().geometry_identifier()};
338 if (fw_success && !last_sf_id.is_invalid() &&
339 last_bound.surface_link() != last_sf_id) {
341 "Transporting bound parameters to last surface: " << last_sf_id);
343 "Last bound parameters: " << updater_state.bound_params());
346 detray::actor::parameter_transporter<algebra_t>{}.get_full_jacobian(
347 *fw_propagation, last_bound);
351 detray::detail::transport_covariance_to_bound_impl(
352 old_cov, propagation_step_jacobian, new_cov);
355 last_bound.set_parameter_vector(
356 fw_propagation->navigation().current_surface().free_to_bound_vector(
357 fw_propagation->context(), fw_propagation->stepping()()));
360 last_bound.set_surface_link(last_sf_id);
363 "Updated bound parameters: " << updater_state.bound_params());
369 propagation->navigation().set_direction(nav_dir);
372 bool success = prop.propagate(*propagation, actor_states);
374 return std::make_tuple(success, std::move(obj_tracer),
375 std::move(step_tracer_state).release_step_data(),
376 std::move(mat_tracer_state).release_track_material(),
377 std::move(mat_tracer_state).release_material_steps(),
378 std::move(nav_printer), std::move(step_printer));
399template <concepts::detector detector_t,
typename traj_t,
400 concepts::algebra algebra_t>
402 const detray::test::navigation_validation_config<algebra_t> &cfg,
403 dvector<intersection_record<detector_t>> &truth_trace,
404 dvector<intersection_record<detector_t>> &recorded_trace,
405 const traj_t &traj, std::size_t trk_no,
406 std::fstream *debug_file =
nullptr) {
407 using data_record_t = intersection_record<detector_t>;
408 using intersection_t =
typename data_record_t::intersection_type;
412 std::size_t max_entries{math::max(recorded_trace.size(), truth_trace.size())};
415 std::stringstream debug_stream;
416 std::stringstream matching_stream;
421 bool matching_traces{
true};
422 std::size_t n_errors{0
u};
423 std::vector<intersection_t> missed_intersections{};
426 auto handle_counting_error = [&matching_stream, &n_errors](
const bool is_OK) {
428 matching_stream <<
"FATAL: Encountered surface of "
429 "unknown type in intersection "
430 "trace. Validate the geometry";
437 auto is_swapped_surfaces = [&recorded_trace, &truth_trace](
const long j) {
438 const auto idx_j{
static_cast<std::size_t
>(j)};
439 const std::size_t next_idx{idx_j + 1u};
441 if (next_idx < truth_trace.size() && next_idx < recorded_trace.size()) {
442 const auto ¤t_nav_inters =
443 recorded_trace.at(idx_j).intersection().surface().identifier();
444 const auto ¤t_truth_inters =
445 truth_trace.at(idx_j).intersection().surface().identifier();
447 const auto &next_nav_inters =
448 recorded_trace.at(next_idx).intersection().surface().identifier();
449 const auto &next_truth_inters =
450 truth_trace.at(next_idx).intersection().surface().identifier();
452 return ((current_nav_inters == next_truth_inters) &&
453 (next_nav_inters == current_truth_inters));
461 for (
long i = 0; i < static_cast<long>(max_entries); ++i) {
463 const auto idx{
static_cast<std::size_t
>(i)};
468 const bool nav_has_next = (idx < recorded_trace.size());
469 detray::geometry::identifier nav_inters{};
471 nav_inters = recorded_trace.at(idx).intersection().surface().
identifier();
474 const bool truth_has_next = (idx < truth_trace.size());
475 detray::geometry::identifier truth_inters{};
476 if (truth_has_next) {
477 truth_inters = truth_trace.at(idx).intersection().surface().
identifier();
481 bool found_same_surfaces =
482 (nav_has_next && truth_has_next && (nav_inters == truth_inters));
484 matching_traces = matching_traces && found_same_surfaces;
486 if (!found_same_surfaces) {
489 long missed_pt_nav{0};
490 long missed_sn_nav{0};
491 long missed_ps_nav{0};
494 long missed_pt_tr{0};
495 long missed_sn_tr{0};
496 long missed_ps_tr{0};
499 auto count_one_missed = []<
typename insers_t>(
500 const insers_t &intr,
long int &missed_pt,
501 long int &missed_sn,
long int &missed_ps) {
517 throw std::runtime_error(
"Unknown surface type during counting");
523 auto compare_and_equalize =
524 [&i, &handle_counting_error, &is_swapped_surfaces, &count_one_missed,
525 &missed_intersections](
527 typename dvector<data_record_t>::iterator last_missed_itr,
529 long int &missed_pt,
long int &missed_sn,
long int &missed_ps) {
531 auto first_missed = std::begin(trace) + i;
532 const auto n_check{std::distance(first_missed, last_missed_itr)};
537 for (
long j = i; j < i + n_check; ++j) {
539 trace.at(
static_cast<std::size_t
>(j)).intersection();
543 if (sfi.surface().is_portal() && is_swapped_surfaces(j)) {
548 missed_intersections.push_back(sfi);
550 other_trace.insert(other_trace.begin() + i, data_record_t{});
553 const bool valid{missed_stats.count(sfi.surface())};
554 handle_counting_error(valid);
557 count_one_missed(sfi.surface(), missed_pt, missed_sn, missed_ps);
560 assert(missed_pt >= 0);
561 assert(missed_sn >= 0);
562 assert(missed_ps >= 0);
564 const long n_missed{missed_pt + missed_sn + missed_ps};
566 assert(n_missed > 0);
577 auto search_nav_on_truth = [nav_inters](
const data_record_t &tr) {
578 return tr.intersection().surface().identifier() == nav_inters;
582 auto search_truth_on_nav = [truth_inters](
const data_record_t &nr) {
583 return nr.intersection().surface().identifier() == truth_inters;
589 if (is_swapped_surfaces(i)) {
591 matching_traces =
true;
592 if (!recorded_trace.at(idx).intersection().surface().is_portal() ||
593 !truth_trace.at(idx).intersection().surface().is_portal()) {
595 "Track " << trk_no <<
", inters. " << idx
596 <<
": Surfaces are swapped in trace, "
597 "possibly due to overlaps/large mask tolerances.");
601 }
else if (
auto last_missed_by_nav = std::ranges::find_if(
602 std::ranges::begin(truth_trace) + i,
603 std::ranges::end(truth_trace), search_nav_on_truth);
604 last_missed_by_nav != std::end(truth_trace)) {
606 compare_and_equalize(truth_trace, last_missed_by_nav, recorded_trace,
607 missed_stats_nav, missed_pt_nav, missed_sn_nav,
610 }
else if (
auto last_missed_by_tr = std::ranges::find_if(
611 std::ranges::begin(recorded_trace) + i,
612 std::ranges::end(recorded_trace), search_truth_on_nav);
613 last_missed_by_tr != std::end(recorded_trace)) {
615 compare_and_equalize(recorded_trace, last_missed_by_tr, truth_trace,
616 missed_stats_tr, missed_pt_tr, missed_sn_tr,
619 }
else if (!truth_has_next) {
623 truth_trace.push_back(data_record_t{});
625 const bool valid{missed_stats_tr.count(nav_inters)};
626 handle_counting_error(valid);
629 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
632 }
else if (!nav_has_next) {
636 recorded_trace.push_back(data_record_t{});
638 const bool valid{missed_stats_nav.count(truth_inters)};
639 handle_counting_error(valid);
643 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
649 bool valid{missed_stats_tr.count(nav_inters)};
650 valid = valid && missed_stats_nav.count(truth_inters);
651 handle_counting_error(valid);
655 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
658 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
664 auto print_err_extra_sf = [&matching_stream, max_entries, i](
665 const std::string &sf_type,
long n_sf) {
666 matching_stream <<
"\n -> Detray navigator found " << n_sf
667 <<
" additional " << sf_type <<
"(s) at: " << i <<
"/"
668 << max_entries <<
" (Inserted dummy record(s))";
671 auto print_err_missed = [&matching_stream, max_entries, i](
672 const std::string &sf_type,
long n_sf) {
673 matching_stream <<
"\n -> Detray navigator missed " << n_sf <<
" "
674 << sf_type <<
"(s) at: " << i <<
"/" << max_entries
676 <<
" (Inserted dummy record(s))";
679 if (missed_pt_tr > 0) {
680 print_err_extra_sf(
"portal", missed_pt_tr);
682 if (missed_sn_tr > 0) {
683 print_err_extra_sf(
"sensitive", missed_sn_tr);
685 if (missed_ps_tr > 0) {
686 print_err_extra_sf(
"passive", missed_ps_tr);
689 if (missed_pt_nav > 0) {
690 print_err_missed(
"portal", missed_pt_nav);
692 if (missed_sn_nav > 0) {
693 print_err_missed(
"sensitive", missed_sn_nav);
695 if (missed_ps_nav > 0) {
696 print_err_missed(
"passive", missed_ps_nav);
701 assert(matching_traces ||
702 (missed_pt_tr + missed_sn_tr + missed_ps_tr + missed_pt_nav +
703 missed_sn_nav + missed_ps_nav >
708 max_entries = math::max(recorded_trace.size(), truth_trace.size());
711 matching_stream <<
"\n\n -> Detray navigator skipped "
712 << missed_stats_nav.n_total() <<
" surface(s) and found "
713 << missed_stats_tr.n_total() <<
" extra surface(s).\n";
716 debug_stream << std::left;
717 for (std::size_t intr_idx = 0u; intr_idx < max_entries; ++intr_idx) {
718 debug_stream <<
" -------Intersection ( " << intr_idx <<
" )\n";
719 if (intr_idx < truth_trace.size()) {
720 debug_stream << std::setw(20) <<
"\n Reference: "
721 << truth_trace.at(intr_idx).intersection() <<
", vol id: "
722 << truth_trace.at(intr_idx).intersection().surface().volume()
725 debug_stream <<
"\n Reference: -" << std::endl;
727 if (intr_idx < recorded_trace.size()) {
728 debug_stream << std::setw(20) <<
"\n Detray navigator: "
729 << recorded_trace.at(intr_idx).intersection() << std::endl
732 debug_stream <<
"\n Detray navigator: -\n" << std::endl;
737 const bool any_error{(missed_stats_nav.n_total() != 0u) ||
738 (missed_stats_tr.n_total() != 0u) || (n_errors != 0
u)};
743 DETRAY_ERROR_HOST(
"--------\n" << matching_stream.str());
746 *debug_file <<
"\n>>>>>>>>>>>>>>>>>>\nFAILURE\n<<<<<<<<<<<<<<<<<<\n"
747 <<
"\nSUMMARY:\n--------\n"
748 <<
"Track no. " << trk_no <<
"/" << cfg.
n_tracks() <<
":\n"
749 << traj << matching_stream.str() <<
"\n--------\n"
750 <<
"\nFull Trace:\n\n"
751 << debug_stream.str();
756 if (n_errors != 0u) {
757 std::stringstream err_str{};
758 err_str <<
"Errors during matching: " << n_errors <<
"\n"
759 << matching_stream.str();
761 DETRAY_FATAL_HOST(err_str.str());
762 throw std::runtime_error(err_str.str());
766 const bool is_size{recorded_trace.size() == truth_trace.size()};
768 std::stringstream err_str{};
769 err_str <<
"Intersection traces have different number "
770 "of surfaces after matching! Please check unmatched elements\n"
771 <<
" -> Truth: " << truth_trace.size()
772 <<
"\n -> Nav. : " << recorded_trace.size() <<
"\n"
773 << debug_stream.str();
775 DETRAY_FATAL_HOST(err_str.str());
776 throw std::runtime_error(err_str.str());
781 if ((missed_stats_nav.n_total() > 1u) && cfg.
verbose()) {
782 DETRAY_WARN_HOST(
"Detray navigator skipped multiple surfaces: "
783 << missed_stats_nav.n_total() <<
"\n");
785 if ((missed_stats_tr.n_total() > 1u) && cfg.
verbose()) {
786 DETRAY_WARN_HOST(
"Detray navigator found multiple extra surfaces: "
787 << missed_stats_tr.n_total() <<
"\n");
791 if (!matching_traces) {
792 bool is_counting_error{
false};
794 if (missed_stats_nav.n_total() + missed_stats_tr.n_total() == 0) {
795 is_counting_error =
true;
797 const std::string msg{
798 "Difference to truth trace was not counted correctly"};
800 DETRAY_FATAL_HOST(
"" << msg);
801 throw std::runtime_error(msg);
803 DETRAY_ERROR_HOST(
"" << msg);
808 std::size_t n_miss_truth{0
u};
809 std::size_t n_miss_nav{0
u};
810 for (std::size_t i = 0u; i < truth_trace.size(); ++i) {
811 const auto truth_desc{truth_trace.at(i).intersection().surface()};
812 const auto nav_desc{recorded_trace.at(i).intersection().surface()};
814 if (truth_desc.identifier().is_invalid()) {
817 if (nav_desc.identifier().is_invalid()) {
820 if (truth_desc.identifier() != nav_desc.identifier() &&
821 !(truth_desc.identifier().is_invalid() ||
822 nav_desc.identifier().is_invalid())) {
824 if (!is_swapped_surfaces(
static_cast<long>(i))) {
835 if (n_miss_truth != missed_stats_tr.n_total()) {
836 is_counting_error =
true;
837 const std::string msg{
838 "Missed truth surfaces not counted correctly. Was " +
839 std::to_string(missed_stats_tr.n_total()) +
", should be " +
840 std::to_string(n_miss_truth)};
842 DETRAY_FATAL_HOST(
"" << msg);
843 throw std::runtime_error(msg);
845 DETRAY_ERROR_HOST(
"" << msg);
848 if (n_miss_nav != missed_stats_nav.n_total()) {
849 is_counting_error =
true;
850 const std::string msg{
851 "Missed navigation surfaces not counted correctly. Was " +
852 std::to_string(missed_stats_nav.n_total()) +
", should be " +
853 std::to_string(n_miss_nav)};
855 DETRAY_FATAL_HOST(
"" << msg);
856 throw std::runtime_error(msg);
858 DETRAY_ERROR_HOST(
"" << msg);
862 if (is_counting_error) {
866 *debug_file <<
"\n>>>>>>>>>>>>>>>>>>\nCOUNTING "
867 "ERROR\n<<<<<<<<<<<<<<<<<<\n"
868 <<
"\nSUMMARY:\n--------\n"
869 <<
"Track no. " << trk_no <<
"/" << cfg.
n_tracks() <<
":\n"
870 << traj << matching_stream.str() <<
"\n--------\n"
871 <<
"\nFull Trace:\n\n"
872 << debug_stream.str();
877 const bool success{!any_error && is_size};
878 return std::make_tuple(success, missed_stats_nav, missed_stats_tr, n_errors,
879 missed_intersections);
884template <concepts::detector detector_t,
typename allocator_t>
885auto write_tracks(
const std::string &track_param_file_name,
886 const std::vector<
dvector<intersection_record<detector_t>>,
887 allocator_t> &intersection_traces) {
888 using algebra_t =
typename detector_t::algebra_type;
893 std::vector<std::vector<std::pair<scalar_t, track_param_t>>> track_params{};
895 for (
const auto &trace : intersection_traces) {
896 track_params.push_back({});
897 track_params.back().reserve(trace.size());
899 for (
const auto &record : trace) {
900 track_params.back().emplace_back(record.template charge<value_t>(),
901 record.track_param());
906 io::csv::write_free_track_params(track_param_file_name, track_params);
915template <concepts::detector detector_t,
typename track_t,
916 typename intersection_t>
917auto write_dist_to_boundary(
918 const detector_t &det,
const typename detector_t::name_map &names,
919 const std::string &file_name,
920 const std::vector<std::pair<track_t, std::vector<intersection_t>>>
921 &missed_intersections) {
922 typename detector_t::geometry_context gctx{};
925 std::ios_base::openmode io_mode = std::ios::trunc | std::ios::out;
926 detray::io::file_handle dist_file{file_name, io_mode};
927 *dist_file <<
"track_id,volume_id,volume_name,phi,eta,path,dist,inside_wo_"
931 for (
const auto &[i, entry] :
932 detray::views::enumerate(missed_intersections)) {
933 const auto &missed_inters_vec = entry.second;
935 for (
const auto &missed_sfi : missed_inters_vec) {
936 const auto &track = entry.first;
937 const auto sf = geometry::surface{det, missed_sfi.surface().identifier()};
938 const auto vol = tracking_volume{det, sf.volume()};
941 sf.template visit_mask<min_dist_to_boundary>(missed_sfi.local());
942 const auto glob_pos = sf.local_to_global(gctx, missed_sfi.local(),
943 track.
dir(missed_sfi.path()));
945 *dist_file << i <<
"," << sf.volume() <<
", " << vol.name(names) <<
","
946 << vector::phi(glob_pos) <<
", " << vector::eta(glob_pos)
947 <<
"," << missed_sfi.path() <<
", " << dist <<
", "
948 << std::boolalpha << sf.is_inside(missed_sfi.local(), 0.f)
949 <<
", " <<
static_cast<int>(sf.shape_id()) << std::endl;
955inline auto print_efficiency(std::size_t n_tracks,
959 std::size_t n_fatal_error,
960 std::size_t n_matching_error) {
962 constexpr int cw{20};
965 if (n_miss_nav.n_total() > 0u || n_miss_truth.n_total() > 0u ||
966 n_fatal_error > 0u || n_matching_error > 0u) {
967 std::clog << std::left <<
"-----------------------------------"
968 <<
"Error Statistic:"
969 <<
"\nTotal number of tracks: " << n_tracks
970 <<
"\n\nTotal number of surfaces: " << n_surfaces.n_total()
971 << std::setw(cw) <<
"\n portals: " << n_surfaces.n_portals
973 <<
"\n sensitives: " << n_surfaces.n_sensitives
974 << std::setw(cw) <<
"\n passives: " << n_surfaces.n_passives
975 <<
"\n\n -> missed by navigator: " << n_miss_nav.n_total()
976 << std::setw(cw) <<
"\n portals: " << n_miss_nav.n_portals
978 <<
"\n sensitives: " << n_miss_nav.n_sensitives
979 << std::setw(cw) <<
"\n passives: " << n_miss_nav.n_passives
980 <<
"\n\n -> found in add. by navigator: "
981 << n_miss_truth.n_total() << std::setw(cw)
982 <<
"\n portals: " << n_miss_truth.n_portals << std::setw(cw)
983 <<
"\n sensitives: " << n_miss_truth.n_sensitives
985 <<
"\n passives: " << n_miss_truth.n_passives
986 <<
"\n\nFatal propagation failures: " << n_fatal_error
987 <<
"\nErrors during truth matching: " << n_matching_error;
989 std::clog <<
"-----------------------------------\n"
990 <<
"Tested " << n_tracks <<
" tracks: OK\n"
991 <<
"total number of surfaces: " << n_surfaces.n_total();
994 assert(n_miss_nav.n_total() <= n_surfaces.n_total());
995 assert(n_miss_nav.n_portals <= n_surfaces.n_portals);
996 assert(n_miss_nav.n_sensitives <= n_surfaces.n_sensitives);
997 assert(n_miss_nav.n_passives <= n_surfaces.n_passives);
1000 auto print_eff = [&n_surfaces](
const std::string &sf_type,
1001 const std::size_t n_sf,
1002 const std::size_t n_missed) {
1005 2 +
static_cast<int>(math::ceil(math::log10(n_surfaces.n_total())))};
1007 const auto k{
static_cast<double>(n_sf - n_missed)};
1008 const auto n{
static_cast<double>(n_sf)};
1011 const auto eff{k / n};
1015 const double var_bayesian{(k + 1.) * (k + 2.) / ((n + 2.) * (n + 3.)) -
1016 std::pow((k + 1.), 2) / std::pow((n + 2.), 2)};
1020 << sf_type <<
" finding eff.: " << std::fixed
1021 << std::setprecision(n_sig) << 100. * eff <<
" \u00b1 "
1022 << 100. * math::sqrt(var_bayesian) <<
"%";
1025 std::clog << std::endl;
1026 if (n_surfaces.n_portals != 0u) {
1027 print_eff(
"Portal sf.", n_surfaces.n_portals, n_miss_nav.n_portals);
1029 if (n_surfaces.n_sensitives != 0u) {
1030 print_eff(
"Sensitive sf.", n_surfaces.n_sensitives,
1031 n_miss_nav.n_sensitives);
1033 if (n_surfaces.n_passives != 0u) {
1034 print_eff(
"Passive sf.", n_surfaces.n_passives, n_miss_nav.n_passives);
1037 std::clog << std::endl;
1038 if (n_surfaces.n_total() != 0u) {
1039 print_eff(
"Surface", n_surfaces.n_total(), n_miss_nav.n_total());
1041 DETRAY_ERROR_HOST(
"No surfaces found in truth data!");
1044 std::clog <<
"\n-----------------------------------\n" << std::endl;
1072template <
typename stepper_t,
typename... actor_ts,
1073 concepts::detector detector_t,
typename field_view_t,
1074 concepts::algebra algebra_t>
1075auto compare_to_navigation(
1076 const detray::test::navigation_validation_config<algebra_t> &cfg,
1077 vecmem::host_memory_resource &host_mr,
const detector_t &det,
1078 const typename detector_t::name_map &names,
1079 const typename detector_t::geometry_context ctx,
1080 const std::optional<field_view_t> field_view,
1081 const propagation::config &prop_cfg,
1082 std::vector<
dvector<intersection_record<detector_t>>> &truth_traces,
1086 assert(truth_traces.size() == tracks.size());
1087 assert(!state_tuples.empty());
1090 std::ios_base::openmode io_mode = std::ios::trunc | std::ios::out;
1091 detray::io::file_handle debug_file{
1092 "navigation_validation_" + cfg.
name() +
".txt", io_mode};
1094 const std::size_t n_samples{truth_traces.size()};
1097 std::size_t n_trk_holes{0};
1098 std::size_t n_matching_error{0
u};
1099 std::size_t n_fatal_error{0
u};
1112 std::vector<dvector<step_record<algebra_t>>> step_traces{};
1113 std::vector<dvector<material_record>> mat_steps{};
1114 std::vector<material_validator::track_material> track_mat_vec{};
1116 mat_steps.reserve(n_samples);
1117 track_mat_vec.reserve(n_samples);
1118 step_traces.reserve(n_samples);
1121 for (std::size_t i = 0u; i < n_samples; ++i) {
1122 const auto &track = tracks.at(i);
1123 auto &truth_trace = truth_traces.at(i);
1125 state_tuples.size() > i ? state_tuples.at(i) : state_tuples.at(0);
1127 assert(!truth_traces.empty());
1129 DETRAY_VERBOSE_HOST(
"Track " << i <<
":");
1132 auto [success, obj_tracer, step_trace, track_mat, mat_trace, nav_printer,
1134 record_propagation<stepper_t, actor_ts...>(
1140 std::stringstream strm{};
1142 strm <<
"Track " << i <<
": Propagation aborted! "
1143 << nav_printer.fata_error_msg << std::endl;
1145 DETRAY_FATAL_HOST(strm.str());
1146 *debug_file <<
"FATAL: " << strm.str();
1152 step_traces.push_back(std::move(step_trace));
1153 mat_steps.push_back(std::move(mat_trace));
1154 track_mat_vec.push_back(track_mat);
1158 recorded_trace.reserve(obj_tracer.trace().size());
1161 for (
const auto &rec : obj_tracer.trace()) {
1162 if (rec.intersection().surface().is_sensitive()) {
1163 recorded_trace.push_back(rec);
1167 std::ranges::copy(obj_tracer.trace(), std::back_inserter(recorded_trace));
1171 if (recorded_trace.size() < truth_trace.size()) {
1176 detray::detail::helix ideal_traj{track, *field_view};
1177 auto [traces_match, n_miss_trace_nav, n_miss_trace_truth, n_error_trace,
1178 missed_inters] = compare_traces(cfg, truth_trace, recorded_trace,
1179 ideal_traj, i, &(*debug_file));
1181 if (!traces_match || !success) {
1183 *debug_file <<
"TEST TRACK " << i <<
":\n\n"
1184 << nav_printer.to_string() << step_printer.to_string();
1187 if (n_miss_trace_truth.n_total() != 0u ||
1188 n_miss_trace_nav.n_total() != 0u || n_error_trace != 0u) {
1191 (n_miss_trace_nav.n_total() != 0u))) {
1193 ctx, det, names, cfg.
name(), ideal_traj, truth_trace,
1194 cfg.
svg_style(), i, n_samples, recorded_trace,
1195 {dindex_invalid, dindex_invalid}, cfg.
verbose());
1201 if (truth_trace.size() != recorded_trace.size()) {
1202 throw std::runtime_error(
1203 "ERROR: Track " + std::to_string(i) +
1204 ": Trace comparison failed: Trace do not have the same "
1205 "size after intersection matching");
1209 if (n_error_trace != 0u) {
1210 throw std::runtime_error(
1211 "FATAL: Track " + std::to_string(i) +
1212 ": Error during track comparison. Please check log "
1217 n_miss_nav += n_miss_trace_nav;
1218 n_miss_truth += n_miss_trace_truth;
1219 n_matching_error += n_error_trace;
1222 for (std::size_t j = 0; j < truth_trace.size(); ++j) {
1223 n_surfaces.count(truth_trace[j].intersection().surface(), cfg.
verbose());
1227 if (n_miss_trace_nav.n_sensitives != 0u) {
1228 trk_stats.n_tracks_w_holes++;
1230 if (n_error_trace == 0u && n_miss_trace_nav.n_total() == 0u &&
1231 n_miss_trace_truth.n_total() == 0u) {
1232 trk_stats.n_good_tracks++;
1235 if (n_miss_trace_truth.n_total() != 0u) {
1236 trk_stats.n_tracks_w_extra++;
1240 trk_stats.n_max_missed_per_trk = math::max(trk_stats.n_max_missed_per_trk,
1241 n_miss_trace_nav.n_sensitives);
1242 trk_stats.n_max_extra_per_trk =
1243 math::max(trk_stats.n_max_extra_per_trk, n_miss_trace_truth.n_total());
1246 trk_stats.n_tracks++;
1250 const auto n_tracks{
static_cast<double>(trk_stats.n_tracks)};
1251 const auto n_tracks_w_holes{
static_cast<double>(trk_stats.n_tracks_w_holes)};
1252 const auto n_tracks_w_extra{
static_cast<double>(trk_stats.n_tracks_w_extra)};
1253 const auto n_good_tracks{
static_cast<double>(trk_stats.n_good_tracks)};
1254 const auto n_max_holes_per_trk{
1255 static_cast<double>(trk_stats.n_max_missed_per_trk)};
1256 const auto n_max_extra_per_trk{
1257 static_cast<double>(trk_stats.n_max_extra_per_trk)};
1260 if (
static_cast<double>(n_trk_holes) > n_tracks_w_holes) {
1261 throw std::runtime_error(
"Number of tracks with holes is underestimated!");
1266 print_efficiency(trk_stats.n_tracks, n_surfaces, n_miss_nav, n_miss_truth,
1267 n_fatal_error, n_matching_error);
1271 constexpr int cw{35};
1273 std::clog << std::left << std::setw(cw)
1274 <<
"No. Tracks with miss. surfaces: " << n_tracks_w_holes <<
"/"
1275 << n_tracks <<
" (" << 100. * n_tracks_w_holes / n_tracks <<
"%)"
1277 std::clog << std::left << std::setw(cw)
1278 <<
"No. Tracks with add. surfaces: " << n_tracks_w_extra <<
"/"
1279 << n_tracks <<
" (" << 100. * n_tracks_w_extra / n_tracks <<
"%)"
1281 std::clog << std::left << std::setw(cw)
1282 <<
"No. Good Tracks (exact match): " << n_good_tracks <<
"/"
1283 << n_tracks <<
" (" << 100. * n_good_tracks / n_tracks <<
"%)\n"
1285 std::clog << std::left << std::setw(cw + 5)
1286 <<
"Max no. of miss. surfaces per track: " << n_max_holes_per_trk
1288 << ((n_tracks_w_holes == 0)
1291 static_cast<double>(n_miss_nav.n_sensitives) /
1293 <<
")" << std::endl;
1294 std::clog << std::left << std::setw(cw + 5)
1295 <<
"Max no. of add. surfaces per track: " << n_max_extra_per_trk
1297 << ((n_tracks_w_extra == 0)
1300 static_cast<double>(n_miss_truth.n_total()) /
1302 <<
")" << std::endl;
1303 std::clog <<
"\n-----------------------------------" << std::endl;
1305 return std::tuple{trk_stats, n_surfaces, n_miss_nav, n_miss_truth,
1306 step_traces, track_mat_vec, mat_steps};
detail::unique_t< detail::tuple_cat_t< detail::state_ref_tuple_t< actors_t >... > > state_ref_tuple
Tuple of state references that is used in the propagator.
Definition actor_chain.hpp:45
Unique identifier for geometry objects, based on the ACTS GeometryIdentifier.
Definition identifier.hpp:36
constexpr surface_id id() const
Definition identifier.hpp:64
constexpr identifier(value_t encoded)
Construct from an already encoded value.
Definition identifier.hpp:46
Mask a region on a surface and link it to a volume.
Definition mask.hpp:46
auto min_dist_to_boundary(const point3_type &loc_p) const -> scalar_type
Find the minimum distance to any boundary.
Definition mask.hpp:292
Check if a type can be used as a detector.
Definition concepts.hpp:47
constexpr double u
atomic mass unit u
Definition Units.hpp:155
void display_error(const typename detector_t::geometry_context gctx, const detector_t &det, const typename detector_t::name_map &vol_names, const std::string &test_name, const trajectory_t &test_track, const dvector< intersection_record< detector_t > > &truth_trace, const detray::svgtools::styling::style &svg_style, const std::size_t i_track, const std::size_t n_tracks, const dvector< intersection_record< detector_t > > &recorded_trace={}, const dindex_range overlap_idx={detray::detail::invalid_value< dindex >(), detray::detail::invalid_value< dindex >()}, const bool verbose=true)
Print the failed intersection trace as svg (dumped to files).
Definition detector_scan_utils.hpp:738
get_scalar_t< A, T > dscalar
Scalar type for algebra A and value type T.
Definition algebra.hpp:64
vecmem::vector< value_t > dvector
Vector container backed by vecmem.
Definition containers.hpp:34
surface_id
surface type, resolved during navigation.
Definition geometry.hpp:41
const auto & get(const detray::tuple< Ts... > &t) noexcept
Get a tuple element by index from a const tuple.
Definition tuple.hpp:152
constexpr detray::tuple< Ts &... > tie(Ts &...args)
Create a tuple of references to the supplied objects.
Definition tuple.hpp:211
dmatrix< algebra_t, e_bound_size, e_bound_size > bound_matrix
Covariance matrix type for bound track parametrization.
Definition track_parametrization.hpp:81
get_value_t< A > dvalue
Value scalar type of the algebra backend.
Definition algebra.hpp:48
constexpr pdg_particle< scalar_t > update_particle_hypothesis(const pdg_particle< scalar_t > &ptc, const track_t ¶ms)
Select the hypothesis matching the sign of the track charge over momentum.
Definition pdg_particle.hpp:78
free_parameters_vector< algebra_t > free_track_parameters
The free track parameters consist only of the parameter vector.
Definition free_track_parameters.hpp:249
vector3_type dir() const
Definition free_track_parameters.hpp:128
Negative muon hypothesis.
Definition pdg_particle.hpp:111
B-field placeholder for straight-line navigation.
Definition navigation_validation_utils.hpp:76
A functor to get the minimum distance to any surface boundary.
Definition navigation_validation_utils.hpp:54
auto operator()(const mask_group_t &mask_group, const idx_range_t idx_range, const point_t &loc_p) const
Find the minimum distance to the boundary among a mask range.
Definition navigation_validation_utils.hpp:61
Struct that keeps track of the number of encountered/skipped surfaces.
Definition navigation_validation_utils.hpp:79
std::size_t n_total() const
The total number of skipped surfaces.
Definition navigation_validation_utils.hpp:89
std::size_t n_sensitives
Number of encountered sensitive surfaces.
Definition navigation_validation_utils.hpp:83
bool count(const sf_descriptor_t &sf_desc, const bool verbose=true)
Count a surface depending on its type.
Definition navigation_validation_utils.hpp:97
std::size_t n_portals
Number of encountered portals.
Definition navigation_validation_utils.hpp:81
bool count(geometry::identifier geo_id, const bool verbose=true)
Count a surface by its geometry identifier.
Definition navigation_validation_utils.hpp:105
surface_stats & operator+=(const surface_stats &other)
Count up.
Definition navigation_validation_utils.hpp:133
std::size_t n_passives
Number of encountered passive surfaces.
Definition navigation_validation_utils.hpp:85
Statistics on tracks with holes and extra surfaces.
Definition navigation_validation_utils.hpp:143
std::size_t n_max_extra_per_trk
Maximum number of additional surfaces on one track.
Definition navigation_validation_utils.hpp:155
std::size_t n_max_missed_per_trk
Maximum number of missed surfaces on one track.
Definition navigation_validation_utils.hpp:153
std::size_t n_good_tracks
Number of tracks that match exactly.
Definition navigation_validation_utils.hpp:151
std::size_t n_tracks
Number of processed tracks.
Definition navigation_validation_utils.hpp:145
std::size_t n_tracks_w_holes
Number of tracks with missing surfaces.
Definition navigation_validation_utils.hpp:147
std::size_t n_tracks_w_extra
Number of tracks with additional surfaces.
Definition navigation_validation_utils.hpp:149
Particle hypothesis identified by PDG number, mass, and electric charge.
Definition pdg_particle.hpp:24
Configuration of the propagation.
Definition propagation_config.hpp:23
stepping::config stepping
Stepper options controlling integration and step limits.
Definition propagation_config.hpp:27
interface type to construct a subrange_view with CTAD
Definition subrange.hpp:90
float path_limit
Maximal path length of track.
Definition stepping_config.hpp:37
bool collect_sensitives_only() const
Check whether only sensitive surfaces are collected.
Definition navigation_validation_config.hpp:95
bool fail_on_diff() const
Check whether validation stops at the first difference.
Definition navigation_validation_config.hpp:104
bool display_svg() const
Check whether SVG output is enabled.
Definition navigation_validation_config.hpp:98
navigation::direction navigation_direction() const
Get the navigation direction.
Definition navigation_validation_config.hpp:92
bool verbose() const
Check whether verbose output is enabled.
Definition navigation_validation_config.hpp:107
pdg_particle< scalar_type > ptc_hypothesis() const
Get the particle hypothesis.
Definition navigation_validation_config.hpp:89
std::size_t n_tracks() const
Get the maximum number of tracks.
Definition navigation_validation_config.hpp:86
bool display_only_missed() const
Check whether SVG output is limited to missed surfaces.
Definition navigation_validation_config.hpp:101
const std::string & name() const
GettersGet the test name.
Definition navigation_validation_config.hpp:77
const auto & svg_style() const
Get the SVG style.
Definition navigation_validation_config.hpp:116