ACTS
Experiment-independent tracking
Loading...
Searching...
No Matches
navigation_validation_utils.hpp
1// This file is part of the ACTS project.
2//
3// Copyright (C) 2016 CERN for the benefit of the ACTS project
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9#pragma once
10
11// Project include(s)
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"
23
24// Detray IO include(s)
25#include "detray/io/csv/intersection2D.hpp"
26#include "detray/io/csv/track_parameters.hpp"
27#include "detray/io/utils/file_handle.hpp"
28
29// Detray test include(s)
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"
35
36// Detray plugin include(s)
37#include "detray/plugins/svgtools/styling/styling.hpp"
38
39// Vecmem include(s)
40#include <vecmem/memory/host_memory_resource.hpp>
41
42// System include(s)
43#include <algorithm>
44#include <iomanip>
45#include <iterator>
46#include <memory>
47#include <optional>
48#include <ranges>
49#include <sstream>
50
51namespace detray::navigation_validator {
52
60 template <typename mask_group_t, typename idx_range_t, typename point_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;
65
66 scalar_t min_dist{detail::invalid_value<scalar_t>()};
67 for (const auto &mask : detray::ranges::subrange(mask_group, idx_range)) {
68 min_dist = math::min(min_dist, mask.min_dist_to_boundary(loc_p));
69 }
70
71 return min_dist;
72 }
73};
74
76struct empty_bfield {};
77
81 std::size_t n_portals{0u};
83 std::size_t n_sensitives{0u};
85 std::size_t n_passives{0u};
86
89 std::size_t n_total() const { return n_portals + n_sensitives + n_passives; }
90
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);
99 }
100
105 bool count(geometry::identifier geo_id, const bool verbose = true) {
106 switch (geo_id.id()) {
107 using enum surface_id;
108 case e_portal: {
109 n_portals++;
110 return true;
111 }
112 case e_sensitive: {
113 n_sensitives++;
114 return true;
115 }
116 case e_passive: {
117 n_passives++;
118 return true;
119 }
120 default: {
121 if (verbose) {
122 DETRAY_ERROR_HOST("Surface type unknown " << geo_id);
123 }
124 return false;
125 }
126 }
127 }
128
129
134 n_portals += other.n_portals;
135 n_sensitives += other.n_sensitives;
136 n_passives += other.n_passives;
137
138 return *this;
139 }
140};
141
145 std::size_t n_tracks{0u};
147 std::size_t n_tracks_w_holes{0u}; //< Number of tracks with missing surface
149 std::size_t n_tracks_w_extra{0u}; //< Number of tracks with extra surfaces
151 std::size_t n_good_tracks{0u}; //< Number of tracks that match exactly
153 std::size_t n_max_missed_per_trk{0u}; //< Max hole count
155 std::size_t n_max_extra_per_trk{0u}; //< Max count of additional sf
156};
157
159template <typename stepper_t, typename... actor_ts,
160 concepts::detector detector_t, typename bfield_t = empty_bfield>
161inline auto record_propagation(
162 const typename detector_t::geometry_context ctx,
163 vecmem::memory_resource *host_mr, const detector_t &det,
164 const propagation::config &cfg,
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;
172 using scalar_t = dscalar<algebra_t>;
173
175 using intersection_t = intersection2D<typename detector_t::surface_type,
176 algebra_t, intersection::contains_pos>;
177
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;
187 using inspector_t =
188 aggregate_inspector<object_tracer_t, nav_print_inspector_t>;
189
190 // Navigation with inspection
191 using navigator_t =
192 caching_navigator<detector_t, navigation::default_cache_size, inspector_t,
193 intersection_t>;
194
195 // Propagator with pathlimit aborter and validation actors
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>;
199
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>;
204
205 // Propagator
206 propagator_t prop{cfg};
207
208 // Build actor states to collect data
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>};
212
213 typename pathlimit_aborter_t::state pathlimit_aborter_state{
214 cfg.stepping.path_limit};
215 typename step_tracer_t::state step_tracer_state{*host_mr};
216 typename material_tracer_t::state mat_tracer_state{*host_mr};
217
218 // Combine all actor states
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...> /*ids*/) {
225 return detray::tie(s1, detail::get<indices>(t)..., s2, s3);
226 };
227 constexpr auto idx_seq{
228 std::make_index_sequence<detail::tuple_size_v<state_ref_tuple_t>>{}};
229
230 auto actor_states =
231 setup_actor_states(pathlimit_aborter_state, step_tracer_state,
232 mat_tracer_state, state_tuple, idx_seq);
233
234 std::unique_ptr<typename propagator_t::state> propagation{nullptr};
235 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
236 propagation =
237 std::make_unique<typename propagator_t::state>(track, det, ctx);
238 } else {
239 if (!bfield.has_value()) {
240 DETRAY_FATAL_HOST("No bfield passed for RKN stepper!");
241 std::exit(EXIT_FAILURE);
242 }
243 propagation = std::make_unique<typename propagator_t::state>(track, *bfield,
244 det, ctx);
245 }
246
247 // Access to navigation information
248 auto &nav_inspector = propagation->navigation().inspector();
249 auto &obj_tracer = nav_inspector.template get<object_tracer_t>();
250 auto &nav_printer = nav_inspector.template get<navigation::print_inspector>();
251
252 // Access to the stepper information
253 auto &step_printer = propagation->stepping().inspector();
254
255 // Find the end point and direction of the track (approximately) for
256 // backward propagation
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>;
261
262 std::unique_ptr<typename fw_propagator_t::state> fw_propagation{nullptr};
263 if constexpr (std::is_same_v<bfield_t, empty_bfield>) {
264 fw_propagation =
265 std::make_unique<typename fw_propagator_t::state>(track, det, ctx);
266 } else {
267 if (!bfield.has_value()) {
268 DETRAY_FATAL_HOST("No bfield passed for RKN stepper!");
269 std::exit(EXIT_FAILURE);
270 }
271 fw_propagation = std::make_unique<typename fw_propagator_t::state>(
272 track, *bfield, det, ctx);
273 }
274
275 // Make a deep copy of states for forward propagation, but omit the
276 // expensive tracers for the forward pass
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...> /*ids*/) {
281 using fw_state_tuple_t = typename fw_actor_chain_t::state_tuple;
282 return fw_state_tuple_t{
283 s1,
284 detail::get<
285 detail::tuple_element_t<indices + 1u, fw_state_tuple_t> &>(t)...};
286 };
287
288 // Setup forward actor states
289 auto setup_fw_actor_states = []<std::size_t... indices>(
290 typename fw_actor_chain_t::state_tuple &t,
291 std::index_sequence<indices...> /*ids*/) {
292 return detray::tie(detail::get<indices>(t)...);
293 };
294
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);
300
301 // Perform forward propagation
302 fw_propagation->set_particle(update_particle_hypothesis(ptc_hypo, track));
303
304 // Make sure parameters are transported, even if there is no actor to
305 // update them and enforce the write back to the updater state
306 if constexpr (has_param_transport) {
307 auto &updater_state = detail::get<updater_state_t &>(fw_actor_states);
308 updater_state.always_update(true);
309 }
310
311 const bool fw_success =
312 fw_propagator_t{cfg}.propagate(*fw_propagation, fw_actor_states);
313
314 if (!fw_success) {
315 DETRAY_ERROR_HOST(
316 "Could not propagate to end of track to prepare backward "
317 "propagation");
318 }
319
320 // Use the result to set up main propagation run
321 propagation->stepping()() = fw_propagation->stepping()();
322 propagation->navigation().set_volume(fw_propagation->navigation().volume());
323
324 // If parameter transport is part of the propagation, copy the final
325 // bound parameters to the backward propagation
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);
329
330 // Make sure that the bound parameters have been transported to the
331 // "turn-around" surface. End-of-world portals might not trigger the
332 // parameter transport if there is no material
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()};
337
338 if (fw_success && !last_sf_id.is_invalid() &&
339 last_bound.surface_link() != last_sf_id) {
340 DETRAY_DEBUG_HOST(
341 "Transporting bound parameters to last surface: " << last_sf_id);
342 DETRAY_DEBUG_HOST(
343 "Last bound parameters: " << updater_state.bound_params());
344
345 const bound_matrix<algebra_t> propagation_step_jacobian =
346 detray::actor::parameter_transporter<algebra_t>{}.get_full_jacobian(
347 *fw_propagation, last_bound);
348
349 const bound_matrix<algebra_t> old_cov = last_bound.covariance();
350 bound_matrix<algebra_t> &new_cov = last_bound.covariance();
351 detray::detail::transport_covariance_to_bound_impl(
352 old_cov, propagation_step_jacobian, new_cov);
353
354 // Get the bound parameters at the destination surface
355 last_bound.set_parameter_vector(
356 fw_propagation->navigation().current_surface().free_to_bound_vector(
357 fw_propagation->context(), fw_propagation->stepping()()));
358
359 // Set new surface link
360 last_bound.set_surface_link(last_sf_id);
361
362 DETRAY_DEBUG_HOST(
363 "Updated bound parameters: " << updater_state.bound_params());
364 }
365 }
366 }
367
368 // Run the propagation
369 propagation->navigation().set_direction(nav_dir);
370 propagation->set_particle(update_particle_hypothesis(ptc_hypo, track));
371 // TODO: Copy the bound params from the forward pass into the actor state
372 bool success = prop.propagate(*propagation, actor_states);
373
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));
379}
380
399template <concepts::detector detector_t, typename traj_t,
400 concepts::algebra algebra_t>
401auto compare_traces(
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;
409
410 // Current number of entries to compare (may become more if both traces
411 // missed several surfaces)
412 std::size_t max_entries{math::max(recorded_trace.size(), truth_trace.size())};
413
414 // Catch some debug output
415 std::stringstream debug_stream;
416 std::stringstream matching_stream;
417
418 // Collect some statistics and additional data
419 surface_stats missed_stats_nav{};
420 surface_stats missed_stats_tr{};
421 bool matching_traces{true};
422 std::size_t n_errors{0u};
423 std::vector<intersection_t> missed_intersections{};
424
425 // An error might have occurred occurred
426 auto handle_counting_error = [&matching_stream, &n_errors](const bool is_OK) {
427 if (!is_OK) {
428 matching_stream << "FATAL: Encountered surface of "
429 "unknown type in intersection "
430 "trace. Validate the geometry";
431 ++n_errors;
432 }
433 };
434
435 // Intersection records at portal boundary might be flipped
436 // (the portals overlap completely)
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};
440
441 if (next_idx < truth_trace.size() && next_idx < recorded_trace.size()) {
442 const auto &current_nav_inters =
443 recorded_trace.at(idx_j).intersection().surface().identifier();
444 const auto &current_truth_inters =
445 truth_trace.at(idx_j).intersection().surface().identifier();
446
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();
451
452 return ((current_nav_inters == next_truth_inters) &&
453 (next_nav_inters == current_truth_inters));
454 } else {
455 return false;
456 }
457 };
458
459 // Iterate until 'max_entries', because dummy records will be added to the
460 // shorter trace
461 for (long i = 0; i < static_cast<long>(max_entries); ++i) {
462 // Check the records at the current index
463 const auto idx{static_cast<std::size_t>(i)};
464
465 // If only sensitives are collected and the recorded trace has one
466 // entry less than the truth trace, the navigator missed the last
467 // sensitive surface
468 const bool nav_has_next = (idx < recorded_trace.size());
469 detray::geometry::identifier nav_inters{};
470 if (nav_has_next) {
471 nav_inters = recorded_trace.at(idx).intersection().surface().identifier();
472 }
473
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();
478 }
479
480 // Check if size of traces is still in sync and records match
481 bool found_same_surfaces =
482 (nav_has_next && truth_has_next && (nav_inters == truth_inters));
483
484 matching_traces = matching_traces && found_same_surfaces;
485
486 if (!found_same_surfaces) {
487 // Count the number of missed surfaces for this mismatch
488 // Missed by navigator
489 long missed_pt_nav{0};
490 long missed_sn_nav{0};
491 long missed_ps_nav{0};
492
493 // Found in addition by navigation (missed by truth)
494 long missed_pt_tr{0};
495 long missed_sn_tr{0};
496 long missed_ps_tr{0};
497
498 // Count a missed surface by surface type
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) {
502 switch (intr.id()) {
503 using enum surface_id;
504 case e_portal: {
505 missed_pt++;
506 break;
507 }
508 case e_sensitive: {
509 missed_sn++;
510 break;
511 }
512 case e_passive: {
513 missed_ps++;
514 break;
515 }
516 default: {
517 throw std::runtime_error("Unknown surface type during counting");
518 }
519 }
520 };
521
522 // Compare two traces and insert dummy records for any skipped cand.
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,
528 dvector<data_record_t> &other_trace, surface_stats &missed_stats,
529 long int &missed_pt, long int &missed_sn, long int &missed_ps) {
530 // The navigator missed a(multiple) surface(s)
531 auto first_missed = std::begin(trace) + i;
532 const auto n_check{std::distance(first_missed, last_missed_itr)};
533 assert(n_check > 0);
534
535 // Check and record surfaces that were missed: Insert dummy
536 // records until traces align again
537 for (long j = i; j < i + n_check; ++j) {
538 const auto &sfi =
539 trace.at(static_cast<std::size_t>(j)).intersection();
540
541 // Portals may be swapped and wrongfully included in the
542 // range of missed surfaces - skip them
543 if (sfi.surface().is_portal() && is_swapped_surfaces(j)) {
544 ++j;
545 continue;
546 }
547 // Record the missed intersections for later analysis
548 missed_intersections.push_back(sfi);
549 // Insert dummy record to match the truth trace size
550 other_trace.insert(other_trace.begin() + i, data_record_t{});
551
552 // Count this missed intersection depending on sf. type
553 const bool valid{missed_stats.count(sfi.surface())};
554 handle_counting_error(valid);
555
556 // Missed surfaces this time
557 count_one_missed(sfi.surface(), missed_pt, missed_sn, missed_ps);
558 }
559
560 assert(missed_pt >= 0);
561 assert(missed_sn >= 0);
562 assert(missed_ps >= 0);
563
564 const long n_missed{missed_pt + missed_sn + missed_ps};
565 // We landed here because something was missed
566 assert(n_missed > 0);
567
568 // Continue checking where trace might match again
569 i += (n_missed - 1);
570 };
571
572 // Match the identifiers to find how many surfaces were skipped
573 //
574 // If the current nav_inters can be found on the truth trace at a
575 // later place, the navigator potentially missed the surfaces that
576 // lie in between (except for swapped portals)
577 auto search_nav_on_truth = [nav_inters](const data_record_t &tr) {
578 return tr.intersection().surface().identifier() == nav_inters;
579 };
580 // As above, but this time check if the navigator found additional
581 // surfaces
582 auto search_truth_on_nav = [truth_inters](const data_record_t &nr) {
583 return nr.intersection().surface().identifier() == truth_inters;
584 };
585
586 // Check if the portal order is swapped or the surface appears
587 // later in the truth/navigation trace (this means one or
588 // multiple surfaces were skipped respectively)
589 if (is_swapped_surfaces(i)) {
590 // Was not wrong after all
591 matching_traces = true;
592 if (!recorded_trace.at(idx).intersection().surface().is_portal() ||
593 !truth_trace.at(idx).intersection().surface().is_portal()) {
594 DETRAY_WARN_HOST(
595 "Track " << trk_no << ", inters. " << idx
596 << ": Surfaces are swapped in trace, "
597 "possibly due to overlaps/large mask tolerances.");
598 }
599 // Have already checked the next record
600 ++i;
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)) {
605 // The navigator missed a(multiple) surface(s)
606 compare_and_equalize(truth_trace, last_missed_by_nav, recorded_trace,
607 missed_stats_nav, missed_pt_nav, missed_sn_nav,
608 missed_ps_nav);
609
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)) {
614 // The navigator found a(multiple) extra surface(s)
615 compare_and_equalize(recorded_trace, last_missed_by_tr, truth_trace,
616 missed_stats_tr, missed_pt_tr, missed_sn_tr,
617 missed_ps_tr);
618
619 } else if (!truth_has_next) {
620 // The nav_inters could not be found on the truth trace, because
621 // the truth trace does not have anymore records left to check:
622 // The surface was missed on the truth side
623 truth_trace.push_back(data_record_t{});
624
625 const bool valid{missed_stats_tr.count(nav_inters)};
626 handle_counting_error(valid);
627 if (valid) {
628 // Count to output error messages correctly
629 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
630 missed_ps_tr);
631 }
632 } else if (!nav_has_next) {
633 // The truth_inters could not be found on the recorded trace,
634 // because the recorded trace does not have any records left
635 // to check: The surface was missed by the navigator
636 recorded_trace.push_back(data_record_t{});
637
638 const bool valid{missed_stats_nav.count(truth_inters)};
639 handle_counting_error(valid);
640
641 if (valid) {
642 // Count to output error messages correctly
643 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
644 missed_ps_nav);
645 }
646 } else {
647 // Both missed a surface at the same time, as neither record
648 // can be found in each others traces
649 bool valid{missed_stats_tr.count(nav_inters)};
650 valid = valid && missed_stats_nav.count(truth_inters);
651 handle_counting_error(valid);
652
653 if (valid) {
654 // Count to output error messages correctly
655 count_one_missed(truth_inters, missed_pt_nav, missed_sn_nav,
656 missed_ps_nav);
657
658 count_one_missed(nav_inters, missed_pt_tr, missed_sn_tr,
659 missed_ps_tr);
660 }
661 }
662
663 // Print error statements for the user
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))";
669 };
670
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
675 << ": "
676 << " (Inserted dummy record(s))";
677 };
678
679 if (missed_pt_tr > 0) {
680 print_err_extra_sf("portal", missed_pt_tr);
681 }
682 if (missed_sn_tr > 0) {
683 print_err_extra_sf("sensitive", missed_sn_tr);
684 }
685 if (missed_ps_tr > 0) {
686 print_err_extra_sf("passive", missed_ps_tr);
687 }
688
689 if (missed_pt_nav > 0) {
690 print_err_missed("portal", missed_pt_nav);
691 }
692 if (missed_sn_nav > 0) {
693 print_err_missed("sensitive", missed_sn_nav);
694 }
695 if (missed_ps_nav > 0) {
696 print_err_missed("passive", missed_ps_nav);
697 }
698
699 // Something must have been missed (unless it was just swapped
700 // portals)
701 assert(matching_traces ||
702 (missed_pt_tr + missed_sn_tr + missed_ps_tr + missed_pt_nav +
703 missed_sn_nav + missed_ps_nav >
704 0));
705 }
706
707 // Re-evaluate the size after dummy records were added
708 max_entries = math::max(recorded_trace.size(), truth_trace.size());
709 }
710
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";
714
715 // Fill the debug stream with the final information from both traces
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()
723 << std::endl;
724 } else {
725 debug_stream << "\n Reference: -" << std::endl;
726 }
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
730 << std::endl;
731 } else {
732 debug_stream << "\n Detray navigator: -\n" << std::endl;
733 }
734 }
735
736 // Missed surfaces or errors during matching
737 const bool any_error{(missed_stats_nav.n_total() != 0u) ||
738 (missed_stats_tr.n_total() != 0u) || (n_errors != 0u)};
739
740 // Print debugging information if anything went wrong
741 if (any_error) {
742 if (cfg.verbose()) {
743 DETRAY_ERROR_HOST("--------\n" << matching_stream.str());
744 }
745 if (debug_file) {
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();
752 }
753 }
754
755 // Unknown error occurred during matching
756 if (n_errors != 0u) {
757 std::stringstream err_str{};
758 err_str << "Errors during matching: " << n_errors << "\n"
759 << matching_stream.str();
760
761 DETRAY_FATAL_HOST(err_str.str());
762 throw std::runtime_error(err_str.str());
763 }
764
765 // After inserting the placeholders, do a final check on the trace sizes
766 const bool is_size{recorded_trace.size() == truth_trace.size()};
767 if (!is_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();
774
775 DETRAY_FATAL_HOST(err_str.str());
776 throw std::runtime_error(err_str.str());
777 }
778
779 // Multiple missed surfaces are a hint that something might be off with this
780 // track
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");
784 }
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");
788 }
789
790 // Make sure the mismatches were correctly counted
791 if (!matching_traces) {
792 bool is_counting_error{false};
793 // No mismatch counted for traces that don't match: Error
794 if (missed_stats_nav.n_total() + missed_stats_tr.n_total() == 0) {
795 is_counting_error = true;
796
797 const std::string msg{
798 "Difference to truth trace was not counted correctly"};
799 if (cfg.fail_on_diff()) {
800 DETRAY_FATAL_HOST("" << msg);
801 throw std::runtime_error(msg);
802 } else {
803 DETRAY_ERROR_HOST("" << msg);
804 }
805 }
806
807 // Recount on the final traces, to be absolutely sure.
808 std::size_t n_miss_truth{0u};
809 std::size_t n_miss_nav{0u};
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()};
813
814 if (truth_desc.identifier().is_invalid()) {
815 n_miss_truth++;
816 }
817 if (nav_desc.identifier().is_invalid()) {
818 n_miss_nav++;
819 }
820 if (truth_desc.identifier() != nav_desc.identifier() &&
821 !(truth_desc.identifier().is_invalid() ||
822 nav_desc.identifier().is_invalid())) {
823 // Swapped in trace, possibly due to overlaps
824 if (!is_swapped_surfaces(static_cast<long>(i))) {
825 n_miss_truth++;
826 n_miss_nav++;
827 } else {
828 // If the surfaces were swapped, we already know the next
829 // element to be correct
830 ++i;
831 }
832 }
833 }
834
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)};
841 if (cfg.fail_on_diff()) {
842 DETRAY_FATAL_HOST("" << msg);
843 throw std::runtime_error(msg);
844 } else {
845 DETRAY_ERROR_HOST("" << msg);
846 }
847 }
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)};
854 if (cfg.fail_on_diff()) {
855 DETRAY_FATAL_HOST("" << msg);
856 throw std::runtime_error(msg);
857 } else {
858 DETRAY_ERROR_HOST("" << msg);
859 }
860 }
861
862 if (is_counting_error) {
863 n_errors++;
864
865 if (debug_file) {
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();
873 }
874 }
875 }
876
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);
880}
881
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;
889 using value_t = dvalue<algebra_t>;
890 using scalar_t = dscalar<algebra_t>;
891 using track_param_t = free_track_parameters<algebra_t>;
892
893 std::vector<std::vector<std::pair<scalar_t, track_param_t>>> track_params{};
894
895 for (const auto &trace : intersection_traces) {
896 track_params.push_back({});
897 track_params.back().reserve(trace.size());
898
899 for (const auto &record : trace) {
900 track_params.back().emplace_back(record.template charge<value_t>(),
901 record.track_param());
902 }
903 }
904
905 // Write to file
906 io::csv::write_free_track_params(track_param_file_name, track_params);
907}
908
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{};
923
924 // Write to csv file
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_"
928 "tol,sf_type"
929 << std::endl;
930
931 for (const auto &[i, entry] :
932 detray::views::enumerate(missed_intersections)) {
933 const auto &missed_inters_vec = entry.second;
934
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()};
939
940 const auto dist =
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()));
944
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;
950 }
951 }
952}
953
955inline auto print_efficiency(std::size_t n_tracks,
956 const surface_stats &n_surfaces,
957 const surface_stats &n_miss_nav,
958 const surface_stats &n_miss_truth,
959 std::size_t n_fatal_error,
960 std::size_t n_matching_error) {
961 // Column width in output
962 constexpr int cw{20};
963
964 // Print general information
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
972 << std::setw(cw)
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
977 << std::setw(cw)
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
984 << std::setw(cw)
985 << "\n passives: " << n_miss_truth.n_passives
986 << "\n\nFatal propagation failures: " << n_fatal_error
987 << "\nErrors during truth matching: " << n_matching_error;
988 } else {
989 std::clog << "-----------------------------------\n"
990 << "Tested " << n_tracks << " tracks: OK\n"
991 << "total number of surfaces: " << n_surfaces.n_total();
992 }
993
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);
998
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) {
1003 // How many significant digits to print
1004 const auto n_sig{
1005 2 + static_cast<int>(math::ceil(math::log10(n_surfaces.n_total())))};
1006
1007 const auto k{static_cast<double>(n_sf - n_missed)};
1008 const auto n{static_cast<double>(n_sf)};
1009
1010 // Estimate of the surface finding efficiency by the navigator
1011 const auto eff{k / n};
1012
1013 // Variance
1014 // const double var_binomial{eff * (1. - eff) / 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)};
1017
1018 // In percent
1019 std::clog << "\n"
1020 << sf_type << " finding eff.: " << std::fixed
1021 << std::setprecision(n_sig) << 100. * eff << " \u00b1 "
1022 << 100. * math::sqrt(var_bayesian) << "%";
1023 };
1024
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);
1028 }
1029 if (n_surfaces.n_sensitives != 0u) {
1030 print_eff("Sensitive sf.", n_surfaces.n_sensitives,
1031 n_miss_nav.n_sensitives);
1032 }
1033 if (n_surfaces.n_passives != 0u) {
1034 print_eff("Passive sf.", n_surfaces.n_passives, n_miss_nav.n_passives);
1035 }
1036
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());
1040 } else {
1041 DETRAY_ERROR_HOST("No surfaces found in truth data!");
1042 }
1043
1044 std::clog << "\n-----------------------------------\n" << std::endl;
1045}
1046
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,
1083 const std::vector<free_track_parameters<algebra_t>> &tracks,
1084 dvector<typename actor_chain<actor_ts...>::state_ref_tuple> state_tuples = {
1085 {}}) {
1086 assert(truth_traces.size() == tracks.size());
1087 assert(!state_tuples.empty());
1088
1089 // Write navigation and stepping debug info if a track fails
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};
1093
1094 const std::size_t n_samples{truth_traces.size()};
1095
1096 // Collect some statistics for consistency checking
1097 std::size_t n_trk_holes{0};
1098 std::size_t n_matching_error{0u};
1099 std::size_t n_fatal_error{0u};
1100
1101 // Collect global track statistics
1102 track_statistics trk_stats{};
1103
1104 // Total number of encountered surfaces
1105 surface_stats n_surfaces{};
1106 // Missed by navigator
1107 surface_stats n_miss_nav{};
1108 // Missed in truth trace
1109 surface_stats n_miss_truth{};
1110
1111 // Collect step and material traces for all tracks
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{};
1115
1116 mat_steps.reserve(n_samples);
1117 track_mat_vec.reserve(n_samples);
1118 step_traces.reserve(n_samples);
1119
1120 // Run the navigation on every truth trace
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);
1124 auto state_tuple =
1125 state_tuples.size() > i ? state_tuples.at(i) : state_tuples.at(0);
1126
1127 assert(!truth_traces.empty());
1128
1129 DETRAY_VERBOSE_HOST("Track " << i << ":");
1130
1131 // Record the propagation through the geometry
1132 auto [success, obj_tracer, step_trace, track_mat, mat_trace, nav_printer,
1133 step_printer] =
1134 record_propagation<stepper_t, actor_ts...>(
1135 ctx, &host_mr, det, prop_cfg, track, cfg.ptc_hypothesis(),
1136 field_view, cfg.navigation_direction(), state_tuple);
1137
1138 // Fatal propagation error: Data unreliable
1139 if (!success) {
1140 std::stringstream strm{};
1141
1142 strm << "Track " << i << ": Propagation aborted! "
1143 << nav_printer.fata_error_msg << std::endl;
1144
1145 DETRAY_FATAL_HOST(strm.str());
1146 *debug_file << "FATAL: " << strm.str();
1147
1148 n_fatal_error++;
1149 }
1150
1151 // Save material for later comparison
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);
1155
1156 // Compare to truth trace
1158 recorded_trace.reserve(obj_tracer.trace().size());
1159 // Get only the sensitive surfaces
1160 if (cfg.collect_sensitives_only()) {
1161 for (const auto &rec : obj_tracer.trace()) {
1162 if (rec.intersection().surface().is_sensitive()) {
1163 recorded_trace.push_back(rec);
1164 }
1165 }
1166 } else {
1167 std::ranges::copy(obj_tracer.trace(), std::back_inserter(recorded_trace));
1168 }
1169
1170 // The recorded trace missed something
1171 if (recorded_trace.size() < truth_trace.size()) {
1172 n_trk_holes++;
1173 }
1174
1175 // Compare recorded and truth traces and count missed surfaces for both
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));
1180 // Comparison failed or propagation failed
1181 if (!traces_match || !success) {
1182 // Write debug info to file
1183 *debug_file << "TEST TRACK " << i << ":\n\n"
1184 << nav_printer.to_string() << step_printer.to_string();
1185
1186 // Create SVGs for traces with mismatches
1187 if (n_miss_trace_truth.n_total() != 0u ||
1188 n_miss_trace_nav.n_total() != 0u || n_error_trace != 0u) {
1189 // Only dump SVG if navigator missed a sf. or an error occurred
1190 if (cfg.display_svg() && (!cfg.display_only_missed() ||
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());
1196 }
1197 }
1198 }
1199
1200 // After dummy records insertion, traces should have the same size
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");
1206 }
1207
1208 // Internal error during trace 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 "
1213 "files");
1214 }
1215
1216 // Add to global statistics
1217 n_miss_nav += n_miss_trace_nav;
1218 n_miss_truth += n_miss_trace_truth;
1219 n_matching_error += n_error_trace;
1220
1221 // Count the number of surfaces per type in all traces
1222 for (std::size_t j = 0; j < truth_trace.size(); ++j) {
1223 n_surfaces.count(truth_trace[j].intersection().surface(), cfg.verbose());
1224 }
1225
1226 // Did the navigation miss a sensitive surface? => count a hole
1227 if (n_miss_trace_nav.n_sensitives != 0u) {
1228 trk_stats.n_tracks_w_holes++;
1229 }
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++;
1233 }
1234 // Any additional surfaces found (might have material)
1235 if (n_miss_trace_truth.n_total() != 0u) {
1236 trk_stats.n_tracks_w_extra++;
1237 }
1238
1239 // Maximum number of missed/extra surfaces over all tracks
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());
1244
1245 // Count the number of tracks that were tested surccessfully
1246 trk_stats.n_tracks++;
1247 }
1248
1249 // Print results
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)};
1258
1259 // Self check
1260 if (static_cast<double>(n_trk_holes) > n_tracks_w_holes) {
1261 throw std::runtime_error("Number of tracks with holes is underestimated!");
1262 }
1263
1264 // Calculate and display the surface finding efficiency
1265 if (cfg.verbose()) {
1266 print_efficiency(trk_stats.n_tracks, n_surfaces, n_miss_nav, n_miss_truth,
1267 n_fatal_error, n_matching_error);
1268 }
1269
1270 // Column width in output
1271 constexpr int cw{35};
1272
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 << "%)"
1276 << std::endl;
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 << "%)"
1280 << std::endl;
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"
1284 << std::endl;
1285 std::clog << std::left << std::setw(cw + 5)
1286 << "Max no. of miss. surfaces per track: " << n_max_holes_per_trk
1287 << " (Mean: "
1288 << ((n_tracks_w_holes == 0)
1289 ? "-"
1290 : std::to_string(
1291 static_cast<double>(n_miss_nav.n_sensitives) /
1292 n_tracks_w_holes))
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
1296 << " (Mean: "
1297 << ((n_tracks_w_extra == 0)
1298 ? "-"
1299 : std::to_string(
1300 static_cast<double>(n_miss_truth.n_total()) /
1301 n_tracks_w_extra))
1302 << ")" << std::endl;
1303 std::clog << "\n-----------------------------------" << std::endl;
1304
1305 return std::tuple{trk_stats, n_surfaces, n_miss_nav, n_miss_truth,
1306 step_traces, track_mat_vec, mat_steps};
1307}
1308
1309} // namespace detray::navigation_validator
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 &params)
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