55 constexpr auto inv_idx{detray::detail::invalid_value<dindex>()};
58 const std::size_t n_rec{intersection_records.size()};
59 std::size_t n_eq_intrs{1u};
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);
66 if (math::fabs(next_rec.intersection().path() - rec.intersection().path()) <
74 if (n_eq_intrs == 1u) {
78 if (n_eq_intrs == 2u) {
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();
87 std::stringstream err_stream;
88 err_stream <<
"The following surfaces overlap at\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;
99 DETRAY_ERROR_HOST(err_stream.str());
100 overlap_idx = {
static_cast<dindex>(i - 1u),
static_cast<dindex>(i)};
109 std::size_t first{(i - (n_eq_intrs - 1u))};
114 std::multimap<std::size_t, std::size_t> pt_buckets{};
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;
122 pt_buckets.insert({
static_cast<std::size_t
>(intr.volume_link()), j});
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) {
139 if (n_erased != 1u || pt_buckets.empty() || !is_all_portals) {
140 DETRAY_ERROR_HOST(
"Could not resolve exit portal in overlap correction");
142 overlap_idx = {
static_cast<dindex>(first),
static_cast<dindex>(last)};
146 const auto &exit_rec = intersection_records.at(exit_idx);
147 auto itr = intersection_records.cbegin();
150 for (
const auto &[v_link, idx] : pt_buckets | std::views::reverse) {
152 assert(v_link == exit_rec.volume());
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);
196 std::vector<std::pair<entry_type, entry_type>> trace,
197 dindex start_volume = 0u) {
199 std::stringstream err_stream;
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;
210 err_stream <<
"Trace empty!";
211 print_err(err_stream);
216 std::stringstream record_stream;
219 dindex current_volume = start_volume;
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) {
229 get_connected_record =
230 [&trace, ¤t_volume](index_t next) -> records_iterator_t {
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))) {
243 get_connected_record =
244 [&trace, ¤t_volume](index_t ) -> 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);
255 auto record = get_connected_record(i);
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;
262 print_err(err_stream);
268 while (record != trace.end()) {
269 auto first_vol = std::get<1>(record->first);
270 auto second_vol = std::get<1>(record->second);
272 record_stream <<
"On volume: " << current_volume <<
" and record ("
273 << first_vol <<
", " << second_vol <<
")";
276 current_volume = (current_volume == first_vol ? second_vol : first_vol);
278 record_stream <<
" -> next volume: " << current_volume << std::endl;
282 if constexpr (!check_sorted_trace) {
287 record = get_connected_record(++i);
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();
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);
302 err_stream <<
"(" << first_vol <<
", " << second_vol <<
")" << std::endl;
305 print_err(err_stream);
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};
355 inline bool is_invalid()
const {
358 inline auto surface_idx()
const {
361 inline auto surface_volume_idx()
const {
364 inline auto &inters()
const {
return entry.
intersection(); }
365 inline auto volume_idx()
const {
return entry.
volume(); }
366 inline auto volume_link()
const {
370 inline bool is_portal()
const {
373 inline bool is_sensitive()
const {
376 inline bool is_passive()
const {
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;
391 if (intersection_records.empty()) {
392 err_stream <<
"No surfaces found in detector!";
393 print_err(err_stream);
395 return std::make_tuple(portal_trace, module_trace, error_code);
399 if (intersection_records.size() == 1u) {
400 const record rec{intersection_records.at(0u)};
403 if (!rec.is_portal()) {
404 if (rec.is_invalid()) {
405 err_stream <<
"No surfaces found in detector!";
406 print_err(err_stream);
408 const std::string sf_type{rec.is_sensitive() ?
"sensitive" :
"passive"};
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!";
414 print_err(err_stream);
417 return std::make_tuple(portal_trace, module_trace, error_code);
422 dindex current_vol = start_volume;
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)};
432 record_stream << current_rec.volume_idx() <<
"\t" << current_rec.inters()
438 if (!current_rec.is_portal()) {
441 const bool is_in_volume =
442 (current_rec.volume_idx() == current_rec.surface_volume_idx()) &&
443 (current_rec.surface_volume_idx() == current_vol);
445 module_trace.emplace_back(current_rec.surface_idx(),
446 current_rec.volume_idx());
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()
453 err_stream << record_stream.str();
455 print_err(err_stream);
457 return std::make_tuple(portal_trace, module_trace, error_code);
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();
470 record_stream << next_rec.volume_idx() <<
"\t" << next_rec.inters()
475 const bool is_in_volume =
476 next_rec.volume_idx() == next_rec.surface_volume_idx();
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());
483 const bool is_self_link = current_rec.volume_idx() == next_rec.volume_idx();
485 const bool is_mixed = (current_rec.is_portal() && !next_rec.is_portal()) ||
486 (next_rec.is_portal() && !current_rec.is_portal());
489 record_stream <<
"\n(!!) Surface outside of its volume (Found: "
490 << next_rec.volume_idx()
491 <<
", belongs in: " << next_rec.surface_volume_idx() <<
")"
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!"
502 record_stream <<
"\n(!!) Found portal crossing inside volume ("
503 << current_rec.volume_idx() <<
")!" << std::endl;
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;
511 if (is_in_volume && is_valid && !is_self_link && !is_mixed) {
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);
520 err_stream <<
"\nError in portal matching:\n" << std::endl;
521 err_stream <<
"volume id\t(intersection info)" << std::endl;
523 err_stream << record_stream.str() << std::endl;
525 err_stream <<
"-----\nINFO: Ray terminated at portal x-ing "
526 << (rec + 1) / 2 <<
":\n"
527 << current_rec.inters() <<
" <-> " << next_rec.inters()
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()
542 print_err(err_stream);
544 return std::make_tuple(portal_trace, module_trace, error_code);
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);
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);
562 return std::make_tuple(portal_trace, module_trace, error_code);
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) {
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);
593 if (obj_hashes.find(sf_index) == obj_hashes.end()) {
594 adj_list[vol_index][vol_index]++;
595 obj_hashes.insert(sf_index);
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);
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);
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);
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()));
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);
650 if (obj_hashes.find(sf_index) == obj_hashes.end()) {
651 adj_matrix[dim * vol_index + vol_index]++;
652 obj_hashes.insert(sf_index);
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);
663 if (obj_hashes.find(pt_index_1) == obj_hashes.end()) {
666 if (vol_index_2 != invalid_value) {
667 mat_elem_vol1 = dim * vol_index_1 + vol_index_2;
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);
674 mat_elem_vol1 = dim * vol_index_1 + dim - 1;
676 adj_matrix[mat_elem_vol1]++;
677 obj_hashes.insert(pt_index_1);
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,
744 const std::size_t i_track, [[maybe_unused]]
const std::size_t n_tracks,
746 const dindex_range overlap_idx = {detray::detail::invalid_value<dindex>(),
747 detray::detail::invalid_value<dindex>()},
748 const bool verbose =
true) {
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) {
763 track_type =
"helix";
767 DETRAY_ERROR_HOST(
"\nFailed on " << track_type <<
": " << i_track <<
"/"
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);