ACTS
Experiment-independent tracking
Loading...
Searching...
No Matches
detector_scan_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/utils/logging.hpp"
14#include "detray/utils/ranges.hpp"
15
16// Detray plugin include(s)
17#include "detray/plugins/svgtools/illustrator.hpp"
18
19// Detray test include(s)
20#include "detray/test/utils/data_record.hpp"
21#include "detray/test/validation/svg_display.hpp"
22
23// System include(s)
24#include <algorithm>
25#include <functional>
26#include <iostream>
27#include <iterator>
28#include <map>
29#include <ranges>
30#include <set>
31#include <sstream>
32#include <string>
33#include <tuple>
34#include <type_traits>
35#include <unordered_set>
36#include <utility>
37#include <vector>
38
40
50template <concepts::detector detector_t>
52 dvector<intersection_record<detector_t>> &intersection_records,
53 const float tol = 1e-4f * unit<float>::mm) {
54 // Document any overlaps on the range
55 constexpr auto inv_idx{detray::detail::invalid_value<dindex>()};
56 dindex_range overlap_idx{inv_idx, inv_idx};
57
58 const std::size_t n_rec{intersection_records.size()};
59 std::size_t n_eq_intrs{1u}; //< Number of consecutive overlapping inters.
60 for (std::size_t i = 0u; i < n_rec - 1u; ++i) {
61 const auto &rec = intersection_records.at(i);
62 const auto &next_rec = intersection_records.at(i + 1u);
63
64 // This record and the following are overlapping: Count until we reach
65 // the end of the overlapping surfaces or end of trace
66 if (math::fabs(next_rec.intersection().path() - rec.intersection().path()) <
67 tol &&
68 i != (n_rec - 2u)) {
69 ++n_eq_intrs;
70 continue;
71 }
72
73 // No overlap: continue
74 if (n_eq_intrs == 1u) {
75 continue;
76 }
77 // Found two overlapping surfaces
78 if (n_eq_intrs == 2u) {
79 // Two overlapping portals form a valid, connected volume boundary
80 if (const auto &prev_rec = intersection_records.at(i - 1u);
81 !(rec.intersection().surface().is_portal() &&
82 prev_rec.intersection().surface().is_portal())) {
83 auto prev_sf_desc = prev_rec.intersection().surface();
84 auto sf_desc = rec.intersection().surface();
85
86 // Other types of surfaces must not overlap!
87 std::stringstream err_stream;
88 err_stream << "The following surfaces overlap at\n"
89 << "POS:\n"
90 << "glob: " << prev_rec.template position<double>()
91 << ", loc: " << prev_rec.intersection().local()
92 << "\nvs.\nglob: " << rec.template position<double>()
93 << ", loc: " << rec.intersection().local() << std::endl;
94 err_stream << "SURFACES:\n -> " << prev_sf_desc << std::endl;
95 err_stream << " -> " << sf_desc << std::endl;
96
97 // TODO: Fix wire_chamber geometry
98 // throw std::invalid_argument(err_stream.str());
99 DETRAY_ERROR_HOST(err_stream.str());
100 overlap_idx = {static_cast<dindex>(i - 1u), static_cast<dindex>(i)};
101 }
102 // Reset to find the next range
103 n_eq_intrs = 1u;
104 continue;
105 }
106
107 // Found more than two overlapping surfaces: Remove oversized portal
108 // intersections
109 std::size_t first{(i - (n_eq_intrs - 1u))};
110 std::size_t last{i};
111
112 // Map of the volume index the portals link to and the portal
113 // intersection indices in the intersection trace
114 std::multimap<std::size_t, std::size_t> pt_buckets{};
115
116 bool is_all_portals{true};
117 for (std::size_t j = first; j <= last; ++j) {
118 const auto &intr = intersection_records.at(j).intersection();
119 if (!intr.surface().is_portal()) {
120 is_all_portals = false;
121 }
122 pt_buckets.insert({static_cast<std::size_t>(intr.volume_link()), j});
123 }
124
125 // Remove buckets that contain only one portal: This is the valid exit
126 // portal that the overlapping portals need to be matched against
127 auto exit_idx{std::numeric_limits<std::size_t>::max()};
128 const auto n_erased =
129 std::erase_if(pt_buckets, [&pt_buckets, &exit_idx](const auto &item) {
130 const auto &[vol_link, rec_idx] = item;
131 if (pt_buckets.count(vol_link) == 1u) {
132 exit_idx = rec_idx;
133 return true;
134 }
135 return false;
136 });
137
138 // This is not a case of oversized portals, treat as actual overlaps
139 if (n_erased != 1u || pt_buckets.empty() || !is_all_portals) {
140 DETRAY_ERROR_HOST("Could not resolve exit portal in overlap correction");
141
142 overlap_idx = {static_cast<dindex>(first), static_cast<dindex>(last)};
143 } else {
144 // Keep only the portal that forms the correct volume crossing
145 // with the portal intersection at 'exit_idx'
146 const auto &exit_rec = intersection_records.at(exit_idx);
147 auto itr = intersection_records.cbegin();
148
149 // Remove elements indiceated by ordered map with higher index first
150 for (const auto &[v_link, idx] : pt_buckets | std::views::reverse) {
151 // All these portals link against the exit portal
152 assert(v_link == exit_rec.volume());
153
154 auto test_elem = std::next(itr, static_cast<int>(idx));
155 if (exit_rec.intersection().volume_link() != test_elem->volume()) {
156 intersection_records.erase(test_elem);
157 }
158 }
159 }
160
161 // Reset
162 n_eq_intrs = 1u;
163 }
164
165 return overlap_idx;
166}
167
193template <dindex invalid_value = dindex_invalid, bool check_sorted_trace = true,
194 typename entry_type = std::pair<dindex, dindex>>
196 std::vector<std::pair<entry_type, entry_type>> trace,
197 dindex start_volume = 0u) {
199 std::stringstream err_stream;
200
202 auto print_err = [](const std::stringstream &stream) {
203 std::cerr << "\n<<<<<<<<<<<<<<< ERROR in connectivity check\n" << std::endl;
204 std::cerr << stream.str() << std::endl;
205 std::cerr << "\n>>>>>>>>>>>>>>>\n" << std::endl;
206 };
207
208 // There must always be portals!
209 if (trace.empty()) {
210 err_stream << "Trace empty!";
211 print_err(err_stream);
212
213 return false;
214 }
215 // Keep record of leftovers
216 std::stringstream record_stream;
217
218 // Where are we on the trace?
219 dindex current_volume = start_volume;
220
221 // If the intersection trace comes from the ray gun/trace intersections
222 // function it should be sorted, which is the stronger constraint
223 using vector_t = decltype(trace);
224 using records_iterator_t = typename vector_t::iterator;
225 using index_t = std::iter_difference_t<records_iterator_t>;
226 std::function<records_iterator_t(index_t)> get_connected_record;
227 if constexpr (check_sorted_trace) {
228 // Get the next record
229 get_connected_record =
230 [&trace, &current_volume](index_t next) -> records_iterator_t {
231 // Make sure that the record contains the volume that is currently
232 // being checked for connectivity
233 if (auto rec = trace.begin() + next;
234 rec != trace.end() &&
235 ((std::get<1>(rec->first) == current_volume) ||
236 (std::get<1>(rec->second) == current_volume))) {
237 return rec;
238 }
239 return trace.end();
240 };
241 } else {
242 // Search for the existence of a fitting record over the entire trace
243 get_connected_record =
244 [&trace, &current_volume](index_t /*next*/) -> records_iterator_t {
245 return std::ranges::find_if(
246 trace, [&](const std::pair<entry_type, entry_type> &rec) -> bool {
247 return (std::get<1>(rec.first) == current_volume) ||
248 (std::get<1>(rec.second) == current_volume);
249 });
250 };
251 }
252
253 // Init chain search
254 index_t i{0};
255 auto record = get_connected_record(i);
256
257 // Check first volume index, which has no partner otherwise
258 if (std::get<1>(record->first) != start_volume) {
259 err_stream << "First record does not start at given initial volume: "
260 << std::get<1>(record->first) << " vs. " << start_volume;
261
262 print_err(err_stream);
263
264 return false;
265 }
266
267 // Walk along the trace as long as a connection is found
268 while (record != trace.end()) {
269 auto first_vol = std::get<1>(record->first);
270 auto second_vol = std::get<1>(record->second);
271
272 record_stream << "On volume: " << current_volume << " and record ("
273 << first_vol << ", " << second_vol << ")";
274
275 // update to next volume
276 current_volume = (current_volume == first_vol ? second_vol : first_vol);
277
278 record_stream << " -> next volume: " << current_volume << std::endl;
279
280 // Don't search this key again -> only one potential key with current
281 // index left
282 if constexpr (!check_sorted_trace) {
283 trace.erase(record);
284 }
285
286 // find connected record for the current volume
287 record = get_connected_record(++i);
288 }
289
290 // There are unconnected elements left (we didn't leave world before
291 // termination)
292 if (current_volume != invalid_value) {
293 err_stream << "Didn't leave world or unconnected elements left in trace:"
294 << "\n\nValid connections that were found:" << std::endl;
295 err_stream << record_stream.str();
296
297 err_stream << "\nPairs left to match:" << std::endl;
298 for (auto j = static_cast<std::size_t>(i); j < trace.size(); ++j) {
299 auto first_vol = std::get<1>(trace[j].first);
300 auto second_vol = std::get<1>(trace[j].second);
301
302 err_stream << "(" << first_vol << ", " << second_vol << ")" << std::endl;
303 }
304
305 print_err(err_stream);
306
307 return false;
308 }
309
310 return true;
311}
312
333template <concepts::detector detector_t>
335 const dvector<intersection_record<detector_t>> &intersection_records,
336 dindex start_volume = 0u) {
338 using trace_entry = std::pair<dindex, dindex>;
340 std::vector<std::pair<trace_entry, trace_entry>> portal_trace = {};
342 std::vector<trace_entry> module_trace = {};
344 std::stringstream record_stream;
346 std::stringstream err_stream;
347 bool error_code{true};
348
350 struct record {
352
355 inline bool is_invalid() const {
356 return entry.intersection().surface().identifier().is_invalid();
357 }
358 inline auto surface_idx() const {
359 return entry.intersection().surface().index();
360 }
361 inline auto surface_volume_idx() const {
362 return entry.intersection().surface().volume();
363 }
364 inline auto &inters() const { return entry.intersection(); }
365 inline auto volume_idx() const { return entry.volume(); }
366 inline auto volume_link() const {
367 return entry.intersection().volume_link();
368 }
369 inline auto dist() const { return entry.intersection().path(); }
370 inline bool is_portal() const {
371 return entry.intersection().surface().is_portal();
372 }
373 inline bool is_sensitive() const {
374 return entry.intersection().surface().is_sensitive();
375 }
376 inline bool is_passive() const {
377 return entry.intersection().surface().is_passive();
378 }
380 };
381
383 auto print_err = [&error_code](const std::stringstream &stream) {
384 std::cerr << "\n<<<<<<<<<<<<<<< ERROR intersection trace\n" << std::endl;
385 std::cerr << stream.str() << std::endl;
386 std::cerr << "\n>>>>>>>>>>>>>>>\n" << std::endl;
387 error_code = false;
388 };
389
390 // No intersections found by ray
391 if (intersection_records.empty()) {
392 err_stream << "No surfaces found in detector!";
393 print_err(err_stream);
394
395 return std::make_tuple(portal_trace, module_trace, error_code);
396 }
397
398 // If there is only one surface in the trace, it must be a portal
399 if (intersection_records.size() == 1u) {
400 const record rec{intersection_records.at(0u)};
401
402 // No exit potal
403 if (!rec.is_portal()) {
404 if (rec.is_invalid()) {
405 err_stream << "No surfaces found in detector!";
406 print_err(err_stream);
407 } else {
408 const std::string sf_type{rec.is_sensitive() ? "sensitive" : "passive"};
409
410 err_stream << "We don't leave the detector by portal!" << std::endl;
411 err_stream << "Only found single " << sf_type
412 << " surface: portal(s) missing!";
413
414 print_err(err_stream);
415 }
416
417 return std::make_tuple(portal_trace, module_trace, error_code);
418 }
419 }
420
421 // Go through recorded intersection (two at a time)
422 dindex current_vol = start_volume;
423 // The first entry is the dummy record that preserves initial track
424 // parameters, skip it
425 const std::size_t start_idx{
426 record{intersection_records.at(0)}.is_invalid() ? 1u : 0u};
427 for (std::size_t rec = start_idx; rec < (intersection_records.size() - 1u);) {
428 const record current_rec = record{intersection_records.at(rec)};
429 const record next_rec = record{intersection_records.at(rec + 1u)};
430
431 // Keep a debug stream
432 record_stream << current_rec.volume_idx() << "\t" << current_rec.inters()
433 << std::endl;
434
435 // If the current record is not a portal, add an entry to the module
436 // trace and continue in more fine-grained steps (sensitive/passive
437 // surfaces do not come in pairs)
438 if (!current_rec.is_portal()) {
439 // Check that the surface was found in the volume it claims to
440 // belong to
441 const bool is_in_volume =
442 (current_rec.volume_idx() == current_rec.surface_volume_idx()) &&
443 (current_rec.surface_volume_idx() == current_vol);
444 if (is_in_volume) {
445 module_trace.emplace_back(current_rec.surface_idx(),
446 current_rec.volume_idx());
447 } else {
448 err_stream << "\n(!!) Surface " << current_rec.surface_idx()
449 << " outside of its volume (Found in: " << current_vol
450 << ", belongs in: " << current_rec.surface_volume_idx()
451 << ")\n";
452
453 err_stream << record_stream.str();
454
455 print_err(err_stream);
456
457 return std::make_tuple(portal_trace, module_trace, error_code);
458 }
459 ++rec;
460 continue;
461 }
462 // If the record is a portal, the current volume switches
463 // the portals in the pair may be unordered, so check both
464 else if (current_vol == current_rec.volume_idx()) {
465 current_vol = current_rec.volume_link();
466 } else if (current_vol == next_rec.volume_idx()) {
467 current_vol = next_rec.volume_link();
468 }
469
470 record_stream << next_rec.volume_idx() << "\t" << next_rec.inters()
471 << std::endl;
472
473 // Check that also the second surface was found in the volume it claims
474 // to belong to
475 const bool is_in_volume =
476 next_rec.volume_idx() == next_rec.surface_volume_idx();
477 // Is this doublet connected via a valid portal intersection?
478 const bool is_valid =
479 (current_rec.inters() == next_rec.inters()) &&
480 (current_rec.volume_idx() == next_rec.volume_link()) &&
481 (next_rec.volume_idx() == current_rec.volume_link());
482 // Is this indeed a portal crossing, i.e. changing volumes?
483 const bool is_self_link = current_rec.volume_idx() == next_rec.volume_idx();
484 // Is the record doublet we picked made up of a portal and a module?
485 const bool is_mixed = (current_rec.is_portal() && !next_rec.is_portal()) ||
486 (next_rec.is_portal() && !current_rec.is_portal());
487
488 if (!is_in_volume) {
489 record_stream << "\n(!!) Surface outside of its volume (Found: "
490 << next_rec.volume_idx()
491 << ", belongs in: " << next_rec.surface_volume_idx() << ")"
492 << std::endl;
493 }
494 if (!is_valid) {
495 record_stream << "\n(!!) Not a valid portal crossing ("
496 << current_rec.volume_idx() << " <-> "
497 << next_rec.volume_idx() << "):\nPortals are not "
498 << "connected, either geometrically or by linking!"
499 << std::endl;
500 }
501 if (is_self_link) {
502 record_stream << "\n(!!) Found portal crossing inside volume ("
503 << current_rec.volume_idx() << ")!" << std::endl;
504 }
505 if (is_mixed) {
506 record_stream << "\n(!!) Portal crossing involves module surface ("
507 << current_rec.volume_idx() << " <-> "
508 << next_rec.volume_idx() << ")! The second surface in"
509 << " this portal crossing is not a portal!" << std::endl;
510 }
511 if (is_in_volume && is_valid && !is_self_link && !is_mixed) {
512 // Insert into portal trace
513 trace_entry lower{current_rec.surface_idx(), current_rec.volume_idx()};
514 trace_entry upper{next_rec.surface_idx(), next_rec.volume_idx()};
515 portal_trace.emplace_back(lower, upper);
516 }
517 // Something went wrong
518 else {
519 // Print search log
520 err_stream << "\nError in portal matching:\n" << std::endl;
521 err_stream << "volume id\t(intersection info)" << std::endl;
522
523 err_stream << record_stream.str() << std::endl;
524
525 err_stream << "-----\nINFO: Ray terminated at portal x-ing "
526 << (rec + 1) / 2 << ":\n"
527 << current_rec.inters() << " <-> " << next_rec.inters()
528 << std::endl;
529
530 const record rec_front{record{intersection_records.front()}.is_invalid()
531 ? intersection_records[1u]
532 : intersection_records.front()};
533 const record rec_back{intersection_records.back()};
534 err_stream << "Start volume : " << start_volume << std::endl;
535 err_stream << "- first recorded intersection: (sf id:"
536 << rec_front.surface_idx() << ", dist:" << rec_front.dist()
537 << ")," << std::endl;
538 err_stream << "- last recorded intersection: (sf id:"
539 << rec_back.surface_idx() << ", dist:" << rec_back.dist()
540 << "),";
541
542 print_err(err_stream);
543
544 return std::make_tuple(portal_trace, module_trace, error_code);
545 }
546
547 // Advance to inspect next pair
548 rec += 2u;
549 }
550
551 // Look at the last entry, which is a single portal
552 if (const record rec_back{intersection_records.back()};
553 !rec_back.is_portal()) {
554 err_stream << "We don't leave the detector by portal!";
555 print_err(err_stream);
556 } else {
557 trace_entry lower(rec_back.surface_idx(), rec_back.volume_idx());
558 trace_entry upper(rec_back.surface_idx(), rec_back.volume_link());
559 portal_trace.emplace_back(lower, upper);
560 }
561
562 return std::make_tuple(portal_trace, module_trace, error_code);
563}
564
581template <dindex invalid_value = dindex_invalid,
582 typename entry_type = std::pair<dindex, dindex>>
583inline auto build_adjacency(
584 const std::vector<std::pair<entry_type, entry_type>> &portal_trace,
585 const std::vector<entry_type> &module_trace,
586 std::map<dindex, std::map<dindex, dindex>> &adj_list,
587 std::unordered_set<dindex> &obj_hashes) {
588 // Every module that was recorded adds a link to the mother volume
589 for (const auto &record : module_trace) {
590 const auto sf_index = std::get<0>(record);
591 const auto vol_index = std::get<1>(record);
592 // Check whether we have seen this module in this volume before
593 if (obj_hashes.find(sf_index) == obj_hashes.end()) {
594 adj_list[vol_index][vol_index]++;
595 obj_hashes.insert(sf_index);
596 }
597 }
598
599 // Portal in first volume links to second volume in the record
600 for (const auto &record : portal_trace) {
601 const auto pt_index_1 = std::get<0>(record.first);
602 const auto vol_index_1 = std::get<1>(record.first);
603 const auto pt_index_2 = std::get<0>(record.second);
604 const auto vol_index_2 = std::get<1>(record.second);
605
606 if (obj_hashes.find(pt_index_1) == obj_hashes.end()) {
607 adj_list[vol_index_1][vol_index_2]++;
608 obj_hashes.insert(pt_index_1);
609 }
610 // Assume the return link for now (filter out portal that leaves world)
611 if (vol_index_2 != invalid_value &&
612 obj_hashes.find(pt_index_2) == obj_hashes.end()) {
613 adj_list[vol_index_2][vol_index_1]++;
614 obj_hashes.insert(pt_index_2);
615 }
616 }
617
618 return adj_list;
619}
620
637template <dindex invalid_value = dindex_invalid,
638 typename entry_type = std::pair<dindex, dindex>>
639inline auto build_adjacency(
640 const std::vector<std::pair<entry_type, entry_type>> &portal_trace,
641 const std::vector<entry_type> &module_trace, dvector<dindex> &adj_matrix,
642 std::unordered_set<dindex> &obj_hashes) {
643 const auto dim = static_cast<dindex>(math::sqrt(adj_matrix.size()));
644
645 // Every module that was recorded adds a link to the mother volume
646 for (const auto &record : module_trace) {
647 const auto sf_index = std::get<0>(record);
648 const auto vol_index = std::get<1>(record);
649 // Check whether we have seen this module in this volume before
650 if (obj_hashes.find(sf_index) == obj_hashes.end()) {
651 adj_matrix[dim * vol_index + vol_index]++;
652 obj_hashes.insert(sf_index);
653 }
654 }
655
656 // Portal in first volume links to second volume in the record
657 for (const auto &record : portal_trace) {
658 const auto pt_index_1 = std::get<0>(record.first);
659 const auto vol_index_1 = std::get<1>(record.first);
660 const auto pt_index_2 = std::get<0>(record.second);
661 const auto vol_index_2 = std::get<1>(record.second);
662
663 if (obj_hashes.find(pt_index_1) == obj_hashes.end()) {
664 dindex mat_elem_vol1{dindex_invalid};
665 // Assume the return link for now (filtering out portals that leave world)
666 if (vol_index_2 != invalid_value) {
667 mat_elem_vol1 = dim * vol_index_1 + vol_index_2;
668
669 if (obj_hashes.find(pt_index_2) == obj_hashes.end()) {
670 adj_matrix[dim * vol_index_2 + vol_index_1]++;
671 obj_hashes.insert(pt_index_2);
672 }
673 } else {
674 mat_elem_vol1 = dim * vol_index_1 + dim - 1;
675 }
676 adj_matrix[mat_elem_vol1]++;
677 obj_hashes.insert(pt_index_1);
678 }
679 }
680
681 return adj_matrix;
682}
683
692template <concepts::detector detector_t>
693inline bool check_trace(
694 const dvector<intersection_record<detector_t>> &intersection_trace,
695 const dindex start_index, dvector<dindex> &adj_mat_scan,
696 std::unordered_set<dindex> &obj_hashes) {
697 using nav_link_t = typename detector_t::surface_type::navigation_link;
698 static constexpr auto leaving_world{
699 detray::detail::invalid_value<nav_link_t>()};
700
701 // Create a trace of the volume indices that were encountered
702 // and check that portal intersections are connected
703 auto [portal_trace, surface_trace, err_code] =
704 detector_scanner::trace_intersections(intersection_trace, start_index);
705
706 DETRAY_DEBUG_HOST("portal_trace " << portal_trace.size() << ", surface_trace "
707 << surface_trace.size());
708
709 // Is the succession of volumes consistent ?
710 err_code = err_code &&
712
713 if (!adj_mat_scan.empty()) {
714 // Build an adjacency matrix from this trace that can be checked
715 // against the geometry hash (see 'track_geometry_changes')
717 portal_trace, surface_trace, adj_mat_scan, obj_hashes);
718 }
719
720 return err_code;
721}
722
737template <concepts::detector detector_t, typename trajectory_t>
738inline void display_error(
739 const typename detector_t::geometry_context gctx, const detector_t &det,
740 const typename detector_t::name_map &vol_names,
741 const std::string &test_name, const trajectory_t &test_track,
742 const dvector<intersection_record<detector_t>> &truth_trace,
743 const detray::svgtools::styling::style &svg_style,
744 const std::size_t i_track, [[maybe_unused]] const std::size_t n_tracks,
745 const dvector<intersection_record<detector_t>> &recorded_trace = {},
746 const dindex_range overlap_idx = {detray::detail::invalid_value<dindex>(),
747 detray::detail::invalid_value<dindex>()},
748 const bool verbose = true) {
749 // Creating the svg generator for the detector.
750 detray::svgtools::illustrator il{det, vol_names, svg_style};
751 il.show_info(true);
752 il.hide_eta_lines(true);
753 il.hide_portals(false);
754 il.hide_passives(false);
755
756 std::string track_type{};
757 static constexpr auto is_ray{
758 std::is_same_v<trajectory_t,
759 detray::detail::ray<typename detector_t::algebra_type>>};
760 if constexpr (is_ray) {
761 track_type = "ray";
762 } else {
763 track_type = "helix";
764 }
765
766 if (verbose) {
767 DETRAY_ERROR_HOST("\nFailed on " << track_type << ": " << i_track << "/"
768 << n_tracks << "\n"
769 << test_track);
770 }
771
772 detray::detail::svg_display(gctx, il, truth_trace, test_track,
773 track_type + "_" + std::to_string(i_track),
774 test_name, recorded_trace, overlap_idx, verbose);
775}
776
781template <concepts::detector detector_t>
782inline std::string print_trace(
783 const dvector<intersection_record<detector_t>> &truth_trace,
784 std::size_t n) {
785 std::stringstream out_stream{};
786 out_stream << "TRACE NO. " << n << std::endl;
787
788 for (const auto &[idx, record] : detray::views::enumerate(truth_trace)) {
789 out_stream << "\nRecord " << idx << std::endl;
790
791 out_stream << " -> volume " << record.volume() << std::endl;
792
793 const auto pos = record.template position<double>();
794 const auto dir = record.template direction<double>();
795 out_stream << " -> track pos: [" << pos[0] << ", " << pos[1] << ", "
796 << pos[2] << std::endl;
797 out_stream << " -> track dir: [" << dir[0] << ", " << dir[1] << ", "
798 << dir[2] << std::endl;
799
800 out_stream << " -> intersection " << record.intersection() << std::endl;
801 }
802
803 return out_stream.str();
804}
805
809inline std::string print_adj(const dvector<dindex> &adjacency_matrix) {
810 std::size_t dim = static_cast<dindex>(math::sqrt(adjacency_matrix.size()));
811 std::stringstream out_stream{};
812
813 for (std::size_t i = 0u; i < dim - 1; ++i) {
814 out_stream << "[>>] Node with index " << i << std::endl;
815 out_stream << " -> edges: " << std::endl;
816 for (std::size_t j = 0u; j < dim; ++j) {
817 const auto degr = adjacency_matrix[dim * i + j];
818 if (degr == 0) {
819 continue;
820 }
821 std::string n_occur =
822 degr > 1 ? "\t\t\t\t(" + std::to_string(degr) + "x)" : "";
823
824 // Edge that leads out of the detector world
825 if (j == dim - 1 && degr != 0) {
826 out_stream << " -> leaving world " + n_occur << std::endl;
827 } else {
828 out_stream << " -> " << std::to_string(j) + "\t" + n_occur
829 << std::endl;
830 }
831 }
832 }
833
834 return out_stream.str();
835}
836
837} // namespace detray::detector_scanner
constexpr nav_link_type volume_link() const
Definition intersection.hpp:291
constexpr surface_type surface() const
Definition intersection.hpp:279
constexpr scalar_type path() const
Definition intersection.hpp:307
SVG generator for a detector and related entities.
Definition illustrator.hpp:61
void hide_passives(bool toggle=true)
Toggle passive surfaces.
Definition illustrator.hpp:126
void hide_portals(bool toggle=true)
Toggle portal surfaces.
Definition illustrator.hpp:119
void hide_eta_lines(bool toggle=true)
Toggle eta lines in detector rz-view.
Definition illustrator.hpp:98
void show_info(bool toggle=true)
Toggle info boxes.
Definition illustrator.hpp:91
Run a scan on detector object by shooting test particles through it.
Definition detector_scan_utils.hpp:39
dindex_range overlaps_removal(dvector< intersection_record< detector_t > > &intersection_records, const float tol=1e-4f *unit< float >::mm)
Filter the intersection trace for overlapping surfaces and remove duplicates.
Definition detector_scan_utils.hpp:51
auto trace_intersections(const dvector< intersection_record< detector_t > > &intersection_records, dindex start_volume=0u)
Check if a recording of portal/module intersections form a coherent trace through a geometry.
Definition detector_scan_utils.hpp:334
std::string print_adj(const dvector< dindex > &adjacency_matrix)
Print an adjacency list.
Definition detector_scan_utils.hpp:809
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
bool check_trace(const dvector< intersection_record< detector_t > > &intersection_trace, const dindex start_index, dvector< dindex > &adj_mat_scan, std::unordered_set< dindex > &obj_hashes)
Run all checks on an intersection trace.
Definition detector_scan_utils.hpp:693
bool check_connectivity(std::vector< std::pair< entry_type, entry_type > > trace, dindex start_volume=0u)
Check if a set of volume indices from portal intersections from a path (works even if the pairs are n...
Definition detector_scan_utils.hpp:195
std::string print_trace(const dvector< intersection_record< detector_t > > &truth_trace, std::size_t n)
Print an intersection trace.
Definition detector_scan_utils.hpp:782
auto build_adjacency(const std::vector< std::pair< entry_type, entry_type > > &portal_trace, const std::vector< entry_type > &module_trace, std::map< dindex, std::map< dindex, dindex > > &adj_list, std::unordered_set< dindex > &obj_hashes)
Build an adjacency list from intersection traces.
Definition detector_scan_utils.hpp:583
detail::index_range< dindex > dindex_range
Index ranges and sequences.
Definition indexing.hpp:24
vecmem::vector< value_t > dvector
Vector container backed by vecmem.
Definition containers.hpp:34
unsigned int dindex
Default index type.
Definition indexing.hpp:18
constexpr dindex dindex_invalid
Global invalid index definition.
Definition indexing.hpp:21
Record of a surface intersection along a track.
Definition data_record.hpp:27
const intersection_type & intersection() const
Get the intersection result.
Definition data_record.hpp:87
dindex volume() const
Get the containing volume index.
Definition data_record.hpp:95
Range adaptor that creates an enumerate view, optionally starting at a selected index.
Definition enumerate.hpp:266
Global styling options.
Definition styling.hpp:184
static constexpr scalar_t mm
Native length unit, the millimetre.
Definition units.hpp:27